linwu před 1 rokem
revize
58fd6c8eae
100 změnil soubory, kde provedl 9711 přidání a 0 odebrání
  1. 2 0
      .dockerignore
  2. 11 0
      .gitignore
  3. 2 0
      CONTRIBUTING.md
  4. 40 0
      Dockerfile
  5. 21 0
      LICENSE
  6. 82 0
      README.md
  7. 186 0
      api/activity/controller/ActivityController.php
  8. 63 0
      api/activity/controller/ActivityReviewController.php
  9. 101 0
      api/activity/controller/SiteController.php
  10. 19 0
      api/activity/model/ActivityJoinModel.php
  11. 42 0
      api/activity/model/ActivityModel.php
  12. 23 0
      api/activity/model/ActivityReviewModel.php
  13. 19 0
      api/activity/model/ActivitySiteJoinModel.php
  14. 19 0
      api/activity/model/ActivitySiteModel.php
  15. 98 0
      api/applet/controller/AppletController.php
  16. 27 0
      api/applet/model/UserModel.php
  17. 79 0
      api/business/controller/BusinessController.php
  18. 19 0
      api/business/model/BusinessJoinModel.php
  19. 19 0
      api/business/model/BusinessModel.php
  20. 350 0
      api/common/Http.php
  21. 188 0
      api/crontab/controller/DateController.php
  22. 45 0
      api/home/controller/SlidesController.php
  23. 422 0
      api/portal/controller/ArticlesController.php
  24. 81 0
      api/portal/controller/CategoriesController.php
  25. 76 0
      api/portal/controller/IndexController.php
  26. 79 0
      api/portal/controller/ListsController.php
  27. 65 0
      api/portal/controller/PagesController.php
  28. 86 0
      api/portal/controller/TagsController.php
  29. 140 0
      api/portal/controller/UserArticlesController.php
  30. 41 0
      api/portal/controller/UserController.php
  31. 324 0
      api/portal/logic/PortalPostModel.php
  32. 17 0
      api/portal/model/FeedbackModel.php
  33. 82 0
      api/portal/model/PortalCategoryModel.php
  34. 24 0
      api/portal/model/PortalCategoryPostModel.php
  35. 491 0
      api/portal/model/PortalPostModel.php
  36. 25 0
      api/portal/model/PortalTagModel.php
  37. 29 0
      api/portal/model/PortalTagPostModel.php
  38. 16 0
      api/portal/model/RecycleBinModel.php
  39. 41 0
      api/portal/model/UserModel.php
  40. 22 0
      api/portal/route.php
  41. 59 0
      api/portal/service/PortalCategoryService.php
  42. 108 0
      api/portal/service/PortalPostService.php
  43. 100 0
      api/portal/service/PortalTagService.php
  44. 34 0
      api/portal/validate/ArticlesValidate.php
  45. 80 0
      api/rob/controller/MyController.php
  46. 111 0
      api/rob/controller/ProductController.php
  47. 312 0
      api/user/controller/ProfileController.php
  48. 222 0
      api/user/controller/PublicController.php
  49. 20 0
      api/user/model/JucaiMemberModel.php
  50. 150 0
      app/activity/controller/AdminActivityController.php
  51. 96 0
      app/activity/controller/AdminActivityJoinController.php
  52. 98 0
      app/activity/controller/AdminActivityReviewController.php
  53. 60 0
      app/activity/controller/AdminActivitySiteJoinController.php
  54. 66 0
      app/activity/controller/AdminSiteController.php
  55. 23 0
      app/activity/model/ActivityJoinModel.php
  56. 39 0
      app/activity/model/ActivityModel.php
  57. 23 0
      app/activity/model/ActivityReviewModel.php
  58. 19 0
      app/activity/model/ActivitySiteModel.php
  59. 94 0
      app/activitymanage/controller/AdminActivityJoinManageController.php
  60. 63 0
      app/activitymanage/controller/AdminActivityManageController.php
  61. 36 0
      app/activitymanage/controller/AdminActivityReviewManageController.php
  62. 133 0
      app/admin/controller/MainController.php
  63. 69 0
      app/business/controller/AdminBusinessController.php
  64. 99 0
      app/business/controller/AdminBusinessJoinController.php
  65. 19 0
      app/business/model/BusinessJoinModel.php
  66. 19 0
      app/business/model/BusinessModel.php
  67. 109 0
      app/businessmanage/controller/AdminBusinessJoinManageController.php
  68. 38 0
      app/businessmanage/controller/AdminBusinessManageController.php
  69. 137 0
      app/common.php
  70. 67 0
      app/portal/api/CategoryApi.php
  71. 76 0
      app/portal/api/PageApi.php
  72. 451 0
      app/portal/controller/AdminArticleController.php
  73. 370 0
      app/portal/controller/AdminCategoryController.php
  74. 63 0
      app/portal/controller/AdminFeedbackController.php
  75. 32 0
      app/portal/controller/AdminIndexController.php
  76. 239 0
      app/portal/controller/AdminPageController.php
  77. 153 0
      app/portal/controller/AdminTagController.php
  78. 102 0
      app/portal/controller/ArticleController.php
  79. 27 0
      app/portal/controller/IndexController.php
  80. 39 0
      app/portal/controller/ListController.php
  81. 44 0
      app/portal/controller/PageController.php
  82. 32 0
      app/portal/controller/SearchController.php
  83. 47 0
      app/portal/controller/TagController.php
  84. 96 0
      app/portal/hooks.php
  85. 13 0
      app/portal/lang/en-us.php
  86. 15 0
      app/portal/lang/en-us/common.php
  87. 21 0
      app/portal/lang/zh-cn.php
  88. 16 0
      app/portal/lang/zh-cn/common.php
  89. 13 0
      app/portal/lang/zh-cn/home.php
  90. 19 0
      app/portal/model/FeedbackModel.php
  91. 217 0
      app/portal/model/PortalCategoryModel.php
  92. 363 0
      app/portal/model/PortalPostModel.php
  93. 21 0
      app/portal/model/PortalTagModel.php
  94. 23 0
      app/portal/model/UserModel.php
  95. 14 0
      app/portal/nav.php
  96. 459 0
      app/portal/service/ApiService.php
  97. 287 0
      app/portal/service/PostService.php
  98. 372 0
      app/portal/taglib/Portal.php
  99. 53 0
      app/portal/url.php
  100. 15 0
      app/portal/user_action.php

+ 2 - 0
.dockerignore

@@ -0,0 +1,2 @@
+# Created by .ignore support plugin (hsz.mobi)
+.git*

+ 11 - 0
.gitignore

@@ -0,0 +1,11 @@
+.buildpath
+.DS_Store
+.project
+.settings
+.idea
+.git
+/build
+/public/assets/dist
+/node_modules
+Vagrantfile
+.vagrant

+ 2 - 0
CONTRIBUTING.md

@@ -0,0 +1,2 @@
+如何贡献我的源代码
+===

+ 40 - 0
Dockerfile

@@ -0,0 +1,40 @@
+FROM php:7.1-apache
+
+MAINTAINER jayknoxqu@gmail.com
+
+#设置容器时区
+ENV TZ Asia/Shanghai
+
+#设置程序入口
+ENV APACHE_DOCUMENT_ROOT /var/www/html/public
+
+RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone \
+    && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
+    && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
+    #添加阿里云的镜像源
+    && mv /etc/apt/sources.list /etc/apt/sources.list.bak \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch main non-free contrib" >/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian-security stretch/updates main" >>/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch-updates main non-free contrib" >>/etc/apt/sources.list \
+    && echo "deb http://mirrors.aliyun.com/debian/ stretch-backports main non-free contrib" >>/etc/apt/sources.list \
+    #安装程序依赖库
+    && apt-get update && apt-get install -y \
+              libfreetype6-dev \
+              libjpeg62-turbo-dev \
+              libpng-dev \
+    #安装 PHP 依赖
+    && docker-php-ext-install pdo_mysql \
+    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
+    && docker-php-ext-install -j$(nproc) gd \
+    #删除包缓存中的所有包
+    && apt-get clean \
+    && apt-get autoclean \
+    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+#复制代码到PHP容器
+COPY . /var/www/html
+
+# 开启URL重写 并且 添加目录权限
+RUN a2enmod rewrite \
+    && chmod -R 0755 /var/www/html \
+    && chown -R www-data:www-data /var/www/html

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2013-present ThinkCMF (https://www.thinkcmf.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 82 - 0
README.md

@@ -0,0 +1,82 @@
+晋爱人才小程序
+===============
+
+
+### 开发手册
+参考http://www.kancloud.cn/thinkcmf/doc5_1
+
+### 环境推荐
+> php7.1
+
+> mysql 5.6+
+
+> 打开rewrite
+
+
+### 最低环境要求
+> php5.6+
+
+> mysql 5.5+ (mysql5.1安装时选择utf8编码,不支持表情符)
+
+> 打开rewrite
+
+
+### 运行环境配置教程
+https://www.thinkcmf.com/topic/1502.html
+
+1. public目录做为网站根目录,入口文件在 public/index.php
+2. 配置好网站,请访问http://你的域名
+3. sql文件放在database目录下
+4. 网站的配置在data/config
+
+### 完整版目录结构
+~~~
+thinkcmf  根目录
+├─api                     api目录
+│  ├─demo                 演示应用api目录
+│  │  ├─controller        控制器目录
+│  │  ├─model             模型目录
+│  │  └─ ...              更多类库目录
+├─app                     应用目录
+│  ├─demo                 演示应用目录
+│  │  ├─controller        控制器目录
+│  │  ├─model             模型目录
+│  │  └─ ...              更多类库目录
+│  ├─ ...                 更多应用
+│  ├─app.php              应用(公共)配置文件[可选]
+│  ├─command.php          命令行工具配置文件[可选]
+│  ├─common.php           应用公共(函数)文件[可选]
+│  ├─database.php         数据库配置文件[可选]
+│  ├─tags.php             应用行为扩展定义文件[可选]
+├─data                    数据目录(可写)
+│  ├─config               动态配置目录(可写)
+│  ├─route                动态路由目录(可写)
+│  ├─runtime              应用的运行时目录(可写)
+│  └─ ...                 更多
+├─public                  WEB 部署目录(对外访问目录)
+│  ├─plugins              插件目录
+│  ├─static               官方静态资源存放目录(css,js,image),勿放自己项目文件
+│  ├─themes               前后台主题目录
+│  │  ├─admin_simpleboot3 后台默认主题
+│  │  └─default           前台默认主题
+│  ├─upload               文件上传目录
+│  ├─api.php              API入口
+│  ├─index.php            入口文件
+│  ├─robots.txt           爬虫协议文件
+│  ├─router.php           快速测试文件
+│  └─.htaccess            apache重写文件
+├─extend                  扩展类库目录[可选]
+├─vendor                  第三方类库目录(Composer)
+│  ├─thinkphp             ThinkPHP目录
+│  └─...             
+├─composer.json           composer 定义文件
+├─LICENSE                 授权说明文件
+├─README.md               README 文件
+├─think                   命令行入口文件
+~~~
+
+### 小程序
+> 小程序在applet目录下,直接使用微信开发者工具导入即可使用
+
+
+

+ 186 - 0
api/activity/controller/ActivityController.php

@@ -0,0 +1,186 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\activity\controller;
+
+use api\activity\model\ActivityJoinModel;
+use api\activity\model\ActivityModel;
+use api\activity\model\ActivitySiteJoinModel;
+use api\applet\model\UserModel;
+use cmf\controller\RestBaseController;
+
+class ActivityController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [['status', '=', 2]];
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        if (!empty($param['user_id'])) {
+            $where[] = ['user_id', '=', $param['user_id']];
+        }
+        $list = ActivityModel::where($where)->order('create_time desc')->page($page, $size)->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['main_image']  = cmf_get_image_preview_url($v['main_image']);
+                $v['status_text'] = ActivityModel::statusText($v);
+                $v['start_time']  = date('Y-m-d H:i', $v['start_time']);
+                $v['end_time']    = date('Y-m-d H:i', $v['end_time']);
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id   = $this->request->post('id');
+        $info = ActivityModel::get($id, ['review']);
+
+        $info['main_image']  = cmf_get_image_preview_url($info['main_image']);
+        $info['status_text'] = ActivityModel::statusText($info);
+        $info['start_time']  = date('Y-m-d H:i', $info['start_time']);
+        $info['end_time']    = date('Y-m-d H:i', $info['end_time']);
+        $info['create_time'] = date('Y-m-d H:i', $info['create_time']);
+        if (!empty($info['options'])) {
+            $options = [];
+            foreach ($info['options'] as $v) {
+                $options[] = [
+                    'url'  => cmf_get_file_download_url($v['url']),
+                    'name' => $v['name'],
+                ];
+            }
+            $info['options'] = $options;
+        }
+
+        $this->success('成功', $info);
+    }
+
+    /**
+     * 报名列表
+     */
+    public function joinList()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        $user_id     = $this->getUserId();
+        $activityIds = ActivityJoinModel::where('user_id', '=', $user_id)
+            ->order('create_time desc')
+            ->page($page, $size)
+            ->column('activity_id');
+
+        $list = ActivityModel::where('id', 'in', $activityIds)->order('create_time desc')->select();
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['main_image']  = cmf_get_image_preview_url($v['main_image']);
+                $v['status_text'] = ActivityModel::statusText($v);
+                $v['start_time']  = date('Y-m-d H:i', $v['start_time']);
+                $v['end_time']    = date('Y-m-d H:i', $v['end_time']);
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 报名
+     */
+    public function join()
+    {
+        $activity_id = $this->request->param('id');
+        $user_id     = $this->getUserId();
+        $user        = UserModel::get($user_id);
+        if (empty($user['user_name']) || empty($user['mobile'])) {
+            $this->success('', ['code' => 1001, 'msg' => '请完善信息']);
+        }
+
+        $check = ActivityJoinModel::where('user_id', '=', $user_id)->where('activity_id', '=', $activity_id)->find();
+        if (empty($check)) {
+            //活动报名
+            ActivityJoinModel::create([
+                'user_id'     => $user_id,
+                'activity_id' => $activity_id,
+                'create_time' => time(),
+            ]);
+            $site_id = ActivityModel::where('id', $activity_id)->value('user_id');
+            //站点报名记录
+            $site_check = ActivitySiteJoinModel::where('user_id', '=', $user_id)->where('site_id', '=', $site_id)->find();
+            if (empty($site_check)) {
+                ActivitySiteJoinModel::create([
+                    'user_id'     => $user_id,
+                    'site_id'     => $site_id,
+                    'create_time' => time(),
+                ]);
+            }
+            $this->success('报名成功', ['msg' => '报名成功']);
+        } else {
+            $this->success('请勿重复报名', ['msg' => '请勿重复报名']);
+        }
+    }
+
+    /**
+     * 签到
+     */
+    public function signin()
+    {
+        $activity_id = $this->request->param('activity_id');
+        $code        = $this->request->param('code');
+
+        //查找记录
+        $activity = ActivityModel::get(['id' => $activity_id, 'signin_code' => $code]);
+        if (empty($activity)) {
+            $this->error('二维码有误!请重新确认');
+        }
+
+        //签到
+        $user_id = $this->getUserId();
+        $join    = ActivityJoinModel::get(['user_id' => $user_id, 'activity_id' => $activity_id]);
+        if (empty($join)) {
+            $this->error('请先报名该活动!');
+        }
+        if ($join['status'] == 1) {
+            $this->error('请勿重复签到!');
+        }
+
+        //增加积分
+        if ($activity->score > 0) {
+            $model = new \app\user\model\UserModel();
+            $res = $model->changeScore($activity->score, "活动签到:{$activity->title}");
+            if ($res['code'] == 0) {
+                $this->error($res['msg']);
+            }
+        }
+
+        //更改签到状态
+        $join->status = 1;
+        $join->save();
+
+        $this->success('签到成功', ['msg' => '签到成功']);
+    }
+}

+ 63 - 0
api/activity/controller/ActivityReviewController.php

@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\activity\controller;
+
+use api\activity\model\ActivityReviewModel;
+use cmf\controller\RestBaseController;
+
+class ActivityReviewController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $list = ActivityReviewModel::with(['activity'])->where($where)->order('create_time', 'DESC')->page($page, $size)->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['main_image']    = cmf_get_image_preview_url($v['main_image']);
+                $v['activity_time'] = date('Y-m-d H:i', $v['activity']['start_time']);
+                $v['address']       = $v['activity']['address'];
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id   = $this->request->post('id');
+        $info = ActivityReviewModel::get($id, ['activity']);
+
+        $info['main_image']             = cmf_get_image_preview_url($info['main_image']);
+        $info['activity']['start_time'] = date('Y-m-d H:i', $info['activity']['start_time']);
+        $info['activity']['end_time']   = date('Y-m-d H:i', $info['activity']['end_time']);
+
+        $this->success('成功', $info);
+    }
+
+}

+ 101 - 0
api/activity/controller/SiteController.php

@@ -0,0 +1,101 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\activity\controller;
+
+use api\activity\model\ActivityModel;
+use api\activity\model\ActivitySiteModel;
+use cmf\controller\RestBaseController;
+
+class SiteController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['keyword'])) {
+            $where[] = ['site_name', 'like', "%{$param['keyword']}%"];
+        }
+        $list = ActivitySiteModel::where($where)->order('create_time', 'DESC')->page($page, $size)->select();
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 所有列表
+     */
+    public function all()
+    {
+        $where   = [];
+        $is_map  = $this->request->param('is_map', 0);
+        $keyword = $this->request->param('keyword', '');
+        if (!empty($is_map)) {
+            $where[] = ['latitude', '>', 0];
+        }
+        if (!empty($keyword)) {
+            $where[] = ['site_name', 'like', "%{$keyword}%"];
+        }
+        $list = ActivitySiteModel::order('list_order', 'ASC')->where($where)->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['main_image'] = cmf_get_image_preview_url($v['main_image']);
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id                 = $this->request->post('id');
+        $info               = ActivitySiteModel::get($id);
+        $info['main_image'] = cmf_get_image_preview_url($info['main_image']);
+
+        $this->success('成功', $info);
+    }
+
+    /**
+     * 统计
+     */
+    public function getCount()
+    {
+        $id = $this->request->param('id');
+
+        $total = ActivityModel::where('user_id', $id)->count();
+
+        $month_start = strtotime(date('Y-m-01'));
+        $month_end   = strtotime(date('Y-m-01') . ' +1 month') - 1;
+        $month_total = ActivityModel::where('user_id', $id)->where('create_time', 'between', [$month_start, $month_end])->count();
+
+        $year_start = strtotime(date('Y-01-01'));
+        $year_end   = strtotime(date('Y-01-01') . ' +1 year') - 1;
+        $year_total = ActivityModel::where('user_id', $id)->where('create_time', 'between', [$year_start, $year_end])->count();
+
+        $this->success('成功', [
+            'total'       => $total,
+            'month_total' => $month_total,
+            'year_total'  => $year_total,
+        ]);
+    }
+}

+ 19 - 0
api/activity/model/ActivityJoinModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\activity\model;
+
+use think\Model;
+
+class ActivityJoinModel extends Model
+{
+
+
+}

+ 42 - 0
api/activity/model/ActivityModel.php

@@ -0,0 +1,42 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\activity\model;
+
+use think\Model;
+use think\model\concern\SoftDelete;
+
+class ActivityModel extends Model
+{
+    use SoftDelete;
+    protected $type = ['options' => 'array'];
+
+    public function review()
+    {
+        return $this->hasOne(ActivityReviewModel::class,'activity_id','id');
+    }
+
+    /**
+     * 状态名
+     */
+    static public function statusText($value)
+    {
+        $time = time();
+        if ($value['start_time'] > $time) {
+            return '未开始';
+        }
+        if ($value['start_time'] < $time && $value['end_time'] > $time) {
+            return '进行中';
+        }
+        if ($value['end_time'] < $time) {
+            return '已结束';
+        }
+    }
+}

+ 23 - 0
api/activity/model/ActivityReviewModel.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\activity\model;
+
+use think\Model;
+
+class ActivityReviewModel extends Model
+{
+
+    public function activity()
+    {
+        return $this->hasOne(ActivityModel::class,'id','activity_id');
+    }
+
+}

+ 19 - 0
api/activity/model/ActivitySiteJoinModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\activity\model;
+
+use think\Model;
+
+class ActivitySiteJoinModel extends Model
+{
+
+
+}

+ 19 - 0
api/activity/model/ActivitySiteModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\activity\model;
+
+use think\Model;
+
+class ActivitySiteModel extends Model
+{
+
+
+}

+ 98 - 0
api/applet/controller/AppletController.php

@@ -0,0 +1,98 @@
+<?php
+namespace api\applet\controller;
+
+use api\applet\model\UserModel;
+use api\common\Http;
+use cmf\controller\RestBaseController;
+use think\Db;
+
+class AppletController extends RestBaseController
+{
+    const TOKEN_URL       = 'https://api.weixin.qq.com/cgi-bin/token';  //get
+    const APPLET_CODE_URL = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit'; //post
+    const CODE_URL        = 'https://api.weixin.qq.com/sns/jscode2session'; //post
+
+    /**
+     * 根据code登录
+     * @param $code
+     */
+    public function login()
+    {
+        $wxappSettings = cmf_get_option('wxapp_settings')['default'];
+
+        //获取open_id
+        $code   = $this->request->post('code');
+        $url    = self::CODE_URL . '?appid=' . $wxappSettings['app_id'] . '&secret=' . $wxappSettings['app_secret'] . '&js_code=' . $code . '&grant_type=authorization_code';
+        $header = ['content-type: application/json'];
+        $result = Http::http_post_json($url, '', $header);
+        if (!empty($result['errcode'])) {
+            $this->error($result['errmsg']);
+        }
+
+        //获取token
+        $user = UserModel::where('open_id', $result['openid'])->find();
+        if (empty($user)) {
+            $user = UserModel::create([
+                'user_pass' => cmf_password('123456'),
+                'open_id'     => $result['openid'],
+                'union_id'     => $result['unionid'],
+                'user_type'   => 2,
+                'create_time' => time(),
+            ]);
+        }
+        if (empty($user['unionid'])) {
+            $user->union_id = $result['unionid'];
+            $user->save();
+        }
+
+        //写入token
+        $token       = md5(uniqid()) . md5(uniqid());
+        $currentTime = time();
+        $expireTime  = $currentTime + 24 * 3600 * 180;
+        Db::name("user_token")->insert([
+            'token'       => $token,
+            'user_id'     => $user['id'],
+            'expire_time' => $expireTime,
+            'create_time' => $currentTime,
+            'device_type' => $this->deviceType,
+        ]);
+
+        $this->success('OK',['token' => $token, 'expires_time' => $expireTime]);
+    }
+
+    public function test()
+    {
+        /*$url = 'https://api.weixin.qq.com/cgi-bin/openapi/rid/get?access_token={$token}';
+        $data = '{"rid":1}';
+        $header = ['content-type: application/json'];
+        $result = Http::http_post_json($url, $data, $header);
+        halt($result);*/
+
+        $token = $this->_getAccessToken();
+        $url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={$token}";
+        $data = [
+            'touser' => 'oQeNu5TLNQBgPOKBoH4wK8VyAHMQ',
+            'msgtype' => 'text',
+            'text' => ['content'=>'Hello World']
+        ];
+        $header = ['content-type: application/json'];
+        $result = Http::http_post_json($url, json_encode($data), $header);
+        halt($result);
+    }
+
+    public function _getAccessToken()
+    {
+        $time = time();
+        $wx_access_token = cmf_get_option('wx_access_token');
+        if (empty($wx_access_token) || $wx_access_token['expires_time'] < $time) {
+            $wxappSettings = cmf_get_option('wxapp_settings')['default'];
+            $url    = self::TOKEN_URL . '?appid=' . $wxappSettings['app_id'] . '&secret=' . $wxappSettings['app_secret'] .'&grant_type=client_credential';
+            $header = ['content-type: application/json'];
+            $wx_access_token = Http::http_post_json($url, '', $header);
+            $wx_access_token['expires_time'] = $time + $wx_access_token['expires_in'];
+            cmf_set_option('wx_access_token',$wx_access_token);
+        }
+
+        return $wx_access_token['access_token'];
+    }
+}

+ 27 - 0
api/applet/model/UserModel.php

@@ -0,0 +1,27 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\applet\model;
+
+
+use think\Model;
+
+class UserModel extends Model
+{
+    /**
+     * more 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getAvatarAttr($value)
+    {
+        $value = !empty($value) ? cmf_get_image_url($value) : $value;
+        return $value;
+    }
+}

+ 79 - 0
api/business/controller/BusinessController.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace api\business\controller;
+
+use api\applet\model\UserModel;
+use api\business\model\BusinessJoinModel;
+use api\business\model\BusinessModel;
+use cmf\controller\RestBaseController;
+
+class BusinessController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [
+            ['is_show','=',1]
+        ];
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $list = BusinessModel::where($where)->page($page, $size)->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['main_image'] = cmf_get_image_preview_url($v['main_image']);
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id   = $this->request->post('id');
+        $info = BusinessModel::get($id);
+
+        //信息处理
+        if (!empty($info['main_image'])) {
+            $info['main_image'] = cmf_get_image_preview_url($info['main_image']);
+        }
+        if (!empty($info['tags'])) {
+            $info['tags'] = json_decode($info['tags'], true);
+        } else {
+            $info['tags'] = [];
+        }
+
+        $this->success('成功', $info);
+    }
+
+    /**
+     * 参加活动
+     */
+    public function join()
+    {
+        $user_id     = $this->getUserId();
+        $user        = UserModel::get($user_id);
+        if (empty($user['user_name']) || empty($user['mobile'])) {
+            $this->success('', ['code' => 1001, 'msg' => '请完善信息']);
+        }
+
+        $param                = $this->request->post();
+        $param['user_id']     = $user_id;
+        $param['create_time'] = time();
+
+        BusinessJoinModel::create($param);
+        $this->success('提交成功');
+    }
+}

+ 19 - 0
api/business/model/BusinessJoinModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\business\model;
+
+use think\Model;
+
+class BusinessJoinModel extends Model
+{
+
+
+}

+ 19 - 0
api/business/model/BusinessModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\business\model;
+
+use think\Model;
+
+class BusinessModel extends Model
+{
+
+
+}

+ 350 - 0
api/common/Http.php

@@ -0,0 +1,350 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2009 http://thinkphp.cn All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: liu21st <liu21st@gmail.com>
+// +----------------------------------------------------------------------
+namespace api\common;
+
+/**
+ * Http 工具类
+ * 提供一系列的Http方法
+ * @author    liu21st <liu21st@gmail.com>
+ */
+class Http {
+
+    /**
+     * 采集远程文件
+     * @access public
+     * @param string $remote 远程文件名
+     * @param string $local 本地保存文件名
+     * @return mixed
+     */
+    static public function curlDownload($remote,$local) {
+        $cp = curl_init($remote);
+        $fp = fopen($local,"w");
+        curl_setopt($cp, CURLOPT_FILE, $fp);
+        curl_setopt($cp, CURLOPT_HEADER, 0);
+        curl_exec($cp);
+        curl_close($cp);
+        fclose($fp);
+    }
+
+   /**
+    * 使用 fsockopen 通过 HTTP 协议直接访问(采集)远程文件
+    * 如果主机或服务器没有开启 CURL 扩展可考虑使用
+    * fsockopen 比 CURL 稍慢,但性能稳定
+    * @static
+    * @access public
+    * @param string $url 远程URL
+    * @param array $conf 其他配置信息
+    *        int   limit 分段读取字符个数
+    *        string post  post的内容,字符串或数组,key=value&形式
+    *        string cookie 携带cookie访问,该参数是cookie内容
+    *        string ip    如果该参数传入,$url将不被使用,ip访问优先
+    *        int    timeout 采集超时时间
+    *        bool   block 是否阻塞访问,默认为true
+    * @return mixed
+    */
+    static public function fsockopenDownload($url, $conf = array()) {
+        $return = '';
+        if(!is_array($conf)) return $return;
+
+        $matches = parse_url($url);
+        !isset($matches['host']) 	&& $matches['host'] 	= '';
+        !isset($matches['path']) 	&& $matches['path'] 	= '';
+        !isset($matches['query']) 	&& $matches['query'] 	= '';
+        !isset($matches['port']) 	&& $matches['port'] 	= '';
+        $host = $matches['host'];
+        $path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
+        $port = !empty($matches['port']) ? $matches['port'] : 80;
+
+        $conf_arr = array(
+            'limit'		=>	0,
+            'post'		=>	'',
+            'cookie'	=>	'',
+            'ip'		=>	'',
+            'timeout'	=>	15,
+            'block'		=>	TRUE,
+            );
+
+        foreach (array_merge($conf_arr, $conf) as $k=>$v) ${$k} = $v;
+
+        if($post) {
+            if(is_array($post))
+            {
+                $post = http_build_query($post);
+            }
+            $out  = "POST $path HTTP/1.0\r\n";
+            $out .= "Accept: */*\r\n";
+            //$out .= "Referer: $boardurl\r\n";
+            $out .= "Accept-Language: zh-cn\r\n";
+            $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
+            $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
+            $out .= "Host: $host\r\n";
+            $out .= 'Content-Length: '.strlen($post)."\r\n";
+            $out .= "Connection: Close\r\n";
+            $out .= "Cache-Control: no-cache\r\n";
+            $out .= "Cookie: $cookie\r\n\r\n";
+            $out .= $post;
+        } else {
+            $out  = "GET $path HTTP/1.0\r\n";
+            $out .= "Accept: */*\r\n";
+            //$out .= "Referer: $boardurl\r\n";
+            $out .= "Accept-Language: zh-cn\r\n";
+            $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
+            $out .= "Host: $host\r\n";
+            $out .= "Connection: Close\r\n";
+            $out .= "Cookie: $cookie\r\n\r\n";
+        }
+        $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
+        if(!$fp) {
+            return '';
+        } else {
+            stream_set_blocking($fp, $block);
+            stream_set_timeout($fp, $timeout);
+            @fwrite($fp, $out);
+            $status = stream_get_meta_data($fp);
+            if(!$status['timed_out']) {
+                while (!feof($fp)) {
+                    if(($header = @fgets($fp)) && ($header == "\r\n" ||  $header == "\n")) {
+                        break;
+                    }
+                }
+
+                $stop = false;
+                while(!feof($fp) && !$stop) {
+                    $data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
+                    $return .= $data;
+                    if($limit) {
+                        $limit -= strlen($data);
+                        $stop = $limit <= 0;
+                    }
+                }
+            }
+            @fclose($fp);
+            return $return;
+        }
+    }
+
+    /**
+     * 下载文件
+     * 可以指定下载显示的文件名,并自动发送相应的Header信息
+     * 如果指定了content参数,则下载该参数的内容
+     * @static
+     * @access public
+     * @param string $filename 下载文件名
+     * @param string $showname 下载显示的文件名
+     * @param string $content  下载的内容
+     * @param integer $expire  下载内容浏览器缓存时间
+     * @return void
+     */
+    static public function download ($filename, $showname='',$content='',$expire=180) {
+        if(is_file($filename)) {
+            $length = filesize($filename);
+        }elseif(is_file(UPLOAD_PATH.$filename)) {
+            $filename = UPLOAD_PATH.$filename;
+            $length = filesize($filename);
+        }elseif($content != '') {
+            $length = strlen($content);
+        }else {
+            E($filename.L('下载文件不存在!'));
+        }
+        if(empty($showname)) {
+            $showname = $filename;
+        }
+        $showname = basename($showname);
+		if(!empty($filename)) {
+			$finfo 	= 	new \finfo(FILEINFO_MIME);
+			$type 	= 	$finfo->file($filename);			
+		}else{
+			$type	=	"application/octet-stream";
+		}
+        //发送Http Header信息 开始下载
+        header("Pragma: public");
+        header("Cache-control: max-age=".$expire);
+        //header('Cache-Control: no-store, no-cache, must-revalidate');
+        header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT");
+        header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT");
+        header("Content-Disposition: attachment; filename=".$showname);
+        header("Content-Length: ".$length);
+        header("Content-type: ".$type);
+        header('Content-Encoding: none');
+        header("Content-Transfer-Encoding: binary" );
+        if($content == '' ) {
+            readfile($filename);
+        }else {
+        	echo($content);
+        }
+        exit();
+    }
+
+    /**
+     * 显示HTTP Header 信息
+     * @return string
+     */
+    static function getHeaderInfo($header='',$echo=true) {
+        ob_start();
+        $headers   	= getallheaders();
+        if(!empty($header)) {
+            $info 	= $headers[$header];
+            echo($header.':'.$info."\n"); ;
+        }else {
+            foreach($headers as $key=>$val) {
+                echo("$key:$val\n");
+            }
+        }
+        $output 	= ob_get_clean();
+        if ($echo) {
+            echo (nl2br($output));
+        }else {
+            return $output;
+        }
+
+    }
+
+    /**
+     * HTTP Protocol defined status codes
+     * @param int $num
+     */
+	static function sendHttpStatus($code) {
+		static $_status = array(
+			// Informational 1xx
+			100 => 'Continue',
+			101 => 'Switching Protocols',
+
+			// Success 2xx
+			200 => 'OK',
+			201 => 'Created',
+			202 => 'Accepted',
+			203 => 'Non-Authoritative Information',
+			204 => 'No Content',
+			205 => 'Reset Content',
+			206 => 'Partial Content',
+
+			// Redirection 3xx
+			300 => 'Multiple Choices',
+			301 => 'Moved Permanently',
+			302 => 'Found',  // 1.1
+			303 => 'See Other',
+			304 => 'Not Modified',
+			305 => 'Use Proxy',
+			// 306 is deprecated but reserved
+			307 => 'Temporary Redirect',
+
+			// Client Error 4xx
+			400 => 'Bad Request',
+			401 => 'Unauthorized',
+			402 => 'Payment Required',
+			403 => 'Forbidden',
+			404 => 'Not Found',
+			405 => 'Method Not Allowed',
+			406 => 'Not Acceptable',
+			407 => 'Proxy Authentication Required',
+			408 => 'Request Timeout',
+			409 => 'Conflict',
+			410 => 'Gone',
+			411 => 'Length Required',
+			412 => 'Precondition Failed',
+			413 => 'Request Entity Too Large',
+			414 => 'Request-URI Too Long',
+			415 => 'Unsupported Media Type',
+			416 => 'Requested Range Not Satisfiable',
+			417 => 'Expectation Failed',
+
+			// Server Error 5xx
+			500 => 'Internal Server Error',
+			501 => 'Not Implemented',
+			502 => 'Bad Gateway',
+			503 => 'Service Unavailable',
+			504 => 'Gateway Timeout',
+			505 => 'HTTP Version Not Supported',
+			509 => 'Bandwidth Limit Exceeded'
+		);
+		if(isset($_status[$code])) {
+			header('HTTP/1.1 '.$code.' '.$_status[$code]);
+		}
+	}
+
+
+    /**
+     * GET 请求
+     * @param string $url
+     */
+    public static function http_get($url){
+        $oCurl = curl_init();
+        if(stripos($url,"https://")!==FALSE){
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+        }
+        curl_setopt($oCurl, CURLOPT_URL, $url);
+        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
+        $sContent = curl_exec($oCurl);
+        $aStatus = curl_getinfo($oCurl);
+        curl_close($oCurl);
+        if(intval($aStatus["http_code"])==200){
+            return $sContent;
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * POST 请求
+     * @param string $url
+     * @param array|string $param
+     * @param boolean $post_file 是否文件上传
+     * @param array
+     * @return string content
+     */
+    public static function http_post($url,$param,$header=null){
+        $oCurl = curl_init();
+        if(stripos($url,"https://")!==FALSE){
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
+            curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
+            curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
+        }
+        if (is_string($param)) {
+            $strPOST = $param;
+        } else {
+            $aPOST = array();
+            foreach($param as $key=>$val){
+                $aPOST[] = $key."=".urlencode($val);
+            }
+            $strPOST =  join("&", $aPOST);
+        }
+        curl_setopt($oCurl, CURLOPT_URL, $url);
+        curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
+        curl_setopt($oCurl, CURLOPT_POST,true);
+        curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
+        if (!empty($header)) {
+            curl_setopt($oCurl, CURLOPT_HTTPHEADER, $header);
+        }
+        $sContent = curl_exec($oCurl);
+        $aStatus = curl_getinfo($oCurl);
+        curl_close($oCurl);
+        if(intval($aStatus["http_code"])==200){
+            return $sContent;
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * POST 请求 Json
+     * @param $url
+     * @param $param
+     */
+    public static function http_post_json($url ,$param,$header) {
+        $result =  Http::http_post($url,$param,$header);
+        if ($result === false) {
+            die("Http 错误");
+        }
+        return json_decode($result,true);
+    }
+}//类定义结束

+ 188 - 0
api/crontab/controller/DateController.php

@@ -0,0 +1,188 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\crontab\controller;
+
+use api\activity\model\ActivityModel;
+use api\common\Http;
+use api\portal\model\PortalPostModel;
+use api\user\model\UserModel;
+use think\Db;
+
+class DateController
+{
+    /**
+     * 主方法
+     */
+    public function index()
+    {
+        $this->_colNews();
+        $this->_colActive();
+    }
+
+    /**
+     * 新闻采集
+     */
+    public function _colNews()
+    {
+        $page       = 0;
+        $detail_url = "https://www.qzrcgw.gov.cn/api/app/cms/zc/tbCmsZc/queryById?id=";
+        $this->_colNewsChild($page, $detail_url, '晋江');
+        $this->_colNewsChild($page, $detail_url, '泉州市');
+    }
+
+    /**
+     * 新闻列表和详情采集
+     */
+    private function _colNewsChild($page, $detail_url, $title)
+    {
+        while (true) {
+            $page++;
+            $title    = urlencode($title);
+            $list_url = "https://www.qzrcgw.gov.cn/api/app/cms/zc/tbCmsZc/list?pageNo={$page}&pageSize=20&title={$title}";
+            $contents = json_decode(Http::http_get($list_url), true);
+            if ($contents['code'] == 200) {
+                $list      = $contents['page']['list'];
+                $ids       = array_column($list, 'id');
+                $third_ids = PortalPostModel::where('third_id', 'in', $ids)->column('third_id');
+                $is_end    = true;
+                foreach ($list as $v) {
+                    //查重
+                    if (in_array($v['id'], $third_ids)) {
+                        continue;
+                    } else {
+                        $is_end = false;
+                    }
+
+                    //获取详情
+                    $detail_url_true = $detail_url . $v['id'];
+                    $detail          = json_decode(file_get_contents($detail_url_true), true);
+                    if ($detail['code'] != 200) {
+                        break;
+                    }
+
+                    //数据处理
+                    $detail_data = $detail['data'];
+                    $create      = [
+                        'third_id'       => $detail_data['id'],
+                        'user_id'        => 1,
+                        'create_time'    => strtotime($detail_data['createDate']),
+                        'update_time'    => strtotime($detail_data['updateDate']),
+                        'published_time' => strtotime($detail_data['createDate']),
+                        'post_title'     => $detail_data['title'],
+                        'post_source'    => $detail_data['source'],
+                        'post_content'   => $detail_data['cont'],
+                        'more'           => '{"thumbnail":""}',
+                    ];
+
+                    Db::name('portal_post')->insert($create);
+                }
+
+                if ($is_end) {
+                    break;
+                }
+
+                if (count($list) < 20) {
+                    break;
+                }
+            }
+        }
+    }
+
+    //站点活动采集
+    public function _colActive()
+    {
+        $page       = 0;
+        $detail_url = "https://www.qzrcgw.gov.cn//api/app/cms/rczj/tbCmsRczjHd/queryById?id=";
+        $title      = urlencode('晋江');
+        while (true) {
+            $page++;
+            $list_url = "https://www.qzrcgw.gov.cn/api/app/cms/rczj/tbCmsRczjHd/list?pageNo={$page}&pageSize=20&title={$title}";
+            $contents = json_decode(Http::http_get($list_url), true);
+            if ($contents['code'] == 200) {
+                $list      = $contents['page']['list'];
+                $ids       = array_column($list, 'id');
+                $third_ids = ActivityModel::where('third_id', 'in', $ids)->column('third_id');
+                $is_end    = true;
+
+                //获取用户id
+                $user_ids = [];
+                foreach ($list as $v) {
+                    $user_ids[] = $v['rczj']['id'];
+                }
+                $user_list = UserModel::where('third_id', 'in', array_unique($user_ids))->column('id', 'third_id');
+
+                foreach ($list as $v) {
+                    //查重
+                    if (in_array($v['id'], $third_ids)) {
+                        continue;
+                    } else {
+                        $is_end = false;
+                    }
+
+                    if (empty($user_list[$v['rczj']['id']])) {
+                        continue;
+                    }
+
+                    //获取详情
+                    $detail_url_true = $detail_url . $v['id'];
+                    $detail          = json_decode(file_get_contents($detail_url_true), true);
+                    if ($detail['code'] != 200) {
+                        break;
+                    }
+
+                    //数据处理
+                    $detail_data = $detail['data'];
+                    $main_image = $v['urls'];
+                    if (strpos($main_image,'|') !== false) {
+                        $main_image = substr($main_image,0,strpos($main_image,'|'));
+                    }
+                    $create      = [
+                        'third_id'    => $detail_data['id'],
+                        'title'       => $detail_data['title'],
+                        'main_image'  => 'https://www.qzrcgw.gov.cn' . $main_image,
+                        'start_time'  => strtotime($v['kssj']),
+                        'end_time'    => strtotime($v['jssj']),
+                        'address'     => $detail_data['address'],
+                        'user_id'     => $user_list[$v['rczj']['id']],
+                        'content'     => $detail_data['cont'],
+                        'create_time' => strtotime($v['createDate']),
+                        'status'      => 2,
+                        'options'     => '[]',
+                        'signin_code' => $this->_randomStr(16),
+                    ];
+
+                    Db::name('activity')->insert($create);
+                }
+
+                if ($is_end) {
+                    break;
+                }
+
+                if (count($list) < 20) {
+                    break;
+                }
+            }
+        }
+    }
+
+    private function _randomStr($length)
+    {
+        // 密码字符集,可任意添加你需要的字符
+        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+        $str   = '';
+        for ($i = 0; $i < $length; $i++) {
+            $str .= $chars[mt_rand(0, strlen($chars) - 1)];
+        }
+        return $str;
+    }
+}

+ 45 - 0
api/home/controller/SlidesController.php

@@ -0,0 +1,45 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\home\controller;
+
+use api\home\service\SlideService;
+use cmf\controller\RestBaseController;
+
+class SlidesController extends RestBaseController
+{
+    /**
+     * 获取幻灯片
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read()
+    {
+        //slide为空或不存在抛出异常
+        $id = $this->request->param('id', 0, 'intval');
+        if (empty($id)) {
+            $this->error('缺少ID参数');
+        }
+
+        $map['id']    = $id;
+        $slideService = new SlideService();
+        $data         = $slideService->SlideList($map);
+        //剔除分类状态隐藏 剔除分类下显示数据为空
+        if (empty($data) || $data['items']->isEmpty()) {
+            $this->success('该组幻灯片显示数据为空', []);
+        }
+
+        $this->success("该组幻灯片获取成功!", $data['items']);
+    }
+
+}

+ 422 - 0
api/portal/controller/ArticlesController.php

@@ -0,0 +1,422 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use api\user\model\UserFavoriteModel;
+use api\user\model\UserLikeModel;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalPostModel;
+use think\Db;
+
+class ArticlesController extends RestBaseController
+{
+    /**
+     * 文章列表
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $page    = $this->request->post('page', 1);
+        $size    = $this->request->post('size', 10);
+        $keyword = $this->request->post('keyword', '');
+
+        //搜索条件
+        $where = [];
+        if (!empty($keyword)) {
+            $where[] = ['post_title', 'like', "%{$keyword}%"];
+        }
+        $list = PortalPostModel::where($where)->order('is_top desc,create_time desc')->page($page, $size)->select();
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 获取指定的文章
+     * @param $id
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的文章id!');
+        } else {
+            $postModel = new PortalPostModel();
+            $data      = $postModel->where('id', $id)->find();
+
+            if (empty($data)) {
+                $this->error('文章不存在!');
+            } else {
+                $postModel->where('id', $id)->setInc('post_hits');
+//                $url         = cmf_url('portal/Article/index', ['id' => $id, 'cid' => $data['categories'][0]['id']], true, true);
+//                $data['url'] = $url;
+                $this->success('请求成功!', $data);
+            }
+
+        }
+    }
+
+    /**
+     * 我的文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function my()
+    {
+        $params            = $this->request->get();
+        $params['user_id'] = $this->getUserId();
+
+        $postService = new PortalPostService();
+        $data        = $postService->postArticles($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = [$data];
+        } else {
+            $response = ['list' => $data];
+        }
+
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 添加文章
+     * @throws \think\Exception
+     */
+    public function save()
+    {
+        $data            = $this->request->post();
+        $data['user_id'] = $this->getUserId();
+        $result          = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (empty($data['published_time'])) {
+            $data['published_time'] = time();
+        }
+        $postModel = new PortalPostModel();
+        $postModel->addArticle($data);
+        $this->success('添加成功!');
+    }
+
+    /**
+     * 更新文章
+     * @param $id
+     * @throws \think\Exception
+     */
+    public function update($id)
+    {
+        $data   = $this->request->put();
+        $result = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        $postModel = new PortalPostModel();
+        $res       = $postModel->articleFind(['id' => $id, 'user_id' => $this->getUserId()]);
+        if (empty($res)) {
+            $this->error('文章不存在或者已经删除!');
+        }
+
+        $result = $postModel->editArticle($data, $id, $this->getUserId());
+
+        if ($result === false) {
+            $this->error('编辑失败!');
+        } else {
+            $this->success('编辑成功!');
+        }
+    }
+
+    /**
+     * 删除文章
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function delete($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($id, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 批量删除文章
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function deletes()
+    {
+        $ids = $this->request->post('ids/a');
+        if (empty($ids)) {
+            $this->error('文章id不能为空');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($ids, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 搜索查询
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function search()
+    {
+        $params = $this->request->get();
+
+        if (!empty($params['keyword'])) {
+            $postService = new PortalPostService();
+            $data        = $postService->postArticles($params);
+
+            if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+                $response = $data;
+            } else {
+                $response = ['list' => $data,];
+            }
+            $this->success('请求成功!', $response);
+        } else {
+            $this->error('搜索关键词不能为空!');
+        }
+
+    }
+
+    /**
+     * 文章点赞
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function doLike()
+    {
+        $userId    = $this->getUserId();
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userLikeModel = new UserLikeModel();
+
+        $findLikeCount = $userLikeModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId,
+        ])->where('table_name', 'portal_post')->count();
+
+        if (empty($findLikeCount)) {
+            $postModel = new PortalPostModel();
+            $article   = $postModel->where('id', $articleId)->field('id,post_title,post_excerpt,more')->find();
+
+            if (empty($article)) {
+                $this->error('文章不存在!');
+            }
+
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setInc('post_like');
+                $thumbnail = empty($article['more']['thumbnail']) ? '' : $article['more']['thumbnail'];
+                $userLikeModel->insert([
+                    'user_id'     => $userId,
+                    'object_id'   => $articleId,
+                    'table_name'  => 'portal_post',
+                    'title'       => $article['post_title'],
+                    'thumbnail'   => $thumbnail,
+                    'description' => $article['post_excerpt'],
+                    'url'         => json_encode(['action' => 'portal/Article/index', 'param' => ['id' => $articleId, 'cid' => $article['categories'][0]['id']]]),
+                    'create_time' => time(),
+                ]);
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('点赞失败!');
+            }
+
+            $likeCount = $postModel->where('id', $articleId)->value('post_like');
+            $this->success("赞好啦!", ['post_like' => $likeCount]);
+        } else {
+            $this->error("您已赞过啦!");
+        }
+    }
+
+    /**
+     * 取消文章点赞
+     */
+    public function cancelLike()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userLikeModel = new UserLikeModel();
+
+        $findLikeCount = $userLikeModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId,
+        ])->where('table_name', 'portal_post')->count();
+
+        if (!empty($findLikeCount)) {
+            $postModel = new PortalPostModel();
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setDec('post_like');
+                $userLikeModel->where([
+                    'user_id'   => $userId,
+                    'object_id' => $articleId,
+                ])->where('table_name', 'portal_post')->delete();
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('取消点赞失败!');
+            }
+
+            $likeCount = $postModel->where('id', $articleId)->value('post_like');
+            $this->success("取消点赞成功!", ['post_like' => $likeCount]);
+        } else {
+            $this->error("您还没赞过!");
+        }
+    }
+
+    /**
+     * 文章收藏
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function doFavorite()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userFavoriteModel = new UserFavoriteModel();
+
+        $findFavoriteCount = $userFavoriteModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId,
+        ])->where('table_name', 'portal_post')->count();
+
+        if (empty($findFavoriteCount)) {
+            $postModel = new PortalPostModel();
+            $article   = $postModel->where(['id' => $articleId])->field('id,post_title,post_excerpt,more')->find();
+            if (empty($article)) {
+                $this->error('文章不存在!');
+            }
+
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setInc('post_favorites');
+                $thumbnail = empty($article['more']['thumbnail']) ? '' : $article['more']['thumbnail'];
+                $userFavoriteModel->insert([
+                    'user_id'     => $userId,
+                    'object_id'   => $articleId,
+                    'table_name'  => 'portal_post',
+                    'thumbnail'   => $thumbnail,
+                    'title'       => $article['post_title'],
+                    'description' => $article['post_excerpt'],
+                    'url'         => json_encode(['action' => 'portal/Article/index', 'param' => ['id' => $articleId, 'cid' => $article['categories'][0]['id']]]),
+                    'create_time' => time(),
+                ]);
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+
+                $this->error('收藏失败!');
+            }
+
+            $favoriteCount = $postModel->where('id', $articleId)->value('post_favorites');
+            $this->success("收藏好啦!", ['post_favorites' => $favoriteCount]);
+        } else {
+            $this->error("您已收藏过啦!");
+        }
+    }
+
+    /**
+     * 取消文章收藏
+     */
+    public function cancelFavorite()
+    {
+        $userId = $this->getUserId();
+
+        $articleId = $this->request->param('id', 0, 'intval');
+
+        $userFavoriteModel = new UserFavoriteModel();
+
+        $findFavoriteCount = $userFavoriteModel->where([
+            'user_id'   => $userId,
+            'object_id' => $articleId,
+        ])->where('table_name', 'portal_post')->count();
+
+        if (!empty($findFavoriteCount)) {
+            $postModel = new PortalPostModel();
+            Db::startTrans();
+            try {
+                $postModel->where(['id' => $articleId])->setDec('post_favorites');
+                $userFavoriteModel->where([
+                    'user_id'   => $userId,
+                    'object_id' => $articleId,
+                ])->where('table_name', 'portal_post')->delete();
+                Db::commit();
+            } catch (\Exception $e) {
+                Db::rollback();
+                $this->error('取消失败!');
+            }
+
+            $favoriteCount = $postModel->where('id', $articleId)->value('post_favorites');
+            $this->success("取消成功!", ['post_favorites' => $favoriteCount]);
+        } else {
+            $this->error("您还没收藏过!");
+        }
+    }
+
+
+    /**
+     * 相关文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function relatedArticles()
+    {
+        $articleId  = $this->request->param('id', 0, 'intval');
+        $categoryId = Db::name('portal_category_post')->where('post_id', $articleId)->value('category_id');
+
+        $postModel = new PortalPostModel();
+        $articles  = $postModel
+            ->alias('post')
+            ->join('__PORTAL_CATEGORY_POST__ category_post', 'post.id=category_post.post_id')
+            ->where(['post.delete_time' => 0, 'post.post_status' => 1, 'category_post.category_id' => $categoryId])
+            ->orderRaw('rand()')
+            ->limit(5)
+            ->select();
+        if ($articles->isEmpty()) {
+            $this->error('没有相关文章!');
+        }
+        $this->success('success', ['list' => $articles]);
+    }
+}

+ 81 - 0
api/portal/controller/CategoriesController.php

@@ -0,0 +1,81 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalCategoryService;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalCategoryModel;
+
+class CategoriesController extends RestBaseController
+{
+    /**
+     * 获取分类列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params          = $this->request->get();
+        $categoryService = new PortalCategoryService();
+        $data            = $categoryService->categories($params);
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data];
+        }
+
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 显示指定的分类
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+        $categoryModel = new PortalCategoryModel();
+        $data          = $categoryModel
+            ->where('delete_time', 0)
+            ->where('status', 1)
+            ->where('id', $id)
+            ->find();
+        if ($data) {
+            $this->success('请求成功!', $data);
+        } else {
+            $this->error('该分类不存在!');
+        }
+
+    }
+
+    /**
+     * 获取指定分类的子分类列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function subCategories()
+    {
+        $id = $this->request->get('category_id', 0, 'intval');
+
+        $categoryModel = new PortalCategoryModel();
+        $categories    = $categoryModel->where(['parent_id' => $id])->select();
+        if (!$categories->isEmpty()) {
+            $this->success('请求成功', ['categories' => $categories]);
+        } else {
+            $this->error('该分类下没有子分类!');
+        }
+
+
+    }
+}

+ 76 - 0
api/portal/controller/IndexController.php

@@ -0,0 +1,76 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalPostModel;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalTagModel;
+
+class IndexController extends RestBaseController
+{
+    protected $tagModel;
+
+    /**
+     * 获取标签列表
+     */
+    public function index()
+    {
+        $this->success('请求成功!', "DD");
+    }
+
+    /**
+     * 获取热门标签列表
+     */
+    public function hotTags()
+    {
+        $params                         = $this->request->get();
+        $params['where']['recommended'] = 1;
+        $data                           = $this->tagModel->getDatas($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取标签文章列表
+     * @param int $id
+     */
+    public function articles($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的标签id!');
+        } else {
+            $params    = $this->request->param();
+            $postModel = new PortalPostModel();
+
+            unset($params['id']);
+
+            $articles = $postModel->paramsFilter($params)->alias('post')
+                ->join('__PORTAL_TAG_POST__ tag_post', 'post.id = tag_post.post_id')
+                ->where(['tag_post.tag_id' => $id])->select();
+
+            if (!empty($params['relation'])) {
+                $allowedRelations = $postModel->allowedRelations($params['relation']);
+                if (!empty($allowedRelations)) {
+                    if (count($articles) > 0) {
+                        $articles->load($allowedRelations);
+                        $articles->append($allowedRelations);
+                    }
+                }
+            }
+
+
+            $this->success('请求成功!', ['articles' => $articles]);
+        }
+    }
+}

+ 79 - 0
api/portal/controller/ListsController.php

@@ -0,0 +1,79 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalCategoryModel;
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+
+class ListsController extends RestBaseController
+{
+
+    /**
+     * 推荐文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function recommended()
+    {
+        $param                = $this->request->param();
+        $param['recommended'] = true;
+
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        //是否需要关联模型
+        if (!$articles->isEmpty()) {
+            if (!empty($param['relation'])) {
+                $allowedRelations = allowed_relations(['user', 'categories'], $param['relation']);
+                if (!empty($allowedRelations)) {
+                    $articles->load($allowedRelations);
+                    $articles->append($allowedRelations);
+                }
+            }
+        }
+        $this->success('ok', ['list' => $articles]);
+    }
+
+    /**
+     * 分类文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function getCategoryPostLists()
+    {
+        $categoryId = $this->request->param('category_id', 0, 'intval');
+
+        $portalCategoryModel = new  PortalCategoryModel();
+        $findCategory        = $portalCategoryModel->where('id', $categoryId)->find();
+
+        //分类是否存在
+        if (empty($findCategory)) {
+            $this->error('分类不存在!');
+        }
+
+        $param = $this->request->param();
+
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        //是否需要关联模型
+        if (!$articles->isEmpty()) {
+            if (!empty($param['relation'])) {
+                $allowedRelations = allowed_relations(['user', 'categories'], $param['relation']);
+                if (!empty($allowedRelations)) {
+                    $articles->load($allowedRelations);
+                    $articles->append($allowedRelations);
+                }
+            }
+        }
+        $this->success('ok', ['list' => $articles]);
+    }
+
+}

+ 65 - 0
api/portal/controller/PagesController.php

@@ -0,0 +1,65 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+use api\portal\model\PortalPostModel;
+
+class PagesController extends RestBaseController
+{
+    /**
+     * 页面列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params      = $this->request->get();
+        $postService = new PortalPostService();
+        $data        = $postService->postArticles($params, true);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取页面
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function read($id)
+    {
+
+        $params    = $this->request->get();
+        $field     = empty($params['field']) ? '*' : $params['field'];
+        $postModel = new PortalPostModel();
+        $data      = $postModel
+            ->field($field)
+            ->where('id', $id)
+            ->where('delete_time', 0)
+            ->where('post_status', 1)
+            ->where('post_type', 2)
+            ->find();
+        if ($data){
+            $this->success('请求成功!', $data);
+        }else{
+            $this->error('文章不存在!');
+        }
+
+    }
+}

+ 86 - 0
api/portal/controller/TagsController.php

@@ -0,0 +1,86 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\model\PortalTagModel;
+use api\portal\service\PortalTagService;
+use cmf\controller\RestBaseController;
+use think\db\Query;
+
+class TagsController extends RestBaseController
+{
+
+    /**
+     * 获取标签列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $params     = $this->request->get();
+        $tagService = new PortalTagService();
+        $data       = $tagService->tagList($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        if ($data->isEmpty()) {
+            $this->error('没有标签!');
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取热门标签列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function hotTags()
+    {
+        $params                         = $this->request->get();
+        $params['where']['recommended'] = 1;
+
+        $tagService = new PortalTagService();
+        $data       = $tagService->tagList($params);
+
+        if (empty($this->apiVersion) || $this->apiVersion == '1.0.0') {
+            $response = $data;
+        } else {
+            $response = ['list' => $data,];
+        }
+        $this->success('请求成功!', $response);
+    }
+
+    /**
+     * 获取标签文章列表
+     * @param $id
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articles($id)
+    {
+        if (intval($id) === 0) {
+            $this->error('无效的标签id!');
+        } else {
+            $filter       = $this->request->param();
+            $filter['id'] = $id;
+            $tagService   = new PortalTagService();
+            $tag          = $tagService->portalTagArticles($filter);
+            if ($tag) {
+                $this->error('没有相关文章');
+            }
+            $this->success('请求成功!', $tag);
+        }
+    }
+}

+ 140 - 0
api/portal/controller/UserArticlesController.php

@@ -0,0 +1,140 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use cmf\controller\RestUserBaseController;
+use api\portal\logic\PortalPostModel;
+
+class UserArticlesController extends RestUserBaseController
+{
+    /**
+     * 显示资源列表
+     */
+    public function index()
+    {
+        $params    = $this->request->get();
+        $userId    = $this->getUserId();
+        $postModel = new PortalPostModel();
+        $dates     = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $dates);
+    }
+
+    /**
+     * 保存新建的资源
+     */
+    public function save()
+    {
+        $dates            = $this->request->post();
+        $dates['user_id'] = $this->getUserId();
+        $result           = $this->validate($dates, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        if (empty($dates['published_time'])) {
+            $dates['published_time'] = time();
+        }
+        $postModel = new PortalPostModel();
+        $postModel->addArticle($dates);
+        $this->success('添加成功!');
+    }
+
+    /**
+     * 显示指定的资源
+     * @param $id
+     */
+    public function read($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $params       = $this->request->get();
+        $params['id'] = $id;
+        $userId       = $this->getUserId();
+        $postModel    = new PortalPostModel();
+        $dates        = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $dates);
+    }
+
+    /**
+     * 保存更新的资源
+     * @param $id
+     */
+    public function update($id)
+    {
+        $data   = $this->request->put();
+        $result = $this->validate($data, 'Articles.article');
+        if ($result !== true) {
+            $this->error($result);
+        }
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->editArticle($data, $id, $this->getUserId());
+        if ($result === false) {
+            $this->error('编辑失败!');
+        } else {
+            $this->success('编辑成功!');
+        }
+    }
+
+    /**
+     * 删除指定资源
+     * @param $id
+     */
+    public function delete($id)
+    {
+        if (empty($id)) {
+            $this->error('无效的文章id');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($id, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 批量删除文章
+     */
+    public function deletes()
+    {
+        $ids = $this->request->post('ids/a');
+        if (empty($ids)) {
+            $this->error('文章id不能为空');
+        }
+        $postModel = new PortalPostModel();
+        $result    = $postModel->deleteArticle($ids, $this->getUserId());
+        if ($result == -1) {
+            $this->error('文章已删除');
+        }
+        if ($result) {
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败!');
+        }
+    }
+
+    /**
+     * 我的文章列表
+     */
+    public function my()
+    {
+        $params    = $this->request->get();
+        $userId    = $this->getUserId();
+        $postModel = new PortalPostModel();
+        $data      = $postModel->getUserArticles($userId, $params);
+        $this->success('请求成功!', $data);
+    }
+}

+ 41 - 0
api/portal/controller/UserController.php

@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\controller;
+
+use api\portal\service\PortalPostService;
+use cmf\controller\RestBaseController;
+
+class UserController extends RestBaseController
+{
+    /**
+     * 会员文章列表
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articles()
+    {
+        $userId = $this->request->param('user_id', 0, 'intval');
+
+        if (empty($userId)) {
+            $this->error('用户id不能空!');
+        }
+
+        $param             = $this->request->param();
+        $param['user_id']  = $userId;
+        $portalPostService = new PortalPostService();
+        $articles          = $portalPostService->postArticles($param);
+        if ($articles->isEmpty()) {
+            $this->error('没有数据');
+        } else {
+            $this->success('ok', ['list' => $articles]);
+        }
+    }
+
+}

+ 324 - 0
api/portal/logic/PortalPostModel.php

@@ -0,0 +1,324 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\logic;
+
+use api\portal\model\PortalPostModel as PortalPost;
+use think\Db;
+class PortalPostModel extends PortalPost
+{
+    /**
+     * 获取相关文章
+     * @param int|string|array $postIds 文章id
+     * @return array
+     */
+    public function getRelationPosts($postIds)
+    {
+        $posts = $this->with('articleUser')
+            ->field('id,post_title,user_id,is_top,post_hits,post_like,comment_count,more')
+            ->whereIn('id', $postIds)
+            ->select();
+        foreach ($posts as $post) {
+            $post->appendRelationAttr('articleUser', 'user_nickname');
+        }
+        return $posts;
+    }
+    /**
+     * 获取用户文章
+     */
+    public function getUserArticles($userId, $params)
+    {
+        $where = [
+            'post_type' => 1,
+            'user_id'   => $userId
+        ];
+        if (!empty($params)) {
+            $this->paramsFilter($params);
+        }
+        return $this->where($where)->select();
+    }
+
+    /**
+     * 会员添加文章
+     * @param array $data 文章数据
+     * @return $this
+     */
+    public function addArticle($data)
+    {
+    	//设置图片附件,写入字段过滤
+    	$dataField  =   $this->setMoreField($data);
+    	$data       =   $dataField[0];
+	    array_push($dataField[1],'user_id');
+	    $this->readonly = array_diff(['user_id'],$this->readonly);
+        $this->allowField($dataField[1])->data($data, true)->isUpdate(false)->save();
+        $categories = $this->strToArr($data['categories']);
+        $this->categories()->attach($categories);
+        if (!empty($data['post_keywords']) && is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+            $this->addTags($keywords, $this->id);
+        }
+        return $this;
+    }
+
+    /**
+     * 会员文章编辑
+     * @param array $data 文章数据
+     * @param int $id 文章id
+     * @param int $userId 文章所属用户id [可选]
+     * @return boolean   成功 true 失败 false
+     */
+    public function editArticle($data, $id, $userId = '')
+    {
+        if (!empty($userId)) {
+            $isBelong = $this->isuserPost($id, $userId);
+            if ($isBelong === false) {
+                return $isBelong;
+            }
+        }
+	    //设置图片附件,写入字段过滤
+	    $dataField             = $this->setMoreField($data);
+        $data                  = $dataField[0];
+        $data['id']            = $id;
+        $this->allowField($dataField[1])->data($data, true)->isUpdate(true)->save();
+
+        $categories            = $this->strToArr($data['categories']);
+        $oldCategoryIds        = $this->categories()->column('category_id');
+        $sameCategoryIds       = array_intersect($categories, $oldCategoryIds);
+        $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
+        $newCategoryIds        = array_diff($categories, $sameCategoryIds);
+        if (!empty($needDeleteCategoryIds)) {
+            $this->categories()->detach($needDeleteCategoryIds);
+        }
+        if (!empty($newCategoryIds)) {
+            $this->categories()->attach(array_values($newCategoryIds));
+        }
+        if (!isset($data['post_keywords'])) {
+	        $keywords = [];
+        } elseif (is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+        }
+        $this->addTags($keywords, $data['id']);
+        return $this;
+    }
+
+    /**
+     * 根据文章关键字,增加标签
+     * @param array $keywords 文章关键字数组
+     * @param int $articleId 文章id
+     * @return void
+     */
+    public function addTags($keywords, $articleId)
+    {
+        foreach ($keywords as $key => $value) {
+            $keywords[$key] = trim($value);
+        }
+        $continue = true;
+        $names    = $this->tags()->column('name');
+        if (!empty($keywords) || !empty($names)) {
+            if (!empty($names)) {
+                $sameNames         = array_intersect($keywords, $names);
+                $keywords          = array_diff($keywords, $sameNames);
+                $shouldDeleteNames = array_diff($names, $sameNames);
+                if (!empty($shouldDeleteNames)) {
+                    $tagIdNames = $this->tags()
+                        ->where('name', 'in', $shouldDeleteNames)
+                        ->column('pivot.id', 'tag_id');
+                    $tagIds     = array_keys($tagIdNames);
+                    $tagPostIds = array_values($tagIdNames);
+                    $tagPosts   = DB::name('portal_tag_post')->where('tag_id', 'in', $tagIds)
+                        ->field('id,tag_id,post_id')
+                        ->select();
+                    $keepTagIds = [];
+                    foreach ($tagPosts as $key => $tagPost) {
+                        if ($articleId != $tagPost['post_id']) {
+                            array_push($keepTagIds, $tagPost['tag_id']);
+                        }
+                    }
+                    $keepTagIds         = array_unique($keepTagIds);
+                    $shouldDeleteTagIds = array_diff($tagIds, $keepTagIds);
+                    DB::name('PortalTag')->delete($shouldDeleteTagIds);
+                    DB::name('PortalTagPost')->delete($tagPostIds);
+                }
+            } else {
+                $tagIdNames = DB::name('portal_tag')->where('name', 'in', $keywords)->column('name', 'id');
+                if (!empty($tagIdNames)) {
+                    $tagIds = array_keys($tagIdNames);
+                    $this->tags()->attach($tagIds);
+                    $keywords = array_diff($keywords, array_values($tagIdNames));
+                    if (empty($keywords)) {
+                        $continue = false;
+                    }
+                }
+            }
+            if ($continue) {
+                foreach ($keywords as $key => $value) {
+                    if (!empty($value)) {
+                        $this->tags()->attach(['name' => $value]);
+                    }
+                }
+            }
+        }
+    }
+
+	/**
+	 * 设置缩略图,图片,附件
+	 * 懒人方法
+	 * @param $data 表单数据
+	 */
+	public function setMoreField($data)
+	{
+		$allowField = [
+			'post_title','post_keywords','post_source',
+			'post_excerpt','post_content','more',
+			'published_time'
+		];
+		if (!empty($data['more'])) {
+			$data['more'] = $this->setMoreUrl($data['more']);
+		}
+		if (!empty($data['thumbnail'])) {
+			$data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+		}
+		return [$data,$allowField];
+	}
+
+    /**
+     * 获取图片附件url相对地址
+     * 默认上传名字 *_names  地址 *_urls
+     * @param $annex 上传附件
+     * @return array
+     */
+    public function setMoreUrl($annex)
+    {
+        $more = [];
+        if (!empty($annex)) {
+            foreach ($annex as $key => $value) {
+                $nameArr = $key . '_names';
+                $urlArr  = $key . '_urls';
+                if (is_string($value[$nameArr]) && is_string($value[$urlArr])) {
+                    $more[$key] = [$value[$nameArr], $value[$urlArr]];
+                } elseif (!empty($value[$nameArr]) && !empty($value[$urlArr])) {
+                    $more[$key] = [];
+                    foreach ($value[$urlArr] as $k => $url) {
+                        $url = cmf_asset_relative_url($url);
+                        array_push($more[$key], ['url' => $url, 'name' => $value[$nameArr][$k]]);
+                    }
+                }
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 删除文章
+     * @param $ids  int|array   文章id
+     * @param int $userId 文章所属用户id  [可选]
+     * @return bool|int 删除结果  true 成功 false 失败  -1 文章不存在
+     */
+    public function deleteArticle($ids, $userId)
+    {
+        $time   = time();
+        $result = false;
+        $where  = [];
+
+        if (!empty($userId)) {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    if ($this->isUserPost($ids, $userId) || $userId == 1) {
+                        $where['id'] = $ids;
+                    }
+                }
+            } else {
+                $ids      = $this->strToArr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $deleteIds = $this->isUserPosts($ids, $userId);
+                    if (!empty($deleteIds)) {
+                        $where['id'] = ['in', $deleteIds];
+                    }
+                }
+            }
+        } else {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    $where['id'] = $ids;
+                }
+            } else {
+                $ids      = $this->strToArr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $where['id'] = ['in', $ids];
+                }
+            }
+        }
+        if (empty($article) && empty($articles)) {
+            return -1;
+        }
+        if (!empty($where)) {
+            $result = $this->useGlobalScope(false)
+                ->where($where)
+                ->setField('delete_time', $time);
+        }
+        if ($result) {
+            $data = [
+                'create_time' => $time,
+                'table_name'  => 'portal_post'
+            ];
+            if (!empty($article)) {
+                $data['name'] = $article['post_title'];
+                $article->recycleBin()->save($data);
+            }
+
+            if (!empty($articles)) {
+                foreach ($articles as $article) {
+                    $data['name'] = $article['post_title'];
+                    $article->recycleBin()->save($data);
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 判断文章所属用户是否为当前用户,超级管理员除外
+     * @params  int $id     文章id
+     * @param   int $userId 当前用户id
+     * @return  boolean     是 true , 否 false
+     */
+    public function isUserPost($id, $userId)
+    {
+        $postUserId = $this->useGlobalScope(false)
+            ->getFieldById($id, 'user_id');
+        if ($postUserId != $userId || $userId != 1) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * 过滤属于当前用户的文章,超级管理员除外
+     * @params  array $ids     文章id的数组
+     * @param   int $userId 当前用户id
+     * @return  array     属于当前用户的文章id
+     */
+    public function isUserPosts($ids, $userId)
+    {
+        $postIds = $this->useGlobalScope(false)
+            ->where('user_id', $userId)
+            ->where('id', 'in', $ids)
+            ->column('id');
+        return array_intersect($ids, $postIds);
+    }
+}

+ 17 - 0
api/portal/model/FeedbackModel.php

@@ -0,0 +1,17 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class FeedbackModel extends Model
+{
+
+}

+ 82 - 0
api/portal/model/PortalCategoryModel.php

@@ -0,0 +1,82 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalCategoryModel extends Model
+{
+    //类型转换
+    protected $type = [
+        'more' => 'array',
+    ];
+
+
+    //模型关联方法
+    protected $relationFilter = ['articles'];
+
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getMoreAttr($value)
+    {
+        $more = json_decode($value, true);
+        if (!empty($more['thumbnail'])) {
+            $more['thumbnail'] = cmf_get_image_url($more['thumbnail']);
+        }
+
+        if (!empty($more['photos'])) {
+            foreach ($more['photos'] as $key => $value) {
+                $more['photos'][$key]['url'] = cmf_get_image_url($value['url']);
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 关联文章表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function articles()
+    {
+        return $this->belongsToMany('PortalPostModel', 'portal_category_post', 'post_id', 'category_id');
+    }
+
+    /**
+     * 关联文章分类和文章表
+     * @return \think\model\relation\HasMany
+     */
+    public function PostIds()
+    {
+        return $this->hasMany('PortalCategoryPostModel', 'category_id', 'id');
+    }
+
+    /**
+     * 此类文章id数组
+     * @param string $category_id 分类di
+     * @return array|string|Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public static function categoryPostIds($category_id)
+    {
+        $ids      = [];
+        $post_ids = self::relation('PostIds')->field(true)->where('id', $category_id)->find();
+        foreach ($post_ids['PostIds'] as $key => $id) {
+            $ids[] = $id['post_id'];
+        }
+        $post_ids['PostIds'] = $ids;
+        return $post_ids;
+    }
+}

+ 24 - 0
api/portal/model/PortalCategoryPostModel.php

@@ -0,0 +1,24 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalCategoryPostModel extends Model
+{
+    /**
+     * 基础查询
+     */
+    protected function base($query)
+    {
+        $query->where('status', 1);
+    }
+}

+ 491 - 0
api/portal/model/PortalPostModel.php

@@ -0,0 +1,491 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Db;
+use think\db\Query;
+use think\Model;
+
+/**
+ * @method getFieldById($id, $string)
+ * @property mixed id
+ */
+class PortalPostModel extends Model
+{
+    //设置只读字段
+    protected $readonly = ['user_id'];
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = true;
+
+    //类型转换
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    /**
+     *  关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function user()
+    {
+        return $this->belongsTo('api\portal\model\UserModel', 'user_id');
+    }
+
+    /**
+     * 关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function articleUser()
+    {
+        return $this->belongsTo('api\portal\model\UserModel', 'user_id')->field('id,user_nickname');
+    }
+
+    /**
+     * 关联分类表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function categories()
+    {
+        return $this->belongsToMany('api\portal\model\PortalCategoryModel', 'portal_category_post', 'category_id', 'post_id');
+    }
+
+    /**
+     * 关联标签表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function tags()
+    {
+        return $this->belongsToMany('api\portal\model\PortalTagModel', 'portal_tag_post', 'tag_id', 'post_id');
+    }
+
+    /**
+     * 关联 回收站 表
+     * @return \think\model\relation\HasOne
+     */
+    public function recycleBin()
+    {
+        return $this->hasOne('api\portal\model\RecycleBinModel', 'object_id');
+    }
+
+    /**
+     * published_time   自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPublishedTimeAttr($value)
+    {
+        // 兼容老版本 1.0.0的客户端
+        $apiVersion = request()->header('XX-Api-Version');
+        if (empty($apiVersion)) {
+            return date('Y-m-d H:i:s', $value);
+        } else {
+            return $value;
+        }
+    }
+
+    /**
+     * published_time   自动转化
+     * @param $value
+     * @return int
+     */
+    public function setPublishedTimeAttr($value)
+    {
+        if (is_numeric($value)) {
+            return $value;
+        }
+        return strtotime($value);
+    }
+
+    public function getPostTitleAttr($value)
+    {
+        return htmlspecialchars_decode($value);
+    }
+
+    public function getPostExcerptAttr($value)
+    {
+        return htmlspecialchars_decode($value);
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPostContentAttr($value)
+    {
+        return cmf_replace_content_file_url(htmlspecialchars_decode($value));
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function setPostContentAttr($value)
+    {
+        return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
+    }
+
+    /**
+     * Thumbnail 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getThumbnailAttr($value)
+    {
+        return cmf_get_image_url($value);
+    }
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return array
+     */
+    public function getMoreAttr($value)
+    {
+        $more = json_decode($value, true);
+        if (!empty($more['thumbnail'])) {
+            $more['thumbnail'] = cmf_get_image_url($more['thumbnail']);
+        }
+
+        if (!empty($more['audio'])) {
+            $more['audio'] = cmf_get_file_download_url($more['audio']);
+        }
+
+        if (!empty($more['video'])) {
+            $more['video'] = cmf_get_file_download_url($more['video']);
+        }
+
+        if (!empty($more['photos'])) {
+            foreach ($more['photos'] as $key => $value) {
+                $more['photos'][$key]['url'] = cmf_get_image_url($value['url']);
+            }
+        }
+
+        if (!empty($more['files'])) {
+            foreach ($more['files'] as $key => $value) {
+                $more['files'][$key]['url'] = cmf_get_file_download_url($value['url']);
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 文章查询
+     * @param array $filter 数据
+     * @return array|\PDOStatement|string|Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function articleFind($filter)
+    {
+        $result = $this
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['id'])) {
+                    $query->where('id', $filter['id']);
+                }
+                if (!empty($filter['user_id'])) {
+                    $query->where('user_id', $filter['user_id']);
+                }
+            })
+            ->where('delete_time', 0)
+            ->where('post_status', 1)
+            ->where('post_type', 1)
+            ->find();
+        return $result;
+    }
+
+    /**
+     * 会员添加文章
+     * @param array $data 文章数据
+     * @return $this
+     * @throws \think\Exception
+     */
+    public function addArticle($data)
+    {
+        if (!empty($data['more'])) {
+            $data['more'] = $this->setMoreUrl($data['more']);
+        }
+        if (!empty($data['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+        }
+        $this->allowField(true)->data($data, true)->isUpdate(false)->save();
+        $categories = str_to_arr($data['categories']);
+        //TODO 无法录入多个分类
+        $this->categories()->attach($categories);
+        if (!empty($data['post_keywords']) && is_string($data['post_keywords'])) {
+            //加入标签
+            $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+            $keywords              = explode(',', $data['post_keywords']);
+            $this->addTags($keywords, $this->id);
+        }
+        return $this;
+    }
+
+    /**
+     * 会员文章编辑
+     * @param array  $data   文章数据
+     * @param int    $id     文章id
+     * @param string $userId 文章所属用户id [可选]
+     * @return PortalPostModel|bool
+     * @throws \think\Exception
+     */
+    public function editArticle($data, $id, $userId = '')
+    {
+
+        if (!empty($userId)) {
+            //判断是否属于当前用户的文章
+            $isBelong = $this->isuserPost($id, $userId);
+            if ($isBelong === false) {
+                return $isBelong;
+            }
+        }
+
+        if (!empty($data['more'])) {
+            $data['more'] = $this->setMoreUrl($data['more']);
+        }
+        if (!empty($data['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
+        }
+        $data['id'] = $id;
+//        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+//        $data['is_top']      = empty($data['is_top']) ? 0 : 1;
+//        $data['recommended'] = empty($data['recommended']) ? 0 : 1;
+        $this->allowField(true)->data($data, true)->isUpdate(true)->save();
+
+        $categories     = str_to_arr($data['categories']);
+        $oldCategoryIds = $this->categories()->column('category_id');
+
+        $sameCategoryIds       = array_intersect($categories, $oldCategoryIds);
+        $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
+        $newCategoryIds        = array_diff($categories, $sameCategoryIds);
+        if (!empty($needDeleteCategoryIds)) {
+            $this->categories()->detach($needDeleteCategoryIds);
+        }
+
+        if (!empty($newCategoryIds)) {
+            $this->categories()->attach(array_values($newCategoryIds));
+        }
+
+        $keywords = [];
+
+        if (!empty($data['post_keywords'])) {
+            if (is_string($data['post_keywords'])) {
+                //加入标签
+                $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
+                $keywords              = explode(',', $data['post_keywords']);
+            }
+        }
+
+        $this->addTags($keywords, $data['id']);
+
+        return $this;
+    }
+
+    /**
+     * 根据文章关键字,增加标签
+     * @param array $keywords  文章关键字数组
+     * @param int   $articleId 文章id
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function addTags($keywords, $articleId)
+    {
+        foreach ($keywords as $key => $value) {
+            $keywords[$key] = trim($value);
+        }
+        $continue = true;
+        $names    = $this->tags()->column('name');
+        if (!empty($keywords) || !empty($names)) {
+            if (!empty($names)) {
+                $sameNames         = array_intersect($keywords, $names);
+                $keywords          = array_diff($keywords, $sameNames);
+                $shouldDeleteNames = array_diff($names, $sameNames);
+                if (!empty($shouldDeleteNames)) {
+                    $tagIdNames = $this->tags()
+                        ->where('name', 'in', $shouldDeleteNames)
+                        ->column('pivot.id', 'tag_id');
+                    $tagIds     = array_keys($tagIdNames);
+                    $tagPostIds = array_values($tagIdNames);
+                    $tagPosts   = DB::name('portal_tag_post')
+                        ->where('tag_id', 'in', $tagIds)
+                        ->field('id,tag_id,post_id')
+                        ->select();
+                    $keepTagIds = [];
+                    foreach ($tagPosts as $key => $tagPost) {
+                        if ($articleId != $tagPost['post_id']) {
+                            array_push($keepTagIds, $tagPost['tag_id']);
+                        }
+                    }
+                    $keepTagIds         = array_unique($keepTagIds);
+                    $shouldDeleteTagIds = array_diff($tagIds, $keepTagIds);
+                    Db::name('PortalTag')->delete($shouldDeleteTagIds);
+                    Db::name('PortalTagPost')->delete($tagPostIds);
+                }
+            } else {
+                $tagIdNames = DB::name('portal_tag')->where('name', 'in', $keywords)->column('name', 'id');
+                if (!empty($tagIdNames)) {
+                    $tagIds = array_keys($tagIdNames);
+                    $this->tags()->attach($tagIds);
+                    $keywords = array_diff($keywords, array_values($tagIdNames));
+                    if (empty($keywords)) {
+                        $continue = false;
+                    }
+                }
+            }
+            if ($continue) {
+                foreach ($keywords as $key => $value) {
+                    if (!empty($value)) {
+                        $this->tags()->attach(['name' => $value]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取图片附件url相对地址
+     * 默认上传名字 *_names  地址 *_urls
+     * @param array $annex 上传附件
+     * @return array
+     */
+    public function setMoreUrl($annex)
+    {
+        $more = [];
+        if (!empty($annex)) {
+            foreach ($annex as $key => $value) {
+                $nameArr = $key . '_names';
+                $urlArr  = $key . '_urls';
+                if (is_string($value[$nameArr]) && is_string($value[$urlArr])) {
+                    $more[$key] = [$value[$nameArr], $value[$urlArr]];
+                } elseif (!empty($value[$nameArr]) && !empty($value[$urlArr])) {
+                    $more[$key] = [];
+                    foreach ($value[$urlArr] as $k => $url) {
+                        $url = cmf_asset_relative_url($url);
+                        array_push($more[$key], ['url' => $url, 'name' => $value[$nameArr][$k]]);
+                    }
+                }
+            }
+        }
+        return $more;
+    }
+
+    /**
+     * 删除文章
+     * @param  int|array $ids    文章id
+     * @param  string    $userId 文章所属用户id  [可选]
+     * @return bool|int 删除结果  true 成功 false 失败  -1 文章不存在
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function deleteArticle($ids, $userId = '')
+    {
+        $time   = time();
+        $result = false;
+        $where  = [];
+
+        if (!empty($userId)) {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    if ($this->isUserPost($ids, $userId) || $userId == 1) {
+                        $where['id'] = $ids;
+                    }
+                }
+            } else {
+                $ids      = str_to_arr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $deleteIds = $this->isUserPosts($ids, $userId);
+                    if (!empty($deleteIds)) {
+                        $where['id'] = ['in', $deleteIds];
+                    }
+                }
+            }
+        } else {
+            if (is_numeric($ids)) {
+                $article = $this->find($ids);
+                if (!empty($article)) {
+                    $where['id'] = $ids;
+                }
+            } else {
+                $ids      = str_to_arr($ids);
+                $articles = $this->where('id', 'in', $ids)->select();
+                if (!empty($articles)) {
+                    $where['id'] = ['in', $ids];
+                }
+            }
+        }
+        if (empty($article) && empty($articles)) {
+            return -1;
+        }
+        if (!empty($where)) {
+            $result = $this->useGlobalScope(false)
+                ->where($where)
+                ->setField('delete_time', $time);
+        }
+        if ($result) {
+            $data = [
+                'create_time' => $time,
+                'table_name'  => 'portal_post'
+            ];
+            if (!empty($article)) {
+                $data['name'] = $article['post_title'];
+                $article->recycleBin()->save($data);
+            }
+
+            if (!empty($articles)) {
+                foreach ($articles as $article) {
+                    $data['name'] = $article['post_title'];
+                    $article->recycleBin()->save($data);
+                }
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * 判断文章所属用户是否为当前用户,超级管理员除外
+     * @param   int $id     文章id
+     * @param   int $userId 当前用户id
+     * @return  boolean     是 true , 否 false
+     */
+    public function isUserPost($id, $userId)
+    {
+        $postUserId = $this->getFieldById($id, 'user_id');
+        if ($postUserId == $userId || $userId == 1) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 过滤属于当前用户的文章,超级管理员除外
+     * @param   array $ids    文章id的数组
+     * @param   int   $userId 当前用户id
+     * @return  array     属于当前用户的文章id
+     */
+    public function isUserPosts($ids, $userId)
+    {
+        $postIds = $this
+            ->useGlobalScope(false)
+            ->where('user_id', $userId)
+            ->where('id', 'in', $ids)
+            ->column('id');
+        return array_intersect($ids, $postIds);
+    }
+
+
+}

+ 25 - 0
api/portal/model/PortalTagModel.php

@@ -0,0 +1,25 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalTagModel extends Model
+{
+
+    /**
+     * 关联 文章表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function articles()
+    {
+        return $this->belongsToMany('PortalPostModel','portal_tag_post','post_id','tag_id')->alias('post');
+    }
+}

+ 29 - 0
api/portal/model/PortalTagPostModel.php

@@ -0,0 +1,29 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+use think\Model;
+
+class PortalTagPostModel extends Model
+{
+    /**
+     * 获取指定id相关的文章id数组
+     * @param $post_id  文章id
+     * @return array    相关的文章id
+     */
+    function getRelationPostIds($post_id)
+    {
+        $tagIds  = $this->where('post_id', $post_id)
+            ->column('tag_id');
+        $postIds = $this->whereIn('tag_id', $tagIds)
+            ->column('post_id');
+        return array_unique($postIds);
+    }
+}

+ 16 - 0
api/portal/model/RecycleBinModel.php

@@ -0,0 +1,16 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\model;
+
+use think\Model;
+
+class RecycleBinModel extends Model
+{
+
+}

+ 41 - 0
api/portal/model/UserModel.php

@@ -0,0 +1,41 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace api\portal\model;
+
+
+use think\Model;
+
+class UserModel extends Model
+{
+
+    //模型关联方法
+    protected $relationFilter = ['user'];
+
+
+    /**
+     * more 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getAvatarAttr($value)
+    {
+        $value = !empty($value) ? cmf_get_image_url($value) : $value;
+        return $value;
+    }
+
+    /**
+     * 关联 user表
+     * @return string 关联数据
+     */
+    public function user()
+    {
+        return $this->belongsTo('UserModel', 'user_id')->setEagerlyType(1);
+    }
+}

+ 22 - 0
api/portal/route.php

@@ -0,0 +1,22 @@
+<?php
+
+use think\facade\Route;
+
+Route::resource('portal/categories', 'portal/Categories');
+Route::get('portal/categories/subCategories', 'portal/Categories/subCategories');
+Route::resource('portal/articles', 'portal/Articles');
+Route::resource('portal/pages', 'portal/Pages');
+Route::resource('portal/userArticles', 'portal/UserArticles');
+
+Route::get('portal/search', 'portal/Articles/search');
+Route::get('portal/articles/my', 'portal/Articles/my');
+Route::get('portal/articles/relatedArticles', 'portal/Articles/relatedArticles');
+Route::post('portal/articles/doLike', 'portal/Articles/doLike');
+Route::post('portal/articles/cancelLike', 'portal/Articles/cancelLike');
+Route::post('portal/articles/doFavorite', 'portal/Articles/doFavorite');
+Route::post('portal/articles/cancelFavorite', 'portal/Articles/cancelFavorite');
+Route::get('portal/tags/:id/articles', 'portal/Tags/articles');
+Route::get('portal/tags', 'portal/Tags/index');
+Route::get('portal/tags/hotTags', 'portal/Tags/hotTags');
+
+Route::post('portal/userArticles/deletes', 'portal/UserArticles/deletes');

+ 59 - 0
api/portal/service/PortalCategoryService.php

@@ -0,0 +1,59 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/08
+// | Time:上午 10:32
+// +----------------------------------------------------------------------
+
+
+namespace api\portal\service;
+
+
+use api\portal\model\PortalCategoryModel;
+use think\db\Query;
+
+class PortalCategoryService
+{
+    /**
+     * @param $filter
+     * @return array|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function categories($filter)
+    {
+        $categoryModel = new PortalCategoryModel();
+        //条件分解
+        $field = empty($filter['field']) ? '*' : $filter['field'];
+        $order = empty($filter['order']) ? ['-id'] : explode(',', $filter['order']);
+        $page  = empty($filter['page']) ? '' : $filter['page'];
+        $limit = empty($filter['limit']) ? '' : $filter['limit'];
+        if (!empty($page)) {
+            $categoryModel = $categoryModel->page($page);
+        } elseif (!empty($limit)) {
+            $categoryModel = $categoryModel->limit($limit);
+        } else {
+            $categoryModel = $categoryModel->limit(10);
+        }
+        //转化-+为desc、asc
+        $orderArr = order_shift($order);
+
+        $result = $categoryModel
+            ->field($field)
+            ->where('delete_time', 0)
+            ->where('status', 1)
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['ids'])) {
+                    $query->where('id', 'in', $filter['ids']);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+        return $result;
+    }
+}

+ 108 - 0
api/portal/service/PortalPostService.php

@@ -0,0 +1,108 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+namespace api\portal\service;
+
+use api\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PortalPostService
+{
+    //模型关联方法
+    protected $relationFilter = ['user'];
+
+    /**
+     * 文章列表
+     * @param      $filter
+     * @param bool $isPage
+     * @return array|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function postArticles($filter, $isPage = false)
+    {
+        $join = [];
+
+        $field = empty($filter['field']) ? 'a.*' : explode(',', $filter['field']);
+        //转为查询条件
+        if (is_array($field)) {
+            foreach ($field as $key => $value) {
+                $field[$key] = 'a.' . $value;
+            }
+            $field = implode(',', $field);
+        }
+        $page     = empty($filter['page']) ? '' : $filter['page'];
+        $limit    = empty($filter['limit']) ? '' : $filter['limit'];
+        $order    = empty($filter['order']) ? ['-update_time'] : explode(',', $filter['order']);
+        $category = empty($filter['category_id']) ? 0 : intval($filter['category_id']);
+        if (!empty($category)) {
+            array_push($join, ['__PORTAL_CATEGORY_POST__ b', 'a.id = b.post_id']);
+            $field = $field.',b.id AS post_category_id,b.list_order,b.category_id';
+        }
+
+        $orderArr = order_shift($order);
+
+        $portalPostModel = new PortalPostModel();
+
+
+        if (!empty($page)) {
+            $portalPostModel = $portalPostModel->page($page);
+        } elseif (!empty($limit)) {
+            $portalPostModel = $portalPostModel->limit($limit);
+        } else {
+            $portalPostModel = $portalPostModel->limit(10);
+        }
+
+        $articles = $portalPostModel
+            ->alias('a')
+            ->field($field)
+            ->join($join)
+            ->where('a.create_time', '>=', 0)
+            ->where('a.delete_time', 0)
+            ->where('a.post_status', 1)
+            ->where(function (Query $query) use ($filter, $isPage) {
+                if (!empty($filter['user_id'])) {
+                    $query->where('a.user_id', $filter['user_id']);
+                }
+                $category = empty($filter['category_id']) ? 0 : intval($filter['category_id']);
+                if (!empty($category)) {
+                    $query->where('b.category_id', $category);
+                }
+                $startTime = empty($filter['start_time']) ? 0 : strtotime($filter['start_time']);
+                $endTime   = empty($filter['end_time']) ? 0 : strtotime($filter['end_time']);
+                if (!empty($startTime)) {
+                    $query->where('a.published_time', '>=', $startTime);
+                }
+                if (!empty($endTime)) {
+                    $query->where('a.published_time', '<=', $endTime);
+                }
+                $keyword = empty($filter['keyword']) ? '' : $filter['keyword'];
+                if (!empty($keyword)) {
+                    $query->where('a.post_title', 'like', "%$keyword%");
+                }
+                if ($isPage) {
+                    $query->where('a.post_type', 2);
+                } else {
+                    $query->where('a.post_type', 1);
+                }
+                if (!empty($filter['recommended'])) {
+                    $query->where('a.recommended', 1);
+                }
+                if (!empty($filter['ids'])) {
+                    $ids = str_to_arr($filter['ids']);
+                    $query->where('a.id', 'in', $ids);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+
+        return $articles;
+    }
+
+}

+ 100 - 0
api/portal/service/PortalTagService.php

@@ -0,0 +1,100 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2018 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/09
+// | Time:下午 06:10
+// +----------------------------------------------------------------------
+
+
+namespace api\portal\service;
+
+
+use api\portal\model\PortalPostModel;
+use api\portal\model\PortalTagModel;
+use think\db\Query;
+
+class PortalTagService
+{
+    /**
+     * 获取标签列表
+     * @param array $filter 参数
+     * @return array|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function tagList($filter)
+    {
+        $field    = empty($filter['field']) ? '*' : $filter['field'];
+        $page     = empty($filter['page']) ? '' : $filter['page'];
+        $limit    = empty($filter['limit']) ? '' : $filter['limit'];
+        $order    = empty($filter['order']) ? ['-id'] : explode(',', $filter['order']);
+        $orderArr = order_shift($order);
+        $tagModel = new PortalTagModel();
+        if (!empty($page)) {
+            $tagModel = $tagModel->page($page);
+        } elseif (!empty($limit)) {
+            $tagModel = $tagModel->limit($limit);
+        } else {
+            $tagModel = $tagModel->limit(10);
+        }
+
+        $result = $tagModel
+            ->field($field)
+            ->where('status', 1)
+            ->where(function (Query $query) use ($filter) {
+                if (!empty($filter['id'])) {
+                    $query->where('id', $filter['id']);
+                }
+            })
+            ->order($orderArr)
+            ->select();
+        return $result;
+    }
+
+    /**
+     * @param $filter
+     * @return array|\PDOStatement|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function portalTagArticles($filter)
+    {
+        $tagModel = new PortalTagModel();
+        $tag      = $tagModel
+            ->with([
+                'articles' => function (Query $query) use ($filter) {
+                    $field = empty($filter['field']) ? 'post.*' : explode(',', $filter['field']);
+                    //转为查询条件
+                    if (is_array($field)) {
+                        foreach ($field as $key => $value) {
+                            $field[$key] = 'post.' . $value;
+                        }
+                        $field = implode(',', $field);
+                    }
+                    $page     = empty($filter['page']) ? '' : $filter['page'];
+                    $limit    = empty($filter['limit']) ? '10' : $filter['limit'];
+                    $order    = empty($filter['order']) ? ['-post.id'] : explode(',', $filter['order']);
+                    $orderArr = order_shift($order);
+                    $query->field($field);
+                    if (!empty($page)) {
+                        $query->page($page);
+                    } elseif (!empty($limit)) {
+                        $query->limit($limit);
+                    } else {
+                        $query->limit(10);
+                    }
+                    $query->order($orderArr);
+                    $query->hidden(['pivot']);
+                }
+            ])
+            ->where('id', $filter['id'])
+            ->find();
+        return $tag;
+    }
+}

+ 34 - 0
api/portal/validate/ArticlesValidate.php

@@ -0,0 +1,34 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// | Date: 2019/01/11
+// | Time:下午 03:24
+// +----------------------------------------------------------------------
+namespace api\portal\validate;
+
+use think\Validate;
+
+class ArticlesValidate extends Validate
+{
+    protected $rule = [
+        'post_title'        =>  'require',
+	    'post_content'      =>  'require',
+	    'categories'        =>  'require'
+    ];
+    protected $message = [
+        'post_title.require'    =>  '文章标题不能为空',
+	    'post_content.require'  =>  '内容不能为空',
+	    'categories.require'    =>  '文章分类不能为空'
+    ];
+
+    protected $scene = [
+        'article'  => [ 'post_title' , 'post_content' , 'categories' ],
+        'page' => ['post_title']
+    ];
+}

+ 80 - 0
api/rob/controller/MyController.php

@@ -0,0 +1,80 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\rob\controller;
+
+use app\rob\model\OrderModel;
+use app\rob\model\OrderProductModel;
+use cmf\controller\RestBaseController;
+
+class MyController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['status'])) {
+            $where[] = ['status', '=', $param['status']];
+        }
+        $list = OrderModel::with('orderProduct')->where($where)
+            ->order('update_time desc')
+            ->page($page, $size)
+            ->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                foreach ($v['order_product'] as $product) {
+                    $product['product_image'] = cmf_get_image_preview_url($product['product_image']);
+                }
+                $v['create_time'] = date('Y-m-d H:i:s', $v['create_time']);
+                $v['status_text'] = $v['status_text'];
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 确认订单
+     */
+    public function orderConfirm()
+    {
+        $id = $this->request->param('id');
+
+        OrderModel::update(['status' => 2, 'receive_time' => time()], ['id' => $id]);
+        $this->success('操作成功');
+    }
+
+    /**
+     * 获取二维码
+     */
+    public function qrcode()
+    {
+        include('../extend/util/phpqrcode/qrlib.php');
+        $id = $this->request->param('id');
+        $product = OrderProductModel::where('order_id',$id)->find();
+        if (empty($product)) {
+            $this->error('该订单不支持二维码!');
+        }
+
+        return \QRcode::png($product['code']);
+//        $this->success('成功',);
+    }
+}

+ 111 - 0
api/rob/controller/ProductController.php

@@ -0,0 +1,111 @@
+<?php
+// +----------------------------------------------------------------------
+// | 文件说明:幻灯片
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: wuwu <15093565100@163.com>
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Date: 2017-5-25
+// +----------------------------------------------------------------------
+namespace api\rob\controller;
+
+use api\activity\model\ActivityJoinModel;
+use api\activity\model\ActivityModel;
+use api\activity\model\ActivitySiteJoinModel;
+use api\applet\model\UserModel;
+use app\rob\model\OrderModel;
+use app\rob\model\ProductModel;
+use cmf\controller\RestBaseController;
+
+class ProductController extends RestBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+        $page  = empty($param['page']) ? 1 : $param['page'];
+        $size  = empty($param['size']) ? 10 : $param['size'];
+
+        //搜索条件
+        $where = [['on_sale', '=', 1]];
+        $exp   = '(CASE WHEN start_time <= NOW() AND NOW() <= end_time THEN 1 ELSE 0 END) DESC,';
+        $exp   .= '(CASE WHEN start_time > NOW() THEN 1 ELSE 0 END) DESC,';
+        $exp   .= '(CASE WHEN end_time < NOW() THEN 1 ELSE 0 END) DESC';
+        $order = new \think\db\Expression($exp);
+        $list  = ProductModel::where($where)
+            ->order($order)
+            ->page($page, $size)
+            ->select();
+
+        //数据处理
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['image_main']  = cmf_get_image_preview_url($v['image_main']);
+                $v['status']      = $v['status'];
+                $v['status_text'] = $v['status_text'];
+                $v['type_text']   = $v['type_text'];
+            }
+        }
+
+        $this->success('成功', $list);
+    }
+
+    /**
+     * 详情
+     */
+    public function detail()
+    {
+        $id = $this->request->post('id');
+
+        $info               = ProductModel::get($id);
+        $info['type_text']  = $info['type_text'];
+        $info['image_main'] = cmf_get_image_preview_url($info['image_main']);
+        if (!empty($info['image_list'])) {
+            $options = [];
+            foreach ($info['image_list'] as $v) {
+                $options[] = [
+                    'url'  => cmf_get_file_download_url($v['url']),
+                    'name' => $v['name'],
+                ];
+            }
+            $info['image_list'] = $options;
+        }
+
+        $this->success('成功', $info);
+    }
+
+    /**
+     * 购买
+     */
+    public function buy()
+    {
+        $id = $this->request->post('id');
+
+        $model = new ProductModel();
+        $model->startTrans();
+        try {
+            $product = $model->lock(true)->where('id', $id)->find();
+            if ($this->user['score'] < $product['point']) {
+                throw new \Exception("积分不足,您的积分只有{$this->user['score']}!");
+            }
+            if ($product['quantity'] <= 0) {
+                throw new \Exception("商品数量不足!");
+            }
+            OrderModel::addOrder($this->user, $product, 1);
+            $product->quantity--;
+            $product->save();
+
+            $model->commit();
+        } catch (\Exception $e) {
+            $model->rollback();
+            $this->error($e->getMessage());
+        }
+
+        $this->success('抢购成功');
+    }
+}

+ 312 - 0
api/user/controller/ProfileController.php

@@ -0,0 +1,312 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+namespace api\user\controller;
+
+use api\portal\model\FeedbackModel;
+use api\user\model\JucaiMemberModel;
+use api\user\model\UserScoreLogModel;
+use cmf\controller\RestUserBaseController;
+use think\Db;
+use think\Validate;
+
+class ProfileController extends RestUserBaseController
+{
+    /**
+     * 用户密码修改
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function changePassword()
+    {
+        $validate = new Validate([
+            'old_password'     => 'require',
+            'password'         => 'require',
+            'confirm_password' => 'require|confirm:password',
+        ]);
+
+        $validate->message([
+            'old_password.require'     => '请输入您的旧密码!',
+            'password.require'         => '请输入您的新密码!',
+            'confirm_password.require' => '请输入确认密码!',
+            'confirm_password.confirm' => '两次输入的密码不一致!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        $userId       = $this->getUserId();
+        $userPassword = Db::name("user")->where('id', $userId)->value('user_pass');
+
+        if (!cmf_compare_password($data['old_password'], $userPassword)) {
+            $this->error('旧密码不正确!');
+        }
+
+        Db::name("user")->where('id', $userId)->update(['user_pass' => cmf_password($data['password'])]);
+
+        $this->success("密码修改成功!");
+
+    }
+
+    /**
+     * 用户绑定邮箱
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function bindingEmail()
+    {
+        $validate = new Validate([
+            'email'             => 'require|email|unique:user,user_email',
+            'verification_code' => 'require',
+        ]);
+
+        $validate->message([
+            'email.require'             => '请输入您的邮箱!',
+            'email.email'               => '请输入正确的邮箱格式!',
+            'email.unique'              => '邮箱账号已存在!',
+            'verification_code.require' => '请输入数字验证码!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        $userId    = $this->getUserId();
+        $userEmail = Db::name("user")->where('id', $userId)->value('user_email');
+
+        if (!empty($userEmail)) {
+            $this->error("您已经绑定邮箱!");
+        }
+
+        $errMsg = cmf_check_verification_code($data['email'], $data['verification_code']);
+        if (!empty($errMsg)) {
+            $this->error($errMsg);
+        }
+
+        Db::name("user")->where('id', $userId)->update(['user_email' => $data['email']]);
+
+        $this->success("绑定成功!");
+    }
+
+    /**
+     * 用户绑定手机号
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function bindingMobile()
+    {
+        $validate = new Validate([
+            'mobile'            => 'require|unique:user,mobile',
+            'verification_code' => 'require',
+        ]);
+
+        $validate->message([
+            'mobile.require'            => '请输入您的手机号!',
+            'mobile.unique'             => '手机号已经存在!',
+            'verification_code.require' => '请输入数字验证码!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        if (!cmf_check_mobile($data['mobile'])) {
+            $this->error("请输入正确的手机格式!");
+        }
+
+
+        $userId = $this->getUserId();
+        $mobile = Db::name("user")->where('id', $userId)->value('mobile');
+
+        if (!empty($mobile)) {
+            $this->error("您已经绑定手机!");
+        }
+
+        $errMsg = cmf_check_verification_code($data['mobile'], $data['verification_code']);
+        if (!empty($errMsg)) {
+            $this->error($errMsg);
+        }
+
+        Db::name("user")->where('id', $userId)->update(['mobile' => $data['mobile']]);
+
+        $this->success("绑定成功!");
+    }
+
+    /**
+     * 用户基本信息获取及修改
+     * @param string $field 需要获取的字段名
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function userInfo($field = '')
+    {
+        //判断请求为GET,获取信息
+        if ($this->request->isGet()) {
+            $userId   = $this->getUserId();
+            $fieldStr = 'id,user_name,user_idcard,user_type,user_login,mobile,user_email,user_nickname,avatar,signature,user_url,sex,birthday,score,coin,user_status,user_activation_key,create_time,last_login_time,last_login_ip,company_name,job,is_talent,talent_level,talent_card,jucai_id';
+            if (empty($field)) {
+                $userData = Db::name("user")->field($fieldStr)->find($userId);
+            } else {
+                $fieldArr     = explode(',', $fieldStr);
+                $postFieldArr = explode(',', $field);
+                $mixedField   = array_intersect($fieldArr, $postFieldArr);
+                if (empty($mixedField)) {
+                    $this->error('您查询的信息不存在!');
+                }
+                if (count($mixedField) > 1) {
+                    $fieldStr = implode(',', $mixedField);
+                    $userData = Db::name("user")->field($fieldStr)->find($userId);
+                } else {
+                    $userData = Db::name("user")->where('id', $userId)->value($mixedField);
+                }
+            }
+            $this->success('获取成功!', $userData);
+        }
+        //判断请求为POST,修改信息
+        if ($this->request->isPost()) {
+            $userId   = $this->getUserId();
+            $fieldStr = 'user_login,user_name,user_idcard,mobile,user_nickname,avatar,signature,user_url,sex,birthday,company_name,job,is_talent,talent_level,talent_card';
+            $data     = $this->request->post();
+            if (empty($data)) {
+                $this->error('修改失败,提交表单为空!');
+            }
+
+            if (!empty($data['birthday'])) {
+                $data['birthday'] = strtotime($data['birthday']);
+            }
+
+            $upData = Db::name("user")->where('id', $userId)->field($fieldStr)->update($data);
+            if ($upData !== false) {
+                $this->success('修改成功!');
+            } else {
+                $this->error('修改失败!');
+            }
+        }
+    }
+
+    // 用户头像上传
+    public function avatarUpload()
+    {
+        $file   = $this->request->file('file');
+        $result = $file->validate([
+            'ext'  => 'jpg,jpeg,png',
+            'size' => 1024 * 1024,
+        ])->move(WEB_ROOT . 'upload' . DIRECTORY_SEPARATOR . 'avatar' . DIRECTORY_SEPARATOR);
+
+        if ($result) {
+            $avatarSaveName = str_replace('//', '/', str_replace('\\', '/', $result->getSaveName()));
+            $avatar         = cmf_get_user_avatar_url('avatar/' . $avatarSaveName);
+            $userId         = $this->getUserId();
+            Db::name("user")->where('id', $userId)->update(['avatar' => $avatar]);
+
+            $this->success('上传成功', $avatar);
+        } else {
+            $this->error($file->getError());
+        }
+    }
+
+    /**
+     * 意见反馈
+     */
+    public function feedback()
+    {
+        $data = $this->request->post();
+        if (empty($data['content'])) {
+            $this->error('请输入意见或建议!');
+        }
+
+        $data['user_id']     = $this->getUserId();
+        $data['create_time'] = time();
+        FeedbackModel::create($data);
+
+        $this->success('反馈成功!');
+    }
+
+    /**
+     * 聚才网用户信息
+     */
+    public function jucaiInfo()
+    {
+        $userId = $this->getUserId();
+        $user   = Db::name("user")->find($userId);
+        if (empty($user['jucai_id'])) {
+            $this->success('获取成功!', []);
+        } else {
+            $info = JucaiMemberModel::field('id,username,email,mobile,status')->where('id', $user['jucai_id'])->find();
+            $this->success('获取成功!', $info);
+        }
+    }
+
+    /**
+     * 绑定聚才网帐号
+     */
+    public function bindJucai()
+    {
+        //验证手机号
+        $validate = new Validate([
+            'mobile'   => 'require',
+            'password' => 'require',
+        ]);
+        $validate->message([
+            'mobile.require'   => '请输入您的手机号!',
+            'password.require' => '请输入数字验证码!',
+        ]);
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+        if (!cmf_check_mobile($data['mobile'])) {
+            $this->error("请输入正确的手机格式!");
+        }
+
+        //获取聚才网信息
+        $info = JucaiMemberModel::get(['mobile' => $data['mobile']]);
+        if (empty($info)) {
+            $this->error('该手机号不存在,请先去聚才网注册!');
+        }
+        if (!password_verify($data['password'], $info['password'])) {
+            $this->error('密码错误!');
+        }
+
+        $userId = $this->getUserId();
+        Db::name("user")->where('id', $userId)->update(['jucai_id' => $info['id']]);
+        $this->success('绑定成功!', ['id' => $info['id']]);
+    }
+
+    /**
+     * 积分列表
+     */
+    public function scoreList()
+    {
+        $userId = $this->getUserId();
+
+        $page = $this->request->param('page');
+        $size = $this->request->param('size');
+        $list = UserScoreLogModel::where('user_id', $userId)->order('create_time desc')->page($page, $size)->select();
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                $v['create_time'] = date('Y-m-d H:i:s', $v['create_time']);
+            }
+        }
+
+        $this->success('', $list);
+    }
+}

+ 222 - 0
api/user/controller/PublicController.php

@@ -0,0 +1,222 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: Dean <zxxjjforever@163.com>
+// +----------------------------------------------------------------------
+namespace api\user\controller;
+
+use think\Db;
+use think\facade\Validate;
+use cmf\controller\RestBaseController;
+
+class PublicController extends RestBaseController
+{
+    /**
+     *  用户注册
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function register()
+    {
+        $validate = new \think\Validate([
+            'username' => 'require',
+            'password' => 'require',
+        ]);
+
+        $validate->message([
+            'username.require' => '请输入用户名!',
+            'password.require' => '请输入您的密码!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        $findUserCount = Db::name("user")->where('user_login', $data['username'])->count();
+
+        if ($findUserCount > 0) {
+            $this->error("此账号已存在!");
+        }
+
+        $user                = [];
+        $user['user_login']  = $data['username'];
+        $user['create_time'] = time();
+        $user['user_status'] = 1;
+        $user['user_type']   = 2;
+        $user['user_pass']   = cmf_password($data['password']);
+
+        $result = Db::name("user")->insert($user);
+
+
+        if (empty($result)) {
+            $this->error("注册失败,请重试!");
+        }
+
+        $this->success("注册并激活成功,请登录!");
+
+    }
+
+    /**
+     * 用户登录
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    // TODO 增加最后登录信息记录,如 ip
+    public function login()
+    {
+        $validate = new \think\Validate([
+            'username' => 'require',
+            'password' => 'require',
+        ]);
+        $validate->message([
+            'username.require' => '请输入用户名!',
+            'password.require' => '请输入您的密码!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        $findUserWhere = [];
+
+        $findUserWhere['user_login'] = $data['username'];
+
+        $findUser = Db::name("user")->where($findUserWhere)->find();
+
+        if (empty($findUser)) {
+            $this->error("用户不存在!");
+        } else {
+
+            switch ($findUser['user_status']) {
+                case 0:
+                    $this->error('您已被拉黑!');
+                case 2:
+                    $this->error('账户还没有验证成功!');
+            }
+
+            if (!cmf_compare_password($data['password'], $findUser['user_pass'])) {
+                $this->error("密码不正确!");
+            }
+        }
+
+        $allowedDeviceTypes = $this->allowedDeviceTypes;
+
+        if (empty($this->deviceType) && (empty($data['device_type']) || !in_array($data['device_type'], $this->allowedDeviceTypes))) {
+            $this->error("请求错误,未知设备!");
+        } else if (!empty($data['device_type'])) {
+            $this->deviceType = $data['device_type'];
+        }
+
+//        Db::name("user_token")
+//            ->where('user_id', $findUser['id'])
+//            ->where('device_type', $data['device_type']);
+        $findUserToken = Db::name("user_token")
+            ->where('user_id', $findUser['id'])
+            ->where('device_type', $this->deviceType)
+            ->find();
+        $currentTime   = time();
+        $expireTime    = $currentTime + 24 * 3600 * 180;
+        $token         = md5(uniqid()) . md5(uniqid());
+        if (empty($findUserToken)) {
+            $result = Db::name("user_token")->insert([
+                'token'       => $token,
+                'user_id'     => $findUser['id'],
+                'expire_time' => $expireTime,
+                'create_time' => $currentTime,
+                'device_type' => $this->deviceType,
+            ]);
+        } else {
+            $result = Db::name("user_token")
+                ->where('user_id', $findUser['id'])
+                ->where('device_type', $this->deviceType)
+                ->update([
+                    'token'       => $token,
+                    'expire_time' => $expireTime,
+                    'create_time' => $currentTime,
+                ]);
+        }
+
+
+        if (empty($result)) {
+            $this->error("登录失败!");
+        }
+
+        $this->success("登录成功!", ['token' => $token, 'user' => $findUser]);
+    }
+
+    /**
+     * 用户退出
+     * @throws \think\Exception
+     * @throws \think\exception\PDOException
+     */
+    public function logout()
+    {
+        $userId = $this->getUserId();
+        Db::name('user_token')->where([
+            'token'       => $this->token,
+            'user_id'     => $userId,
+            'device_type' => $this->deviceType,
+        ])->update(['token' => '']);
+
+        $this->success("退出成功!");
+    }
+
+    /**
+     * 用户密码重置
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function passwordReset()
+    {
+        $validate = new \think\Validate([
+            'username'          => 'require',
+            'password'          => 'require',
+            'verification_code' => 'require',
+        ]);
+
+        $validate->message([
+            'username.require'          => '请输入手机号,邮箱!',
+            'password.require'          => '请输入您的密码!',
+            'verification_code.require' => '请输入数字验证码!',
+        ]);
+
+        $data = $this->request->param();
+        if (!$validate->check($data)) {
+            $this->error($validate->getError());
+        }
+
+        $userWhere = [];
+        if (Validate::is($data['username'], 'email')) {
+            $userWhere['user_email'] = $data['username'];
+        } else if (cmf_check_mobile($data['username'])) {
+            $userWhere['mobile'] = $data['username'];
+        } else {
+            $this->error("请输入正确的手机或者邮箱格式!");
+        }
+
+        $errMsg = cmf_check_verification_code($data['username'], $data['verification_code']);
+        if (!empty($errMsg)) {
+            $this->error($errMsg);
+        }
+
+        $userPass = cmf_password($data['password']);
+        Db::name("user")->where($userWhere)->update(['user_pass' => $userPass]);
+
+        $this->success("密码重置成功,请使用新密码登录!");
+
+    }
+}

+ 20 - 0
api/user/model/JucaiMemberModel.php

@@ -0,0 +1,20 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace api\user\model;
+
+use think\Model;
+
+class JucaiMemberModel extends Model
+{
+    protected $connection = 'jucai';
+    protected $table = 'members';
+
+}

+ 150 - 0
app/activity/controller/AdminActivityController.php

@@ -0,0 +1,150 @@
+<?php
+
+namespace app\activity\controller;
+
+use app\activity\model\ActivityModel;
+use app\activity\model\ActivityReviewModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $this->checkSite();
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['user_id', '=', $sessionAdminId]];
+        if (!empty($param['start_time'])) {
+            $where[] = ['start_time', '>=', strtotime($param['start_time'])];
+        }
+        if (!empty($param['end_time'])) {
+            $where[] = ['end_time', '<=', strtotime($param['end_time'])];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $activity = ActivityModel::where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('activity', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加活动
+     */
+    public function add()
+    {
+        return $this->fetch();
+    }
+
+    /**
+     * 添加活动提交
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data                = $this->request->post();
+            $data['create_time'] = time();
+            $data['user_id']     = session('ADMIN_ID');
+            $data['signin_code'] = randomStr(16);
+            $data['options']     = [];
+            if (!empty($data['file_urls'])) {
+                foreach ($data['file_urls'] as $k => $v) {
+                    $data['options'][] = ['url' => $v, 'name' => $data['file_names'][$k]];
+                }
+            }
+            ActivityModel::create($data);
+
+            $this->success('添加成功!', url('AdminActivity/index'));
+        }
+    }
+
+    /**
+     * 编辑活动
+     */
+    public function edit()
+    {
+        $id   = $this->request->param('id', 0, 'intval');
+        $info = ActivityModel::get($id);
+        $this->assign('info', $info);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑活动提交
+     */
+    public function editPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+            $data['status']  = 1;
+            $data['options'] = [];
+            if (!empty($data['file_urls'])) {
+                foreach ($data['file_urls'] as $k => $v) {
+                    $data['options'][] = ['url' => $v, 'name' => $data['file_names'][$k]];
+                }
+            }
+
+            ActivityModel::update($data, ['id' => $data['id']]);
+
+            $this->success('编辑成功!', url('AdminActivity/index'));
+        }
+    }
+
+    /**
+     * 删除
+     */
+    public function delete()
+    {
+        $id   = $this->request->param('id', 0, 'intval');
+        $info = ActivityModel::get($id);
+        if ($info['join_num'] > 0) {
+            $this->error('该活动已有人参加,无法删除');
+        }
+
+        $review = ActivityReviewModel::where('activity_id', $id)->find();
+        if (!empty($review)) {
+            $this->error('该活动已有活动回顾,无法删除');
+        }
+
+        $this->success('删除成功');
+    }
+
+    /**
+     * 选择活动
+     */
+    public function select()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [
+            ['have_review', '=', 2],
+            ['status', '=', 2],
+            ['user_id', '=', $sessionAdminId],
+        ];
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+
+        $activity = ActivityModel::where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('activity', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+}

+ 96 - 0
app/activity/controller/AdminActivityJoinController.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace app\activity\controller;
+
+use app\activity\model\ActivityJoinModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityJoinController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $this->checkSite();
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['a.user_id', '=', $sessionAdminId]];
+        if (!empty($param['keyword'])) {
+            $where[] = ['a.title', 'like', "%{$param['keyword']}%"];
+        }
+        if (!empty($param['activity_id'])) {
+            $where[] = ['aj.activity_id', '=', $param['activity_id']];
+        }
+        if (isset($param['status']) && ($param['status'] === '0' || $param['status'] > 0)) {
+            $where[] = ['aj.status', '=', $param['status']];
+        }
+        $activity = ActivityJoinModel::alias('aj')
+            ->leftJoin('activity a', 'aj.activity_id = a.id')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,a.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->paginate(10, false, ['query' => $param]);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('activity_id', isset($param['activity_id']) ? $param['activity_id'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('activity', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+
+    public function export()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['a.user_id', '=', $sessionAdminId]];
+        if (!empty($param['ids'])) {
+            $where[] = ['aj.id', 'in', $param['ids']];
+        } else {
+            if (!empty($param['keyword'])) {
+                $where[] = ['a.title', 'like', "%{$param['keyword']}%"];
+            }
+            if (!empty($param['activity_id'])) {
+                $where[] = ['aj.activity_id', '=', $param['activity_id']];
+            }
+            if (isset($param['status']) && ($param['status'] === '0' || $param['status'] > 0)) {
+                $where[] = ['aj.status', '=', $param['status']];
+            }
+        }
+
+        //获取数据
+        $activity = ActivityJoinModel::alias('aj')
+            ->leftJoin('activity a', 'aj.activity_id = a.id')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,a.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->select();
+
+        //表头
+        $header = [
+            'id'          => 'id',
+            'activity_id' => '活动id',
+            'title'       => '活动标题',
+            'user_id'     => '用户id',
+            'user_name'   => '用户姓名',
+            'mobile'      => '用户电话',
+            'status'      => '状态',
+            'create_time' => '报名时间',
+        ];
+
+        //表格内容
+        foreach ($activity as $v) {
+            $v['create_time'] = date('Y-m-d H:i', $v['create_time']);
+            $v['status']      = $v['status'] == 0 ? '未签到' : '已签到';
+        }
+        export_excel_data($header, $activity, '报名列表', ['mobile','create_time']);
+    }
+}

+ 98 - 0
app/activity/controller/AdminActivityReviewController.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace app\activity\controller;
+
+use app\activity\model\ActivityModel;
+use app\activity\model\ActivityReviewModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityReviewController extends AdminBaseController
+{
+    /**
+     * 回顾列表
+     */
+    public function index()
+    {
+        $this->checkSite();
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['user_id', '=', $sessionAdminId]];
+        if (!empty($param['activity_id'])) {
+            $where[] = ['activity_id', '=', $param['activity_id']];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $activity = ActivityReviewModel::with('activity')->where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('activity_id', isset($param['activity_id']) ? $param['activity_id'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('list', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加回顾
+     */
+    public function add()
+    {
+        return $this->fetch();
+    }
+
+    /**
+     * 添加回顾提交
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+
+            $review = ActivityReviewModel::where('activity_id', $data['activity_id'])->find();
+            if (!empty($review)) {
+                $this->error('该活动已有回顾,请勿重复添加');
+            }
+
+            $data['create_time'] = time();
+            ActivityReviewModel::create($data);
+            ActivityModel::update(['have_review' => 1, 'id' => $data['activity_id']]);
+
+            $this->success('添加成功!', url('AdminActivityReview/index'));
+        }
+    }
+
+    /**
+     * 编辑活动
+     */
+    public function edit()
+    {
+        $id   = $this->request->param('id', 0, 'intval');
+        $info = ActivityReviewModel::get($id, ['activity']);
+        $this->assign('info', $info);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑活动提交
+     */
+    public function editPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->post();
+
+            $review = ActivityReviewModel::where('activity_id', $data['activity_id'])->where('id', '<>', $data['id'])->find();
+            if (!empty($review)) {
+                $this->error('该活动已有回顾,请勿重复添加');
+            }
+
+            ActivityReviewModel::update($data, ['id' => $data['id']]);
+            ActivityModel::update(['have_review' => 1, 'id' => $data['activity_id']]);
+
+            $this->success('编辑成功!', url('AdminActivityReview/index'));
+        }
+    }
+}

+ 60 - 0
app/activity/controller/AdminActivitySiteJoinController.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace app\activity\controller;
+
+use api\activity\model\ActivitySiteJoinModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivitySiteJoinController extends AdminBaseController
+{
+    /**
+     * 列表
+     */
+    public function index()
+    {
+        $this->checkSite();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['aj.site_id', '=', $sessionAdminId]];
+        $list           = ActivitySiteJoinModel::alias('aj')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->paginate(10);
+
+        $this->assign('list', $list->items());
+        $this->assign('page', $list->render());
+
+        return $this->fetch();
+    }
+
+    public function export()
+    {
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['aj.site_id', '=', $sessionAdminId]];
+        $list           = ActivitySiteJoinModel::alias('aj')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->select();
+
+        //表头
+        $header = [
+            'user_id'     => '用户id',
+            'user_name'   => '用户姓名',
+            'mobile'      => '用户电话',
+            'create_time' => '首次报名时间',
+        ];
+
+        //表格内容
+        foreach ($list as $v) {
+            $v['create_time'] = date('Y-m-d H:i', $v['create_time']);
+        }
+
+        export_excel_data($header, $list, '往届报名列表', ['mobile', 'create_time']);
+    }
+}

+ 66 - 0
app/activity/controller/AdminSiteController.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace app\activity\controller;
+
+use app\activity\model\ActivitySiteModel;
+use cmf\controller\AdminBaseController;
+
+class AdminSiteController extends AdminBaseController
+{
+    /**
+     * 站点列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['keyword'])) {
+            $where[] = ['site_name', 'like', "%{$param['keyword']}%"];
+        }
+        $list = ActivitySiteModel::where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('list', $list->items());
+        $this->assign('page', $list->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 站点信息
+     */
+    public function info()
+    {
+        $sessionAdminId = session('ADMIN_ID');
+        $info           = ActivitySiteModel::get($sessionAdminId);
+        if (empty($info)) {
+            $info = [];
+        }
+
+        $this->assign('info', $info);
+        return $this->fetch();
+    }
+
+    /**
+     * 站点信息提交
+     */
+    public function infoPost()
+    {
+        if ($this->request->isPost()) {
+            $param          = $this->request->post();
+            $sessionAdminId = session('ADMIN_ID');
+            $info           = ActivitySiteModel::get($sessionAdminId);
+            if (empty($info)) {
+                $param['id']          = $sessionAdminId;
+                $param['create_time'] = time();
+                ActivitySiteModel::create($param);
+            } else {
+                ActivitySiteModel::update($param, ['id' => $sessionAdminId]);
+            }
+
+            $this->success('保存成功');
+        }
+    }
+}

+ 23 - 0
app/activity/model/ActivityJoinModel.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\activity\model;
+
+use app\user\model\UserModel;
+use think\Model;
+
+class ActivityJoinModel extends Model
+{
+
+    public function user()
+    {
+        return $this->hasOne(UserModel::Class,'id','user_id');
+    }
+}

+ 39 - 0
app/activity/model/ActivityModel.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\activity\model;
+
+use think\Model;
+use think\model\concern\SoftDelete;
+
+class ActivityModel extends Model
+{
+    use SoftDelete;
+    protected $type = ['options' => 'array'];
+
+    /**
+     * 状态名
+     */
+    public function getStatusTextAttr($value, $data)
+    {
+        $status = ['待审核', '通过', '不通过'];
+        return $status[$data['status'] - 1];
+    }
+
+    public function setStartTimeAttr($value)
+    {
+        return strtotime($value);
+    }
+
+    public function setEndTimeAttr($value)
+    {
+        return strtotime($value);
+    }
+}

+ 23 - 0
app/activity/model/ActivityReviewModel.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\activity\model;
+
+use think\Model;
+
+class ActivityReviewModel extends Model
+{
+
+    public function activity()
+    {
+        return $this->hasOne(ActivityModel::class,'id','activity_id');
+    }
+
+}

+ 19 - 0
app/activity/model/ActivitySiteModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\activity\model;
+
+use think\Model;
+
+class ActivitySiteModel extends Model
+{
+
+
+}

+ 94 - 0
app/activitymanage/controller/AdminActivityJoinManageController.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace app\activitymanage\controller;
+
+use app\activity\model\ActivityJoinModel;
+use app\activity\model\ActivityModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityJoinManageController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where          = [];
+        if (!empty($param['keyword'])) {
+            $where[] = ['a.title', 'like', "%{$param['keyword']}%"];
+        }
+        if (!empty($param['activity_id'])) {
+            $where[] = ['aj.activity_id', '=', $param['activity_id']];
+        }
+        if (isset($param['status']) && ($param['status'] === '0' || $param['status'] > 0)) {
+            $where[] = ['aj.status', '=', $param['status']];
+        }
+        $activity = ActivityJoinModel::alias('aj')
+            ->leftJoin('activity a', 'aj.activity_id = a.id')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,a.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->paginate(10, false, ['query' => $param]);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('activity_id', isset($param['activity_id']) ? $param['activity_id'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('activity', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+
+    public function export()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where          = [];
+        if (!empty($param['ids'])) {
+            $where[] = ['aj.id', 'in', $param['ids']];
+        } else {
+            if (!empty($param['keyword'])) {
+                $where[] = ['a.title', 'like', "%{$param['keyword']}%"];
+            }
+            if (!empty($param['activity_id'])) {
+                $where[] = ['aj.activity_id', '=', $param['activity_id']];
+            }
+            if (isset($param['status']) && ($param['status'] === '0' || $param['status'] > 0)) {
+                $where[] = ['aj.status', '=', $param['status']];
+            }
+        }
+
+        //获取数据
+        $activity = ActivityJoinModel::alias('aj')
+            ->leftJoin('activity a', 'aj.activity_id = a.id')
+            ->leftJoin('user u', 'aj.user_id = u.id')
+            ->field('aj.*,a.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->select();
+
+        //表头
+        $header = [
+            'id'          => 'id',
+            'activity_id' => '活动id',
+            'title'       => '活动标题',
+            'user_id'     => '用户id',
+            'user_name'   => '用户姓名',
+            'mobile'      => '用户电话',
+            'status'      => '状态',
+            'create_time' => '报名时间',
+        ];
+
+        //表格内容
+        foreach ($activity as $v) {
+            $v['create_time'] = date('Y-m-d H:i', $v['create_time']);
+            $v['status']      = $v['status'] == 0 ? '未签到' : '已签到';
+        }
+        export_excel_data($header, $activity, '报名列表', ['mobile','create_time']);
+    }
+}

+ 63 - 0
app/activitymanage/controller/AdminActivityManageController.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace app\activitymanage\controller;
+
+use app\activity\model\ActivityModel;
+use app\activity\model\ActivityReviewModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityManageController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['start_time'])) {
+            $where[] = ['start_time', '>=', strtotime($param['start_time'])];
+        }
+        if (!empty($param['end_time'])) {
+            $where[] = ['end_time', '<=', strtotime($param['end_time'])];
+        }
+        if (!empty($param['status'])) {
+            $where[] = ['status', '=', $param['status']];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $activity = ActivityModel::where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('activity', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 审核
+     */
+    public function check()
+    {
+        $param = $this->request->param();
+        $this->assign('id', $param['id']);
+        return $this->fetch();
+    }
+
+    /**
+     * 审核提交
+     */
+    public function checkPost()
+    {
+        $param = $this->request->param();
+        ActivityModel::update($param, ['id' => $param['id']]);
+        $this->success('审核成功');
+    }
+}

+ 36 - 0
app/activitymanage/controller/AdminActivityReviewManageController.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace app\activitymanage\controller;
+
+use app\activity\model\ActivityModel;
+use app\activity\model\ActivityReviewModel;
+use cmf\controller\AdminBaseController;
+
+class AdminActivityReviewManageController extends AdminBaseController
+{
+    /**
+     * 回顾列表
+     */
+    public function index()
+    {
+        $this->checkSite();
+        $param = $this->request->param();
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['activity_id'])) {
+            $where[] = ['activity_id', '=', $param['activity_id']];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $activity = ActivityReviewModel::with('activity')->where($where)->order('create_time', 'DESC')->paginate(10, false, ['query' => $param]);
+
+        $this->assign('activity_id', isset($param['activity_id']) ? $param['activity_id'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('list', $activity->items());
+        $this->assign('page', $activity->render());
+
+        return $this->fetch();
+    }
+}

+ 133 - 0
app/admin/controller/MainController.php

@@ -0,0 +1,133 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\admin\controller;
+
+use app\activity\model\ActivityModel;
+use app\activity\model\ActivitySiteModel;
+use app\admin\model\UserModel;
+use app\business\model\BusinessJoinModel;
+use app\business\model\BusinessModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+class MainController extends AdminBaseController
+{
+
+    /**
+     *  后台欢迎页
+     */
+    public function index()
+    {
+        $dashboardWidgets = [];
+        $widgets          = cmf_get_option('admin_dashboard_widgets');
+
+        $defaultDashboardWidgets = [
+            '_SystemCmfHub'           => ['name' => 'CmfHub', 'is_system' => 1],
+            '_SystemCmfDocuments'     => ['name' => 'CmfDocuments', 'is_system' => 1],
+            '_SystemMainContributors' => ['name' => 'MainContributors', 'is_system' => 1],
+            '_SystemContributors'     => ['name' => 'Contributors', 'is_system' => 1],
+            '_SystemCustom1'          => ['name' => 'Custom1', 'is_system' => 1],
+            '_SystemCustom2'          => ['name' => 'Custom2', 'is_system' => 1],
+            '_SystemCustom3'          => ['name' => 'Custom3', 'is_system' => 1],
+            '_SystemCustom4'          => ['name' => 'Custom4', 'is_system' => 1],
+            '_SystemCustom5'          => ['name' => 'Custom5', 'is_system' => 1],
+        ];
+
+        if (empty($widgets)) {
+            $dashboardWidgets = $defaultDashboardWidgets;
+        } else {
+            foreach ($widgets as $widget) {
+                if ($widget['is_system']) {
+                    $dashboardWidgets['_System' . $widget['name']] = ['name' => $widget['name'], 'is_system' => 1];
+                } else {
+                    $dashboardWidgets[$widget['name']] = ['name' => $widget['name'], 'is_system' => 0];
+                }
+            }
+
+            foreach ($defaultDashboardWidgets as $widgetName => $widget) {
+                $dashboardWidgets[$widgetName] = $widget;
+            }
+
+
+        }
+
+        $dashboardWidgetPlugins = [];
+
+        $hookResults = hook('admin_dashboard');
+
+        if (!empty($hookResults)) {
+            foreach ($hookResults as $hookResult) {
+                if (isset($hookResult['width']) && isset($hookResult['view']) && isset($hookResult['plugin'])) { //验证插件返回合法性
+                    $dashboardWidgetPlugins[$hookResult['plugin']] = $hookResult;
+                    if (!isset($dashboardWidgets[$hookResult['plugin']])) {
+                        $dashboardWidgets[$hookResult['plugin']] = ['name' => $hookResult['plugin'], 'is_system' => 0];
+                    }
+                }
+            }
+        }
+
+        $smtpSetting = cmf_get_option('smtp_setting');
+
+        $this->assign('dashboard_widgets', $dashboardWidgets);
+        $this->assign('dashboard_widget_plugins', $dashboardWidgetPlugins);
+        $this->assign('has_smtp_setting', empty($smtpSetting) ? false : true);
+
+        $admin_id = session('ADMIN_ID');
+        $this->assign('admin_id', session('ADMIN_ID'));
+        if ($admin_id == 1) {
+            $activityCount = ActivityModel::count();
+            $this->assign('activity_count', $activityCount);
+
+            $userCount = UserModel::where('user_type', 2)->count();
+            $this->assign('user_count', $userCount);
+        } else {
+            $role_id = Db::name('role_user')->where('user_id',$admin_id)->value('role_id');
+            $this->assign('role_id', $role_id);
+            if ($role_id == 2) {
+                $activityCount = ActivityModel::where('user_id', $admin_id)->count();
+                $this->assign('activity_count', $activityCount);
+
+                $info = ActivitySiteModel::get($admin_id);
+                $this->assign('info', $info);
+            } elseif ($role_id == 3) {
+                $joinCount = BusinessJoinModel::where('business_id',$admin_id)->count();
+                $this->assign('join_count',$joinCount);
+
+                $info = BusinessModel::get($admin_id);
+                $this->assign('info',$info);
+            }
+
+        }
+
+        return $this->fetch();
+    }
+
+    public function dashboardWidget()
+    {
+        $dashboardWidgets = [];
+        $widgets          = $this->request->param('widgets/a');
+        if (!empty($widgets)) {
+            foreach ($widgets as $widget) {
+                if ($widget['is_system']) {
+                    array_push($dashboardWidgets, ['name' => $widget['name'], 'is_system' => 1]);
+                } else {
+                    array_push($dashboardWidgets, ['name' => $widget['name'], 'is_system' => 0]);
+                }
+            }
+        }
+
+        cmf_set_option('admin_dashboard_widgets', $dashboardWidgets, true);
+
+        $this->success('更新成功!');
+
+    }
+
+}

+ 69 - 0
app/business/controller/AdminBusinessController.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace app\business\controller;
+
+use app\activity\model\ActivitySiteModel;
+use app\business\model\BusinessModel;
+use cmf\controller\AdminBaseController;
+
+class AdminBusinessController extends AdminBaseController
+{
+    /**
+     * 信息
+     */
+    public function info()
+    {
+        $sessionAdminId = session('ADMIN_ID');
+        $info = BusinessModel::get($sessionAdminId);
+        if (empty($info)) {
+            $info = [];
+        }
+
+        //信息处理
+        if (!empty($info['tags'])) {
+            $info['tags'] = implode(' ',json_decode($info['tags'],true));
+        }
+
+        $this->assign('info', $info);
+        return $this->fetch();
+    }
+
+    /**
+     * 信息提交
+     */
+    public function infoPost()
+    {
+        if ($this->request->isPost()) {
+            $param = $this->request->post();
+            $sessionAdminId = session('ADMIN_ID');
+
+            //地址
+            if (empty($param['longitude']) || empty($param['latitude']) || empty($param['address'])) {
+                $this->error('请在地图上选择地址');
+            }
+
+            //标签处理
+            if (!empty($param['tags'])) {
+                $tags = explode(' ',$param['tags']);
+                foreach ($tags as $k => $t) {
+                    if (empty(trim($t))) {
+                        unset($tags[$k]);
+                    }
+                    $tags[$k] = trim($t);
+                }
+                $param['tags'] = json_encode(array_values($tags));
+            }
+
+            $info = BusinessModel::get($sessionAdminId);
+            if (empty($info)) {
+                $param['id'] = $sessionAdminId;
+                $param['create_time'] = time();
+                BusinessModel::create($param);
+            } else {
+                BusinessModel::update($param, ['id' => $sessionAdminId]);
+            }
+
+            $this->success('保存成功');
+        }
+    }
+}

+ 99 - 0
app/business/controller/AdminBusinessJoinController.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace app\business\controller;
+
+use app\business\model\BusinessJoinModel;
+use cmf\controller\AdminBaseController;
+
+class AdminBusinessJoinController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['bj.business_id', '=', $sessionAdminId]];
+        if (!empty($param['start_time'])) {
+            $where[] = ['bj.create_time', '>=', strtotime($param['start_time'])];
+        }
+        if (!empty($param['end_time'])) {
+            $where[] = ['bj.create_time', '<=', strtotime($param['end_time'])];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['u.user_name', 'like', "%{$param['keyword']}%"];
+        }
+        if (!empty($param['status'])) {
+            $where[] = ['bj.status', '=', $param['status']];
+        }
+        $list = BusinessJoinModel::alias('bj')
+            ->leftJoin('user u', 'bj.user_id = u.id')
+            ->field('bj.*,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->paginate(10, false, ['query' => $param]);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('list', $list->items());
+        $this->assign('page', $list->render());
+
+        return $this->fetch();
+    }
+
+    public function export()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['bj.business_id', '=', $sessionAdminId]];
+        if (!empty($param['ids'])) {
+            $where[] = ['bj.id', 'in', $param['ids']];
+        } else {
+            if (!empty($param['start_time'])) {
+                $where[] = ['bj.create_time', '>=', strtotime($param['start_time'])];
+            }
+            if (!empty($param['end_time'])) {
+                $where[] = ['bj.create_time', '<=', strtotime($param['end_time'])];
+            }
+            if (!empty($param['keyword'])) {
+                $where[] = ['u.user_name', 'like', "%{$param['keyword']}%"];
+            }
+            if (!empty($param['status'])) {
+                $where[] = ['bj.status', '=', $param['status']];
+            }
+        }
+
+        //获取数据
+        $activity = BusinessJoinModel::alias('bj')
+            ->leftJoin('user u', 'bj.user_id = u.id')
+            ->field('bj.*,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->select();
+
+        //表头
+        $header = [
+            'id'          => 'id',
+            'user_id'     => '用户id',
+            'user_name'   => '用户姓名',
+            'mobile'      => '用户电话',
+            'comment'     => '用户备注',
+            'status'      => '状态',
+            'create_time' => '报名时间',
+        ];
+
+        //表格内容
+        foreach ($activity as $v) {
+            $v['create_time'] = date('Y-m-d H:i', $v['create_time']);
+            $v['status']      = $v['status'] == 1 ? '已参加' : '不允许参加';
+        }
+        export_excel_data($header, $activity, '商家活动参与列表', ['mobile', 'create_time']);
+    }
+}

+ 19 - 0
app/business/model/BusinessJoinModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\business\model;
+
+use think\Model;
+
+class BusinessJoinModel extends Model
+{
+
+
+}

+ 19 - 0
app/business/model/BusinessModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\business\model;
+
+use think\Model;
+
+class BusinessModel extends Model
+{
+
+
+}

+ 109 - 0
app/businessmanage/controller/AdminBusinessJoinManageController.php

@@ -0,0 +1,109 @@
+<?php
+
+namespace app\businessmanage\controller;
+
+use app\business\model\BusinessJoinModel;
+use cmf\controller\AdminBaseController;
+
+class AdminBusinessJoinManageController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['start_time'])) {
+            $where[] = ['bj.create_time', '>=', strtotime($param['start_time'])];
+        }
+        if (!empty($param['end_time'])) {
+            $where[] = ['bj.create_time', '<=', strtotime($param['end_time'])];
+        }
+        if (!empty($param['keyword'])) {
+            $where[] = ['u.user_name', 'like', "%{$param['keyword']}%"];
+        }
+        if (!empty($param['business_id'])) {
+            $where[] = ['bj.business_id', '=', $param['business_id']];
+        }
+        if (!empty($param['status'])) {
+            $where[] = ['bj.status', '=', $param['status']];
+        }
+        $list = BusinessJoinModel::alias('bj')
+            ->leftJoin('business b', 'bj.business_id = b.id')
+            ->leftJoin('user u', 'bj.user_id = u.id')
+            ->field('bj.*,b.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->paginate(10, false, ['query' => $param]);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('business_id', isset($param['business_id']) ? $param['business_id'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('list', $list->items());
+        $this->assign('page', $list->render());
+
+        return $this->fetch();
+    }
+
+    public function export()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $sessionAdminId = session('ADMIN_ID');
+        $where          = [['bj.business_id', '=', $sessionAdminId]];
+        if (!empty($param['ids'])) {
+            $where[] = ['bj.id', 'in', $param['ids']];
+        } else {
+            if (!empty($param['start_time'])) {
+                $where[] = ['bj.create_time', '>=', strtotime($param['start_time'])];
+            }
+            if (!empty($param['end_time'])) {
+                $where[] = ['bj.create_time', '<=', strtotime($param['end_time'])];
+            }
+            if (!empty($param['keyword'])) {
+                $where[] = ['u.user_name', 'like', "%{$param['keyword']}%"];
+            }
+            if (!empty($param['business_id'])) {
+                $where[] = ['bj.business_id', '=', $param['business_id']];
+            }
+            if (!empty($param['status'])) {
+                $where[] = ['bj.status', '=', $param['status']];
+            }
+        }
+
+        //获取数据
+        $activity = BusinessJoinModel::alias('bj')
+            ->leftJoin('business b', 'bj.business_id = b.id')
+            ->leftJoin('user u', 'bj.user_id = u.id')
+            ->field('bj.*,b.title,u.user_name,u.mobile')
+            ->where($where)
+            ->order('create_time', 'DESC')
+            ->select();
+
+        //表头
+        $header = [
+            'id'          => 'id',
+            'business_id' => '商家id',
+            'title'       => '商家',
+            'user_id'     => '用户id',
+            'user_name'   => '用户姓名',
+            'mobile'      => '用户电话',
+            'comment'     => '用户备注',
+            'status'      => '状态',
+            'create_time' => '报名时间',
+        ];
+
+        //表格内容
+        foreach ($activity as $v) {
+            $v['create_time'] = date('Y-m-d H:i', $v['create_time']);
+            $v['status']      = $v['status'] == 1 ? '已参加' : '不允许参加';
+        }
+        export_excel_data($header, $activity, '商家活动参与列表', ['mobile', 'create_time']);
+    }
+}

+ 38 - 0
app/businessmanage/controller/AdminBusinessManageController.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace app\businessmanage\controller;
+
+use app\business\model\BusinessModel;
+use cmf\controller\AdminBaseController;
+
+class AdminBusinessManageController extends AdminBaseController
+{
+    /**
+     * 活动列表
+     */
+    public function index()
+    {
+        $param = $this->request->param();
+
+        //搜索条件
+        $where = [];
+        if (!empty($param['keyword'])) {
+            $where[] = ['title', 'like', "%{$param['keyword']}%"];
+        }
+        $list = BusinessModel::where($where)->paginate(10, false, ['query' => $param]);
+
+        if (!$list->isEmpty()) {
+            foreach ($list as $v) {
+                if (!empty($v['tags'])) {
+                    $v['tags'] = implode(' ',json_decode($v['tags'],true));
+                }
+            }
+        }
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('list', $list->items());
+        $this->assign('page', $list->render());
+
+        return $this->fetch();
+    }
+}

+ 137 - 0
app/common.php

@@ -0,0 +1,137 @@
+<?php
+/**
+ * excel导出数据
+ * @param $resHeader array 表头
+ * @param $resData array 数据
+ * @param $filename string 文件名
+ * @param $strType  array 需要设置成文本设置的单元格
+ */
+function export_excel_data($resHeader, $resData, $filename, $strType = null)
+{
+    if ($resHeader && $resData && $filename) {
+        header("Content-type:application/vnd.ms-excel");
+        header("Content-Disposition: attachment; filename=" . $filename . ".xls");
+        echo "<style>
+                    br {mso-data-placement:same-cell;} 
+              </style>";
+        echo "<table border='1'><tr>";
+        $t_field = [];
+        foreach ($resHeader as $ha => $h) {
+            $t_field[] = $ha;
+            echo "<th>" . $h . "</th>";
+        }
+        echo "</tr>";
+        foreach ($resData as $dd => $d) {
+            echo "<tr>";
+            foreach ($t_field as $td => $t) {
+                //判断是否合并单元格
+                echo '<td';
+                //判断是否设置单元格为文本
+                if (is_array($strType) && in_array($t, $strType)) {
+                    echo ' style="vnd.ms-excel.numberformat:@"';
+                }
+                echo ">";
+                //判断是否拆分单元格
+                if (is_array($d[$t])) {
+                    echo '<table width=\'100%\' border=\'1\' rules=\'all\'>';
+                    foreach ($d[$t] as $rowValue) {
+                        echo '<tr><td>' . $rowValue . '</td></tr>';
+                    }
+                    echo '</table>';
+                } else {
+                    echo $d[$t];
+                }
+                echo "</td>";
+            }
+            echo "</tr>";
+        }
+        echo "</table>";
+        exit;
+    }
+}
+
+/**
+ * CSV导出
+ * @param $file_name
+ * @param $header
+ * @param $list
+ */
+function exportCsv($header, $list, $file_name)
+{
+    static $source = '';
+
+    if (empty($source)) {
+        header('Content-type: text/csv; charset=utf-8');
+        header('Content-Disposition: attachment;filename="' . $file_name . '.csv"');
+        header('Cache-Control: max-age=0');
+        // PHP文件句柄,php://output 表示直接输出到浏览器
+        $source = fopen('php://output', 'a');
+
+        // 写入列头
+        foreach ($header as $k => $v) {
+            // CSV的Excel支持GBK编码,一定要转换,否则乱码
+            //$header[$k] = iconv('utf-8', 'gbk', $v);
+            $header[$k] = $v;
+        }
+        fputcsv($source, $header);
+    }
+
+    foreach ($list as $key => $value) {
+        $temp = [];
+        foreach ($header as $k => $v) {
+            //$temp[$k] = "\t" . iconv('utf-8', 'gbk', $value[$k]);
+            $temp[$k] = "\t" . $value[$k];
+        }
+        fputcsv($source, $temp);
+        ob_flush();
+        flush();
+    }
+
+}
+
+/*
+ *  生成随机字符串
+ *
+ *   $length    字符串长度
+ */
+function randomStr($length)
+{
+    // 密码字符集,可任意添加你需要的字符
+    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+    $str   = '';
+    for ($i = 0; $i < $length; $i++) {
+        $str .= $chars[mt_rand(0, strlen($chars) - 1)];
+    }
+    return $str;
+}
+
+function get_image_url($file) {
+    if (strpos($file, "http") === 0) {
+        return $file;
+    } else if (strpos($file, "/") === 0) {
+        return cmf_get_domain() . cmf_get_root() . $file;
+    } else {
+        $storage = \cmf\lib\Storage::instance();
+        return $storage->getPreviewUrl($file);
+    }
+}
+
+/**
+ * 生成唯一的订单号 20110809111259232312
+ * 2011-年日期
+ * 08-月份
+ * 09-日期
+ * 11-小时
+ * 12-分
+ * 59-秒
+ * 2323-微秒
+ * 12-随机值
+ * @return string
+ */
+function get_trade_no()
+{
+    list($usec, $sec) = explode(" ", microtime());
+    $usec = substr(str_replace('0.', '', $usec), 0, 4);
+    $str  = rand(10, 99);
+    return date("YmdHis") . $usec . $str;
+}

+ 67 - 0
app/portal/api/CategoryApi.php

@@ -0,0 +1,67 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\api;
+
+use app\portal\model\PortalCategoryModel;
+use think\db\Query;
+
+class CategoryApi
+{
+    /**
+     * 分类列表 用于模板设计
+     * @param array $param
+     * @return false|\PDOStatement|string|\think\Collection
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index($param = [])
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = ['delete_time' => 0];
+
+        //返回的数据必须是数据集或数组,item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        return $portalCategoryModel->where($where)
+            ->where(function (Query $query) use ($param) {
+                if (!empty($param['keyword'])) {
+                    $query->where('name', 'like', "%{$param['keyword']}%");
+                }
+            })->select();
+    }
+
+    /**
+     * 分类列表 用于导航选择
+     * @return array
+     */
+    public function nav()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = ['delete_time' => 0];
+
+        $categories = $portalCategoryModel->where($where)->select();
+
+        $return = [
+            //'name'  => '文章分类',
+            'rule'  => [
+                'action' => 'portal/List/index',
+                'param'  => [
+                    'id' => 'id'
+                ]
+            ],//url规则
+            'items' => $categories //每个子项item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        ];
+
+        return $return;
+    }
+
+}

+ 76 - 0
app/portal/api/PageApi.php

@@ -0,0 +1,76 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\api;
+
+use app\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PageApi
+{
+    /**
+     * 页面列表 用于模板设计
+     * @param array $param
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public function index($param = [])
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_type'      => 2,
+            'post_status'    => 1,
+            'delete_time'    => 0
+        ];
+
+        //返回的数据必须是数据集或数组,item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        return $portalPostModel->field('id,post_title AS name')
+            ->where($where)
+            ->where('published_time',['<', time()], ['> time', 0],'and')
+            ->where(function (Query $query) use ($param) {
+                if (!empty($param['keyword'])) {
+                    $query->where('post_title', 'like', "%{$param['keyword']}%");
+                }
+            })->select();
+    }
+
+    /**
+     * 页面列表 用于导航选择
+     * @return array
+     */
+    public function nav()
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_type'      => 2,
+            'post_status'    => 1,
+            'delete_time'    => 0
+        ];
+
+
+        $pages = $portalPostModel->field('id,post_title AS name')
+            ->where('published_time',['<', time()], ['> time', 0],'and')
+            ->where($where)->select();
+
+        $return = [
+            'rule'  => [
+                'action' => 'portal/Page/index',
+                'param'  => [
+                    'id' => 'id'
+                ]
+            ],//url规则
+            'items' => $pages //每个子项item里必须包括id,name,如果想表示层级关系请加上 parent_id
+        ];
+
+        return $return;
+    }
+
+}

+ 451 - 0
app/portal/controller/AdminArticleController.php

@@ -0,0 +1,451 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalPostModel;
+use app\portal\service\PostService;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use app\admin\model\ThemeModel;
+
+class AdminArticleController extends AdminBaseController
+{
+    /**
+     * 文章列表
+     * @adminMenu(
+     *     'name'   => '文章管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章列表',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_article_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $param = $this->request->param();
+
+        $postService = new PostService();
+        $data        = $postService->adminArticleList($param);
+
+        $data->appends($param);
+
+        $this->assign('start_time', isset($param['start_time']) ? $param['start_time'] : '');
+        $this->assign('end_time', isset($param['end_time']) ? $param['end_time'] : '');
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('articles', $data->items());
+        $this->assign('page', $data->render());
+
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章
+     * @adminMenu(
+     *     'name'   => '添加文章',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_article_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $themeModel        = new ThemeModel();
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+        $this->assign('article_theme_files', $articleThemeFiles);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章提交
+     * @adminMenu(
+     *     'name'   => '添加文章提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+
+            //状态只能设置默认值。未发布、未置顶、未推荐
+            $data['post']['post_status'] = 1;
+            $data['post']['is_top']      = 0;
+            $data['post']['recommended'] = 0;
+
+            $post = $data['post'];
+
+            $result = $this->validate($post, 'AdminArticle');
+            if ($result !== true) {
+                $this->error($result);
+            }
+
+            $portalPostModel = new PortalPostModel();
+
+            if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+                $data['post']['more']['photos'] = [];
+                foreach ($data['photo_urls'] as $key => $url) {
+                    $photoUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+                }
+            }
+
+            if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+                $data['post']['more']['files'] = [];
+                foreach ($data['file_urls'] as $key => $url) {
+                    $fileUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+                }
+            }
+
+
+            $portalPostModel->adminAddArticle($data['post']);
+
+            $data['post']['id'] = $portalPostModel->id;
+            $hookParam          = [
+                'is_add'  => true,
+                'article' => $data['post']
+            ];
+            hook('portal_admin_after_save_article', $hookParam);
+
+
+            $this->success('添加成功!', url('AdminArticle/edit', ['id' => $portalPostModel->id]));
+        }
+
+    }
+
+    /**
+     * 编辑文章
+     * @adminMenu(
+     *     'name'   => '编辑文章',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function edit()
+    {
+        $content = hook_one('portal_admin_article_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+
+        $portalPostModel = new PortalPostModel();
+        $post            = $portalPostModel->where('id', $id)->find();
+        $postCategories  = $post->categories()->alias('a')->column('a.name', 'a.id');
+        $postCategoryIds = implode(',', array_keys($postCategories));
+
+        $themeModel        = new ThemeModel();
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+        $this->assign('article_theme_files', $articleThemeFiles);
+        $this->assign('post', $post);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑文章提交
+     * @adminMenu(
+     *     'name'   => '编辑文章提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章提交',
+     *     'param'  => ''
+     * )
+     * @throws \think\Exception
+     */
+    public function editPost()
+    {
+
+        if ($this->request->isPost()) {
+            $data = $this->request->param();
+
+            //需要抹除发布、置顶、推荐的修改。
+            unset($data['post']['post_status']);
+            unset($data['post']['is_top']);
+            unset($data['post']['recommended']);
+
+            $post   = $data['post'];
+            $result = $this->validate($post, 'AdminArticle');
+            if ($result !== true) {
+                $this->error($result);
+            }
+
+            $portalPostModel = new PortalPostModel();
+
+            if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+                $data['post']['more']['photos'] = [];
+                foreach ($data['photo_urls'] as $key => $url) {
+                    $photoUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+                }
+            }
+
+            if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+                $data['post']['more']['files'] = [];
+                foreach ($data['file_urls'] as $key => $url) {
+                    $fileUrl = cmf_asset_relative_url($url);
+                    array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+                }
+            }
+
+            $portalPostModel->adminEditArticle($data['post']);
+
+            $hookParam = [
+                'is_add'  => false,
+                'article' => $data['post']
+            ];
+            hook('portal_admin_after_save_article', $hookParam);
+
+            $this->success('保存成功!');
+
+        }
+    }
+
+    /**
+     * 文章删除
+     * @adminMenu(
+     *     'name'   => '文章删除',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章删除',
+     *     'param'  => ''
+     * )
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function delete()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['id'])) {
+            $id           = $this->request->param('id', 0, 'intval');
+            $result       = $portalPostModel->where('id', $id)->find();
+            $data         = [
+                'object_id'   => $result['id'],
+                'create_time' => time(),
+                'table_name'  => 'portal_post',
+                'name'        => $result['post_title'],
+                'user_id'     => cmf_get_current_admin_id()
+            ];
+            $resultPortal = $portalPostModel
+                ->where('id', $id)
+                ->update(['delete_time' => time()]);
+            if ($resultPortal) {
+                Db::name('portal_category_post')->where('post_id', $id)->update(['status' => 0]);
+                Db::name('portal_tag_post')->where('post_id', $id)->update(['status' => 0]);
+
+                Db::name('recycleBin')->insert($data);
+            }
+            $this->success("删除成功!", '');
+
+        }
+
+        if (isset($param['ids'])) {
+            $ids     = $this->request->param('ids/a');
+            $recycle = $portalPostModel->where('id', 'in', $ids)->select();
+            $result  = $portalPostModel->where('id', 'in', $ids)->update(['delete_time' => time()]);
+            if ($result) {
+                Db::name('portal_category_post')->where('post_id', 'in', $ids)->update(['status' => 0]);
+                Db::name('portal_tag_post')->where('post_id', 'in', $ids)->update(['status' => 0]);
+                foreach ($recycle as $value) {
+                    $data = [
+                        'object_id'   => $value['id'],
+                        'create_time' => time(),
+                        'table_name'  => 'portal_post',
+                        'name'        => $value['post_title'],
+                        'user_id'     => cmf_get_current_admin_id()
+                    ];
+                    Db::name('recycleBin')->insert($data);
+                }
+                $this->success("删除成功!", '');
+            }
+        }
+    }
+
+    /**
+     * 文章发布
+     * @adminMenu(
+     *     'name'   => '文章发布',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章发布',
+     *     'param'  => ''
+     * )
+     */
+    public function publish()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+            $portalPostModel->where('id', 'in', $ids)->update(['post_status' => 1, 'published_time' => time()]);
+            $this->success("发布成功!", '');
+        }
+
+        if (isset($param['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+            $portalPostModel->where('id', 'in', $ids)->update(['post_status' => 0]);
+            $this->success("取消发布成功!", '');
+        }
+
+    }
+
+    /**
+     * 文章置顶
+     * @adminMenu(
+     *     'name'   => '文章置顶',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章置顶',
+     *     'param'  => ''
+     * )
+     */
+    public function top()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['is_top' => 1]);
+
+            $this->success("置顶成功!", '');
+
+        }
+
+        if (isset($_POST['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['is_top' => 0]);
+
+            $this->success("取消置顶成功!", '');
+        }
+    }
+
+    /**
+     * 文章推荐
+     * @adminMenu(
+     *     'name'   => '文章推荐',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章推荐',
+     *     'param'  => ''
+     * )
+     */
+    public function recommend()
+    {
+        $param           = $this->request->param();
+        $portalPostModel = new PortalPostModel();
+
+        if (isset($param['ids']) && isset($param["yes"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['recommended' => 1]);
+
+            $this->success("推荐成功!", '');
+
+        }
+        if (isset($param['ids']) && isset($param["no"])) {
+            $ids = $this->request->param('ids/a');
+
+            $portalPostModel->where('id', 'in', $ids)->update(['recommended' => 0]);
+
+            $this->success("取消推荐成功!", '');
+
+        }
+    }
+
+    /**
+     * 文章排序
+     * @adminMenu(
+     *     'name'   => '文章排序',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章排序',
+     *     'param'  => ''
+     * )
+     */
+    public function listOrder()
+    {
+        parent::listOrders(Db::name('portal_category_post'));
+        $this->success("排序更新成功!", '');
+    }
+}

+ 370 - 0
app/portal/controller/AdminCategoryController.php

@@ -0,0 +1,370 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\admin\model\RouteModel;
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use app\admin\model\ThemeModel;
+
+
+class AdminCategoryController extends AdminBaseController
+{
+    /**
+     * 文章分类列表
+     * @adminMenu(
+     *     'name'   => '分类管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类列表',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_category_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $portalCategoryModel = new PortalCategoryModel();
+        $keyword             = $this->request->param('keyword');
+
+        if (empty($keyword)) {
+            $categoryTree = $portalCategoryModel->adminCategoryTableTree();
+            $this->assign('category_tree', $categoryTree);
+        } else {
+            $categories = $portalCategoryModel->where('name', 'like', "%{$keyword}%")
+                ->where('delete_time', 0)->select();
+            $this->assign('categories', $categories);
+        }
+
+        $this->assign('keyword', $keyword);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章分类
+     * @adminMenu(
+     *     'name'   => '添加文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章分类',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_category_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $parentId            = $this->request->param('parent', 0, 'intval');
+        $portalCategoryModel = new PortalCategoryModel();
+        $categoriesTree      = $portalCategoryModel->adminCategoryTree($parentId);
+
+        $themeModel        = new ThemeModel();
+        $listThemeFiles    = $themeModel->getActionThemeFiles('portal/List/index');
+        $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+
+        $this->assign('list_theme_files', $listThemeFiles);
+        $this->assign('article_theme_files', $articleThemeFiles);
+        $this->assign('categories_tree', $categoriesTree);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章分类提交
+     * @adminMenu(
+     *     'name'   => '添加文章分类提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章分类提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $data = $this->request->param();
+
+        $result = $this->validate($data, 'PortalCategory');
+
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        $result = $portalCategoryModel->addCategory($data);
+
+        if ($result === false) {
+            $this->error('添加失败!');
+        }
+
+        $this->success('添加成功!', url('AdminCategory/index'));
+    }
+
+    /**
+     * 编辑文章分类
+     * @adminMenu(
+     *     'name'   => '编辑文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章分类',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function edit()
+    {
+
+        $content = hook_one('portal_admin_category_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+        if ($id > 0) {
+            $portalCategoryModel = new PortalCategoryModel();
+            $category            = $portalCategoryModel->get($id)->toArray();
+
+
+            $categoriesTree = $portalCategoryModel->adminCategoryTree($category['parent_id'], $id);
+
+            $themeModel        = new ThemeModel();
+            $listThemeFiles    = $themeModel->getActionThemeFiles('portal/List/index');
+            $articleThemeFiles = $themeModel->getActionThemeFiles('portal/Article/index');
+
+            $routeModel = new RouteModel();
+            $alias      = $routeModel->getUrl('portal/List/index', ['id' => $id]);
+
+            $category['alias'] = $alias;
+            $this->assign($category);
+            $this->assign('list_theme_files', $listThemeFiles);
+            $this->assign('article_theme_files', $articleThemeFiles);
+            $this->assign('categories_tree', $categoriesTree);
+            return $this->fetch();
+        } else {
+            $this->error('操作错误!');
+        }
+
+    }
+
+    /**
+     * 编辑文章分类提交
+     * @adminMenu(
+     *     'name'   => '编辑文章分类提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑文章分类提交',
+     *     'param'  => ''
+     * )
+     */
+    public function editPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data, 'PortalCategory');
+
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $result = $portalCategoryModel->editCategory($data);
+
+        if ($result === false) {
+            $this->error('保存失败!');
+        }
+
+        $this->success('保存成功!');
+    }
+
+    /**
+     * 文章分类选择对话框
+     * @adminMenu(
+     *     'name'   => '文章分类选择对话框',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类选择对话框',
+     *     'param'  => ''
+     * )
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function select()
+    {
+        $ids                 = $this->request->param('ids');
+        $selectedIds         = explode(',', $ids);
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $tpl = <<<tpl
+<tr class='data-item-tr'>
+    <td>
+        <input type='checkbox' class='js-check' data-yid='js-check-y' data-xid='js-check-x' name='ids[]'
+               value='\$id' data-name='\$name' \$checked>
+    </td>
+    <td>\$id</td>
+    <td>\$spacer <a href='\$url' target='_blank'>\$name</a></td>
+</tr>
+tpl;
+
+        $categoryTree = $portalCategoryModel->adminCategoryTableTree($selectedIds, $tpl);
+
+        $categories = $portalCategoryModel->where('delete_time', 0)->select();
+
+        $this->assign('categories', $categories);
+        $this->assign('selectedIds', $selectedIds);
+        $this->assign('categories_tree', $categoryTree);
+        return $this->fetch();
+    }
+
+    /**
+     * 文章分类排序
+     * @adminMenu(
+     *     'name'   => '文章分类排序',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类排序',
+     *     'param'  => ''
+     * )
+     */
+    public function listOrder()
+    {
+        parent::listOrders(Db::name('portal_category'));
+        $this->success("排序更新成功!", '');
+    }
+
+    /**
+     * 文章分类显示隐藏
+     * @adminMenu(
+     *     'name'   => '文章分类显示隐藏',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章分类显示隐藏',
+     *     'param'  => ''
+     * )
+     */
+    public function toggle()
+    {
+        $data                = $this->request->param();
+        $portalCategoryModel = new PortalCategoryModel();
+        $ids                 = $this->request->param('ids/a');
+
+        if (isset($data['ids']) && !empty($data["display"])) {
+            $portalCategoryModel->where('id', 'in', $ids)->update(['status' => 1]);
+            $this->success("更新成功!");
+        }
+
+        if (isset($data['ids']) && !empty($data["hide"])) {
+            $portalCategoryModel->where('id', 'in', $ids)->update(['status' => 0]);
+            $this->success("更新成功!");
+        }
+
+    }
+
+    /**
+     * 删除文章分类
+     * @adminMenu(
+     *     'name'   => '删除文章分类',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除文章分类',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+        $id                  = $this->request->param('id');
+        //获取删除的内容
+        $findCategory = $portalCategoryModel->where('id', $id)->find();
+
+        if (empty($findCategory)) {
+            $this->error('分类不存在!');
+        }
+        //判断此分类有无子分类(不算被删除的子分类)
+        $categoryChildrenCount = $portalCategoryModel->where(['parent_id' => $id, 'delete_time' => 0])->count();
+
+        if ($categoryChildrenCount > 0) {
+            $this->error('此分类有子类无法删除!');
+        }
+
+        $categoryPostCount = Db::name('portal_category_post')->where('category_id', $id)->count();
+
+        if ($categoryPostCount > 0) {
+            $this->error('此分类有文章无法删除!');
+        }
+
+        $data   = [
+            'object_id'   => $findCategory['id'],
+            'create_time' => time(),
+            'table_name'  => 'portal_category',
+            'name'        => $findCategory['name']
+        ];
+        $result = $portalCategoryModel
+            ->where('id', $id)
+            ->update(['delete_time' => time()]);
+        if ($result) {
+            Db::name('recycleBin')->insert($data);
+            $this->success('删除成功!');
+        } else {
+            $this->error('删除失败');
+        }
+    }
+}

+ 63 - 0
app/portal/controller/AdminFeedbackController.php

@@ -0,0 +1,63 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author:kane < chengjin005@163.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\portal\model\FeedbackModel;
+use app\portal\model\PortalTagModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+/**
+ * Class AdminTagController 标签管理控制器
+ * @package app\portal\controller
+ */
+class AdminFeedbackController extends AdminBaseController
+{
+
+    public function index()
+    {
+        $param = $this->request->param();
+        //搜索条件
+        $where = [];
+        if (!empty($param['type'])) {
+            $where[] = ['type', '=', $param['type']];
+        }
+        if (!empty($param['status'])) {
+            $where[] = ['status', '=', $param['status']];
+        }
+
+        $model = new FeedbackModel();
+        $list  = $model->where($where)->order('status asc,create_time desc')->paginate();
+
+        $this->assign("arrStatus", $model::$STATUS);
+        $this->assign("arrType", $model::$TYPE);
+        $this->assign('type', isset($param['type']) ? $param['type'] : '');
+        $this->assign('status', isset($param['status']) ? $param['status'] : '');
+        $this->assign('list', $list);
+        $this->assign('page', $list->render());
+        return $this->fetch();
+    }
+
+
+    public function read()
+    {
+        $id = $this->request->param("id");
+        if (empty($id)) {
+            $this->error(lang("NO_ID"));
+        }
+
+        $model = new FeedbackModel();
+        $model->isUpdate(true)->save(["status" => 1], ["id" => $id]);
+
+        $this->success(lang("SAVE_SUCCESS"));
+
+    }
+}

+ 32 - 0
app/portal/controller/AdminIndexController.php

@@ -0,0 +1,32 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\AdminBaseController;
+
+/**
+ * Class AdminIndexController
+ * @package app\portal\controller
+ * @adminMenuRoot(
+ *     'name'   =>'门户管理',
+ *     'action' =>'default',
+ *     'parent' =>'',
+ *     'display'=> true,
+ *     'order'  => 30,
+ *     'icon'   =>'th',
+ *     'remark' =>'门户管理'
+ * )
+ */
+class AdminIndexController extends AdminBaseController
+{
+
+
+}

+ 239 - 0
app/portal/controller/AdminPageController.php

@@ -0,0 +1,239 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\admin\model\RouteModel;
+use cmf\controller\AdminBaseController;
+use app\portal\model\PortalPostModel;
+use app\portal\service\PostService;
+use app\admin\model\ThemeModel;
+
+class AdminPageController extends AdminBaseController
+{
+
+    /**
+     * 页面管理
+     * @adminMenu(
+     *     'name'   => '页面管理',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '页面管理',
+     *     'param'  => ''
+     * )
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_page_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $param = $this->request->param();
+
+        $postService = new PostService();
+        $data        = $postService->adminPageList($param);
+        $data->appends($param);
+
+        $this->assign('keyword', isset($param['keyword']) ? $param['keyword'] : '');
+        $this->assign('pages', $data->items());
+        $this->assign('page', $data->render());
+
+        return $this->fetch();
+    }
+
+    /**
+     * 添加页面
+     * @adminMenu(
+     *     'name'   => '添加页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加页面',
+     *     'param'  => ''
+     * )
+     */
+    public function add()
+    {
+        $content = hook_one('portal_admin_page_add_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $themeModel     = new ThemeModel();
+        $pageThemeFiles = $themeModel->getActionThemeFiles('portal/Page/index');
+        $this->assign('page_theme_files', $pageThemeFiles);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加页面提交
+     * @adminMenu(
+     *     'name'   => '添加页面提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加页面提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data['post'], 'AdminPage');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+            $data['post']['more']['photos'] = [];
+            foreach ($data['photo_urls'] as $key => $url) {
+                $photoUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+            }
+        }
+
+        if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+            $data['post']['more']['files'] = [];
+            foreach ($data['file_urls'] as $key => $url) {
+                $fileUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+            }
+        }
+
+        $portalPostModel = new PortalPostModel();
+        $portalPostModel->adminAddPage($data['post']);
+        $this->success(lang('ADD_SUCCESS'), url('AdminPage/edit', ['id' => $portalPostModel->id]));
+
+    }
+
+    /**
+     * 编辑页面
+     * @adminMenu(
+     *     'name'   => '编辑页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑页面',
+     *     'param'  => ''
+     * )
+     */
+    public function edit()
+    {
+        $content = hook_one('portal_admin_page_edit_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $id = $this->request->param('id', 0, 'intval');
+
+        $portalPostModel = new PortalPostModel();
+        $post            = $portalPostModel->where('id', $id)->find();
+
+        $themeModel     = new ThemeModel();
+        $pageThemeFiles = $themeModel->getActionThemeFiles('portal/Page/index');
+
+        $routeModel         = new RouteModel();
+        $alias              = $routeModel->getUrl('portal/Page/index', ['id' => $id]);
+        $post['post_alias'] = $alias;
+        $this->assign('page_theme_files', $pageThemeFiles);
+        $this->assign('post', $post);
+
+        return $this->fetch();
+    }
+
+    /**
+     * 编辑页面提交
+     * @adminMenu(
+     *     'name'   => '编辑页面提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '编辑页面提交',
+     *     'param'  => ''
+     * )
+     */
+    public function editPost()
+    {
+        $data = $this->request->param();
+
+        $result = $this->validate($data['post'], 'AdminPage');
+        if ($result !== true) {
+            $this->error($result);
+        }
+
+        if (!empty($data['photo_names']) && !empty($data['photo_urls'])) {
+            $data['post']['more']['photos'] = [];
+            foreach ($data['photo_urls'] as $key => $url) {
+                $photoUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['photos'], ["url" => $photoUrl, "name" => $data['photo_names'][$key]]);
+            }
+        }
+
+        if (!empty($data['file_names']) && !empty($data['file_urls'])) {
+            $data['post']['more']['files'] = [];
+            foreach ($data['file_urls'] as $key => $url) {
+                $fileUrl = cmf_asset_relative_url($url);
+                array_push($data['post']['more']['files'], ["url" => $fileUrl, "name" => $data['file_names'][$key]]);
+            }
+        }
+
+        $portalPostModel = new PortalPostModel();
+
+        $portalPostModel->adminEditPage($data['post']);
+
+        $this->success(lang('SAVE_SUCCESS'));
+
+    }
+
+    /**
+     * 删除页面
+     * @author    iyting@foxmail.com
+     * @adminMenu(
+     *     'name'   => '删除页面',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除页面',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $portalPostModel = new PortalPostModel();
+        $data            = $this->request->param();
+
+        $result = $portalPostModel->adminDeletePage($data);
+        if ($result) {
+            $this->success(lang('DELETE_SUCCESS'));
+        } else {
+            $this->error(lang('DELETE_FAILED'));
+        }
+
+    }
+
+}

+ 153 - 0
app/portal/controller/AdminTagController.php

@@ -0,0 +1,153 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author:kane < chengjin005@163.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use app\portal\model\PortalTagModel;
+use cmf\controller\AdminBaseController;
+use think\Db;
+
+/**
+ * Class AdminTagController 标签管理控制器
+ * @package app\portal\controller
+ */
+class AdminTagController extends AdminBaseController
+{
+    /**
+     * 文章标签管理
+     * @adminMenu(
+     *     'name'   => '文章标签',
+     *     'parent' => 'portal/AdminIndex/default',
+     *     'display'=> true,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function index()
+    {
+        $content = hook_one('portal_admin_tag_index_view');
+
+        if (!empty($content)) {
+            return $content;
+        }
+
+        $portalTagModel = new PortalTagModel();
+        $tags           = $portalTagModel->paginate();
+
+        $this->assign("arrStatus", $portalTagModel::$STATUS);
+        $this->assign("tags", $tags);
+        $this->assign('page', $tags->render());
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章标签
+     * @adminMenu(
+     *     'name'   => '添加文章标签',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> true,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function add()
+    {
+        $portalTagModel = new PortalTagModel();
+        $this->assign("arrStatus", $portalTagModel::$STATUS);
+        return $this->fetch();
+    }
+
+    /**
+     * 添加文章标签提交
+     * @adminMenu(
+     *     'name'   => '添加文章标签提交',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '添加文章标签提交',
+     *     'param'  => ''
+     * )
+     */
+    public function addPost()
+    {
+
+        $arrData = $this->request->param();
+
+        $portalTagModel = new PortalTagModel();
+        $portalTagModel->isUpdate(false)->allowField(true)->save($arrData);
+
+        $this->success(lang("SAVE_SUCCESS"));
+
+    }
+
+    /**
+     * 更新文章标签状态
+     * @adminMenu(
+     *     'name'   => '更新标签状态',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '更新标签状态',
+     *     'param'  => ''
+     * )
+     */
+    public function upStatus()
+    {
+        $intId     = $this->request->param("id");
+        $intStatus = $this->request->param("status");
+        $intStatus = $intStatus ? 1 : 0;
+        if (empty($intId)) {
+            $this->error(lang("NO_ID"));
+        }
+
+        $portalTagModel = new PortalTagModel();
+        $portalTagModel->isUpdate(true)->save(["status" => $intStatus], ["id" => $intId]);
+
+        $this->success(lang("SAVE_SUCCESS"));
+
+    }
+
+    /**
+     * 删除文章标签
+     * @adminMenu(
+     *     'name'   => '删除文章标签',
+     *     'parent' => 'index',
+     *     'display'=> false,
+     *     'hasView'=> false,
+     *     'order'  => 10000,
+     *     'icon'   => '',
+     *     'remark' => '删除文章标签',
+     *     'param'  => ''
+     * )
+     */
+    public function delete()
+    {
+        $intId = $this->request->param("id", 0, 'intval');
+
+        if (empty($intId)) {
+            $this->error(lang("NO_ID"));
+        }
+        $portalTagModel = new PortalTagModel();
+
+        $portalTagModel->where('id' , $intId)->delete();
+        Db::name('portal_tag_post')->where('tag_id', $intId)->delete();
+        $this->success(lang("DELETE_SUCCESS"));
+    }
+}

+ 102 - 0
app/portal/controller/ArticleController.php

@@ -0,0 +1,102 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalCategoryModel;
+use app\portal\service\PostService;
+use app\portal\model\PortalPostModel;
+use think\Db;
+
+class ArticleController extends HomeBaseController
+{
+    /**
+     * 文章详情
+     * @return mixed
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+
+        $portalCategoryModel = new PortalCategoryModel();
+        $postService         = new PostService();
+
+        $articleId  = $this->request->param('id', 0, 'intval');
+        $categoryId = $this->request->param('cid', 0, 'intval');
+        $article    = $postService->publishedArticle($articleId, $categoryId);
+
+        if (empty($article)) {
+            abort(404, '文章不存在!');
+        }
+
+
+        $prevArticle = $postService->publishedPrevArticle($articleId, $categoryId);
+        $nextArticle = $postService->publishedNextArticle($articleId, $categoryId);
+
+        $tplName = 'article';
+
+        if (empty($categoryId)) {
+            /*$categories = $article['categories'];
+
+            if (count($categories) > 0) {
+                $this->assign('category', $categories[0]);
+            } else {
+                abort(404, '文章未指定分类!');
+            }*/
+
+        } else {
+            $category = $portalCategoryModel->where('id', $categoryId)->where('status', 1)->find();
+
+            if (empty($category)) {
+                abort(404, '文章不存在!');
+            }
+
+            $this->assign('category', $category);
+
+            $tplName = empty($category["one_tpl"]) ? $tplName : $category["one_tpl"];
+        }
+
+        Db::name('portal_post')->where('id', $articleId)->setInc('post_hits');
+
+
+        hook('portal_before_assign_article', $article);
+
+        $this->assign('article', $article);
+        $this->assign('prev_article', $prevArticle);
+        $this->assign('next_article', $nextArticle);
+
+        $tplName = empty($article['more']['template']) ? $tplName : $article['more']['template'];
+
+        return $this->fetch("/$tplName");
+    }
+
+    // 文章点赞
+    public function doLike()
+    {
+        $this->checkUserLogin();
+        $articleId = $this->request->param('id', 0, 'intval');
+
+
+        $canLike = cmf_check_user_action("posts$articleId", 1);
+
+        if ($canLike) {
+            Db::name('portal_post')->where('id', $articleId)->setInc('post_like');
+
+            $this->success("赞好啦!");
+        } else {
+            $this->error("您已赞过啦!");
+        }
+    }
+
+}

+ 27 - 0
app/portal/controller/IndexController.php

@@ -0,0 +1,27 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+
+class IndexController extends HomeBaseController
+{
+    public function index()
+    {
+        return redirect('/admin');
+//        return $this->fetch(':index');
+    }
+
+    public function manage()
+    {
+        return $this->fetch(':manage');
+    }
+}

+ 39 - 0
app/portal/controller/ListController.php

@@ -0,0 +1,39 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalCategoryModel;
+
+class ListController extends HomeBaseController
+{
+    /***
+     * 文章列表
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $id                  = $this->request->param('id', 0, 'intval');
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $category = $portalCategoryModel->where('id', $id)->where('status', 1)->find();
+       
+        $this->assign('category', $category);
+
+        $listTpl = empty($category['list_tpl']) ? 'list' : $category['list_tpl'];
+
+        return $this->fetch('/' . $listTpl);
+    }
+
+}

+ 44 - 0
app/portal/controller/PageController.php

@@ -0,0 +1,44 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\service\PostService;
+
+class PageController extends HomeBaseController
+{
+    /**
+     * 页面管理
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $postService = new PostService();
+        $pageId      = $this->request->param('id', 0, 'intval');
+        $page        = $postService->publishedPage($pageId);
+
+        if (empty($page)) {
+            abort(404, ' 页面不存在!');
+        }
+
+        $this->assign('page', $page);
+
+        $more = $page['more'];
+
+        $tplName = empty($more['template']) ? 'page' : $more['template'];
+
+        return $this->fetch("/$tplName");
+    }
+
+}

+ 32 - 0
app/portal/controller/SearchController.php

@@ -0,0 +1,32 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+
+class SearchController extends HomeBaseController
+{
+    /**
+     * 搜索
+     * @return mixed
+     */
+    public function index()
+    {
+        $keyword = $this->request->param('keyword');
+
+        if (empty($keyword)) {
+            $this -> error("关键词不能为空!请重新输入!");
+        }
+
+        $this -> assign("keyword", $keyword);
+        return $this->fetch('/search');
+    }
+}

+ 47 - 0
app/portal/controller/TagController.php

@@ -0,0 +1,47 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\controller;
+
+use cmf\controller\HomeBaseController;
+use app\portal\model\PortalTagModel;
+
+class TagController extends HomeBaseController
+{
+    /**
+     * 标签
+     * @return mixed
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function index()
+    {
+        $id             = $this->request->param('id');
+
+        $portalTagModel = new PortalTagModel();
+
+        if(is_numeric($id)){
+            $tag = $portalTagModel->where('id', $id)->where('status', 1)->find();
+        }else{
+            $tag = $portalTagModel->where('name', $id)->where('status', 1)->find();
+        }
+
+
+        if (empty($tag)) {
+            abort(404, '标签不存在!');
+        }
+
+        $this->assign('tag', $tag);
+
+        return $this->fetch('/tag');
+    }
+
+}

+ 96 - 0
app/portal/hooks.php

@@ -0,0 +1,96 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'portal_before_assign_article'    => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '文章显示之前', // 钩子名称
+        "description" => "文章显示之前", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_after_save_article' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '后台文章保存之后', // 钩子名称
+        "description" => "后台文章保存之后", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_article_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章管理列表界面', // 钩子名称
+        "description" => "门户后台文章管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加界面', // 钩子名称
+        "description" => "门户后台文章添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章编辑界面', // 钩子名称
+        "description" => "门户后台文章编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类管理列表界面', // 钩子名称
+        "description" => "门户后台文章分类管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类添加界面', // 钩子名称
+        "description" => "门户后台文章分类添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_category_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章分类编辑界面', // 钩子名称
+        "description" => "门户后台文章分类编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面管理列表界面', // 钩子名称
+        "description" => "门户后台页面管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_add_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面添加界面', // 钩子名称
+        "description" => "门户后台页面添加界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_page_edit_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台页面编辑界面', // 钩子名称
+        "description" => "门户后台页面编辑界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_tag_index_view' => [
+        "type"        => 2,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章标签管理列表界面', // 钩子名称
+        "description" => "门户后台文章标签管理列表界面", //钩子描述
+        "once"        => 1 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view_right_sidebar' => [
+        "type"        => 4,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加编辑界面右侧栏', // 钩子名称
+        "description" => "门户后台文章添加编辑界面右侧栏", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+    'portal_admin_article_edit_view_main' => [
+        "type"        => 4,//钩子类型(默认为应用钩子;2:应用钩子;3:模板钩子;4:后台模板钩子)
+        "name"        => '门户后台文章添加编辑界面主要内容', // 钩子名称
+        "description" => "门户后台文章添加编辑界面主要内容", //钩子描述
+        "once"        => 0 // 是否只执行一次
+    ],
+];

+ 13 - 0
app/portal/lang/en-us.php

@@ -0,0 +1,13 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'ADMIN_CENTER' => 'Admin Center',
+];

+ 15 - 0
app/portal/lang/en-us/common.php

@@ -0,0 +1,15 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'TABLE_PORTAL_CATEGORY'  => 'Article Category',
+    'TABLE_PORTAL_POST'      => 'Article',
+    'TABLE_PORTAL_POST#PAGE' => 'Page'
+];

+ 21 - 0
app/portal/lang/zh-cn.php

@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'ADMIN_TAG_ADD'    => '添加标签',
+    'ADMIN_TAG_DELETE' => '删除标签',
+    'ADMIN_TAG_INDEX'  => '标签列表',
+    'SAVE_SUCCESS'     => '保存成功!',
+    'DELETE_SUCCESS'   => '删除成功!',
+    'ADD_SUCCESS'      => '添加成功',
+    'DELETE_FAILED'    => '删除失败',
+    'ADD_FAILED'       => '添加失败',
+];
+

+ 16 - 0
app/portal/lang/zh-cn/common.php

@@ -0,0 +1,16 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'TABLE_PORTAL_CATEGORY'  => '文章分类',
+    'TABLE_PORTAL_POST'      => '文章',
+    'TABLE_SLIDE'      => '幻灯片',
+    'TABLE_PORTAL_POST#PAGE' => '页面'
+];

+ 13 - 0
app/portal/lang/zh-cn/home.php

@@ -0,0 +1,13 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+/*前台通用语言包*/
+return [
+];

+ 19 - 0
app/portal/model/FeedbackModel.php

@@ -0,0 +1,19 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Author: pl125 <xskjs888@163.com>
+// +----------------------------------------------------------------------
+
+namespace app\portal\model;
+
+use think\Model;
+
+class FeedbackModel extends Model
+{
+    public static $STATUS = ['', "已读", "未读"];
+
+    public static $TYPE = ['未知类型', '系统消息', '期望商家'];
+}

+ 217 - 0
app/portal/model/PortalCategoryModel.php

@@ -0,0 +1,217 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use app\admin\model\RouteModel;
+use think\db\Query;
+use think\Model;
+use tree\Tree;
+
+class PortalCategoryModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    /**
+     * 生成分类 select树形结构
+     * @param int $selectId   需要选中的分类 id
+     * @param int $currentCid 需要隐藏的分类 id
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminCategoryTree($selectId = 0, $currentCid = 0)
+    {
+        $categories = $this->order("list_order ASC")
+            ->where('delete_time', 0)
+            ->where(function (Query $query) use ($currentCid) {
+                if (!empty($currentCid)) {
+                    $query->where('id', 'neq', $currentCid);
+                }
+            })
+            ->select()->toArray();
+
+        $tree       = new Tree();
+        $tree->icon = ['&nbsp;&nbsp;│', '&nbsp;&nbsp;├─', '&nbsp;&nbsp;└─'];
+        $tree->nbsp = '&nbsp;&nbsp;';
+
+        $newCategories = [];
+        foreach ($categories as $item) {
+            $item['selected'] = $selectId == $item['id'] ? "selected" : "";
+
+            array_push($newCategories, $item);
+        }
+
+        $tree->init($newCategories);
+        $str     = '<option value=\"{$id}\" {$selected}>{$spacer}{$name}</option>';
+        $treeStr = $tree->getTree(0, $str);
+
+        return $treeStr;
+    }
+
+    /**
+     * 分类树形结构
+     * @param int    $currentIds
+     * @param string $tpl
+     * @return string
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminCategoryTableTree($currentIds = 0, $tpl = '')
+    {
+//        if (!empty($currentCid)) {
+//            $where['id'] = ['neq', $currentCid];
+//        }
+        $categories = $this->order("list_order ASC")->where('delete_time', 0)->select()->toArray();
+
+        $tree       = new Tree();
+        $tree->icon = ['&nbsp;&nbsp;│', '&nbsp;&nbsp;├─', '&nbsp;&nbsp;└─'];
+        $tree->nbsp = '&nbsp;&nbsp;';
+
+        if (!is_array($currentIds)) {
+            $currentIds = [$currentIds];
+        }
+
+        $newCategories = [];
+        foreach ($categories as $item) {
+            $item['parent_id_node'] = ($item['parent_id']) ? ' class="child-of-node-' . $item['parent_id'] . '"' : '';
+            $item['style']          = empty($item['parent_id']) ? '' : 'display:none;';
+            $item['status_text']    = empty($item['status']) ? '<span class="label label-warning">隐藏</span>' : '<span class="label label-success">显示</span>';
+            $item['checked']        = in_array($item['id'], $currentIds) ? "checked" : "";
+            $item['url']            = cmf_url('portal/List/index', ['id' => $item['id']]);
+            $item['str_action']     = '<a class="btn btn-xs btn-primary" href="' . url("AdminCategory/add", ["parent" => $item['id']]) . '">添加子分类</a>  <a class="btn btn-xs btn-primary" href="' . url("AdminCategory/edit", ["id" => $item['id']]) . '">' . lang('EDIT') . '</a>  <a class="btn btn-xs btn-danger js-ajax-delete" href="' . url("AdminCategory/delete", ["id" => $item['id']]) . '">' . lang('DELETE') . '</a> ';
+            if ($item['status']) {
+                $item['str_action'] .= '<a class="btn btn-xs btn-warning js-ajax-dialog-btn" data-msg="您确定隐藏此分类吗" href="' . url('AdminCategory/toggle', ['ids' => $item['id'], 'hide' => 1]) . '">隐藏</a>';
+            } else {
+                $item['str_action'] .= '<a class="btn btn-xs btn-success js-ajax-dialog-btn" data-msg="您确定显示此分类吗" href="' . url('AdminCategory/toggle', ['ids' => $item['id'], 'display' => 1]) . '">显示</a>';
+            }
+            array_push($newCategories, $item);
+        }
+
+        $tree->init($newCategories);
+
+        if (empty($tpl)) {
+            $tpl = " <tr id='node-\$id' \$parent_id_node style='\$style' data-parent_id='\$parent_id' data-id='\$id'>
+                        <td style='padding-left:20px;'><input type='checkbox' class='js-check' data-yid='js-check-y' data-xid='js-check-x' name='ids[]' value='\$id' data-parent_id='\$parent_id' data-id='\$id'></td>
+                        <td><input name='list_orders[\$id]' type='text' size='3' value='\$list_order' class='input-order'></td>
+                        <td>\$id</td>
+                        <td>\$spacer <a href='\$url' target='_blank'>\$name</a></td>
+                        <td>\$description</td>
+                        <td>\$status_text</td>
+                        <td>\$str_action</td>
+                    </tr>";
+        }
+        $treeStr = $tree->getTree(0, $tpl);
+
+        return $treeStr;
+    }
+
+    /**
+     * 添加文章分类
+     * @param $data
+     * @return bool
+     */
+    public function addCategory($data)
+    {
+        $result = true;
+        self::startTrans();
+        try {
+            if (!empty($data['more']['thumbnail'])) {
+                $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            }
+            $this->allowField(true)->save($data);
+            $id = $this->id;
+            if (empty($data['parent_id'])) {
+
+                $this->where('id', $id)->update(['path' => '0-' . $id]);
+            } else {
+                $parentPath = $this->where('id', intval($data['parent_id']))->value('path');
+                $this->where('id', $id)->update(['path' => "$parentPath-$id"]);
+
+            }
+            self::commit();
+        } catch (\Exception $e) {
+            self::rollback();
+            $result = false;
+        }
+
+        if ($result != false) {
+            //设置别名
+            $routeModel = new RouteModel();
+            if (!empty($data['alias']) && !empty($id)) {
+                $routeModel->setRoute($data['alias'], 'portal/List/index', ['id' => $id], 2, 5000);
+                $routeModel->setRoute($data['alias'] . '/:id', 'portal/Article/index', ['cid' => $id], 2, 4999);
+            }
+            $routeModel->getRoutes(true);
+        }
+
+        return $result;
+    }
+
+    public function editCategory($data)
+    {
+        $result = true;
+
+        $id          = intval($data['id']);
+        $parentId    = intval($data['parent_id']);
+        $oldCategory = $this->where('id', $id)->find();
+
+        if (empty($parentId)) {
+            $newPath = '0-' . $id;
+        } else {
+            $parentPath = $this->where('id', intval($data['parent_id']))->value('path');
+            if ($parentPath === false) {
+                $newPath = false;
+            } else {
+                $newPath = "$parentPath-$id";
+            }
+        }
+
+        if (empty($oldCategory) || empty($newPath)) {
+            $result = false;
+        } else {
+
+            $data['path'] = $newPath;
+            if (!empty($data['more']['thumbnail'])) {
+                $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            }
+            $this->isUpdate(true)->allowField(true)->save($data, ['id' => $id]);
+
+            $children = $this->field('id,path')->where('path', 'like', $oldCategory['path'] . "-%")->select();
+            if (!$children->isEmpty()) {
+                foreach ($children as $child) {
+                    $childPath = str_replace($oldCategory['path'] . '-', $newPath . '-', $child['path']);
+                    $this->where('id', $child['id'])->update(['path' => $childPath], ['id' => $child['id']]);
+                }
+            }
+
+            $routeModel = new RouteModel();
+            if (!empty($data['alias'])) {
+                $routeModel->setRoute($data['alias'], 'portal/List/index', ['id' => $data['id']], 2, 5000);
+                $routeModel->setRoute($data['alias'] . '/:id', 'portal/Article/index', ['cid' => $data['id']], 2, 4999);
+            } else {
+                $routeModel->deleteRoute('portal/List/index', ['id' => $data['id']]);
+                $routeModel->deleteRoute('portal/Article/index', ['cid' => $data['id']]);
+            }
+
+            $routeModel->getRoutes(true);
+        }
+
+
+        return $result;
+    }
+
+
+}

+ 363 - 0
app/portal/model/PortalPostModel.php

@@ -0,0 +1,363 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use app\admin\model\RouteModel;
+use think\Model;
+use think\Db;
+
+/**
+ * @property mixed id
+ */
+class PortalPostModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = true;
+
+    /**
+     * 关联 user表
+     * @return \think\model\relation\BelongsTo
+     */
+    public function user()
+    {
+        return $this->belongsTo('UserModel', 'user_id')->setEagerlyType(1);
+    }
+
+    /**
+     * 关联分类表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function categories()
+    {
+        return $this->belongsToMany('PortalCategoryModel', 'portal_category_post', 'category_id', 'post_id');
+    }
+
+    /**
+     * 关联标签表
+     * @return \think\model\relation\BelongsToMany
+     */
+    public function tags()
+    {
+        return $this->belongsToMany('PortalTagModel', 'portal_tag_post', 'tag_id', 'post_id');
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function getPostContentAttr($value)
+    {
+        return cmf_replace_content_file_url(htmlspecialchars_decode($value));
+    }
+
+    /**
+     * post_content 自动转化
+     * @param $value
+     * @return string
+     */
+    public function setPostContentAttr($value)
+    {
+        return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
+    }
+
+    /**
+     * published_time 自动完成
+     * @param $value
+     * @return false|int
+     */
+    public function setPublishedTimeAttr($value)
+    {
+        return strtotime($value);
+    }
+
+    /**
+     * 后台管理添加文章
+     * @param array        $data       文章数据
+     * @return $this
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function adminAddArticle($data)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            $data['thumbnail']         = $data['more']['thumbnail'];
+        }
+
+        if (!empty($data['more']['audio'])) {
+            $data['more']['audio'] = cmf_asset_relative_url($data['more']['audio']);
+        }
+
+        if (!empty($data['more']['video'])) {
+            $data['more']['video'] = cmf_asset_relative_url($data['more']['video']);
+        }
+
+        $this->allowField(true)->data($data, true)->isUpdate(false)->save();
+
+        return $this;
+
+    }
+
+    /**
+     * 后台管理编辑文章
+     * @param array        $data       文章数据
+     * @param array|string $categories 文章分类 id
+     * @return $this
+     * @throws \think\Exception
+     */
+    public function adminEditArticle($data)
+    {
+
+        unset($data['user_id']);
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+            $data['thumbnail']         = $data['more']['thumbnail'];
+        }
+
+        if (!empty($data['more']['audio'])) {
+            $data['more']['audio'] = cmf_asset_relative_url($data['more']['audio']);
+        }
+
+        if (!empty($data['more']['video'])) {
+            $data['more']['video'] = cmf_asset_relative_url($data['more']['video']);
+        }
+
+        $this->allowField(true)->isUpdate(true)->data($data, true)->save();
+
+        return $this;
+
+    }
+
+    /**
+     * 增加标签
+     * @param $keywords
+     * @param $articleId
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @throws \think\exception\PDOException
+     */
+    public function addTags($keywords, $articleId)
+    {
+        $portalTagModel = new PortalTagModel();
+
+        $tagIds = [];
+
+        $data = [];
+
+        if (!empty($keywords)) {
+
+            $oldTagIds = Db::name('portal_tag_post')->where('post_id', $articleId)->column('tag_id');
+
+            foreach ($keywords as $keyword) {
+                $keyword = trim($keyword);
+                if (!empty($keyword)) {
+                    $findTag = $portalTagModel->where('name', $keyword)->find();
+                    if (empty($findTag)) {
+                        $tagId = $portalTagModel->insertGetId([
+                            'name' => $keyword
+                        ]);
+                    } else {
+                        $tagId = $findTag['id'];
+                    }
+
+                    if (!in_array($tagId, $oldTagIds)) {
+                        array_push($data, ['tag_id' => $tagId, 'post_id' => $articleId]);
+                    }
+
+                    array_push($tagIds, $tagId);
+
+                }
+            }
+
+
+            if (empty($tagIds) && !empty($oldTagIds)) {
+                Db::name('portal_tag_post')->where('post_id', $articleId)->delete();
+            }
+
+            $sameTagIds = array_intersect($oldTagIds, $tagIds);
+
+            $shouldDeleteTagIds = array_diff($oldTagIds, $sameTagIds);
+
+            if (!empty($shouldDeleteTagIds)) {
+                Db::name('portal_tag_post')
+                    ->where('post_id', $articleId)
+                    ->where('tag_id', 'in', $shouldDeleteTagIds)
+                    ->delete();
+            }
+
+            if (!empty($data)) {
+                Db::name('portal_tag_post')->insertAll($data);
+            }
+
+
+        } else {
+            Db::name('portal_tag_post')->where('post_id', $articleId)->delete();
+        }
+    }
+
+    /**
+     * @param $data
+     * @return bool
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function adminDeletePage($data)
+    {
+
+        if (isset($data['id'])) {
+            $id = $data['id']; //获取删除id
+
+            $res = $this->where('id', $id)->find();
+
+            if ($res) {
+                $res = json_decode(json_encode($res), true); //转换为数组
+
+                $recycleData = [
+                    'object_id'   => $res['id'],
+                    'create_time' => time(),
+                    'table_name'  => 'portal_post#page',
+                    'name'        => $res['post_title'],
+
+                ];
+
+                Db::startTrans(); //开启事务
+                $transStatus = false;
+                try {
+                    Db::name('portal_post')->where('id', $id)->update([
+                        'delete_time' => time()
+                    ]);
+                    Db::name('recycle_bin')->insert($recycleData);
+
+                    $transStatus = true;
+                    // 提交事务
+                    Db::commit();
+                } catch (\Exception $e) {
+
+                    // 回滚事务
+                    Db::rollback();
+                }
+                return $transStatus;
+
+
+            } else {
+                return false;
+            }
+        } elseif (isset($data['ids'])) {
+            $ids = $data['ids'];
+
+            $res = $this->where('id', 'in', $ids)
+                ->select();
+
+            if ($res) {
+                $res = json_decode(json_encode($res), true);
+                foreach ($res as $key => $value) {
+                    $recycleData[$key]['object_id']   = $value['id'];
+                    $recycleData[$key]['create_time'] = time();
+                    $recycleData[$key]['table_name']  = 'portal_post';
+                    $recycleData[$key]['name']        = $value['post_title'];
+
+                }
+
+                Db::startTrans(); //开启事务
+                $transStatus = false;
+                try {
+                    Db::name('portal_post')->where('id', 'in', $ids)
+                        ->update([
+                            'delete_time' => time()
+                        ]);
+
+
+                    Db::name('recycle_bin')->insertAll($recycleData);
+
+                    $transStatus = true;
+                    // 提交事务
+                    Db::commit();
+
+                } catch (\Exception $e) {
+
+                    // 回滚事务
+                    Db::rollback();
+
+
+                }
+                return $transStatus;
+
+
+            } else {
+                return false;
+            }
+
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 后台管理添加页面
+     * @param array $data 页面数据
+     * @return $this
+     */
+    public function adminAddPage($data)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+        }
+
+        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+        $data['post_type']   = 2;
+        $this->allowField(true)->data($data, true)->save();
+
+        return $this;
+
+    }
+
+    /**
+     * 后台管理编辑页面
+     * @param array $data 页面数据
+     * @return $this
+     */
+    public function adminEditPage($data)
+    {
+        $data['user_id'] = cmf_get_current_admin_id();
+
+        if (!empty($data['more']['thumbnail'])) {
+            $data['more']['thumbnail'] = cmf_asset_relative_url($data['more']['thumbnail']);
+        }
+
+        $data['post_status'] = empty($data['post_status']) ? 0 : 1;
+        $data['post_type']   = 2;
+        $this->allowField(true)->isUpdate(true)->data($data, true)->save();
+
+        $routeModel = new RouteModel();
+        $routeModel->setRoute($data['post_alias'], 'portal/Page/index', ['id' => $data['id']], 2, 5000);
+
+        $routeModel->getRoutes(true);
+        return $this;
+    }
+
+}

+ 21 - 0
app/portal/model/PortalTagModel.php

@@ -0,0 +1,21 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author:kane < chengjin005@163.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use think\Model;
+
+class PortalTagModel extends Model
+{
+    public static   $STATUS = array(
+        0=>"未启用",
+        1=>"已启用",
+    );
+}

+ 23 - 0
app/portal/model/UserModel.php

@@ -0,0 +1,23 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\model;
+
+use think\Model;
+
+class UserModel extends Model
+{
+
+    protected $type = [
+        'more' => 'array',
+    ];
+
+
+}

+ 14 - 0
app/portal/nav.php

@@ -0,0 +1,14 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    ['name' => '文章分类', 'api' => "Category/nav"],
+    ['name' => '所有页面', 'api' => "Page/nav"]
+];

+ 459 - 0
app/portal/service/ApiService.php

@@ -0,0 +1,459 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\service;
+
+use app\portal\model\PortalPostModel;
+use app\portal\model\PortalCategoryModel;
+use think\Db;
+use think\db\Query;
+
+class ApiService
+{
+    /**
+     * 功能:查询文章列表,支持分页;<br>
+     * 注:此方法查询时关联两个表portal_category_post(category_post),portal_post(post);在指定排序(order),指定查询条件(where)最好指定一下表别名
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'category_ids'=>'',
+     *                     'where'=>'',
+     *                     'limit'=>'',
+     *                     'order'=>'',
+     *                     'page'=>'',
+     *                     'relation'=>''
+     *                     )
+     *                     字段说明:
+     *                     category_ids:文章所在分类,可指定一个或多个分类id,以英文逗号分隔,如1或1,2,3 默认值为全部
+     *                     field:调用指定的字段@todo
+     *                     如只调用posts表里的id和post_title字段可以是post.id,post.post_title; 默认全部,
+     *                     此方法查询时关联两个表portal_category_post(category_post),portal_post(post);
+     *                     所以最好指定一下表名,以防字段冲突
+     *                     limit:数据条数,默认值为10,可以指定从第几条开始,如3,8(表示共调用8条,从第3条开始)
+     *                     order:排序方式,如按posts表里的published_time字段倒序排列:post.published_time desc
+     *                     where:查询条件,字符串形式,和sql语句一样,请在事先做好安全过滤,最好使用第二个参数$where的数组形式进行过滤,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突,查询条件(只支持数组),格式和thinkPHP的where方法一样,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突;
+     *                     </pre>
+     * @return array 包括分页的文章列表<pre>
+     *                     格式:
+     *                     array(
+     *                     "articles"=>array(),//文章列表,array
+     *                     "page"=>"",//生成的分页html,不分页则没有此项
+     *                     "total"=>100, //符合条件的文章总数,不分页则没有此项
+     *                     "total_pages"=>5 // 总页数,不分页则没有此项
+     *                     )</pre>
+     */
+    public static function articles($param)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post.post_status' => 1,
+            'post.post_type'   => 1,
+            'post.delete_time' => 0
+        ];
+
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $limit       = empty($param['limit']) ? 10 : $param['limit'];
+        $order       = empty($param['order']) ? '' : $param['order'];
+        $page        = isset($param['page']) ? $param['page'] : false;
+        $relation    = empty($param['relation']) ? '' : $param['relation'];
+        $categoryIds = empty($param['category_ids']) ? '' : $param['category_ids'];
+
+        $join = [
+            //['__USER__ user', 'post.user_id = user.id'],
+        ];
+
+        $whereCategoryId = null;
+
+        if (!empty($categoryIds)) {
+
+            $field = !empty($param['field']) ? $param['field'] : 'post.*,min(category_post.category_id) as category_id';
+            array_push($join, ['__PORTAL_CATEGORY_POST__ category_post', 'post.id = category_post.post_id']);
+
+            if (!is_array($categoryIds)) {
+                $categoryIds = explode(',', $categoryIds);
+            }
+
+            if (count($categoryIds) == 1) {
+                $whereCategoryId = function (Query $query) use ($categoryIds) {
+                    $query->where('category_post.category_id', $categoryIds[0]);
+                };
+            } else {
+                $whereCategoryId = function (Query $query) use ($categoryIds) {
+                    $query->where('category_post.category_id', 'in', $categoryIds);
+                };
+            }
+        } else {
+
+            $field = !empty($param['field']) ? $param['field'] : 'post.*,min(category_post.category_id) as category_id';
+            array_push($join, ['__PORTAL_CATEGORY_POST__ category_post', 'post.id = category_post.post_id']);
+        }
+
+        $articles = $portalPostModel->alias('post')->field($field)
+            ->join($join)
+            ->where($where)
+            ->where($paramWhere)
+            ->where($whereCategoryId)
+            ->where('post.published_time', ['> time', 0], ['<', time()], 'and')
+            ->order($order)
+            ->group('post.id');
+
+        $return = [];
+
+        if (empty($page)) {
+            $articles = $articles->limit($limit)->select();
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $return['articles'] = $articles;
+        } else {
+
+            if (is_array($page)) {
+                if (empty($page['list_rows'])) {
+                    $page['list_rows'] = 10;
+                }
+
+                $articles = $articles->paginate($page);
+            } else {
+                $articles = $articles->paginate(intval($page));
+            }
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $articles->appends(request()->get());
+            $articles->appends(request()->post());
+
+            $return['articles']    = $articles->items();
+            $return['page']        = $articles->render();
+            $return['total']       = $articles->total();
+            $return['total_pages'] = $articles->lastPage();
+        }
+
+
+        return $return;
+
+    }
+
+    /**
+     * 功能:查询标签文章列表,支持分页;<br>
+     * 注:此方法查询时关联两个表portal_tag_post(tag_post),portal_post(post);在指定排序(order),指定查询条件(where)最好指定一下表别名
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'tag_id'=>'',
+     *                     'where'=>'',
+     *                     'limit'=>'',
+     *                     'order'=>'',
+     *                     'page'=>'',
+     *                     'relation'=>''
+     *                     )
+     *                     字段说明:
+     *                     field:调用指定的字段@todo
+     *                     如只调用posts表里的id和post_title字段可以是post.id,post.post_title; 默认全部,
+     *                     此方法查询时关联两个表portal_tag_post(category_post),portal_post(post);
+     *                     所以最好指定一下表名,以防字段冲突
+     *                     limit:数据条数,默认值为10,可以指定从第几条开始,如3,8(表示共调用8条,从第3条开始)
+     *                     order:排序方式,如按posts表里的published_time字段倒序排列:post.published_time desc
+     *                     where:查询条件,字符串形式,和sql语句一样,请在事先做好安全过滤,最好使用第二个参数$where的数组形式进行过滤,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突,查询条件(只支持数组),格式和thinkPHP的where方法一样,此方法查询时关联多个表,所以最好指定一下表名,以防字段冲突;
+     *                     </pre>
+     * @return array 包括分页的文章列表<pre>
+     *                     格式:
+     *                     array(
+     *                     "articles"=>array(),//文章列表,array
+     *                     "page"=>"",//生成的分页html,不分页则没有此项
+     *                     "total"=>100, //符合条件的文章总数,不分页则没有此项
+     *                     "total_pages"=>5 // 总页数,不分页则没有此项
+     *                     )</pre>
+     */
+    public static function tagArticles($param)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post.post_status' => 1,
+            'post.post_type'   => 1,
+            'post.delete_time' => 0
+        ];
+
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $limit    = empty($param['limit']) ? 10 : $param['limit'];
+        $order    = empty($param['order']) ? '' : $param['order'];
+        $page     = isset($param['page']) ? $param['page'] : false;
+        $relation = empty($param['relation']) ? '' : $param['relation'];
+        $tagId    = empty($param['tag_id']) ? '' : $param['tag_id'];
+
+        $join = [
+            //['__USER__ user', 'post.user_id = user.id'],
+        ];
+
+        if (empty($tagId)) {
+            return null;
+
+        } else {
+            $field = !empty($param['field']) ? $param['field'] : 'post.*';
+            array_push($join, ['__PORTAL_TAG_POST__ tag_post', 'post.id = tag_post.post_id']);
+
+            $where['tag_post.tag_id'] = $tagId;
+        }
+
+        $articles = $portalPostModel->alias('post')->field($field)
+            ->join($join)
+            ->where($where)
+            ->where($paramWhere)
+            ->where('post.published_time', ['> time', 0], ['<', time()], 'and')
+            ->order($order);
+
+        $return = [];
+
+        if (empty($page)) {
+            $articles = $articles->limit($limit)->select();
+
+            if (!empty($relation) && !empty($articles['items'])) {
+                $articles->load($relation);
+            }
+
+            $return['articles'] = $articles;
+        } else {
+
+            if (is_array($page)) {
+                if (empty($page['list_rows'])) {
+                    $page['list_rows'] = 10;
+                }
+
+                $articles = $articles->paginate($page);
+            } else {
+                $articles = $articles->paginate(intval($page));
+            }
+
+            if (!empty($relation) && !empty($articles->items())) {
+                $articles->load($relation);
+            }
+
+            $articles->appends(request()->get());
+            $articles->appends(request()->post());
+
+            $return['articles']    = $articles->items();
+            $return['page']        = $articles->render();
+            $return['total']       = $articles->total();
+            $return['total_pages'] = $articles->lastPage();
+        }
+
+        return $return;
+    }
+
+    /**
+     * 获取指定id的文章
+     * @param int $id
+     * @return array|false|\PDOStatement|string|\think\Model
+     */
+    public static function article($id)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 1,
+            'id'          => $id,
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel->where($where)
+            ->where('published_time', ['> time', 0], ['<', time()], 'and')
+            ->find();
+    }
+
+    /**
+     * 获取指定条件的页面列表
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'where'=>'',
+     *                     'order'=>'',
+     *                     )</pre>
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public static function pages($param)
+    {
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $order = empty($param['order']) ? '' : $param['order'];
+
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 2, //页面
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel
+            ->where($where)
+            ->where($paramWhere)
+            ->where('published_time', [['> time', 0], ['<', time()]], 'and')
+            ->order($order)
+            ->select();
+    }
+
+    /**
+     * 获取指定id的页面
+     * @param int $id 页面的id
+     * @return array|false|\PDOStatement|string|\think\Model 返回符合条件的页面
+     */
+    public static function page($id)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        $where = [
+            'post_status' => 1,
+            'post_type'   => 2,
+            'id'          => $id,
+            'delete_time' => 0
+        ];
+
+        return $portalPostModel->where($where)
+            ->where('published_time', ['> time', 0], ['<', time()], 'and')
+            ->find();
+    }
+
+    /**
+     * 返回指定分类
+     * @param int $id 分类id
+     * @return array 返回符合条件的分类
+     */
+    public static function category($id)
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+            'id'          => $id
+        ];
+
+        return $portalCategoryModel->where($where)->find();
+    }
+
+    /**
+     * 返回指定分类下的子分类
+     * @param int $categoryId 分类id
+     * @param     $field      string  指定查询字段
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     * @return false|\PDOStatement|string|\think\Collection 返回指定分类下的子分类
+     */
+    public static function subCategories($categoryId, $field = '*')
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+            'parent_id'   => $categoryId
+        ];
+
+		return $portalCategoryModel->field($field)->where($where)->order('list_order ASC')->select();
+	}
+
+    /**
+     * 返回指定分类下的所有子分类
+     * @param int $categoryId 分类id
+     * @return array 返回指定分类下的所有子分类
+     */
+    public static function allSubCategories($categoryId)
+    {
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $categoryId = intval($categoryId);
+
+        if ($categoryId !== 0) {
+            $category = $portalCategoryModel->field('path')->where('id', $categoryId)->find();
+
+            if (empty($category)) {
+                return [];
+            }
+
+            $categoryPath = $category['path'];
+        } else {
+            $categoryPath = 0;
+        }
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0
+        ];
+
+        return $portalCategoryModel->where($where)->whereLike('path', "$categoryPath-%")->select();
+    }
+
+    /**
+     * 返回符合条件的所有分类
+     * @param array $param 查询参数<pre>
+     *                     array(
+     *                     'where'=>'',
+     *                     'order'=>'',
+     *                     )</pre>
+     * @return false|\PDOStatement|string|\think\Collection
+     */
+    public static function categories($param)
+    {
+        $paramWhere = empty($param['where']) ? '' : $param['where'];
+
+        $order = empty($param['order']) ? '' : $param['order'];
+
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $where = [
+            'status'      => 1,
+            'delete_time' => 0,
+        ];
+
+        $temp = $portalCategoryModel
+            ->where($where)
+            ->where($paramWhere)
+            ->order($order);
+
+        if (!empty($param['ids'])) {
+            $temp->whereIn('id', $param['ids']);
+        }
+        return $temp->select();
+    }
+
+    /**
+     * 获取面包屑数据
+     * @param int     $categoryId  当前文章所在分类,或者当前分类的id
+     * @param boolean $withCurrent 是否获取当前分类
+     * @return array 面包屑数据
+     */
+    public static function breadcrumb($categoryId, $withCurrent = false)
+    {
+        $data                = [];
+        $portalCategoryModel = new PortalCategoryModel();
+
+        $path = $portalCategoryModel->where(['id' => $categoryId])->value('path');
+
+        if (!empty($path)) {
+            $parents = explode('-', $path);
+            if (!$withCurrent) {
+                array_pop($parents);
+            }
+
+            if (!empty($parents)) {
+                $data = $portalCategoryModel->where('id', 'in', $parents)->order('path ASC')->select();
+            }
+        }
+
+        return $data;
+    }
+
+}

+ 287 - 0
app/portal/service/PostService.php

@@ -0,0 +1,287 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 小夏 < 449134904@qq.com>
+// +----------------------------------------------------------------------
+namespace app\portal\service;
+
+use app\portal\model\PortalPostModel;
+use think\db\Query;
+
+class PostService
+{
+    /**
+     * 文章查询
+     * @param $filter
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminArticleList($filter)
+    {
+        return $this->adminPostList($filter);
+    }
+
+    /**
+     * 页面文章列表
+     * @param $filter
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminPageList($filter)
+    {
+        return $this->adminPostList($filter, true);
+    }
+
+    /**
+     * 文章查询
+     * @param      $filter
+     * @param bool $isPage
+     * @return \think\Paginator
+     * @throws \think\exception\DbException
+     */
+    public function adminPostList($filter, $isPage = false)
+    {
+
+        $join = [
+            ['__USER__ u', 'a.user_id = u.id']
+        ];
+
+        $field = 'a.*,u.user_login,u.user_nickname,u.user_email';
+
+        $category = empty($filter['category']) ? 0 : intval($filter['category']);
+        if (!empty($category)) {
+            array_push($join, [
+                '__PORTAL_CATEGORY_POST__ b', 'a.id = b.post_id'
+            ]);
+            $field = 'a.*,b.id AS post_category_id,b.list_order,b.category_id,u.user_login,u.user_nickname,u.user_email';
+        }
+
+        $portalPostModel = new PortalPostModel();
+        $articles        = $portalPostModel->alias('a')->field($field)
+            ->join($join)
+            ->where('a.create_time', '>=', 0)
+            ->where('a.delete_time', 0)
+            ->where(function (Query $query) use ($filter, $isPage) {
+
+                $category = empty($filter['category']) ? 0 : intval($filter['category']);
+                if (!empty($category)) {
+                    $query->where('b.category_id', $category);
+                }
+
+                $startTime = empty($filter['start_time']) ? 0 : strtotime($filter['start_time']);
+                $endTime   = empty($filter['end_time']) ? 0 : strtotime($filter['end_time']);
+                if (!empty($startTime)) {
+                    $query->where('a.published_time', '>=', $startTime);
+                }
+                if (!empty($endTime)) {
+                    $query->where('a.published_time', '<=', $endTime);
+                }
+
+                $keyword = empty($filter['keyword']) ? '' : $filter['keyword'];
+                if (!empty($keyword)) {
+                    $query->where('a.post_title', 'like', "%$keyword%");
+                }
+
+                if ($isPage) {
+                    $query->where('a.post_type', 2);
+                } else {
+                    $query->where('a.post_type', 1);
+                }
+            })
+            ->order('update_time', 'DESC')
+            ->paginate(10, false, ['query' => $filter]);
+
+        return $articles;
+
+    }
+
+    /**
+     * 已发布文章查询
+     * @param  int $postId     文章id
+     * @param int  $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+                'post.id'          => $postId
+            ];
+
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->where($where)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->find();
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+                'relation.post_id'     => $postId
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 上一篇文章
+     * @param int $postId     文章id
+     * @param int $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedPrevArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+            ];
+
+            $article = $portalPostModel
+                ->alias('post')
+                ->field('post.*')
+                ->where($where)
+                ->where('post.id', '<', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'DESC')
+                ->find();
+
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel
+                ->alias('post')
+                ->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('relation.post_id', '<', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'DESC')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 下一篇文章
+     * @param int $postId     文章id
+     * @param int $categoryId 分类id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedNextArticle($postId, $categoryId = 0)
+    {
+        $portalPostModel = new PortalPostModel();
+
+        if (empty($categoryId)) {
+
+            $where = [
+                'post.post_type'   => 1,
+                'post.post_status' => 1,
+                'post.delete_time' => 0,
+            ];
+
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->where($where)
+                ->where('post.id', '>', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'ASC')
+                ->find();
+        } else {
+            $where = [
+                'post.post_type'       => 1,
+                'post.post_status'     => 1,
+                'post.delete_time'     => 0,
+                'relation.category_id' => $categoryId,
+
+            ];
+
+            $join    = [
+                ['__PORTAL_CATEGORY_POST__ relation', 'post.id = relation.post_id']
+            ];
+            $article = $portalPostModel->alias('post')->field('post.*')
+                ->join($join)
+                ->where($where)
+                ->where('relation.post_id', '>', $postId)
+                ->where('post.published_time', ['< time', time()], ['> time', 0], 'and')
+                ->order('id', 'ASC')
+                ->find();
+        }
+
+
+        return $article;
+    }
+
+    /**
+     * 页面管理查询
+     * @param int $pageId 文章id
+     * @return array|string|\think\Model|null
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\exception\DbException
+     */
+    public function publishedPage($pageId)
+    {
+
+        $where = [
+            'post_type'   => 2,
+            'post_status' => 1,
+            'delete_time' => 0,
+            'id'          => $pageId
+        ];
+
+        $portalPostModel = new PortalPostModel();
+        $page            = $portalPostModel
+            ->where($where)
+            ->where('published_time', ['< time', time()], ['> time', 0], 'and')
+            ->find();
+
+        return $page;
+    }
+
+}

+ 372 - 0
app/portal/taglib/Portal.php

@@ -0,0 +1,372 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+namespace app\portal\taglib;
+
+use think\template\TagLib;
+
+class Portal extends TagLib
+{
+    /**
+     * 定义标签列表
+     */
+    protected $tags = [
+        // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次
+        'articles'         => ['attr' => 'field,where,limit,order,page,relation,returnVarName,pageVarName,categoryIds', 'close' => 1],//非必须属性item
+        'tagarticles'      => ['attr' => 'field,where,limit,order,page,relation,returnVarName,pageVarName,tagId', 'close' => 1],//非必须属性item
+        'page'             => ['attr' => 'id', 'close' => 1],//非必须属性item
+        'breadcrumb'       => ['attr' => 'cid', 'close' => 1],//非必须属性self
+        'categories'       => ['attr' => 'ids,where,order', 'close' => 1],//非必须属性item
+        'category'         => ['attr' => 'id', 'close' => 1],//非必须属性item
+        'subcategories'    => ['attr' => 'categoryId', 'close' => 1],//非必须属性item
+        'allsubcategories' => ['attr' => 'categoryId', 'close' => 1],//非必须属性item
+    ];
+
+    /**
+     * 文章列表标签
+     */
+    public function tagArticles($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? 'post.published_time DESC' : $tag['order'];
+        $relation      = empty($tag['relation']) ? '' : $tag['relation'];
+        $pageVarName   = empty($tag['pageVarName']) ? '__PAGE_VAR_NAME__' : $tag['pageVarName'];
+        $returnVarName = empty($tag['returnVarName']) ? 'articles_data' : $tag['returnVarName'];
+
+        $field = "''";
+        if (!empty($tag['field'])) {
+            if (strpos($tag['field'], '$') === 0) {
+                $field = $tag['field'];
+                $this->autoBuildVar($field);
+            } else {
+                $field = "'{$tag['field']}'";
+            }
+        }
+
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $limit = "''";
+        if (!empty($tag['limit'])) {
+            if (strpos($tag['limit'], '$') === 0) {
+                $limit = $tag['limit'];
+                $this->autoBuildVar($limit);
+            } else {
+                $limit = "'{$tag['limit']}'";
+            }
+        }
+
+        $page = "''";
+        if (!empty($tag['page'])) {
+            if (strpos($tag['page'], '$') === 0) {
+                $page = $tag['page'];
+                $this->autoBuildVar($page);
+            } else {
+                $page = intval($tag['page']);
+                $page = "'{$page}'";
+            }
+        }
+
+        $categoryIds = "''";
+        if (!empty($tag['categoryIds'])) {
+            if (strpos($tag['categoryIds'], '$') === 0) {
+                $categoryIds = $tag['categoryIds'];
+                $this->autoBuildVar($categoryIds);
+            } else {
+                $categoryIds = "'{$tag['categoryIds']}'";
+            }
+        }
+
+        if (!empty($order) && strpos($order, '$') === 0) {
+            $this->autoBuildVar($order);
+        } else {
+            $order = "'{$order}'";
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::articles([
+    'field'   => {$field},
+    'where'   => {$where},
+    'limit'   => {$limit},
+    'order'   => {$order},
+    'page'    => {$page},
+    'relation'=> '{$relation}',
+    'category_ids'=>{$categoryIds}
+]);
+
+\${$pageVarName} = isset(\${$returnVarName}['page'])?\${$returnVarName}['page']:'';
+
+ ?>
+<volist name="{$returnVarName}.articles" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 标签文章列表标签
+     */
+    public function tagTagArticles($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? 'post.published_time DESC' : $tag['order'];
+        $relation      = empty($tag['relation']) ? '' : $tag['relation'];
+        $pageVarName   = empty($tag['pageVarName']) ? '__PAGE_VAR_NAME__' : $tag['pageVarName'];
+        $returnVarName = empty($tag['returnVarName']) ? 'tag_articles_data' : $tag['returnVarName'];
+
+        $field = "''";
+        if (!empty($tag['field'])) {
+            if (strpos($tag['field'], '$') === 0) {
+                $field = $tag['field'];
+                $this->autoBuildVar($field);
+            } else {
+                $field = "'{$tag['field']}'";
+            }
+        }
+
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $limit = "''";
+        if (!empty($tag['limit'])) {
+            if (strpos($tag['limit'], '$') === 0) {
+                $limit = $tag['limit'];
+                $this->autoBuildVar($limit);
+            } else {
+                $limit = "'{$tag['limit']}'";
+            }
+        }
+
+        $page = "''";
+        if (!empty($tag['page'])) {
+            if (strpos($tag['page'], '$') === 0) {
+                $page = $tag['page'];
+                $this->autoBuildVar($page);
+            } else {
+                $page = intval($tag['page']);
+                $page = "'{$page}'";
+            }
+        }
+
+        $tagId = "''";
+        if (!empty($tag['tagId'])) {
+            if (strpos($tag['tagId'], '$') === 0) {
+                $tagId = $tag['tagId'];
+                $this->autoBuildVar($tagId);
+            } else {
+                $tagId = "'{$tag['tagId']}'";
+            }
+        }
+
+        if (strpos($order, '$') === 0) {
+            $this->autoBuildVar($order);
+        } else {
+            $order = "'{$order}'";
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::tagArticles([
+    'field'   => {$field},
+    'where'   => {$where},
+    'limit'   => {$limit},
+    'order'   => {$order},
+    'page'    => {$page},
+    'relation'=> '{$relation}',
+    'tag_id'=>{$tagId}
+]);
+
+\${$pageVarName} = isset(\${$returnVarName}['page'])?\${$returnVarName}['page']:'';
+
+ ?>
+<volist name="{$returnVarName}.articles" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 单页文章标签
+     */
+    public function tagPage($tag, $content)
+    {
+        $id = empty($tag['id']) ? 0 : $tag['id'];
+        if (strpos($id, '$') === 0) {
+            $this->autoBuildVar($id);
+        }
+        $returnVarName = empty($tag['item']) ? 'portal_page' : $tag['item'];
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::page({$id});
+?>
+{$content}
+parse;
+        return $parse;
+    }
+
+    /**
+     * 面包屑标签
+     */
+    public function tagBreadcrumb($tag, $content)
+    {
+        $cid = empty($tag['cid']) ? '0' : $tag['cid'];
+
+        if (!empty($cid)) {
+            $this->autoBuildVar($cid);
+        }
+
+        $self = isset($tag['self']) ? $tag['self'] : 'false';
+
+        $parse = <<<parse
+<?php
+if(!empty({$cid})){
+    \$__BREADCRUMB_ITEMS__ = \app\portal\service\ApiService::breadcrumb({$cid},{$self});
+?>
+
+<volist name="__BREADCRUMB_ITEMS__" id="vo">
+    {$content}
+</volist>
+
+<?php
+}
+?>
+parse;
+
+        return $parse;
+
+    }
+
+    /**
+     * 文章分类标签
+     */
+    public function tagCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $order         = empty($tag['order']) ? '' : $tag['order'];
+        $ids           = empty($tag['ids']) ? '""' : $tag['ids'];
+        $returnVarName = 'portal_categories_data';
+        if (strpos($ids, '$') === 0) {
+            $this->autoBuildVar($ids);
+        }
+        $where = '""';
+        if (!empty($tag['where']) && strpos($tag['where'], '$') === 0) {
+            $where = $tag['where'];
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::categories([
+    'where'   => {$where},
+    'order'   => '{$order}',
+    'ids'     => {$ids}
+]);
+
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+    /**
+     * 文章分类详情标签
+     * @param array  $tag
+     * @param string $content
+     * @return string
+     */
+    public function tagCategory($tag, $content)
+    {
+        $id = empty($tag['id']) ? 0 : $tag['id'];
+        if (strpos($id, '$') === 0) {
+            $this->autoBuildVar($id);
+        }
+        $returnVarName = empty($tag['item']) ? 'portal_category' : $tag['item'];
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::category({$id});
+?>
+{$content}
+parse;
+        return $parse;
+    }
+
+    /**
+     * 文章子分类标签
+     */
+    public function tagSubCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $returnVarName = 'portal_sub_categories_data';
+
+        $categoryId = "0";
+        if (!empty($tag['categoryId'])) {
+            if (strpos($tag['categoryId'], '$') === 0) {
+                $categoryId = $tag['categoryId'];
+                $this->autoBuildVar($categoryId);
+            } else {
+                $categoryId = intval($tag['categoryId']);
+                $categoryId = "{$categoryId}";
+            }
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::subCategories({$categoryId});
+ 
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+
+    /**
+     * 文章分类所有子分类标签
+     */
+    public function tagAllSubCategories($tag, $content)
+    {
+        $item          = empty($tag['item']) ? 'vo' : $tag['item'];//循环变量名
+        $returnVarName = 'portal_all_sub_categories_data';
+
+        $categoryId = "0";
+        if (!empty($tag['categoryId'])) {
+            if (strpos($tag['categoryId'], '$') === 0) {
+                $categoryId = $tag['categoryId'];
+                $this->autoBuildVar($categoryId);
+            } else {
+                $categoryId = intval($tag['categoryId']);
+                $categoryId = "{$categoryId}";
+            }
+        }
+
+        $parse = <<<parse
+<?php
+\${$returnVarName} = \app\portal\service\ApiService::allSubCategories({$categoryId});
+ ?>
+<volist name="{$returnVarName}" id="{$item}">
+{$content}
+</volist>
+parse;
+        return $parse;
+    }
+
+}

+ 53 - 0
app/portal/url.php

@@ -0,0 +1,53 @@
+<?php
+// +----------------------------------------------------------------------
+// | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2013-2019 http://www.thinkcmf.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
+// +----------------------------------------------------------------------
+// | Author: 老猫 <thinkcmf@126.com>
+// +----------------------------------------------------------------------
+return [
+    'List/index'    => [
+        'name'   => '门户应用-文章列表',
+        'vars'   => [
+            'id' => [
+                'pattern' => '\d+',
+                'require' => true
+            ]
+        ],
+        'simple' => true
+    ],
+    'Page/index'    => [
+        'name'   => '门户应用-页面页',
+        'vars'   => [
+            'id' => [
+                'pattern' => '\d+',
+                'require' => true
+            ]
+        ],
+        'simple' => true
+    ],
+    'Article/index' => [
+        'name'   => '门户应用-文章页',
+        'vars'   => [
+            'id'  => [
+                'pattern' => '\d+',
+                'require' => true
+            ],
+            'cid' => [
+                'pattern' => '\d+',
+                'require' => false
+            ]
+        ],
+        'simple' => true
+    ],
+    'Search/index'  => [
+        'name'   => '门户应用-搜索页',
+        'vars'   => [
+
+        ],
+        'simple' => false
+    ],
+];

+ 15 - 0
app/portal/user_action.php

@@ -0,0 +1,15 @@
+<?php
+return [
+//    'test' => [
+//        'name'          => '用户登录',//用户操作名称
+//        'score'         => 1,//更改积分,可以为负
+//        'coin'          => 0,//更改金币,可以为负
+//        'cycle_time'    => 1,//周期时间值
+//        'cycle_type'    => 1,//周期类型;0:不限;1:按天;2:按小时;3:永久
+//        'reward_number' => 1,//奖励次数
+//        'url'           => [
+//            'action' => 'portal/Test/test',
+//            'param'  => ['id' => 1]
+//        ],//执行操作的url
+//    ]
+];

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů