a123456789 2 years ago
commit
bff0ff0e1f
100 changed files with 43853 additions and 0 deletions
  1. 15 0
      .editorconfig
  2. 61 0
      .env.example
  3. 5 0
      .gitattributes
  4. 14 0
      .gitignore
  5. 0 0
      .htaccess
  6. 191 0
      app/Console/Commands/ArticleMonitor.php
  7. 99 0
      app/Console/Commands/Beg.php
  8. 73 0
      app/Console/Commands/KeywordExpansionSync.php
  9. 93 0
      app/Console/Commands/Link.php
  10. 88 0
      app/Console/Commands/MasterMeter.php
  11. 192 0
      app/Console/Commands/Message.php
  12. 135 0
      app/Console/Commands/MessageInquiry.php
  13. 101 0
      app/Console/Commands/Num.php
  14. 134 0
      app/Console/Commands/PlanWeek.php
  15. 65 0
      app/Console/Commands/SiteStatusCheck.php
  16. 67 0
      app/Console/Commands/SocialSync.php
  17. 53 0
      app/Console/Commands/StatusSync.php
  18. 89 0
      app/Console/Commands/TemplateLibrarySyncPictures.php
  19. 47 0
      app/Console/Commands/Test.php
  20. 88 0
      app/Console/Commands/UrlCheck.php
  21. 59 0
      app/Console/Kernel.php
  22. 61 0
      app/Exceptions/Handler.php
  23. 33 0
      app/Exports/BasicExport.php
  24. 90 0
      app/Exports/BqProgressRateExport.php
  25. 90 0
      app/Exports/CommonExport.php
  26. 115 0
      app/Exports/EditingScheduleExport.php
  27. 30 0
      app/Exports/InquireExport.php
  28. 42 0
      app/Exports/LinkCountExport.php
  29. 47 0
      app/Exports/LinkHallExport.php
  30. 91 0
      app/Exports/PlannerScheduleExport.php
  31. 96 0
      app/Exports/ProgressRateExport.php
  32. 47 0
      app/Exports/SocialTemplateExport.php
  33. 20 0
      app/Http/Constant/CacheConstant.php
  34. 200 0
      app/Http/Controllers/Admin/AgentController.php
  35. 21 0
      app/Http/Controllers/Admin/AliYunOssController.php
  36. 311 0
      app/Http/Controllers/Admin/Analyze/CollectController.php
  37. 348 0
      app/Http/Controllers/Admin/Analyze/GoogleTrendsController.php
  38. 4557 0
      app/Http/Controllers/Admin/Analyze/IndexController.php
  39. 4528 0
      app/Http/Controllers/Admin/Analyze/IndexController_bak.php
  40. 152 0
      app/Http/Controllers/Admin/Analyze/StaffController.php
  41. 1327 0
      app/Http/Controllers/Admin/ArticleController.php
  42. 1235 0
      app/Http/Controllers/Admin/ArticleController_bak.php
  43. 1238 0
      app/Http/Controllers/Admin/ArticleController_bak04-24.php
  44. 84 0
      app/Http/Controllers/Admin/AuthController.php
  45. 921 0
      app/Http/Controllers/Admin/BidController.php
  46. 404 0
      app/Http/Controllers/Admin/BqTrafficController.php
  47. 185 0
      app/Http/Controllers/Admin/ClassroomController.php
  48. 212 0
      app/Http/Controllers/Admin/CustomerUserController.php
  49. 77 0
      app/Http/Controllers/Admin/Enquiry/EnquiryController.php
  50. 354 0
      app/Http/Controllers/Admin/FinanceController.php
  51. 147 0
      app/Http/Controllers/Admin/FinanceController_bak.php
  52. 445 0
      app/Http/Controllers/Admin/Flow/IndexController.php
  53. 225 0
      app/Http/Controllers/Admin/Flow/PlanController.php
  54. 2461 0
      app/Http/Controllers/Admin/Flow/ProgressRateController.php
  55. 81 0
      app/Http/Controllers/Admin/Flow/TplController.php
  56. 427 0
      app/Http/Controllers/Admin/HootsuiteController.php
  57. 214 0
      app/Http/Controllers/Admin/HtPlantController.php
  58. 60 0
      app/Http/Controllers/Admin/ImageController.php
  59. 153 0
      app/Http/Controllers/Admin/IndexController.php
  60. 324 0
      app/Http/Controllers/Admin/InquireController.php
  61. 70 0
      app/Http/Controllers/Admin/InvoiceController.php
  62. 96 0
      app/Http/Controllers/Admin/LadingBillController.php
  63. 1674 0
      app/Http/Controllers/Admin/LinkController.php
  64. 33 0
      app/Http/Controllers/Admin/LogController.php
  65. 215 0
      app/Http/Controllers/Admin/MessageController.php
  66. 613 0
      app/Http/Controllers/Admin/MeterController.php
  67. 49 0
      app/Http/Controllers/Admin/NumController.php
  68. 1034 0
      app/Http/Controllers/Admin/Plan/TaskController.php
  69. 418 0
      app/Http/Controllers/Admin/ProcessController.php
  70. 72 0
      app/Http/Controllers/Admin/ProductController.php
  71. 678 0
      app/Http/Controllers/Admin/PromoteReportController.php
  72. 327 0
      app/Http/Controllers/Admin/PromoteYearController.php
  73. 188 0
      app/Http/Controllers/Admin/RankController.php
  74. 199 0
      app/Http/Controllers/Admin/ReportController.php
  75. 239 0
      app/Http/Controllers/Admin/ReptileController.php
  76. 113 0
      app/Http/Controllers/Admin/SchoolController.php
  77. 72 0
      app/Http/Controllers/Admin/ServerController.php
  78. 2698 0
      app/Http/Controllers/Admin/SiteController.php
  79. 2469 0
      app/Http/Controllers/Admin/SiteController2022.04.29.php
  80. 1769 0
      app/Http/Controllers/Admin/SiteController_bak.php
  81. 1941 0
      app/Http/Controllers/Admin/SiteController_bak2022-04.php
  82. 333 0
      app/Http/Controllers/Admin/SocialController.php
  83. 491 0
      app/Http/Controllers/Admin/SocialStatisticsController.php
  84. 205 0
      app/Http/Controllers/Admin/Stencil/AdvertiseController.php
  85. 873 0
      app/Http/Controllers/Admin/Stencil/IndexController.php
  86. 443 0
      app/Http/Controllers/Admin/Stencil/TemplateLibraryController.php
  87. 401 0
      app/Http/Controllers/Admin/Stencil/TplController.php
  88. 88 0
      app/Http/Controllers/Admin/System/PermissionController.php
  89. 92 0
      app/Http/Controllers/Admin/System/RoleController.php
  90. 190 0
      app/Http/Controllers/Admin/System/UserController.php
  91. 187 0
      app/Http/Controllers/Admin/ToolController.php
  92. 166 0
      app/Http/Controllers/Admin/User/UserController
  93. 203 0
      app/Http/Controllers/Admin/User/UserController.php
  94. 1466 0
      app/Http/Controllers/Admin/WebmasterController.php
  95. 354 0
      app/Http/Controllers/Admin/WitnessProject/WitnessProjectController.php
  96. 127 0
      app/Http/Controllers/Admin/WorkTaskController.php
  97. 74 0
      app/Http/Controllers/ApiController.php
  98. 32 0
      app/Http/Controllers/Auth/ForgotPasswordController.php
  99. 39 0
      app/Http/Controllers/Auth/LoginController.php
  100. 0 0
      app/Http/Controllers/Auth/RegisterController.php

+ 15 - 0
.editorconfig

@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.yml]
+indent_size = 2

+ 61 - 0
.env.example

@@ -0,0 +1,61 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
+
+LOG_CHANNEL=stack
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=homestead
+DB_USERNAME=homestead
+DB_PASSWORD=secret
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+QUEUE_CONNECTION=sync
+SESSION_DRIVER=file
+SESSION_LIFETIME=120
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_DRIVER=smtp
+MAIL_HOST=smtp.mailtrap.io
+MAIL_PORT=2525
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+PUSHER_APP_CLUSTER=mt1
+
+MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+
+
+#社会化账号配置
+
+LINKEDIN_CLIENT_ID=77wcfns3xa9bzr
+LINKEDIN_CLIENT_SECRET=zVo9O20BOmcS1Mgp
+LINKEDIN_REDIRECT_URL=http://admin.yinqingli.com/admin/socials/linked-in
+
+#twitter不需要去授权 这些参数可以直接在 twitter开发者后台获取
+TWITTER_CONSUMER_KEY=QfqLozpNfcbXp042lMmQtFQbE
+TWITTER_CONSUMER_SECRET=rI4crlFCxprr2DtbA9BygpymuiClkG6hKArU7C6uuyuKEfpAi2
+TWITTER_ACCESS_TOKEN=4296714074-8HUSqvOOfHRAnq880OQu3X36pZu69ltPOkddwYK
+TWITTER_ACCESS_TOKEN_SECRET=bxtLHmfZkfYj2GlNcoPLgMKjvmd23ad8hmZTI4IJCQbVM
+
+FACEBOOK_CLIENT_ID=2404825676269848
+FACEBOOK_CLIENT_SECRET=e4387c50b2e0388baa547f60f85aa8f9
+FACEBOOK_REDIRECT_URL=https://wall.yinqingli.com/demo/two
+
+#授权一次就是永久令牌
+PINTEREST_CLIENT_ID=5039687067087454400
+PINTEREST_CLIENT_SECRET=5297ced058bc3f52c06f1979d02366332943d5be46ac9903fb66283b6a51c0cc
+PINTEREST_REDIRECT_URL=https://wall.yinqingli.com/demo/two

+ 5 - 0
.gitattributes

@@ -0,0 +1,5 @@
+* text=auto
+*.css linguist-vendored
+*.scss linguist-vendored
+*.js linguist-vendored
+CHANGELOG.md export-ignore

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+/node_modules
+/public/hot
+/public/remote
+/public/storage
+/storage/*.key
+/vendor
+/.idea
+.env
+.phpunit.result.cache
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
+.project

+ 0 - 0
.htaccess


+ 191 - 0
app/Console/Commands/ArticleMonitor.php

@@ -0,0 +1,191 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Article;
+use App\Http\Models\ArticleStatistic;
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Relations\HasOne;
+
+class ArticleMonitor extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'article:monitor';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $sites = Site::query()->selectRaw('expired_at,id,status,renewal_at,order_at')->with(['siteInfo' => function (HasOne $q) {
+            $q->selectRaw('site_id,article_need_num');
+        }])->whereIn('status', [2,3])->get();
+
+
+        $beginAt = date('Y-m-01 00:00:00');
+        $endAt = date('Y-m-t 23:59:59');
+
+        $siteIds = array_column($sites->toArray(), 'id');
+        $createMap = Article::query()->selectRaw('SUM(id) as total,site_id')->whereIn('site_id', $siteIds)->where([['created_at', '>=', $beginAt], ['created_at', '<=', $endAt]])
+            ->groupBy('site_id')->pluck('total', 'site_id')->toArray();
+//        dump($createMap);
+
+        $pubMap = Article::query()->selectRaw('SUM(id) as total,site_id')->whereIn('site_id', $siteIds)->where([['publish_at', '>=', $beginAt], ['publish_at', '<=', $endAt]])
+            ->groupBy('site_id')->pluck('total', 'site_id')->toArray();
+//        dd($pubMap);
+
+
+        $totalNeedNum = [
+            'exec' => 0, //实施期
+            'server' => 0 //服务期
+        ];
+        //本月待查找文章数:实施期:xxx篇 服务期:xxx篇
+        //本月待更新文章数:实施期:xxx篇 服务期:xxx篇
+        $totalCreateNum = $totalPubNum = ['exec' => 0, 'server' => 0];
+
+//        dump('$siteIds=',$siteIds);
+
+        foreach ($sites as $site) {
+
+
+            if ($site->siteInfo->article_need_num ?? 0) {
+                $needNum = $site->siteInfo->article_need_num;
+
+            } else {
+                $whereAt = null;
+                if (!empty($site->renewal_at)) {
+                    $whereAt = $site->renewal_at;
+                } else {
+                    $whereAt = $site->order_at;
+                }
+                if (!$whereAt || empty($site->expired_at)) {
+//                    echo sprintf('whereAt continue=%d%s',$site->id,PHP_EOL);
+                    continue;
+                }
+
+                //距离到期时间的月份数
+                $md = $this->getMonthNum(strtotime($site->expired_at));
+                if ($md < 1) {
+//                    echo sprintf('md continue=%d%s',$site->id,PHP_EOL);
+                    continue;
+                }
+
+                //剩余文章数
+                $surplusNum = Article::query()->where([['created_at', '>', $whereAt], ['site_id', '=', $site->id]])->count();
+                $needNum = floor($surplusNum / $md);
+            }
+
+
+            if ($site->status == 2) { //实施期
+
+                $needNum = $needNum < 10 ? 10 : $needNum;
+                $totalNeedNum['exec'] += $needNum;
+
+                $createNum = $createMap[$site->id] ?? 0;
+
+
+
+
+                if ($createNum > $needNum) {
+                    $createNum = $needNum;
+                }
+                $pubNum = $pubMap[$site->id] ?? 0;
+
+
+                $totalCreateNum['exec'] += $createNum;
+                $totalPubNum['exec'] += $pubNum;
+
+            }
+
+            if ($site->status == 3) {
+                $needNum = $needNum < 4 ? 4 : $needNum;
+                $totalNeedNum['server'] += $needNum;
+
+
+                $createNum = $createMap[$site->id] ?? 0;
+                if ($createNum > $needNum) {
+                    $createNum = $needNum;
+                }
+                $pubNum = $pubMap[$site->id] ?? 0;
+
+                $totalCreateNum['server'] += $createNum;
+                $totalPubNum['server'] += $pubNum;
+            }
+        }
+
+
+        //本月待查找文章数量=所有项目的本月需更新文章数总和-已上传数量(根据上传时间计算)
+        //本月待更新文章数量=所有项目的本月需更新文章数总和-已发布数量(根据发布时间计算)
+
+        ArticleStatistic::query()->create([
+            'day' => date('Ymd'),
+            'query_num' =>
+                [
+                    'exec' => $totalNeedNum['exec'] - $totalCreateNum['exec'],
+                    'server' => $totalNeedNum['server'] - $totalCreateNum['server'],
+                ]
+            ,
+            'update_num' =>
+                [
+                    'exec' => $totalNeedNum['exec'] - $totalPubNum['exec'],
+                    'server' => $totalNeedNum['server'] - $totalPubNum['server']
+                ]
+
+
+        ]);
+
+
+        dump($totalNeedNum);
+
+        $this->info('success');
+        return;
+    }
+
+
+    protected function getMonthNum($targetTime)
+    {
+        $target = date('Ym', $targetTime);
+
+        $now = date('Ym');
+
+        $ty = substr($target, 0, 4);
+        $tm = substr($target, 4, 2);
+
+        $ny = substr($now, 0, 4);
+        $nm = substr($now, 4, 2);
+
+        $yd = intval($ty) - intval($ny);
+
+
+        $md = intval($tm - $nm);
+
+        $result = $yd * 12 + $md;
+
+        return $result;
+
+    }
+}

+ 99 - 0
app/Console/Commands/Beg.php

@@ -0,0 +1,99 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use GuzzleHttp\Client;
+use GuzzleHttp\Pool;
+use Illuminate\Console\Command;
+use GuzzleHttp\Psr7\Response;
+use Illuminate\Support\Facades\Log;
+
+class Beg extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'beg:master {type}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    const URL_MAP = [
+        'email' => 'queue/mail',
+        'report' => 'queue/report'
+    ];
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $type = $this->argument('type');
+        if (!in_array($type, ['email', 'report'])) {
+            $this->error('参数必须为email 或 report');
+            return;
+        }
+        $taskUrls = Site::query()->whereIn('status', [2, 3, 5, 6, 8, 9])->get()->pluck('api_url')->filter()->toArray();
+
+//        $http = new Client([
+//            'verify' => false,
+//            'timeout' => 60
+//        ]);
+
+//        $taskUrls->each(function ($item) use ($http, $type) {
+//
+//            try {
+//                $http->get(sprintf('%s%s', $item, self::URL_MAP[$type]));
+//            } catch (\Throwable $throwable) {
+//                Log::notice(var_export($throwable->getMessage(), 1));
+//            }
+//        });
+
+
+        $client = new Client([
+            'verify' => false,
+            'timeout' => 25
+        ]); //并发请求链接地址
+        $requests = function () use ($client, $taskUrls, $type) {
+            foreach ($taskUrls as $item) {
+                if (empty($item))
+                    continue;
+                yield new \GuzzleHttp\Psr7\Request('GET', $item . self::URL_MAP[$type]);
+            }
+        };
+        $pool = new Pool($client, $requests(), [
+            'concurrency' => 10, //同时并发抓取几个
+            'fulfilled' => function (Response $response, $index) {
+            },
+            'rejected' => function (\Throwable $throwable, $index) {
+//                $this->error($index . var_export($throwable->getMessage(), 1));
+                Log::error(var_export($throwable->getMessage(), 1));
+            },
+        ]);
+        $promise = $pool->promise();
+        $promise->wait();
+
+        $this->info('done');
+        return;
+    }
+}

+ 73 - 0
app/Console/Commands/KeywordExpansionSync.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\PrKeywordExtend;
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+
+class KeywordExpansionSync extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'sync:keyword_expansion';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+
+
+    /**
+     * 删除备注
+     * @return mixed
+     */
+    public function handle()
+    {
+        //每个项目最新的一次的记录的id
+        $maxIds = PrKeywordExtend::query()->selectRaw('max(id) as id_max,site_id')
+            ->groupBy('site_id')
+            ->pluck('id_max', 'site_id')->toArray();
+        //3个月内没有拓展过
+        $list = PrKeywordExtend::query()->whereIn('id', $maxIds)->get();
+        $ids = [];
+        $date = date("Ym", strtotime("last day of -3 month", strtotime(date("Ym"))));
+
+        foreach ($list as $value) {
+            if ($value->ym <= $date) {
+                $ids[] = $value->site_id;
+            }
+        }
+        Site::query()
+            ->whereIn('id', $ids)
+            ->whereIn('status', [2, 3])
+            ->update(['keyword_memo' => '']);
+
+        //从未拓展过
+        $date = date("Y-m-d", strtotime("last day of -3 month", strtotime(date("Y-m-d"))));
+        $id = Site::query()
+            ->where('online_at', '<=', $date)
+            ->whereIn('status', [2, 3])
+            ->pluck('id')->toArray();
+
+        $ids1 = [];
+        foreach ($id as $value) {
+            $result = PrKeywordExtend::query()->where('site_id', $value)->first();
+            if (empty($result)) {
+                $ids1[] = $value;
+            }
+        }
+
+        Site::query()
+            ->whereIn('id', $ids1)
+            ->whereIn('status', [2, 3])
+            ->update(['keyword_memo' => '']);
+    }
+
+}

+ 93 - 0
app/Console/Commands/Link.php

@@ -0,0 +1,93 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\LinkStatistical;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Builder;
+
+class Link extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'link:link';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $sites = Site::query()
+            ->whereIn('status', [2, 3])
+            ->with(['users' => function (\Illuminate\Database\Eloquent\Relations\BelongsToMany $q) {
+                $q->select(['id', 'username', 'nickname', 'role_id']);
+            }, 'linkDetails' => function (\Illuminate\Database\Eloquent\Relations\HasMany $q) {
+                $q->select(['id', 'redundant_site_id', 'link_id'])
+                    ->where(['enable' => 1, 'link_tasks_detail.status' => 5]);
+            }])->withCount([
+                'linkUrls as publishThisMonth' => function (Builder $b) {
+                    $b->where('link_tasks_detail.enable', 1)
+                        ->where('link_tasks_detail.status', 5)
+                        ->where('link_tasks_url.status', 5)
+                        ->whereBetween('link_tasks_url.created_at', [date('Y-m-01 00:00:00'), date('Y-m-t 23:59:59')]);
+                },
+                'linkUrls as publishLastMonth' => function (Builder $b) {
+                    $b->where('link_tasks_detail.enable', 1)
+                        ->where('link_tasks_detail.status', 5)
+                        ->where('link_tasks_url.status', 5)
+                        ->whereBetween('link_tasks_url.created_at', [date('Y-m-01 00:00:00', strtotime('last month')), date('Y-m-d 23:59:59', strtotime(date('Y-m-1') . '-1 day'))]);
+                },
+                'linkUrls as linkUrls' => function (Builder $query) {
+                    $query->where('link_tasks_detail.enable', 1)
+                        ->where('link_tasks_detail.status', 5)
+                        ->where('link_tasks_url.status', 5);
+                }])->orderByDesc('id')->get();
+
+        $data = [];
+        foreach ($sites as $value) {
+            $data[] = [
+                'site_id' => $value->id,
+                'cn_title' => $value->cn_title ?? '',
+                'domain' => $value->domain ?? '',
+                'site_status' => $value->status,
+                'link_goal' => $value->link_goal ?? 0,
+                'link_pact_count' => $value->linkDetails->groupBy('link_id')->count(),
+                'link_urls' => $value->linkUrls ?? 0,
+                'publish_last_month' => $value->publishLastMonth ?? 0,
+                'publish_this_month' => $value->publishThisMonth ?? 0,
+                'server_people_id' => implode(',', $value->users->where('role_id', Role::TYPE_SERVER)->pluck('id')->toArray()),
+                'chongqing_link_people_id' => implode(',', $value->users->where('role_id', Role::TYPE_LINK_PART_CHONGQING)->pluck('id')->toArray()),
+                'optimizer_people_id' => implode(',', $value->users->where('role_id', Role::TYPE_OPTIMIZER)->pluck('id')->toArray()),
+                'optimizer_editing_people_id' => implode(',', $value->users->where('role_id', Role::TYPE_OPTIMIZATION_EDITING)->pluck('id')->toArray()),
+                'created_at' => date('Y-m-d H:i:s')
+            ];
+        }
+        LinkStatistical::query()->delete();
+        LinkStatistical::query()->insert($data);
+    }
+
+}

+ 88 - 0
app/Console/Commands/MasterMeter.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class MasterMeter extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'master:meter';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $siteList = Site::query()->where('is_bq', 1)->get();
+        $rankConnection = DB::connection('rank');
+
+        foreach ($siteList as $key => $value) {
+
+            if (!empty($value->old_id)) {
+
+                $listReportList = $rankConnection->table('project_listreport')->where('project_id', $value->old_id)->get();
+                foreach ($listReportList as $kk => $vv) {
+
+                    $ym = $vv->ym . '01';
+                    $date = date('Y-m-d H:i:s', strtotime('last day of this month', strtotime($ym)));
+
+                    if ($vv->traffic >= 2000 && !$value->reach_2000_at) {
+                        $value->update(['reach_2000_at' => $date]);
+                    }
+                    if ($vv->traffic >= 1500 && !$value->reach_1500_at) {
+                        $value->update(['reach_1500_at' => $date]);
+                    }
+                    if ($vv->traffic >= 1000 && !$value->reach_1000_at) {
+                        $value->update(['reach_1000_at' => $date]);
+                    }
+                    if ($vv->traffic >= 500 && !$value->reach_500_at) {
+                        $value->update(['reach_500_at' => $date]);
+                    }
+                    if ($vv->traffic >= 300 && !$value->reach_300_at) {
+                        $value->update(['reach_300_at' => $date]);
+                    }
+                    \App\Http\Models\MasterMeter::query()->create([
+                        'site_id' => $value->id,
+                        'old_id' => $value->old_id,
+                        'cn_title' => $value->cn_title,
+                        'domain' => $value->domain,
+                        'traffic' => $vv->traffic,
+                        'inquire' => $vv->inquire,
+                        'ym' => date('Ym')
+                    ]);
+                }
+            }
+        }
+
+        $this->info('success');
+        return;
+    }
+
+}

+ 192 - 0
app/Console/Commands/Message.php

@@ -0,0 +1,192 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use App\Http\Services\YouMenGApiService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class Message extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'message:day';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    const MESSAGE_STENCIL = [
+        'siteExpires' => 1,//网站到期
+        'sslExpires' => 2,//证书到期
+        'cdnExpires' => 3,//cdn到期
+        'domainNameExpires' => 4,//域名到期
+        'servicePeriodExpires' => 5,//服务期到期
+    ];
+    const SEVEN_DAYS = 604800;//7天的时间戳
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     * 任务计划
+     * @return mixed
+     */
+    public function handle()
+    {
+        $siteList = Site::query()->where('status', [2, 3, 9])->get();
+        foreach ($siteList as $key => $value) {
+            //网站到期 ,提前一个月
+            if ($value->expired_at && $value->expired_at < date('Y-m-d', strtotime("-1 months"))) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['siteExpires'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $value->id,
+                    'title' => $messageStencil->title,
+                    'message' => $this->getMessageInfo($value->domain ?? '', $value->expired_at, $messageStencil->message),
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url,
+                    'img' => $messageStencil->img,
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $this->insertMessageInfo($value->id, $messageStencil->type, $info);
+            }
+            //证书到期,提前一个月
+            if ($value->cert_expired_date && $value->cert_expired_date < date('Y-m-d', strtotime("-1 months"))) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['sslExpires'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $value->id,
+                    'title' => $messageStencil->title,
+                    'message' => $this->getMessageInfo($value->domain ?? '', $value->cert_expired_date, $messageStencil->message),
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url,
+                    'img' => $messageStencil->img,
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $this->insertMessageInfo($value->id, $messageStencil->type, $info);
+            }
+            //cdn到期,提前一个月
+            if ($value->cdn_expired_date && $value->cdn_expired_date < date('Y-m-d', strtotime("-1 months"))) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['cdnExpires'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $value->id,
+                    'title' => $messageStencil->title,
+                    'message' => $this->getMessageInfo($value->domain ?? '', $value->cdn_expired_date, $messageStencil->message),
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url,
+                    'img' => $messageStencil->img,
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $this->insertMessageInfo($value->id, $messageStencil->type, $info);
+            }
+            //域名到期,提前一个月
+            if ($value->domain_expired_date && $value->domain_expired_date < date('Y-m-d', strtotime("-1 months"))) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['domainNameExpires'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $value->id,
+                    'title' => $messageStencil->title,
+                    'message' => $this->getMessageInfo($value->domain ?? '', $value->domain_expired_date, $messageStencil->message),
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url,
+                    'img' => $messageStencil->img,
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $this->insertMessageInfo($value->id, $messageStencil->type, $info);
+            }
+            //服务期到期,提前两个月
+            if ($value->renewal_end_at && $value->renewal_end_at < date('Y-m-d', strtotime("-2 months"))) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['servicePeriodExpires'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $value->id,
+                    'title' => $messageStencil->title,
+                    'message' => $this->getMessageInfo($value->domain ?? '', $value->renewal_end_at, $messageStencil->message),
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url,
+                    'img' => $messageStencil->img,
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $this->insertMessageInfo($value->id, $messageStencil->type, $info);
+            }
+        }
+        $messageList = DB::table('message_status')
+            ->whereIn('type', [1, 2, 3, 4, 5])
+            ->where('status', 0)->get()->toArray();
+
+        $youMenGApiService = new YouMenGApiService();
+
+        foreach ($messageList as $key => $value) {
+            $message = [
+                'title' => $value->title,
+                'subtitle' => $value->message,
+                'body' => ''
+            ];
+            $youMenGApiService->pushIosDevice($value->site_id, $message);
+            $youMenGApiService->pushAndroidDevice($value->site_id, $message);
+
+            DB::table('message_status')->where('id', $value->id)->update(['push_time' => date('Y-m-d H:i:s'), 'status' => 1]);
+        }
+    }
+
+    /**
+     * 替换模版中的变量
+     * @param $website
+     * @param $date
+     * @param $message
+     * @return mixed
+     */
+    public function getMessageInfo($website = '', $date = '', $message = '')
+    {
+        $message = str_replace('{$website}', $website, $message);
+        $message = str_replace('{$date}', $date, $message);
+        return $message;
+    }
+
+    /**
+     * 插入任务
+     * @param $siteId
+     * @param $type
+     * @param $info
+     */
+    public function insertMessageInfo($siteId, $type, $info)
+    {
+        $condition = [
+            ['site_id', '=', $siteId],
+            ['type', '=', $type],
+        ];
+        $messageStatus = DB::table('message_status');
+        $result = $messageStatus->where($condition)->orderBy('id', 'desc')->first();
+
+        $day = (int)strtotime(date('Y-m 00:00:00')) - self::SEVEN_DAYS;
+        if (empty($result)) {
+            $messageStatus->insert($info);
+        } else {
+            Log::info(json_encode($result));
+            Log::info($result->date);
+
+            if ($result->date < $day) {
+                $messageStatus->insert($info);
+            }
+        }
+    }
+}

+ 135 - 0
app/Console/Commands/MessageInquiry.php

@@ -0,0 +1,135 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Services\YouMenGApiService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class MessageInquiry extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'message:inquiry';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    const MESSAGE_STENCIL = [
+        'softText' => 6,//软文
+        'report' => 7,//报表
+        'inquiry' => 8,//8询盘
+    ];
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     * 任务计划
+     * @return mixed
+     */
+    public function handle()
+    {
+        $messageStatus = DB::table('message_status');
+        $softTextNoticeList = DB::table('articles_notice')->get()->toArray();
+        //软文推送任务表
+        foreach ($softTextNoticeList as $key => $value) {
+
+            $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['softText'])->first();
+            $info = [
+                'type' => $messageStencil->type,
+                'site_id' => $value->site_id,
+                'title' => $messageStencil->title ?? '',
+                'message' => $messageStencil->message ?? '',
+                'message_id' => $messageStencil->id,
+                'url' => $messageStencil->url ?? '',
+                'img' => $messageStencil->img ?? '',
+                'date' => strtotime(date('Y-m 00:00:00')),
+            ];
+            $messageStatus->insert($info);
+            $deleteCondition = [
+                ['site_id', '=', $value->site_id],
+            ];
+            DB::table('articles_notice')->where($deleteCondition)->delete();
+        }
+
+        $reportNoticeList = DB::table('report_notice')->get()->toArray();
+        foreach ($reportNoticeList as $key => $value) {
+            //报表推送任务表
+            $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['report'])->first();
+            $info = [
+                'type' => $messageStencil->type,
+                'site_id' => $value->site_id,
+                'title' => $messageStencil->title ?? '',
+                'message' => $messageStencil->message ?? '',
+                'message_id' => $messageStencil->id,
+                'url' => $messageStencil->url ?? '',
+                'img' => $messageStencil->img ?? '',
+                'date' => strtotime(date('Y-m 00:00:00')),
+            ];
+            $messageStatus->insert($info);
+            $deleteCondition = [
+                ['site_id', '=', $value->site_id],
+            ];
+            DB::table('report_notice')->where($deleteCondition)->delete();
+        }
+
+        $inquiryNoticeList = DB::table('inquiry_notice')->get()->toArray();
+        foreach ($inquiryNoticeList as $key => $value) {
+            //询盘推送任务表
+            $siteId = DB::table('sites')->where('database', $value->website)->value('id');
+            if (!empty($siteId)) {
+                $messageStencil = DB::table('message')->where('id', self::MESSAGE_STENCIL['inquiry'])->first();
+                $info = [
+                    'type' => $messageStencil->type,
+                    'site_id' => $siteId,
+                    'title' => $messageStencil->title ?? '',
+                    'message' => $messageStencil->message ?? '',
+                    'message_id' => $messageStencil->id,
+                    'url' => $messageStencil->url ?? '',
+                    'img' => $messageStencil->img ?? '',
+                    'date' => strtotime(date('Y-m 00:00:00')),
+                ];
+                $messageStatus->insert($info);
+                $deleteCondition = [
+                    ['website', '=', $value->website],
+                ];
+                DB::table('inquiry_notice')->where($deleteCondition)->delete();
+            }
+        }
+
+        $messageList = DB::table('message_status')
+            ->where('status', 0)
+            ->whereIn('type', [6, 7, 8])
+            ->get()->toArray();
+
+        $youMenGApiService = new YouMenGApiService();
+
+        foreach ($messageList as $key => $value) {
+            $message = [
+                'title' => $value->title,
+                'subtitle' => $value->message,
+                'body' => ''
+            ];
+            $youMenGApiService->pushIosDevice($value->site_id, $message);
+            $youMenGApiService->pushAndroidDevice($value->site_id, $message);
+
+            DB::table('message_status')->where('id', $value->id)->update(['push_time' => date('Y-m-d H:i:s'), 'status' => 1]);
+        }
+    }
+}

+ 101 - 0
app/Console/Commands/Num.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class Num extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'num:count';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     * 每日数据统计
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        $rankCon = DB::connection('rank');
+        $allSites = Site::query()->with('server')->whereIn('status', [2, 3, 5, 8, 9])->get();
+
+        DB::table('num_log')->insert([
+            'title' => 'count',
+            'content' => $allSites->count(),
+            'created_at' => date('Y-m-d H:i:s')
+        ]);
+
+        $errorCount = 0;
+        foreach ($allSites as $site) {
+            try {
+                if (empty($site->server)) {
+                    continue;
+                }
+                $top10 = $rankCon->table('project_history')->selectRaw('top10')
+                    ->where(['project_id' => $site->old_id])->orderByDesc('create_time')->first();
+
+                //流程程序开发流程 部署
+                $config = [
+                    'connection_name' => sprintf('connection_name_%s', $site->id),
+                    'host' => $site->server->server_ip,
+                    'port' => '3306',
+                    'database' => $site->database,
+                    'username' => $site->server->mysql_user_name,
+                    'password' => $site->server->mysql_passwd,
+                ];
+
+                config_connection($config);
+
+                $inquire = DB::connection($config['connection_name'])->table('user_msg')->count();
+
+                $traffic = DB::connection($config['connection_name'])->table('traffic_report_hourly')->selectRaw('SUM(pv) as pvTotal')->first();
+                DB::table('num')->insert([
+                    'site_id' => $site->id,
+                    'cn_title' => $site->cn_title,
+                    'domain' => $site->domain,
+                    'ymd' => date('Ymd'),
+                    'traffic' => $traffic->pvTotal ?? 0,
+                    'inquire' => $inquire,
+                    'top10' => $top10->top10 ?? 0,
+                    'created_at' => date('Y-m-d H:i:s')
+                ]);
+            } catch (\Throwable $throwable) {
+                $errorCount++;
+                Log::error(substr(var_export($throwable->getMessage(), 1), 0, 200));
+                echo $site->cn_title . PHP_EOL;
+            }
+        }
+        DB::table('num_log')->insert([
+            'title' => 'error',
+            'content' => $errorCount,
+            'created_at' => date('Y-m-d H:i:s')
+        ]);
+
+        $this->info('success');
+    }
+}

+ 134 - 0
app/Console/Commands/PlanWeek.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\ScheduledTasks;
+use App\Http\Models\WeekTaskHistory;
+use App\Http\Models\WeekTaskInfo;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class PlanWeek extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'plan:week';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     * 任务计划
+     * @return mixed
+     */
+    public function handle()
+    {
+        $condMap = DB::table('week_task_cond')->pluck('name', 'id')->toArray();
+        $itemsMap = DB::table('week_task_items')->pluck('name', 'id')->toArray();
+
+        $weekInfoList = WeekTaskInfo::query()
+            ->whereNotIn('user_type', ['wfp-web', 'wfp-design', 'wfp-exam', 'wfp-site-exam', 'wfp-site', 'wfp-seo'])
+            ->where(['type' => 'now'])->get()->toArray();
+
+        $result = [];
+        foreach ($weekInfoList as $item) {
+
+            $result[] = [
+                'day' => $item['day'],
+                'user_type' => $item['user_type'],
+                'describe' => $item['describe'],
+                'duty_id' => $item['duty_id'],
+                'design_id' => $item['design_id'],
+                'web_id' => $item['web_id'],
+                'cond_name' => $condMap[$item['cond_id']] ?? '',
+                'cond_id' => $item['cond_id'],
+                'cond_item_id' => $item['cond_item_id'],
+                'cond_item_name' => $itemsMap[$item['cond_item_id']] ?? '',
+                'remark' => $item['remark'],
+                'status' => $item['status'],
+                'insert_date' => date('Y-m-d'),
+                'slices_number' => $item['slices_number'] ?? 0,
+                'check_score' => $item['check_score'] ?? 0,
+                'created_at' => $item['created_at'] ?? null,
+                'updated_at' => $item['updated_at'] ?? null,
+                'deadline' => $item['deadline'] ?? null,
+                'complete' => $item['complete'] ?? null,
+            ];
+        }
+        WeekTaskHistory::query()->insert($result);
+
+
+        //获取近期的日期 排除周末
+        $dayList = $this->getDay();
+
+        //创建延迟任务
+        $taskList = ScheduledTasks::query()->get();
+        if (!empty($taskList->toArray())) {
+            foreach ($taskList as $key => $value) {
+                $diff = strtotime(date('Y-m-d')) - strtotime(substr($value->created_at, 0, 10));
+                $day = 86400 * 3;
+                if ($diff >= $day) {
+                    $update = [
+                        'type' => $value->type,
+                        'day' => $value->day,
+                        'user_type' => $value->user_type,
+                        'describe' => $value->describe,
+                        'duty_id' => $value->duty_id,
+                        'design_id' => $value->design_id,
+                        'web_id' => $value->web_id,
+                        'feedback' => $value->feedback,
+                        'remark' => $value->remark,
+                        'status' => $value->status,
+                        'cond_id' => $value->cond_id,
+                        'created_at' => date('Y-m-d H:i:s'),
+                        'deadline' => $dayList[2],
+                    ];
+                    WeekTaskInfo::query()->insert($update);
+                    ScheduledTasks::query()->where('id', $value->id)->delete();
+                }
+            }
+        }
+
+    }
+
+    public function getDay()
+    {
+        $days = 12;
+        $dayList = [];
+        for ($i = 1; $i <= $days; $i++) {
+            $day = date('Y-m-d', strtotime(date('Y-m-d')) + 86400 * $i);
+            $result = $this->isWeekend($day);
+            if (!$result) {
+                $dayList[] = $day;
+            }
+        }
+        return $dayList;
+    }
+
+    public function isWeekend($date)
+    {
+        if ((date('w', strtotime($date)) == 6) || (date('w', strtotime($date)) == 0)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 65 - 0
app/Console/Commands/SiteStatusCheck.php

@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class SiteStatusCheck extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'siteStatus:check';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = '检测站点状态并切换';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle(): void
+    {
+        return;
+        $sites = Site::query()->get();
+
+        foreach ($sites as $site) {
+            if ($site->expired_at) {
+
+                if (time() > strtotime($site->expired_at) && !in_array($site->status, [4, 6, 7])) {
+                    $site->update(['status' => 6]); //已过期
+                    continue;
+                }
+
+                if (strtotime('+3 month') > strtotime($site->expired_at) &&
+                    time() < strtotime($site->expired_at) && !in_array($site->status, [4, 5, 7])) {
+                    $site->update(['status' => 5]); //续费期
+                    continue;
+                }
+            }
+
+        }
+        $this->info('success');
+    }
+
+}

+ 67 - 0
app/Console/Commands/SocialSync.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Logics\Admin\SocialLogic;
+use App\Http\Models\Article;
+use App\Http\Models\Site;
+use App\Http\Models\SocialPublish;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+
+class SocialSync extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'social:sync {type=0}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $type = $this->argument('type');
+        $social = new SocialLogic();
+
+        $sitesMap = Site::query()->select(['id', 'facebook_page', 'facebook_page_token'])->get()->keyBy('id')->toArray();
+
+        if ($type == 2) {
+            $records = SocialPublish::query()->where([
+                ['publish_at', '<', date('Y-m-d H:i:s')],
+                ['social_sync_at', '=', null]
+            ])->get();
+            foreach ($records as $record) {
+                list($err, $resultStatus) = $social->publish($record, $sitesMap);
+//                $article->social_result = $result;
+                $record->err = $err;
+                $record->result_status = $resultStatus;
+                $record->social_sync_at = date('Y-m-d H:i:s');
+                $record->save();
+            }
+        }
+        $this->info('success');
+        return;
+    }
+}

+ 53 - 0
app/Console/Commands/StatusSync.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+class StatusSync extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'status:sync';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $rankDatabase = DB::connection('rank');
+
+        $siteResult = Site::query()->selectRaw('old_id,status')->whereNotNull('old_id')->get();
+
+        foreach ($siteResult as $item) {
+//            dd( Site::STATUS_MAP[$item->status],$item->old_id);
+            $statusText = Site::STATUS_MAP[$item->status] ?? '';
+            $rankDatabase->table('project')->where(['id' => $item->old_id])->update(['status_text' => $statusText]);
+        }
+        dd('success');
+    }
+}

+ 89 - 0
app/Console/Commands/TemplateLibrarySyncPictures.php

@@ -0,0 +1,89 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use App\Http\Services\TemplateLibraryApiService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class TemplateLibrarySyncPictures extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'sync_pictures:template_library';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+
+    private $templateLibraryApiService;
+
+    public function __construct(TemplateLibraryApiService $templateLibraryApiService)
+    {
+        parent::__construct();
+        $this->templateLibraryApiService = $templateLibraryApiService;
+
+    }
+
+    const HOST = '121.199.40.85';
+    const USER = 'root';
+    const PASSWORD = 'JGJHD84@8&a';
+
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        /*try {
+            $ssh2 = ssh2_connect(self::HOST, 22);
+            ssh2_auth_password($ssh2, self::USER, self::PASSWORD);
+
+            $siteId = 974;
+            $domain = Site::query()->where('id', $siteId)->value('domain');
+            //远程目录
+            $targetDirectory = '/www/wwwroot/' . $domain . '/sepSsr/uploads';
+            //递归创建目录
+            $sftp = ssh2_sftp($ssh2);
+            ssh2_sftp_mkdir($sftp, $targetDirectory, 0755, true);
+
+            $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+            $imagesList = $connection->table('image')->get();
+            foreach ($imagesList as $item) {
+
+                $imagePathArray = explode('storage', $item->src);
+                $fileName = explode('/', $imagePathArray[1]);
+                $file = array_pop($fileName);
+
+                //本地目录
+                $sourceDirectory = base_path() . '/storage/app/public' . $imagePathArray[1];
+                $stream = ssh2_scp_send($ssh2, $sourceDirectory, $targetDirectory . '/' . $file, 0644);
+                if ($stream) {
+                    $connection->table('image')->where('id', $item->id)->update(['is_synchronize' => 1]);
+                }
+            }
+
+        } catch (\Exception $exception) {
+            Log::info(date('Y-m-d H:i:s') . 'sync_pictures error!' . $exception->getMessage());
+        }
+        Log::info(date('Y-m-d H:i:s') . 'sync_pictures success!');*/
+
+    }
+
+}

+ 47 - 0
app/Console/Commands/Test.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Log;
+
+class Test extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'do:test {type=0}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Command description';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $type = $this->argument('type');
+        $str = sprintf('type:%s', $type);
+        Log::info($str);
+        dump($str);
+
+    }
+}

+ 88 - 0
app/Console/Commands/UrlCheck.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Http\Models\Site;
+use GuzzleHttp\Client;
+use GuzzleHttp\Pool;
+use GuzzleHttp\Psr7\Response;
+use Illuminate\Console\Command;
+use App\Http\Models\UrlCheck as UrlCheckModel;
+use Illuminate\Support\Collection;
+
+class UrlCheck extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'url:check';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'check url';
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+
+        UrlCheckModel::query()->truncate(); //截断表
+        $taskUrls = Site::query()->whereIn('status', [2,3,5,8,9])->selectRaw('id as site_id,cn_title,webmaster_domain as domain')->limit(1)->get();
+
+        $client = new Client([
+            'verify' => false,
+            'timeout' => 10
+        ]); //并发请求链接地址
+        $requests = function () use ($client, $taskUrls) {
+            foreach ($taskUrls as $item) {
+                if (empty($item->domain))
+                    continue;
+                yield new \GuzzleHttp\Psr7\Request('GET',$item->domain);
+            }
+        };
+
+        $failed = [];
+        $pool = new Pool($client, $requests(), [
+            'concurrency' => 5, //同时并发抓取几个
+            'rejected' => function (\Throwable $throwable, $index) use (&$failed) {
+                $failed[] = $index;
+            },
+        ]);
+        $promise = $pool->promise();
+        $promise->wait();
+
+        $failedSites = collect([]);
+
+        foreach ($taskUrls as $key => $item) {
+            if (in_array($key, $failed)) {
+                $item->setCreatedAt(date('Y-m-d H:i:s'));
+                $item->setUpdatedAt(date('Y-m-d H:i:s'));
+                $failedSites->push($item);
+            }
+        }
+        //分批批量插入
+        $failedSites->chunk(50)->each(function (Collection $item, $key) {
+            UrlCheckModel::query()->insert($item->toArray());
+        });
+        $this->info('success');
+        return;
+    }
+}

+ 59 - 0
app/Console/Kernel.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace App\Console;
+
+use App\Console\Commands\Message;
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+use Illuminate\Support\Facades\Log;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+        //
+        Message::class,
+    ];
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param \Illuminate\Console\Scheduling\Schedule $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        if (config('app.env') == 'wall') {
+            $schedule->command('social:sync 2')->everyFiveMinutes();
+        } else {
+            $schedule->command('beg:master email')->everyTenMinutes();
+            $schedule->command('beg:master report')->twiceDaily(1, 13);
+            $schedule->command('master:meter')->dailyAt('02:00');
+            $schedule->command('plan:week')->dailyAt('22:00');
+            $schedule->command('url:check')->dailyAt('23:00');
+            $schedule->command('num:count')->dailyAt('21:00');
+            $schedule->command('article:monitor')->dailyAt('8:00');
+            $schedule->command('message:day')->dailyAt('09:00');
+            $schedule->command('message:inquiry')->everyMinute();
+            $schedule->command('link:link')->dailyAt('08:00');
+            $schedule->command('sync_pictures:template_library')->dailyAt('15:50');
+            $schedule->command('sync:keyword_expansion')->monthlyOn(1, '01:00');
+        }
+    }
+
+    /**
+     * Register the commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        $this->load(__DIR__ . '/Commands');
+
+        require base_path('routes/console.php');
+    }
+}

+ 61 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Illuminate\Support\Facades\Log;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that are not reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [
+        //
+    ];
+
+    /**
+     * A list of the inputs that are never flashed for validation exceptions.
+     *
+     * @var array
+     */
+    protected $dontFlash = [
+        'password',
+        'password_confirmation',
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * @param  \Exception $exception
+     * @return void
+     */
+    public function report(Exception $exception)
+    {
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  \Exception $exception
+     * @return \Illuminate\Http\Response
+     */
+    public function render($request, Exception $exception)
+    {
+
+        if ($exception instanceof NotFoundHttpException) {
+            $code = $exception->getStatusCode();
+            if (view()->exists('errors.' . $code)) {
+                return response()->view('errors.' . $exception->getStatusCode(), ['message' => $exception->getMessage()], 404);
+            }
+        }
+
+        return parent::render($request, $exception);
+    }
+}

+ 33 - 0
app/Exports/BasicExport.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;
+
+
+class BasicExport implements FromArray, ShouldAutoSize
+{
+    use Exportable;
+
+    public $dataList;
+
+    public function __construct(array $dataList)
+    {
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+}

+ 90 - 0
app/Exports/BqProgressRateExport.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;//自动宽度
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class BqProgressRateExport extends CommonExport implements FromArray, WithEvents//,ShouldAutoSize
+{
+    use Exportable;
+
+    public $dataList;
+    public $list;
+
+    public function __construct(array $dataList)
+    {
+        $this->list = [];
+        foreach ($dataList as $key => $value) {
+
+            if ($key >= 2) {
+
+                if (isset($value['bq_meeting']) && $value['bq_meeting'] == 1) {
+                    $this->list[] = 'F' . ($key + 1);
+                }
+                if (isset($value['home_page_adjustment']) && $value['home_page_adjustment'] == 1) {
+                    $this->list[] = 'G' . ($key + 1);
+                }
+                if (isset($value['product_series_page_adjustment']) && $value['product_series_page_adjustment'] == 1) {
+                    $this->list[] = 'H' . ($key + 1);
+                }
+                if (isset($value['other_page_adjustments']) && $value['other_page_adjustments'] == 1) {
+                    $this->list[] = 'I' . ($key + 1);
+                }
+                if (isset($value['code_adjustment']) && $value['code_adjustment'] == 1) {
+                    $this->list[] = 'J' . ($key + 1);
+                }
+                if (isset($value['web_page_adjustment']) && $value['web_page_adjustment'] == 1) {
+                    $this->list[] = 'K' . ($key + 1);
+                }
+                if (isset($value['website_seo_quality_inspection']) && $value['website_seo_quality_inspection'] == 1) {
+                    $this->list[] = 'L' . ($key + 1);
+                }
+                if (isset($value['small_language_station']) && $value['small_language_station'] == 1) {
+                    $this->list[] = 'M' . ($key + 1);
+                }
+
+                $dataList[$key]['bq_meeting'] = '百千会议';
+                $dataList[$key]['home_page_adjustment'] = '首页调整';
+                $dataList[$key]['product_series_page_adjustment'] = '产品系列页面调整';
+                $dataList[$key]['other_page_adjustments'] = '其他页面调整';
+                $dataList[$key]['code_adjustment'] = '代码调整';
+                $dataList[$key]['web_page_adjustment'] = '网页调整';
+                $dataList[$key]['website_seo_quality_inspection'] = '网站SEO质检';
+                $dataList[$key]['small_language_station'] = '小语言站';
+            }
+        }
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+                $cells = ['A1:M1'];
+                $this->setCellWidth($event, $cells);
+                $cells[] = 'A2:M2';
+                $this->setCellColor($cells, $event);//表头
+                $this->setCellColorBorders($cells, $event);
+                $this->setCellColor($this->list, $event, '87CEFA');
+            }
+        ];
+    }
+
+
+}

+ 90 - 0
app/Exports/CommonExport.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace App\Exports;
+
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Fill;
+
+class CommonExport
+{
+    protected function setCellWidth($event, $cells)
+    {
+        //设置列宽
+        $event->sheet->getDelegate()->getColumnDimension('A')->setWidth(5);
+        $event->sheet->getDelegate()->getColumnDimension('B')->setWidth(35);
+        $event->sheet->getDelegate()->getColumnDimension('C')->setWidth(30);
+        $event->sheet->getDelegate()->getColumnDimension('D')->setWidth(25);
+        $event->sheet->getDelegate()->getColumnDimension('E')->setWidth(25);
+        $event->sheet->getDelegate()->getColumnDimension('F')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('G')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('H')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('I')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('J')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('K')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('L')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('M')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('N')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('O')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('P')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('Q')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('R')->setWidth(20);
+        $event->sheet->getDelegate()->getColumnDimension('S')->setWidth(20);
+
+        for ($i = 0; $i <= 1000; $i++) {
+            $event->sheet->getDelegate()->getRowDimension($i)->setRowHeight(30);
+        }
+        foreach ($cells as $k => $v) {
+            //设置区域单元格垂直居中
+            $event->sheet->getDelegate()->getStyle($v)->getAlignment()->setVertical('center');
+            //设置区域单元格水平居中
+            $event->sheet->getDelegate()->getStyle($v)->getAlignment()->setHorizontal('center');
+            $event->sheet->getDelegate()->mergeCells($v);
+        }
+    }
+
+    protected function setCellColor($cells, $event, $rgb = 'acc8cc')
+    {
+        foreach ($cells as $cell) {
+            //设置区域单元格字体、颜色、背景等,其他设置请查看 applyFromArray 方法,提供了注释
+            $event->sheet->getDelegate()->getStyle($cell)->applyFromArray([
+                'font' => [
+                    'name' => 'Arial',
+                    'bold' => true,
+                    'italic' => false,
+                    'strikethrough' => false,
+                    'color' => [
+                        'rgb' => '000000'
+                    ]
+                ],
+                'fill' => [
+                    'fillType' => Fill::FILL_SOLID, //线性填充,类似渐变
+                    'rotation' => 0, //渐变角度
+                    'startColor' => [
+                        'rgb' => $rgb //初始颜色
+                    ],
+                    //结束颜色,如果需要单一背景色,请和初始颜色保持一致
+                    'endColor' => [
+                        'argb' => $rgb
+                    ]
+                ]
+            ]);
+        }
+    }
+
+    protected function setCellColorBorders($cells, $event)
+    {
+        foreach ($cells as $cell) {
+            $event->sheet->getDelegate()->getStyle($cell)->getBorders()->applyFromArray(
+                [
+                    'allBorders' => [
+                        'borderStyle' => Border::BORDER_THIN,
+                        'color' => [
+                            'rgb' => '5F9EA0'
+                        ]
+                    ]
+                ]
+            );
+        }
+    }
+
+}

+ 115 - 0
app/Exports/EditingScheduleExport.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;//自动宽度
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class EditingScheduleExport extends CommonExport implements FromArray, WithEvents//,ShouldAutoSize
+{
+    use Exportable;
+
+    public $dataList;
+    public $list;
+
+    public function __construct(array $dataList)
+    {
+        $this->list = [];
+        foreach ($dataList as $key => $value) {
+
+            if ($key >= 3) {
+
+                if (isset($value['is_backstage']) && $value['is_backstage'] == 1) {
+                    $this->list[] = 'F' . ($key + 1);
+                }
+                if (isset($value['is_first_mail']) && $value['is_first_mail'] == 1) {
+                    $this->list[] = 'G' . ($key + 1);
+                }
+                if (isset($value['is_data']) && $value['is_data'] == 1) {
+                    $this->list[] = 'H' . ($key + 1);
+                }
+                if (isset($value['is_folder']) && $value['is_folder'] == 1) {
+                    $this->list[] = 'I' . ($key + 1);
+                }
+                if (isset($value['is_order_index']) && $value['is_order_index'] == 1) {
+                    $this->list[] = 'J' . ($key + 1);
+                }
+                if (isset($value['is_product_index']) && $value['is_product_index'] == 1) {
+                    $this->list[] = 'K' . ($key + 1);
+                }
+                if (isset($value['is_test_station']) && $value['is_test_station'] == 1) {
+                    $this->list[] = 'L' . ($key + 1);
+                }
+                if (isset($value['is_opinion']) && $value['is_opinion'] == 1) {
+                    $this->list[] = 'M' . ($key + 1);
+                }
+                if (isset($value['is_project_group']) && $value['is_project_group'] == 1) {
+                    $this->list[] = 'N' . ($key + 1);
+                }
+                if (isset($value['is_client_feedback']) && $value['is_client_feedback'] == 1) {
+                    $this->list[] = 'O' . ($key + 1);
+                }
+                if (isset($value['is_online_inspection_form']) && $value['is_online_inspection_form'] == 1) {
+                    $this->list[] = 'P' . ($key + 1);
+                }
+                if (isset($value['is_in_group_inspection']) && $value['is_in_group_inspection'] == 1) {
+                    $this->list[] = 'Q' . ($key + 1);
+                }
+                if (isset($value['is_quality_inspection_departments']) && $value['is_quality_inspection_departments'] == 1) {
+                    $this->list[] = 'R' . ($key + 1);
+                }
+                if (isset($value['is_optimization']) && $value['is_optimization'] == 1) {
+                    $this->list[] = 'S' . ($key + 1);
+                }
+
+                $dataList[$key]['is_backstage'] = '后台搭建';
+                $dataList[$key]['is_first_mail'] = '第一封邮件';
+                $dataList[$key]['is_data'] = '资料进度表';
+                $dataList[$key]['is_folder'] = '共享盘文件夹整理';
+                $dataList[$key]['is_order_index'] = '单页面提供完整';
+                $dataList[$key]['is_product_index'] = '产品页面提供完整';
+                $dataList[$key]['is_test_station'] = '测试站检查表';
+                $dataList[$key]['is_opinion'] = '组内意见';
+                $dataList[$key]['is_project_group'] = '项目部群';
+                $dataList[$key]['is_client_feedback'] = '客户反馈';
+                $dataList[$key]['is_online_inspection_form'] = '上线检查表格';
+                $dataList[$key]['is_in_group_inspection'] = '组内检查';
+                $dataList[$key]['is_quality_inspection_departments'] = '质检部';
+                $dataList[$key]['is_optimization'] = 'Hina&优化师检查';
+
+            }
+        }
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+                $cells = ['A1:S1', 'A2:E2', 'F2:G2', 'H2:K2', 'L2:O2', 'P2:S2'];
+                $this->setCellWidth($event, $cells);
+                $cells[] = 'A3:S3';
+                $this->setCellColor($cells, $event);//表头
+                $this->setCellColorBorders($cells, $event);
+                $this->setCellColor($this->list, $event, '87CEFA');
+            }
+        ];
+    }
+
+
+}

+ 30 - 0
app/Exports/InquireExport.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+
+class InquireExport implements FromArray
+{
+    use Exportable;
+
+    public $dataList;
+
+    public function __construct(array  $dataList)
+    {
+        $this->dataList =$dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+}

+ 42 - 0
app/Exports/LinkCountExport.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class LinkCountExport implements FromArray,ShouldAutoSize,WithEvents
+{
+    use Exportable;
+
+    public $dataList;
+
+    public function __construct(array $dataList)
+    {
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+//
+    public function registerEvents(): array
+    {
+        return [
+//            AfterSheet::class => function (AfterSheet $event) {
+//                $event->sheet->getDelegate()->mergeCells('A1:B1');
+//            }
+        ];
+    }
+}

+ 47 - 0
app/Exports/LinkHallExport.php

@@ -0,0 +1,47 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class LinkHallExport implements FromArray, ShouldAutoSize, WithEvents
+{
+    use Exportable;
+
+    public $dataList;
+
+    public function __construct(array $dataList)
+    {
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+
+                foreach ($this->dataList as $inx => $item) {
+                    if ($inx == 0) continue;
+                    $event->sheet->getDelegate()->getStyle('F' . $inx)->getAlignment()->setWrapText(true);
+                }
+            }
+        ];
+    }
+
+}

+ 91 - 0
app/Exports/PlannerScheduleExport.php

@@ -0,0 +1,91 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;//自动宽度
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class PlannerScheduleExport extends CommonExport implements FromArray, WithEvents//,ShouldAutoSize
+{
+    use Exportable;
+
+    public $dataList;
+    public $list;
+
+    public function __construct(array $dataList)
+    {
+        $this->list = [];
+        foreach ($dataList as $key => $value) {
+
+            if ($key >= 3) {
+
+                if (isset($value['is_keywords']) && $value['is_keywords'] == 1) {
+                    $this->list[] = 'F' . ($key + 1);
+                }
+                if (isset($value['is_keywords_map']) && $value['is_keywords_map'] == 1) {
+                    $this->list[] = 'G' . ($key + 1);
+                }
+                if (isset($value['is_tdk']) && $value['is_tdk'] == 1) {
+                    $this->list[] = 'H' . ($key + 1);
+                }
+                if (isset($value['is_keywords_implantation']) && $value['is_keywords_implantation'] == 1) {
+                    $this->list[] = 'I' . ($key + 1);
+                }
+                if (isset($value['is_home_planning']) && $value['is_home_planning'] == 1) {
+                    $this->list[] = 'J' . ($key + 1);
+                }
+                if (isset($value['is_product_guidance_document']) && $value['is_product_guidance_document'] == 1) {
+                    $this->list[] = 'K' . ($key + 1);
+                }
+                if (isset($value['is_inside_page_planning']) && $value['is_inside_page_planning'] == 1) {
+                    $this->list[] = 'L' . ($key + 1);
+                }
+                if (isset($value['is_special_page_planning']) && $value['is_special_page_planning'] == 1) {
+                    $this->list[] = 'M' . ($key + 1);
+                }
+
+                $dataList[$key]['is_keywords'] = '关键词初选';
+                $dataList[$key]['is_keywords_map'] = '关键词地图';
+                $dataList[$key]['is_tdk'] = 'TDK';
+                $dataList[$key]['is_keywords_implantation'] = '关键词植入';
+                $dataList[$key]['is_home_planning'] = '首页策划';
+                $dataList[$key]['is_product_guidance_document'] = '产品指导交流';
+                $dataList[$key]['is_inside_page_planning'] = '内页策划';
+                $dataList[$key]['is_special_page_planning'] = '特殊页策划';
+            }
+        }
+
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+                $cells = ['A1:M1', 'A2:E2', 'F2:H2', 'I2:M2'];
+                $this->setCellWidth($event, $cells);
+                $cells[] = 'A3:M3';
+                $this->setCellColor($cells, $event);//表头
+                $this->setCellColorBorders($cells, $event);
+                $this->setCellColor($this->list, $event, '87CEFA');
+            }
+        ];
+    }
+
+
+}

+ 96 - 0
app/Exports/ProgressRateExport.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;//自动宽度
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class ProgressRateExport extends CommonExport implements FromArray, WithEvents//,ShouldAutoSize
+{
+    use Exportable;
+
+    public $dataList;
+    public $list;
+
+    public function __construct(array $dataList)
+    {
+        $this->list = [];
+        foreach ($dataList as $key => $value) {
+
+            if ($key >= 3) {
+
+                if (isset($value['info']) && $value['info'] == 1) {
+                    $this->list[] = 'H' . ($key + 1);
+                }
+                if (isset($value['prophase']) && $value['prophase'] == 1) {
+                    $this->list[] = 'I' . ($key + 1);
+                }
+                if (isset($value['data']) && $value['data'] == 1) {
+                    $this->list[] = 'J' . ($key + 1);
+                }
+                if (isset($value['test']) && $value['test'] == 1) {
+                    $this->list[] = 'K' . ($key + 1);
+                }
+                if (isset($value['online']) && $value['online'] == 1) {
+                    $this->list[] = 'L' . ($key + 1);
+                }
+                if (isset($value['is_keywords']) && $value['is_keywords'] == 1) {
+                    $this->list[] = 'M' . ($key + 1);
+                }
+                if (isset($value['is_keywords_map']) && $value['is_keywords_map'] == 1) {
+                    $this->list[] = 'N' . ($key + 1);
+                }
+                if (isset($value['is_tdk']) && $value['is_tdk'] == 1) {
+                    $this->list[] = 'O' . ($key + 1);
+                }
+                if (isset($value['page_planning']) && $value['page_planning'] == 1) {
+                    $this->list[] = 'P' . ($key + 1);
+                }
+
+                $dataList[$key]['info'] = $value['domain'];
+                $dataList[$key]['prophase'] = '建站前期';
+                $dataList[$key]['data'] = '资料';
+                $dataList[$key]['test'] = '测试站';
+                $dataList[$key]['online'] = '上线';
+                $dataList[$key]['is_keywords'] = '关键词初选';
+                $dataList[$key]['is_keywords_map'] = '关键词地图';
+                $dataList[$key]['is_tdk'] = 'TDK';
+                $dataList[$key]['page_planning'] = '页面策划';
+                unset($dataList[$key]['domain']);
+            }
+        }
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class => function (AfterSheet $event) {
+                $cells = ['A1:P1', 'A2:H2', 'I2:L2', 'M2:P2'];
+                $this->setCellWidth($event, $cells);
+                $cells[] = 'A3:P3';
+                $this->setCellColor($cells, $event);//表头
+                $this->setCellColorBorders($cells, $event);
+                $this->setCellColor($this->list, $event, '87CEFA');
+
+            }
+        ];
+    }
+
+
+}

+ 47 - 0
app/Exports/SocialTemplateExport.php

@@ -0,0 +1,47 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/9/24 0024
+ * Time: 13:38
+ */
+
+namespace App\Exports;
+
+
+use Maatwebsite\Excel\Concerns\FromArray;
+use Maatwebsite\Excel\Concerns\Exportable;
+use Maatwebsite\Excel\Concerns\ShouldAutoSize;
+use Maatwebsite\Excel\Events\AfterSheet;
+use Maatwebsite\Excel\Concerns\WithEvents;
+
+class SocialTemplateExport implements FromArray, ShouldAutoSize, WithEvents
+{
+    use Exportable;
+
+    public $dataList;
+
+    public function __construct(array $dataList)
+    {
+        $this->dataList = $dataList;
+    }
+
+    public function array(): array
+    {
+        return $this->dataList;
+    }
+
+    public function registerEvents(): array
+    {
+        return [
+            AfterSheet::class  => function(AfterSheet $event) {
+
+                $event->sheet->getDelegate()->getParent()->getDefaultStyle()->getFont()->setName('Verdana');
+                for ($i = 0; $i<=1265; $i++) {
+                    $event->sheet->getDelegate()->getRowDimension('A')->setRowHeight(30);
+                    $event->sheet->getDelegate()->getRowDimension($i)->setRowHeight(30);
+                }
+            }
+        ];
+    }
+}

+ 20 - 0
app/Http/Constant/CacheConstant.php

@@ -0,0 +1,20 @@
+<?php
+
+
+namespace App\Http\Constant;
+
+
+class CacheConstant
+{
+    const NOW_MONTH_KEY_WORDS_CACHE_KEY = '_nowMonthKeywordsCacheKey_';
+    const LAST_MONTH_KEY_WORDS_CACHE_KEY = '_lastMonthKeywordsCacheKey_';
+    const LAST_MONTH_BQ_KEY_WORDS_CACHE_KEY = '_lastMonthBqKeywordsCacheKey_';
+
+    const NOW_MONTH_INQUIRE_CACHE_KEY = '_nowMonthInquireCacheKey_';
+    const LAST_MONTH_INQUIRE_CACHE_KEY = '_lastMonthInquireCacheKey_';
+    const LAST_MONTH_BQ_INQUIRE_CACHE_KEY = '_lastMonthBqInquireCacheKey_';
+
+    const NOW_MONTH_FLOW_CACHE_KEY = '_nowMonthFlowCacheKey_';
+    const LAST_MONTH_FLOW_CACHE_KEY = '_lastMonthFlowCacheKey_';
+    const LAST_MONTH_BQ_FLOW_CACHE_KEY = '_lastMonthBqFlowCacheKey_';
+}

+ 200 - 0
app/Http/Controllers/Admin/AgentController.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Agent;
+use App\Http\Models\AgentCustomer;
+use App\Http\Models\Role;
+use App\Http\Models\User;
+use App\Http\Requests\Sell\AgentCustomerSaveRequest;
+use App\Http\Requests\Sell\AgentSaveRequest;
+use App\Http\Requests\Sell\AgentUserSaveRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+/**
+ * 代理商 之前提的需求 基本没啥用了
+ * Class SellController
+ * @package App\Http\Controllers\Admin
+ */
+class AgentController extends Controller
+{
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/agent/index');
+        }
+        $builder = Agent::query();
+        if ($keyword = $request->input('keyword')) {
+            $builder->where('contact_name', 'like', '%' . $keyword . '%')
+                ->orWhere('mobile', 'like', '%' . $keyword . '%');
+        }
+
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    public function statusToggle(Request $request)
+    {
+        $ids = $request->input('ids');
+        $status = $request->input('status');
+
+        Agent::query()->whereIn('id', $ids)->update(['status' => $status]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function save(AgentSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            $data = $id > 0 ? Agent::query()->find($id) : null;
+            return view('admin/agent/save', [
+                'data' => $data,
+                'channels' => Role::getUsers(Role::TYPE_CHANNEL_WORKER),
+                'agentUsers' => [],
+                'hasChannels' => $data->channel_ids ?? []
+            ]);
+        }
+
+        $validated = $request->validated();
+        if ($id == 0) {
+            $validated['status'] = 1;
+        }
+        Agent::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function delete(Request $request)
+    {
+        $ids = $request->input('ids');
+        Agent::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    public function customer(Request $request)
+    {
+
+
+        if (!$request->ajax()) {
+
+            AgentCustomer::query()->where([
+                ['status', '=', 1],
+                ['created_at', '<', date('Y-m-d H:i:s', strtotime('-15 day'))]
+            ])->whereNull('remark')->update(['status' => 0]);
+            return view('admin/agent/customer');
+        }
+
+        $builder = AgentCustomer::query()->scopes(['user'])->with('user');
+        if ($keyword = $request->input('keyword')) {
+            $builder->where('customer_name', 'like', '%' . $keyword . '%')
+                ->orWhere('contact_user', 'like', '%' . $keyword . '%')
+                ->orWhere('contact_phone', 'like', '%' . $keyword . '%');
+        }
+
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+
+        $items = $records->items();
+        array_walk($items, function ($item) {
+            $item->createUsername = $item->user->username ?? '';
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    public function customerSave(AgentCustomerSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            $data = $id > 0 ? AgentCustomer::query()->find($id) : null;
+            return view('admin/agent/customer_save', [
+                'data' => $data,
+                'users' => User::query()->whereIn('role_id', [
+                    Role::TYPE_CHANNEL_BOSS,
+                    Role::TYPE_CHANNEL_MANAGER,
+                    Role::TYPE_CHANNEL_WORKER,
+                    Role::TYPE_AGENT_BOSS,
+                    Role::TYPE_AGENT_MANAGER,
+                    Role::TYPE_AGENT_WORKER
+                ])->select(['id', 'username'])->get(),
+            ]);
+        }
+        $validated = $request->validated();
+
+        if ($id == 0) {
+            $user = auth()->user();
+            $validated['status'] = 1;
+            $validated['user_id'] = $user->id;
+
+            if (in_array($user->role_id, [Role::TYPE_AGENT_BOSS, Role::TYPE_AGENT_MANAGER, Role::TYPE_AGENT_WORKER])) {
+                $validated['origin'] = 1;
+            } elseif (in_array($user->role_id, [Role::TYPE_CHANNEL_BOSS, Role::TYPE_CHANNEL_MANAGER, Role::TYPE_CHANNEL_WORKER])) {
+                $validated['origin'] = 2;
+            } else {
+                return response()->json(['message' => '当前身份既不是代理商也不是渠道'], 400);
+            }
+        } else {
+            //防止未选择的时候 覆盖掉
+            if (empty($validated['user_id'])) {
+                unset($validated['user_id']);
+            }
+        }
+        AgentCustomer::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function agentUsers(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/agent/user');
+        }
+
+        $builder = User::query()->where([['agent_id', '>', 0]]);
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    public function agentUsersSave(AgentUserSaveRequest $request, $id)
+    {
+
+
+        if (!$request->ajax()) {
+            $data = $id > 0 ? User::query()->find($id) : null;
+            $roles = Role::query()->whereIn('id', [
+                Role::TYPE_AGENT_BOSS,
+                Role::TYPE_AGENT_MANAGER,
+                Role::TYPE_AGENT_WORKER
+            ])->get();
+            return view('admin/agent/user_save', [
+                'user' => $data,
+                'roles' => $roles,
+                'agents' => Agent::all()
+            ]);
+        }
+        $validated = $request->validated();
+
+
+        if (!empty($validated['password'])) {
+            $validated['password'] = bcrypt($validated['password']);
+        } else {
+            unset($validated['password']);
+        }
+
+        if ($id === 0) {
+            $validated['status'] = 1;
+        }
+        User::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 21 - 0
app/Http/Controllers/Admin/AliYunOssController.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Http\Controllers\Controller;
+use App\Http\Services\AliYunOSService;
+use Illuminate\Http\Request;
+
+
+class AliYunOssController extends Controller
+{
+    public function upload(Request $request)
+    {
+        $file = $request->file('file');
+        $aliYunOSService = new AliYunOSService();
+        $result = $aliYunOSService->upload($file->getRealPath(), $file->getClientOriginalName());
+        return response()->json(['message' => '上传成功', 'path' => $result]);
+    }
+
+}

+ 311 - 0
app/Http/Controllers/Admin/Analyze/CollectController.php

@@ -0,0 +1,311 @@
+<?php
+/**
+ * 汇总统计
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-10-13
+ */
+
+namespace App\Http\Controllers\Admin\Analyze;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\PrKeywordExtend;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\SitesStatus;
+use App\Http\Models\User;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Contracts\View;
+use Illuminate\Http\JsonResponse;
+
+class CollectController extends Controller
+{
+
+    /**
+     * 404合集
+     * @param Request $request
+     * @return View\Factory|JsonResponse|View\View
+     */
+    public function collect(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/analyze/collect', [
+                'web' => Role::getUsers(Role::TYPE_WEB),
+                'optimizers' => Role::getUsers(Role::TYPE_OPTIMIZER),
+                'optimizers_edit' => Role::getUsers(Role::TYPE_OPTIMIZATION_EDITING),
+            ]);
+        }
+        $build = SitesStatus::query();
+        $build = $this->inquire($request, $build);
+
+        $cnTitleList = Site::query()->pluck('cn_title', 'id');
+        $domainList = Site::query()->pluck('domain', 'id');
+        $memoList = Site::query()->pluck('not_found_memo', 'id');
+
+        $list = $build->with(['users'])
+            ->where('status', 404)
+            ->where('is_handle', 0)
+            ->where('url', 'NOT LIKE', '%javascript:%')
+            ->where('url', 'NOT LIKE', '%tel:%')
+            ->where('url', 'NOT LIKE', '%mailto:%')
+            ->where('url', 'NOT LIKE', '%$%')
+            ->where('url', 'NOT LIKE', '%skype%')
+            ->where('url', 'NOT LIKE', '%whatsapp%')
+            ->selectRaw('count(*) as count,site_id')
+            ->groupBy('site_id')
+            ->paginate($request->input('pageSize') ?? 20);
+
+        foreach ($list as $item) {
+            $item->not_found = $memoList[$item->site_id] ?? '';
+            $item->cn_title = $cnTitleList[$item->site_id] ?? '';
+            $item->domain = $domainList[$item->site_id] ?? '';
+            $item->web = $item->users->where('role_id', Role::TYPE_WEB)->first()->nickname ?? '';
+            $item->optimization = $item->users->where('role_id', Role::TYPE_OPTIMIZER)->first()->nickname ?? '';
+            $item->optimizationAe = $item->users->where('role_id', Role::TYPE_OPTIMIZATION_EDITING)->first()->nickname ?? '';
+        }
+
+        return response()->json([
+            'rows' => $list->items(),
+            'total' => $list->total()
+        ]);
+    }
+
+    /**
+     * 测速合集
+     * @param Request $request
+     * @return View\Factory|JsonResponse|\Illuminate\View\View
+     */
+    public function speedMeasurement(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/analyze/speed', [
+                'web' => Role::getUsers(Role::TYPE_WEB),
+                'optimizers' => Role::getUsers(Role::TYPE_OPTIMIZER),
+                'optimizers_edit' => Role::getUsers(Role::TYPE_OPTIMIZATION_EDITING),
+            ]);
+        }
+        $build = Site::query();
+        $build = $this->inquire($request, $build, 'id');
+
+        $siteList = $build->with(['users'])
+            ->whereIn('status', [2, 3])
+            ->select('id', 'old_id', 'domain', 'cn_title', 'speed_memo')
+            ->whereNotNull('database')
+            ->get();
+
+        $pcSpeedMeasurement = [];
+        $mobileSpeedMeasurement = [];
+
+        $speedMeasurementList = DB::table('app_speed_measurement_cache')->get();
+        foreach ($speedMeasurementList as $key => $value) {
+            $array = json_decode($value->cache, true);
+            $pcSpeedMeasurementResult = $array['pc'];
+            $mobileSpeedMeasurementResult = $array['mobile'];
+
+            $oldId = str_replace('cache:app/Http/Controllers/SpeedMeasurementController/index:', '', $value->key);
+            $pcSpeedMeasurement[$oldId] = $pcSpeedMeasurementResult;
+            $mobileSpeedMeasurement[$oldId] = $mobileSpeedMeasurementResult;
+        }
+
+
+        foreach ($siteList as $item) {
+            $item->web = $item->users->where('role_id', Role::TYPE_WEB)->first()->nickname ?? '';
+            $item->optimization = $item->users->where('role_id', Role::TYPE_OPTIMIZER)->first()->nickname ?? '';
+            $item->optimizationAe = $item->users->where('role_id', Role::TYPE_OPTIMIZATION_EDITING)->first()->nickname ?? '';
+            $item->pcSpeedMeasurement = $pcSpeedMeasurement[$item->old_id] ?? '';
+            $item->mobileSpeedMeasurement = $mobileSpeedMeasurement[$item->old_id] ?? '';
+        }
+
+        foreach ($siteList as $key => $value) {
+            if ($value->pcSpeedMeasurement > 79) {
+                unset($siteList[$key]);
+            }
+        }
+
+        $res = collect($siteList)->sortByDesc('pcSpeedMeasurement');
+        $perPage = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+        $result = $this->paginateCollection($res, $perPage);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total()
+        ]);
+    }
+
+    /**
+     * 查询
+     * @param $request object
+     * @param $build object
+     * @param $siteFile string
+     * @return mixed
+     */
+    private function inquire($request, $build, $siteFile = 'site_id')
+    {
+        $keyword = $request->input('keyword');
+        if (!empty($keyword)) {
+            $siteIds = Site::query()
+                ->where('domain', 'like', '%' . $keyword . '%')
+                ->orWhere('cn_title', 'like', '%' . $keyword . '%')
+                ->pluck('id');
+            $build->whereIn($siteFile, $siteIds);
+        }
+        $webId = $request->input('webId');
+        if (!empty($webId)) {
+            $userIds = User::query()->where('id', $webId)->pluck('id');
+            $siteIds = DB::table('user_has_sites')->whereIn('user_id', $userIds)->pluck('site_id');
+            $build->whereIn($siteFile, $siteIds);
+        }
+
+        $optimizerId = $request->input('optimizerId');
+        if (!empty($optimizerId)) {
+            $userIds = User::query()->where('id', $optimizerId)->pluck('id');
+            $siteIds = DB::table('user_has_sites')->whereIn('user_id', $userIds)->pluck('site_id');
+            $build->whereIn($siteFile, $siteIds);
+        }
+        $optimizersEditId = $request->input('optimizersEditId');
+        if (!empty($optimizersEditId)) {
+            $userIds = User::query()->where('id', $optimizersEditId)->pluck('id');
+            $siteIds = DB::table('user_has_sites')->whereIn('user_id', $userIds)->pluck('site_id');
+            $build->whereIn($siteFile, $siteIds);
+        }
+        return $build;
+    }
+
+    /**
+     * 保存备注
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function saveMemo(Request $request)
+    {
+        $memo = $request->input('memo') ?? '';
+        $type = $request->input('type') ?? 1;
+        $siteId = $request->input('siteId');
+
+        if (!empty($siteId)) {
+            if ($type == 1) {
+                Site::query()->where('id', $siteId)->update(['speed_memo' => $memo]);
+            } elseif ($type == 2) {
+                Site::query()->where('id', $siteId)->update(['not_found_memo' => $memo]);
+            } else {
+                Site::query()->where('id', $siteId)->update(['keyword_memo' => $memo]);
+            }
+            return response()->json(['message' => '操作成功']);
+        }
+    }
+
+    /**
+     * 关键词未拓展统计
+     * @param Request $request
+     * @return View\Factory|\Illuminate\View\View
+     */
+    public function keywordsExpand(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/analyze/keywords_expand', [
+                'services' => Role::getUsers(Role::TYPE_SERVER), //服务人员
+                'optimizers' => Role::getUsers(Role::TYPE_OPTIMIZER),
+                'sellerUsers' => Role::getUsers(Role::TYPE_SELLER),
+                'optimizationEditingUser' => Role::getUsers(Role::TYPE_OPTIMIZATION_EDITING),
+            ]);
+        }
+        $keyword = $request->input('keyword');
+
+        $filterUserIds = [];
+        $sellerId = $request->input('sellerId');
+        $sellerId && $filterUserIds[] = $sellerId;
+        $serverId = $request->input('serverId');
+        $serverId && $filterUserIds[] = $serverId;
+        $optimizerId = $request->input('optimizerId');
+        $optimizerId && $filterUserIds[] = $optimizerId;
+        $optimizationEditingUserId = $request->input('optimizationEditingUserId');
+        $optimizationEditingUserId && $filterUserIds[] = $optimizationEditingUserId;
+
+        $builder = Site::query()->with(['users']);
+        if ($filterUserIds) {
+            $builder->whereExists(function (\Illuminate\Database\Query\Builder $b) use ($filterUserIds) {
+                $raw = sprintf('SUM(CASE WHEN user_id in (%s) then  1 ELSE  0 end) as total', implode(',', $filterUserIds));
+                $b->select(DB::raw($raw))->from('user_has_sites')
+                    ->whereRaw(sprintf('user_has_sites.site_id=sites.id HAVING total=%s', count($filterUserIds)));
+            });
+        }
+        $type = $request->input('type');
+
+        //每个项目最新的一次的记录的id
+        $maxIds = PrKeywordExtend::query()->selectRaw('max(id) as id_max,site_id')
+            ->groupBy('site_id')
+            ->pluck('id_max', 'site_id')->toArray();
+        //全部拓展过
+        $siteByKeyWordList = PrKeywordExtend::query()
+            ->whereIn('id', $maxIds)
+            ->pluck('ym', 'site_id')->toArray();
+        $ids = array_keys($siteByKeyWordList);
+
+        //3个月内没有拓展过
+        if (empty($type)) {
+            $list = PrKeywordExtend::query()->whereIn('id', $maxIds)->get();
+            $ids = [];
+            $date = date("Ym", strtotime("last day of -3 month", strtotime(date("Ym"))));
+            foreach ($list as $value) {
+                if ($value->ym <= $date) {
+                    $ids[] = $value->site_id;
+                }
+            }
+        }
+
+        //从未拓展过
+        if ($type == 1) {
+            $date = date("Y-m-d", strtotime("last day of -3 month", strtotime(date("Y-m-d"))));
+            $id = Site::query()
+                ->where('online_at', '<=', $date)
+                ->whereIn('status', [2, 3])
+                ->pluck('id')->toArray();
+
+            $ids = [];
+            foreach ($id as $value) {
+                $result = PrKeywordExtend::query()->where('site_id', $value)->first();
+                if (empty($result)) {
+                    $ids[] = $value;
+                }
+            }
+        }
+        $siteList = $builder
+            ->select('id', 'old_id', 'cn_title', 'status', 'domain', 'expired_at', 'reach_at', 'keyword_goal', 'keyword_memo')
+            ->whereIn('id', $ids)
+            ->whereIn('status', [2, 3])
+            ->where(function (Builder $builder) use ($keyword) {
+                if ($keyword) {
+                    $builder->where('domain', 'like', '%' . $keyword . '%')
+                        ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                }
+            })->get();
+        $oldIds = array_filter(array_column($siteList->toArray(), 'old_id'));
+        $top10ListMap = DB::connection('rank')->table('project_keyword')
+            ->whereIn('project_id', $oldIds)
+            ->selectRaw('SUM(CASE WHEN google_rank <= 10 THEN 1 ELSE 0 END) as top10,project_id')
+            ->groupBy('project_id')->get()->keyBy('project_id')->toArray();
+
+        foreach ($siteList as $item) {
+            $item->status = Site::STATUS_MAP[$item->status];
+            $item->renewal_at = substr($item->renewal_at, 0, 10);
+            $item->expired_at = substr($item->expired_at, 0, 10);
+            $item->reach_at = substr($item->reach_at, 0, 10);
+            $item->top10 = $item->old_id ? ($top10ListMap[$item->old_id]->top10 ?? '-') : '未关联'; //实际达标关键词
+            $item->ym = $siteByKeyWordList[$item->id] ?? '';
+            $item->optimizer = implode('-', $item->users->where('role_id', Role::TYPE_OPTIMIZER)->pluck('nickname')->toArray());
+            $item->optimizerAe = implode('-', $item->users->where('role_id', Role::TYPE_OPTIMIZATION_EDITING)->pluck('nickname')->toArray());
+        };
+
+        $res = collect($siteList)->sortByDesc('ym');
+        $perPage = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+        $result = $this->paginateCollection($res, $perPage);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total()
+        ]);
+
+    }
+}

+ 348 - 0
app/Http/Controllers/Admin/Analyze/GoogleTrendsController.php

@@ -0,0 +1,348 @@
+<?php
+/**
+ * 谷歌趋势
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-10-13
+ */
+
+namespace App\Http\Controllers\Admin\Analyze;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\GoogleTrendsKeyword;
+use App\Http\Models\Role;
+use App\Http\Models\User;
+use App\Http\Models\WeekTaskHistory;
+use App\Http\Models\WeekTaskInfo;
+use App\Http\Services\SendMessageService;
+use App\Imports\GoogleTrendsKeywordImport;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\Support\Facades\Log;
+use Illuminate\View\View;
+use Maatwebsite\Excel\Facades\Excel;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http;
+
+class GoogleTrendsController extends Controller
+{
+    public $sendMessageService;
+   // const MOBILE = '17805855987';//cc
+    //const MOBILE = '17771777094';//test
+    const MOBILE = '18705195517';//cc
+
+
+    /**
+     * 短信服务类
+     * GoogleTrendsController constructor.
+     * @param SendMessageService $sendMessageService
+     */
+    public function __construct(SendMessageService $sendMessageService)
+    {
+        $this->sendMessageService = $sendMessageService;
+    }
+
+    /**
+     * 报表
+     * @param Request $request
+     * @return Factory|Http\RedirectResponse|View
+     */
+    public function index(Request $request)
+    {
+        try {
+            $keyWold = $request->input('keyWold');
+            $disableList = GoogleTrendsKeyword::query()->where('enable', 1)->pluck('keyword');
+            if (in_array($keyWold, $disableList->toArray())) {
+                return view('admin/site/not_found', [
+                    'tips' => '该词不在接单范围内',
+                    'siteId' => 0,
+                ]);
+            }
+            $keyWoldInfo = GoogleTrendsKeyword::query()->where('keyword', $keyWold)->first();
+            if (empty($keyWoldInfo)) {
+                $this->sendMessageService->sendMessage(self::MOBILE, $keyWold);
+                return view('admin/site/not_found', [
+                    'tips' => '暂无资料',
+                    'siteId' => 0,
+                ]);
+            }
+            if (empty($keyWoldInfo->monthly_searches) || empty($keyWoldInfo->monthly_searches) || empty($keyWoldInfo->monthly_searches)) {
+                return view('admin/site/not_found', [
+                    'tips' => '暂无资料',
+                    'siteId' => 0,
+                ]);
+            }
+
+            if (!empty($keyWoldInfo->cache)) {
+                $cache = \GuzzleHttp\json_decode($keyWoldInfo->cache, true);
+                $relatedInformation = $cache['relatedInformation'] ?? [];
+                $relatedTopic = $cache['relatedTopic'] ?? [];
+                $dateList = $cache['dateList'] ?? [];
+                $countryList = $cache['countryList'] ?? [];
+            } else {
+                //请求香港的后台服务器47.56.232.20
+                $client = new Client();
+                $response = $client->post('http://test.build.yinqingli.net/googleTrendsApi/getKeyWordResult', [
+                    'form_params' => ['keyWold' => $keyWold],
+                ]);
+                $result = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
+                if ($result['status'] == 200) {
+                    GoogleTrendsKeyword::query()->where('id', $keyWoldInfo->id)->update(['cache' => \GuzzleHttp\json_encode($result['data'])]);
+                }
+                $relatedInformation = $result['data']['relatedInformation'] ?? [];
+                $relatedTopic = $result['data']['relatedTopic'] ?? [];
+                $dateList = $result['data']['dateList'] ?? [];
+                $countryList = $result['data']['countryList'] ?? [];
+            }
+
+            return view('admin/google_trends/google_trends', [
+                'relatedInformation' => $relatedInformation,
+                'relatedTopic' => $relatedTopic,
+                'dateList' => $dateList,
+                'countryList' => $countryList,
+                'keyWoldInfo' => $keyWoldInfo,
+                'country' => array_merge($this->arraySort($countryList, 'value', 'desc')),
+            ]);
+
+        } catch (\Exception $e) {
+            echo $e->getMessage();
+        }
+    }
+
+
+    /**
+     * 查询
+     * @param Request $request
+     * @return Factory|JsonResponse|View
+     */
+    public function query(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/google_trends/google_trends_query', [
+            ]);
+        }
+        $keyWold = strtolower(trim($request->input('keyWold')));
+        if (empty($keyWold)) {
+            return response()->json(['status' => 500, 'message' => '请输入关键词']);
+        }
+        $cnKeyWord = strtolower(trim($request->input('cnKeyWord'))) ?? '';
+        $website = strtolower(trim($request->input('website'))) ?? '';
+
+        $disableList = GoogleTrendsKeyword::query()->where('enable', 1)->pluck('keyword');
+        if (in_array($keyWold, $disableList->toArray())) {
+            $remarks = GoogleTrendsKeyword::query()->where('keyword', $keyWold)->value('remarks') ?? '';
+            return response()->json(['status' => 500, 'message' => $remarks]);
+        }
+
+        $keyWoldInfo = GoogleTrendsKeyword::query()->where('keyword', $keyWold)->first();
+        if (empty($keyWoldInfo)) {
+            GoogleTrendsKeyword::query()->insert(
+                [
+                    'keyword' => $keyWold,
+                    'cn_keyword' => $cnKeyWord,
+                    'website' => $website,
+                    'created_at' => date('Y-m-d H:i:s'),
+                    'user_id' => $authUser = auth()->user()->id,
+                ]);
+            $this->sendMessageService->sendMessage(self::MOBILE, $keyWold);
+            return response()->json(['status' => 301]);
+        }
+        if (empty($keyWoldInfo->monthly_searches)) {
+            return response()->json(['status' => 301]);
+        }
+        return response()->json(['status' => 200]);
+    }
+
+
+    /**
+     * 关键词
+     * @param Request $request
+     * @return Factory|View
+     */
+    public function keyword(Request $request)
+    {
+        if (!$request->ajax()) {
+            $disabled = GoogleTrendsKeyword::query()->where('enable', 1)->count() ?? 0;//禁用
+            $enable = GoogleTrendsKeyword::query()->where('enable', 0)->count() ?? 0;//启用
+            return view('admin/analyze/keyword', [
+                'optimizers' => Role::getUsers(Role::TYPE_OPTIMIZER),
+                'seller' => Role::getUsers(Role::TYPE_SELLER),
+                'disabled' => $disabled,
+                'enable' => $enable
+            ]);
+        }
+        $keyword = $request->input('keyword');
+        $result = GoogleTrendsKeyword::query()->whereNull('deleted_at');
+        if (!empty($keyword)) {
+            $result = $result->where('keyword', 'like', '%' . $keyword . '%');
+        }
+        $optimizerId = $request->input('optimizerId');
+        if (!empty($optimizerId)) {
+            $result = $result->where('principal', $optimizerId);
+        }
+        $sellerId = $request->input('sellerId');
+        if (!empty($sellerId)) {
+            $result = $result->where('user_id', $sellerId);
+        }
+        $enable = $request->input('enable');
+        if (isset($enable)) {
+            $result = $result->where('enable', $enable);
+        }
+        $result = $result->orderByDesc('id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $userList = User::query()->where('status', 1)->pluck('nickname', 'id');
+
+        foreach ($result->items() as $item) {
+            $item->user = $userList[$item->user_id] ?? '';
+            $item->taskUser = $userList[$item->principal] ?? '';
+            $keywordsCondition = [
+                'user_type' => $item->principal,
+                'describe' => $item->keyword,
+            ];
+            $item->taskStatus = '未完成';
+            $task = WeekTaskInfo::query()->where($keywordsCondition)->first();
+            $historyTask = WeekTaskHistory::query()->where($keywordsCondition)->orderBy('id', 'desc')->first();
+            if (!empty($task) && $task->status == 'ok') {
+                $item->taskStatus = '完成';
+            }
+            if (!empty($historyTask) && $historyTask->status == 'ok') {
+                $item->taskStatus = '完成';
+            }
+        }
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total()
+        ]);
+    }
+
+    /**
+     * 分配任务,并发送短信
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function keywordTask(Request $request)
+    {
+        $taskId = $request->input('taskId');
+        $userId = $request->input('userId');
+        $keyword = GoogleTrendsKeyword::query()->where('id', $taskId)->first();
+        if (!empty($keyword)) {
+            GoogleTrendsKeyword::query()->where('id', $taskId)->update(['principal' => $userId]);
+        }
+
+        $user = User::query()->where('id', $userId)->first();
+        if (!empty($user->phone)) {
+
+            //自动创建任务并发短信给优化
+            $this->sendMessageService->sendMessage($user->phone, $keyword->keyword ?? '', 3);
+            $data = [
+                'type' => 'now',
+                'day' => date('w'),
+                'user_type' => $userId,
+                'duty_id' => $keyword->user_id,
+                'describe' => $keyword->keyword,
+                'remark' => $keyword->keyword . ' 接单关键词',
+                'cond_id' => 87,
+                'created_at' => date('Y-m-d H:i:s'),
+                'deadline' => date("Y-m-d", strtotime("+1 day")),//一天完成
+            ];
+            $info = WeekTaskInfo::query()->where(['describe' => $data['describe'], 'cond_id' => 87])->first();
+            if (empty($info)) {
+                WeekTaskInfo::query()->insert($data);
+            } else {
+                WeekTaskInfo::query()->where('id', $info->id)->update(['user_type' => $userId]);
+            }
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 删除关键词
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function deleteKeyword(Request $request)
+    {
+        $taskId = $request->input('taskId');
+        $update = [
+            'updated_at' => date('Y-m-d H:i:s'),
+            'deleted_at' => date('Y-m-d H:i:s'),
+        ];
+        GoogleTrendsKeyword::query()->where('id', $taskId)->update($update);
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    /**
+     * 保存
+     * @param Request $request
+     * @param $id
+     * @return Factory|JsonResponse|View
+     */
+    public function keywordSave(Request $request, $id)
+    {
+        if (!$request->ajax()) {
+            $info = GoogleTrendsKeyword::query()->where('id', $id)->first();
+            return view('admin/analyze/save_keyword', [
+                'info' => $info
+            ]);
+        }
+        $result = $request->all();
+        if (!empty($result['monthly_searches']) && !is_numeric($result['monthly_searches'])) {
+            return response()->json(['message' => '请填纯数字,不要带格式'], 400);
+        }
+        if (!empty($result['mumber_of_search_results']) && !is_numeric($result['mumber_of_search_results'])) {
+            return response()->json(['message' => '请填纯数字,不要带格式'], 400);
+        }
+        if (!empty($result['competition_index']) && !is_numeric($result['competition_index'])) {
+            return response()->json(['message' => '请填纯数字,不要带格式'], 400);
+        }
+        $result['updated_at'] = date('Y-m-d H:i:s');
+        GoogleTrendsKeyword::query()->where('id', $id)->update($result);
+        return response()->json(['message' => '操作成功']);
+
+    }
+
+    /**
+     * 导入
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function importKeyWord(Request $request)
+    {
+        set_time_limit(0);
+        try {
+            $excelPath = $request->input('excel_path');
+            if (!$excelPath) {
+                return response()->json(['message' => '请先上传excel文件'], 422);
+            }
+            Excel::import(new GoogleTrendsKeywordImport(), $excelPath, 'public');
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['message' => '导入失败'], 400);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 关键词列表
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function getKeyWordList(Request $request)
+    {
+        $data = [];
+        $keyword = $request->input('keyword');
+        $list = GoogleTrendsKeyword::query()->where('keyword', 'like', '%' . $keyword . '%')->pluck('keyword') ?? [];
+        if (!empty($list)) {
+            foreach ($list as $key => $item) {
+                $data[] = [
+                    'value' => $item,
+                    'data' => $item,
+                ];
+            }
+        }
+        return response()->json(['data' => $data]);
+    }
+}

File diff suppressed because it is too large
+ 4557 - 0
app/Http/Controllers/Admin/Analyze/IndexController.php


File diff suppressed because it is too large
+ 4528 - 0
app/Http/Controllers/Admin/Analyze/IndexController_bak.php


+ 152 - 0
app/Http/Controllers/Admin/Analyze/StaffController.php

@@ -0,0 +1,152 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2020/6/19 0019
+ * Time: 11:34
+ */
+
+namespace App\Http\Controllers\Admin\Analyze;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Role;
+
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+class StaffController extends Controller
+{
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            $roles = Role::query()->pluck('name', 'id');
+            return view('admin.analyze.staff', [
+                'roles' => $roles,
+            ]);
+        }
+
+        $builder = User::query();
+        if ($roleId = $request->input('roleId')) {
+            $builder->where('role_id', $roleId);
+        } else {
+            //$builder->whereIn('role_id', [Role::TYPE_MANAGER, Role::TYPE_AE, Role::TYPE_OPTIMIZER, Role::TYPE_MANAGE_HELPER]);
+        }
+        $records = $builder->with(['role', 'sites' => function (BelongsToMany $query) {
+            $query->selectRaw('id,status,design_score,online_score,reach_score');
+        }])->where(['status' => 1])->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+
+        $items = $records->items();
+
+        foreach ($items as &$item) {
+            $item->roleName = $item->role->name ?? '暂无';
+            $item->buildNum = $item->sites->where('status', 1)->count();
+            $item->impleNum = $item->sites->where('status', 2)->count();
+            $item->srvNum = $item->sites->where('status', 3)->count();
+
+            $designNum = $designScore = 0;
+            $onlineNum = $onlineScore = 0;
+            $reachNum = $reachScore = 0;
+
+            foreach ($item->sites as $site) {
+                if ($site->design_score) {
+                    $designNum++;
+                    $designScore += floatval($site->design_score);
+                }
+                if ($site->online_score) {
+                    $onlineNum++;
+                    $onlineScore += floatval($site->online_score);
+                }
+                if ($site->reach_score) {
+                    $reachNum++;
+                    $reachScore += floatval($site->reach_score);
+                }
+            }
+
+            $item->avgDesignScore = $designNum ? number_format($designScore / $designNum, 2) : 0;
+            $item->avgOnlineScore = $onlineNum ? number_format($onlineScore / $onlineNum, 2) : 0;
+            $item->avgReachScore = $reachNum ? number_format($reachScore / $reachNum, 2) : 0;
+            if ($item->role_id == Role::TYPE_OPTIMIZER) {
+                $item->totalScore = $item->impleNum + $item->srvNum;
+            } else {
+                $item->totalScore = $item->buildNum + $item->impleNum + $item->srvNum;
+            }
+        }
+        return response()->json([
+            'rows' => $records->items(),
+            'total' => $records->total()
+        ]);
+    }
+
+    public function sales(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin.analyze.sales', [
+            ]);
+        }
+        $users = User::query()->where('role_id', Role::TYPE_SELLER)->where('status', 1)->pluck('nickname', 'id');
+        $userList = DB::table('users as a')
+            ->leftJoin('user_has_sites as b', 'a.id', '=', 'b.user_id')
+            ->leftJoin('sites as c', 'b.site_id', '=', 'c.id')
+            ->select('a.id', 'a.nickname', 'c.cn_title', 'c.status')
+            ->where('a.role_id', Role::TYPE_SELLER)
+            ->where('a.status', 1)
+            ->whereNull('c.deleted_at')
+            ->get();
+
+        $result = [];
+        foreach ($users as $key => $value) {
+            $sitesCount = 0;//总数
+            $buildStation = 0;//建站
+            $implement = 0;//实施
+            $service = 0;//服务器
+            $pause = 0;//暂停
+            $renew = 0;//续费
+            $termination = 0;//终止
+
+            foreach ($userList as $kk => $vv) {
+
+                if ($key == $vv->id) {
+                    $sitesCount++;
+                    if ($vv->status == 1) {
+                        $buildStation++;
+                    }
+                    if ($vv->status == 2) {
+                        $implement++;
+                    }
+                    if ($vv->status == 3) {
+                        $service++;
+                    }
+                    if ($vv->status == 4) {
+                        $pause++;
+                    }
+                    if ($vv->status == 5) {
+                        $renew++;
+                    }
+                    if ($vv->status == 7) {
+                        $termination++;
+                    }
+                }
+            }
+            $result[] = [
+                'userId' => $key,
+                'sitesCount' => $sitesCount,
+                'buildStation' => $buildStation,
+                'implement' => $implement,
+                'service' => $service,
+                'pause' => $pause,
+                'renew' => $renew,
+                'termination' => $termination
+            ];
+        }
+        foreach ($result as $key => $value) {
+            $result[$key]['nickname'] = $users[$value['userId']] ?? '';
+        }
+        return response()->json([
+            'rows' => $result,
+            'total' => count($result) ?? 0
+        ]);
+    }
+}

File diff suppressed because it is too large
+ 1327 - 0
app/Http/Controllers/Admin/ArticleController.php


File diff suppressed because it is too large
+ 1235 - 0
app/Http/Controllers/Admin/ArticleController_bak.php


File diff suppressed because it is too large
+ 1238 - 0
app/Http/Controllers/Admin/ArticleController_bak04-24.php


+ 84 - 0
app/Http/Controllers/Admin/AuthController.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\User;
+use App\Http\Models\Permission;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Cookie;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Facades\Session;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\URL;
+use Illuminate\Support\Str;
+
+class AuthController extends Controller
+{
+    public $request;
+
+    public function __construct(Request $request)
+    {
+        $this->middleware(['logonRedirect'])->only('logon', 'login');
+        $this->request = $request;
+    }
+    //登录入口1
+    public function logon(Request $request)
+    {
+
+        $requestUrl = $request->getRequestUri();
+        if (!$request->ajax()) {
+            return view('admin/auth/logon');
+        }
+
+        $backUser = User::query()->where(['username' => $this->request->input('username')])->first();
+        if (!$backUser || !Hash::check($this->request->input('password'), $backUser->password)) {
+            return response()->json(['status' => 400, 'message' => '用户名或密码错误']);
+        }
+        if (!$backUser->status) {
+            return response()->json(['status' => 400, 'message' => '用户已禁用']);
+        }
+        Auth::loginUsingId($backUser->id);
+//        session()->put('entry_page', 'admin/auth/logon');
+        session()->put('entry_page', $requestUrl);
+        do_log('logon')->use('system_log')->by(Auth::user())->log('安全登录');
+        return response()->json(['status' => 200])->withCookie('entry_page', $requestUrl);
+    }
+    //登出
+    public function logoff(Request $request)
+    {
+        do_log('logoff')->use('system_log')->by(Auth::user())->log('安全退出');
+        $entryPage = session('entry_page') ?? $request->input('entry_page') ?? 'admin/auth/logon';
+        Auth::logout();
+        Session::flush();
+        \cookie()->forget('laravel_session');
+        return redirect($entryPage);
+    }
+
+    //登录入口2
+    public function login(Request $request)
+    {
+        $from = Route::currentRouteName();
+
+        if (!$request->ajax()) {
+            return view('admin/auth/login', [
+                'from' => $from
+            ]);
+        }
+        $backUser = User::query()->where(['username' => $this->request->input('username')])->first();
+        if (!$backUser || !Hash::check($this->request->input('password'), $backUser->password)) {
+            return response()->json(['status' => 400, 'message' => '用户名或密码错误']);
+        }
+        if (!$backUser->status) {
+            return response()->json(['status' => 400, 'message' => '用户已禁用']);
+        }
+
+        Auth::loginUsingId($backUser->id);
+        $requestUrl = sprintf('/%s', $from);
+        session()->put('entry_page', $requestUrl);
+
+        do_log('logon')->use('system_log')->by(Auth::user())->log('安全登录');
+        return response()->json(['status' => 200])->withCookie('entry_page', $requestUrl);
+    }
+}

+ 921 - 0
app/Http/Controllers/Admin/BidController.php

@@ -0,0 +1,921 @@
+<?php
+/**
+ * 竞价管理
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-08-23
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\SitePayment;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Facades\DB;
+use App\Http\Models\Contract;
+
+class BidController extends Controller
+{
+    //竞价列表
+    public function siteList(Request $request, $type)
+    {
+        if (!$request->ajax()) {
+
+            $userList = User::query()->select(['id', 'nickname'])->where(['status' => 1])->whereIn('role_id', [16, 14, 27])->get();
+            return view('admin.bid.site_list', [
+                'userList' => $userList,
+                'type' => $type
+            ]);
+        }
+        if ($status = $request->input('status')) {
+            $condition['status'] = $status;
+        }
+        if ($request->input('bid_status') == 'on') {
+            $condition['bid_status'] = 1;
+        }
+        //已投放个数
+        if ($type == 1) {
+            $condition['bid_status'] = 1;
+        }
+        //待投放个数
+        if ($type == 2) {
+            $condition['bid_status'] = 0;
+            $status = [2, 3, 5, 6, 7, 8, 9];
+            $condition[] = [function ($query) use ($status) {
+                $query->whereIn('status', $status);
+            }];
+        }
+        //已暂停放个数
+        if ($type == 3) {
+            $siteIdList = Site::query()->whereJsonContains('contract_ids', "3")->pluck('id');
+            $siteIds = SitePayment::query()->whereIn('site_id', $siteIdList)->whereNotNull('bid_account_pause_time')->pluck('site_id')->toArray() ?? [];
+
+            $condition[] = [function ($query) use ($siteIds) {
+                $query->whereIn('id', $siteIds);
+            }];
+        }
+        //已续费个数
+        if ($type == 4) {
+            $siteIdList = Site::query()->whereJsonContains('contract_ids', "3")->pluck('id');
+            $payRecords = SitePayment::query()->whereIn('site_id', $siteIdList)->selectRaw('site_id,google_bid')->whereNotNull('google_bid')->get();
+
+            $totalRenewalNum = [];
+            foreach ($payRecords as $record) {
+                $bids = $record->google_bid;
+                $lastItem = array_pop($bids);
+
+                if (!empty($lastItem['google_bid_time'])) {
+                    $totalRenewalNum[] = $record->site_id;
+                }
+            }
+            $condition[] = [function ($query) use ($totalRenewalNum) {
+                $query->whereIn('id', $totalRenewalNum);
+            }];
+        }
+
+        if ($siteUserId = $request->input('siteUserId')) {
+            $siteIds = DB::table('user_has_sites')->where(['user_id' => $siteUserId])->pluck('site_id')->toArray();
+        }
+
+        $keyword = $request->input('keyword');
+
+        $builder = Site::query()->with(['users', 'sitePayment'])
+            ->where($condition ?? [])->where(function (Builder $builder) use ($keyword) {
+                if ($keyword) {
+                    $builder->where('domain', 'like', '%' . $keyword . '%')
+                        ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                }
+
+            })->whereJsonContains('contract_ids', "3");
+        if (isset($siteIds)) {
+            $builder = $builder->whereIn('id', $siteIds);
+        }
+
+        $sortName = $request->input('sortName');
+        $sortOrder = $request->input('sortOrder');
+
+        if ($sortName == 'statusTitle') {
+            $builder->orderBy('status', $sortOrder);
+        }
+
+        $sites = $builder->orderByDesc('id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $sites->items();
+
+        foreach ($items as $item) {
+
+            $item->designer = implode('-', $item->users->where('role_id', Role::TYPE_DESIGNER)->pluck('nickname')->toArray());
+            $item->web_front = implode('-', $item->users->where('role_id', Role::TYPE_WEB)->pluck('nickname')->toArray());
+            $item->bidder_title = implode('-', $item->users->where('role_id', Role::TYPE_BID)->pluck('nickname')->toArray());
+
+            $item->statusTitle = Site::STATUS_MAP[$item->status] ?? '';
+
+            $item->bid_give_fee = $item->sitePayment->bid_give_fee ?? '';
+            $bidGiveFee = floatval($item->bid_give_fee);
+            $gBid = $item->sitePayment->google_bid ?? [];
+            $totalFee = $bidGiveFee;
+            foreach ($gBid as $v) {
+                $tempFee = floatval($v['google_bid_fee'] ?? 0);
+                $totalFee += $tempFee;
+            }
+            $item->total_fee = $totalFee;
+
+            $item->bid_account_online_time = empty($item->sitePayment->bid_account_online_time) ?
+                '' : date('Y-m-d', strtotime($item->sitePayment->bid_account_online_time));
+            $item->bid_account_pause_time = empty($item->sitePayment->bid_account_pause_time) ? ''
+                : date('Y-m-d', strtotime($item->sitePayment->bid_account_pause_time));
+            $item->bid_score = $item->sitePayment->bid_score ?? '';
+
+
+            $item->sustain_days = '';
+            if ($item->bid_account_online_time && $item->bid_account_pause_time) {
+                $onlineAt = date_create($item->bid_account_online_time);
+                $pauseAt = date_create($item->bid_account_pause_time);
+                $diff = date_diff($pauseAt, $onlineAt);
+                $item->sustain_days = $diff->days;
+            }
+
+            $item->is_google_bid = in_array(Contract::GOOGLE_BID, $item->contract_ids ?? []) ? 1 : 0;
+        }
+
+        $res = collect($items);
+
+        if ($sortName == 'bid_account_online_time') {
+            $res = $res->sortByDesc('bid_account_online_time');
+        }
+        if ($sortName == 'bid_score') {
+            $res = $res->sortByDesc('bid_score');
+        }
+        if ($sortName == 'sustain_days') {
+            $res = $res->sortByDesc('sustain_days');
+        }
+
+        return response()->json([
+            'rows' => array_values($res->toArray()),
+            'total' => $sites->total()
+        ]);
+    }
+
+
+    //竞价仪表
+
+    public function dashboard()
+    {
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+        $thisMonth = date('Y-m-t 23:59:59');
+
+        //竞价总费用
+        $data['totalTotalFee'] = $this->totalFee();
+        //竞价总费用三个月
+        $data['totalFeeDiff'] = $this->totalFee2();
+        //客户续费总和
+        $data['totalAvgScore'] = $this->renew(1);
+        //客户续费三个月
+        $data['avgScoreDiff'] = $this->renew();
+        //续费个数
+        $data['totalRenewalNum'] = $this->renewal();
+        //续费个数三个月
+        $data['renewalNumDiff'] = $this->renewal2();
+        //账户总个数
+        $data['totalPpc'] = Site::query()->selectRaw('id,contract_ids')
+            ->whereJsonContains('contract_ids', "3")->count();
+        //账户三个月
+        $data['ppcDiff'] = $this->ppcDiff($threeMonthsAgo, $thisMonth);
+        //在投放个数
+        $data['totalRedPpc'] = Site::query()->selectRaw('id,contract_ids')
+            ->whereJsonContains('contract_ids', "3")->where('bid_status', 1)->count();
+        //在投放个数三个月
+        $data['redPpcDiff'] = $this->redPpcDiff($threeMonthsAgo, $thisMonth);
+        //未投放个数
+        $data['totalGrayPpc'] = Site::query()->whereIn('status', [2, 3, 5, 6, 7, 8, 9])->selectRaw('id,contract_ids')
+            ->whereJsonContains('contract_ids', "3")->where('bid_status', 0)->count();
+        //未投放个数三个月
+        $data['grayPpcDiff'] = $this->grayPpcDiff($threeMonthsAgo, $thisMonth);
+
+        //已暂停个数
+        $data['totalLineRedPpc'] = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+            ->whereNotNull('bid_account_pause_time')
+            ->whereJsonContains('contract_ids', "3")->count();
+        //已暂停个数三个月
+        $data['lineRedPpcDiff'] = $this->lineRedPpcDiff($threeMonthsAgo, $thisMonth);
+        //既没有暂停也没有充值过的项目
+        $totalRedPpc = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+            ->whereNull('bid_account_pause_time')
+            ->whereJsonContains('contract_ids', "3")->where('bid_status', 1)->get();
+        $num = 0;
+        foreach ($totalRedPpc as $key => $value) {
+            $result = json_decode($value->google_bid);
+            if (!empty($result)) {
+                foreach ($result as $kk => $vv) {
+                    if (empty($vv->google_bid_fee)) {
+                        $num++;
+                        break;
+                    }
+                }
+            } else {
+                $num++;
+            }
+        }
+
+        //续费率=已续费个数/(已投放个数 - 既没有暂停也没有充值过的项目)
+        $data['totalRenewalRate'] = number_format($data['totalRenewalNum'] / ($data['totalRedPpc'] - $num), 2);
+        //续费率三个月
+        $data['renewalRateDiff'] = $this->ratio();
+
+
+        $data['ppcPie'] = $this->pie();
+        $data['feeLine'] = $this->line();
+
+        return view('admin.bid.dashboard', $data);
+    }
+
+    public function renew($type = 0)
+    {
+        $list = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+                ->select('google_bid')
+                ->whereJsonContains('contract_ids', "3")
+                ->get()->toArray() ?? [];
+        $array = [];
+        foreach ($list as $key => $value) {
+            $array[] = json_decode($value['google_bid'], true);
+        }
+        $result = [];
+        foreach ($array as $key => $value) {
+
+            if (is_array($value)) {
+                foreach ($value as $kk => $vv) {
+                    if ($vv['google_bid_time'] && $vv['google_bid_fee']) {
+                        $vv['date'] = date('Y-m', strtotime($vv['google_bid_time']));
+                        $result[] = $vv;
+                    }
+                }
+            }
+        }
+
+        if ($type != 0) {
+            $num = 0;
+            foreach ($result as $key => $value) {
+                $num += (int)$value['google_bid_fee'];
+            }
+            return $num;
+        }
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+        $res = $this->arrayGroupBy($result, 'date');
+
+        $list1 = [];
+        foreach ($res as $key => $value) {
+            if (strtotime($key) >= strtotime($threeMonthsAgo)) {
+                $num = 0;
+                foreach ($value as $kk => $vv) {
+                    $num += (int)$vv['google_bid_fee'];
+                }
+                $array = [
+                    'date' => $key,
+                    'sum' => $num,
+                ];
+                $list1[] = $array;
+            }
+        }
+
+        sort($list1);
+
+        return [
+            'xAxis' => array_column($list1, 'date'),
+            'yAxis' => array_column($list1, 'sum'),
+        ];
+
+    }
+
+    public function lineRedPpcDiff($threeMonthsAgo, $thisMonth)
+    {
+        $list = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+                ->selectRaw('DATE_FORMAT(bid_account_pause_time,"%Y-%m") as date,count(*) as value')
+                ->whereBetween('bid_account_pause_time', [$threeMonthsAgo, $thisMonth])
+                ->whereJsonContains('contract_ids', "3")
+                ->groupBy('date')
+                ->get()->toArray() ?? [];
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'value'),
+        ];
+    }
+
+    public function grayPpcDiff($threeMonthsAgo, $thisMonth)
+    {
+        $list = Site::query()->selectRaw('DATE_FORMAT(online_at,"%Y-%m") as date,count(*) as value')
+                ->whereIn('status', [2, 3, 5, 6, 7, 8, 9])
+                ->whereBetween('online_at', [$threeMonthsAgo, $thisMonth])
+                ->whereJsonContains('contract_ids', "3")->where('bid_status', 0)
+                ->groupBy('date')
+                ->get()->toArray() ?? [];
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'value'),
+        ];
+    }
+
+    public function redPpcDiff($threeMonthsAgo, $thisMonth, $type = 0)
+    {
+        $list = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+                ->selectRaw('DATE_FORMAT(bid_account_online_time,"%Y-%m") as date,count(*) as value')
+                ->whereBetween('bid_account_online_time', [$threeMonthsAgo, $thisMonth])
+                ->whereJsonContains('contract_ids', "3")->where('bid_status', 1)
+                ->groupBy('date')
+                ->get()->toArray() ?? [];
+
+        if ($type == 1) {
+
+            $list = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+                    ->selectRaw('DATE_FORMAT(bid_account_online_time,"%Y-%m") as date,count(*) as value')
+                    ->whereBetween('bid_account_online_time', [$threeMonthsAgo, $thisMonth])
+                    ->whereNull('bid_account_pause_time')
+                    ->whereNull('google_bid_fee')
+                    ->whereJsonContains('contract_ids', "3")->where('bid_status', 1)
+                    ->groupBy('date')
+                    ->get()->toArray() ?? [];
+            return $list;
+        }
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'value'),
+        ];
+    }
+
+    public function ppcDiff($threeMonthsAgo, $thisMonth)
+    {
+        $list = Site::query()->selectRaw('DATE_FORMAT(order_at,"%Y-%m") as date,count(*) as value')
+                ->whereBetween('order_at', [$threeMonthsAgo, $thisMonth])
+                ->whereJsonContains('contract_ids', "3")
+                ->groupBy('date')
+                ->get()->toArray() ?? [];
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'value'),
+        ];
+    }
+
+    public function ratio()
+    {
+        $ratio = $this->renewal2(1);
+        //在投放个数三个月
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+        $thisMonth = date('Y-m-t 23:59:59');
+        $res = $this->redPpcDiff($threeMonthsAgo, $thisMonth, 1);
+
+        foreach ($ratio as $key => $value) {
+            foreach ($res as $kk => $vv) {
+                if ($value['date'] == $vv['date']) {
+                    $ratio[$key]['ratio'] = number_format($value['count'] / $vv['value'], 2);
+                }
+            }
+        }
+        sort($ratio);
+        return [
+            'xAxis' => array_column($ratio, 'date'),
+            'yAxis' => array_column($ratio, 'ratio'),
+        ];
+    }
+
+    //饼状图
+    private function pie()
+    {
+
+        $no = Site::query()->selectRaw('id,contract_ids')
+            ->whereJsonContains('contract_ids', "3")->where('bid_status', '<>', 1)->count();
+        $in = Site::query()->selectRaw('id,contract_ids')
+            ->whereJsonContains('contract_ids', "3")->where('bid_status', 1)->count();
+
+        $pause = Site::query()->join('sites_payment', 'sites.id', '=', 'sites_payment.site_id')
+            ->whereNotNull('bid_account_pause_time')
+            ->where('bid_status', '=', 1)->whereJsonContains('contract_ids', "3")->count();
+
+        return [
+            'xAxis' => ['待投放个数', '投放个数', '已暂停个数'],
+            'hybrid' => [
+                ['name' => '待投放个数', 'value' => $no],
+                ['name' => '投放个数', 'value' => $in],
+                ['name' => '已暂停个数', 'value' => $pause],
+            ]
+        ];
+    }
+
+    //折线图
+    private function line()
+    {
+        $monthScope = [];
+        $monthResult = [];
+        for ($i = 0; $i < 6; $i++) {
+            $monthScope[] = date('Y-m', strtotime('first day of -' . $i . ' month'));
+
+            $monthResult[date('Ym', strtotime('first day of -' . $i . ' month'))] = [
+                'begin' => strtotime(date('Y-m-d 00:00:00', strtotime('first day of -' . $i . ' month'))),
+                'end' => strtotime(date('Y-m-d 23:59:59', strtotime('last day of -' . $i . ' month'))),
+                'total_fee' => 0
+            ];
+        }
+
+        $where = sprintf("google_bid->'$[*].google_bid_time' REGEXP '%s'", implode("|", $monthScope));
+
+        $payRecords = SitePayment::query()->select(['site_id', 'google_bid'])->whereRaw($where)->get();
+
+        foreach ($payRecords as $item) {
+            $bids = $item->google_bid;
+
+            foreach ($bids as $val) {
+                if (!empty($val['google_bid_time'])) {
+                    $time = strtotime($val['google_bid_time']);
+                    foreach ($monthResult as &$v) {
+                        if ($time >= $v['begin'] && $time <= $v['end']) {
+                            $v['total_fee'] += floatval($val['google_bid_fee']);
+
+                        }
+                    }
+
+                }
+            }
+        }
+
+        return [
+            'xAxis' => array_reverse(array_keys($monthResult)),
+            'yAxis' => array_reverse(array_column($monthResult, 'total_fee'))
+        ];
+    }
+
+    //续费
+    private function renewal()
+    {
+        $siteIdList = Site::query()->whereJsonContains('contract_ids', "3")->pluck('id');
+        $payRecords = SitePayment::query()->whereIn('site_id', $siteIdList)->selectRaw('site_id,google_bid')->whereNotNull('google_bid')->get();
+
+        $totalRenewalNum = 0;
+        foreach ($payRecords as $record) {
+            $bids = $record->google_bid;
+            $lastItem = array_pop($bids);
+
+            if (!empty($lastItem['google_bid_time'])) {
+                $totalRenewalNum++;
+            }
+        }
+        return $totalRenewalNum;
+    }
+
+    //续费三个月
+    private function renewal2($type = 0)
+    {
+        $siteIdList = Site::query()->whereJsonContains('contract_ids', "3")->pluck('id');
+        $payRecords = SitePayment::query()->whereIn('site_id', $siteIdList)->select('site_id', 'google_bid')->whereNotNull('google_bid')->get();
+        $infos = [];
+        foreach ($payRecords as $record) {
+            foreach ($record->google_bid as $kk => $vv) {
+                $res = [
+                    'site_id' => $record->site_id,
+                    'google_bid_fee' => $vv['google_bid_fee'],
+                    'google_bid_time' => $vv['google_bid_time'],
+                ];
+                $infos[] = $res;
+            }
+        }
+
+        $array = [];
+        foreach ($infos as $kk => $vv) {
+            if (!empty($vv['google_bid_time'])) {
+                $vv['google_bid_date'] = date('Y-m', strtotime($vv['google_bid_time']));
+                $array[] = $vv;
+            }
+        }
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+
+        $argc = [];
+        $result = $this->arrayGroupBy($array, 'google_bid_date');
+
+        foreach ($result as $key => $value) {
+
+            //去掉每个月相同项目id
+            $array = $this->arrUniq($value, 'site_id');
+            foreach ($array as $kk => $vv) {
+                $argc[] = $vv;
+            }
+        }
+        $arrayGroupBy = $this->arrayGroupBy($argc, 'google_bid_date');
+
+        $list = [];
+        foreach ($arrayGroupBy as $key => $value) {
+            if (strtotime($key) >= strtotime($threeMonthsAgo)) {
+                $array = [
+                    'date' => $key,
+                    'count' => count($value),
+                ];
+                $list[] = $array;
+            }
+        }
+
+        sort($list);
+        if ($type != 0) {
+            return $list;
+        }
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'count'),
+        ];
+    }
+
+    //二维数组按照指定键值去重
+    function arrUniq($arr, $key)
+    {
+        $key_arr = [];
+        foreach ($arr as $k => $v) {
+            if (in_array($v[$key], $key_arr)) {
+                unset($arr[$k]);
+            } else {
+                $key_arr[] = $v[$key];
+            }
+        }
+        return $arr;
+    }
+
+    //二维数组GroupBy
+    function arrayGroupBy($arr, $key)
+    {
+        $grouped = array();
+        foreach ($arr as $value) {
+            $grouped[$value[$key]][] = $value;
+        }
+        if (func_num_args() > 2) {
+            $args = func_get_args();
+            foreach ($grouped as $key => $value) {
+                $parameter = array_merge($value, array_slice($args, 2, func_num_args()));
+                $grouped[$key] = call_user_func_array('array_group_by', $parameter);
+            }
+        }
+        return $grouped;
+    }
+
+    private function totalFee()
+    {
+        $totalRecords = Site::query()
+            ->whereJsonContains('contract_ids', "3")
+            ->where('bid_status', '=', 1)
+            ->with(['sitePayment' => function (\Illuminate\Database\Eloquent\Relations\HasOne $builder) {
+                $builder->selectRaw('site_id,bid_give_fee,google_bid');
+            }])->selectRaw('id,order_at')->get();
+
+        $totalTotalFee = 0;
+        foreach ($totalRecords as $record) {
+
+            $record->bid_give_fee = $record->sitePayment->bid_give_fee ?? '';
+            $bidGiveFee = floatval($record->bid_give_fee);
+            $gBid = $record->sitePayment->google_bid ?? [];
+            $totalFee = $bidGiveFee;
+            foreach ($gBid as $v) {
+                $tempFee = floatval($v['google_bid_fee'] ?? 0);
+                $totalFee += $tempFee;
+            }
+
+            $totalTotalFee += $totalFee;
+        }
+
+        return $totalTotalFee;
+    }
+
+
+    private function totalFee2()
+    {
+        $totalRecords = Site::query()
+            ->whereJsonContains('contract_ids', "3")
+            ->where('bid_status', '=', 1)
+            ->with(['sitePayment' => function (\Illuminate\Database\Eloquent\Relations\HasOne $builder) {
+                $builder->selectRaw('site_id,bid_give_fee,google_bid');
+            }])->selectRaw('id,order_at')->get();
+
+        $list = [];
+        foreach ($totalRecords as $record) {
+            $list[] = $record->sitePayment->google_bid;
+        }
+
+        $array = [];
+        foreach ($list as $key => $value) {
+
+            if (is_array($value)) {
+                foreach ($value as $kk => $vv) {
+
+                    if (!empty($vv['google_bid_time'])) {
+                        $vv['google_bid_date'] = date('Y-m', strtotime($vv['google_bid_time']));
+                        $array[] = $vv;
+                    }
+                }
+            }
+        }
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+
+        $list = [];
+        $result = $this->arrayGroupBy($array, 'google_bid_date');
+
+        foreach ($result as $key => $value) {
+            if (strtotime($key) >= strtotime($threeMonthsAgo)) {
+                $num = 0;
+                foreach ($value as $kk => $vv) {
+                    $num += (int)$vv['google_bid_fee'];
+                }
+                $array = [
+                    'date' => $key,
+                    'sum' => $num,
+                ];
+
+                $list[] = $array;
+            }
+        }
+        sort($list);
+
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'sum'),
+        ];
+    }
+
+    protected function avgScore()
+    {
+        $totalRecords = Site::query()->with(['sitePayment' => function (\Illuminate\Database\Eloquent\Relations\HasOne $builder) {
+            $builder->selectRaw('site_id,bid_score');
+        }])->selectRaw('id,order_at')->get();
+
+        $totalNum = 0;
+        $totalTotalScore = 0;
+        foreach ($totalRecords as $record) {
+            if (!empty($record->sitePayment->bid_score)) {
+                $totalNum++;
+                $totalTotalScore += floatval($record->sitePayment->bid_score);
+            }
+        }
+
+        $totalAvg = $totalNum == 0 ? 0 : $totalTotalScore / $totalNum;
+
+        return number_format($totalAvg, 2);
+    }
+
+    protected function avgScore2()
+    {
+        $totalRecords = Site::query()->with(['sitePayment' => function (\Illuminate\Database\Eloquent\Relations\HasOne $builder) {
+            $builder->selectRaw('site_id,bid_score,bid_score_time');
+        }])->selectRaw('id,order_at')->get();
+        $array = [];
+        foreach ($totalRecords as $record) {
+            if (!empty($record->sitePayment->bid_score)) {
+                $data = [
+                    'bid_score' => $record->sitePayment->bid_score,
+                    'bid_score_time' => $record->sitePayment->bid_score_time,
+                    'date' => date('Y-m', strtotime($record->sitePayment->bid_score_time)),
+                ];
+                $array[] = $data;
+            }
+        }
+
+        $threeMonthsAgo = date('Y-m-01 00:00:00', strtotime('first day of -2 month'));
+
+        $result = $this->arrayGroupBy($array, 'date');
+
+        $list = [];
+        foreach ($result as $key => $value) {
+            if (strtotime($key) >= strtotime($threeMonthsAgo)) {
+                $num = 0;
+                $sum = 0;
+                foreach ($value as $kk => $vv) {
+                    $num++;
+                    $sum += floatval($vv['bid_score']);
+                }
+                $average = $sum / $num;
+                $array = [
+                    'date' => $key,
+                    'average' => number_format($average, 2),
+                ];
+
+                $list[] = $array;
+            }
+        }
+        sort($list);
+
+        return [
+            'xAxis' => array_column($list, 'date'),
+            'yAxis' => array_column($list, 'average'),
+        ];
+    }
+
+    //上线账户
+    public function online(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin.bid.online');
+        }
+        $keyword = $request->input('keyword');
+        $status = $request->input('status');
+
+        $payIds = SitePayment::query()->whereNotNull('bid_account_online_time')->pluck('site_id')->toArray();
+        $records = Site::query()
+            ->where($condition ?? [])
+            ->where(function (Builder $builder) use ($keyword, $status) {
+                if ($keyword) {
+                    $builder->where('domain', 'like', '%' . $keyword . '%')
+                        ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                }
+                if ($status) {
+                    $builder->where('status', $status);
+                }
+            })
+            ->whereIn('id', $payIds)->with(['users', 'sitePayment'])
+            ->get();
+
+        foreach ($records as $record) {
+            $record->bidder_title = implode('-', $record->users->where('role_id', Role::TYPE_BID)->pluck('nickname')->toArray());
+            $record->bid_give_fee = $record->sitePayment->bid_give_fee ?? '';
+            $record->bid_account_online_time = empty($record->sitePayment->bid_account_online_time) ? '' : date('Y-m-d', strtotime($record->sitePayment->bid_account_online_time));
+        }
+
+        $res = collect($records->toArray())->sortByDesc('bid_account_online_time');
+
+        $perPage = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+        $result = $this->paginateCollection($res, $perPage);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total(),
+        ]);
+
+    }
+
+    //充值账户
+    public function recharge(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin.bid.recharge');
+        }
+
+        $pageSize = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+
+
+        $monthScope = [];
+        for ($i = 0; $i < 36; $i++) {
+            $monthScope[] = date('Y-m', strtotime('first day of -' . $i . ' month'));
+        }
+
+        $where = sprintf("google_bid->'$[*].google_bid_time' REGEXP '%s'", implode("|", $monthScope));
+
+        $site = SitePayment::query();
+
+        $keyword = $request->input('keyword');
+        $status = $request->input('status');
+        if (!empty($keyword)) {
+            $siteIds = Site::query()->where('domain', 'like', '%' . $keyword . '%')
+                ->orWhere('cn_title', 'like', '%' . $keyword . '%')
+                ->pluck('id');
+            $site->whereIn('site_id', $siteIds);
+        }
+        if (!empty($status)) {
+            $siteIds = Site::query()
+                ->where('status', $status)
+                ->pluck('id');
+            $site->whereIn('site_id', $siteIds);
+        }
+        if (!empty($keyword) && !empty($status)) {
+            $siteIds = Site::query()->where('domain', 'like', '%' . $keyword . '%')
+                ->orWhere('cn_title', 'like', '%' . $keyword . '%')
+                ->where('status', $status)
+                ->pluck('id');
+            $site->whereIn('site_id', $siteIds);
+        }
+
+
+        $payRecords = $site->whereNotNull('bid_account_online_time')->select(['site_id', 'google_bid'])->whereRaw($where)->get();
+
+        $results = [];
+        foreach ($payRecords as $record) {
+            $bids = $record->google_bid;
+            foreach ($bids as $item) {
+                if (strtotime($item['google_bid_time']) >= strtotime($monthScope[35])) {
+                    $results[] = [
+                        'site_id' => $record->site_id,
+                        'fee' => $item['google_bid_fee'],
+                        'time' => $item['google_bid_time'],
+                    ];
+                }
+            }
+        }
+        $siteIds = array_column($results, 'site_id');
+
+        $siteRecords = Site::query()
+            ->with(['users'])
+            ->selectRaw('id,domain,cn_title')
+            ->whereIn('id', $siteIds)->get();
+
+        foreach ($siteRecords as $item) {
+            $item->bidder_title = implode('-', $item->users->where('role_id', Role::TYPE_BID)->pluck('nickname')->toArray());
+            unset($item->users);
+        }
+
+        $mapRecords = $siteRecords->keyBy('id')->toArray();
+        foreach ($results as &$record) {
+            $record = array_merge($record, $mapRecords[$record['site_id']] ?? '');
+        }
+
+        $res = collect($results)->sortByDesc('time');
+        $result = $this->paginateCollection($res, $pageSize);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total(),
+        ]);
+    }
+
+    //暂停账户
+    public function pause(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin.bid.pause');
+        }
+
+        $payIds = SitePayment::query()->whereNotNull('bid_account_pause_time')->orderBy('bid_account_pause_time', 'desc')->pluck('site_id')->toArray();
+        $keyword = $request->input('keyword');
+        $status = $request->input('status');
+
+        $records = Site::query()
+            ->where(function (Builder $builder) use ($keyword, $status) {
+                if ($keyword) {
+                    $builder->where('domain', 'like', '%' . $keyword . '%')
+                        ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                }
+                if ($status) {
+                    $builder->where('status', $status);
+                }
+            })
+            ->whereIn('id', $payIds)->with(['users', 'sitePayment'])->get();
+
+        foreach ($records as $record) {
+            $record->bidder_title = implode('-', $record->users->where('role_id', Role::TYPE_BID)->pluck('nickname')->toArray());
+            $record->bid_give_fee = $record->sitePayment->bid_give_fee ?? '';
+            $record->bid_account_pause_time = $record->sitePayment->bid_account_pause_time ?? '';
+
+            $record->bid_give_fee = $record->sitePayment->bid_give_fee ?? '';
+            $bidGiveFee = floatval($record->bid_give_fee);
+            $gBid = $record->sitePayment->google_bid ?? [];
+            $totalFee = $bidGiveFee;
+            foreach ($gBid as $v) {
+                $tempFee = floatval($v['google_bid_fee'] ?? 0);
+                $totalFee += $tempFee;
+            }
+            $record->total_fee = $totalFee;
+        }
+        $res = collect($records->toArray())->sortByDesc('bid_account_pause_time');
+
+        $perPage = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+        $result = $this->paginateCollection($res, $perPage);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => count($res),
+        ]);
+    }
+
+    //客户评分
+    public function score(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin.bid.score');
+        }
+
+        $payIds = SitePayment::query()->whereNotNull('bid_score_time')->orderBy('bid_score_time', 'desc')->pluck('site_id');
+
+        $keyword = $request->input('keyword');
+        $status = $request->input('status');
+
+        $records = Site::query()
+            ->whereIn('id', $payIds)
+            ->with(['users', 'sitePayment'])
+            ->where(function (Builder $builder) use ($keyword, $status) {
+                if ($keyword) {
+                    $builder->where('domain', 'like', '%' . $keyword . '%')
+                        ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                }
+                if ($status) {
+                    $builder->where('status', $status);
+                }
+            })->get();
+
+        foreach ($records as $record) {
+            $record->bidder_title = implode('-', $record->users->where('role_id', Role::TYPE_BID)->pluck('nickname')->toArray());
+            $record->bid_score = $record->sitePayment->bid_score ?? '';
+            $record->bid_score_time = $record->sitePayment->bid_score_time ?? '';
+        }
+
+        $res = collect($records->toArray())->sortByDesc('score');
+        $perPage = $request->input('pageSize') ?? TABLE_PAGE_SIZE;
+        $result = $this->paginateCollection($res, $perPage);
+
+        return response()->json([
+            'rows' => $result->items(),
+            'total' => $result->total(),
+        ]);
+
+    }
+
+}

+ 404 - 0
app/Http/Controllers/Admin/BqTrafficController.php

@@ -0,0 +1,404 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2020/1/7 0007
+ * Time: 13:19
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\BqFlow;
+use App\Http\Models\BqFlowInfo;
+use App\Http\Models\FlowInfoTpl;
+use App\Http\Models\OptimizationFlow;
+use App\Http\Models\OptimizationFlowInfo;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+
+class BqTrafficController extends Controller
+{
+    //百千计划
+    public function index(Request $request, $siteId)
+    {
+        $clientInquiryStatistics = DB::table('clientInquiry_statistics')->where('site_id', $siteId)->first();
+        if (empty(!$clientInquiryStatistics)) {
+            $clientInquiryStatisticsList = \GuzzleHttp\json_decode($clientInquiryStatistics->clientInquiry_list, true);
+        }
+
+        $item = Site::query()
+            ->where('id', $siteId)->where('is_bq', 1)
+            ->whereIn('status', [2, 3])->first();
+
+        if (empty($item)) {
+            return view('admin/site/tips', [
+                'siteId' => $siteId,
+                'tips' => '此项目暂无百千计划'
+            ]);
+        }
+        //询盘不读这里这个
+        $listReportMap = DB::connection('rank')->table('project_listreport')
+            ->select('Ym', 'traffic', 'inquire')
+            ->where('project_id', $item->old_id)
+            ->get()->toArray();
+
+        //百千约访时间
+        $bqAt = date('Y-m-d', strtotime($item->bq_at));
+        $currentMonth = date('Y-m-d', strtotime('-2 Month', strtotime($bqAt)));
+
+        $bqAtList = [];
+        for ($i = 1; $i < 13; $i++) {
+            $bqAtList[] = date('Ym', strtotime("+{$i} Month", strtotime($currentMonth)));
+        }
+        $trafficList = [];
+        $inquireList = [];
+
+        foreach ($bqAtList as $key => $value) {
+            $trafficList[$key]['date'] = $value;
+            $trafficList[$key]['traffic'] = '';
+        }
+
+        foreach ($bqAtList as $key => $value) {
+            $inquireList[$key]['date'] = $value;
+            $inquireList[$key]['inquire'] = '';
+        }
+        //实时询盘
+        $realInquireList = $this->getSiteInquireList($item);
+        if (!empty($realInquireList)) {
+            foreach ($inquireList as $key => $value) {
+                foreach ($realInquireList as $kk => $vv) {
+                    if ($value['date'] == $vv->date) {
+                        $inquireList[$key]['inquire'] = $vv->sum;
+                    }
+                }
+            }
+        }
+
+        foreach ($listReportMap as $key => $value) {
+            $item->traffic = '';
+            $item->inquire = '';
+
+            foreach ($trafficList as $kk => $vv) {
+
+                if ($value->Ym == $vv['date']) {
+                    $trafficList[$kk]['traffic'] = $value->traffic;
+                }
+                if ($kk == 0) {
+                    $item->traffic = $value->traffic;
+                }
+            }
+
+            if ($item->reach_300_at && $item->online_at) {
+                $one = date_create($item->online_at);
+                $two = date_create($item->reach_300_at);
+                $diff = date_diff($one, $two);
+                $item->duration_300 = $diff->days;
+            }
+
+            if ($item->reach_500_at && $item->online_at) {
+                $one = date_create($item->online_at);
+                $two = date_create($item->reach_500_at);
+                $diff = date_diff($one, $two);
+                $item->duration_500 = $diff->days;
+            }
+
+            if ($item->reach_1000_at && $item->online_at) {
+                $one = date_create($item->online_at);
+                $two = date_create($item->reach_1000_at);
+                $diff = date_diff($one, $two);
+                $item->duration_1000 = $diff->days;
+            }
+
+            if ($item->reach_1500_at && $item->online_at) {
+                $one = date_create($item->online_at);
+                $two = date_create($item->reach_1500_at);
+                $diff = date_diff($one, $two);
+                $item->duration_1500 = $diff->days;
+            }
+
+            if ($item->reach_2000_at && $item->online_at) {
+                $one = date_create($item->online_at);
+                $two = date_create($item->reach_2000_at);
+                $diff = date_diff($one, $two);
+                $item->duration_2000 = $diff->days;
+            }
+        }
+
+
+        //百千约访流量
+        $trafficTable = [];
+        foreach ($trafficList as $key => $value) {
+            $trafficTable['xAxis'][] = $value['date'];
+            $trafficTable['yAxis'][] = $value['traffic'];
+        }
+
+        //百千约访后台询盘
+        $inquireTable = [];
+        foreach ($inquireList as $key => $value) {
+            $inquireTable['xAxis'][] = $value['date'];
+            $inquireTable['yAxis'][] = $value['inquire'];
+        }
+
+        $result[] = $item->toArray();
+        $resList = collect($result);
+
+        $clientInquireList = [];
+        foreach ($bqAtList as $key => $value) {
+            $clientInquireList[$key]['date'] = $value;
+            $clientInquireList[$key]['clientInquire'] = '';
+        }
+
+        $clientInquireList1 = $clientInquireList;
+        if (!empty($clientInquiryStatisticsList)) {
+            $clientInquireList1 = $clientInquiryStatisticsList;
+            //百千约访客户询盘
+            $clientInquireList2 = [];
+            foreach ($clientInquiryStatisticsList as $key => $value) {
+                $clientInquireList2['xAxis'][] = $value['date'];
+                $clientInquireList2['yAxis'][] = $value['clientInquire'];
+            }
+        } else {
+            //百千约访客户询盘
+            $clientInquireList2 = [];
+            foreach ($bqAtList as $key => $value) {
+                $clientInquireList2['xAxis'][] = '';
+                $clientInquireList2['yAxis'][] = '';
+            }
+        }
+
+        if (!$request->ajax()) {
+            return view('admin/bqTraffic/index', [
+                'siteId' => $siteId,
+                'siteInfo' => $item,
+                'trafficList' => $trafficTable,
+                'inquireList' => $inquireTable,
+                'clientInquireList' => $clientInquireList2,
+                'clientInquireDate' => $clientInquireList1
+            ]);
+        }
+
+        return response()->json([
+            'rows' => $resList ?? [],
+            'total' => count($result) ?? 0,
+        ]);
+    }
+
+
+    public function saveClientInquiry(Request $request, $siteId)
+    {
+        $data = $request->all();
+        $clientInquiryStatisticsList1 = [];
+
+        $array = [];
+        foreach ($data as $key => $value) {
+            $clientInquiryStatisticsList1['date'] = $key;
+            $clientInquiryStatisticsList1['clientInquire'] = $value;
+            $array[] = $clientInquiryStatisticsList1;
+        }
+
+        $result = DB::table('clientInquiry_statistics')->where('site_id', $siteId)->first();
+
+        if (empty($result)) {
+            DB::table('clientInquiry_statistics')->insert(['clientInquiry_list' => json_encode($array), 'site_id' => $siteId]);
+        } else {
+            DB::table('clientInquiry_statistics')->where('site_id', $siteId)->update(['clientInquiry_list' => json_encode($array)]);
+        }
+
+        return $this->success('success' . $siteId);
+    }
+
+    public function getSiteInquireList($site)
+    {
+        $config = [
+            'connection_name' => sprintf('connection_name_%s', $site->id),
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => $site->database,
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd,
+        ];
+        config_connection($config);
+
+        $list = DB::connection($config['connection_name'])->table('user_msg')
+                ->selectRaw('FROM_UNIXTIME(create_time, "%Y%m") as date, count(*) as count')
+                ->groupBy('date')
+                ->get()->toArray() ?? [];
+
+        if (!empty($list)) {
+            foreach ($list as $key => $value) {
+                $num = 0;
+                foreach ($list as $kk => $vv) {
+                    if (strtotime($value->date) >= strtotime($vv->date)) {
+                        $num += $vv->count;
+                    }
+                }
+                $list[$key]->sum = $num;
+            }
+        }
+        return $list;
+    }
+
+    //百千进度流程
+    public function bqProcess($siteId)
+    {
+        $tplStageList = BqFlow::query()->where('site_id', $siteId)->with('bqFlowInfo')->get()->toArray();
+
+        return view('admin.bqTraffic.bq_process', [
+            'siteId' => $siteId,
+            'tplStageList' => $tplStageList ?? [],
+            'userList' => $this->getFlowUserList($siteId),
+        ]);
+    }
+
+    //百千进度模版
+    public function bqProcessTpl($siteId)
+    {
+        $tplStageList = BqFlow::query()->where('site_id', 0)->with('bqFlowInfo')->get()->toArray();
+        return view('admin.bqTraffic.bq_process_tpl', [
+            'siteId' => $siteId,
+            'tplStageList' => $tplStageList,
+        ]);
+    }
+
+    //初始化模版
+    public function bqProcessReset($siteId)
+    {
+        try {
+            DB::transaction(function () use ($siteId) {
+                BqFlow::query()->where('site_id', $siteId)->delete();
+                BqFlowInfo::query()->where('site_id', $siteId)->delete();
+
+                $tplStageList = BqFlow::query()->where('site_id', 0)->with('bqFlowInfo')->get()->toArray();
+
+                $result = [];
+                foreach ($tplStageList as $item) {
+
+                    $data = [
+                        'site_id' => $siteId,
+                        'title' => $item['title'],
+                        'created_at' => date('Y-m-d H:i:s')
+                    ];
+                    $bqFlowId = BqFlow::query()->insertGetId($data);
+
+                    foreach ($item['bq_flow_info'] as $value) {
+                        $result[] = [
+                            'site_id' => $siteId,
+                            'stage_id' => $bqFlowId,
+                            'detail_list' => \GuzzleHttp\json_encode($value['detail_list']),
+                            'created_at' => date('y-m-d H:i:s'),
+                        ];
+                    }
+                }
+                BqFlowInfo::query()->insert($result);
+            });
+            return $this->success('success');
+
+        } catch (\Throwable $exception) {
+            return response()->json(['message' => $exception->getMessage()], 400);
+        }
+    }
+
+    public function bqProcessSave(Request $request, $siteId)
+    {
+        $dataList = $request->input('dataList') ?? [];
+        if (!empty($dataList)) {
+            try {
+                DB::transaction(function () use ($dataList, $siteId) {
+
+                    BqFlow::query()->where('site_id', $siteId)->delete();
+
+                    $data = [];
+                    foreach ($dataList as $key => $value) {
+
+                        $title = [
+                            'site_id' => $siteId,
+                            'title' => $value['step_title'],
+                            'expected_date' => $value['expected_date'] ?? null,
+                            'created_at' => date('Y-m-d H:i:s'),
+                        ];
+                        $id = BqFlow::query()->insertGetId($title);
+                        $data[] = [
+                            'detail_list' => json_encode($value['children']),
+                            'site_id' => $siteId,
+                            'created_at' => date('Y-m-d H:i:s'),
+                            'stage_id' => $id,
+                        ];
+                    }
+                    BqFlowInfo::query()->where('site_id', $siteId)->delete();
+                    BqFlowInfo::query()->insert($data);
+                });
+            } catch (\Throwable $exception) {
+                return response()->json(['message' => $exception->getMessage()], 400);
+            }
+        } else {
+            BqFlow::query()->where('site_id', $siteId)->delete();
+            BqFlowInfo::query()->where('site_id', $siteId)->delete();
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function optimizationProcess($siteId)
+    {
+        $tplStageList = OptimizationFlow::query()->where('site_id', $siteId)->with('optimizationFlowInfoList')->get()->toArray();
+        return view('admin.bqTraffic.optimization_process', [
+            'siteId' => $siteId,
+            'tplStageList' => $tplStageList,
+        ]);
+    }
+
+    public function optimizationProcessSave(Request $request, $siteId)
+    {
+        $dataList = $request->input('dataList') ?? [];
+        if (!empty($dataList)) {
+            try {
+                DB::transaction(function () use ($dataList, $siteId) {
+
+                    OptimizationFlow::query()->where('site_id', $siteId)->delete();
+                    $data = [];
+                    foreach ($dataList as $key => $value) {
+                        $title = [
+                            'site_id' => $siteId,
+                            'title' => $value['step_title'],
+                            'created_at' => date('Y-m-d H:i:s'),
+                        ];
+                        $id = OptimizationFlow::query()->insertGetId($title);
+                        $data[] = [
+                            'detail_list' => json_encode($value['children']),
+                            'site_id' => $siteId,
+                            'created_at' => date('Y-m-d H:i:s'),
+                            'stage_id' => $id,
+                        ];
+                    }
+                    OptimizationFlowInfo::query()->where('site_id', $siteId)->delete();
+                    OptimizationFlowInfo::query()->insert($data);
+                });
+            } catch (\Throwable $exception) {
+                return response()->json(['message' => $exception->getMessage()], 400);
+            }
+        } else {
+            OptimizationFlow::query()->where('site_id', $siteId)->delete();
+            OptimizationFlowInfo::query()->where('site_id', $siteId)->delete();
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function getFlowUserList($siteId)
+    {
+        $roleScope = array_keys(FlowInfoTpl::RoleScope);
+        unset($roleScope[1]); //删除客户
+
+        $userList = User::query()->select(['id', 'nickname', 'role_id'])->whereIn('role_id', $roleScope)
+            ->get()->toArray();
+        $site = Site::query()->select(['cn_title'])->find($siteId);
+        $userList[] = ['id' => -1, 'role_id' => -1, 'nickname' => $site->cn_title ?? '站点名称'];
+
+        return $userList;
+    }
+
+}

+ 185 - 0
app/Http/Controllers/Admin/ClassroomController.php

@@ -0,0 +1,185 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 大课堂
+ * Class WorkTaskController
+ * @package App\Http\Controllers\Admin
+ */
+class ClassroomController extends Controller
+{
+    public function questionsAndAnswers(Request $request)
+    {
+        $model = DB::table('questions_and_answers');
+        if (!$request->ajax()) {
+            return view('admin/classroom/questions_and_answers');
+        }
+        $pageSize = $request->input('pageSize') ?? 10;
+        return $this->getList($model, $pageSize);
+    }
+
+    public function tool(Request $request)
+    {
+        $model = DB::table('tool');
+        if (!$request->ajax()) {
+            return view('admin/classroom/tool');
+        }
+        $pageSize = $request->input('pageSize') ?? 10;
+        return $this->getList($model, $pageSize);
+    }
+
+    public function socialMedia(Request $request)
+    {
+        $model = DB::table('social_media');
+        if (!$request->ajax()) {
+            return view('admin/classroom/social_media');
+        }
+        $pageSize = $request->input('pageSize') ?? 10;
+        return $this->getList($model, $pageSize);
+    }
+
+    public function video(Request $request)
+    {
+        $model = DB::table('video');
+        if (!$request->ajax()) {
+            return view('admin/classroom/video');
+        }
+        return $this->getList($model, $request->input('pageSize') ?? 10);
+    }
+
+    public function questionsAndAnswersUpdate(Request $request, $id = 0)
+    {
+        $model = DB::table('questions_and_answers');
+        if (!empty($id)) {
+            $info = $model->where('id', $id)->first();
+        }
+        if (!$request->ajax()) {
+            return view('admin/classroom/questions_and_answers_update', [
+                'info' => $info ?? []
+            ]);
+        }
+        $rules = [
+            'title' => 'required|max:255',
+            'content' => 'required',
+        ];
+        $message = [
+            'title.required' => '标题不能为空',
+            'content.required' => '内容不能为空',
+            'title.max' => '标题不能超过255个字符',
+        ];
+        return $this->verification($request->all(), $rules, $message, $model, $id);
+    }
+
+    public function questionsAndAnswersDelete($id = 0)
+    {
+        $model = DB::table('questions_and_answers');
+        return $this->deleteInfo($model, $id);
+    }
+
+    public function toolUpdate(Request $request, $id = 0)
+    {
+        $model = DB::table('tool');
+        if (!empty($id)) {
+            $info = $model->where('id', $id)->first();
+        }
+        if (!$request->ajax()) {
+            return view('admin/classroom/tool_update', [
+                'info' => $info ?? []
+            ]);
+        }
+        $rules = [
+            'title' => 'required|max:255',
+            'content' => 'required|max:255',
+            'image' => 'required',
+            'details' => 'required',
+        ];
+        $message = [
+            'title.required' => '标题不能为空',
+            'content.required' => '内容不能为空',
+            'image.required' => '图片不能为空',
+            'details.required' => '详情不能为空',
+            'title.max' => '标题不能超过255个字符',
+            'content.max' => '内容不能超过255个字符',
+        ];
+        return $this->verification($request->all(), $rules, $message, $model, $id);
+    }
+
+    public function toolDelete($id = 0)
+    {
+        $model = DB::table('tool');
+        return $this->deleteInfo($model, $id);
+    }
+
+    public function socialMediaUpdate(Request $request, $id = 0)
+    {
+        $model = DB::table('social_media');
+        if (!empty($id)) {
+            $info = $model->where('id', $id)->first();
+        }
+        if (!$request->ajax()) {
+            return view('admin/classroom/social_media_update', [
+                'info' => $info ?? []
+            ]);
+        }
+        $rules = [
+            'title' => 'required|max:255',
+            'content' => 'required|max:255',
+            'image' => 'required',
+            'details' => 'required',
+        ];
+        $message = [
+            'title.required' => '标题不能为空',
+            'content.required' => '内容不能为空',
+            'image.required' => '图片不能为空',
+            'details.required' => '详情不能为空',
+            'title.max' => '标题不能超过255个字符',
+            'content.max' => '内容不能超过255个字符',
+        ];
+        return $this->verification($request->all(), $rules, $message, $model, $id);
+    }
+
+    public function socialMediaDelete($id = 0)
+    {
+        $model = DB::table('social_media');
+        return $this->deleteInfo($model, $id);
+    }
+
+    public function videoUpdate(Request $request, $id = 0)
+    {
+        $model = DB::table('video');
+        if (!empty($id)) {
+            $info = $model->where('id', $id)->first();
+        }
+        if (!$request->ajax()) {
+            return view('admin/classroom/video_update', [
+                'info' => $info ?? []
+            ]);
+        }
+        $rules = [
+            'title' => 'required|max:255',
+            'user' => 'required|max:255',
+            'image' => 'required',
+            'video' => 'required|max:255',
+        ];
+        $message = [
+            'title.required' => '标题不能为空',
+            'user.required' => '发布者姓名不能为空',
+            'image.required' => '图片不能为空',
+            'video.required' => '视频链接不能为空',
+            'title.max' => '标题不能超过255个字符',
+            'user.max' => '发布者姓名不能超过255个字符',
+        ];
+        return $this->verification($request->all(), $rules, $message, $model, $id);
+    }
+
+    public function videoDelete($id = 0)
+    {
+        $model = DB::table('video');
+        return $this->deleteInfo($model, $id);
+    }
+}

+ 212 - 0
app/Http/Controllers/Admin/CustomerUserController.php

@@ -0,0 +1,212 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/4/9 0009
+ * Time: 14:05
+ */
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Http\Models\Permission;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use App\Http\Requests\CustomerUser\CustomerUserSaveRequest;
+use App\Http\Traits\HasSites;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 客户用户管理
+ * Class CustomerUserController
+ * @package App\Http\Controllers\Admin
+ */
+class CustomerUserController extends Controller
+{
+    use HasSites;
+
+    //用户列表
+    public function index(Request $request)
+    {
+
+        $oneSite = $this->hasUserOneSite();
+        if (!$request->ajax()) {
+            return view('admin/customer_user/user', [
+                'roles' => Role::all()
+            ]);
+        }
+
+
+        if ($keyword = $request->input('keyword')) {
+            $filter[] = ['username', 'like', '%' . $keyword . '%'];
+        }
+
+        $filter[] = ['role_id', '=', Role::TYPE_TYPE_CUSTOMER_STAFF];
+
+        $select = ['id', 'username', 'email', 'created_at', 'role_id', 'status', 'is_super', 'nickname'];
+        $roles = User::query()->with('role')->select($select)
+            ->where($filter ?? [])->whereHas('sites', function (Builder $builder) use ($oneSite) {
+                $builder->where('id', $oneSite->id);
+            })->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+
+
+        $items = $roles->items();
+
+        array_walk($items, function ($item) {
+            $item->role_name = '客户雇员';
+            $item->status_title = $item->status_with_css;
+            unset($item->role);
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $roles->total()
+        ]);
+    }
+
+    //用户权限
+    public function userPermission(Request $request)
+    {
+
+        $userIds = $request->input('userIds');
+        if (!$request->ajax()) {
+
+            $selects = [];
+            if (count($userIds) == 1) {
+                $user = User::query()->where(['id' => $userIds[0]])->first();
+                if (!$user) {
+                    return response()->json(['message' => '参数错误'], 400);
+                }
+                $selects = $user->permissions->pluck('id')->toArray();
+            }
+
+
+            /*  $nowUser = auth()->user();
+              $permissions = $nowUser->permissions()
+                  ->select(['title as name', 'id', 'parent_id', 'type'])->get()->toArray();*/
+
+            $permissionsIds = DB::table('role_has_permissions')->where('role_id', Role::TYPE_CUSTOMER)->pluck('permission_id');
+            $permissions = Permission::query()->whereIn('id', $permissionsIds)->get()->toArray();
+
+            array_walk($permissions, function (&$item) use ($selects) {
+                $item['open'] = true;
+                $item['name'] = $item['title'] . ($item['type'] == 1 ? '【菜单】' : '【功能】');
+                if (in_array($item['id'], $selects)) {
+                    $item['checked'] = true;
+                }
+            });
+            $trees = list_to_tree($permissions, 'id', 'parent_id', 'children');
+            return view('admin/customer_user/user_permission', [
+                'trees' => $trees,
+                'userIds' => $userIds
+            ]);
+        }
+
+        $permissionIds = $request->input('permissionIds');
+        $records = User::query()->whereIn('id', $userIds)->get();
+        foreach ($records as $record) {
+            $record->permissions()->sync($permissionIds);
+        }
+        return response()->json(['message' => '操作成功']);
+
+
+    }
+
+    //保存
+    public function store(CustomerUserSaveRequest $request)
+    {
+        $validated = $request->validated();
+
+        $validated['password'] = bcrypt($validated['password']);
+        $validated['profile_img'] = asset('img/social_round_github_64px_1196568_easyicon.net.png');
+
+        $validated['role_id'] = Role::TYPE_TYPE_CUSTOMER_STAFF;//公司雇员
+        $user = User::query()->create($validated);
+        /**@var  \App\Http\Models\User $user * */
+
+        $oneSite = $this->hasUserOneSite();
+        if (!$oneSite) {
+            return response()->json(['message' => '当前用户未分配站点'], 400);
+        }
+
+        $user->sites()->sync([$oneSite->id]);
+
+        $nowUserPermissions = auth()->user()->permissions->pluck('id')->toArray();
+        //剔除用户管理的权限
+        $searchIndex = array_search(39, $nowUserPermissions);
+        if ($searchIndex !== false) {
+            unset($nowUserPermissions[$searchIndex]);
+        }
+
+        $user->permissions()->sync(array_values($nowUserPermissions));
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //修改
+    public function update(CustomerUserSaveRequest $request, $id)
+    {
+        $user = User::query()->where(['id' => $id])->first();
+        /**@var  \App\Http\Models\User $user * */
+        if (!$user) return response()->json(['message' => '数据不存在']);
+        $validated = $request->validated();
+
+        $user->update($validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function show($id)
+    {
+        if ($id > 0) {
+            $user = User::query()->with('sites')->where(['id' => $id])->first();
+            $hasSiteIds = $user->sites->pluck('id')->toArray(); //用户所关联的站点
+        }
+        return view('admin/customer_user/user_show', [
+            'user' => $user ?? null,
+            'roles' => Role::query()->select(['id', 'name'])->get(),
+            'sites' => Site::all(),
+            'hasSiteIds' => $hasSiteIds ?? []
+        ]);
+    }
+
+    public function detail($id)
+    {
+        $user = User::query()->select()->find($id);
+        return view('/admin/customer_user/user_detail', [
+            'user' => $user
+        ]);
+    }
+
+    public function destroy($id)
+    {
+        User::destroy($id);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function batchDestroy(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //用户状态切换 启用
+    public function on(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::query()->whereIn('id', $ids)->update(['status' => 1]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //用户状态切换 禁用
+    public function off(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::query()->whereIn('id', $ids)->update(['status' => 0]);
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 77 - 0
app/Http/Controllers/Admin/Enquiry/EnquiryController.php

@@ -0,0 +1,77 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: vanshao
+ * Date: 2019-04-17
+ * Time: 09:24
+ */
+namespace App\Http\Controllers\Admin\Enquiry;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Enquiry;
+use Illuminate\Http\Request;
+
+//use App\Http\Requests\Request;
+
+class EnquiryController extends Controller{
+
+    public function __construct() {
+    }
+
+
+    public function list(Request $request){
+
+    if(!$request->ajax()){
+        return view('admin/enquiry/enquiry');
+    }
+        $keyword = $request->input('keyword');
+        $builder = Enquiry::query();
+//        if ($keyword) {
+//            $builder->where('domain', 'like', '%' . $keyword . '%');
+//            $builder->orWhere('cn_title', 'like', '%' . $keyword . '%');
+//            $builder->orWhere('en_title', 'like', '%' . $keyword . '%');
+//        }
+
+        $sites = $builder->orderByDesc('created_at')->paginate();
+        $items = $sites->items();
+        array_walk($items, function ($item) {
+            $item->tx_status_title=$item->status_title;
+            $item->tx_type_title=$item->type_title;
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $sites->total()
+        ]);
+
+    }
+
+    public function detail($id)
+    {
+        $user = Enquiry::query()->select()->find($id);
+        $data['status']=2;
+        $user->update($data);
+        return view('/admin/enquiry/enquiry_detail', [
+            'user' => $user
+        ]);
+    }
+
+    public function destroy(Request $request)
+    {
+        $id=$request->input('id');
+        Enquiry::destroy($id);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 批量删除
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function batchDestroy(Request $request)
+    {
+        $ids = $request->input('ids');
+        Enquiry::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+}

+ 354 - 0
app/Http/Controllers/Admin/FinanceController.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Agent;
+use App\Http\Models\Invoice;
+use App\Http\Models\LadingBill;
+use App\Http\Models\Site;
+use App\Http\Models\SitePayment;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 财务 之前提的需求 基本没啥用了
+ * Class FinanceController
+ * @package App\Http\Controllers\Admin
+ */
+class FinanceController extends Controller
+{
+    public function Save($id)
+    {
+        $sites_info =Site::query()->find($id);
+        $site_payment=SitePayment::query()->where('site_id','=',$id)->first();
+        $data = [
+            'cn_title'=>$sites_info->cn_title,
+        ];
+
+        return view('admin/finance/save', [
+            'data' => $sites_info,
+            'payment'=>$site_payment
+        ]);
+    }
+    /**
+     * 逾期未收款报表
+     * @param Request $request
+     * @return array|\Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\Http\JsonResponse|\Illuminate\View\View|mixed
+     */
+    public function finances_overdue (Request $request)
+    {
+
+        $total='0';
+        $filters='';
+        $inputs = $request->input();
+        $keyword = $request->input('keyword');
+
+        if (!$request->ajax()) {
+            return view('admin/finance/finances_receivables', [
+                'sum_total' => $total
+            ]);
+        }
+        $keyword = $request->input('keyword');
+        $type_status =$request->input('type_status');
+        $builder = Site::query()->with('sitePayment')->whereIn('status', [1, 2, 3, 4, 5, 6, 8, 9, 10]);
+        if ($keyword) {
+
+            $siteIds = Site::query()->where('cn_title', 'like', '%' . $keyword . '%')
+                ->orWhere('domain', 'like', '%' . $keyword . '%')
+                ->pluck('id')->toArray();
+            $builder->WhereIn('id', $siteIds);
+        }
+        if ($type_status) {
+            if($type_status=='1'){
+                $siteIds = SitePayment::query()->where('head_type', '!=', '2')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='2'){
+                $siteIds = SitePayment::query()->where('done_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='3'){
+                $siteIds = SitePayment::query()->where('reach_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='4'){
+                $siteIds = SitePayment::query()->where('service_final_payment_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='5'){
+                $siteIds = SitePayment::query()->where('renewal_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }
+
+            $builder->WhereIn('id', $siteIds);
+        }
+
+
+
+        $result = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        foreach($result as $key=>$value){
+            $sit_pay_info = SitePayment::query()->where('site_id','=',$value->id)->first();
+            $value->contract_total_fee=$sit_pay_info->contract_total_fee;
+            $value->wiki_fee=$sit_pay_info->wiki_fee;
+            $value->other_fee=$sit_pay_info->other_fee;
+            $value->status_title=Site::STATUS_MAP[$value->status]?? '';
+        }
+        $items = $result->items();
+
+
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $result->total(),
+            'sum_total'=>$total
+        ]);
+    }
+    /**
+     * 应收款报表
+     * @param Request $request
+     * @return array|\Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\Http\JsonResponse|\Illuminate\View\View|mixed
+     */
+    public function finances_receivables(Request $request)
+    {
+
+        $total='0';
+        $filters='';
+        $inputs = $request->input();
+        $keyword = $request->input('keyword');
+
+        if (!$request->ajax()) {
+            return view('admin/finance/finances_receivables', [
+                'sum_total' => $total
+            ]);
+        }
+        $keyword = $request->input('keyword');
+        $type_status =$request->input('type_status');
+        $builder = Site::query()->with('sitePayment')->whereIn('status', [1, 2, 3, 4, 5, 6, 8, 9, 10]);
+        if ($keyword) {
+
+            $siteIds = Site::query()->where('cn_title', 'like', '%' . $keyword . '%')
+                ->orWhere('domain', 'like', '%' . $keyword . '%')
+                ->pluck('id')->toArray();
+            $builder->WhereIn('id', $siteIds);
+        }
+        if ($type_status) {
+            if($type_status=='1'){
+                $siteIds = SitePayment::query()->where('head_type', '!=', '2')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='2'){
+                $siteIds = SitePayment::query()->where('done_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='3'){
+                $siteIds = SitePayment::query()->where('reach_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='4'){
+                $siteIds = SitePayment::query()->where('service_final_payment_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }else if($type_status=='5'){
+                $siteIds = SitePayment::query()->where('renewal_type', '!=', '1')
+                    ->pluck('site_id')->toArray();
+            }
+
+            $builder->WhereIn('id', $siteIds);
+        }
+
+
+
+        $result = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        foreach($result as $key=>$value){
+            $sit_pay_info = SitePayment::query()->where('site_id','=',$value->id)->first();
+            $value->contract_total_fee=$sit_pay_info->contract_total_fee;
+            $value->wiki_fee=$sit_pay_info->wiki_fee;
+            $value->other_fee=$sit_pay_info->other_fee;
+            $value->status_title=Site::STATUS_MAP[$value->status]?? '';
+        }
+        $items = $result->items();
+
+
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $result->total(),
+            'sum_total'=>$total
+        ]);
+    }
+    /**
+     * 收款报表
+     * @param Request $request
+     * @return array|\Illuminate\Contracts\View\Factory|\Illuminate\Foundation\Application|\Illuminate\Http\JsonResponse|\Illuminate\View\View|mixed
+     */
+    public function finances_collection(Request $request)
+    {
+
+        $total='0';
+        $filters='';
+        $inputs = $request->input();
+        $keyword = $request->input('keyword');
+        if($keyword){
+            $condition1[] = ['s.domain', 'like', '%' . $keyword . '%'];
+            $condition[] = ['s.cn_title', 'like', '%' . $keyword . '%'];
+        }
+        $total = DB::table('sites_payment as sp')
+            ->leftJoin('sites as s', 'sp.site_id', '=', 's.id')
+            ->where('s.status','!=','7')
+            ->where($condition ?? [])
+            ->orWhere($condition1 ?? [])
+            ->where('s.deleted_at','=',Null)
+            //->select();
+            ->SUM('sp.contract_total_fee');
+        if (!$request->ajax()) {
+            return view('admin/finance/finances_collection', [
+                'sum_total' => $total
+            ]);
+        }
+
+
+        $records = DB::table('sites_payment as sp')
+            ->leftJoin('sites as s', 'sp.site_id', '=', 's.id')
+            ->where('s.status','!=','7')
+            ->where('s.deleted_at','=',Null)
+            ->where($condition ?? [])
+            ->orWhere($condition1 ?? [])
+            ->orderByDesc('sp.site_id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        foreach($records as $key => $value){
+            //$item['status_title'] = Site::STATUS_MAP[$item->status]?? '';
+            $value->status_title = Site::STATUS_MAP[$value->status]?? '';
+        }
+        $items = $records->items();
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total(),
+            'sum_total'=>$total
+        ]);
+    }
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/finance/index');
+        }
+
+        $inputs = $request->input();
+        !empty($inputs['start_at']) && $filters[] = ['created_at', '>=', $inputs['start_at']];
+        !empty($inputs['end_at']) && $filters[] = ['created_at', '<=', $inputs['end_at']];
+        $filters[] = ['is_settle', '=', 0];
+
+        $records = LadingBill::query()->where($filters)->selectRaw('user_id,sum(amount) as total_amount,GROUP_CONCAT(id) as ids')
+            ->groupBy('user_id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        $userIds = array_column($items, 'user_id');
+        $invoices = Agent::query()->whereIn('user_id', $userIds)
+            ->select(['user_id', 'discount', 'cash_deposit'])->get()->keyBy('user_id')->toArray();
+
+        $userMaps = User::query()->whereIn('id', $userIds)->pluck('username', 'id')->toArray();
+
+
+        array_walk($items, function ($item) use ($invoices, $userMaps) {
+            $item->settle_amount = bcadd($item->total_amount, ($invoices[$item->user_id]['discount']??0), 2);
+            $item->settle_amount = bcmul($item->total_amount, ($invoices[$item->user_id]['discount'] ?? 100) / 100, 2);
+            $item->username = $userMaps[$item->user_id] ?? '';
+            $item->ids = explode(',', ($item->ids??''));
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    /**
+     * 结算
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function settle(Request $request)
+    {
+        $ids = $request->input('ids');
+        $records = LadingBill::query()->with('agent')->whereIn('id', $ids)->get();
+        foreach ($records as $record) {
+            if (!empty($record->agent)) {
+                $settleAmount = bcmul($record->amount, ($record->agent->discount ?? 100) / 100, 2);
+                $record->update([
+                    'settle_amount' => $settleAmount,
+                    'is_settle' => 1,
+                    'settle_at' => date('Y-m-d H:i:s')
+                ]);
+            }
+
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 结算历史
+     * @param Request $request
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function history(Request $request)
+    {
+        if (!$request->ajax()) {
+
+            $settleTotal = LadingBill::query()->where(['is_settle' => 1])->sum('settle_amount');
+            return view('admin/finance/history', [
+                'settleTotal' => $settleTotal
+            ]);
+        }
+
+        $inputs = $request->input();
+        !empty($inputs['start_at']) && $filters[] = ['created_at', '>=', $inputs['start_at']];
+        !empty($inputs['end_at']) && $filters[] = ['created_at', '<=', $inputs['end_at']];
+        $filters[] = ['is_settle', '=', 1];
+
+        $records = LadingBill::query()->where($filters)->selectRaw('user_id,sum(settle_amount) as settle_total_amount,GROUP_CONCAT(id) as ids')
+            ->groupBy('user_id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        $userIds = array_column($items, 'user_id');
+        $invoices = Agent::query()->whereIn('user_id', $userIds)
+            ->select(['user_id', 'discount', 'cash_deposit'])->get()->keyBy('user_id')->toArray();
+
+        $userMaps = User::query()->whereIn('id', $userIds)->pluck('username', 'id')->toArray();
+
+        array_walk($items, function ($item) use ($invoices, $userMaps) {
+            $item->username = $userMaps[$item->user_id] ?? '';
+            $item->ids = explode(',', $item->ids);
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total(),
+            'settle_total' => time()
+        ]);
+    }
+
+    public function detail(Request $request)
+    {
+        $isSettle = $request->input('isSettle');
+        $ladingBillIds = $request->input('ladingBillIds') ?? [];
+        if (!$request->ajax()) {
+            return view('admin/finance/detail', [
+                'ladingBillIds' => array_map(function ($item) {
+                    return intval($item);
+                }, $ladingBillIds)
+            ]);
+        }
+        $records = LadingBill::query()->with(['agent', 'site'])->whereIn('id', $ladingBillIds)
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        array_walk($items, function ($item) use ($isSettle) {
+            if (!$isSettle) {
+                $item->settle_amount = bcmul($item->amount, ($item->agent->discount ?? 100) / 100, 2);
+            }
+            $item->relate_site = $item->site->cn_title ?? '';
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+}

+ 147 - 0
app/Http/Controllers/Admin/FinanceController_bak.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Agent;
+use App\Http\Models\Invoice;
+use App\Http\Models\LadingBill;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 财务 之前提的需求 基本没啥用了
+ * Class FinanceController
+ * @package App\Http\Controllers\Admin
+ */
+class FinanceController extends Controller
+{
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/finance/index');
+        }
+
+        $inputs = $request->input();
+        !empty($inputs['start_at']) && $filters[] = ['created_at', '>=', $inputs['start_at']];
+        !empty($inputs['end_at']) && $filters[] = ['created_at', '<=', $inputs['end_at']];
+        $filters[] = ['is_settle', '=', 0];
+
+        $records = LadingBill::query()->where($filters)->selectRaw('user_id,sum(amount) as total_amount,GROUP_CONCAT(id) as ids')
+            ->groupBy('user_id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        $userIds = array_column($items, 'user_id');
+        $invoices = Agent::query()->whereIn('user_id', $userIds)
+            ->select(['user_id', 'discount', 'cash_deposit'])->get()->keyBy('user_id')->toArray();
+
+        $userMaps = User::query()->whereIn('id', $userIds)->pluck('username', 'id')->toArray();
+
+
+        array_walk($items, function ($item) use ($invoices, $userMaps) {
+            $item->settle_amount = bcadd($item->total_amount, ($invoices[$item->user_id]['discount']??0), 2);
+            $item->settle_amount = bcmul($item->total_amount, ($invoices[$item->user_id]['discount'] ?? 100) / 100, 2);
+            $item->username = $userMaps[$item->user_id] ?? '';
+            $item->ids = explode(',', ($item->ids??''));
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    /**
+     * 结算
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function settle(Request $request)
+    {
+        $ids = $request->input('ids');
+        $records = LadingBill::query()->with('agent')->whereIn('id', $ids)->get();
+        foreach ($records as $record) {
+            if (!empty($record->agent)) {
+                $settleAmount = bcmul($record->amount, ($record->agent->discount ?? 100) / 100, 2);
+                $record->update([
+                    'settle_amount' => $settleAmount,
+                    'is_settle' => 1,
+                    'settle_at' => date('Y-m-d H:i:s')
+                ]);
+            }
+
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 结算历史
+     * @param Request $request
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function history(Request $request)
+    {
+        if (!$request->ajax()) {
+
+            $settleTotal = LadingBill::query()->where(['is_settle' => 1])->sum('settle_amount');
+            return view('admin/finance/history', [
+                'settleTotal' => $settleTotal
+            ]);
+        }
+
+        $inputs = $request->input();
+        !empty($inputs['start_at']) && $filters[] = ['created_at', '>=', $inputs['start_at']];
+        !empty($inputs['end_at']) && $filters[] = ['created_at', '<=', $inputs['end_at']];
+        $filters[] = ['is_settle', '=', 1];
+
+        $records = LadingBill::query()->where($filters)->selectRaw('user_id,sum(settle_amount) as settle_total_amount,GROUP_CONCAT(id) as ids')
+            ->groupBy('user_id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        $userIds = array_column($items, 'user_id');
+        $invoices = Agent::query()->whereIn('user_id', $userIds)
+            ->select(['user_id', 'discount', 'cash_deposit'])->get()->keyBy('user_id')->toArray();
+
+        $userMaps = User::query()->whereIn('id', $userIds)->pluck('username', 'id')->toArray();
+
+        array_walk($items, function ($item) use ($invoices, $userMaps) {
+            $item->username = $userMaps[$item->user_id] ?? '';
+            $item->ids = explode(',', $item->ids);
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total(),
+            'settle_total' => time()
+        ]);
+    }
+
+    public function detail(Request $request)
+    {
+        $isSettle = $request->input('isSettle');
+        $ladingBillIds = $request->input('ladingBillIds') ?? [];
+        if (!$request->ajax()) {
+            return view('admin/finance/detail', [
+                'ladingBillIds' => array_map(function ($item) {
+                    return intval($item);
+                }, $ladingBillIds)
+            ]);
+        }
+        $records = LadingBill::query()->with(['agent', 'site'])->whereIn('id', $ladingBillIds)
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+
+        array_walk($items, function ($item) use ($isSettle) {
+            if (!$isSettle) {
+                $item->settle_amount = bcmul($item->amount, ($item->agent->discount ?? 100) / 100, 2);
+            }
+            $item->relate_site = $item->site->cn_title ?? '';
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+}

+ 445 - 0
app/Http/Controllers/Admin/Flow/IndexController.php

@@ -0,0 +1,445 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Flow;
+
+use App\Exports\BasicExport;
+use App\Http\Controllers\Controller;
+use App\Http\Models\FlowInfo;
+use App\Http\Models\FlowInfoTpl;
+use App\Http\Models\FlowStage;
+use App\Http\Models\FlowStageTpl;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\SiteProcess;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 项目管理 详情下 新版流程
+ * Class IndexController
+ * @package App\Http\Controllers\Admin\Flow
+ */
+class IndexController extends Controller
+{
+
+    public function index($siteId)
+    {
+        $stageList = FlowStage::query()->where(['site_id' => $siteId])
+            ->with('infoList')->orderBy('sort')->get();
+        foreach ($stageList as $item) {
+            if ($item->expected_date) {
+                $item->expected_date = date('Y-m-d', strtotime($item->expected_date));
+            }
+            if ($item->complete_date) {
+                $item->complete_date = date('Y-m-d', strtotime($item->complete_date));
+            }
+            $item->extension = explode(',', $item->extension);
+        }
+
+        $roleScope = array_keys(FlowInfoTpl::RoleScope);
+        unset($roleScope[1]); //删除客户
+
+        $userList = User::query()->select(['id', 'nickname', 'role_id'])->whereIn('role_id', $roleScope)
+            ->get()->toArray();
+        $site = Site::query()->select(['cn_title'])->find($siteId);
+
+        $userList[] = ['id' => -1, 'role_id' => -1, 'nickname' => $site->cn_title ?? '站点名称'];
+        $deploy = SiteProcess::query()->where([['process_id', '=', 7], ['active', '=', 2], ['site_id', '=', $siteId]])->whereNotNull('deploy')->value('deploy');
+
+        return view('admin.flow.index', [
+            'siteId' => $siteId,
+            'stageList' => $stageList,
+            'userList' => $userList,
+            'authUser' => auth()->user(),
+            'domain' => $deploy['domain'] ?? '',
+        ]);
+    }
+
+    //流程保存
+    public function flowSave(Request $request, $siteId)
+    {
+        $dataList = $request->input('dataList') ?? [];
+
+
+        $where = ['site_id' => $siteId];
+        $stepIds = array_column($dataList, 'step_id');
+        $stageIds = FlowStage::query()->where($where)->pluck('id')->toArray();
+        $delIds = array_diff($stageIds, $stepIds);
+        if ($delIds) {
+            FlowStage::query()->where($where)->whereIn('id', $delIds)->delete();
+        }
+
+
+        $stageTplList = FlowInfoTpl::query()->get();
+
+        $mapInfoIds = FlowInfo::query()->select(['id', 'stage_id'])->where($where)->get()->groupBy('stage_id')->toArray();
+
+
+        $reqInfoList = [];
+        foreach ($dataList as $item) {
+            foreach ($item['children'] as $v) {
+                $reqInfoList[] = [
+                    'except_range_date' => $v['except_range_date'],
+                    'info_id' => $v['info_id'] ?? 0,
+                    'step_id' => $item['step_id'],
+                    'step_title' => $item['step_title'],
+                ];
+            }
+        }
+
+        $scopeInfoList = FlowInfo::query()->selectRaw('id,except_range_date')->get();
+
+        $modifyInfo = [
+            'info_id' => 0,
+            'except_range_date' => null,
+            'step_title' => '',
+        ];
+
+        foreach ($reqInfoList as $item) {
+            foreach ($scopeInfoList as $v) {
+                if ($item['info_id'] == $v->id && !empty($item['except_range_date']) && ($item['except_range_date'] != $v->except_range_date)) {
+                    $modifyInfo = [
+                        'info_id' => $item['info_id'],
+                        'except_range_date' => $item['except_range_date'],
+                        'step_title' => $item['step_title'],
+                    ];
+                    break;
+                }
+            }
+        }
+
+
+        $isUpdate = false;
+
+        $nextDate = null;
+
+        $stageExcept = [
+            'collect' => null,
+            'schema' => null,
+            'homepage' => null,
+            'insidePage' => null,
+            'wholeOffer' => null,
+            'testSite' => null,
+            'keyword' => null,
+            'seo' => null,
+            'online' => null
+        ];
+
+        foreach ($dataList as $step) {
+
+            $stepChildren = $step['children'] ?? [];
+
+            if (!empty($step['extension'])) {
+                $extension = implode(',', $step['extension']);
+            } else {
+                $extension = '';
+            }
+
+            if (empty($step['step_id'])) {
+                $stage = FlowStage::query()->create([
+                    'title' => $step['step_title'],
+                    'expected_date' => $step['expected_date'],
+                    'complete_date' => $step['complete_date'],
+                    'extension' => $extension,
+                    'site_id' => $siteId,
+                    'sort' => $this->getStageSort($stageTplList, $step['step_title'])
+                ]);
+                foreach ($stepChildren as $item) {
+                    FlowInfo::query()->create([
+                        'site_id' => $siteId,
+                        'stage_id' => $stage->id,
+                        'except_range_date' => $item['except_range_date'],
+                        'detail_list' => ($item['children'] ?? []),
+                    ]);
+                }
+            } else {
+
+
+                $infoIds = array_column($mapInfoIds[$step['step_id']] ?? [], 'id');
+                $dataInfoIds = array_column($stepChildren, 'info_id');
+
+                $infoDelIds = array_diff($infoIds, $dataInfoIds);
+                if ($infoIds) {
+                    FlowInfo::query()->where($where)->whereIn('id', $infoDelIds)->delete();
+                }
+
+                $updateData = [
+                    'title' => $step['step_title'],
+                    'expected_date' => $step['expected_date'],
+                    'complete_date' => $step['complete_date'],
+                    'extension' => $extension,
+                ];
+
+                FlowStage::query()->where(['id' => $step['step_id']])->update($updateData);
+                foreach ($stepChildren as $item) {
+                    if (empty($item['info_id'])) {
+                        FlowInfo::query()->create([
+                            'site_id' => $siteId,
+                            'stage_id' => $step['step_id'],
+                            'except_range_date' => $item['except_range_date'],
+                            'detail_list' => ($item['children'] ?? []),
+                        ]);
+                    } else {
+
+                        $exceptDate = $item['except_range_date'];
+
+
+//                        $exceptDate = null;
+                        if ($step['step_title'] == "项目资料搜集") {
+                            $stageExcept['collect'] = $item['except_range_date'];
+                        }
+
+
+                        if ($step['step_title'] == "网站架构" && $stageExcept['collect']) {
+
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['schema'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['collect'])));
+                            } else {
+                                $stageExcept['schema'] = $item['except_range_date'];
+                            }
+
+                        }
+                        if ($step['step_title'] == "首页设计" && $stageExcept['schema']) {
+
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['homepage'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['schema'])));
+                            } else {
+                                $stageExcept['homepage'] = $item['except_range_date'];
+                            }
+                        }
+                        if ($step['step_title'] == "内页设计" && $stageExcept['homepage']) {
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['insidePage'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['homepage'])));
+                            } else {
+                                $stageExcept['insidePage'] = $item['except_range_date'];
+                            }
+//                            dd($exceptDate);
+                        }
+                        if ($step['step_title'] == "整站资料提供" && $stageExcept['schema']) {
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['wholeOffer'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['schema'])));
+                            } else {
+                                $stageExcept['wholeOffer'] = $item['except_range_date'];
+                            }
+
+                        }
+                        if ($step['step_title'] == "测试站" && $stageExcept['insidePage']) {
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['testSite'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['insidePage'])));
+                            } else {
+                                $stageExcept['testSite'] = $item['except_range_date'];
+                            }
+
+                        }
+                        if ($step['step_title'] == "关键词" && $stageExcept['insidePage']) {
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['keyword'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['insidePage'])));
+                            } else {
+                                $stageExcept['keyword'] = $item['except_range_date'];
+                            }
+
+                        }
+                        if ($step['step_title'] == "SEO完善" && $stageExcept['testSite']) {
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['seo'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['testSite'])));
+                            } else {
+                                $stageExcept['seo'] = $item['except_range_date'];
+                            }
+
+                        }
+                        if ($step['step_title'] == "上线" && $stageExcept['seo']) {
+
+                            if ($isUpdate) {
+                                $exceptDate = $stageExcept['online'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['seo'])));
+                            } else {
+                                $stageExcept['online'] = $item['except_range_date'];
+                            }
+
+                        }
+
+
+//                        if ($isUpdate) {
+//                            $exceptDate= $nextDate = $this->computeDate($step['step_title'], $nextDate);
+//                        }
+
+                        if ($item['info_id'] == $modifyInfo['info_id']) {
+//
+                            $exceptDate = $nextDate = $modifyInfo['except_range_date'];//一定有值
+                            $isUpdate = true;
+                        }
+
+                        FlowInfo::query()->where(['id' => $item['info_id']])->update([
+                            'except_range_date' => $exceptDate,
+                            'detail_list' => json_encode($item['children'] ?? []),
+                        ]);
+                    }
+                }
+            }
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //流程清空
+    public function flowClear($siteId)
+    {
+        FlowStage::query()->where(['site_id' => $siteId])->delete();
+        FlowInfo::query()->where(['site_id' => $siteId])->delete();
+    }
+
+    //初始化流程
+    public function initFlow($siteId)
+    {
+        $site = Site::query()->select(['created_at'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点信息不存在'], 400);
+        }
+        $site = $site->toArray();
+
+        $stageTplList = FlowStageTpl::query()->with('infoTplList')->get();
+
+        if (FlowStage::query()->where(['site_id' => $siteId])->exists()) {
+            return response()->json(['message' => '流程已初始化'], 400);
+        }
+
+
+        $userIds = DB::table('user_has_sites')->where(['site_id' => $siteId])->pluck('user_id')->toArray();
+
+        $userList = User::query()->select(['id', 'role_id'])->whereIn('id', $userIds)->get()->toArray();
+
+        $datetime = date('Y-m-d H:i:s');
+
+
+        $stageExcept = [
+            'collect' => null,
+            'schema' => null,
+            'homepage' => null,
+            'insidePage' => null,
+            'wholeOffer' => null,
+            'testSite' => null,
+            'keyword' => null,
+            'seo' => null,
+            'online' => null
+        ];
+
+        foreach ($stageTplList as $item) {
+            $stage = FlowStage::query()->create([
+                'sort' => $item->sort,
+                'title' => $item->title,
+                'site_id' => $siteId,
+            ]);
+            $insertData = [];
+            foreach ($item->infoTplList as $info) {
+
+                $detailList = $info->detail_list;
+                foreach ($detailList as &$detail) {
+                    $duty_man = $detail['duty_man'] ?? [];
+                    $detail['duty_user'] = $this->getUserIdsByRoleIds($userList, $duty_man);
+                }
+
+
+                $exceptDate = null;
+                if ($item->title == "项目资料搜集") {
+                    $exceptDate = $stageExcept['collect'] = date('Y-m-d', strtotime($site['created_at']));
+                }
+                if ($item->title == "网站架构" && $stageExcept['collect']) {
+                    $exceptDate = $stageExcept['schema'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['collect'])));
+                }
+                if ($item->title == "首页设计" && $stageExcept['schema']) {
+                    $exceptDate = $stageExcept['homepage'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['schema'])));
+                }
+                if ($item->title == "内页设计" && $stageExcept['homepage']) {
+                    $exceptDate = $stageExcept['insidePage'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['homepage'])));
+                }
+                if ($item->title == "整站资料提供" && $stageExcept['schema']) {
+                    $exceptDate = $stageExcept['wholeOffer'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['schema'])));
+                }
+                if ($item->title == "测试站" && $stageExcept['insidePage']) {
+                    $exceptDate = $stageExcept['testSite'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['insidePage'])));
+                }
+                if ($item->title == "关键词" && $stageExcept['insidePage']) {
+                    $exceptDate = $stageExcept['keyword'] = date('Y-m-d', strtotime('+4 week', strtotime($stageExcept['insidePage'])));
+                }
+                if ($item->title == "SEO完善" && $stageExcept['testSite']) {
+                    $exceptDate = $stageExcept['seo'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['testSite'])));
+                }
+                if ($item->title == "上线" && $stageExcept['seo']) {
+                    $exceptDate = $stageExcept['online'] = date('Y-m-d', strtotime('+1 week', strtotime($stageExcept['seo'])));
+                }
+
+                $insertData[] = [
+                    'site_id' => $siteId,
+                    'stage_id' => $stage->id,
+                    'detail_list' => json_encode(array_values($detailList)),
+                    'except_range_date' => $exceptDate,
+                    'created_at' => $datetime,
+                    'updated_at' => $datetime,
+                ];
+            }
+            FlowInfo::query()->insert($insertData);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function getStageSort($stageTpl, $stageTitle)
+    {
+        foreach ($stageTpl as $item) {
+            if ($item->title === $stageTitle) {
+                return $item->sort;
+            }
+        }
+        return 0;
+    }
+
+    private function computeDate($stepTitle, $prevDate)
+    {
+
+        $preTime = strtotime($prevDate);
+
+        if ($stepTitle == "项目资料搜集") {
+            return $prevDate;
+        }
+        if ($stepTitle == "网站架构") {
+            return date('Y-m-d', strtotime('+1 week', $preTime));
+        }
+        if ($stepTitle == "首页设计") {
+            return date('Y-m-d', strtotime('+1 week', $preTime));
+        }
+        if ($stepTitle == "内页设计") {
+            return date('Y-m-d', strtotime('+1 week', $preTime));
+        }
+        if ($stepTitle == "整站资料提供") {
+            return date('Y-m-d', strtotime('+2 week', $preTime));
+        }
+        if ($stepTitle == "测试站") {
+            return date('Y-m-d', strtotime('+4 week', $preTime));
+        }
+        if ($stepTitle == "关键词") {
+            return date('Y-m-d', strtotime('+0 week', $preTime));
+        }
+        if ($stepTitle == "SEO完善") {
+            return date('Y-m-d', strtotime('+1 week', $preTime));
+        }
+        if ($stepTitle == "上线") {
+            return date('Y-m-d', strtotime('+1 week', $preTime));
+        }
+        return null;
+    }
+
+    protected function getUserIdsByRoleIds(&$scopes, $roleIds)
+    {
+
+        $userIds = [];
+        foreach ($scopes as $scope) {
+            if (in_array($scope['role_id'], $roleIds)) {
+                $userIds[] = $scope['id'];
+            }
+
+        }
+
+        if (in_array(-1, $roleIds)) {
+            $userIds[] = -1;
+        }
+        return $userIds;
+    }
+}

+ 225 - 0
app/Http/Controllers/Admin/Flow/PlanController.php

@@ -0,0 +1,225 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2020/4/30 0030
+ * Time: 15:57
+ */
+
+namespace App\Http\Controllers\Admin\Flow;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\FlowInfoTpl;
+use App\Http\Models\FlowPlan;
+use App\Http\Models\FlowStage;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/**
+ *  项目管理 详情下 流程规划
+ * Class PlanController
+ * @package App\Http\Controllers\Admin\Flow
+ */
+class PlanController extends Controller
+{
+    public function index($siteId)
+    {
+        $week = date('YW');
+        $where = ['site_id' => $siteId, 'week' => $week];
+        $nowPlanList = FlowPlan::query()->where($where)->get();
+
+        $nextWeek = date('YW', strtotime('+1 week'));
+        $nextWhere = ['site_id' => $siteId, 'week' => $nextWeek];
+        $nextPlanList = FlowPlan::query()->where($nextWhere)->get();
+
+        $historyList = FlowPlan::query()->where([['site_id', '=', $siteId], ['week', '<', $week]])->get()->groupBy('week')->toArray();
+
+        foreach ($historyList as $key => $value) {
+            foreach ($value as $kk => $vv) {
+                $value[$kk]['user_ids'] = explode(',', $vv['user_ids']) ?? '';
+            }
+            $historyList[$key] = $value;
+        }
+
+        $roleScope = array_keys(FlowInfoTpl::RoleScope);
+        unset($roleScope[1]); //删除客户
+
+        $userList = User::query()->select(['id', 'nickname', 'role_id'])->whereIn('role_id', $roleScope)
+            ->get()->toArray();
+        $site = Site::query()->select(['cn_title'])->find($siteId);
+
+        $userList[] = ['id' => -1, 'role_id' => -1, 'nickname' => $site->cn_title ?? '站点名称'];
+        foreach ($nowPlanList as $key => $value) {
+            $nowPlanList[$key]->user_ids = explode(',', $value->user_ids) ?? '';
+        }
+
+        foreach ($nextPlanList as $key => $value) {
+            $nextPlanList[$key]->user_ids = explode(',', $value->user_ids) ?? '';
+        }
+
+        return view('admin.flow.plan', [
+            'siteId' => $siteId,
+            'nowPlanList' => $nowPlanList,
+            'nextPlanList' => $nextPlanList,
+            'historyList' => $historyList,
+            'userList' => $userList,
+        ]);
+    }
+
+    //规划保存
+    public function planSave(Request $request, $siteId)
+    {
+        $isNext = $request->input('isNext');
+        $dataList = $request->input('dataList') ?? [];
+        $isMove = $request->input('isMove');
+
+        $week = date('YW');
+        if ($isNext) {
+            $week = date('YW', strtotime('+1 week'));
+        }
+
+        if ($isMove) {
+            $week = date('YW', strtotime('+1 week'));
+
+            FlowPlan::query()->where(['id' => $isMove])->update(array('week' => $week));
+
+            return response()->json(['message' => '操作成功']);
+        }
+
+
+        $where = ['site_id' => $siteId, 'week' => $week];
+        $requestIds = array_column($dataList, 'id');
+
+        $ids = FlowPlan::query()->where($where)->pluck('id')->toArray();
+        $delIds = array_diff($ids, $requestIds);
+        if ($delIds) {
+            FlowPlan::query()->whereIn('id', $delIds)->delete();
+        }
+
+        foreach ($dataList as $item) {
+            if (!empty($item['user_ids'])) {
+                $userIds = implode(',', $item['user_ids']);
+            }
+            if (!empty($item['id'])) {
+                FlowPlan::query()->where(['id' => $item['id']])->delete();
+            }
+            FlowPlan::query()->create([
+                'site_id' => $siteId,
+                'done_date' => $item['done_date'] ?? null,
+                'title' => $item['title'] ?? '',
+                'username' => $item['username'] ?? '',
+                'remark' => $item['remark'] ?? '',
+                'user_ids' => $userIds ?? '',
+                'week' => $week
+            ]);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //流程编辑
+    public function planEdit(Request $request)
+    {
+        $planId = $request->input('planId');
+        $title = $request->input('title');
+        $doneDate = $request->input('doneDate');
+        $username = $request->input('username');
+        $remark = $request->input('remark');
+        FlowPlan::query()->where(['id' => $planId])->update([
+            'title' => $title,
+            'done_date' => $doneDate,
+            'username' => $username,
+            'remark' => $remark,
+        ]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //流程历史删除
+    public function planHistoryDel(Request $request)
+    {
+        $planId = $request->input('planId');
+        FlowPlan::query()->where(['id' => $planId])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    public function historyUpdate(Request $request, $id)
+    {
+        if (!$request->ajax()) {
+
+            $flowPlan = FlowPlan::query()->where(['id' => $id])->first();
+            $flowPlan->user_ids = explode(',', $flowPlan->user_ids) ?? '';
+
+            $roleScope = array_keys(FlowInfoTpl::RoleScope);
+            unset($roleScope[1]); //删除客户
+
+            $site = Site::query()->where('id', $flowPlan->site_id)->first();
+            $userList = User::query()->select(['id', 'nickname', 'role_id'])->whereIn('role_id', $roleScope)
+                ->get()->toArray();
+            $userList[] = ['id' => -1, 'role_id' => -1, 'nickname' => $site->cn_title ?? '站点名称'];
+
+            return view('admin.flow.history_update', [
+                'flowPlan' => $flowPlan,
+                'userList' => $userList,
+            ]);
+        }
+        $request = $request->all();
+        $update = [
+            'done_date' => $request['done_date'] ?? date('Y-m-d'),
+            'username' => $request['username'] ?? '',
+            'title' => $request['title'] ?? '',
+            'remark' => $request['remark'] ?? '',
+            'user_ids' => implode(',', $request['user_ids']) ?? '',
+        ];
+        FlowPlan::query()->where('id', $request['id'])->update($update);
+        return response()->json(['message' => '操作成功']);
+
+    }
+
+    public function extension($siteId)
+    {
+        $stageList = FlowStage::query()->where(['site_id' => $siteId])->get();
+        foreach ($stageList as $item) {
+
+            $extension = explode(',', $item->extension);
+
+            $item->client_cause = 0;
+            if (in_array(1, $extension)) {
+                $item->client_cause = 1;
+            }
+            $item->collection_cause = 0;
+            if (in_array(2, $extension)) {
+                $item->collection_cause = 1;
+            }
+            $item->plan_cause = 0;
+            if (in_array(3, $extension)) {
+                $item->plan_cause = 1;
+            }
+            $item->design_cause = 0;
+            if (in_array(4, $extension)) {
+                $item->design_cause = 1;
+            }
+            $item->project_progress_cause = 0;
+            if (in_array(5, $extension)) {
+                $item->project_progress_cause = 1;
+            }
+            $item->memo_cause = 0;
+            if (in_array(6, $extension)) {
+                $item->memo_cause = 1;
+            }
+
+            if ($item->expected_date) {
+                $item->expected_date = date('Y-m-d', strtotime($item->expected_date));
+            }
+            if ($item->complete_date) {
+                $item->complete_date = date('Y-m-d', strtotime($item->complete_date));
+            }
+        }
+
+        return view('admin.flow.extension', [
+            'siteId' => $siteId,
+            'stageList' => $stageList,
+        ]);
+    }
+}

File diff suppressed because it is too large
+ 2461 - 0
app/Http/Controllers/Admin/Flow/ProgressRateController.php


+ 81 - 0
app/Http/Controllers/Admin/Flow/TplController.php

@@ -0,0 +1,81 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Flow;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\FlowInfoTpl;
+use App\Http\Models\FlowStageTpl;
+use Illuminate\Http\Request;
+
+/**
+ * 项目管理 详情下 的新版流程 模板
+ * Class TplController
+ * @package App\Http\Controllers\Admin\Flow
+ */
+class TplController extends Controller
+{
+
+    //新版流程 模板
+    public function tplIndex($siteId){
+        $tplStageList = FlowStageTpl::query()->with('infoTplList')->orderBy('sort')->get();
+
+        return view('admin.flow.tpl', [
+            'siteId' => $siteId,
+            'tplStageList' => $tplStageList
+        ]);
+    }
+    //模板保存
+    public function tplSave(Request $request)
+    {
+        $dataList = $request->input('dataList')??[];
+        $stepIds = array_column($dataList, 'step_id');
+        $stageIds = FlowStageTpl::query()->pluck('id')->toArray();
+        $delIds = array_diff($stageIds, $stepIds);
+        if ($delIds) {
+            FlowStageTpl::query()->whereIn('id', $delIds)->delete();
+        }
+
+        $mapInfoIds=FlowInfoTpl::query()->select(['id','stage_id'])->get()->groupBy('stage_id')->toArray();
+
+        foreach ($dataList as $step) {
+
+            $stepChildren=$step['children'] ?? [];
+
+            if (empty($step['step_id'])) {
+                $stage = FlowStageTpl::query()->create([
+                    'title' => $step['step_title'],
+                ]);
+                foreach ($stepChildren as $item) {
+                    FlowInfoTpl::query()->create([
+                        'stage_id' => $stage->id,
+                        'detail_list' => ($item['children'] ?? []),
+                    ]);
+                }
+            } else {
+
+                $infoIds=array_column($mapInfoIds[$step['step_id']]??[],'id');
+                $dataInfoIds=array_column($stepChildren,'info_id');
+
+                $infoDelIds=array_diff($infoIds,$dataInfoIds);
+                if ($infoIds){
+                    FlowInfoTpl::query()->whereIn('id', $infoDelIds)->delete();
+                }
+
+                FlowStageTpl::query()->where(['id' => $step['step_id']])->update(['title' => $step['step_title']]);
+                foreach ($stepChildren as $item) {
+                    if (empty($item['info_id'])) {
+                        FlowInfoTpl::query()->create([
+                            'stage_id' => $step['step_id'],
+                            'detail_list' => ($item['children'] ?? []),
+                        ]);
+                    } else {
+                        FlowInfoTpl::query()->where(['id' => $item['info_id']])->update([
+                            'detail_list' => json_encode($item['children'] ?? []),
+                        ]);
+                    }
+                }
+            }
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 427 - 0
app/Http/Controllers/Admin/HootsuiteController.php

@@ -0,0 +1,427 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/11/27 0027
+ * Time: 9:49
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Logics\Admin\HootsuiteLogic;
+use App\Http\Models\HootsuiteUser;
+use App\Http\Models\Site;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * Hootsuite社交信息
+ * Class HootsuiteController
+ * @package App\Http\Controllers\Admin
+ */
+class HootsuiteController extends Controller
+{
+    protected $logic;
+
+    public function __construct(HootsuiteLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+    //绑定站点
+    public function bindSite(Request $request)
+    {
+        // $hootsuiteId = $request->input('hootsuiteId');
+        // $siteId = $request->input('siteId');
+        // if (!$hootsuiteId || !$siteId) {
+        //     return response()->json(['message' => '参数错误'], 400);
+        // }
+
+        // if (HootsuiteUser::query()->where(['site_id' => $siteId])->exists()) {
+        //     return response()->json(['message' => '该站点已绑定其他hootsuite'], 400);
+        // }
+        // $hootsuiteUser = HootsuiteUser::query()->find($hootsuiteId);
+        // if (!$hootsuiteUser) {
+        //     return response()->json(['message' => 'hootsuite信息不存在'], 400);
+        // }
+        // $hootsuiteUser->site_id = $siteId;
+        // $hootsuiteUser->save();
+        // return response()->json(['message' => '操作成功']);
+        $hootsuiteId = $request->input('hootsuiteId');
+        $siteId = $request->input('siteId');
+        if (!$hootsuiteId || !$siteId) {
+            return response()->json(['message' => '参数错误'], 400);
+        }
+//        if (HootsuiteUser::query()->where(['site_id' => $siteId])->exists()) {
+//            return response()->json(['message' => '该站点已绑定其他hootsuite'], 400);
+//        }
+        //
+        $hootsuiteUser = HootsuiteUser::query()->find($hootsuiteId);
+        if($hootsuiteUser['site_id']!='' || $hootsuiteUser['site_id'] !='' || !empty($hootsuiteUser['site_id'])){
+            return response()->json(['message' => '该站点已绑定其他hootsuite'], 400);
+        }
+        if (!$hootsuiteUser) {
+            return response()->json(['message' => 'hootsuite信息不存在'], 400);
+        }
+        $hootsuiteUser->site_id = $siteId;
+        $hootsuiteUser->save();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //绑定多个社交
+    public function bindSocialIds(Request $request)
+    {
+        $socialIds = $request->input('socialIds');
+
+        $site = Site::query()->where(['id' => $request->input('siteId')])->first();
+        if (!$site) {
+            return response()->json(['message' => '站点信息不存在'], 400);
+        }
+        $site->social_ids = json_encode($socialIds);
+        $site->save();
+        return response()->json(['message' => '操作成功']);
+    }
+    //获取token
+    public function getToken(Request $request)
+    {
+        $client = new Client();
+
+        $authorization = base64_encode(sprintf('%s:%s', $this->logic->clientId, $this->logic->clientSecret));
+        try {
+            $response = $client->request('POST', 'https://platform.hootsuite.com/oauth2/token', [
+                'headers' => [
+                    'Authorization' => 'Basic ' . $authorization
+                ],
+                'form_params' => [
+                    'grant_type' => 'authorization_code',
+                    'code' => $request->input('code'),
+                    'redirect_uri' => 'https://admin.yinqingli.com/admin/socials/hootsuite',
+                    'scope' => $request->input('scope') ?? 'offline',
+                ]
+            ]);
+            $result = json_decode($response->getBody()->getContents(), true);
+
+            $meResponse = $client->request('GET', 'https://platform.hootsuite.com/v1/me', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $result['access_token'])
+                ]
+            ]);
+            $meResult = json_decode($meResponse->getBody()->getContents(), true);
+
+            $socialResponse = $client->request('GET', 'https://platform.hootsuite.com/v1/socialProfiles', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $result['access_token'])
+                ]
+            ]);
+            $socialResult = json_decode($socialResponse->getBody()->getContents(), true);
+
+            $user = HootsuiteUser::query()->where(['identify' => $meResult['data']['id']])->first();
+            if (!$user) {
+                HootsuiteUser::query()->create([
+                    'identify' => $meResult['data']['id'],
+                    'email' => $meResult['data']['email'],
+                    'full_name' => $meResult['data']['fullName'],
+
+                    'access_token' => $result['access_token'],
+                    'refresh_token' => $result['refresh_token'],
+                    'access_token_expired' => time() + intval($result['expires_in']) - 120,
+                    'social_profiles' => $socialResult['data']
+                ]);
+            } else {
+                $user->update([
+                    'access_token' => $result['access_token'],
+                    'refresh_token' => $result['refresh_token'],
+                    'access_token_expired' => time() + intval($result['expires_in']) - 120,
+                    'social_profiles' => $socialResult['data']
+                ]);
+            }
+
+            return response()->json([
+                'code' => 200,
+                'authInfo' => $result,
+                'meInfo' => $meResult
+            ]);
+
+
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            return response()->json([
+                'code' => 400,
+                'message' => var_export($exception->getMessage(), 1)
+            ]);
+        }
+    }
+
+    //获取社交列表
+    public function getSocialList(Request $request)
+    {
+        $siteId = $request->input('siteId');
+
+        $client = new Client();
+
+        $user = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+        $accessToken = $this->logic->accessToken($user);
+        try {
+
+            $response = $client->request('GET', 'https://platform.hootsuite.com/v1/socialProfiles', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $accessToken)
+                ]
+            ]);
+            $result = json_decode($response->getBody()->getContents(), true);
+            $user->social_profiles = $result['data'];
+            $user->save();
+            dd($result);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            return '请求错误';
+        }
+    }
+    //调度消息
+    public function scheduleMessage(Request $request)
+    {
+        $remoteImg = $request->input('remoteImg');
+        $siteId = $request->input('siteId');
+        $sendAt = $request->input('sendAt'); //        $sendAt = '2020-06-01 12:12:12';
+        $text = $request->input('content');
+        $socialIds = $request->input('socialIds'); // ['128172427', '128172414']
+        if (!$siteId || !$sendAt || !$text || !is_array($socialIds)) {
+            return response()->json(['message' => '参数错误', 'code' => 400]);
+        }
+
+        $sendTime = date('Y-m-d\TH:i:s\Z', strtotime($sendAt));
+
+        $user = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+        try {
+            $accessToken = $this->logic->accessToken($user);
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['code' => 400, 'message' => 'access_token获取失败']);
+        }
+
+        if ($remoteImg) {
+            try {
+                $mediaId = $this->logic->uploadMedia($remoteImg, $accessToken);
+                $mediaList = [
+                    ['id' => $mediaId]
+                ];
+                sleep(4); //必须要sleep 不延迟 hootsuite 会报错 猜测是创建上传之后 hootsuite mediaId 要过段时间才可用
+            } catch (\Throwable $throwable) {
+                Log::warning('remoteImg error');
+                Log::warning(var_export($throwable->getMessage(), 1));
+            }
+        }
+
+        $client = new Client();
+
+        try {
+            $response = $client->request('POST', 'https://platform.hootsuite.com/v1/messages', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $accessToken)
+                ],
+                'json' => [
+                    'text' => $text,
+                    'socialProfileIds' => $socialIds,
+                    'scheduledSendTime' => $sendTime,
+                    'media' => $mediaList ?? []
+                ]
+            ]);
+
+            $result = json_decode($response->getBody()->getContents(), true);
+            return response()->json($result);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            Log::error(var_export($exception->getMessage(), 1));
+            Log::error(var_export('mediaId=' . ($mediaId ?? ''), 1));
+            return response()->json(['code' => 400, 'message' => 'GuzzleException']);
+
+        }
+    }
+
+    //检索消息
+    public function retrieveMessage(Request $request)
+    {
+
+        $siteId = $request->input('siteId');
+
+        if (!$siteId ) {
+            return response()->json(['message' => '参数错误', 'code' => 400]);
+        }
+
+        $user = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+        try {
+            $accessToken = $this->logic->accessToken($user);
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['code' => 400, 'message' => 'access_token获取失败']);
+        }
+
+
+        $client = new Client();
+
+        try {
+            $response = $client->get('https://platform.hootsuite.com/v1/messages/{msgId}', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $accessToken)
+                ]
+            ]);
+
+            $result = json_decode($response->getBody()->getContents(), true);
+            return response()->json($result);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            return response()->json(['code' => 400, 'message' => 'GuzzleException']);
+
+        }
+    }
+
+    //为了测试
+    public function uploadMedia(Request $request)
+    {
+        if ($request->input('dd')) {
+            $remoteImg = 'https://www.kamcappower.com/uploads/image/20190613/14/medical.jpg';
+
+        } else {
+//            $remoteImg='./demo.jpg';
+            $remoteImg = 'http://kamcappower.com/uploads/image/20190628/11/application-prospect-of-supercapacitors-in-smart-grid.jpg';
+        }
+
+
+        $mime = image_type_to_mime_type(exif_imagetype($remoteImg));
+
+        $fileStr = file_get_contents($remoteImg);
+        $fileSize = strlen($fileStr);
+
+
+        $user = HootsuiteUser::query()->where(['site_id' => 4])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+
+        $accessToken = $this->logic->accessToken($user);
+        $client = new Client();
+
+        $response = $client->request('POST', 'https://platform.hootsuite.com/v1/media', [
+            'headers' => [
+                'Authorization' => sprintf('Bearer %s', $accessToken)
+            ],
+            'json' => [
+                'sizeBytes' => $fileSize,
+                'mimeType' => $mime
+            ]
+        ]);
+        $result = json_decode($response->getBody()->getContents(), true);
+        $uploadUrl = $result['data']['uploadUrl'];
+//            $mediaId = $result['data']['id'];
+        $data = fopen($remoteImg, 'r');
+
+        $uploadResponse = $client->request('PUT', $uploadUrl, [
+            'headers' => [
+                'Content-Length' => $fileSize,
+                'Content-Type' => $mime
+            ],
+            'body' => $fileStr,
+        ]);
+
+        $code = $uploadResponse->getStatusCode();
+
+        if ($code == 200) {
+            dd($result, $uploadResponse->getBody()->getContents());
+        } else {
+            throw new \Exception($code);
+        }
+
+    }
+
+    public function uploadMediaBak(Request $request)
+    {
+        $fileImage = $request->file('imageFile');
+
+        $siteId = $request->input('siteId');
+
+        $user = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+
+        $accessToken = $this->logic->accessToken($user);
+
+        $mediaFile = $fileImage->getRealPath();
+
+        $client = new Client();
+        try {
+            $response = $client->request('POST', 'https://platform.hootsuite.com/v1/media', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $accessToken)
+                ],
+                'json' => [
+                    'sizeBytes' => filesize($mediaFile),
+                    'mimeType' => mime_content_type($mediaFile)
+                ]
+            ]);
+            $result = json_decode($response->getBody()->getContents(), true);
+            $uploadUrl = $result['data']['uploadUrl'];
+//            $mediaId = $result['data']['id'];
+            $data = fopen($mediaFile, 'r');
+
+            $uploadResponse = $client->request('PUT', $uploadUrl, [
+                'headers' => [
+                    'Content-Length' => filesize($mediaFile),
+                    'Content-Type' => mime_content_type($mediaFile)
+                ],
+                'body' => $data,
+            ]);
+            $code = $uploadResponse->getStatusCode();
+
+            dd($result['data'], $code);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            dd($exception->getMessage());
+        }
+    }
+
+    public function retrieveAllMessage(Request $request)
+    {
+
+        $siteId = $request->input('siteId');
+
+        $user = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+
+        if (!$user) {
+            return response()->json(['message' => '用户信息不存在', 'code' => 400]);
+        }
+        $accessToken = $this->logic->accessToken($user);
+
+        $query = [
+            'startTime' => date(DATE_ISO8601, strtotime('-4 week')),
+            'endTime' => date(DATE_ISO8601)
+//            'socialProfileIds',
+//            'state',
+//            'limit'
+        ];
+        $client = new Client();
+        try {
+            $response = $client->request('GET', 'https://platform.hootsuite.com/v1/messages', [
+                'headers' => [
+                    'Authorization' => sprintf('Bearer %s', $accessToken)
+                ],
+                'query' => $query
+
+            ]);
+            $result = json_decode($response->getBody()->getContents(), true);
+            dd($result['data']);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            dd($exception->getMessage());
+        }
+    }
+}

+ 214 - 0
app/Http/Controllers/Admin/HtPlantController.php

@@ -0,0 +1,214 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: vanshao
+ * Date: 2019-10-24
+ * Time: 11:24
+ */
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Logics\Admin\SiteLogic;
+use App\Http\Logics\Admin\SiteSyncLogic;
+use App\Http\Models\Business;
+use App\Http\Models\Contract;
+use App\Http\Models\Role;
+use App\Http\Models\Server;
+use App\Http\Models\Site;
+use App\Http\Models\SiteAddition;
+use App\Http\Models\SiteEnterprise;
+use App\Http\Models\SiteInfo;
+use App\Http\Models\SiteMarketer;
+use App\Http\Models\SitePayment;
+
+use App\Http\Models\SiteSns;
+use App\Http\Models\SiteStation;
+use App\Http\Models\Social;
+use App\Http\Models\SocialPublish;
+use App\Http\Requests\Site\PaymentRequest;
+use App\Http\Requests\Site\SiteOverviewRequest;
+use App\Http\Requests\Site\CustomerRequest;
+use App\Http\Requests\Site\SiteSaveRequest;
+use App\Http\Requests\Site\SocialPublishRequest;
+use App\Http\Requests\Site\SocialQueueRequest;
+use App\Http\Requests\Site\SocialSaveRequest;
+use GuzzleHttp\Client;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+
+use Illuminate\Support\Facades\Log;
+use App\Http\Traits\HasSites;
+
+class HtPlantController extends Controller{
+
+
+    public function index(Request $request)
+    {
+
+        if (!$request->ajax()) {
+            return view('admin/ht_plant/index');
+        }
+
+
+        $cacheKey=md5('htall');
+        $cacheContent = Cache::get($cacheKey);
+        $cacheContentTotal = Cache::get($cacheKey.'total');
+
+        $sortName = $request->input('sortName')=='id'?'traffic':$request->input('sortName');
+        $sortOrder = $request->input('sortOrder');
+        $traffic_number=$request->input('traffic_number');
+        $inquire_number=$request->input('inquire_number');
+
+
+        if ($cacheContent&&!$cacheContent->isEmpty()) {
+            $records=$cacheContent;
+            $total=$cacheContentTotal;
+
+        }else{
+
+            if ($status = $request->input('status')) {
+                $condition['status'] = $status;
+            }
+
+            DB::connection()->enableQueryLog();
+
+            $keyword = $request->input('keyword');
+            $builder = Site::query()->withCount(['articles' , 'linkDetails'=>function(Builder $query){
+                $query->where([
+                    ['enable', '=', 1]
+                ]);
+
+            }])
+                ->withCount(['linkUrls'])
+                ->with(['users','linkDetails','business'])->where($condition ?? [])->whereIn('status',[2,3])->where(function (Builder $builder) use ($keyword) {
+                    if ($keyword) {
+                        $builder->where('domain', 'like', '%' . $keyword . '%')
+                            ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+                    }
+
+                });
+
+
+
+            if ($sortName == 'statusTitle') {
+                $builder->orderBy('status', $sortOrder);
+            }
+
+            if ($sortName == 'order_at') {
+                $builder->orderBy('order_at', $sortOrder);
+            }
+
+            $sites = $builder->orderByDesc('id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+            $items = $sites->items();
+            $oldIds = array_filter(array_column($items, 'old_id'));
+
+            $rankConnection = DB::connection('rank');
+            $top10ListMap = $rankConnection->table('project_keyword')
+                ->selectRaw('SUM(CASE WHEN google_rank <= 10 THEN 1 ELSE 0 END) as top10,count(id) as keyword_num,project_id')->whereIn('project_id', $oldIds)
+                ->groupBy('project_id')->get()->keyBy('project_id')->toArray();
+
+            $listReportMap = $rankConnection->table('project_listreport')->whereIn('project_id', $oldIds)->where([
+                'ym' => date('Ym', strtotime('-1 month'))
+            ])->get()->keyBy('project_id')->toArray();
+
+            $reachTimeMap = $rankConnection->table('project')->whereIn('id',$oldIds)->get()->keyBy('id')->toArray();
+
+            array_walk($items, function ($item) use (&$top10ListMap, &$listReportMap,&$reachTimeMap) {
+                $item->identify = base64_encode($item->id);
+                $item->managers_title = implode('-', $item->users->where('role_id', Role::TYPE_MANAGER)->pluck('nickname')->toArray());
+                $item->statusTitle = Site::STATUS_MAP[$item->status] ?? '';
+
+                $item->top10 = $item->old_id ? ($top10ListMap[$item->old_id]->top10 ?? '-') : '未关联';
+                $item->keyword_num = $item->old_id ? ($top10ListMap[$item->old_id]->keyword_num ?? '-') : '未关联';
+                $item->traffic = $item->old_id ? ($listReportMap[$item->old_id]->traffic ?? '-') : '未关联';
+                $item->inquire = $item->old_id ? ($listReportMap[$item->old_id]->inquire ?? '-') : '未关联';
+
+                $item->reach_time = $item->old_id ?($reachTimeMap[$item->old_id]->reach_time?intval((time()-$reachTimeMap[$item->old_id]->reach_time)/86400).'天':'未达标') : '未关联';
+                $item->is_google_bid = in_array(Contract::GOOGLE_BID, $item->contract_ids ?? []) ? 1 : 0;
+                $item->online_date = $item->online_at ? date('Y-m-d', strtotime($item->online_at)) : '';
+                if($item->online_date){
+                    $online_time=strtotime($item->online_date);
+                    if($item->old_id){
+                        $seo_times=$reachTimeMap[$item->old_id]->reach_time?$reachTimeMap[$item->old_id]->reach_time-$online_time:time()-$online_time;
+                        $item->seo_time=intval($seo_times/86400);
+                        $item->seo_time=$item->seo_time>0?$item->seo_time.'天':'-';
+                    }
+
+                }else{
+                    $item->seo_time='-';
+                }
+
+                try{
+                    if($item->webmaster_domain){
+                        $opts=array(
+                            "http"=>array(
+                                "method"=>"GET",
+                                "timeout"=>3
+                            ),
+                        );
+                        $context = stream_context_create($opts);
+                        $web_count=substr_count(file_get_contents($item->webmaster_domain.'sitemap.xml','false',$context),'http');
+                        $item->web_count=$web_count;
+                    }
+                }catch(\Throwable $throwable){
+
+                }
+
+
+                $temp_link_details=array();
+                foreach ($item->linkDetails as $value){
+                    $temp_link_details[]=$value['link_id'];
+                }
+                if($temp_link_details){
+                    $item->link_details_num=count(array_unique($temp_link_details));
+                }
+                unset($item->linkDetails);
+
+
+            });
+            $records = collect($items);
+
+            $total=$sites->total();
+
+
+            Cache::put($cacheKey,$records,10080);
+            Cache::put($cacheKey.'total',$sites->total(),10080);
+        }
+
+
+
+        if (in_array($sortName, ['traffic', 'inquire', 'top10'])) {
+
+            $descending = strtolower($sortOrder) == 'asc' ? false : true;
+            $records = $records->sortBy(function ($item, $key) use ($sortName, $descending) {
+                return intval($item[$sortName]);
+            }, SORT_REGULAR, $descending)->values();
+        }
+
+        if($traffic_number){
+            $number=explode(',',$traffic_number);
+
+            $records=$records->filter(function ($value,$key) use($number){
+
+                return $value['traffic']>$number[0]&&$value['traffic']<=$number[1];
+            });
+        }
+
+
+        if($inquire_number){
+            $number=explode(',',$inquire_number);
+            $records=$records->filter(function ($value,$key) use($number){
+
+                return $value['inquire']>$number[0]&&$value['inquire']<=$number[1];
+            });
+        }
+
+
+        return response()->json([
+            'rows' => array_values($records->toArray()),
+            'total' => $total
+        ]);
+    }
+}

+ 60 - 0
app/Http/Controllers/Admin/ImageController.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2020/6/4 0004
+ * Time: 13:52
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Image;
+use Illuminate\Http\Request;
+
+
+class ImageController extends Controller
+{
+    public function index(Request $request)
+    {
+
+        if (!$request->ajax()) {
+            return view('admin.image.index',[
+                'siteId'=>$request->input('siteId')
+            ]);
+        }
+        if ($siteId = $request->input('siteId')) {
+            $condition[] = ['site_id', '=', $siteId];
+        }
+        if ($title = $request->input('title')) {
+            $condition[] = ['title', 'like', '%' . $title . '%'];
+        }
+        $results = Image::query()->where($condition ?? [])->orderByDesc('id')
+            ->paginate(12);
+        $items = $results->items();
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $results->total()
+        ]);
+    }
+
+    public function upload(Request $request)
+    {
+        $file = $request->file('file');
+        if (!$file) return response()->json(['message' => '没有上传文件'], 422);
+        $name = $file->getClientOriginalName();
+        $fileData['file_url'] = $file->storeAs(date('Ym') . '/' . $file->getClientOriginalExtension(), $name, 'public');
+        $fileData['file_url'] = sprintf('/storage/%s', $fileData['file_url']);
+        $fileData['original_name'] = $name;
+        $fileData['time'] = time();
+
+        Image::query()->create([
+            'title' => $name,
+            'url' => $fileData['file_url'],
+            'site_id' => $request->input('siteId'),
+        ]);
+        return response()->json(['message' => '上传成功']);
+    }
+
+}

+ 153 - 0
app/Http/Controllers/Admin/IndexController.php

@@ -0,0 +1,153 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Mailbox;
+use App\Http\Models\Permission;
+use App\Http\Models\ProjectFlowMemo;
+use App\Http\Models\ProjectFlowMemoRelation;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\WeekTaskInfo;
+use App\Http\Requests\Index\ProfilerRequest;
+use App\Http\Requests\Request;
+use Illuminate\Support\Facades\Auth;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+/** 后台首页
+ * Class IndexController
+ * @package App\Http\Controllers\Admin
+ */
+class IndexController extends Controller
+{
+
+    protected function indexPage($role, $menusArr)
+    {
+        switch ($role->id ?? null) {
+            case Role::TYPE_TYPE_CUSTOMER_STAFF:
+                $url = '/admin/meter/single';
+                return ['title' => '控制台', 'src' => $url];
+            case Role::TYPE_BID:
+                return ['title' => '竞价仪表', 'src' => '/admin/bid/dashboard'];
+            case Role::TYPE_CUSTOMER:
+                $menuIds = array_column($menusArr, 'id');
+                $url = '/admin/meter/single';
+                if (!in_array(32, $menuIds)) {
+                    $url = '/admin/rank/keyword';
+                }
+                return ['title' => '控制台', 'src' => $url];
+            default:
+                return ['title' => '首页', 'src' => '/admin/flow/home'];
+        }
+    }
+
+    //后台首页
+    public function main()
+    {
+        $user = auth()->user();
+        $menusArr = $this->menus($user);
+        $menus = list_to_tree($menusArr, 'id', 'parent_id');
+        $role = $user->role ?? null;
+        $roleName = $role->name ?? '';
+        if (!empty($user->is_super)) {
+            $roleName = '超级管理员';
+        }
+
+        $addition = [];
+        if (in_array(($role->id ?? null), [Role::TYPE_CUSTOMER, Role::TYPE_TYPE_CUSTOMER_STAFF])) {
+            $userSite = $user->sites->first();
+            $addition['site'] = $userSite;
+            $addition['masterUrl'] = sprintf('http://%s/admincp', ($userSite->domain ?? ''));
+        }
+
+        if (in_array($role->id, [Role::TYPE_LINK_PART, Role::TYPE_LINK_PART_CHONGQING])) {
+            return redirect('/admin/link/hall');
+        }
+        if ($role->id == Role::TYPE_ARTICLE_PART) {
+            return redirect('/admin/articles-hall');
+        }
+
+
+        $statusMap = Site::query()->selectRaw('COUNT(id) as num,status')->whereIn('status', [1, 2, 3, 7])
+            ->groupBy('status')->get()->keyBy('status')->toArray();
+        $buildSiteNum = $statusMap[1]['num'] ?? 0;
+        $srvSiteNum = $statusMap[3]['num'] ?? 0;
+        $impSiteNum = $statusMap[2]['num'] ?? 0;
+        $doneSiteNum = $statusMap[7]['num'] ?? 0;
+        $totalSite = Site::query()->count();
+
+        $numInfo = DB::table('num')
+            ->selectRaw('SUM(traffic) AS traffic,SUM(inquire) AS inquire,SUM(top10) AS top10,ymd')
+            ->where('ymd', date('Ymd', strtotime('-1 day')))
+            ->groupBy('ymd')->first();
+
+        $roles = auth()->user();
+        if ($roles->is_super == 1) {
+            $taskNum = ProjectFlowMemo::query()->where('status', '=', '0')->count() ?? 0;
+            $mailboxNum = Mailbox::query()->where('is_read', 0)->count() ?? 0;
+        } else {
+            $memoIdList = ProjectFlowMemoRelation::query()->where('user_id', $roles->id)->pluck('memo_id');
+            $taskNum = ProjectFlowMemo::query()->where('status', '=', '0')->whereIn('id', $memoIdList)->count() ?? 0;
+            $mailboxNum = Mailbox::query()->where('user_id', $roles->id)->count() ?? 0;
+        }
+        //续费客户
+        $renewalCount = Site::query()->where('status', 3)->whereNotNull('renewal_at')->count() ?? 0;
+        $taskCount = WeekTaskInfo::query()->count() ?? 0;
+        return view('admin/index/main', [
+                'mailboxNum' => $mailboxNum,
+                'renewalCount' => $renewalCount,
+                'taskNum' => $taskNum,
+                'buildSiteNum' => $buildSiteNum,
+                'srvSiteNum' => $srvSiteNum,
+                'customerNum' => $totalSite - $doneSiteNum,
+                'impSiteNum' => $impSiteNum,
+                'menus' => $menus,
+                'user' => $user,
+                'roleName' => $roleName,
+                'indexPage' => $this->indexPage($role, $menusArr),
+                'top10' => $numInfo->top10 ?? 0,
+                'inquire' => $numInfo->inquire ?? 0,
+                'taskCount' => $taskCount
+            ] + $addition);
+    }
+
+    //获取菜单
+    protected function menus($user)
+    {
+        /** @var \App\Http\Models\User $user */
+        if (!empty($user->is_super))
+            return Permission::query()->where(['type' => 1])->orderByDesc('sort')
+                ->get()->toArray();
+        if (in_array($user->role_id, [Role::TYPE_CUSTOMER, Role::TYPE_TYPE_CUSTOMER_STAFF])) { //客户人员菜单独自配置
+            return $user->permissions()->orderByDesc('sort')->get()->toArray();
+        }
+        return ($role = $user->role ?? null)
+            ? $menus = $role->permissions()->where('type', 1)->orderByDesc('sort')->get()->toArray()
+            : [];
+    }
+
+    //个人信息
+    public function profile(ProfilerRequest $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/index/profile', [
+                'data' => Auth::user()
+            ]);
+        }
+
+        $user = Auth::user();
+        /** @var \App\Http\Models\User $user * */
+        $validated = $request->validated();
+        if (!empty($validated['password'])) {
+            $validated['password'] = bcrypt($validated['password']);
+        } else {
+            unset($validated['password']);
+        }
+        if (empty($validated['profile_img'])) {
+            $validated['profile_img'] = asset('img/social_round_github_64px_1196568_easyicon.net.png');
+        }
+        $user->update($validated);
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 324 - 0
app/Http/Controllers/Admin/InquireController.php

@@ -0,0 +1,324 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Exports\InquireExport;
+use App\Http\Logics\Admin\InquireLogic;
+use App\Http\Models\Role;
+use App\Http\Models\Server;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use App\Http\Traits\HasSites;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 询盘管理
+ * Class InquireController
+ * @package App\Http\Controllers\Admin
+ */
+class InquireController extends Controller
+{
+    use HasSites;
+    protected $logic;
+
+    public function __construct(InquireLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+
+    private function getTimeFilter($dateRange)
+    {
+        if (!$dateRange) {
+            return [];
+        }
+        switch ($dateRange) {
+            case 'week':
+                $condition = [
+                    ['create_time', '>', strtotime(date('Y-m-d 23:59:59', strtotime('-1 week')))],
+                    ['create_time', '<=', strtotime(date('Y-m-d 23:59:59'))]
+                ];
+                break;
+            case 'month':
+                $condition = [
+                    ['create_time', '>', strtotime(date('Y-m-d 23:59:59', strtotime('-1 month')))],
+                    ['create_time', '<=', strtotime(date('Y-m-d 23:59:59'))]
+                ];
+                break;
+            case 'year':
+                $condition = [
+                    ['create_time', '>', strtotime(date('Y-m-d 23:59:59', strtotime('-1 year')))],
+                    ['create_time', '<=', strtotime(date('Y-m-d 23:59:59'))]
+                ];
+                break;
+            case 'all':
+                $condition = [];
+                break;
+            default:
+                $startDate = substr($dateRange, 0, 10);
+                $endDate = substr($dateRange, -10);
+                $condition = [
+                    ['create_time', '>=', strtotime(sprintf('%s 00:00:00', $startDate))],
+                    ['create_time', '<=', strtotime(sprintf('%s 23:59:59', $endDate))]
+                ];
+                break;
+        }
+        return $condition;
+    }
+
+
+    /**
+     * 客户询盘列表
+     * @param Request $request
+     * @param $siteId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function index(Request $request, $siteId)
+    {
+        $dateRange = $request->input('time_range');
+        $condition = $this->getTimeFilter($dateRange);
+
+        if (!$request->ajax()) {
+            return view('admin/inquire/index', [
+                'siteId' => $siteId
+            ]);
+        }
+        $site = Site::query()->where(['id' => $siteId])->first();
+        $server = Server::query()->where(['server_id' => $site->server_id])->first();
+
+        $isDelete = $request->input('is_delete') ?? 0;
+        list($records, $realInquire) = $this->logic->pageRecords($request, $server, $site, $condition, $isDelete);
+        /**
+         * @var \Illuminate\Contracts\Pagination\LengthAwarePaginator $records
+         */
+        return response()->json([
+            'rows' => $records->items(),
+            'total' => $records->total(),
+            'extra' => [
+                'allInquire' => $records->total(),
+                'realInquire' => $realInquire
+            ]
+        ]);
+    }
+
+    //导出到excel
+    public function exportExcel(Request $request, $siteId)
+    {
+
+        $dateRange = $request->input('time_range');
+
+        $condition = $this->getTimeFilter($dateRange);
+
+        $site = Site::query()->where(['id' => $siteId])->first();
+        $server = Server::query()->where(['server_id' => $site->server_id])->first();
+
+
+        $records = $this->logic->pageRecordsExport($request, $server, $site, $condition);
+
+        array_unshift($records, ['客户名字', '客户邮箱', '电话', '国家', '询盘时间', '询盘主题', '询盘内容', '是否真实询盘', '是否查看', '留言', '链接']);
+
+        return (new InquireExport($records))->download(sprintf('询盘%s.xls', date('YmdHis')));
+    }
+
+    //导出到excel
+    public function exportAllExcel($siteId)
+    {
+        $site = Site::query()->where(['id' => $siteId])->first();
+        $server = Server::query()->where(['server_id' => $site->server_id])->first();
+
+        $records = $this->logic->PageRecordsAllExport($server, $site);
+
+        array_unshift($records, ['id', 'user_id', 'notice', 'url', 'request', 'cookie', 'client_ip', 'create_time']);
+
+        return (new InquireExport($records))->download(sprintf('询盘%s.xls', date('YmdHis')));
+    }
+
+    /**
+     * 项目详情客户询盘列表页面
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function lists()
+    {
+
+        $user = auth()->user();
+        $site = $user->sites->first(); //读取分配的站点信息
+
+        if (!$site) {
+            return view('admin/errors/tips', [
+                'tips' => '没有分配的站点'
+            ]);
+        }
+
+        $server = Server::query()->where(['server_id' => $site->server_id])->first();
+
+        if (!$server) {
+            return view('admin/errors/tips', [
+                'tips' => '服务器信息不存在'
+            ]);
+        }
+
+        return view('admin/inquire/show_index', [
+            'siteId' => $site->id,
+            'nowRoleId' => $user->role_id
+        ]);
+    }
+
+    //分配用户
+    public function assignUser(Request $request)
+    {
+
+        $oneSite = $this->hasUserOneSite();
+        $inquireIds = $request->input('inquireIds');
+        $assignUserIds = $request->input('assignUserIds');
+
+
+        if (!$request->ajax()) {
+            $selects = [];
+
+            if (count($inquireIds) == 1) {
+                $selects = DB::table('user_has_inquire')->where(['inquire_id' => $inquireIds[0]])
+                    ->get()->pluck('user_id')->toArray();
+            }
+
+
+            $selectCol = ['id', 'username', 'email', 'created_at', 'role_id', 'status', 'is_super', 'nickname'];
+            $users = User::query()->select($selectCol)
+                ->where([['role_id', '=', Role::TYPE_TYPE_CUSTOMER_STAFF]])->whereHas('sites', function (Builder $builder) use ($oneSite) {
+                    $builder->where('id', $oneSite->id);
+                })->get()->toArray();
+
+            array_walk($users, function (&$item) use (&$selects) {
+                $item['open'] = true;
+                $item['name'] = $item['username'] . '[' . $item['nickname'] . ']';
+                if (in_array($item['id'], $selects)) {
+                    $item['checked'] = true;
+                }
+            });
+
+            return view('admin/inquire/user_assign', [
+                'trees' => $users,
+                'inquireIds' => $inquireIds
+            ]);
+        }
+
+        foreach ($inquireIds as $inquireId) {
+            DB::table('user_has_inquire')->where(['inquire_id' => $inquireId])->delete();
+            $insertData = [];
+            foreach ($assignUserIds as $userId) {
+                $insertData[] = [
+                    'user_id' => $userId,
+                    'inquire_id' => $inquireId
+                ];
+            }
+
+            DB::table('user_has_inquire')->insert($insertData);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //询盘详情
+    public function detail(Request $request, $id)
+    {
+        $siteId = $request->input('siteId');
+        $site = Site::query()->with('server')->where(['id' => $siteId])->first();
+        if (empty($site->server)) {
+            return view('admin/errors/tips', [
+                'tips' => '站点服务器信息不存在',
+                'closeBtn' => true
+            ]);
+        }
+
+        $config = [
+            'connection_name' => sprintf('connection_name_%s', $site->id),
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => $site->database,
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd,
+        ];
+
+        config_connection($config);
+
+        $record = DB::connection($config['connection_name'])->table('user_msg')->where([
+            'id' => $id
+        ])->first();
+
+        if (empty($record->is_read)) {
+            DB::connection($config['connection_name'])->table('user_msg')->where([
+                'id' => $id, 'is_read' => 0
+            ])->update(['is_read' => 1]);
+        }
+
+        return view('admin/inquire/detail', [
+            'data' => $record,
+            'siteId' => $siteId
+        ]);
+    }
+
+
+    //询盘详情
+    public function mailDetail(Request $request, $id)
+    {
+        $siteId = $request->input('siteId');
+        $site = Site::query()->with('server')->where(['id' => $siteId])->first();
+        if (empty($site->server)) {
+            return view('admin/errors/tips', [
+                'tips' => '站点服务器信息不存在',
+                'closeBtn' => true
+            ]);
+        }
+
+        $config = [
+            'connection_name' => sprintf('connection_name_%s', $site->id),
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => $site->database,
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd,
+        ];
+
+        config_connection($config);
+
+        $record = DB::connection($config['connection_name'])->table('user_msg')->where([
+            'id' => $id
+        ])->first();
+
+        return view('admin/inquire/mail_detail', [
+            'data' => $record,
+            'siteId' => $siteId
+        ]);
+    }
+
+
+    /**
+     * 修改询盘状态
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function whetherReality(Request $request)
+    {
+        try {
+            $id = $request->input('id');
+            $status = $request->input('status');
+            $siteId = $request->input('siteId');
+
+            $site = Site::query()->with('server')->where(['id' => $siteId])->first();
+            $config = [
+                'connection_name' => sprintf('connection_name_%s', $site->id),
+                'host' => $site->server->server_ip,
+                'port' => '3306',
+                'database' => $site->database,
+                'username' => $site->server->mysql_user_name,
+                'password' => $site->server->mysql_passwd,
+            ];
+
+            config_connection($config);
+            DB::connection($config['connection_name'])->table('user_msg')->where('id', $id)->update(['is_delete' => $status]);
+            return response()->json(['message' => '操作成功']);
+        } catch (\Throwable $exception) {
+            return response()->json(['message' => '服务器失去链接'], 400);
+        }
+    }
+}

+ 70 - 0
app/Http/Controllers/Admin/InvoiceController.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Invoice;
+use App\Http\Requests\InvoiceSaveRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+/**
+ * 开票管理 之前提的需求 基本没啥用了
+ * Class InvoiceController
+ * @package App\Http\Controllers\Admin
+ */
+class InvoiceController extends Controller
+{
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/invoice/index');
+        }
+        $builder = Invoice::query();
+        if ($keyword = $request->input('keyword')) {
+            $builder->where('title', 'like', '%' . $keyword . '%');
+        }
+
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+//        array_walk($items, function ($item) {
+//            $item->relate_site = $item->site->cn_title ?? '';
+//        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+    public function save(InvoiceSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            return view('admin/invoice/save', [
+                'data' => $id > 0 ? Invoice::query()->find($id) : null
+            ]);
+        }
+        $validated = $request->validated();
+        Invoice::query()->updateOrCreate(
+            ['id' => $id],
+            $validated
+        );
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function delete(Request $request)
+    {
+        $ids = $request->input('ids');
+        Invoice::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function photo(Request $request, $id)
+    {
+        $photo = $request->input('photo');
+
+        $invoice = Invoice::query()->where(['id' => $id])->first();
+        if (!$invoice) return response()->json(['message' => '数据不存在'], 400);
+        $invoice->update(['photo' => $photo]);
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 96 - 0
app/Http/Controllers/Admin/LadingBillController.php

@@ -0,0 +1,96 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\LadingBill;
+use App\Http\Models\Site;
+use App\Http\Requests\LadingBillSaveRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Auth;
+
+/**
+ * 提单 之前提的需求 基本没啥用了
+ * Class LadingBillController
+ * @package App\Http\Controllers\Admin
+ */
+class LadingBillController extends Controller
+{
+    /**
+     * 提单列表
+     * @param Request $request
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/lading_bill/index');
+        }
+        $builder = LadingBill::query()->with(['site', 'user']);
+        if ($keyword = $request->input('keyword')) {
+            $builder->where('title', 'like', '%' . $keyword . '%');
+        }
+
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        array_walk($items, function ($item) {
+            $item->relate_site = $item->site->cn_title ?? '';
+            $item->username = $item->user->username ?? '';
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+
+    public function audit(Request $request)
+    {
+        $ids = $request->input('ids');
+        $auditStatus = $request->input('auditStatus');
+
+        LadingBill::query()->whereIn('id', $ids)->update(['audit' => $auditStatus]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 添加 与 编辑
+     * @param LadingBillSaveRequest $request
+     * @param $id
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function save(LadingBillSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            return view('admin/lading_bill/save', [
+                'data' => $id > 0 ? LadingBill::query()->find($id) : null,
+                'sites' => Site::query()->select(['id', 'cn_title'])->get()
+            ]);
+        }
+
+        $validated = $request->validated();
+        if ($id == 0) { //添加
+            $validated['user_id'] = Auth::id();
+            $validated['is_settle'] = 0; //未结算
+        }
+
+        LadingBill::query()->updateOrCreate(
+            ['id' => $id],
+            $validated
+        );
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 删除
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function delete(Request $request)
+    {
+        $ids = $request->input('ids');
+        LadingBill::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+}

File diff suppressed because it is too large
+ 1674 - 0
app/Http/Controllers/Admin/LinkController.php


+ 33 - 0
app/Http/Controllers/Admin/LogController.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\SystemLog;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+
+/**
+ * 日志
+ * Class LogController
+ * @package App\Http\Controllers\Admin
+ */
+class LogController extends Controller
+{
+    //日志列表
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/log/index');
+        }
+
+        if ($keyword=$request->input('keyword')) {
+            $filter[] = ['content', 'like', '%' .$keyword . '%'];
+        }
+        $records = SystemLog::query()->where($filter ?? [])->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        return response()->json([
+            'rows' => $records->items(),
+            'total' => $records->total()
+        ]);
+    }
+}

+ 215 - 0
app/Http/Controllers/Admin/MessageController.php

@@ -0,0 +1,215 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Message;
+use App\Http\Models\MessageStatus;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use App\Http\Services\YouMenGApiService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Validation\Validator;
+
+
+/**
+ * 项目管理下的详情的工作任务
+ * Class WorkTaskController
+ * @package App\Http\Controllers\Admin
+ */
+class MessageController extends Controller
+{
+
+    /**
+     * 主页
+     * @param Request $request
+     * @return \\Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/message/index');
+        }
+        $pageSize = $request->input('pageSize') ?? 10;
+        $messageList = Message::query()->whereNull('deleted_at')->paginate($pageSize);
+        foreach ($messageList as $key => $value) {
+            $messageList[$key]->messageType = Message::MESSAGE_TYPE[$value->type];
+        }
+        return $this->ajaxListSuccess($messageList->items() ?? [], $messageList->total() ?? 0);
+    }
+
+    /**
+     * 添加或修改
+     * @param Request $request
+     * @param int $id
+     * @return \\Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function addOrUpdate(Request $request, $id = 0)
+    {
+        if (!empty($id)) {
+            $messageInfo = Message::query()->where('id', $id)->first();
+        }
+        if (!$request->ajax()) {
+            return view('admin/message/add', [
+                'messageInfo' => $messageInfo ?? []
+            ]);
+        }
+
+        $data = $request->all();
+        $rules = [
+            'title' => 'required|max:255',
+            'message' => 'required|max:255',
+            'url' => 'required|max:255',
+            'img' => 'required|max:255',
+        ];
+        $message = [
+            'title.required' => '标题不能为空',
+            'message.required' => '内容不能为空',
+            'url.required' => '链接不能为空',
+            'img.required' => 'banner不能为空',
+        ];
+        $validator = \Illuminate\Support\Facades\Validator::make($data, $rules, $message);
+
+        if ($validator->fails()) {
+            $msg = $validator->errors()->all();
+            return $this->error($msg[0]);
+        }
+
+        if (empty($id)) {
+            $result = Message::query()->insert($data);
+        } else {
+            $result = Message::query()->where('id', $id)->update($data);
+        }
+        if (empty($result)) {
+            return $this->error('服务器发生错误');
+        }
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 删除消息
+     * @param int $id
+     * @return \\Illuminate\Http\JsonResponse
+     */
+    public function delete($id = 0)
+    {
+        $result = Message::query()->where('id', $id)->update(['deleted_at' => date('Y-m-d H:i:s')]);
+        if (empty($result)) {
+            return $this->error('服务器发生错误');
+        }
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 消息推送
+     * @param Request $request
+     * @param int $id
+     * @return \\Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function push(Request $request, $id = 0)
+    {
+        if (!$request->ajax()) {
+            $siteList = Site::query()->select('id', 'cn_title')->orderBy('cn_title', 'desc')->get();
+            return view('admin/message/push', [
+                'id' => $id,
+                'siteList' => $siteList,
+            ]);
+        }
+
+        $messageInfo = Message::query()->where('id', $id)->first();
+        if (empty($messageInfo)) {
+            return $this->error('模版不存在');
+        }
+        $siteIds = $request->input('siteIds') ?? 0;
+        $siteList2 = Site::query()->whereIn('id', $siteIds)->get();
+        foreach ($siteList2 as $siteInfo) {
+
+            if (in_array($messageInfo->type, [1, 2, 3, 4, 5])) {
+                if ($messageInfo->type == 1) {//网站到期
+                    $date = $siteInfo->expired_at;
+                }
+                if ($messageInfo->type == 2) {//证书到期
+                    $date = $siteInfo->cert_expired_date;
+                }
+                if ($messageInfo->type == 3) {//cdn到期
+                    $date = $siteInfo->cdn_expired_date;
+                }
+                if ($messageInfo->type == 4) {//域名到期
+                    $date = $siteInfo->domain_expired_date;
+                }
+                if ($messageInfo->type == 5) {//服务期到期
+                    $date = $siteInfo->renewal_end_at;
+                }
+
+                $message = str_replace('{$date}', $date ?? '本月', $messageInfo->message);
+                $message = str_replace('{$website}', $siteInfo->domain, $message);
+            } else {
+                $userIdList = DB::table('user_has_sites')->where('site_id', $siteInfo->id)->pluck('user_id');
+                $userList = User::query()->whereIn('id', $userIdList)->get();
+
+                foreach ($userList as $key => $value) {
+                    if ($value->role_id == 7) {
+                        $user = $value->nickname;
+                        $mobile = $value->phone;
+                        break;
+                    }
+                }
+                $message = str_replace('{$user}', $user ?? '小擎', $messageInfo->message);
+                $message = str_replace('{$mobile}', $mobile ?? '0571-86262059', $message);
+            }
+            $update = [
+                'title' => $messageInfo->title,
+                'message' => $message,
+                'url' => $messageInfo->url,
+                'img' => $messageInfo->img,
+                'type' => $messageInfo->type,
+                'site_id' => $siteInfo->id,
+                'message_id' => $id,
+                'date' => strtotime(date('Y-m 00:00:00')),
+            ];
+            MessageStatus::query()->insert($update);
+        }
+        return $this->success('操作成功');
+    }
+
+    /**
+     * 详情
+     * @param Request $request
+     * @param $messageId
+     * @return \\Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function details(Request $request, $messageId)
+    {
+        if (!$request->ajax()) {
+            return view('admin/message/details', [
+                'messageId' => $messageId
+            ]);
+        }
+        $keyword = $request->input('keyword') ?? '';
+        $pageSize = $request->input('pageSize') ?? 10;
+
+        $messageStatus = MessageStatus::query();
+        if (!empty($keyword)) {
+            $siteIds = Site::query()->where('cn_title', 'like', $keyword . '%')->pluck('id');
+            $messageStatus->whereIn('site_id', $siteIds);
+        }
+
+        $messageList = $messageStatus->where('message_id', $messageId)->paginate($pageSize);
+        $siteList = Site::query()->pluck('cn_title', 'id')->toArray();
+
+        foreach ($messageList as $key => $value) {
+
+            $messageList[$key]['site'] = $siteList[$value['site_id']];
+            $messageList[$key]->messageType = Message::MESSAGE_TYPE[$value->type];
+
+            $messageList[$key]['messageStatus'] = '待发送';
+            if ($value['status'] == 1) {
+                $messageList[$key]['messageStatus'] = '成功';
+            }
+        }
+
+        return $this->ajaxListSuccess($messageList->items() ?? [], $messageList->total() ?? 0);
+
+    }
+}

+ 613 - 0
app/Http/Controllers/Admin/MeterController.php

@@ -0,0 +1,613 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Exports\BasicExport;
+use App\Http\Logics\Admin\MeterLogic;
+
+use App\Http\Models\Article;
+use App\Http\Models\Asset;
+use App\Http\Models\Feedback;
+use App\Http\Models\LinkTaskDetail;
+use App\Http\Models\Product;
+use App\Http\Models\Role;
+use App\Http\Models\ServiceFeedback;
+use App\Http\Models\Site;
+use App\Http\Models\SocialPublish;
+use App\Http\Models\UrlCheck;
+use App\Http\Requests\Meter\ServiceFeedbackRequest;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use App\Http\Traits\HasSites;
+
+/**
+ * 控制台
+ * Class MeterController
+ * @package App\Http\Controllers\Admin
+ */
+class MeterController extends Controller
+{
+    use HasSites;
+    protected $logic;
+
+    public function __construct(MeterLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+    //总控制台
+    public function index()
+    {
+
+        $userSites = $this->userSites();
+        $validSiteIds = $userSites->pluck('id')->toArray();
+        $validOldSiteIds = array_filter(array_column($userSites->toArray(), 'id'));
+
+        $data = [];
+        $rankDatabase = DB::connection('rank');
+
+        $cacheKey = sprintf('index_%s_%s', date('Ym'), auth()->id());
+
+        $cacheContent = Cache::get($cacheKey);
+        if ($cacheContent) {
+            $data = $cacheContent;
+            goto render;
+        }
+        //关键词排名急剧下降
+        $data['keyDropSites'] = $this->logic->indexDropKeywordSites($rankDatabase, $validSiteIds);
+        //关键词达标项目
+        $data['reachSites'] = $this->logic->indexReachSites($validSiteIds);
+        //建站完成的
+        $data['buildDoneSites'] = $this->logic->indexBuildDoneSites($validSiteIds);
+        //证书过期项目
+        $data['certExpiredSites'] = $this->logic->indexCertExpiredSites($validSiteIds);
+        //域名过期项目
+        $data['domainExpiredSites'] = $this->logic->indexDomainExpiredSites($validSiteIds);
+        //月均流量 100 300 500 1000
+        $data['masterMeterTraffics'] = $this->logic->indexMasterMeterTraffics($rankDatabase, $validSiteIds);
+        //关键词总量
+        $data['keywordTotal'] = $this->logic->indexKeywordTotal($rankDatabase, $validOldSiteIds);
+        //项目类型数据
+        $data['siteStatus'] = $this->logic->indexSiteStatus($validSiteIds);
+        //公司类型统计
+        $data['nature'] = $this->logic->indexNature($validSiteIds);
+        //根据公司行业
+        $data['business'] = $this->logic->indexBusiness($validSiteIds);
+        //渠道销售占比
+        $data['sales'] = $this->logic->indexSales($validSiteIds);
+        //最新5个项目列表
+        $data['last5Sites'] = $this->logic->indexLast5Sites($rankDatabase, $validSiteIds);
+        //外链库折线图
+        $data['linksLine'] = $this->logic->indexLinksLine();
+
+        Cache::put($cacheKey, $data, new \DateTime(date('Y-m-t 23:59:59')));
+        render:
+
+        //流程过期提醒
+        $data['expireSiteProcessList'] = $this->logic->indexExpireSiteProcessList($validSiteIds);
+        $data['indexCustomerLog'] = $this->logic->indexCustomerLog($validSiteIds);
+        //网址异常的
+        $data['urlFailed'] = $urlFailed = UrlCheck::query()->whereIn('site_id', $validSiteIds)->get();
+
+        return view('admin/meter/index', $data);
+    }
+
+    protected function lastSixMonths()
+    {
+        $sixMonth = [];
+        for ($i = 0; $i < 6; $i++) {
+            $sixMonth[] = date("Y-m", strtotime("first day of -$i month"));
+        }
+        return $sixMonth;
+    }
+
+    //强制删除缓存
+    public function forgetCache(Request $request)
+    {
+        $which = $request->input('which');
+        $siteId = $request->input('siteId');
+        if ($which == 'index') {
+            $cacheKey = sprintf('index_%s_%s', date('Ym'), auth()->id());
+        } elseif ($which == 'single') {
+            $cacheKey = sprintf('single_%s_site_id%s', date('Ym'), $siteId);
+        } else {
+            return response()->json(['message' => '参数错误'], 422);
+        }
+
+        Cache::forget($cacheKey);
+        return response()->json(['message' => '操作成功']);
+    }
+    //针对单个站点的控制台
+    public function singleSite(Request $request)
+    {
+        $siteId = $request->input('siteId');
+        if (!$siteId) {
+            $singleSite = $this->logic->getSingleSite();
+            $siteId = $singleSite->id ?? 0;
+        }
+
+        $data = [];
+        $site = Site::query()->withCount(['articles'])->with(['server', 'users'])->where(['id' => $siteId])->first();
+        if (!$site) {
+            return view('admin/errors/tips', [
+                'tips' => '项目站点信息不存在'
+            ]);
+        }
+        if (!$site->database) {
+            return view('admin/errors/tips', [
+                'tips' => '项目数据库未分配'
+            ]);
+        }
+
+        if (!$site->server) {
+            return view('admin/errors/tips', [
+                'tips' => '服务器未分配'
+            ]);
+        }
+
+        $data['site'] = $site;
+        $config = [
+            'connection_name' => sprintf('connection_name_%s', $siteId),
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => $site->database,
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd,
+        ];
+        config_connection($config);
+        $masterDatabase = DB::connection($config['connection_name']);
+        $rankDatabase = DB::connection('rank');
+
+        try {
+            //检测是否有此数据库
+            $result = $masterDatabase->select(sprintf("show databases like '%s'", $site->database));
+
+            if (!$result) {
+                return view('admin/errors/tips', [
+                    'tips' => $site->cn_title . '项目数据库不存在'
+                ]);
+            }
+        } catch (\Throwable $throwable) {
+            Log::warning(var_export($throwable->getMessage(), 1));
+            return view('admin/errors/tips', [
+                'tips' => $site->cn_title . '项目数据库信息连接失败'
+            ]);
+        }
+
+        $site->linkDetailsCount = LinkTaskDetail::query()->selectRaw('link_id')
+            ->where([
+                'redundant_site_id' => $siteId,
+                'enable' => 1,
+                'status' => 5
+            ])->groupBy('link_id')->get()->count();
+
+
+        $cacheKey = sprintf('single_%s_site_id%s', date('Ym'), $siteId);
+        $cacheContent = Cache::get($cacheKey);
+        if ($cacheContent) {
+            $data = $cacheContent;
+            goto render;
+        }
+
+        //月报记录
+        $historyFlowYm = $rankDatabase->table('report_flow')
+            ->select(['flow_ym', 'project_id'])
+            ->where('project_id', $site->old_id)->orderByDesc('flow_ym')
+            ->limit(6)
+            ->get();
+        $data['historyFlowYm'] = $historyFlowYm;
+
+        //项目概况 引用修改
+        $this->logic->singleSiteProfile($rankDatabase, $site);
+
+        //网页浏览量 访问量
+        $data['pvUvData'] = $this->logic->singlePvUvcData($masterDatabase);
+
+        //首页关键词数
+        $data['topKeywordData'] = $this->logic->singleTopKeywordData($rankDatabase, $site->old_id);
+
+        //询盘
+        $data['inquireTotalData'] = $masterDatabase->table('user_msg')
+            ->where([['is_delete', '=', 0]])->count();
+        $data['inquireData'] = $this->logic->singleInquireData($masterDatabase);
+
+        //网页数
+        $data['pageCountData'] = $this->logic->singlePageCount($masterDatabase);
+
+        //关键词达标率
+        $data['topKeywordRate'] = [
+            'goal' => $site->keyword_goal,
+            'last1Reach' => $data['topKeywordData']['top10']['last1'],
+            'last2Reach' => $data['topKeywordData']['top10']['last2']['val'],
+            'last3Reach' => $data['topKeywordData']['top10']['last3']['val'],
+        ];
+
+        //网站展示量
+        $data['webmasterEffectImpressionsTotal'] = $rankDatabase->table('webmaster_effect')->selectRaw('ym,SUM(clicks) as clicks,SUM(impressions) AS impressions')
+                ->where(['project_id' => $site->old_id])->first()->impressions ?? 0;
+        $data['webmasterEffect'] = $this->logic->singleWebmasterEffectData($rankDatabase, $site->old_id);
+
+        //文章达标率
+        $data['articleRate'] = $this->logic->singleArticleRate($site);
+
+        //外链达标率
+        $data['linkRate'] = $this->logic->singleLinkRate($site);
+
+        //我的文章
+        $data['myArticles'] = $this->logic->singleMyArticles($siteId);
+
+        //我的外链任务
+        $data['myLinkTasks'] = $this->logic->singleMyLinksTasks($siteId);
+
+        //流量折线图
+        $data['trafficLine'] = $this->logic->singleTrafficLine($masterDatabase);
+        //询盘折线图
+        $data['inquireLine'] = $this->logic->singleInquireLine($masterDatabase);
+        //关键词折线图
+        $data['keywordLine'] = $this->logic->singleKeywordLine($rankDatabase, $site->old_id);
+
+        //引擎力学堂
+        $data['assets'] = Asset::query()->with('assetType')->get();;
+        //客服人员
+        $data['serverUsers'] = $site->users->where('role_id', Role::TYPE_SERVER);
+        //引擎力产品推荐
+        $data['products'] = Product::query()->where(['status' => 1])->get();
+
+        Cache::put($cacheKey, $data, new \DateTime(date('Y-m-t 23:59:59')));
+        render:
+
+        if (!$site->reach_at) {
+            $data['reachDaysData'] = '未达标';
+        } else {
+            $diffDatetime = date_diff(date_create($site->reach_at), date_create());
+            $data['reachDaysData']=$diffDatetime->days>=0? abs($diffDatetime->days).'天':"未达标";
+        }
+
+        //关键词达标
+        $data['reachDays'] = $this->logic->singleReachDays($site);
+        //建站完成的
+        $data['buildDone'] = $this->logic->singleBuildDone($siteId);
+        ////网址异常
+        $data['urlFailed'] = $this->logic->singleUrlFailed($siteId);
+        //关键词排名急剧下降
+		//询盘
+        $data['inquireTotalData'] = $masterDatabase->table('user_msg')
+            ->where([['is_delete', '=', 0]])->count();
+        $data['inquireData'] = $this->logic->singleInquireData($masterDatabase);
+        //证书过期项目
+        $data['isCertExpired'] = $site->cert_expired_date ? (strtotime($site->cert_expired_date) < strtotime('+1 month')) : 0;
+        //域名过期项目 domain_expired_date
+        $data['isDomainExpired'] = $site->cert_expired_date ? (strtotime($site->domain_expired_date) < strtotime('+1 month')) : 0;
+
+        $data['keywordLessVal'] = $this->logic->singleKeywordLessVal($rankDatabase, $site->old_id);
+
+        return view('admin/meter/single', $data);
+    }
+
+
+    public function serviceFeedback(ServiceFeedbackRequest $request)
+    {
+        $validated = $request->validated();
+        $site = Site::query()->select(['cn_title', 'domain'])->where(['id' => $validated['site_id']])->first();
+        if (!$site) {
+            return response()->json(['message' => '项目信息不存在'], 422);
+        }
+        $validated['creator_id'] = Auth::id();
+        $validated = $validated + $site->toArray();
+        ServiceFeedback::query()->create($validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //客户总控台 投诉表单
+    public function feedback(Request $request)
+    {
+        $content = trim($request->input('content'));
+        $faq = trim($request->input('faq'));
+        if (!$content) {
+            return response()->json(['message' => '请填写内容'], 422);
+        }
+        if (mb_strlen($content) > 2000) {
+            return response()->json(['message' => '内容不能超过两千字'], 422);
+        }
+        Feedback::query()->create([
+            'site_id' => $request->input('siteId'),
+            'content' => $content,
+            'faq' => $faq,
+            'creator_id' => Auth::id()
+        ]);
+        return response()->json(['message' => '操作成功']);
+    }
+    //达标项目导出
+    public function exportReachSitesExcel()
+    {
+        $userSites = $this->userSites();
+        $validSiteIds = $userSites->pluck('id')->toArray();
+
+        $siteRecords = $this->logic->indexReachSites($validSiteIds);
+
+
+        $dataList[] = ['域名', '项目名称', '达标日期', '分数', '尾款'];
+
+        foreach ($siteRecords as $site) {
+            $sitesProcess = $site->sitesProcess->first();
+            $dataList[] = [
+                'domain' => $site->domain,
+                'cn_title' => $site->cn_title,
+                'reach_at' => $site->reach_at,
+                'score' => $sitesProcess->evaluate['score'] ?? '',
+                'reach_payment' => $site->sitePayment->reach ?? ''
+            ];
+        }
+        return (new BasicExport($dataList))->download(sprintf('达标项目列表%s.xls', date('YmdHis')));
+    }
+    //建站项目导出
+    public function exportBuildSitesExcel()
+    {
+        $userSites = $this->userSites();
+        $validSiteIds = $userSites->pluck('id')->toArray();
+
+        $siteRecords = $this->logic->indexBuildDoneSites($validSiteIds);
+
+
+        $dataList[] = ['域名', '项目名称', '达标日期', '分数', '中期款'];
+
+        foreach ($siteRecords as $site) {
+            $sitesProcess = $site->sitesProcess->first();
+            $dataList[] = [
+                'domain' => $site->domain,
+                'cn_title' => $site->cn_title,
+                'reach_at' => $site->reach_at,
+                'score' => $sitesProcess->evaluate['score'] ?? '',
+                'done_payment' => $site->sitePayment->done ?? ''
+            ];
+        }
+        return (new BasicExport($dataList))->download(sprintf('建站完成项目列表%s.xls', date('YmdHis')));
+    }
+
+
+    protected function getTime($dateRange)
+    {
+        if (!$dateRange) {
+            return null;
+        }
+        $startDate = substr($dateRange, 0, 10);
+        $endDate = substr($dateRange, -10, -1);
+        return [
+            ['reach_at', '>=', sprintf('%s 00:00:00', $startDate)],
+            ['reach_at', '<=', sprintf('%s 23:59:59', $endDate)]
+        ];
+    }
+    //筛选达标站点
+    public function filterReachSites(Request $request)
+    {
+
+        $dateRangeFilter = $this->getTime($request->input('dateRange'));
+
+        $userSites = $this->userSites();
+        $validSiteIds = $userSites->pluck('id')->toArray();
+
+        $siteRecords = $this->logic->indexReachSites($validSiteIds, $dateRangeFilter);
+
+        $dataList = [];
+
+        foreach ($siteRecords as $site) {
+            $sitesProcess = $site->sitesProcess->first();
+            $dataList[] = [
+                'domain' => $site->domain,
+                'cn_title' => $site->cn_title,
+                'reach_at' => $site->reach_at,
+                'score' => $sitesProcess->evaluate['score'] ?? '',
+                'reach_payment' => $site->sitePayment->reach ?? ''
+            ];
+        }
+
+        return response()->json(['data' => $dataList]);
+    }
+
+    //筛选建站站点
+    public function filterBuildSites(Request $request)
+    {
+        $dateRangeFilter = $this->getTime($request->input('dateRange'));
+
+        $userSites = $this->userSites();
+        $validSiteIds = $userSites->pluck('id')->toArray();
+
+        $siteRecords = $this->logic->indexBuildDoneSites($validSiteIds, $dateRangeFilter);
+
+        $dataList = [];
+
+        foreach ($siteRecords as $site) {
+            $sitesProcess = $site->sitesProcess->first();
+            $dataList[] = [
+                'domain' => $site->domain,
+                'cn_title' => $site->cn_title,
+                'reach_at' => $site->reach_at,
+                'score' => $sitesProcess->evaluate['score'] ?? '',
+                'done_payment' => $site->sitePayment->done ?? ''
+            ];
+        }
+
+        return response()->json(['data' => $dataList]);
+    }
+    //品宣仪表
+    public function propaganda(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/meter/propaganda');
+        }
+
+        $keyword = $request->input('keyword');
+
+        if ($status = $request->input('status')) {
+            $condition['status'] = $status;
+        }
+
+        $builder = Site::query()->withCount([
+//            'renewHistory',
+            'socialPublish' => function (Builder $query) {
+                $query->whereNull('social_sync_at');
+            },
+
+            'socialPublish as socialPublishFailCount' => function (Builder $query) {
+                $query->whereJsonContains('result_status', ['pin' => SocialPublish::STATUS_FAILURE])
+                    ->orWhereJsonContains('result_status', ['facebook' => SocialPublish::STATUS_FAILURE])
+                    ->orWhereJsonContains('result_status', ['twitter' => SocialPublish::STATUS_FAILURE]);
+            },
+
+            'articles as month_articles_count' => function (Builder $query) {
+                $query->where('created_at', '>', date('Y-m-01 00:00:00'));
+            }, 'articles as no_sync_articles_count' => function (Builder $query) {
+                $query->whereNull('sync_at');
+            },
+            'articles as weekArticlesCount' => function (Builder $query) {
+                $query->where('created_at', '>', date('Y-m-d H:i:s', strtotime('-2 week')));
+            },
+            'articles as monthArticlesCount' => function (Builder $query) {
+                $query->where('created_at', '>', date('Y-m-d H:i:s', strtotime('-1 month')));
+            },
+            'linkDetails' => function (Builder $query) {
+                $query->where([['enable', '=', 1], ['status', '=', 5]]);
+            },
+            'linkDetails as month_link_details_count' => function (Builder $query) {
+                $query->where([['enable', '=', 1], ['status', '=', 5]])->where('created_at', '>', date('Y-m-01 00:00:00'));;
+            },
+            'linkUrls' => function (Builder $query) {
+                $query->where('link_tasks_detail.enable', 1)
+                    ->where('link_tasks_detail.status', 5)
+                    ->where('link_tasks_url.status', 5);
+            },
+            'linkUrls as month_link_urls_count' => function (Builder $query) {
+                $query->where('link_tasks_detail.enable', 1)
+                    ->where('link_tasks_detail.status', 5)
+                    ->where('link_tasks_url.status', 5)
+                    ->where('link_tasks_url.created_at', '>', date('Y-m-01 00:00:00'));
+            },
+            'linkUrls as lastMonthLinkUrlsCount' => function (Builder $query) {
+                $query->where('link_tasks_url.created_at', '>', date('Y-m-d H:i:s', strtotime('-1 month')));
+            }
+        ])->with(['users', 'renewHistory'])->whereIn('status', [2, 3])->where($condition ?? [])->where(function (Builder $builder) use ($keyword) {
+            if ($keyword) {
+                $builder->where('domain', 'like', '%' . $keyword . '%')
+                    ->orWhere('cn_title', 'like', '%' . $keyword . '%');
+            }
+        });
+
+        $sortName = $request->input('sortName');
+        $sortOrder = $request->input('sortOrder');
+
+        if ($sortName == 'statusTitle') {
+            $builder->orderBy('status', $sortOrder);
+        }
+
+
+        $records = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        $oldIds = array_filter(array_column($items, 'old_id'));
+
+        $rankConnection = DB::connection('rank');
+
+        $lastListReportMap = $rankConnection->table('project_listreport')->whereIn('project_id', $oldIds)->where([
+            'ym' => date('Ym', strtotime('first day of -1 month'))
+        ])->get()->keyBy('project_id')->toArray();
+
+        $last2ListReportMap = $rankConnection->table('project_listreport')->whereIn('project_id', $oldIds)->where([
+            'ym' => date('Ym', strtotime('first day of -2 month'))
+        ])->get()->keyBy('project_id')->toArray();
+
+
+        array_walk($items, function ($item) use (&$lastListReportMap, &$last2ListReportMap) {
+
+            $lastRenew = $item->renewHistory->last();
+            //唉。
+            if ($lastRenew) {
+                $publishArticleCount = Article::query()->where([
+                    ['site_id', '=', $item->id],
+                    ['created_at', '>', $lastRenew->created_at]
+                ])->count();
+            } else {
+                $publishArticleCount = Article::query()->where([
+                    ['site_id', '=', $item->id]
+                ])->count();
+            }
+
+            $item->managers_title = implode('-', $item->users->where('role_id', Role::TYPE_MANAGER)->pluck('nickname')->toArray());
+            $item->statusTitle = Site::STATUS_MAP[$item->status] ?? '';
+
+            if ($item->old_id) {
+
+                if (empty($lastListReportMap[$item->old_id])) {
+                    $item->traffic = '-';
+                } else {
+                    $last2Traffic = $last2ListReportMap[$item->old_id]->traffic ?? 0;
+                    $lastTraffic = $lastListReportMap[$item->old_id]->traffic;
+
+                    $isIncrease = ($lastTraffic - $last2Traffic) > 0;
+                    $item->traffic = sprintf('<span class="%s" title="%s">%s</span>', $isIncrease, $last2Traffic, $lastTraffic);
+                }
+            } else {
+                $item->traffic = '未关联';
+            }
+
+            $item->online_date = $item->online_at ? date('Y-m-d', strtotime($item->online_at)) : '';
+            //期数
+            $item->renew_count = $item->renewHistory->count() + 1;
+
+
+            if ($item->socialPublishFailCount > 0) {
+                $failSocialClass = 'socialFail';
+            }
+            $item->social_queue_count = sprintf(
+                '<div class="%s">%s|%s</div>',
+                ($failSocialClass ?? ''),
+                $item->social_publish_count,
+                $item->socialPublishFailCount
+            );
+
+
+            if ($item->article_goal <= $publishArticleCount) {
+                $articleReachClass = 'reachGoal';
+            }
+
+            $item->article_goal_text = sprintf('<div class="%s">%s</div>', ($articleReachClass ?? ''), $item->article_goal);
+
+            if ($item->link_goal <= $item->link_urls_count) {
+                $linkReachClass = 'reachGoal';
+            }
+            $item->link_goal_text = sprintf('<div class="%s">%s</div>', ($linkReachClass ?? ''), $item->link_goal);
+
+
+            if ($item->status == 2 && $item->weekArticlesCount < 1) {//实时期
+                $articleClass = 'articleUnUpdate';
+            }
+            if ($item->status == 3 && $item->monthArticlesCount < 1) {//服务期
+                $articleClass = 'articleUnUpdate';
+            }
+
+            //外链一个月没更新
+            if ($item->lastMonthLinkUrlsCount < 1) {
+                $linkClass = 'linkUnUpdate';
+            }
+
+            $item->articles_count_mul = sprintf(
+                '<div class="%s">%s<b>|</b><span style="color: red">%s</span><b>|</b>%s</div>',
+                ($articleClass ?? ''),
+                $publishArticleCount, $item->month_articles_count, $item->no_sync_articles_count
+            );
+            $item->link_details_count_mul = sprintf('%s|%s', $item->link_details_count, $item->month_link_details_count);
+
+            $item->link_urls_count_mul = sprintf('<div class="%s">%s|%s<div>', ($linkClass ?? ''), $item->link_urls_count, $item->month_link_urls_count);
+
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+
+}

+ 49 - 0
app/Http/Controllers/Admin/NumController.php

@@ -0,0 +1,49 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 每日数据
+ * Class NumController
+ * @package App\Http\Controllers\Admin
+ */
+class NumController extends Controller
+{
+    //每日数据仪表
+    public function index()
+    {
+        $ymdList = [];
+        for ($i = 1; $i <= 7; $i++) {
+            $ymdList[] = date('Ymd', strtotime('-' . $i . ' day'));
+        }
+
+        $numList = DB::table('num')
+            ->selectRaw('SUM(traffic) AS traffic,SUM(inquire) AS inquire,SUM(top10) AS top10,ymd')
+            ->whereIn('ymd', $ymdList)
+            ->groupBy('ymd')->get()->keyBy('ymd')->toArray();
+        $trafficY = $inquireY = $keywordY = [];
+        foreach ($ymdList as $ymd) {
+            $trafficY[] = $numList[$ymd]->traffic ?? 0;
+            $inquireY[] = $numList[$ymd]->inquire ?? 0;
+            $keywordY[] = $numList[$ymd]->top10 ?? 0;
+        }
+
+        return view('admin.num.index', [
+            'trafficLine' => [
+                'xAxis' => $ymdList,
+                'yAxis' => $trafficY,
+            ],
+            'inquireLine' => [
+                'xAxis' => $ymdList,
+                'yAxis' => $inquireY,
+            ],
+            'keywordLine' => [
+                'xAxis' => $ymdList,
+                'yAxis' => $keywordY,
+            ],
+        ]);
+    }
+}

File diff suppressed because it is too large
+ 1034 - 0
app/Http/Controllers/Admin/Plan/TaskController.php


+ 418 - 0
app/Http/Controllers/Admin/ProcessController.php

@@ -0,0 +1,418 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/4/16 0016
+ * Time: 9:51
+ */
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Logics\Admin\ProcessLogic;
+use App\Http\Models\Process;
+use App\Http\Models\Site;
+use App\Http\Models\SiteProcess;
+use App\Http\Models\SiteRenewHistory;
+use App\Http\Requests\Process\RenewFormRequest;
+use App\Http\Traits\HasSites;
+use App\Libs\Ssh;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+/**
+ *项目流程
+ * Class ProcessController
+ * @package App\Http\Controllers\Admin
+ */
+class ProcessController extends Controller
+{
+    use  HasSites;
+    protected $logic;
+
+    public function __construct(ProcessLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+
+    //客户界面的流程
+    public function customer()
+    {
+
+        $site = $this->hasUserOneSite();
+
+        if (!$site) {
+            return view('admin/errors/tips');
+        }
+
+        $processes = Process::query()->orderByDesc('order')->get();
+        $siteProcessList = SiteProcess::query()->where(['site_id' => $site->id])->get()->keyBy('process_id')->toArray();
+
+        return view('admin/process/customer', [
+            'processes' => $processes,
+            'siteId' => $site->id,
+            'processIdMapSiteProcessList' => $siteProcessList,
+            'site' => Site::query()->select(['id', 'domain'])->where(['id' => $site->id])->first()
+        ]);
+
+    }
+
+    /**
+     * 项目详情中的流程
+     * @param $siteId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function siteProcess($siteId)
+    {
+        $processes = Process::query()->orderByDesc('order')->get();
+        $siteProcessList = SiteProcess::query()->where(['site_id' => $siteId])->get()->keyBy('process_id')->toArray();
+
+        return view('admin/process/site_process', [
+            'processes' => $processes,
+            'siteId' => $siteId,
+            'processIdMapSiteProcessList' => $siteProcessList,
+            'site' => Site::query()->select(['id', 'domain'])->where(['id' => $siteId])->first()
+        ]);
+    }
+
+    /**
+     * 确认
+     * @param $id
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function confirm($id)
+    {
+        $siteProcess = SiteProcess::query()->where(['id' => $id])->first();
+        if (!$siteProcess) return response()->json(['message' => '具体流程信息不存在'], 400);
+
+        if ($siteProcess->active != 2) {
+            return response()->json(['message' => '此流程已完成请刷新页面查看最新数据'], 400);
+        }
+
+        //判断是否全部完成检查项
+        if (in_array($siteProcess->process_id, Process::HAS_CHECK_ITEM_IDS)) {
+            $process = Process::query()->where(['id' => $siteProcess->process_id])->first();
+            if (!$process) {
+                return response()->json(['message' => '主流程信息不存在'], 400);
+            }
+            if (array_diff(array_column($process->check_item_scopes, 'inx'), $siteProcess->check_items ?? [])) {
+                return response()->json(['message' => '请先完成全部检查项'], 400);
+            }
+        }
+
+        $siteProcess->active = 1;
+        $siteProcess->save();
+
+        $logic = new ProcessLogic;
+        $logic->changeStatus($siteProcess);
+        $logic->nextProcess($siteProcess);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 流程检查项
+     * @param Request $request
+     * @param $siteProcessId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function checkItem(Request $request, $siteProcessId)
+    {
+        $siteProcess = SiteProcess::query()->with('process')->where(['id' => $siteProcessId])->first();
+        if (!$siteProcess) return response()->json(['message' => '流程信息不存在'], 400);
+        if (!$request->ajax()) {
+            return view('admin/process/check_item', [
+                'siteProcess' => $siteProcess
+            ]);
+        }
+        $checkItems = $request->input('checkItems');
+        if (!is_array($checkItems)) return response()->json(['message' => '检查项参数错误'], 400);
+        $siteProcess->check_items = $checkItems;
+        $siteProcess->save();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 续签表单
+     * @param RenewFormRequest $request
+     * @param $siteProcessId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function renewForm(RenewFormRequest $request, $siteProcessId)
+    {
+        $siteProcess = SiteProcess::query()->where(['id' => $siteProcessId])->first();
+        if (!$siteProcess) return response()->json(['message' => '续签流程信息不存在'], 400);
+
+        if (!$request->ajax()) {
+            return view('admin/process/renew_form', [
+                'siteProcessId' => $siteProcessId
+            ]);
+        }
+
+        $site = Site::query()->where(['id' => $siteProcess->site_id])->first();
+        if (!$site) return response()->json(['message' => '站点信息不存在'], 400);
+
+        $validated = $request->validated();
+        $validated['site_id'] = $siteProcess->site_id;
+
+
+        $recordBak = [
+            'keyword_goal' => $site->keyword_goal,
+            'link_goal' => $site->link_goal,
+            'article_goal' => $site->article_goal,
+            'other_demand' => $site->other_demand
+        ];
+        $validated['remark'] = json_encode($recordBak);
+
+        SiteRenewHistory::query()->create($validated);
+
+        $site->update([
+            'keyword_goal' => $validated['keyword_goal'],
+            'link_goal' => $validated['link_goal'],
+            'article_goal' => $validated['article_goal'],
+            'other_demand' => $validated['other_demand']
+        ]);
+
+        return response()->json(['message' => '操作成功']);
+
+    }
+
+    //初始化项目流程
+    public function initProcess($siteId)
+    {
+        if (SiteProcess::query()->where(['site_id' => $siteId])->exists()) {
+            return response()->json(['message' => '流程已经存在不需要初始化'], 400);
+        }
+        $this->logic->initProcess($siteId);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //续费历史记录
+    public function renewHistory($siteId)
+    {
+        $historyList = SiteRenewHistory::query()->where(['site_id' => $siteId])->orderByDesc('id')->get();
+        return view('admin/process/renew_history', [
+            'historyList' => $historyList
+        ]);
+    }
+
+    //打分和反馈
+    public function scoreFeedback(Request $request, $id)
+    {
+
+        $siteProcess = SiteProcess::query()->where(['id' => $id])->first();
+        if (!$siteProcess) return response()->json(['message' => '流程信息不存在'], 400);
+        if (!$request->ajax()) {
+            return view('admin/process/score_feedback', [
+                'siteProcess' => $siteProcess
+            ]);
+        }
+
+        $inputs = $request->input();
+        if (empty($inputs['score']) || empty($inputs['feedback'])) {
+            return response()->json(['message' => '请填写表单内容'], 422);
+        }
+
+        if ($siteProcess->active != 2) {
+            return response()->json(['message' => '此流程已完成请刷新页面查看最新数据'], 400);
+        }
+        $siteProcess->active = 1;
+        $siteProcess->save();
+
+        $logic = new ProcessLogic;
+        $logic->changeStatus($siteProcess);
+        $logic->nextProcess($siteProcess);
+
+        $siteProcess->evaluate = [
+            'score' => $inputs['score'],
+            'feedback' => $inputs['feedback']
+        ];
+        $siteProcess->save();
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //回复
+    public function reply(Request $request, $siteProcessId)
+    {
+        $siteProcess = SiteProcess::query()->where(['id' => $siteProcessId])->first();
+        if (!$siteProcess) return response()->json(['message' => '流程信息不存在'], 400);
+        if (!$request->ajax()) {
+            return view('admin/process/reply', [
+                'siteProcess' => $siteProcess
+            ]);
+        }
+        $reply = $request->input('reply');
+        if (!$reply) {
+            return response()->json(['message' => '回访记录不能为空'], 422);
+        }
+        $siteProcess->reply = $reply;
+        $siteProcess->save();
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 流程激活
+     * @param Request $request
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function active(Request $request)
+    {
+        $siteProcess = SiteProcess::query()->where(['id' => $request->input('siteProcessId')])->first();
+        if (!$siteProcess) {
+            return response()->json(['message' => '流程信息不存在'], 400);
+        }
+        $siteProcess->active = 1;
+        $siteProcess->save();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 文件上传
+     * @param Request $request
+     * @param $siteProcessId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
+     */
+    public function fileUpload(Request $request, $siteProcessId)
+    {
+        $siteProcess = SiteProcess::query()->where(['id' => $siteProcessId])->first();
+        if (!$request->ajax()) {
+            return view('admin/process/file_upload', [
+                'siteProcess' => $siteProcess
+            ]);
+        }
+        if (!$siteProcess) return response()->json(['message' => '流程信息不存在'], 400);
+        $siteProcessFileList = $siteProcess->file_list ?? [];
+        $fileList = $request->input('file_list');
+        if ($fileList) {
+            $mergeFileList = array_merge($fileList, $siteProcessFileList);
+            $siteProcess->file_list = $mergeFileList;
+            $siteProcess->save();
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    /**
+     * 项目部署
+     * @param $siteId
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function deploy($siteId)
+    {
+        /*                $siteProcess = SiteProcess::query()->where(['id' => $siteProcessId])->first();
+                        if (!$siteProcess) return response()->json(['message' => '部署流程信息不存在'], 400);*/
+
+        $site = Site::query()->with('server')->where(['id' => $siteId])->first();
+
+        if (empty($site->server)) return response()->json(['message' => '站点服务器信息不存在'], 400);
+        if (empty($site->domain)) return response()->json(['message' => '域名信息不存在'], 400);
+
+        if ($site->database || $site->code_dir) {
+            return response()->json(['message' => '项目数据库信息与代码目录都存在,推测该站点之前已经部署'], 400);
+        }
+        if (!empty($site->database)) {
+            return response()->json(['message' => '项目已部署'], 400);
+        }
+        //if ($siteProcess->deploy) return response()->json(['message' => '项目已部署'], 400);
+
+
+        $ph = pathinfo($site->domain);  // pathinfo() 函数以数组的形式返回文件路径的信息
+        $dbName = 'sdb_' . str_replace(array('-', '.'), '', $ph['filename']);
+        $srcPath = '/repo/_sitetpl/';
+        $tarPath = '/repo/' . $site->domain;
+
+        try {
+            Ssh::factory($site->server->server_ip, $site->server->server_user_name, $site->server->server_passwd);
+        } catch (\Throwable $throwable) {
+            return response()->json(['message' => $throwable->getMessage()], 400);
+        }
+        Log::info(Ssh::exec('cp -rf ' . $srcPath . ' ' . $tarPath));
+
+        //新建数据库
+        $db_config = array(
+            'connection_name' => 'new_db',
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => 'sdb_sucocms',
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd
+        );
+
+        $new_db = config_connection($db_config);
+
+        try {
+            DB::connection($new_db)->statement('create database ' . $dbName);
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['message' => $throwable->getMessage()], 400);
+        }
+
+
+        Log::info(Ssh::exec("mysqldump sdb_sucocms -u root -p" . $site->server->mysql_passwd . " --add-drop-table | mysql " . $dbName . " -u root -p" . $site->server->mysql_passwd));
+
+
+        //数据库配置文件
+        $db_config_data = file_get_contents(public_path() . '/remote/db.conf.php');
+        $db_config_data = str_replace('{$dbname}', $dbName, $db_config_data);
+        $db_config_data = str_replace('{$mysql_user_name}', $site->server->mysql_user_name, $db_config_data);
+        $db_config_data = str_replace('{$mysql_passwd}', $site->server->mysql_passwd, $db_config_data);
+        file_put_contents(public_path() . '/remote/temp.db.conf.php', $db_config_data);
+        Ssh::send(public_path() . '/remote/temp.db.conf.php', $tarPath . '/appdata/conf/db.conf.php');
+
+        //vhost配置文件
+        $vhost_config_data = file_get_contents(public_path() . '/remote/vhost.conf');
+        $vhost_config_data = str_replace('$server_name', $ph['filename'] . '.' . $site->server->server_alias, $vhost_config_data);
+        $vhost_config_data = str_replace('$server_alias', $ph['filename'] . '.' . $site->server->server_alias, $vhost_config_data);
+        $vhost_config_data = str_replace('$domain', $tarPath, $vhost_config_data);
+        file_put_contents(public_path() . '/remote/temp.vhost.conf', $vhost_config_data);
+        Ssh::send(public_path() . '/remote/temp.vhost.conf', '/etc/httpd/conf/vhosts/' . $site->domain . '.conf');
+
+
+        //setting配置文件
+        $setting_config_data = file_get_contents(public_path() . '/remote/setting.conf.php');
+        $setting_config_data = str_replace('{$sitename}', $site->en_title, $setting_config_data);
+        $setting_config_data = str_replace('{$domain}', $site->domain, $setting_config_data);
+        file_put_contents(public_path() . '/remote/temp.setting.conf.php', $setting_config_data);
+        Ssh::send(public_path() . '/remote/temp.setting.conf.php', $tarPath . '/appdata/conf/setting.conf.php');
+
+
+        //设置权限
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/appdata');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/uploads');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/sitemap.php');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/.htaccess');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/tmp.xls');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/tag_tpl.xlsx');
+        Ssh::exec('chmod -R 777 ' . $tarPath . '/wwwroot/robots.txt');
+
+        //重启服务
+        if ($site->server->server_ip == '47.89.219.88') {
+            Log::info(Ssh::exec('/usr/bin/sudo /etc/init.d/httpd reload'));
+
+        } else {
+            Log::info(Ssh::exec('lnmp restart'));
+        }
+        $site->database = $dbName;
+        $site->code_dir = $tarPath;
+        $site->save();
+
+        $deploy=[
+            'db' => $dbName,
+            'dir' => $tarPath,
+            'domain' => sprintf('%s.yinqingli.net', $ph['filename'] . '.' . $site->server->server_alias)
+        ];
+        $data = [
+            'process_id' => 7,
+            'active' => 2,
+            'site_id'=>$siteId,
+            'deploy'=>\GuzzleHttp\json_encode($deploy)
+        ];
+        SiteProcess::query()->insert($data);
+
+        return response()->json(['message' => '部署完成']);
+    }
+
+}

+ 72 - 0
app/Http/Controllers/Admin/ProductController.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Http\Models\Product;
+use App\Http\Requests\ProductSaveRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+/**
+ * 引擎力产品
+ * Class ProductController
+ * @package App\Http\Controllers\Admin
+ */
+class ProductController extends Controller
+{
+    //产品列表
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/product/index');
+        }
+
+        $records = Product::query()->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        $statusMap = Product::STATUS_MAP;
+        array_walk($items, function ($item) use ($statusMap) {
+            $item->status_title = $statusMap[$item->status] ?? '';
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+    //切换状态
+    public function toggleStatus(Request $request)
+    {
+        $ids = $request->input('ids');
+        $toggle = $request->input('toggle');
+        Product::query()->whereIn('id', $ids)->update(['status' => $toggle]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    public function save(ProductSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            if ($id > 0) {
+                $record = Product::query()->find($id);
+            }
+            return view('admin/product/save', [
+                'data' => $record ?? null
+            ]);
+        }
+        $validated = $request->validated();
+        if ($id > 0) {
+            $validated['status'] = 1;
+        }
+        Product::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function batchDelete(Request $request)
+    {
+        $ids = $request->input('ids');
+        Product::query()->whereIn('id', $ids)->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+}

+ 678 - 0
app/Http/Controllers/Admin/PromoteReportController.php

@@ -0,0 +1,678 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\PrFeedback;
+use App\Http\Models\PrKeywordExtend;
+use App\Http\Models\PrLandPage;
+use App\Http\Models\PrLandPageScope;
+use App\Http\Models\PrModifyGather;
+use App\Http\Models\PrOptimizeData;
+use App\Http\Models\PrOptimizeDataScope;
+use App\Http\Models\PrOptimizeDataSummary;
+use App\Http\Models\PrPlan;
+use App\Http\Models\PrPlanScope;
+use App\Http\Models\PrSeoItem;
+use App\Http\Models\PrSeoItemScope;
+use App\Http\Models\PrSocial;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Imports\KeywordExtend;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Maatwebsite\Excel\Facades\Excel;
+use Illuminate\Http\JsonResponse;
+
+/**
+ * 项目管理下 项目概况下的 推广月报
+ * Class PromoteReportController
+ * @package App\Http\Controllers\Admin
+ */
+class PromoteReportController extends Controller
+{
+
+    //主页
+    public function index($siteId)
+    {
+        $ym = date('Ym', strtotime('first day of -1 month'));
+        $where = ['site_id' => $siteId, 'ym' => $ym];
+        $optimizeDataList = PrOptimizeData::query()->where($where)->get();
+        $landPageList = PrLandPage::query()->where(['site_id' => $siteId])->get();
+        $socialList = PrSocial::query()->where($where)->get()->keyBy('type')->toArray();
+        $threeMonth = [];
+        for ($i = 1; $i <= 3; $i++) {
+            $threeMonth[] = date('Ym', strtotime("first day of -$i month"));
+        }
+        $modifyGatherList = PrModifyGather::query()->where(['site_id' => $siteId])->get();
+        $seoItemSingle = PrSeoItem::query()->where($where)->first();
+
+        $plan = PrPlan::query()->where($where)->first();
+
+        $feedbackList = PrFeedback::query()->where($where)->orderByDesc('ym')->get();
+
+        $authUser = auth()->user();
+        if ($authUser->is_super || in_array($authUser->role_id, [Role::TYPE_MANAGE_LEADER])) {
+            $scope = true;
+        }
+        $oldId = Site::query()->where('id', $siteId)->value('old_id') ?? 0;
+        $summary = PrOptimizeDataSummary::query()->where($where)->value('content') ?? '';
+        $info = PrOptimizeDataScope::query()->first() ?? [];
+
+        $mobile = DB::table('site_mobile')->where('site_id', $siteId)->value('mobile') ?? '';
+
+        $webmasterAccount = 'Hina';
+        $webmasterType = DB::connection('rank')->table('project')->where('id', $oldId)->value('webmaster_type');
+        if (!empty($webmasterType)) {
+            if ($webmasterType == 2) {
+                $webmasterAccount = 'Tommy Ru';
+            } else {
+                $webmasterAccount = '';
+            }
+        }
+
+        return view('admin.promote_report.index', [
+            'webmasterAccount' => $webmasterAccount,
+            'mobile' => $mobile,
+            'oldId' => $oldId,
+            'siteId' => $siteId,
+            'optimizeDataList' => $optimizeDataList,
+
+            'optimizeDataScopeList1' => json_decode($info->content_one, true) ?? [],
+            'optimizeDataScopeList2' => json_decode($info->content_two, true) ?? [],
+            'optimizeDataScopeList3' => json_decode($info->content_three, true) ?? [],
+            'optimizeDataScopeList4' => json_decode($info->content_four, true) ?? [],
+
+            'landPageList' => $landPageList,
+            'landPageScopeList' => PrLandPageScope::query()->get(),
+            'threeMonth' => $threeMonth,
+            'socialList' => $socialList,
+            'modifyGatherList' => $modifyGatherList,
+            'seoItemSingle' => $seoItemSingle,
+            'seoItemScopeList' => PrSeoItemScope::query()->get(),
+            'plan' => $plan,
+            'planScopeList' => PrPlanScope::query()->get(),
+            'feedbackList' => $feedbackList,
+            'scope' => $scope ?? false,
+            'summary' => $summary
+        ]);
+    }
+
+    //保存优化师数据分析
+    public function saveOptimizeData(Request $request, $siteId)
+    {
+
+        $ym = date('Ym', strtotime('first day of -1 month'));
+
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+
+        $dataList = $request->input('dataList') ?? [];
+
+        PrOptimizeData::query()->where(['site_id' => $siteId, 'ym' => $ym])->delete();
+
+        $insertData = [];
+        $datetime = date('Y-m-d H:i:s');
+        foreach ($dataList as $value) {
+            $insertData[] = [
+                'ym' => $ym,
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'content' => $value,
+                'created_at' => $datetime,
+                'updated_at' => $datetime
+            ];
+        }
+        if ($dataList) {
+            PrOptimizeData::query()->insert($insertData);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //保存优化师数据总结
+    public function saveOptimizeDataSummary(Request $request, $siteId)
+    {
+        $ym = date('Ym', strtotime('first day of -1 month'));
+
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $summary = $request->input('summary') ?? '';
+
+        PrOptimizeDataSummary::query()->where(['site_id' => $siteId, 'ym' => $ym])->delete();
+
+        $datetime = date('Y-m-d H:i:s');
+
+        $insertData = [
+            'ym' => $ym,
+            'site_id' => $siteId,
+            'old_id' => $site->old_id,
+            'content' => $summary,
+            'created_at' => $datetime,
+            'updated_at' => $datetime
+        ];
+        PrOptimizeDataSummary::query()->insert($insertData);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //保存优化师数据分析固定话语
+    public function saveOptimizeDataScope(Request $request)
+    {
+        $dataList1 = $request->input('dataList1') ?? [];
+        $dataList2 = $request->input('dataList2') ?? [];
+        $dataList3 = $request->input('dataList3') ?? [];
+        $dataList4 = $request->input('dataList4') ?? [];
+
+        $datetime = date('Y-m-d H:i:s');
+        $insertData = [
+            'created_at' => $datetime,
+            'updated_at' => $datetime
+        ];
+
+        if (!empty($dataList1)) {
+            $insertData['content_one'] = \GuzzleHttp\json_encode($dataList1);
+        }
+        if (!empty($dataList2)) {
+            $insertData['content_two'] = \GuzzleHttp\json_encode($dataList2);
+        }
+        if (!empty($dataList3)) {
+            $insertData['content_three'] = \GuzzleHttp\json_encode($dataList3);
+        }
+        if (!empty($dataList4)) {
+            $insertData['content_four'] = \GuzzleHttp\json_encode($dataList4);
+        }
+
+        PrOptimizeDataScope::query()->delete();
+
+        PrOptimizeDataScope::query()->insert($insertData);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //着陆页清空
+    public function clearLandPage($siteId)
+    {
+        PrLandPage::query()->where(['site_id' => $siteId])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //着陆页保存
+    public function saveLandPage(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $dataList = $request->input('dataList') ?? [];
+
+        $scopeIds = PrLandPage::query()->where(['site_id' => $siteId])->pluck('id')->toArray();
+
+        $requestIds = array_column($dataList, 'id');
+
+        $delIds = array_diff($scopeIds, $requestIds);
+        if ($delIds) {
+            PrLandPage::destroy($delIds);
+        }
+        $datetime = date('Y-m-d H:i:s');
+        foreach ($dataList as $item) {
+            if (empty($item['id'])) {
+                PrLandPage::query()->create([
+                    'site_id' => $siteId,
+                    'old_id' => $site->old_id,
+                    'content' => $item['content'],
+                    'sub_list' => ($item['subList'] ?? []),
+                    'created_at' => $datetime,
+                    'updated_at' => $datetime
+                ]);
+            } else {
+                PrLandPage::query()->where(['id' => $item['id']])
+                    ->update([
+                        'content' => $item['content'],
+                        'sub_list' => json_encode(($item['subList'] ?? []))
+                    ]);
+            }
+        }
+        return response()->json(['message' => '操作成功', 'data' => PrLandPage::query()->where(['site_id' => $siteId])->get()]);
+    }
+
+    //社交推广保存
+    public function saveSocial(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id', 'id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $facebook = $request->input('facebook');
+        $linkedIn = $request->input('linkedIn');
+        $twitter = $request->input('twitter');
+        $pin = $request->input('pin');
+        $ins = $request->input('ins');
+        $youtube = $request->input('youtube');
+
+        $facebookHide = $request->input('facebookHide');
+        $linkedInHide = $request->input('linkedInHide');
+        $twitterHide = $request->input('twitterHide');
+        $pinHide = $request->input('pinHide');
+        $insHide = $request->input('insHide');
+        $youtubeHide = $request->input('youtubeHide');
+
+        $ym = date('Ym', strtotime('first day of -1 month'));
+
+        $dataList = [
+            [
+                'type' => 'facebook',
+                'data' => [
+                    'data_list' => $facebook,
+                    'hide' => $facebookHide
+                ]
+            ],
+            [
+                'type' => 'linkedIn',
+                'data' => [
+                    'data_list' => $linkedIn,
+                    'hide' => $linkedInHide
+                ]
+            ],
+            [
+                'type' => 'twitter',
+                'data' => [
+                    'data_list' => $twitter,
+                    'hide' => $twitterHide
+                ]
+            ],
+            [
+                'type' => 'pin',
+                'data' => [
+                    'data_list' => $pin,
+                    'hide' => $pinHide
+                ]
+            ],
+            [
+                'type' => 'ins',
+                'data' => [
+                    'data_list' => $ins,
+                    'hide' => $insHide
+                ]
+            ],
+            [
+                'type' => 'youtube',
+                'data' => [
+                    'data_list' => $youtube,
+                    'hide' => $youtubeHide,
+                ]
+            ],
+        ];
+
+        foreach ($dataList as $item) {
+            if (PrSocial::query()->where(['site_id' => $siteId, 'ym' => $ym])->where(['type' => $item['type']])->exists()) {
+                PrSocial::query()->where(['site_id' => $siteId, 'ym' => $ym])->where(['type' => $item['type']])->update([
+                    'data_list' => json_encode($item['data']['data_list']),
+                    'hide' => $item['data']['hide'],
+                ]);
+            } else {
+                PrSocial::query()->create([
+                    'ym' => $ym,
+                    'type' => $item['type'],
+                    'site_id' => $siteId,
+                    'old_id' => $site->old_id,
+                    'data_list' => $item['data']['data_list'],
+                    'hide' => $item['data']['hide']
+                ]);
+            }
+        }
+
+        $this->saveNextSocial($site, $dataList);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //提前保存下个月的
+    private function saveNextSocial($site, $dataList)
+    {
+        $ym = date('Ym');
+
+        foreach ($dataList as $item) {
+            $data = PrSocial::query()->where(['site_id' => $site->id, 'ym' => $ym, 'type' => $item['type']])->first();
+            $item['data']['data_list'][0] = null;
+            if ($data) {
+                $data->data_list = $item['data']['data_list'];
+                $data->hide = $item['data']['hide'];
+                $data->save();
+            } else {
+                PrSocial::query()->create([
+                    'ym' => $ym,
+                    'type' => $item['type'],
+                    'site_id' => $site->id,
+                    'old_id' => $site->old_id,
+                    'data_list' => $item['data']['data_list'],
+                    'hide' => $item['data']['hide']
+                ]);
+            }
+        }
+    }
+
+    //清空修改汇总
+    public function clearModifyGather($siteId)
+    {
+        PrModifyGather::query()->where(['site_id' => $siteId])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //修改汇总保存
+    public function saveModifyGather(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $dataList = $request->input('dataList') ?? [];
+
+        $scopeIds = PrModifyGather::query()->where(['site_id' => $siteId])->pluck('id')->toArray();
+
+        $requestIds = array_column($dataList, 'id');
+
+        $delIds = array_diff($scopeIds, $requestIds);
+        if ($delIds) {
+            PrModifyGather::destroy($delIds);
+        }
+
+        $datetime = date('Y-m-d H:i:s');
+        foreach ($dataList as $item) {
+            if (empty($item['id'])) {
+                PrModifyGather::query()->create([
+                    'site_id' => $siteId,
+                    'old_id' => $site->old_id,
+                    'content' => $item['content'],
+                    'modify_date' => $item['modify_date'] ?? null,
+                    'created_at' => $datetime,
+                    'updated_at' => $datetime
+                ]);
+            } else {
+                PrModifyGather::query()->where(['id' => $item['id']])
+                    ->update([
+                        'content' => $item['content'],
+                        'modify_date' => $item['modify_date'] ?? null
+                    ]);
+            }
+        }
+
+        return response()->json(['message' => '操作成功', 'data' => PrModifyGather::query()->where(['site_id' => $siteId])->get()]);
+
+    }
+
+    //获取seo
+    public function getSeoItemScope()
+    {
+        $result = PrSeoItemScope::query()->get();
+        return response()->json(['data' => $result]);
+    }
+
+    //保存Seo
+    public function saveSeoItem(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id', 'id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+
+        $ym = date('Ym', strtotime('first day of -1 month'));
+
+        $checked_list = $request->input('checked_list') ?? [];
+        $data = PrSeoItem::query()->where(['site_id' => $siteId, 'ym' => $ym])->first();
+        if ($data) {
+            $data->checked_list = $checked_list;
+            $data->save();
+        } else {
+            PrSeoItem::query()->create([
+                'ym' => $ym,
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'checked_list' => $checked_list
+            ]);
+        }
+        $this->saveNextSeoItem($site, $checked_list);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //提前保存下个月的
+    private function saveNextSeoItem($site, $checked_list)
+    {
+        $ym = date('Ym');
+
+        $data = PrSeoItem::query()->where(['site_id' => $site->id, 'ym' => $ym])->first();
+        if ($data) {
+            $data->checked_list = $checked_list;
+            $data->save();
+        } else {
+            PrSeoItem::query()->create([
+                'ym' => $ym,
+                'site_id' => $site->id,
+                'old_id' => $site->old_id,
+                'checked_list' => $checked_list
+            ]);
+        }
+    }
+
+    //seo固定选项保存
+    public function saveSeoItemScope(Request $request)
+    {
+
+        $dataList = $request->input('dataList') ?? [];
+
+        $insertData = [];
+        $datetime = date('Y-m-d H:i:s');
+
+        $idMapList = PrSeoItemScope::query()->get();
+
+        $seoItemIds = array_filter(array_column($dataList, 'seoItemId'));
+
+        foreach ($idMapList as $item) {
+            if (!in_array($item->id, $seoItemIds)) {
+                $item->delete();
+            }
+
+            foreach ($dataList as $v) {
+                if ($v['seoItemId'] == $item->id && $v['content'] != $item->content) {
+                    $item->update(['content' => $v['content']]);
+                }
+            }
+        }
+
+        foreach ($dataList as $value) {
+            if (empty($value['seoItemId'])) {
+                $insertData[] = [
+                    'content' => $value['content'],
+                    'created_at' => $datetime,
+                    'updated_at' => $datetime
+                ];
+            }
+        }
+        if ($dataList) {
+            PrSeoItemScope::query()->insert($insertData);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+//    public function savePlanBak(Request $request, $siteId)
+//    {
+//        $site = Site::query()->select(['old_id'])->find($siteId);
+//        if (!$site) {
+//            return response()->json(['message' => '站点不存在'], 400);
+//        }
+//        $dataList = $request->input('dataList') ?? [];
+//
+//        $ym = date('Ym', strtotime('first day of -1 month'));
+//
+//
+//        PrPlan::query()->where(['site_id' => $siteId, 'ym' => $ym])->delete();
+//
+//
+//        $insertData = [];
+//        $datetime = date('Y-m-d H:i:s');
+//        foreach ($dataList as $value) {
+//            $insertData[] = [
+//                'ym' => $ym,
+//                'site_id' => $siteId,
+//                'old_id' => $site->old_id,
+//                'content' => $value['content'],
+//                'created_at' => $datetime,
+//                'updated_at' => $datetime
+//            ];
+//        }
+//        if ($dataList) {
+//            PrPlan::query()->insert($insertData);
+//        }
+//        return response()->json(['message' => '操作成功']);
+//    }
+
+    //工作规划保存
+    public function savePlan(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $dataList = $request->input('dataList') ?? [];
+
+        $ym = date('Ym', strtotime('first day of -1 month'));
+
+
+        $plan = PrPlan::query()->where(['site_id' => $siteId, 'ym' => $ym])->first();
+        if (!$plan) {
+            PrPlan::query()->create([
+                'ym' => $ym,
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'content_list' => $dataList,
+                'file_name' => $request->input('file_name') ?? '',
+                'file_path' => $request->input('file_path') ?? '',
+            ]);
+        } else {
+            $plan->update([
+                'content_list' => $dataList,
+                'file_name' => $request->input('file_name') ?? '',
+                'file_path' => $request->input('file_path') ?? '',
+            ]);
+        }
+        return response()->json(['message' => '操作成功']);
+
+
+    }
+
+    //工作规划固定话语保存
+    public function savePlanScope(Request $request)
+    {
+        $dataList = $request->input('dataList') ?? [];
+
+        PrPlanScope::query()->delete();
+
+        $insertData = [];
+        $datetime = date('Y-m-d H:i:s');
+        foreach ($dataList as $value) {
+            $insertData[] = [
+                'content' => $value,
+                'created_at' => $datetime,
+                'updated_at' => $datetime
+            ];
+        }
+        if ($dataList) {
+            PrPlanScope::query()->insert($insertData);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //关键词拓展
+    public function keywordExtend(Request $request, $siteId)
+    {
+        if (!$request->ajax()) {
+            return view('admin.promote_report.keyword_extend', [
+                'siteId' => $siteId,
+            ]);
+        }
+
+        $result = PrKeywordExtend::query()->where(['site_id' => $siteId])->orderByDesc('id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $result->items();
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $result->total()
+        ]);
+    }
+
+    //导入关键词
+    public function importKeyword(Request $request)
+    {
+        set_time_limit(0);
+        $siteId = $request->input('siteId');
+        if (!$request->input('excel_path') || !$siteId) {
+            return response()->json(['message' => '请先上传excel文件'], 422);
+        }
+        $site = Site::query()->where(['id' => $siteId])->first();
+        if (empty($site->old_id)) {
+            return response()->json(['message' => '老站点信息不存在'], 400);
+        }
+
+        try {
+            Excel::import(new KeywordExtend($siteId, $site->old_id), $request->input('excel_path'), 'public');
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['message' => '导入失败'], 400);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //清空关键词
+    public function clearKeyword($siteId)
+    {
+        $ym = date('Ym');
+        PrKeywordExtend::query()->where(['site_id' => $siteId, 'ym' => $ym])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 删除关键词
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function deleteKeyword(Request $request, $siteId)
+    {
+        $ids = $request->input('ids');
+        $keywordList = PrKeywordExtend::query()
+            ->where('site_id', $siteId)
+            ->whereIn('id', $ids)
+            ->pluck('keyword');
+        $projectId = Site::query()->where('id', $siteId)->value('old_id');
+
+        try {
+
+            //事务
+            DB::transaction(function () use ($siteId, $ids, $keywordList, $projectId) {
+
+                PrKeywordExtend::query()
+                    ->where('site_id', $siteId)
+                    ->whereIn('id', $ids)
+                    ->delete();
+
+                DB::connection('rank')
+                    ->table('project_keyword')
+                    ->where('project_id', $projectId)
+                    ->whereIn('keyword', $keywordList)
+                    ->delete();
+            });
+
+        } catch (\Throwable $exception) {
+            return response()->json(['message' => $exception->getMessage()], 400);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 327 - 0
app/Http/Controllers/Admin/PromoteYearController.php

@@ -0,0 +1,327 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\PryAnalyzeSummary;
+use App\Http\Models\PryAnalyzeSummaryScope;
+use App\Http\Models\PryOptimizeData;
+use App\Http\Models\PryOptimizeDataScope;
+use App\Http\Models\PrySeoItem;
+use App\Http\Models\PrySeoItemScope;
+use App\Http\Models\PrySocial;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use Illuminate\Http\Request;
+
+/**
+ * 项目管理下 项目概况下的 推广月报
+ * Class PromoteYearController
+ * @package App\Http\Controllers\Admin
+ */
+class PromoteYearController extends Controller
+{
+    //主页
+    public function index($siteId)
+    {
+        $where = ['site_id' => $siteId];
+        $optimizeDataList = PryOptimizeData::query()->where($where)->get()->groupBy('type');
+        $optimizeScopeList=PryOptimizeDataScope::query()->get()->groupBy('type');
+        $seoItemSingle = PrySeoItem::query()->where($where)->first();
+        $authUser = auth()->user();
+        if ($authUser->is_super || in_array($authUser->role_id, [Role::TYPE_MANAGE_LEADER])) {
+            $scope = true;
+        }
+        $analyzeSummaryList=PryAnalyzeSummary::query()->where($where)->get();
+        $analyzeSummaryScopeList=PryAnalyzeSummaryScope::query()->get();
+
+        return view('admin.promote_year.index', [
+            'siteId' => $siteId,
+            'optimizeDataList' => $optimizeDataList,
+            'optimizeDataScopeList' => $optimizeScopeList,
+            'seoItemSingle' => $seoItemSingle,
+            'seoItemScopeList' => PrySeoItemScope::query()->get(),
+            'analyzeSummaryList'=>$analyzeSummaryList,
+            'analyzeSummaryScopeList'=>$analyzeSummaryScopeList,
+            'scope' => $scope ?? false
+        ]);
+    }
+    //优化师数据保存
+    public function saveOptimizeData(Request $request, $siteId)
+    {
+
+        $dataList = $request->input('dataList') ?? [];
+        $type= $request->input('type');
+        if (!in_array($type,['keyword','inquire','profile','traffic'])) {
+            return response()->json(['message' => '参数类型错误'], 400);
+        }
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+
+        $scopeIds=PryOptimizeData::query()->where(['type'=>$type,'site_id'=>$siteId])->pluck('id')->toArray();
+        $requestIds=array_column($dataList,'id');
+        $delIds=array_diff($scopeIds,$requestIds);
+        if ($delIds) {
+            PryOptimizeData::destroy($delIds);
+        }
+
+        foreach ($dataList as $item){
+            if (empty($item['id'])) {
+                PryOptimizeData::query()->create([
+                    'site_id'=>$siteId,
+                    'old_id'=>$site->old_id,
+                    'content'=>$item['content'],
+                    'type'=>$type
+                ]);
+            }else {
+                PryOptimizeData::query()->where(['id'=>$item['id']])
+                    ->update(['content'=>$item['content']]);
+            }
+        }
+
+        return response()->json(['message' => '操作成功','data'=>PryOptimizeData::query()->where(['site_id'=>$siteId,'type'=>$type])->get()]);
+    }
+
+    //优化师数据固定话语保存
+    public function saveOptimizeDataScope(Request $request)
+    {
+
+        $dataList = $request->input('dataList') ?? [];
+        $type= $request->input('type');
+        if (!in_array($type,['keyword','inquire','profile','traffic'])) {
+            return response()->json(['message' => '参数类型错误'], 400);
+        }
+        $scopeIds=PryOptimizeDataScope::query()->where(['type'=>$type])->pluck('id')->toArray();
+        $requestIds=array_column($dataList,'id');
+        $delIds=array_diff($scopeIds,$requestIds);
+        if ($delIds) {
+            PryOptimizeDataScope::destroy($delIds);
+        }
+        foreach ($dataList as $item){
+            if (empty($item['id'])) {
+                PryOptimizeDataScope::query()->create([
+                    'content'=>$item['content'],
+                    'type'=>$type
+                ]);
+            }else {
+                PryOptimizeDataScope::query()->where(['id'=>$item['id']])
+                    ->update(['content'=>$item['content']]);
+            }
+        }
+        return response()->json(['message' => '操作成功','data'=>PryOptimizeDataScope::query()->where(['type'=>$type])->get()]);
+    }
+
+    //获取seo固定选项
+    public function getSeoItemScope()
+    {
+        $result = PrySeoItemScope::query()->get();
+        return response()->json(['data' => $result]);
+    }
+    //保存seo
+    public function saveSeoItem(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+
+        $checked_list = $request->input('checked_list') ?? [];
+        $data = PrySeoItem::query()->where(['site_id' => $siteId])->first();
+        if ($data) {
+            $data->checked_list = $checked_list;
+            $data->save();
+        } else {
+            PrySeoItem::query()->create([
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'checked_list' => $checked_list
+            ]);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //seo固定选项保存
+    public function saveSeoItemScope(Request $request)
+    {
+        $dataList = $request->input('dataList') ?? [];
+
+        $insertData = [];
+        $datetime = date('Y-m-d H:i:s');
+
+        $idMapList = PrySeoItemScope::query()->get();
+
+        $seoItemIds = array_filter(array_column($dataList, 'seoItemId'));
+
+        foreach ($idMapList as $item) {
+            if (!in_array($item->id, $seoItemIds)) {
+                $item->delete();
+            }
+
+            foreach ($dataList as $v) {
+                if ($v['seoItemId'] == $item->id && $v['content'] != $item->content) {
+                    $item->update(['content' => $v['content']]);
+                }
+            }
+        }
+
+        foreach ($dataList as $value) {
+            if (empty($value['seoItemId'])) {
+                $insertData[] = [
+                    'content' => $value['content'],
+                    'created_at' => $datetime,
+                    'updated_at' => $datetime
+                ];
+            }
+        }
+        if ($dataList) {
+            PrySeoItemScope::query()->insert($insertData);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //优化师分析保存
+    public function saveAnalyzeSummary(Request $request, $siteId)
+    {
+
+        $dataList = $request->input('dataList') ?? [];
+
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+
+        $scopeIds=PryAnalyzeSummary::query()->where(['site_id'=>$siteId])->pluck('id')->toArray();
+
+        $requestIds=array_column($dataList,'id');
+
+        $delIds=array_diff($scopeIds,$requestIds);
+        if ($delIds) {
+            PryAnalyzeSummary::destroy($delIds);
+        }
+
+
+        foreach ($dataList as $item){
+            if (empty($item['id'])) {
+                PryAnalyzeSummary::query()->create([
+                    'site_id'=>$siteId,
+                    'old_id'=>$site->old_id,
+                    'content'=>$item['content'],
+
+                ]);
+            }else {
+                PryAnalyzeSummary::query()->where(['id'=>$item['id']])
+                    ->update(['content'=>$item['content']]);
+            }
+        }
+
+        return response()->json(['message' => '操作成功','data'=>PryAnalyzeSummary::query()->where(['site_id'=>$siteId])->get()]);
+
+    }
+
+    //优化师分析固定话语保存
+    public function saveAnalyzeSummaryScope(Request $request)
+    {
+
+        $dataList = $request->input('dataList') ?? [];
+
+
+        $scopeIds=PryAnalyzeSummaryScope::query()->pluck('id')->toArray();
+
+        $requestIds=array_column($dataList,'id');
+
+        $delIds=array_diff($scopeIds,$requestIds);
+        if ($delIds) {
+            PryAnalyzeSummaryScope::destroy($delIds);
+        }
+
+        foreach ($dataList as $item){
+            if (empty($item['id'])) {
+                PryAnalyzeSummaryScope::query()->create([
+                    'content'=>$item['content'],
+
+                ]);
+            }else {
+                PryAnalyzeSummaryScope::query()->where(['id'=>$item['id']])
+                    ->update(['content'=>$item['content']]);
+            }
+        }
+
+        return response()->json(['message' => '操作成功','data'=>PryAnalyzeSummaryScope::query()->get()]);
+    }
+
+    //社交推广保存
+    public function saveSocial(Request $request, $siteId)
+    {
+        $site = Site::query()->select(['old_id'])->find($siteId);
+        if (!$site) {
+            return response()->json(['message' => '站点不存在'], 400);
+        }
+        $facebook = $request->input('facebook');
+        $linkedIn = $request->input('linkedIn');
+        $twitter = $request->input('twitter');
+        $pin = $request->input('pin');
+
+        $facebookHide = $request->input('facebookHide');
+        $linkedInHide= $request->input('linkedInHide');
+        $twitterHide = $request->input('twitterHide');
+        $pinHide = $request->input('pinHide');
+
+        if (PrySocial::query()->where(['site_id' => $siteId])->exists()) {
+            PrySocial::query()->where(['site_id' => $siteId])->where(['type' => 'facebook'])->update([
+                'data_list' => json_encode($facebook),
+                'hide'=>$facebookHide,
+            ]);
+            PrySocial::query()->where(['site_id' => $siteId])->where(['type' => 'linkedIn'])->update([
+                'data_list' => json_encode($linkedIn),
+                'hide'=>$linkedInHide,
+            ]);
+            PrySocial::query()->where(['site_id' => $siteId])->where(['type' => 'twitter'])->update([
+                'data_list' => json_encode($twitter),
+                'hide'=>$twitterHide,
+            ]);
+            PrySocial::query()->where(['site_id' => $siteId])->where(['type' => 'pin'])->update([
+                'data_list' => json_encode($pin),
+                'hide'=>$pinHide,
+            ]);
+        } else {
+            PrySocial::query()->create([
+
+                'type' => 'facebook',
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'data_list' => $facebook,
+                'hide'=>$facebookHide
+            ]);
+            PrySocial::query()->create([
+
+                'type' => 'linkedIn',
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'data_list' => $linkedIn,
+                'hide'=>$linkedInHide
+            ]);
+            PrySocial::query()->create([
+
+                'type' => 'twitter',
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'data_list' => $twitter,
+                'hide'=>$twitterHide
+            ]);
+            PrySocial::query()->create([
+                'type' => 'pin',
+                'site_id' => $siteId,
+                'old_id' => $site->old_id,
+                'data_list' => $pin,
+                'hide'=>$pinHide
+            ]);
+
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+}

+ 188 - 0
app/Http/Controllers/Admin/RankController.php

@@ -0,0 +1,188 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+
+use App\Http\Models\Site;
+use App\Http\Requests\Rank\KeywordRequest;
+use App\Http\Traits\HasSites;
+use App\Imports\Keyword;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Maatwebsite\Excel\Facades\Excel;
+use TheSeer\Tokenizer\Exception;
+
+/**
+ * 关键词排民
+ * Class RankController
+ * @package App\Http\Controllers\Admin
+ */
+class RankController extends Controller
+{
+
+    use HasSites;
+
+    //关键词
+    public function keyword($id)
+    {
+        $site = Site::query()->where(['id' => $id])->first();
+
+        if (!$site) {
+            return view('admin/site/tips', [
+                'tips' => '报告不存在',
+                'siteId' => $id,
+            ]);
+        }
+        try {
+            $rankConnection = DB::connection('rank');
+            $old = $rankConnection->table('project')->where(['id' => $site->old_id])->select(['allow_googlerank', 'number', 'interval_time'])->first();
+            $keywordCount = $rankConnection->table('project_keyword')->where(['project_id' => $site->old_id])->count();
+            $keywordCountNotRun = $rankConnection->table('project_keyword')->where(['project_id' => $site->old_id])->where(['allow_googlerank' => '0'])->count();
+
+            return view('admin/rank/keyword_for_site', [
+                'siteId' => $id,
+                'site' => $site,
+                'old' => $old,
+                'keywordCount' => $keywordCount,
+                'keywordCountNotRun' => $keywordCountNotRun
+            ]);
+
+        } catch (\Throwable $exception) {
+            return view('admin/site/tips', [
+                'tips' => '数据库连接超时,error:' . $exception->getMessage().'http://test.build.yinqingli.net/admin',
+                'siteId' => $id,
+            ]);
+        }
+    }
+
+    //客户界面关键词
+    public function customerKeyword()
+    {
+        $site = $this->hasUserOneSite();
+        if (!$site) {
+            return view('admin/errors/tips');
+        }
+        $rankConnection = DB::connection('rank');
+        $old = $rankConnection->table('project')->where(['id' => $site->old_id])->select(['allow_googlerank', 'number'])->first();
+        $keywordCount = $rankConnection->table('project_keyword')->where(['project_id' => $site->old_id])->count();
+
+        return view('admin/rank/keyword', [
+            'siteId' => $site->id,
+            'site' => $site,
+            'old' => $old,
+            'keywordCount' => $keywordCount
+        ]);
+    }
+
+    //关键词保存
+    public function keywordSave(KeywordRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            return view('admin/rank/keyword_save', [
+                'siteId' => $id
+            ]);
+        }
+        $site = Site::query()->where(['id' => $id])->first();
+        if (!$site) {
+            return response()->json(['message' => '站点信息不存在'], 400);
+        }
+        if (!$site->old_id) {
+            return response()->json(['message' => '请先与rank项目关联'], 400);
+        }
+
+        $rankConnection = DB::connection('rank');
+
+        $maxSn = $rankConnection->table('project_keyword')->where(['project_id' => $site->old_id])->max('sn') ?? 0;
+        $validated = $request->validated();
+
+        $validated['sn'] = $maxSn;
+        $validated['project_id'] = $site->old_id;
+        $validated['google_rank'] = 9999;
+        $validated['target_url'] = ' ';
+        $validated['resules'] = 0;
+        $validated['allintitle'] = -1;
+        $validated['sync_time'] = strtotime('today');
+
+
+        $rankConnection->table('project_keyword')->insert($validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //导入关键词
+    public function importKeyword(Request $request)
+    {
+        set_time_limit(0);
+        $siteId = $request->input('siteId');
+        if (!$request->input('excel_path') || !$siteId) {
+            return response()->json(['message' => '请先上传excel文件'], 422);
+        }
+        $site = Site::query()->where(['id' => $siteId])->first();
+        if (!$site) {
+            return response()->json(['message' => '站点信息不存在'], 400);
+        }
+        if (!$site->old_id) {
+            return response()->json(['message' => '请先与rank项目关联'], 400);
+        }
+
+        try {
+            Excel::import(new Keyword($site->old_id), $request->input('excel_path'), 'public');
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(), 1));
+            return response()->json(['message' => $throwable->getMessage()], 400);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function keywordTpl($siteId)
+    {
+
+    }
+
+    //清空关键词
+    public function clearKeyword($siteId)
+    {
+        $site = Site::query()->where(['id' => $siteId])->first();
+        if (!$site) {
+            return response()->json(['message' => '站点信息不存在'], 400);
+        }
+        if (!$site->old_id) {
+            return response()->json(['message' => '请先与rank项目关联'], 400);
+        }
+
+        DB::connection('rank')->table('project_keyword')->where(['project_id' => $site->old_id])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //允许排名
+    public function allowRank(Request $request, $oldId)
+    {
+        if ($oldId < 1) {
+            return response()->json(['message' => '请先与rank项目关联'], 400);
+        }
+
+        $isAllow = $request->input('isAllow');
+        $number = $request->input('number');
+        $intervalTime = $request->input('interval_time');
+
+        if (!in_array($isAllow, [1, 0])) {
+            return response()->json(['message' => '参数错误'], 400);
+        }
+        if (!in_array($number, [1, 2, 3, 4])) {
+            return response()->json(['message' => '请选择排名服务器'], 400);
+        }
+        if (!in_array($intervalTime, [0, 5, 7, 15, 30])) {
+            return response()->json(['message' => '请选择排名服务器'], 400);
+        }
+
+        DB::connection('rank')->table('project')->where(['id' => $oldId])->update([
+            'allow_googlerank' => $isAllow,
+            'number' => $number,
+            'interval_time' => $intervalTime,
+        ]);
+        return response()->json(['message' => '操作成功']);
+
+    }
+
+}

+ 199 - 0
app/Http/Controllers/Admin/ReportController.php

@@ -0,0 +1,199 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Logics\Admin\ReportLogic;
+use App\Http\Models\Site;
+use App\Http\Requests\Report\ContractRequest;
+use App\Http\Requests\Report\SummaryRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use App\Http\Traits\HasSites;
+
+/**
+ * 项目管理下的 项目概况下的 月报扩展,数据报表
+ * Class ReportController
+ * @package App\Http\Controllers\Admin
+ */
+class ReportController extends Controller
+{
+    use HasSites;
+    protected $logic;
+
+    public function __construct(ReportLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+
+    //数据报表
+    public function index($siteId)
+    {
+
+        $site = Site::query()->where(['id' => $siteId])->select(['old_id'])->first();
+        if (empty($site->old_id)) {
+            return view('admin/site/tips', [
+                'siteId' => $siteId,
+                'tips' => '关联站点信息不存在'
+            ]);
+        }
+        return view('admin/report/index_for_site', [
+                'site' => $site,
+                'siteId' => $siteId //admin/site/side_layout 需要此字段
+            ]
+        );
+    }
+
+    //客户界面的数据报表
+    public function customerReport()
+    {
+        $site = $this->hasUserOneSite();
+        if (!$site) {
+            return view('admin/errors/tips');
+        }
+
+        if (empty($site->old_id)) {
+            return view('admin/errors/tips', [
+                'tips' => '关联站点信息不存在'
+            ]);
+        }
+
+        return view('admin/report/customer_report', [
+                'site' => $site,
+            ]
+        );
+    }
+
+    //月报扩展
+    public function extend($siteId)
+    {
+        $sixMonth = [];
+        for ($count = 1; $count <= 6; $count++) {
+            $sixMonth[] = date('Ym', strtotime('first day of -' . $count . ' month'));
+        }
+
+        $site = Site::query()->where(['id' => $siteId])->first();
+        if (empty($site->old_id)) {
+            return view('admin/site/tips', [
+                'siteId' => $siteId,
+                'tips' => '关联站点信息不存在'
+            ]);
+        }
+
+        $rankConnection = DB::connection('rank');
+
+        $reportExtendList = $rankConnection->table('report_extend')
+            ->where(['project_id' => $site->old_id])->limit(6)->orderBy('ym', 'desc')->get();
+
+        $reportSummaryList = $rankConnection->table('report_summary')
+            ->where(['project_id' => $site->old_id])->get()->map(function ($item) {
+                return (array)$item;
+            })->groupBy('ym')->toArray();
+
+        $customList = $rankConnection->table('webmaster_custom')->whereIn('ym', $sixMonth)->where([
+            'project_id' => $site->old_id
+        ])->get()->toArray();
+
+        $ym = date('Ym', strtotime('-1 month'));
+
+        $reportExtend = $rankConnection->table('report_extend')
+            ->where(['project_id' => $site->old_id, 'ym' => $ym])->first();
+
+
+        $summaryList = $rankConnection->table('report_summary')
+            ->where(['project_id' => $site->old_id, 'ym' => $ym])->get()->toArray();
+
+        $keyCustomList = array_column($customList, null, 'ym');
+
+        $contract = $rankConnection->table('report_contract')->where(['project_id' => $site->old_id, 'ym' => $ym])->first();
+
+
+        return view('admin/report/extend', [
+            'data' => $site,
+            'siteId' => $siteId,
+            'sixMonth' => $sixMonth,
+            'keyCustomList' => $keyCustomList,
+            'reportExtend' => $reportExtend,
+            'reportExtendList' => $reportExtendList,
+            'reportSummaryList' => $reportSummaryList,
+            'summaryList' => $summaryList,
+            'contractList' => $contract
+        ]);
+    }
+
+    //月报扩展下的 外链文章同步
+    public function syncCustomer(Request $request, $oldId)
+    {
+
+        $ymMapData = $request->input('ymMapData');
+        if (!is_array($ymMapData)) {
+            return response()->json(['message' => '参数错误'], 422);
+        }
+
+        foreach ($ymMapData as $key => $item) {
+            $condition = ['project_id' => $oldId, 'ym' => $key];
+            DB::connection('rank')->table('webmaster_custom')
+                ->updateOrInsert($condition, [
+                    'link_count' => $item['linkCount'],
+                    'article_count' => $item['articleCount'],
+                    'create_time' => time()
+                ]);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //月报扩展下的 月度总结同步
+    public function syncSummary(SummaryRequest $request, $oldId)
+    {
+        $validated = $request->validated();
+        $validated['dataList'] = $validated['dataList'] ?? [];
+        $validated['fileList'] = $validated['fileList'] ?? [];
+        $ym = date('Ym', strtotime('-1 month'));
+
+        $this->logic->syncSummary($ym, $oldId, $validated);
+        $this->logic->syncFileExtend($ym, $oldId, $validated);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //月报扩展下的 合同同步
+    public function syncContract(ContractRequest $request, $oldId)
+    {
+        $rankConnection = DB::connection('rank');
+        $ym = date('Ym', strtotime('-1 month'));
+        $validated = $request->validated();
+        $validated['create_time'] = time();
+        $rankConnection->table('report_contract')->updateOrInsert(['ym' => $ym, 'project_id' => $oldId], $validated);
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //清除上一个月数据
+    public function clearLastMonthData($siteId)
+    {
+        $lstMonth = date('Ym', strtotime('-1 month', strtotime(date('Y-m-01'))));
+        $projectId = Site::query()->where('id', $siteId)->value('old_id');
+        if (!empty($projectId)) {
+            $rankConnection = DB::connection('rank');
+            $rankConnection->table('webmaster_country')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_custom')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_effect')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_flow')->where([['project_id', '=', $projectId], ['flow_ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_include')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_keyword')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+            $rankConnection->table('webmaster_keyword_info')->where([['project_id', '=', $projectId], ['ym', '=', $lstMonth]])->delete();
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    //推送消息至app
+    public function pushMessage($siteId)
+    {
+        DB::table('report_notice')->insert(['site_id' => $siteId]);
+        $info = DB::table('report_notice_task')->where([['site_id', '=', $siteId], ['status', '=', 0]])->first();
+        if (empty($info)) {
+            DB::table('report_notice_task')->insert(['site_id' => $siteId]);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 239 - 0
app/Http/Controllers/Admin/ReptileController.php

@@ -0,0 +1,239 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use GuzzleHttp\Client;
+use GuzzleHttp\Pool;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 爬站点所有url-http状态
+ * Class ArticleController
+ * @package App\Http\Controllers\Admin
+ */
+class ReptileController extends Controller
+{
+
+    /**
+     * 爬虫脚本
+     * @param Request $request
+     */
+    public function index(Request $request)
+    {
+
+        DB::table('site_status')->delete();
+        $list = Site::query()->whereIn('status', [2, 3])->select('webmaster_domain')->limit(10)->get();
+        foreach ($list as $items) {
+
+            $url = substr(trim($items->webmaster_domain), -1);
+            if ($url == '/') {
+                Log::info('webmaster_domain:' . $items->webmaster_domain);
+                $testUrl = substr(trim($items->webmaster_domain), 0, -1);
+                $siteContent = $this->getUrlContent($testUrl);
+                if (!empty($siteContent)) {
+                    $urlList = $this->crawler($testUrl, $siteContent);
+                    $list = [];
+                    foreach ($urlList as $item) {
+                        $url = substr($item, 0, strlen($testUrl));
+                        if (empty(strcmp($url, $testUrl))) {
+                            $list[] = $item;
+                        }
+                    }
+                    $list = array_unique($list);
+                    $list1 = $this->filter($list, $testUrl);
+                    $list2 = $this->filter($list1, $testUrl);
+
+                    Log::info('webmaster_domain1:' . count($list));
+
+                    $this->getAllHeaderResponse($list2);
+                }
+            }
+        }
+        die('success');
+
+    }
+
+
+    /**
+     * 读取网站内容并筛选出相同域名下的连接列表
+     * @param $result
+     * @param $testUrl
+     * @return array
+     */
+    private function filter($result, $testUrl)
+    {
+        $list = [];
+        foreach ($result as $item) {
+            $siteContent = $this->getUrlContent($item);
+            if (!empty($siteContent)) {
+                $urlList1 = $this->crawler($item, $siteContent);
+                if (!empty($urlList1)) {
+                    foreach ($urlList1 as $value) {
+                        $url = substr($value, 0, strlen($testUrl));
+                        if (empty(strcmp($url, $testUrl))) {
+                            $list[] = $value;
+                        }
+                    }
+                    $list = array_unique($list);
+                }
+            }
+        }
+
+        return $list;
+    }
+
+    /**
+     * 获取网站http相应状态
+     * @param $result
+     */
+    private function getAllHeaderResponse($result)
+    {
+        //重置索引
+        $result = array_merge($result);
+        $count = count($result) ?? 0;
+        $res = $this->multiCheckNetResource($result);
+        $data = [];
+        foreach ($result as $k => $v) {
+            foreach ($res as $kk => $vv) {
+                if ($k == $kk) {
+                    $data[] = [
+                        'url' => $v,
+                        'status' => $vv,
+                        'count' => $count,
+                    ];
+                }
+            }
+        }
+        DB::table('site_status')->insert($data);
+
+    }
+
+    /**
+     * 获取网站内容
+     * @param $url
+     * @return bool|false|string
+     */
+    private function getUrlContent($url)
+    {
+        try {
+            $handle = file_get_contents($url);
+            return $handle;
+        } catch (\Throwable $exception) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取网站内容链接
+     * @param $url
+     * @param string $content
+     * @return array|bool
+     */
+    private function crawler($url, $content = '')
+    {
+        $urlList = $this->reviseUrl($url, $this->filterUrl($content));
+        if ($urlList) {
+            return $urlList;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 正则域名
+     * @param $webContent
+     * @return bool|mixed
+     */
+    private function filterUrl($webContent)
+    {
+        $reg = '/<[a|A].*?href=[\'\"]{0,1}([^>\'\"\ ]*).*?>/';
+        $result = preg_match_all($reg, $webContent, $matchResult);
+        if ($result) {
+            return $matchResult[1];
+        } else {
+            return false;
+        }
+    }
+
+
+    /**
+     * 获取域名下面的所有子uri
+     * @param $baseUrl
+     * @param $urlList
+     * @return array|bool
+     */
+    private function reviseUrl($baseUrl, $urlList)
+    {
+        $urlInfo = parse_url($baseUrl);
+        $baseUrl = $urlInfo["scheme"] . '://' . $urlInfo["host"];
+
+        $result = [];
+        if (is_array($urlList)) {
+            foreach ($urlList as $urlItem) {
+                if (preg_match('/^http/', $urlItem)) {
+                    // 已经是完整的url
+                    $result[] = $urlItem;
+                } else {
+                    // 不完整的url
+                    if (substr($urlItem, 0, 1) == '/') {
+                        $realUrl = $baseUrl . $urlItem;
+                    } else {
+                        $realUrl = $baseUrl . '/' . $urlItem;
+                    }
+
+                    $result[] = $realUrl;
+                }
+            }
+            return $result;
+        } else {
+            return false;
+        }
+    }
+
+
+    /**
+     * 并发多请求 检查网络资源是否200
+     * @param $taskUrls
+     * @param int $concurrency
+     * @param array $config
+     * @return array
+     */
+    private static function multiCheckNetResource(
+        $taskUrls,
+        $concurrency = 5,
+        $config = [
+            'verify' => false,
+            'timeout' => 3,
+        ]
+    )
+    {
+        $client = new Client($config); //并发请求链接地址
+
+        $requests = function () use ($client, $taskUrls) {
+            foreach ($taskUrls as $item) {
+                yield new \GuzzleHttp\Psr7\Request('HEAD', $item);
+            }
+        };
+
+        $result = [];
+        $pool = new Pool($client, $requests(), [
+            'concurrency' => $concurrency, //同时并发抓取几个
+            'fulfilled' => function (\GuzzleHttp\Psr7\Response $response, $index) use (&$result) {
+                // this is delivered each successful response
+                $result[$index] = $response->getStatusCode();
+            },
+            'rejected' => function (\Throwable $throwable, $index) use (&$result) {
+                $result[$index] = $throwable->getCode();
+                // this is delivered each failed request
+            },
+        ]);
+
+        $promise = $pool->promise();
+        $promise->wait();
+        return $result;
+    }
+}

+ 113 - 0
app/Http/Controllers/Admin/SchoolController.php

@@ -0,0 +1,113 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Asset;
+use App\Http\Models\AssetType;
+use App\Http\Requests\School\AssetSaveRequest;
+use App\Http\Requests\School\AssetTypeSaveRequest;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+/**
+ * 引擎力学堂
+ * Class SchoolController
+ * @package App\Http\Controllers\Admin
+ */
+class SchoolController extends Controller
+{
+    //资源类型
+    public function assetTypes(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/school/asset_type');
+        }
+
+        if ($keyword = $request->input('keyword')) {
+            $filter[] = ['title', 'like', '%' . $keyword . '%'];
+        }
+
+        $records = AssetType::query()->where($filter ?? [])->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+    //资源类型 保存
+    public function assetTypesSave(AssetTypeSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            if ($id > 0) {
+                $record = AssetType::query()->find($id);
+            }
+            return view('admin/school/asset_type_save', [
+                'data' => $record ?? null
+            ]);
+        }
+        $validated = $request->validated();
+        AssetType::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+    //资源类型删除
+    public function assetTypeDelete($id)
+    {
+        if (Asset::query()->where(['type_id' => $id])->exists()) {
+            return response()->json(['message' => '请先删除此类型下的资源'], 400);
+        }
+
+        AssetType::query()->where(['id' => $id])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+    //资源列表
+    public function assets(Request $request)
+    {
+        if (!$request->ajax()) {
+            $assetTypes = AssetType::query()->get();
+            return view('admin/school/asset', [
+                'assetTypes' => $assetTypes
+            ]);
+        }
+        if ($typeId = $request->input('type_id')) {
+            $filter[] = ['type_id', '=', $typeId];
+        }
+
+        $records = Asset::query()->with(['assetType'])->where($filter ?? [])->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        array_walk($items, function ($item) {
+            $item->type_title = $item->assetType->title ?? '';
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+    //资源保存
+    public function assetsSave(AssetSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            $assetTypes = AssetType::query()->get();
+            if ($id > 0) {
+                $record = Asset::query()->find($id);
+            }
+            return view('admin/school/asset_save', [
+                'data' => $record ?? null,
+                'assetTypes' => $assetTypes
+            ]);
+        }
+
+        $validated = $request->validated();
+        Asset::query()->updateOrCreate(['id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+    //资源保存
+    public function assetsDelete(Request $request)
+    {
+        $ids = $request->input('ids');
+        Asset::query()->whereIn('id', $ids)->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 72 - 0
app/Http/Controllers/Admin/ServerController.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Server;
+use App\Http\Requests\Server\SaveRequest;
+use App\Libs\Ssh;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 服务器管理
+ * Class ServerController
+ * @package App\Http\Controllers\Admin
+ */
+class ServerController extends Controller
+{
+    //服务器列表
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/server/index');
+        }
+        $records = Server::query()->where([])->orderByDesc('server_id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $records->items();
+        array_walk($items, function ($item) {
+            $item->type_title = Server::SERVER_TYPE[$item->server_type] ?? '';
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $records->total()
+        ]);
+    }
+    //保存
+    public function save(SaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            $record = Server::query()->find($id);
+            return view('admin/server/save', [
+                'data' => $record
+            ]);
+        }
+        $validated = $request->validated();
+        Server::query()->updateOrCreate(['server_id' => $id], $validated);
+        return response()->json(['message' => '操作成功']);
+    }
+    //批量删除
+    public function batchDelete(Request $request)
+    {
+
+        $ids = $request->input('ids');;
+        Server::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+
+    }
+    //重启
+    public function reload($id)
+    {
+        $record = Server::query()->where(['server_id' => $id])->first();
+        if (!$record) return response()->json(['message' => '数据不存在'], 400);
+        try {
+            Ssh::factory($record->server_ip, $record->server_user_name, $record->server_passwd);
+            Log::info(Ssh::exec('lnmp restart'));
+        } catch (\Throwable $throwable) {
+            Log::error(var_export($throwable->getMessage(),1));
+            return response()->json(['message' =>var_export( $throwable->getMessage(),1)], 400);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+}

File diff suppressed because it is too large
+ 2698 - 0
app/Http/Controllers/Admin/SiteController.php


File diff suppressed because it is too large
+ 2469 - 0
app/Http/Controllers/Admin/SiteController2022.04.29.php


File diff suppressed because it is too large
+ 1769 - 0
app/Http/Controllers/Admin/SiteController_bak.php


File diff suppressed because it is too large
+ 1941 - 0
app/Http/Controllers/Admin/SiteController_bak2022-04.php


+ 333 - 0
app/Http/Controllers/Admin/SocialController.php

@@ -0,0 +1,333 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Logics\Admin\SocialLogic;
+use App\Http\Models\HootsuiteUser;
+use App\Http\Models\Site;
+use App\Http\Models\Social;
+use Facebook\Facebook;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Log;
+use Laravel\Socialite\Facades\Socialite;
+
+/**
+ * 之前版本的社交管理 后被hootsuite替代
+ * Class SocialController
+ * @package App\Http\Controllers\Admin
+ */
+class SocialController extends Controller
+{
+    protected $logic;
+
+    public function __construct(SocialLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+
+    public function pin($siteId)
+    {
+//        $siteId = $request->input('siteId');
+
+        $social = Social::query()->where(['site_id' => $siteId, 'type' => Social::PIN])->first();
+        if (!$social) {
+            return view('admin/errors/tips', [
+                'tips' => '社交信息不存在!!!'
+            ]);
+        }
+
+//        $clientId = $social->app_id;
+//        $clientSecret = $social->app_secret;
+//        $redirectUrl = $social->redirect_url;
+//        $config = new \SocialiteProviders\Manager\Config($clientId, $clientSecret, $redirectUrl);
+
+        config([
+            'services.pinterest' => [
+                'client_id' => $social->app_id,
+                'client_secret' => $social->app_secret,
+                'redirect' => $social->redirect_url
+            ]
+        ]);
+
+//        $user = Socialite::driver('pinterest')->setConfig($config)->user();
+        $user = Socialite::driver('pinterest')->user();
+
+        if (empty($user->token)) {
+            return view('admin/errors/tips', [
+                'tips' => '用户令牌获取失败!!!'
+            ]);
+        }
+
+        $social->token = $user->token;
+        $social->save();
+
+        return view('admin/errors/tips', [
+            'tips' => '授权成功'
+        ]);
+    }
+
+    public function twitter(Request $request)
+    {
+        $user = Socialite::with('twitter')->user();
+
+        if (empty($user->token) || empty($user->tokenSecret)) {
+            return view('admin/errors/tips', [
+                'tips' => '用户令牌获取失败!!!'
+            ]);
+        }
+
+
+        if (!$siteId = $request->input('siteId')) {
+            return view('admin/errors/tips', [
+                'tips' => '未指定站点!!!'
+            ]);
+        }
+        Social::query()->updateOrCreate([
+            'site_id' => $siteId,
+            'type' => Social::TWITTER
+        ], [
+            'token' => $user->token,
+            'token_secret' => $user->tokenSecret
+        ]);
+
+        return view('admin/errors/tips', [
+            'tips' => '授权成功!!!!'
+        ]);
+    }
+
+
+    public function linkedIn(Request $request)
+    {
+        $social = Social::query()->where(['type' => Social::LINKED, 'site_id' => 0])->first();
+        if (!$social) {
+            return view('admin/errors/tips', [
+                'tips' => '社交信息不存在!!!'
+            ]);
+        }
+
+        config([
+            'services.linkedin.client_id' => $social->app_id,
+            'services.linkedin.client_secret' => $social->app_secret,
+            'services.linkedin.redirect' => sprintf('%s?siteId=%s', $social->redirect_url, $request->input('siteId')),
+        ]);
+
+        $user = Socialite::driver('LinkedIn')->stateless()->user();
+        if (empty($user->token)) {
+            return view('admin/errors/tips', [
+                'tips' => '授权失败!!!'
+            ]);
+        }
+        $social->update([
+            'token' => $user->token,
+            'token_expired_at' => date('Y-m-d H:i:s', strtotime('+59 days'))
+        ]);
+        return view('admin/errors/tips', [
+            'tips' => '授权成功,有效期60天'
+        ]);
+    }
+
+    public function facebook()
+    {
+        $user = Socialite::driver('facebook')->user();
+        if (empty($user->token)) {
+            return view('admin/errors/tips', [
+                'tips' => '用户令牌获取失败!!!'
+            ]);
+        }
+
+        try {
+
+            $allSites = Site::query()->select(['id', 'facebook_page', 'facebook_page_token', 'cn_title'])->get();
+
+            $fb = new Facebook([
+                'app_id' => env('FACEBOOK_CLIENT_ID'),
+                'app_secret' => env('FACEBOOK_CLIENT_SECRET'),
+                'default_access_token' => $user->token, // optional
+            ]);
+
+            $invalidTokenSiteTitleList = [];
+            foreach ($allSites as $site) {
+
+                try {
+                    if (empty($site->facebook_page)) {
+                        continue;
+                    }
+
+                    $pageId = $site->facebook_page;
+
+                    $response = $fb->get(sprintf('/%s?fields=access_token', $pageId));
+                    $responseJson = json_decode($response->getBody(), true);
+                    if (empty($responseJson['access_token'])) {
+                        $invalidTokenSiteTitleList[] = ['title' => $site->cn_title, 'failed' => var_export($response->getBody(), 1)];
+                        Log::warning('facebook_page_token:' . var_export($response->getBody(), 1));
+                    }
+                    $pageToken = $responseJson['access_token'];
+                    $site->facebook_page_token = $pageToken;
+                    $site->save();
+                } catch (\Throwable $throwable) {
+                    Log::warning(sprintf('facebook Auth%s', var_export($throwable->getMessage(), 1)));
+
+                    $invalidTokenSiteTitleList[] = [
+                        'title' => $site->cn_title,
+                        'failed' => var_export($throwable->getMessage(), 1)
+                    ];
+                }
+            }
+
+            Social::query()->where(['type' => 'facebook'])->update([
+                'token' => $user->token,
+                'token_expired_at' => date('Y-m-d H:i:s', strtotime('+59 days')),
+                'page_token_expired_at' => date('Y-m-d H:i:s', strtotime('+59 days')),
+                'err' => json_encode($invalidTokenSiteTitleList)
+            ]);
+
+            return view('admin/errors/tips', [
+                'tips' => '授权成功,有效期60天<br>失败列表:' . json_encode($invalidTokenSiteTitleList)
+            ]);
+
+
+        } catch (\Throwable $throwable) {
+            Log::warning(sprintf('facebook Auth%s', var_export($throwable->getMessage(), 1)));
+            return view('admin/errors/tips', [
+                'tips' => '页面令牌获取失败!!!'
+            ]);
+        }
+    }
+
+
+    public function oauth(Request $request)
+    {
+        $siteId = $request->input('siteId');
+//        ->scopes(['w_organization_social', 'r_organization_social', 'w_member_social','r_member_social'])
+        $which = $request->input('which');
+        switch ($which) {
+            case 'pinterest': //pin的授权地址需要完全一样 例如 pin后台填的是baidu.com/api 则授权地址必须是是baidu.com/api
+                if (!$siteId) {
+                    return view('admin/errors/tips', [
+                        'tips' => '请选择参数'
+                    ]);
+                }
+                $social = Social::query()->where(['site_id' => $siteId, 'type' => Social::PIN])->first();
+                if (!$social) {
+                    return view('admin/errors/tips', [
+                        'tips' => '社交账号信息不存在'
+                    ]);
+                }
+//                $clientId = $social->app_id;
+//                $clientSecret = $social->app_secret;
+//                $redirectUrl = $social->redirect_url;
+//                $config = new \SocialiteProviders\Manager\Config($clientId, $clientSecret, $redirectUrl);
+
+                config([
+                    'services.pinterest' => [
+                        'client_id' => $social->app_id,
+                        'client_secret' => $social->app_secret,
+                        'redirect' => $social->redirect_url
+                    ]
+                ]);
+
+//                return Socialite::driver('pinterest')->setConfig($config)->scopes(['read_public', 'write_public'])->redirect();
+                return Socialite::driver('pinterest')->scopes(['read_public', 'write_public'])->redirect();
+            case 'linkedIn': //expiresIn: 5184000 60天
+
+                $social = Social::query()->where(['site_id' => 0, 'type' => Social::LINKED])->first();
+                if (!$social) {
+                    return view('admin/errors/tips', [
+                        'tips' => '社交账号信息不存在'
+                    ]);
+                }
+
+                //UGC Post is an upcoming API that will eventually replace the Shares API.
+                //https://docs.microsoft.com/en-us/linkedin/marketing/integrations/community-management/shares/ugc-post-api
+                config([
+                    'services.linkedin.client_id' => $social->app_id,
+                    'services.linkedin.client_secret' => $social->app_secret,
+                    'services.linkedin.redirect' => sprintf('%s?siteId=%s', $social->redirect_url, $siteId),
+                ]);
+//                return Socialite::driver('LinkedIn')->scopes(['w_member_social', 'w_organization_social'])->redirect();
+                return Socialite::driver('LinkedIn')->scopes(['r_emailaddress', 'r_liteprofile', 'w_member_social', 'w_organization_social'])->redirect();
+//                return Socialite::driver('LinkedIn')->scopes(['r_emailaddress', 'r_liteprofile', 'w_member_social','w_organization_social'])->redirect();
+            case 'twitter': //twitter地址一样但是可以携带查询字符串 比如twitter 后台填的是baidu.com/api 但是baidu.com/api?siteId=1 也是可以的
+                config([
+                    'services.twitter.redirect' => sprintf(
+                        '%s?siteId=%s',
+                        config('services.twitter.redirect'),
+                        $siteId
+                    )
+                ]);
+                return Socialite::with('twitter')->redirect();
+                break;
+            case 'facebook':
+                return Socialite::driver('facebook')->scopes(['manage_pages', 'publish_pages'])->redirect();
+            default:
+                break;
+        }
+        return view('admin/errors/tips', [
+            'tips' => '无授权类型'
+        ]);
+    }
+
+
+    public function index(Request $request)
+    {
+
+        if (!$request->ajax()) {
+            $allSite = Site::query()->select(['id', 'cn_title'])->get();
+            return view('admin/social/index', ['sites' => $allSite]);
+        }
+        $keyword = $request->input('keyword');
+
+        $builder = HootsuiteUser::query()->with('site');
+        if ($keyword) {
+            $siteIds = Site::query()->where('cn_title', 'like', '%' . $keyword . '%')
+                ->orWhere('domain', 'like', '%' . $keyword . '%')->pluck('id')->toArray();
+            $builder->where('email', 'like', '%' . $keyword . '%')->orWhereIn('site_id', $siteIds);
+        }
+
+        $result = $builder->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+
+        $items = $result->items();
+
+
+        array_walk($items, function ($item) {
+            $item->site_title = $item->site->cn_title??'未绑定';
+            unset($item->role);
+        });
+
+        return response()->json([
+            'rows' => $items,
+            'total' => $result->total()
+        ]);
+    }
+
+    public function hootsuite(Request $request)
+    {
+        $client = new Client();
+        $url = sprintf('%s/admin/hootsuite/token', config('app.wall_url'));
+        try {
+            $response = $client->request('POST', $url, [
+                'form_params' => $request->input()
+            ]);
+
+            $result = $response->getBody()->getContents();
+            dd($result);
+        } catch (\GuzzleHttp\Exception\GuzzleException $exception) {
+            dd($exception->getMessage());
+        }
+    }
+
+    public function hootsuiteAuth(){
+        $client_id = 'c1ada351-6797-406e-8706-342af91ecad5';
+        $redirect_uri = 'https://admin.yinqingli.com/admin/socials/hootsuite';
+        $state = '';
+        return redirect(sprintf(
+            'https://platform.hootsuite.com/oauth2/auth?response_type=code&client_id=%s&scope=offline&state=%s&redirect_uri=%s',
+            $client_id,
+            $state,
+            $redirect_uri
+        ));
+    }
+}

+ 491 - 0
app/Http/Controllers/Admin/SocialStatisticsController.php

@@ -0,0 +1,491 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Logics\Admin\SocialLogic;
+use App\Http\Models\HootsuiteHistory;
+use App\Http\Models\HootsuiteUser;
+use App\Http\Models\PrSocial;
+use App\Http\Models\Site;
+use App\Http\Models\Social;
+use Facebook\Facebook;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Laravel\Socialite\Facades\Socialite;
+
+class SocialStatisticsController extends Controller
+{
+    public function socialStatistics($siteId)
+    {
+        $site = Site::query()->where('id', $siteId)->first();
+
+        $startDate = HootsuiteHistory::query()->where('site_id', $siteId)->orderBy('publish_at')->value('publish_at') ?? date('Y-m-01 00:00:00', strtotime('first day of -1 month'));
+
+        $ym = date('Y-m-01 00:00:00', strtotime($startDate));
+        $ym2 = date('Y-m-t 23:59:59', strtotime('first day of -1 month'));
+
+        $this->getSitePublishStatus($siteId, $ym, $ym2);
+
+        $date1 = date('Y-m', strtotime('first day of -3 month'));
+        $date2 = date('Y-m', strtotime('first day of -2 month'));
+        $date3 = date('Y-m', strtotime('first day of -1 month'));
+
+        $twitterCountList = $this->getTwitterCountList($siteId, $ym, $ym2, $date1, $date2, $date3);
+        $facebookCountList = $this->getFacebookCountList($siteId, $ym, $ym2, $date1, $date2, $date3);
+        $linkedInCountList = $this->getLinkedInCountList($siteId, $ym, $ym2, $date1, $date2, $date3);
+        $pinCountList = $this->getPinCountList($siteId, $ym, $ym2, $date1, $date2, $date3);
+        $instagramCountList = $this->getInstagramCount($siteId, $ym, $ym2, $date1, $date2, $date3);
+        $youtubeCountList = $this->getYoutubeCountList($siteId, $ym, $ym2, $date1, $date2, $date3);
+
+        $dataList = [
+            [
+                'type' => 'twitter',
+                'data' => [
+                    'data_list' => $twitterCountList,
+                    'hide' => 0,
+                ]
+            ],
+            [
+                'type' => 'facebook',
+                'data' => [
+                    'data_list' => $facebookCountList,
+                    'hide' => 0,
+                ]
+            ],
+            [
+                'type' => 'linkedIn',
+                'data' => [
+                    'data_list' => $linkedInCountList,
+                    'hide' => 0,
+                ]
+            ],
+
+            [
+                'type' => 'pin',
+                'data' => [
+                    'data_list' => $pinCountList,
+                    'hide' => 0,
+                ]
+            ],
+            [
+                'type' => 'ins',
+                'data' => [
+                    'data_list' => $instagramCountList,
+                    'hide' => 0,
+                ]
+            ],
+            [
+                'type' => 'youtube',
+                'data' => [
+                    'data_list' => $youtubeCountList,
+                    'hide' => 0,
+                ]
+            ],
+        ];
+        $lastMonth = date('Ym', strtotime('first day of -1 month'));
+
+        foreach ($dataList as $item) {
+            if (PrSocial::query()->where(['site_id' => $site->id, 'ym' => $lastMonth])->where(['type' => $item['type']])->exists()) {
+                PrSocial::query()->where(['site_id' => $site->id, 'ym' => $lastMonth])->where(['type' => $item['type']])->update([
+                    'data_list' => json_encode($item['data']['data_list']),
+                    'hide' => $item['data']['hide'],
+                ]);
+            } else {
+                PrSocial::query()->create([
+                    'ym' => $lastMonth,
+                    'type' => $item['type'],
+                    'site_id' => $site->id,
+                    'old_id' => $site->old_id,
+                    'data_list' => $item['data']['data_list'],
+                    'hide' => $item['data']['hide']
+                ]);
+            }
+        }
+        $this->saveNextSocial($site, $dataList);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function getSitePublishStatus($siteId, $ym, $ym2)
+    {
+        $list = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->select('id', 'site_id', 'url', 'publish_at', 'social_ids')
+            ->whereBetween('publish_at', [$ym, [$ym2]])
+            ->get();
+
+        foreach ($list as $key => $value) {
+
+            $socialIdList = json_decode($value->social_ids);
+            if (empty($socialIdList)) {
+                $twitter = strstr($value->url, 'twitter');
+                if (!empty($twitter)) {
+                    $value->is_twitter = 1;
+                }
+                $facebook = strstr($value->url, 'facebook');
+                if (!empty($facebook)) {
+                    $value->is_facebook = 1;
+                }
+                $linkEdin = strstr($value->url, 'linkedin');
+                if (!empty($linkEdin)) {
+                    $value->is_linkedIn = 1;
+                }
+                $pin = strstr($value->url, 'pin');
+                if (!empty($pin)) {
+                    $value->is_pin = 1;
+                }
+                $youtube = strstr($value->url, 'youtube');
+                if (!empty($youtube)) {
+                    $value->is_youtube = 1;
+                }
+                $instagram = strstr($value->url, 'instagram');
+                if (!empty($instagram)) {
+                    $value->is_instagram = 1;
+                }
+            } else {
+                $result = $this->getSocialId($value->site_id, $socialIdList);
+                foreach ($result as $kk => $vv) {
+                    if ($vv == 'TWITTER') {
+                        $value->is_twitter = 1;
+                    }
+                    if ($vv == 'FACEBOOKPAGE') {
+                        $value->is_facebook = 1;
+                    }
+                    if ($vv == 'LINKEDINCOMPANY') {
+                        $value->is_linkedIn = 1;
+                    }
+                    if ($vv == 'PINTEREST') {
+                        $value->is_pin = 1;
+                    }
+                }
+            }
+            $update = [];
+            if (!empty($value->is_twitter)) {
+                $update['is_twitter'] = $value->is_twitter ?? 0;
+            }
+            if (!empty($value->is_facebook)) {
+                $update['is_facebook'] = $value->is_facebook ?? 0;
+            }
+            if (!empty($value->is_linkedIn)) {
+                $update['is_linkedIn'] = $value->is_linkedIn ?? 0;
+            }
+            if (!empty($value->is_pin)) {
+                $update['is_pin'] = $value->is_pin ?? 0;
+            }
+            if (!empty($value->is_youtube)) {
+                $update['is_youtube'] = $value->is_youtube ?? 0;
+            }
+            if (!empty($value->is_instagram)) {
+                $update['is_instagram'] = $value->is_instagram ?? 0;
+            }
+
+            if (!empty($update)) {
+                DB::table('hootsuite_history')->where('id', $value->id)->update($update);
+            }
+        }
+    }
+
+    public function getSocialId($siteId, $socialIdsList)
+    {
+        $array = [];
+        $hootSuiteUser = HootsuiteUser::query()->where(['site_id' => $siteId])->first();
+        $socialProfiles = $hootSuiteUser->social_profiles ?? [];
+        foreach ($socialProfiles as $key => $value) {
+            foreach ($socialIdsList as $kk => $vv) {
+                if ($value['id'] == $vv) {
+                    $array[] = $value['type'];
+                }
+            }
+        }
+
+        return $array;
+    }
+
+    public function getTwitterCountList($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list1 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_twitter', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as twitter_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $twitterCount = [null, null, null];
+        if (empty($list1)) {
+            return $twitterCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+            foreach ($list1 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->twitter_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $twitterCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $twitterCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $twitterCount[0] = $value['sum'];
+            }
+        }
+        return $twitterCount;
+    }
+
+    public function getFacebookCountList($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list2 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_facebook', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as facebook_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $facebookCount = [null, null, null];
+
+        if (empty($list2)) {
+            return $facebookCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+            foreach ($list2 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->facebook_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $facebookCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $facebookCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $facebookCount[0] = $value['sum'];
+            }
+        }
+
+        return $facebookCount;
+    }
+
+    public function getLinkedInCountList($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list3 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_linkedIn', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as linkedIn_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $linkedInCount = [null, null, null];
+
+        if (empty($list3)) {
+            return $linkedInCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+
+            foreach ($list3 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->linkedIn_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $linkedInCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $linkedInCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $linkedInCount[0] = $value['sum'];
+            }
+        }
+
+        return $linkedInCount;
+    }
+
+    public function getPinCountList($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list4 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_pin', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as pin_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $pinCount = [null, null, null];
+        if (empty($list4)) {
+            return $pinCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+
+            foreach ($list4 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->pin_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $pinCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $pinCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $pinCount[0] = $value['sum'];
+            }
+        }
+
+        return $pinCount;
+    }
+
+    public function getYoutubeCountList($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list5 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_youtube', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as youtube_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $youtubeCount = [null, null, null];
+        if (empty($list5)) {
+            return $youtubeCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+            foreach ($list5 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->youtube_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $youtubeCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $youtubeCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $youtubeCount[0] = $value['sum'];
+            }
+        }
+        return $youtubeCount;
+    }
+
+    public function getInstagramCount($siteId, $ym, $ym2, $date1, $date2, $date3)
+    {
+        $list6 = DB::table('hootsuite_history')
+            ->where('site_id', $siteId)
+            ->where('is_instagram', 1)
+            ->whereBetween('publish_at', [$ym, $ym2])
+            ->selectRaw('DATE_FORMAT(publish_at,"%Y-%m") as date, count(*) as instagram_count,site_id')
+            ->groupBy('date', 'site_id')->get()->toArray();
+
+        $instagramCount = [null, null, null];
+        if (empty($list6)) {
+            return $instagramCount;
+        }
+
+        $dateList = $this->getDateList($ym);
+
+        foreach ($dateList as $key => $value) {
+            $sum = 0;
+            foreach ($list6 as $kk => $vv) {
+                if (strtotime($value['date']) >= strtotime($vv->date)) {
+                    $sum += $vv->instagram_count;
+                }
+                $dateList[$key]['sum'] = $sum;
+            }
+        }
+
+        foreach ($dateList as $key => $value) {
+            if ($value['date'] == $date1) {
+                $instagramCount[2] = $value['sum'];
+            }
+            if ($value['date'] == $date2) {
+                $instagramCount[1] = $value['sum'];
+            }
+            if ($value['date'] == $date3) {
+                $instagramCount[0] = $value['sum'];
+            }
+        }
+        return $instagramCount;
+    }
+
+    //提前保存下个月的
+    private function saveNextSocial($site, $dataList)
+    {
+        $ym = date('Ym');
+
+        foreach ($dataList as $item) {
+            $data = PrSocial::query()->where(['site_id' => $site->id, 'ym' => $ym, 'type' => $item['type']])->first();
+            $item['data']['data_list'][0] = null;
+            if ($data) {
+                $data->data_list = $item['data']['data_list'];
+                $data->hide = $item['data']['hide'];
+                $data->save();
+            } else {
+                PrSocial::query()->create([
+                    'ym' => $ym,
+                    'type' => $item['type'],
+                    'site_id' => $site->id,
+                    'old_id' => $site->old_id,
+                    'data_list' => $item['data']['data_list'],
+                    'hide' => $item['data']['hide']
+                ]);
+            }
+        }
+    }
+
+    public function getDateList($ym)
+    {
+        $startTime = strtotime(date("Y-m", strtotime("-1 months", strtotime($ym))));
+        $endTime = strtotime(date('Y-m', strtotime("first day of -1 month")));
+
+        $dateList = [];
+        $i = 0;
+        while ($startTime < $endTime) {
+            $startTime = strtotime('+1 Month', $startTime);
+            $i++;
+            $dateList[$i]['date'] = date('Y-m', $startTime);
+        }
+        return $dateList;
+    }
+}

+ 205 - 0
app/Http/Controllers/Admin/Stencil/AdvertiseController.php

@@ -0,0 +1,205 @@
+<?php
+/**
+ * 页面管理
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-08-01
+ */
+
+namespace App\Http\Controllers\Admin\Stencil;
+
+use App\Http\Controllers\Controller;
+use App\Http\Services\TemplateLibraryApiService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\View\View;
+use Illuminate\Http\JsonResponse;
+
+class AdvertiseController extends Controller
+{
+    private $templateLibraryApiService;
+
+    /**
+     * 模版站服务类
+     * TplController constructor.
+     * @param TemplateLibraryApiService $templateLibraryApiService
+     */
+    public function __construct(TemplateLibraryApiService $templateLibraryApiService)
+    {
+        $this->templateLibraryApiService = $templateLibraryApiService;
+    }
+
+    /**
+     * 广告设置
+     * @param Request $request
+     * @param $siteId
+     * @return Factory|View
+     */
+    public function index(Request $request, $siteId)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!$request->ajax()) {
+            return view('admin.stencil.advertise', [
+                'siteId' => $siteId,
+            ]);
+        }
+        $advertList = $connection->table('advert')->paginate(10);
+        foreach ($advertList as $value) {
+            $value->size = $value->width . '*' . $value->height;
+            $value->date = date('Y-m-d H:i:s', $value->create_time);
+        }
+        return response()->json([
+            'rows' => $advertList->items(),
+            'total' => $advertList->total(),
+        ]);
+    }
+
+
+    /**
+     * 编辑
+     * @param Request $request
+     * @param $id
+     * @param $siteId
+     * @return Factory|JsonResponse|View
+     */
+    public function advertiseEdit(Request $request, $id, $siteId)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!$request->ajax()) {
+            $info = $connection->table('advert')->where('id', $id)->first() ?? [];
+            if (!empty($info)) {
+                $info->advertise_name = $info->name ?? '';
+                $info->advertise_code = $info->code ?? '';
+            }
+            return view('admin.stencil.advertise_edit', [
+                'siteId' => $siteId,
+                'info' => $info,
+            ]);
+        }
+        $request = $request->all();
+        $update = [
+            'code' => $request['advertise_code'] ?? '',
+            'name' => $request['advertise_name'] ?? '',
+            'width' => $request['width'] ?? 0,
+            'height' => $request['height'] ?? 0,
+            'limit' => $request['limit'] ?? 0,
+            'site_id' => 0,
+            'create_time' => time(),
+        ];
+
+        if (empty($id)) {
+            $connection->table('advert')->insert($update);
+        } else {
+            $connection->table('advert')->where('id', $id)->update($update);
+        }
+
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 广告设置
+     * @param Request $request
+     * @param $id
+     * @param $siteId
+     * @return Factory|JsonResponse|View
+     */
+    public function setting(Request $request, $id, $siteId)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!$request->ajax()) {
+            return view('admin.stencil.advertise_setting', [
+                'siteId' => $siteId,
+                'advertId' => $id,
+            ]);
+        }
+        $advertList = $connection->table('advert')->pluck('name', 'id');
+        $advertElementList = $connection->table('advert_element')->where('advert_id', $id)->paginate(10);
+        foreach ($advertElementList as $value) {
+            $value->status = '启用';
+            if ($value->is_enabled == 1) {
+                $value->status = '禁用';
+            }
+            $value->startDate = '';
+            if (!empty($value->start_time)) {
+                $value->startDate = date('Y-m-d H:i:s', $value->start_time);
+            }
+            $value->endDate = '';
+            if (!empty($value->end_time)) {
+                $value->endDate = date('Y-m-d H:i:s', $value->end_time);
+            }
+            $value->pushDate = $value->startDate . ' - ' . $value->endDate;
+            $value->advert = $advertList[$value->advert_id] ?? '';
+        }
+        return response()->json([
+            'rows' => $advertElementList->items(),
+            'total' => $advertElementList->total(),
+        ]);
+    }
+
+
+    /**
+     * 广告设置编辑
+     * @param Request $request
+     * @param $id
+     * @param $siteId
+     * @param $advertId
+     * @return Factory|View
+     */
+    public function settingEdit(Request $request, $id, $siteId, $advertId)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!$request->ajax()) {
+            $advertList = $connection->table('advert')->get();
+            $info = $connection->table('advert_element')->where('id', $id)->first() ?? [];
+            if (!empty($info)) {
+                $info->start_time = date('Y-m-d H:i:s', $info->start_time);
+                $info->end_time = date('Y-m-d H:i:s', $info->end_time);
+            }
+            return view('admin.stencil.setting_edit', [
+                'advertList' => $advertList,
+                'advertId' => $advertId,
+                'siteId' => $siteId,
+                'info' => $info,
+                'id' => $id,
+            ]);
+        }
+        $request = $request->all();
+        $data = [
+            'theme' => $request['theme'] ?? 0,
+            'advert_id' => $request['advert_id'] ?? 0,
+            'type' => $request['type'] ?? 'none',
+            'description' => $request['description'] ?? '',
+            'start_time' => strtotime($request['start_time'] ?? date('Y-m-d H:i:s')),
+            'end_time' => strtotime($request['end_time'] ?? date('Y-m-d H:i:s')),
+            'is_enabled' => $request['is_enabled'] ?? 0,
+            'html' => '',
+        ];
+        if ($request['type'] == 'text') {
+            $data['description'] = $request['text_description'] ?? '';
+            $data['link'] = $request['text_link'] ?? '';
+        }
+        if ($request['type'] == 'image') {
+            $data['source'] = $request['source'] ?? '';
+            $data['description'] = $request['description'] ?? '';
+            $data['description_two'] = $request['description_two'] ?? '';
+            $data['description_three'] = $request['description_three'] ?? '';
+            $data['link'] = $request['link'] ?? '';
+            $data['button'] = $request['button'] ?? '';
+            $data['button_link'] = $request['button_link'] ?? '';
+            $data['button_two'] = $request['button_two'] ?? '';
+            $data['button_two_link'] = $request['button_two_link'] ?? '';
+        }
+        if ($request['type'] == 'html') {
+            $data['html'] = $request['html'] ?? '';
+        }
+        if (empty($id)) {
+            $connection->table('advert_element')->insert($data);
+        } else {
+            $connection->table('advert_element')->where('id', $id)->update($data);
+        }
+        return response()->json(['message' => '操作成功']);
+
+    }
+}

+ 873 - 0
app/Http/Controllers/Admin/Stencil/IndexController.php

@@ -0,0 +1,873 @@
+<?php
+/**
+ * 页面管理
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-08-01
+ */
+
+namespace App\Http\Controllers\Admin\Stencil;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use App\Http\Services\TemplateLibraryApiService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\View\View;
+use Illuminate\Http\JsonResponse;
+
+
+class IndexController extends Controller
+{
+    private $templateLibraryApiService;
+
+    /**
+     * 模版库服务类
+     * IndexController constructor.
+     * @param TemplateLibraryApiService $templateLibraryApiService
+     */
+    public function __construct(TemplateLibraryApiService $templateLibraryApiService)
+    {
+        $this->templateLibraryApiService = $templateLibraryApiService;
+    }
+
+    /**
+     * 页面管理
+     * @param $siteId
+     * @return Factory|View
+     */
+    public function index($siteId)
+    {
+        try {
+            $site = DB::connection($this->templateLibraryApiService->connection($siteId))->table('content_template')->first();
+            $trees = list_to_tree($this->templateLibraryApiService->getWebSitePage($siteId), 'id', 'parent_id', 'children');
+            $templateList = DB::connection($this->templateLibraryApiService->connection($siteId))->table('content_template')
+                    ->where('level', 2)
+                    ->orderBy('rank', 'ASC')
+                    ->orderBy('id', 'ASC')->get() ?? [];
+            return view('admin.stencil.index', [
+                'trees' => $trees,
+                'siteId' => $siteId,
+                'templateList' => $templateList,
+                'site' => $site,
+            ]);
+        } catch (\Throwable $exception) {
+            echo $exception->getMessage();
+        }
+    }
+
+    /**
+     * 内容列表以及选中
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function getResourceList(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        $contentModel = $connection->table('content');
+        if (!empty($result['content'])) {
+            $contentModel = $contentModel->where('title', 'like', '%' . $result['content'] . '%');
+        }
+        $list = $contentModel->where('is_enabled', 1)->paginate($request->input('pageSize') ?? 6);
+        $defaultList = $connection->table('content_var')->where('content_id', $result['contentId'] ?? 0)->get() ?? [];
+
+        $defaultResultList = [];
+        foreach ($defaultList as $value) {
+            $result = unserialize($value->value);
+            foreach ($result as $vv) {
+                $defaultResultList[] = $vv;
+            }
+        }
+
+        foreach ($list as $value) {
+            $value->default = 0;
+            $value->sort = 0;
+
+            foreach ($defaultResultList as $vv) {
+                if ($value->id == $vv['id']) {
+                    $value->default = 1;
+                    $value->sort = 1;
+                }
+            }
+        }
+
+        return response()->json(['message' => '操作成功', 'list' => $list]);
+    }
+
+    /**
+     * 添加页面管理顶级菜单
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function addParentMenu(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        $list = $this->templateLibraryApiService->getIndexFieldList(0, $result['name'] ?? '');
+
+        $connection->table('content')->insert($list);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 复制模版库列表节点
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function indexCopyNode(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        $array = \GuzzleHttp\json_decode($connection->table('content')->get()->toJson(), true) ?? [];
+        $parentList = get_object_vars($connection->table('content')->where('id', $result['id'])->first()) ?? [];
+
+        $childList = $this->getIndexList($array, $result['id']);
+        $childList[] = $parentList;
+
+        $list = $this->arraySort($childList, 'id');
+        $list = array_merge($list);
+
+        $ids = [];
+        foreach ($list as $key => $item) {
+            if ($item['parent_id'] == 0 && $key == 0) {
+                $update = $this->templateLibraryApiService->getFieldList2($item, $ids);
+
+            } elseif ($item['parent_id'] != 0 && $key == 0) {
+
+                $ids[$item['parent_id']] = $item['parent_id'];
+                $update = $this->templateLibraryApiService->getFieldList2($item, $ids);
+            } else {
+                $update = $this->templateLibraryApiService->getFieldList2($item, $ids);
+            }
+            $id = $connection->table('content')->insertGetId($update);
+            $ids[$item['id']] = $id;
+        }
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 递归父级目录
+     * @param $array
+     * @param int $pid
+     * @return array
+     */
+    public function getIndexList($array, $pid = 0)
+    {
+        static $data = [];
+        foreach ($array as $key => $item) {
+            if ($item['parent_id'] == $pid) {
+                $data[] = $this->templateLibraryApiService->getFieldList($item);
+                unset($array[$key]);
+                $this->getIndexList($array, $item['id']);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 添加页面管理子菜单
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function indexAddNode(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        $filed = $this->templateLibraryApiService->getIndexFieldList($result['pid'], $result['node']);
+        $connection->table('content')->insert($filed);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 拖动子节点
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function indexMoveNode(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        $parent = $connection->table('content')->where('id', $result['pid'])->first();
+        $child = $connection->table('content')
+            ->select('id', 'title', 'rank')
+            ->where('id', '=', $result['id'])
+            ->first();
+
+        if ($result['type'] == 'next') {
+            $contentList = $connection->table('content')
+                ->select('id', 'title', 'rank')
+                ->where('id', '!=', $result['id'])
+                ->where('parent_id', $parent->parent_id)
+                ->orderBy('rank', 'asc')
+                ->get()->toArray();
+
+            $list = [];
+            foreach ($contentList as $value) {
+                if ($value->id == $result['pid']) {
+                    $list[] = $value;
+                    $list[] = $child;
+                } else {
+                    $list[] = $value;
+                }
+            }
+
+            $num = 0;
+            foreach ($list as $item) {
+                $connection->table('content')
+                    ->where('id', $item->id)
+                    ->update(['parent_id' => $parent->parent_id ?? 0, 'rank' => ++$num]);
+            }
+        }
+
+        if ($result['type'] == 'prev') {
+            $connection->table('content')
+                ->where('id', $result['id'])
+                ->update(['parent_id' => $parent->id ?? 0, 'rank' => 1]);
+        }
+        if ($result['type'] == 'inner') {
+            $contentList = $connection->table('content')
+                ->select('id', 'title', 'rank')
+                ->where('id', '!=', $result['id'])
+                ->where('parent_id', $parent->id)
+                ->orderBy('rank', 'asc')
+                ->get()->toArray();
+
+            if (!empty($contentList)) {
+
+                $contentList[] = $child;
+                $num = 0;
+                foreach ($contentList as $item) {
+                    $connection->table('content')
+                        ->where('id', $item->id)
+                        ->update(['parent_id' => $parent->id ?? 0, 'rank' => ++$num]);
+                }
+            } else {
+                $connection->table('content')
+                    ->where('id', $result['id'])
+                    ->update(['parent_id' => $parent->id ?? 0, 'rank' => 1]);
+            }
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    /**
+     * 删除页面管理子菜单
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function indexDelNode(Request $request, $siteId)
+    {
+        $result = $request->all();
+        if (empty($result['id']) || $result['id'] == 1) {
+            return response()->json(['message' => '操作失败'], 400);
+        }
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        $contentInfo = $connection->table('content')->where('id', $result['id'])->first();
+        if (!empty($contentInfo)) {
+            if ($contentInfo->status == 2 && $result['type'] == 2) {
+                $result['type'] = 99;//如果已经被删掉了,再次删除就隐藏
+            }
+            $array[] = $contentInfo;
+            $array = $this->templateLibraryApiService->recursion($array, $siteId);
+            $ids = array_column($array, 'id');
+            $connection->table('content')
+                ->whereIn('id', $ids)
+                ->update(['status' => $result['type']]);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 图片上传页面
+     * @param Request $request
+     * @param $siteId
+     * @param $type
+     * @return Factory|JsonResponse|View
+     */
+    public function images(Request $request, $siteId, $type)
+    {
+        if (!$request->ajax()) {
+            return view('admin.stencil.image', [
+                'siteId' => $siteId,
+                'type' => $type
+            ]);
+        }
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId))->table('image');
+        if (!empty($result['title'])) {
+            $connection->where('name', 'like', '%' . $result['title'] . '%');
+        }
+        $items = $connection->orderByDesc('id')->paginate(12);
+        return response()->json([
+            'rows' => $items->items(),
+            'total' => $items->total(),
+        ]);
+    }
+
+    /**
+     * 批量上传图片
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function uploadImages(Request $request, $siteId)
+    {
+        $images = [];
+        if ($request->hasFile('files')) {
+
+            foreach ($request->file('files') as $file) {
+
+                [$name, $alt] = $this->fileRename($file);
+                $fileData['file_url'] = $file->storeAs($siteId, $name, 'download');
+                $fileData['file_url'] = asset('download/' . $fileData['file_url']);
+                $fileData['original_name'] = $name;
+                $fileData['time'] = time();
+                $images[] = [
+                    'ref' =>'',
+                    'sign' => md5($fileData['file_url']),
+                    'name' => $alt,
+                    'src' => $fileData['file_url'],
+                    'size' => $file->getSize(),//bytes
+                    'format' => '',
+                    'site_id' => $siteId,
+                    'create_time' => time(),
+                ];
+            }
+        }
+        DB::connection($this->templateLibraryApiService->connection($siteId))->table('image')->insert($images);
+        return response()->json(['message' => '上传成功', 'data' => $images]);
+    }
+
+    /**
+     * 保存页面信息
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function indexSave(Request $request, $siteId)
+    {
+        $request = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        if (empty($request['title'])) {
+            return response()->json(['message' => '请填写标题'], 400);
+        }
+        if (empty($request['tpl_id'])) {
+            return response()->json(['message' => '请选择模版'], 400);
+        }
+        if (!empty($request['new_pid']) && empty($request['index_id'])) {
+            $filed = $this->templateLibraryApiService->getIndexFieldList($request['new_pid'], $request['title']);
+            $insertGetId = $connection->table('content')->insertGetId($filed);
+            $request['index_id'] = $insertGetId;
+            $request['parent_id'] = $request['new_pid'];
+        }
+        if (empty($request['index_id'])) {
+            return response()->json(['message' => '请选择页面'], 400);
+        }
+
+        $publishTime = time();
+        if (!empty($request['publish_time'])) {
+            $publishTime = strtotime($request['publish_time']);
+        }
+        $expiredTime = time();
+        if (!empty($request['expired_time'])) {
+            $expiredTime = strtotime($request['expired_time']);
+        }
+
+        $alias = strtolower(str_replace(' ', '-', $request['title'])) ?? '';
+
+        $template = $connection->table('content_template')
+            ->where('id', $request['tpl_id'])
+            ->value('name');
+        if ($template == 'basic' || $template == 'product_faq') {
+            $request['deny_spider'] = 1;
+            $request['is_hidemenu'] = 1;
+            $request['is_enabled'] = 1;
+        }
+
+        //没有绑定绑定uri就自动生成uri
+        if (empty($request['is_freeze_url'])) {
+            $urlLevel = $connection->table('content_template')
+                    ->where('id', $request['tpl_id'])
+                    ->value('url_level') ?? 0;
+            $parentId = $connection->table('content')
+                    ->where('id', $request['index_id'])
+                    ->value('parent_id') ?? 0;
+
+            if (empty($urlLevel)) {
+                $uri = $alias;
+                if ($template == 'basic') {
+                    $uri = $request['index_id'] . '-' . $alias;
+                }
+            } elseif ($parentId == 0) {
+                $uri = $alias;
+                if ($template == 'basic') {
+                    $uri = $request['index_id'] . '-' . $alias;
+                }
+            } else {
+                $uri = $this->getUriLevel($siteId, $parentId);
+                $uri = array_reverse($uri);
+                $uri = array_splice($uri, 0, $urlLevel - 1);
+                if ($template == 'basic') {
+                    $uri = implode('/', $uri) . '/' . $request['index_id'] . '-' . $alias;
+                } else {
+                    $uri = implode('/', $uri) . '/' . $alias;
+                }
+            }
+        } else {
+            //绑定的以手写的为主
+            $uri = $request['uri'] ?? '';
+        }
+        //查询生成的uri是否是重复的
+        $uriResult = $connection->table('content')
+            ->where('status', 1)
+            ->where('id', '!=', $request['index_id'])
+            ->where('uri', $uri)
+            ->first();
+
+        if (!empty($uriResult)) {
+            if (!empty($insertGetId)) {
+                $connection->table('content')->where('id', $insertGetId)->delete();
+            }
+            return response()->json(['message' => 'uri重复'], 400);
+        }
+
+        $data = [
+            'parent_id' => $request['parent_id'] ?? 0,      //父级id
+            'tpl_id' => $request['tpl_id'] ?? 0,            //模版id
+            'title' => $request['title'] ?? '',             //标题
+            'subtitle' => $request['subtitle'] ?? '',       //副标题
+            'summary' => $request['summary'] ?? '',         //内容摘要
+            'thumb' => $request['thumb'] ?? '',             //缩略图
+            'content' => $request['content'] ?? '',         //内容
+            'menu_text' => $request['menu_text'] ?? '',     //导航标题
+            'alias' => $request['index_id'] . '-' . $alias, //URL别名
+            'uri' => $uri,
+            'seo_title' => $request['seo_title'] ?? '{$title} - {$setting.sitename}',
+            'seo_keywords' => $request['seo_keywords'] ?? '',
+            'seo_description' => $request['seo_description'] ?? '{$summary}',
+            'path_ids' => $request['path_ids'] ?? '',
+            'lang' => $request['lang'] ?? '',
+            'rank' => $request['rank'] ?? 0,
+            'level' => $request['level'] ?? 0,
+            'redirect' => $request['redirect'] ?? '',
+            'content_type' => $request['content_type'] ?? 'html',
+            'childs_num' => $request['childs_num'] ?? 0,
+            'views_num' => $request['views_num'] ?? 0,
+            'is_enabled' => $request['is_enabled'] ?? 0,
+            'is_hidemenu' => $request['is_hidemenu'] ?? 0,
+            'is_translated' => $request['is_translated'] ?? 0,
+            'is_freeze_url' => $request['is_freeze_url'] ?? 0,
+            'is_301' => $request['is_301'] ?? 0,
+            'deny_spider' => $request['deny_spider'] ?? 0,
+            'trans_src_id' => $request['trans_src_id'] ?? 0,
+            'create_time' => time(),
+            'update_time' => time(),
+            'publish_time' => $publishTime,
+            'expired_time' => $expiredTime,
+            'tmp_xcode' => $request['tmp_xcode'] ?? '',
+            'css_id' => $request['css_id'] ?? '',
+        ];
+
+        $connection->table('content')->where('id', $request['index_id'])->update($data);
+
+        $templateVarList = $connection->table('content_template_var')->get();
+        $variable = [];
+        foreach ($templateVarList as $item) {
+            $variable[] = [
+                'type' => $item->input_type,
+                'key' => $item->name,
+                'value' => $request[$item->name] ?? '',
+                'content_id' => $request['index_id'],
+                'lang' => 'en',
+                'allow_translate' => 0,
+            ];
+        }
+
+        foreach ($variable as $key => $item) {
+            if ($item['type'] == 'resource' || $item['type'] == 'checkbox' || $item['type'] == 'file' || $item['type'] == 'image') {
+                $variable[$key]['value'] = serialize($item['value'] ?? '');
+            }
+        }
+
+        //事务
+        DB::transaction(function () use ($connection, $variable, $request) {
+            $connection->table('content_var')->where('content_id', $request['index_id'])->delete();
+            $connection->table('content_var')->insert($variable);
+        });
+
+        return response()->json(['message' => '保存成功']);
+    }
+
+    /**
+     * 添加内容
+     * @param Request $request
+     * @param $siteId
+     * @param $type
+     * @return Factory|JsonResponse|View
+     */
+    public function resources(Request $request, $siteId, $type)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!$request->ajax()) {
+            $templateList = $connection->table('content_template')
+                    ->where('level', 2)
+                    ->pluck('name', 'id') ?? [];
+            return view('admin.stencil.resources', [
+                'templateList' => $templateList,
+                'siteId' => $siteId,
+                'type' => $type
+            ]);
+        }
+        $resources = $connection->table('content');
+        $title = $request->input('title');
+        if (!empty($title)) {
+            $resources = $resources->where('title', 'like', '%' . $title . '%');
+        }
+        $ref = $request->input('ref');
+        if ($ref == 'now') {
+            $resources = $resources->where('tpl_id', 193);
+        }
+        $tplId = $request->input('tplId');
+        if (!empty($tplId)) {
+            $resources = $resources->where('tpl_id', $tplId);
+        }
+        $resources = $resources->whereIn('status', [1, 2])->orderByDesc('id')->paginate(12);
+
+        return response()->json([
+            'rows' => $resources->items(),
+            'total' => $resources->total(),
+        ]);
+    }
+
+    /**
+     * 批量上传保留上传文件名
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function bulkUpload(Request $request)
+    {
+        $files = [];
+        $siteId = $request->input('siteId') ?? 0;
+        if ($request->hasFile('files')) {
+
+            foreach ($request->file('files') as $file) {
+
+                [$name, $alt] = $this->fileRename($file);
+                $fileUrl = $file->storeAs($siteId, $name, 'download');
+
+                $fileData['file_url'] = asset('download/' . $fileUrl);
+                $fileData['file_size'] = $file->getSize();
+                $fileData['original_name'] = $file->getClientOriginalName();
+                $fileData['time'] = date('Y-m-d H:i:s');
+                $files[] = $fileData;
+            }
+        }
+        return response()->json(['message' => '上传成功', 'data' => $files]);
+    }
+
+
+    /**
+     * 文件重命名
+     * @param $file object
+     * @return array
+     */
+    protected function fileRename($file)
+    {
+        $name = strtolower(str_replace(' ', '-', $file->getClientOriginalName()));
+        $array = explode('.', $name);
+        $alt = str_replace('-', ' ', $array[0] ?? '');
+        $rename = $array[0] . '-' . rand(1000000, 9999999) . '.' . $array[1];
+        return [$rename, $alt];
+    }
+
+    /**
+     * 富文本多图上传接口
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function editorUpload(Request $request, $siteId)
+    {
+        $imageFileList = [];
+        $file = $request->file('editorFile');
+        if ($file) {
+            $imageFileList[] = $file;
+        }
+        $editorFile1 = $request->file('editorFile1');
+        if ($editorFile1) {
+            $imageFileList[] = $editorFile1;
+        }
+        $editorFile2 = $request->file('editorFile2');
+        if ($editorFile2) {
+            $imageFileList[] = $editorFile2;
+        }
+        $editorFile3 = $request->file('editorFile3');
+        if ($editorFile3) {
+            $imageFileList[] = $editorFile3;
+        }
+        $editorFile4 = $request->file('editorFile4');
+        if ($editorFile4) {
+            $imageFileList[] = $editorFile4;
+        }
+        $editorFile5 = $request->file('editorFile5');
+        if ($editorFile5) {
+            $imageFileList[] = $editorFile5;
+        }
+        $data = [];
+        foreach ($imageFileList as $item) {
+
+            [$name, $alt] = $this->fileRename($item);
+            $fileUrl = $item->storeAs($siteId, $name, 'download');
+
+            $data[] = [
+                'url' => asset('download/' . $fileUrl),
+                'alt' => $alt,
+                'href' => asset('download/' . $fileUrl),
+            ];
+        }
+        $result = [
+            'errno' => 0,
+            'data' => $data
+        ];
+        return response()->json($result);
+    }
+
+    /**
+     * 获取模版下面的css列表
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function getCssList(Request $request)
+    {
+        $request = $request->all();
+        $cssList = DB::connection($this->templateLibraryApiService->connection($request['siteId']))
+                ->table('content_template')
+                ->select('name', 'id')
+                ->where('level', 3)//css
+                ->where('parent_id', $request['tpl_id'])
+                ->get() ?? [];
+        return response()->json(['data' => $cssList]);
+    }
+
+
+    /**
+     *根据模版ip 获取关联变量列表
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function getTemplateVariableByTplId(Request $request)
+    {
+        $request = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($request['siteId']));
+        $variableIds = $connection
+            ->table('content_template_relate')
+            ->where('tid', $request['tpl_id'])
+            ->pluck('vid');
+        $variableList = $connection
+                ->table('content_template_var')
+                ->whereIn('id', $variableIds)
+                ->get() ?? [];
+        $variable = [];
+        if (!empty($request['content_id'])) {
+            $variable = $connection->table('content_var')
+                    ->where('content_id', $request['content_id'])
+                    ->pluck('value', 'key') ?? [];
+        }
+        foreach ($variableList as $item) {
+
+            $item->input_opts = explode("\r\n", $item->input_opts) ?? [];
+            $item->value = $item->input_value ?? '';
+            if ($item->input_type == 'checkbox' || $item->input_type == 'resource' || $item->input_type == 'file' || $item->input_type == 'image') {
+                $item->value = [];
+            }
+            if ($item->input_type == 'date') {
+                $item->value = date('Y-m-d');
+            }
+            if (count($variable) > 0) {
+                $item->value = $variable[$item->name] ?? '';
+                if ($item->input_type == 'checkbox' || $item->input_type == 'resource' || $item->input_type == 'file' || $item->input_type == 'image') {
+                    $item->value = unserialize($item->value) ?? '';
+                }
+                if ($item->input_type == 'date') {
+                    $item->value = date('Y-m-d', strtotime($item->value));
+                }
+            }
+        }
+        return response()->json(['data' => $variableList]);
+    }
+
+    /**
+     * 递归uri层级
+     * @param $siteId
+     * @param $parentId
+     * @return array
+     */
+    public function getUriLevel($siteId, $parentId)
+    {
+        static $array = [];
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        $indexInfo = $connection->table('content')->where('id', $parentId)->first();
+        if (!empty($indexInfo)) {
+            $array[] = strtolower(str_replace(' ', '-', $indexInfo->title)) ?? '';
+            $this->getUriLevel($siteId, $indexInfo->parent_id);
+        }
+        return $array;
+    }
+
+
+    public function ftp()
+    {
+        try {
+
+            $siteId = 360;
+            $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+            //同步所有页面的内容正文富文本图片
+            $imagesList = $connection->table('content')->get();
+            $images = [];
+            foreach ($imagesList as $item) {
+                preg_match_all('/<img(.*?)src=\"(.*?)\"(.*?)>/is', $item->content, $matches);//图片的url
+                if (!empty($matches[2])) {
+                    foreach ($matches[2] as $match) {
+                        $images[] = urldecode($match);
+                    }
+                }
+            }
+
+            $imagesPathList = [];
+            if (!empty($images)) {
+                foreach ($images as $image) {
+                    $imagePathArray = explode('storage', $image);
+                    if (!empty($imagePathArray[1])) {
+                        $imagesPathList[] = $imagePathArray[1];
+                    }
+                }
+                $imagesPathList = array_unique($imagesPathList);
+            }
+
+            pre_dump($imagesPathList);
+
+            //同步图片图里面的所有图片
+            $images2 = [];
+            $imagesList = $connection->table('image')->get();
+            foreach ($imagesList as $item) {
+                $imagePathArray = explode('storage', $item->src);
+                if (!empty($imagePathArray)) {
+                    $images2[] = $imagePathArray[1];
+                }
+            }
+            $images2 = array_unique($images2);
+            pre_dump($images2);
+
+            //同步所有页面上面的file变量文件
+            $file = [];
+            $imagesList = $connection->table('content_var')
+                ->where('type', 'file')->get();
+            foreach ($imagesList as $item) {
+                $fileList = unserialize($item->value);
+                if (is_array($fileList)) {
+                    foreach ($fileList as $value) {
+                        $imagePathArray = explode('storage', $value['file']);
+                        if (!empty($imagePathArray[1])) {
+                            $file[] = $imagePathArray[1];
+                        }
+                    }
+                }
+            }
+            $file = array_unique($file);
+            pre_dump($file);
+
+            //同步所有页面上面的file变量文件
+            $richText = [];
+            $imagesList = $connection->table('content_var')
+                ->where('type', 'richtext')->get();
+            foreach ($imagesList as $item) {
+                preg_match_all('/<img(.*?)src=\"(.*?)\"(.*?)>/is', $item->value, $matches);//图片的url
+                if (!empty($matches[2])) {
+                    foreach ($matches[2] as $match) {
+                        $richText[] = urldecode($match);
+                    }
+                }
+            }
+            $richTextImages = [];
+            if (!empty($richText)) {
+                foreach ($richText as $image) {
+                    $imagePathArray = explode('storage', $image);
+                    if (!empty($imagePathArray[1])) {
+                        $imagesPathList[] = $imagePathArray[1];
+                    }
+                }
+                $richTextImages = array_unique($imagesPathList);
+            }
+
+            pre_dump($richTextImages);
+
+            $result = array_merge($imagesPathList, $images2, $file, $richTextImages);
+            $result = array_unique($result);
+            pre_dump($result);
+
+
+            $ssh_host = '121.199.40.85';
+            $ssh2 = ssh2_connect($ssh_host, 22);
+            $user = 'root';
+            $password = 'JGJHD84@8&a';
+            ssh2_auth_password($ssh2, $user, $password);
+
+            $domain = Site::query()
+                ->where('id', $siteId)
+                ->value('domain');
+            //远程目录
+            $targetDirectory = '/www/wwwroot/' . $domain . '/sepSsr/uploads';
+
+            //递归创建目录
+            $sftp = ssh2_sftp($ssh2);
+            ssh2_sftp_mkdir($sftp, $targetDirectory, 0755, true);
+
+            foreach ($result as $item) {
+
+                $fileName = explode('/', $item);
+                $file = array_pop($fileName);
+
+                //本地目录
+                $sourceDirectory = base_path() . '/storage/app/public' . $item;
+
+                $stream = ssh2_scp_send($ssh2, $sourceDirectory, $targetDirectory . '/' . $file, 0644);
+                pre_dump($stream);
+            }
+
+        } catch (\Exception $exception) {
+            echo $exception->getMessage();
+        }
+    }
+
+
+    /*public function automatedDeployment()
+    {
+        $db_config = [
+            'connection_name' => 'new_db',
+            'host' => $site->server->server_ip,
+            'port' => '3306',
+            'database' => 'sdb_sucocms',
+            'username' => $site->server->mysql_user_name,
+            'password' => $site->server->mysql_passwd
+        ];
+        $new_db = config_connection($db_config);
+        DB::connection($new_db)->statement('create database ' . $dbName);
+        Ssh::exec("mysqldump sdb_sucocms -u root -p" . $site->server->mysql_passwd . " --add-drop-table | mysql " . $dbName . " -u root -p" . $site->server->mysql_passwd);
+
+    }*/
+
+}

+ 443 - 0
app/Http/Controllers/Admin/Stencil/TemplateLibraryController.php

@@ -0,0 +1,443 @@
+<?php
+/**
+ * 模版库管理(弃用)
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-08-01
+ */
+
+namespace App\Http\Controllers\Admin\Stencil;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use App\Http\Models\TemplateLibrary;
+use App\Http\Models\TemplateLibraryRelation;
+use App\Http\Models\TemplateLibraryVar;
+use App\Http\Services\TemplateLibraryApiService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\View\View;
+use Illuminate\Http\JsonResponse;
+
+class TemplateLibraryController extends Controller
+{
+    private $templateLibraryApiService;
+    private $siteId;
+
+    /**
+     * 模版站服务类
+     * TplController constructor.
+     * @param TemplateLibraryApiService $templateLibraryApiService
+     */
+    public function __construct(TemplateLibraryApiService $templateLibraryApiService)
+    {
+        $this->templateLibraryApiService = $templateLibraryApiService;
+        $this->siteId = 360;
+    }
+
+    /**
+     * 模版库管理
+     * @return Factory|View
+     */
+    public function templateLibrary()
+    {
+        $variable = [];
+        $trees = TemplateLibrary::query()->with(['templateLibraryRelation'])
+            ->orderBy('sort', 'asc')
+            ->whereNull('deleted_at')->get()->toArray();
+
+        foreach ($trees as $key => $tree) {
+            $trees[$key]['name'] = "{$tree['name']}#{$tree['id']}";
+            $trees[$key]['title'] = $tree['name'];
+            $trees[$key]['images'] = json_decode($tree['images'], true);
+        }
+        $trees = list_to_tree($trees, 'id', 'pid', 'children');
+
+        return view('admin.stencil.template_library', [
+            'trees' => $trees,
+            'variable' => $variable,
+            'siteList' => Site::query()->where('is_stencil', 1)->get(),
+            'templateLibraryVar' => TemplateLibraryVar::query()->whereNull('deleted_at')->get(),
+        ]);
+    }
+
+    /**
+     * 添加模版库列表节点
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function templateLibraryAddNode(Request $request)
+    {
+        $result = $request->all();
+        $update = [
+            'name' => $result['node'] ?? '',
+            'pid' => $result['pid'] ?? 0,
+        ];
+        TemplateLibrary::query()->insert($update);
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 修改模版库列表节点
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function templateLibraryUpdateNode(Request $request)
+    {
+        $result = $request->all();
+        $update = [
+            'name' => $result['node'] ?? '',
+            'pid' => $result['pid'] ?? 0,
+        ];
+        TemplateLibrary::query()->where('id', $result['id'])->update($update);
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 删除模版库列表节点
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function templateLibraryDelNode(Request $request)
+    {
+        $result = $request->all();
+        $update = [
+            'deleted_at' => date('Y-m-d H:i:s'),
+        ];
+        TemplateLibrary::query()->where('id', $result['id'])->update($update);
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 复制模版库列表节点
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function templateLibraryCopyNode(Request $request)
+    {
+        $result = $request->all();
+        $array = TemplateLibrary::query()
+            ->whereNull('deleted_at')->get()->toArray();
+
+        $parentList = TemplateLibrary::query()->where('id', $result['id'])
+            ->whereNull('deleted_at')->first()->toArray();
+
+        $childList = $this->getTemplateLibraryList($array, $result['id']);
+        $childList[] = $parentList;
+        $list = $this->arraySort($childList, 'id');
+        $list = array_merge($list);
+        $ids = [];
+        foreach ($list as $key => $item) {
+            if ($item['pid'] == 0 && $key == 0) {
+                $update = [
+                    'name' => $item['name'],
+                    'alias' => $item['alias'],
+                    'memo' => $item['memo'],
+                    'sort' => $item['sort'],
+                    'pid' => 0,
+                    'css' => $item['css'],
+                    'images' => $item['images'],
+                    'created_at' => date('Y-m-d H:i:s'),
+                ];
+            } elseif ($item['pid'] != 0 && $key == 0) {
+                $ids[$item['pid']] = $item['pid'];
+                $update = [
+                    'name' => $item['name'],
+                    'alias' => $item['alias'],
+                    'memo' => $item['memo'],
+                    'sort' => $item['sort'],
+                    'pid' => $ids[$item['pid']] ?? 0,
+                    'css' => $item['css'],
+                    'images' => $item['images'],
+                    'created_at' => date('Y-m-d H:i:s'),
+                ];
+            } else {
+                $update = [
+                    'name' => $item['name'],
+                    'alias' => $item['alias'],
+                    'memo' => $item['memo'],
+                    'sort' => $item['sort'],
+                    'pid' => $ids[$item['pid']],
+                    'css' => $item['css'],
+                    'images' => $item['images'],
+                    'created_at' => date('Y-m-d H:i:s'),
+                ];
+            }
+
+            $id = TemplateLibrary::query()->insertGetId($update);
+            $ids[$item['id']] = $id;
+        }
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 递归父级目录
+     * @param $array
+     * @param int $pid
+     * @return array
+     */
+    public function getTemplateLibraryList($array, $pid = 0)
+    {
+        static $data = [];
+        foreach ($array as $key => $item) {
+            if ($item['pid'] == $pid) {
+                $data[] = [
+                    'id' => $item['id'],
+                    'name' => "{$item['name']}",
+                    'alias' => $item['alias'] ?? '',
+                    'memo' => $item['memo'] ?? '',
+                    'sort' => $item['sort'] ?? 0,
+                    'pid' => $item['pid'] ?? 0,
+                    'css' => $item['css'] ?? '',
+                    'images' => $item['images'] ?? json_encode([]),
+                ];
+                unset($array[$key]);
+                $this->getTemplateLibraryList($array, $item['id']);
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * 添加顶级菜单
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function addParentMenu(Request $request)
+    {
+        $result = $request->all();
+        $update = [
+            'name' => $result['name'],
+            'pid' => 0,
+        ];
+        TemplateLibrary::query()->insert($update);
+        return response()->json(['message' => '添加成功']);
+    }
+
+    /**
+     * 保存模版信息
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function saveTemplateLibrary(Request $request)
+    {
+        $result = $request->all();
+        $update = [
+            'name' => $result['name'] ?? '',
+            'alias' => $result['alias'] ?? '',
+            'memo' => $result['memo'] ?? '',
+            'sort' => $result['sort'] ?? 0,
+            'pid' => $result['pid'] ?? '',
+            'css' => $result['css'] ?? '',
+            'images' => json_encode($result['images'] ?? []),
+        ];
+
+        try {
+            //事务
+            DB::transaction(function () use ($result, $update) {
+                TemplateLibrary::query()->where('id', $result['template_id'])->update($update);
+
+                if (!empty($result['variableIds'])) {
+                    $variableList = [];
+                    foreach ($result['variableIds'] as $item) {
+                        $variableList[] = [
+                            'template_library_id' => $result['template_id'],
+                            'variable_id' => $item,
+                        ];
+                    }
+                    TemplateLibraryRelation::query()->where('template_library_id', $result['template_id'])->delete();
+                    TemplateLibraryRelation::query()->insert($variableList);
+                }
+            });
+
+        } catch (\Throwable $exception) {
+            return response()->json(['message' => $exception->getMessage()], 400);
+        }
+
+        if (!empty($result['siteId'] && !empty($result['nodeIds']))) {
+
+            $ids = [];
+            $level = [];
+            foreach ($result['nodeIds'] as $key => $id) {
+                $res = explode(':', $id);
+                $ids[] = $res[0];
+                $level[$res[0]] = $res[1];
+            }
+
+            $templateLibraryList = TemplateLibrary::query()->whereIn('id', $ids)->get()->toArray();
+            if (!empty($templateLibraryList)) {
+
+                $templateLibraryResult = [];
+                foreach ($templateLibraryList as $item) {
+                    $templateLibraryResult[] = [
+                        'id' => $item['id'] ?? 0,
+                        'name' => $item['name'] ?? '',
+                        'alias' => $item['alias'] ?? '',
+                        'description' => $item['memo'] ?? '',
+                        'rank' => $item['rank'] ?? 0,
+                        'html' => '',
+                        'allow_write_file' => 0,
+                        'allow_clear_cache' => 0,
+                        'create_time' => time(),
+                        'parent_id' => $item['pid'],
+                        'level' => $level[$item['id']],
+                        'images' => $item['images'] ?? json_encode([]),
+                    ];
+                }
+                //清空表
+                DB::connection($this->templateLibraryApiService->connection($this->siteId))
+                    ->table('content_template')->truncate();
+                //插入到项目数据库形成新的父子关系
+                $this->loop($templateLibraryResult);
+
+                $variableList = TemplateLibraryVar::query()->get()->toArray();
+
+                if (!empty($variableList)) {
+                    $variableListResult = [];
+                    foreach ($variableList as $item) {
+                        $variableListResult[] = [
+                            'name' => $item['variable'] ?? '',
+                            'caption' => $item['tag'] ?? '',
+                            'description' => $item['memo'] ?? '',
+                            'input_type' => $item['input_type'] ?? '',
+                            'input_value' => $item['input_value'] ?? '',
+                            'input_opts' => $item['input_opts'] ?? '',
+                            'input_length' => $item['input_length'] ?? 0,
+                            'allow_blank' => $item['allow_blank'] ?? 0,
+                            'regex_match' => $item['regex_match'] ?? '',
+                            'regex_error' => $item['regex_error'] ?? '',
+                            'rank' => $item['rank'] ?? 0,
+                            'create_time' => $item['create_time'] ?? time(),
+                            'width' => $item['width'] ?? 0,
+                            'height' => $item['height'] ?? 0,
+                            'size' => $item['size'] ?? 0,
+                        ];
+                    }
+
+                    DB::connection($this->templateLibraryApiService->connection($request['siteId']))->table('content_template_var')->truncate();
+                    DB::connection($this->templateLibraryApiService->connection($request['siteId']))
+                        ->table('content_template_var')->insert($variableListResult);
+                }
+            }
+        }
+
+        return response()->json(['message' => '保存成功']);
+    }
+
+    /**
+     * 插入数据库形成新的父子关系
+     * @param $array
+     */
+    public function loop($array)
+    {
+        $ids = [];
+        foreach ($array as $key => $item) {
+            if ($item['parent_id'] == 0 && $key == 0) {
+                $update = [
+                    'id' => $item['id'],
+                    'name' => $item['name'],
+                    'alias' => $item['alias'] ?? '',
+                    'description' => $item['description'] ?? '',
+                    'rank' => $item['rank'] ?? 0,
+                    'html' => $item['html'] ?? 0,
+                    'allow_write_file' => $item['allow_write_file'] ?? '',
+                    'allow_clear_cache' => $item['allow_clear_cache'] ?? '',
+                    'create_time' => $item['create_time'],
+                    'parent_id' => 0,
+                    'level' => $item['level'],
+                    'images' => $item['images'],
+                ];
+            } elseif ($item['parent_id'] != 0 && $key == 0) {
+                $ids[$item['parent_id']] = $item['parent_id'];
+                $update = [
+                    'id' => $item['id'],
+                    'name' => $item['name'],
+                    'alias' => $item['alias'] ?? '',
+                    'description' => $item['description'] ?? '',
+                    'rank' => $item['rank'] ?? 0,
+                    'html' => $item['html'] ?? 0,
+                    'allow_write_file' => $item['allow_write_file'] ?? '',
+                    'allow_clear_cache' => $item['allow_clear_cache'] ?? '',
+                    'create_time' => $item['create_time'],
+                    'parent_id' => $ids[$item['parent_id']] ?? 0,
+                    'level' => $item['level'],
+                    'images' => $item['images'],
+                ];
+            } else {
+                $update = [
+                    'id' => $item['id'],
+                    'name' => $item['name'],
+                    'alias' => $item['alias'] ?? '',
+                    'description' => $item['description'] ?? '',
+                    'rank' => $item['rank'] ?? 0,
+                    'html' => $item['html'] ?? 0,
+                    'allow_write_file' => $item['allow_write_file'] ?? '',
+                    'allow_clear_cache' => $item['allow_clear_cache'] ?? '',
+                    'create_time' => $item['create_time'],
+                    'parent_id' => $ids[$item['parent_id']],
+                    'level' => $item['level'],
+                    'images' => $item['images'],
+                ];
+            }
+            $id = DB::connection($this->templateLibraryApiService->connection($this->siteId))
+                ->table('content_template')->insertGetId($update);
+            $ids[$item['id']] = $id;
+        }
+    }
+
+    /**
+     * 添加模版变量
+     * @param Request $request
+     * @param $id
+     * @return Factory|JsonResponse|View
+     */
+    public function addVariable(Request $request, $id)
+    {
+        if (!$request->ajax()) {
+            $info = TemplateLibraryVar::query()->where('id', $id)->first();
+            return view('admin.stencil.add_variable', [
+                'id' => $id,
+                'info' => $info,
+            ]);
+        }
+        $result = $request->all();
+        $update = [
+            'variable' => $result['variable'] ?? '',
+            'tag' => $result['tag'] ?? '',
+            'sort' => $result['sort'] ?? 0,
+            'memo' => $result['memo'] ?? '',
+            'input_type' => $result['input_type'] ?? '',
+            'input_opts' => $result['input_opts'] ?? '',
+            'input_length' => $result['input_length'] ?? '',
+            'width' => $result['width'] ?? '',
+            'height' => $result['height'] ?? '',
+            'size' => $result['size'] ?? '',
+            'input_value' => $result['input_value'] ?? '',
+            'regex_match' => $result['regex_match'] ?? '',
+            'regex_error' => $result['regex_error'] ?? '',
+        ];
+        if (!empty($id)) {
+            TemplateLibraryVar::query()->where('id', $id)->update($update);
+        } else {
+            TemplateLibraryVar::query()->insert($update);
+        }
+        return response()->json(['message' => '保存成功']);
+    }
+
+    /**
+     * 删除模版变量
+     * @param $id
+     * @return JsonResponse
+     */
+    public function delDelVariable($id)
+    {
+        $update = [
+            'deleted_at' => date('Y-m-d H:i:s')
+        ];
+        TemplateLibraryVar::query()->where('id', $id)->update($update);
+        return response()->json(['message' => '保存成功']);
+    }
+
+
+}

+ 401 - 0
app/Http/Controllers/Admin/Stencil/TplController.php

@@ -0,0 +1,401 @@
+<?php
+/**
+ * 页面模版管理
+ * @copyright 2021-浙江引擎力营销策划有限公司
+ * @author Lc<sunshinecc1@163.com>
+ * @since 2021-08-01
+ */
+
+namespace App\Http\Controllers\Admin\Stencil;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use App\Http\Services\TemplateLibraryApiService;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\View\View;
+use Illuminate\Support\Collection;
+use Illuminate\Http\JsonResponse;
+
+class TplController extends Controller
+{
+    const TEMPLATE_PID = -1;
+    const VARIABLE_PID = -2;
+
+    private $templateLibraryApiService;
+
+    /**
+     * 模版站服务类
+     * TplController constructor.
+     * @param TemplateLibraryApiService $templateLibraryApiService
+     */
+    public function __construct(TemplateLibraryApiService $templateLibraryApiService)
+    {
+        $this->templateLibraryApiService = $templateLibraryApiService;
+    }
+
+    /**
+     * 页面模版管理
+     * @param $siteId
+     * @return Factory|View
+     */
+    public function index($siteId)
+    {
+        try {
+            $site = Site::query()->where('id', $siteId)->first();
+            $templateList = $this->templateLibraryApiService->arraySort($this->getContentTemplateList($siteId), 'name');
+            $templateVarList = $this->templateLibraryApiService->arraySort($this->getContentTemplateVar($siteId), 'name');
+            $trees = list_to_tree(array_merge($templateList, $templateVarList), 'id', 'parent_id', 'children');
+
+            return view('admin.stencil.tpl', [
+                'trees' => $trees,
+                'site' => $site,
+                'siteId' => $siteId,
+                'templateList' => $templateList,
+                'variable' => $this->arraySort(json_decode(json_encode($this->getTemplateList($siteId, true)), true),'name'),
+                'template' => $this->arraySort(json_decode(json_encode($this->getVariableList($siteId)), true),'name'),
+            ]);
+        } catch (\Throwable $exception) {
+            echo $exception->getMessage();
+        }
+    }
+
+    /**
+     * 获取内容模版列表
+     * @param $siteId
+     * @return array
+     */
+    public function getContentTemplateList($siteId)
+    {
+        $templateList = $this->getTemplateList($siteId);
+        $list1 = [];
+
+        if (!empty($templateList)) {
+            foreach ($templateList as $key => $value) {
+                $list1 [] = [
+                    'id' => $value->id,
+                    'parent_id' => $value->parent_id,
+                    'name' => "{$value->name}(#{$value->id})",
+                    'title' => $value->name ?? '',
+                    'alias' => $value->alias ?? '',
+                    'rank' => $value->rank ?? 0,
+                    'description' => $value->description ?? '',
+                    'allow_write_file' => $value->allow_write_file ?? '',
+                    'allow_clear_cache' => $value->allow_clear_cache ?? '',
+                    'url_level' => $value->url_level ?? '',
+                    'images' => json_decode($value->images, true) ?? [],
+                    'level' => $value->level ?? 0,
+                    'type' => 1,//模版,用于区分模版和变量
+                ];
+            }
+            $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+            $relatedIdList = $connection->table('content_template_relate')->get()->toArray();
+            foreach ($list1 as $key => $value) {
+                $list1[$key]['varIdList'] = [];
+                foreach ($relatedIdList as $kk => $vv) {
+                    if ($value['id'] == $vv->tid) {
+                        $list1[$key]['varIdList'][] = $vv->vid;
+                    }
+                }
+            }
+        }
+        return $list1;
+    }
+
+    /**
+     * 获取模版列表
+     * @param $siteId
+     * @param bool $type 是否只获取一级列表
+     * @return Collection
+     */
+    private function getTemplateList($siteId, $type = false)
+    {
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        $template = $connection->table('content_template');
+        if ($type) {
+            $template->where('level', 2);
+        }
+        $templateList = $template
+            ->where('status', '<', 2)
+            ->orderBy('rank', 'asc')
+            ->orderBy('id', 'asc')
+            ->get();
+        return $templateList;
+    }
+
+    /**
+     * 获取变量列表
+     * @param $siteId
+     * @return Collection
+     */
+    private function getVariableList($siteId)
+    {
+        $variableList = DB::connection($this->templateLibraryApiService->connection($siteId))
+            ->table('content_template_var')
+            ->where('status', '<', 2)
+            ->orderBy('rank', 'asc')
+            ->orderBy('id', 'asc')
+            ->get();
+        return $variableList;
+    }
+
+    /**
+     * 获取模版变量
+     * @param $siteId
+     * @return array
+     */
+    public function getContentTemplateVar($siteId)
+    {
+        $variableList = $this->getVariableList($siteId);
+        $list2 = [];
+        if (!empty($variableList)) {
+            $list2[] = [
+                'id' => self::VARIABLE_PID,
+                'parent_id' => 0,
+                'name' => 'Variable',
+                'title' => '',
+                'alias' => '',
+                'rank' => 0,
+                'description' => '',
+                'allow_write_file' => '',
+                'allow_clear_cache' => '',
+                'url_level' => '',
+                'html' => '',
+                'input_length' => 0,
+                'width' => 0,
+                'height' => 0,
+                'size' => 0,
+                'input_value' => '',
+                'regex_match' => '',
+                'regex_error' => '',
+                'type' => 2,//模版,用于区分模版和变量
+            ];
+            foreach ($variableList as $key => $value) {
+                $list2 [] = [
+                    'id' => $value->id,
+                    'parent_id' => $list2[0]['id'],
+                    'name' => "{$value->name}(#{$value->id})",
+                    'title' => $value->name ?? '',
+                    'alias' => $value->caption,
+                    'rank' => $value->rank,
+                    'description' => $value->description,
+                    'allow_write_file' => $value->allow_write_file ?? '',
+                    'allow_clear_cache' => $value->allow_clear_cache ?? '',
+                    'url_level' => $value->url_level ?? '',
+                    'html' => '',
+                    'input_opts' => $value->input_opts ?? '',
+                    'input_length' => $value->input_length ?? 0,
+                    'width' => $value->width ?? 0,
+                    'height' => $value->height ?? 0,
+                    'size' => $value->size ?? 0,
+                    'input_type' => $value->input_type,
+                    'input_value' => $value->input_value,
+                    'regex_match' => $value->regex_match,
+                    'regex_error' => $value->regex_error,
+                    'type' => 2,
+                ];
+            }
+            $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+            $relatedIdList = $connection->table('content_template_relate')->get()->toArray();
+            foreach ($list2 as $key => $value) {
+                $list2[$key]['templateIdList'] = [];
+                foreach ($relatedIdList as $kk => $vv) {
+                    if ($value['id'] == $vv->vid) {
+                        $list2[$key]['templateIdList'][] = $vv->tid;
+                    }
+                }
+            }
+        }
+
+        return $list2;
+    }
+
+
+    /**
+     * 保存模版
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function templateSave(Request $request, $siteId)
+    {
+        $request = $request->all();
+        $update = [
+            'name' => $request['title'] ?? '',
+            'description' => $request['description'] ?? '',
+            'url_rule' => $request['sub_type'] ?? 2,
+            'alias' => $request['alias'] ?? '',
+            'rank' => $request['rank'] ?? 0,
+            'url_level' => $request['url_level'] ?? 0,
+            'images' => json_encode($request['images'] ?? []),
+        ];
+
+        if (empty($request['template_id'])) {
+            return response()->json(['message' => '请选择子菜单操作'], 400);
+        }
+
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!empty($request['checkboxList'])) {
+            $connection->table('content_template_relate')->where('tid', $request['template_id'])->delete();
+            $list = $request['checkboxList'];
+            $data = [];
+            foreach ($list as $value) {
+                $result = [
+                    'vid' => $value,
+                    'tid' => $request['template_id'],
+                ];
+                $data[] = $result;
+            }
+            $connection->table('content_template_relate')->insert($data);
+        } else {
+            $connection->table('content_template_relate')->where('tid', $request['template_id'])->delete();
+        }
+
+        $connection->table('content_template')->where('id', $request['template_id'])->update($update);
+        return response()->json(['message' => '操作成功']);
+
+    }
+
+    /**
+     * 保存变量
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function variableSave(Request $request, $siteId)
+    {
+        $request = $request->all();
+        $update = [
+            'name' => $request['title2'] ?? '',
+            'description' => $request['description'] ?? '',
+            'caption' => $request['alias'] ?? '',
+            'rank' => $request['rank'] ?? 0,
+            'input_type' => $request['input_type'] ?? '',
+            'input_opts' => $request['input_opts'] ?? '',
+            'input_length' => $request['input_length'] ?? '',
+            'width' => $request['width'] ?? 0,
+            'height' => $request['height'] ?? 0,
+            'size' => $request['size'] ?? 0,
+            'input_value' => $request['input_value'] ?? '',
+            'regex_match' => $request['regex_match'] ?? '',
+            'regex_error' => $request['regex_error'] ?? '',
+        ];
+        if (empty($request['variable_id'])) {
+            return response()->json(['message' => '请选择子菜单操作'], 400);
+        }
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+        if (!empty($request['checkboxList'])) {
+            $connection->table('content_template_relate')->where('vid', $request['variable_id'])->delete();
+            $list = $request['checkboxList'];
+            $data = [];
+            foreach ($list as $value) {
+                $result = [
+                    'vid' => $request['variable_id'],
+                    'tid' => $value,
+                ];
+                $data[] = $result;
+            }
+            $connection->table('content_template_relate')->insert($data);
+        } else {
+            $connection->table('content_template_relate')->where('vid', $request['variable_id'])->delete();
+        }
+        $connection->table('content_template_var')->where('id', $request['variable_id'])->update($update);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 添加变量
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function templateAddNode(Request $request, $siteId)
+    {
+        $result = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        if ($result['pid'] == self::TEMPLATE_PID && $result['type'] == 1) {
+            $data = [
+                'name' => $result['node'],
+                'parent_id' => self::TEMPLATE_PID,
+                'alias' => $result['node'],
+                'level' => ++$result['level'] ?? 0,
+                'description' => '',
+                'html' => '',
+                'rank' => 0,
+                'allow_write_file' => 0,
+                'allow_clear_cache' => 0,
+                'create_time' => time()
+            ];
+            $connection->table('content_template')->insert($data);
+
+        } elseif ($result['pid'] != self::TEMPLATE_PID && $result['type'] == 1) {
+            $data = [
+                'name' => $result['node'],
+                'parent_id' => $result['pid'],
+                'alias' => $result['node'],
+                'level' => ++$result['level'] ?? 0,
+                'description' => '',
+                'html' => '',
+                'rank' => 0,
+                'allow_write_file' => 0,
+                'allow_clear_cache' => 0,
+                'create_time' => time()
+            ];
+            $connection->table('content_template')->insert($data);
+
+        } elseif ($result['pid'] == self::VARIABLE_PID && $result['type'] == 2) {
+            $data = [
+                'name' => $result['node'],
+                'caption' => $result['node'],
+                'description' => '',
+                'input_type' => 'text',
+                'input_value' => '',
+                'input_opts' => '',
+                'input_length' => 0,
+                'allow_blank' => 0,
+                'regex_match' => '',
+                'regex_error' => '',
+                'rank' => 0,
+                'status' => 0,
+                'width' => 0,
+                'height' => 0,
+                'size' => 0,
+                'create_time' => time(),
+            ];
+            $connection->table('content_template_var')->insert($data);
+        } else {
+            return response()->json(['message' => '不允许添加子级变量'], 400);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 删除变量
+     * @param Request $request
+     * @param $siteId
+     * @return JsonResponse
+     */
+    public function templateDelNode(Request $request, $siteId)
+    {
+        $request = $request->all();
+        $connection = DB::connection($this->templateLibraryApiService->connection($siteId));
+
+        if ($request['pid'] == self::VARIABLE_PID) {
+            $result = $connection->table('content_template_var')->where('id', $request['id'])->delete();
+        } else {
+            $result = $connection->table('content_template')->where('id', $request['id'])->delete();
+        }
+
+        if (empty($result)) {
+            return response()->json(['message' => '服务器发生错误', '400']);
+        } else {
+            return response()->json(['message' => '操作成功']);
+        }
+    }
+
+}

+ 88 - 0
app/Http/Controllers/Admin/System/PermissionController.php

@@ -0,0 +1,88 @@
+<?php
+
+namespace App\Http\Controllers\Admin\System;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Permission;
+use App\Http\Requests\System\PermissionSaveRequest;
+use Illuminate\Http\Request;
+
+class PermissionController extends Controller
+{
+
+    protected $logic;
+
+    public function __construct()
+    {
+
+    }
+
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            $permissions = Permission::query()->where(['type' => 1])->select(['title as name', 'id', 'parent_id'])
+                ->orderByDesc('sort')->get()->toArray();
+            $trees = list_to_tree($permissions, 'id', 'parent_id', 'children');
+            return view('admin/system/permission', ['trees' => $trees]);
+        }
+        $filter = [['type', '=', 2]];
+        if ($request->input('parentId')) {
+            $filter[] = ['parent_id', '=', $request->input('parentId')];
+        }
+
+        $records = Permission::query()->where($filter)->orderByDesc('sort')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        return response()->json([
+            'rows' => $records->items(),
+            'total' => $records->total()
+        ]);
+    }
+
+    public function save(PermissionSaveRequest $request, $id)
+    {
+        if (!$request->ajax()) {
+            $record = Permission::query()->find($id);
+            return view('admin/system/permission_save', [
+                'data' => $record,
+                'type' => $request->input('type')
+            ]);
+        }
+        $record = Permission::query()->updateOrCreate(['id' => $id], $request->validated());
+        return response()->json(['message' => '操作成功', 'data' => $record]);
+    }
+
+    public function store(PermissionSaveRequest $request)
+    {
+        $validated = $request->validated();
+        Permission::query()->create($validated);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function update(PermissionSaveRequest $request, $id)
+    {
+        $permission = Permission::query()->find($id);
+        if (!$permission) return response()->json(['message' => '数据不存在']);
+        $permission->update($request->validated());
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    public function destroy($id)
+    {
+        $record = Permission::query()->where(['id' => $id])->first();
+        if ($record) {
+            if (Permission::query()->where(['parent_id' => $record->id])->exists()) {
+                return response()->json(['message' => '请先删除子菜单'], 400);
+            }
+            $record->delete();
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function BatchDestroy(Request $request)
+    {
+        $ids = $request->input('ids');
+        Permission::query()->whereIn('id', $ids)->where(['type' => 2])->delete();
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 92 - 0
app/Http/Controllers/Admin/System/RoleController.php

@@ -0,0 +1,92 @@
+<?php
+
+namespace App\Http\Controllers\Admin\System;
+
+use App\Http\Logics\Admin\RoleLogic;
+use App\Http\Models\Permission;
+use App\Http\Models\Role;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class RoleController extends Controller
+{
+    protected $logic;
+
+    public function __construct(RoleLogic $logic)
+    {
+        $this->logic = $logic;
+    }
+
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/system/role/index');
+        }
+        $roles = Role::query()->orderByDesc('id')->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        return response()->json([
+            'rows' => $roles->items(),
+            'total' => $roles->total()
+        ]);
+    }
+
+    public function save(Request $request, $id)
+    {
+        if (!$request->ajax()) {
+            $record = Role::query()->find($id);
+            return view('admin/system/role/save', [
+                'data' => $record
+            ]);
+        }
+        Role::query()->updateOrCreate([
+            'id' => $id
+        ], [
+            'name' => $request->input('name')
+        ]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    public function batchDestroy(Request $request)
+    {
+        $ids = $request->input('ids');
+        Role::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function allotPermissions(Request $request)
+    {
+        $roleIds = $request->input('roleIds');
+
+        if (!$request->ajax()) {
+
+            $selects = [];
+            if (count($roleIds) == 1) {
+                $role = Role::query()->where(['id' => $roleIds[0]])->first();
+                if (!$role) return response()->json(['message' => '参数错误'], 400);
+                $selects = $role->permissions->pluck('id')->toArray();
+            }
+
+            $permissions = $this->logic->getRoleCanPermissions($role ?? null);
+
+            array_walk($permissions, function (&$item) use ($selects) {
+                $item['open'] = true;
+                $item['name'] = $item['name'] . ($item['type'] == 1 ? '【菜单】' : '【功能】');
+                if (in_array($item['id'], $selects)) {
+                    $item['checked'] = true;
+                }
+            });
+            $trees = list_to_tree($permissions, 'id', 'parent_id', 'children');
+            return view('admin/system/role/permission', [
+                'trees' => $trees,
+                'roleIds' => $roleIds
+            ]);
+        }
+
+        $permissionIds = $request->input('permissionIds');
+        $roles = Role::query()->whereIn('id', $roleIds)->get();
+        foreach ($roles as $role) {
+            $role->permissions()->sync($permissionIds);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 190 - 0
app/Http/Controllers/Admin/System/UserController.php

@@ -0,0 +1,190 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: Administrator
+ * Date: 2019/4/9 0009
+ * Time: 14:05
+ */
+
+namespace App\Http\Controllers\Admin\System;
+
+use App\Http\Logics\Admin\RoleLogic;
+use App\Http\Models\Permission;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use App\Http\Requests\System\StoreUserRequest;
+use App\Http\Requests\System\UpdateUserRequest;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class UserController extends Controller
+{
+    public function index(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/system/user', [
+                'roles' => Role::all()
+            ]);
+        }
+
+        $keyword = $request->input('keyword');
+
+
+        if ($roleId = $request->input('role_id')) {
+            $filter[] = ['role_id', '=', $roleId];
+        }
+        $select = ['id', 'username', 'email', 'created_at', 'role_id', 'status', 'is_super', 'nickname', 'profile_img'];
+        $roles = User::query()->with('role')->select($select)
+            ->where($filter ?? [])->where(function (Builder $builder) use ($keyword) {
+                if ($keyword) {
+                    $builder->where('username', 'like', '%' . $keyword . '%')
+                        ->orWhere('nickname', 'like', '%' . $keyword . '%');
+                }
+
+            })->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $items = $roles->items();
+
+        array_walk($items, function ($item) {
+            $item->role_name = $item->is_super ? ($item->role->name ?? '' . '【超级管理员】') : $item->role->name ?? '';
+            $item->status_title = $item->status_with_css;
+            unset($item->role);
+        });
+        return response()->json([
+            'rows' => $items,
+            'total' => $roles->total()
+        ]);
+    }
+
+    public function userPermission(Request $request)
+    {
+        $userIds = $request->input('userIds');
+        if (!$request->ajax()) {
+
+            $selects = [];
+            if (count($userIds) == 1) {
+                $user = User::query()->where(['id' => $userIds[0]])->first();
+                if (!$user) return response()->json(['message' => '参数错误'], 400);
+                $selects = $user->permissions->pluck('id')->toArray();
+            }
+            $permissions = Permission::query()->whereIn('id', RoleLogic::Customer_Permissions)->select(['title as name', 'id', 'parent_id', 'type'])->get()->toArray();
+            array_walk($permissions, function (&$item) use ($selects) {
+                $item['open'] = true;
+                $item['name'] = $item['name'] . ($item['type'] == 1 ? '【菜单】' : '【功能】');
+                if (in_array($item['id'], $selects)) {
+                    $item['checked'] = true;
+                }
+            });
+            $trees = list_to_tree($permissions, 'id', 'parent_id', 'children');
+            return view('admin/system/user_permission', [
+                'trees' => $trees,
+                'userIds' => $userIds
+            ]);
+        }
+        $permissionIds = $request->input('permissionIds');
+        $records = User::query()->whereIn('id', $userIds)->get();
+        foreach ($records as $record) {
+            $record->permissions()->sync($permissionIds);
+        }
+        return response()->json(['message' => '操作成功']);
+
+
+    }
+
+    public function store(StoreUserRequest $request)
+    {
+        $validated = $request->validated();
+
+        $validated['password'] = bcrypt($validated['password']);
+        $validated['profile_img'] = asset('img/social_round_github_64px_1196568_easyicon.net.png');
+        $siteIds = $validated['site_ids'] ?? null;
+        unset($validated['site_ids']);
+        $user = User::query()->create($validated);
+        /**@var  \App\Http\Models\User $user * */
+        if ($siteIds) {
+            $user->sites()->sync($siteIds);
+        }
+
+        if ($validated['role_id'] == Role::TYPE_CUSTOMER) {
+            $user->permissions()->sync(RoleLogic::Customer_Permissions);
+        }
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function update(UpdateUserRequest $request, $id)
+    {
+
+        $user = User::query()->with('sites')->where(['id' => $id])->first();
+        /**@var  \App\Http\Models\User $user * */
+        if (!$user) return response()->json(['message' => '数据不存在']);
+        $validated = $request->validated();
+
+        $siteIds = $validated['site_ids'] ?? [];
+        unset($validated['site_ids']);
+
+        if (!empty($validated['password'])) {
+            $validated['password'] = bcrypt($validated['password']);
+        } else {
+            unset($validated['password']);
+        }
+
+        $user->update($validated);
+        $user->sites()->sync($siteIds);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function show($id)
+    {
+        if ($id > 0) {
+            $user = User::query()->with('sites')->where(['id' => $id])->first();
+            if (!empty($user->entry_time)) {
+                $user->entry_time = substr($user->entry_time, 0, 10);
+            }
+            $hasSiteIds = $user->sites->pluck('id')->toArray(); //用户所关联的站点
+        }
+        return view('admin/system/user_show', [
+            'user' => $user ?? null,
+            'roles' => Role::query()->select(['id', 'name'])->get(),
+            'sites' => Site::all(),
+            'hasSiteIds' => $hasSiteIds ?? []
+        ]);
+    }
+
+    public function detail($id)
+    {
+        $user = User::query()->select()->find($id);
+        return view('/admin/system/user_detail', [
+            'user' => $user
+        ]);
+    }
+
+    public function destroy($id)
+    {
+        User::destroy($id);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function batchDestroy(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::destroy($ids);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function on(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::query()->whereIn('id', $ids)->update(['status' => 1]);
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function off(Request $request)
+    {
+        $ids = $request->input('ids');
+        User::query()->whereIn('id', $ids)->update(['status' => 0]);
+        return response()->json(['message' => '操作成功']);
+    }
+}

+ 187 - 0
app/Http/Controllers/Admin/ToolController.php

@@ -0,0 +1,187 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Constant\CacheConstant;
+use App\Http\Models\LinkTask;
+use App\Http\Models\LinkTaskDetail;
+use App\Http\Models\Site;
+use App\Http\Models\SiteProcess;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Str;
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Pool;
+
+
+class ToolController extends Controller
+{
+    public function upload(Request $request)
+    {
+        $file = $request->file('file');
+        if (!$file) {
+            return response()->json(['message' => '没有上传文件'], 422);
+        }
+
+        $name = strtolower(str_replace(' ', '-', $file->getClientOriginalName()));
+        $array = explode('.', $name);
+        if (!count($array) || count($array) > 2) {
+            return response()->json(['message' => '命名格式不对,不允许文件名中出现 "."'], 422);
+        }
+        $fileName = $array[0] . '_' . date('YmdHis');
+        $name = $fileName . '.' . $array[1];
+
+        $fileData['file_url'] = $file->storeAs(date('Ym'), $name, 'public');
+        $fileData['file_url'] = $request->input('notAsset') ? sprintf('/storage/%s', $fileData['file_url']) : asset('storage/' . $fileData['file_url']);
+
+        $fileData['original_name'] = $file->getClientOriginalName();
+        $fileData['time'] = date('Y-m-d H:i:s');
+        return response()->json(['message' => '上传成功', 'data' => $fileData]);
+    }
+
+
+    public function excelUpload(Request $request)
+    {
+        $file = $request->file('file');
+        if (!$file) return response()->json(['message' => '没有上传文件'], 422);
+        $name = sprintf('%s.%s', Str::random(40), $file->getClientOriginalExtension());
+        $fileData['file_url'] = $file->storeAs(date('Ym') . '/' . $file->getClientOriginalExtension(), $name, 'public');
+        $fileData['original_name'] = $file->getClientOriginalName();
+        $fileData['time'] = time();
+        return response()->json(['message' => '上传成功', 'data' => $fileData]);
+    }
+
+    public function download(Request $request)
+    {
+        $path = str_replace(config('app.url'), '', $request->input('file_url'));
+        if (!file_exists(public_path($path))) {
+            abort(404, '文件不存在');
+        }
+        return response()->download(public_path($path));
+    }
+
+
+    public function clearProcess($siteId)
+    {
+
+
+        SiteProcess::query()->where([
+            'site_id' => $siteId
+        ])->whereNotIn('process_id', [1, 2, 12, 7])->delete();
+
+        SiteProcess::query()->where(['site_id' => $siteId, 'process_id' => 1])->update([
+            'active' => 1
+        ]);
+        SiteProcess::query()->where(['site_id' => $siteId, 'process_id' => 2])->update([
+            'active' => 2
+        ]);
+
+        SiteProcess::query()->where(['site_id' => $siteId, 'process_id' => 12])->update([
+            'active' => 2
+        ]);
+
+        SiteProcess::query()->where(['site_id' => $siteId, 'process_id' => 7])->update([
+            'active' => 2
+        ]);
+
+
+        dd('success');
+    }
+
+    public function resetTask(Request $request)
+    {
+        $taskId = 63;
+        $task = LinkTask::query()->where(['id' => $taskId])->first();
+        dd($task);
+        $task->status = 3;
+        $task->save();
+
+        LinkTaskDetail::query()->where(['task_id' => $task->id])->update(['status' => 3]);
+        dd('success');
+    }
+
+    public function generateReport()
+    {
+        ignore_user_abort(true);
+        set_time_limit(0);
+
+        $projectYm = date('Ym', strtotime('first day of -1 month'));
+        $rankConn = DB::connection('rank');
+        $projectIds = $rankConn->table('webmaster_flow')->select(['project_id'])->where(['flow_ym' => $projectYm, 'status' => 1])->pluck('project_id')->toArray();
+
+        $taskUrls = [];
+        foreach ($projectIds as $item) {
+            $taskUrls[] = sprintf("https://rank.yinqingli.cn/webmaster/report?projectId=%s", base64_encode($item));
+        }
+
+        $client = new Client([
+            'verify' => false,
+            'timeout' => 30
+        ]); //并发请求链接地址
+        $requests = function () use ($client, $taskUrls) {
+            foreach ($taskUrls as $item) {
+                if (empty($item))
+                    continue;
+                yield new \GuzzleHttp\Psr7\Request('GET', $item);
+            }
+        };
+
+        $result = [];
+        $fails = [];
+        $pool = new Pool($client, $requests(), [
+            'concurrency' => 10, //同时并发抓取几个
+            'fulfilled' => function (Response $response, $index) use (&$result) {
+                $result[] = $index;
+            },
+            'rejected' => function (\Throwable $throwable, $index) use (&$fails) {
+                $fails[] = $index;
+            },
+        ]);
+
+        $promise = $pool->promise();
+        $promise->wait();
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function rmCacheKey()
+    {
+        $cacheList = [
+            CacheConstant::NOW_MONTH_KEY_WORDS_CACHE_KEY,
+            CacheConstant::LAST_MONTH_KEY_WORDS_CACHE_KEY,
+            CacheConstant::LAST_MONTH_BQ_KEY_WORDS_CACHE_KEY,
+            CacheConstant::NOW_MONTH_INQUIRE_CACHE_KEY,
+            CacheConstant::LAST_MONTH_INQUIRE_CACHE_KEY,
+            CacheConstant::LAST_MONTH_BQ_INQUIRE_CACHE_KEY,
+            CacheConstant::NOW_MONTH_FLOW_CACHE_KEY,
+            CacheConstant::LAST_MONTH_FLOW_CACHE_KEY,
+            CacheConstant::LAST_MONTH_BQ_FLOW_CACHE_KEY,
+        ];
+        foreach ($cacheList as $value) {
+            Cache::forget($value);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    public function bulkUpload(Request $request)
+    {
+        $images = [];
+        if ($request->hasFile('files')) {
+
+            foreach ($request->file('files') as $file) {
+                $name = sprintf('%s.%s', Str::random(40), $file->getClientOriginalExtension());
+                $fileData['file_url'] = $file->storeAs(date('Ym') . '/' . $file->getClientOriginalExtension(), $name, 'public');
+                $fileData['file_url'] = $request->input('notAsset') ? sprintf('/storage/%s', $fileData['file_url']) : asset('storage/' . $fileData['file_url']);
+                $fileData['original_name'] = $file->getClientOriginalName();
+                $fileData['time'] = time();
+                $images[] = $fileData;
+            }
+        }
+        return response()->json(['message' => '上传成功', 'data' => $images]);
+    }
+
+
+}

+ 166 - 0
app/Http/Controllers/Admin/User/UserController

@@ -0,0 +1,166 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: vanshao
+ * Date: 2019-05-10
+ * Time: 10:20
+ */
+
+namespace App\Http\Controllers\Admin\User;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Log;
+
+class UserController extends Controller
+{
+    public function __construct()
+    {
+    }
+
+    public function index(Request $request)
+    {
+
+        if ($siteId = $request->input('siteId')) {
+            $site = Site::query()->where(['id' => $siteId])->first();
+            $view = 'admin/user/user_for_site';
+            $btn['closeBtn'] = 1;
+//            dd($siteId,$site);
+        } else {
+            $user = auth()->user();
+            $site = $user->sites->first();
+            $view = 'admin/user/user';
+            $btn['closeBtn'] = 0;
+        }
+
+
+        if (!$site) {
+            return view('admin/errors/tips', $btn);
+        }
+
+        //访问地区数据
+        $area = 'SELECT country AS region, SUM(pv) AS pv,SUM(uv) AS uv,SUM(ip) AS ip,SUM(duration) AS duration,SUM(bounces) AS bounces FROM `scn_traffic_report_region` GROUP BY `country` ORDER BY `pv` DESC limit 10';
+        ////访问量数据
+        $access = sprintf('SELECT FROM_UNIXTIME(timestamp, "%%Y-%%m-%%d") AS lt,
+				SUM(pv) AS pv,
+				SUM(uv) AS uv,
+				SUM(ip) AS ip,
+				SUM(duration) AS duration,
+				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` WHERE timestamp <= %s GROUP BY `lt` ORDER BY `lt` desc LIMIT 14 ', time());
+        //总访问量
+        $totalAccess = 'SELECT 
+				SUM(pv) AS pv,
+				SUM(uv) AS uv,
+				SUM(duration) AS duration,
+				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` limit 1';
+        //top10url
+        $top10Url = sprintf('SELECT url, SUM(pv) AS pv FROM `scn_traffic_report_url` WHERE timestamp <= %s GROUP BY `url` ORDER BY `pv` DESC limit 10 ', strtotime('-30 days'));
+        ////top10入口
+        $top10Entrance = sprintf('SELECT ref_host, SUM(pv) AS pv FROM `scn_traffic_report_referrer` WHERE timestamp >= %s GROUP BY `ref_host` ORDER BY `pv` DESC limit 10', strtotime('-30 days'));
+        $content = 'select count(1) from scn_content';
+
+        $http = new Client();
+//        $sql = array(
+//            'SELECT country AS region,
+//                        SUM(pv) AS pv,
+//						SUM(uv) AS uv,
+//						SUM(ip) AS ip,
+//						SUM(duration) AS duration,
+//						SUM(bounces) AS bounces FROM `scn_traffic_report_region` GROUP BY `country` ORDER BY `pv` DESC limit 10 ',//访问地区数据
+//            'SELECT FROM_UNIXTIME(timestamp, "%Y-%m-%d") AS lt,
+//				SUM(pv) AS pv,
+//				SUM(uv) AS uv,
+//				SUM(ip) AS ip,
+//				SUM(duration) AS duration,
+//				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` WHERE timestamp <= ' . "'" . time() . "'" . ' GROUP BY `lt` ORDER BY `lt` desc LIMIT 14 ',//访问量数据
+//            'SELECT
+//				SUM(pv) AS pv,
+//				SUM(uv) AS uv,
+//				SUM(duration) AS duration,
+//				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` limit 1',//总访问量
+//            'SELECT url, SUM(pv) AS pv FROM `scn_traffic_report_url` WHERE timestamp <= ' . "'" . strtotime('-30 days') . "'" . ' GROUP BY `url` ORDER BY `pv` DESC limit 10 ',//top10url
+//            'SELECT ref_host, SUM(pv) AS pv FROM `scn_traffic_report_referrer` WHERE timestamp >= ' . "'" . strtotime('-30 days') . "'" . ' GROUP BY `ref_host` ORDER BY `pv` DESC limit 10 ',//top10入口
+//            'select count(1) from scn_content'
+//        );
+
+        try {
+            $response = $http->post($site->api_url . 'api / getdata', [
+                'form_params' => [
+                    $area,
+                    $access,
+                    $totalAccess,
+                    $top10Url,
+                    $top10Entrance,
+                    $content
+                ]
+            ]);
+            $data = json_decode($response->getBody(), true);
+            $country_data = array_reverse($data['0']);
+            $pv_data = array_reverse($data['1']);
+            $total_pv_data = $data['2']['0'];
+            $top_url = $data['3'];
+            $top_host = $data['4'];
+            $total_content = $data['5']['0']['count(1)'];
+            //访问地区数据
+            foreach ($country_data as $value) {
+                $country_data['country'][] = $value['region'];
+                $country_data['pv'][] = $value['pv'];
+                $country_data['uv'][] = $value['uv'];
+                $country_data['duration'][] = round($value['duration'] / $value['pv'] / 60) . ' . ' . ($value['duration'] / $value['pv']) % 60;
+                $country_data['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+                $country_data['pie'][] = array(
+                    'value' => $value['pv'],
+                    'name' => $value['region']
+                );
+            }
+            $country_data['pie_country'] = array_reverse($country_data['country']);//饼图国家倒叙
+
+            //平均在线时长
+            $total_pv_data['avg_duration'] = round($total_pv_data['duration'] / $total_pv_data['pv'] / 60) . ':' . ($total_pv_data['duration'] / $total_pv_data['pv']) % 60;
+
+            //访问量数据
+            foreach ($pv_data as $value) {
+                $pv_data['pv'][] = $value['pv'];
+                $pv_data['uv'][] = $value['uv'];
+                $pv_data['duration'][] = round($value['duration'] / $value['pv'] / 60) . ' . ' . ($value['duration'] / $value['pv']) % 60;
+                $pv_data['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+            }
+            $pv_data['xAxis'] = $this->getDescDayTime(13);
+
+            $addition = [
+                'pv_data' => $pv_data,
+                'country_data' => $country_data,
+                'total_pv_data' => $total_pv_data,
+                'top_url' => $top_url,
+                'top_host' => $top_host,
+                'total_content' => $total_content,
+            ];
+
+        } catch (\Throwable $throwable) {
+            Log::info(var_export($throwable->getMessage(), 1));
+            return view('admin / errors / tips', [
+                    'tips' => '请求错误'
+                ] + $btn);
+        }
+
+
+        return view($view, ['site' => $site, 'siteId' => $site->id] + $addition);
+    }
+
+    //获取倒序日期时间
+    public function getDescDayTime($days)
+    {
+
+        for ($i = $days; $i >= 0; $i--) {
+            $return_data[] = date("m-d", strtotime("-$i day"));
+        }
+
+        return $return_data;
+    }
+
+
+}

+ 203 - 0
app/Http/Controllers/Admin/User/UserController.php

@@ -0,0 +1,203 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: vanshao
+ * Date: 2019-05-10
+ * Time: 10:20
+ */
+
+namespace App\Http\Controllers\Admin\User;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Site;
+use App\Http\Models\User;
+use GuzzleHttp\Client;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Log;
+
+class UserController extends Controller
+{
+    public function __construct()
+    {
+    }
+
+    public function index(Request $request)
+    {
+
+        if ($siteId = $request->input('siteId')) {
+            $site = Site::query()->where(['id' => $siteId])->first();
+            $view = 'admin/user/user_for_site';
+            $btn['closeBtn'] = 1;
+//            dd($siteId,$site);
+        } else {
+            $user = auth()->user();
+            $site = $user->sites->first();
+            $view = 'admin/user/user';
+            $btn['closeBtn'] = 0;
+        }
+
+
+        if (!$site) {
+            if($siteId) {
+                return view('admin/site/tips', [
+                    'tips' => '站点信息不存在',
+                    'siteId' => $siteId
+                ]);
+            }
+           return view('admin/errors/tips', $btn);
+        }
+
+        //访问地区数据
+        $area = 'SELECT country AS region, SUM(pv) AS pv,SUM(uv) AS uv,SUM(ip) AS ip,SUM(duration) AS duration,SUM(bounces) AS bounces FROM `scn_traffic_report_region` GROUP BY `country` ORDER BY `pv` DESC limit 10';
+        ////访问量数据//流量趋势 行为趋势
+        $access = sprintf('SELECT FROM_UNIXTIME(timestamp, "%%Y-%%m-%%d") AS lt,
+				SUM(pv) AS pv,
+				SUM(uv) AS uv,
+				SUM(ip) AS ip,
+				SUM(duration) AS duration,
+				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` WHERE timestamp <= %s GROUP BY `lt` ORDER BY `lt` desc LIMIT 14 ', time());
+        //总访问量
+        $totalAccess = 'SELECT 
+				SUM(pv) AS pv,
+				SUM(uv) AS uv,
+				SUM(duration) AS duration,
+				SUM(bounces) AS bounces FROM `scn_traffic_report_hourly` limit 1';
+        //top10url
+//        $top10Url = sprintf('SELECT url, SUM(pv) AS pv FROM `scn_traffic_report_url` WHERE timestamp <= %s GROUP BY `url` ORDER BY `pv` DESC limit 100 ', strtotime('-30 days'));
+        $top10Url = sprintf('SELECT url, SUM(pv) AS pv FROM `scn_traffic_report_url` WHERE timestamp >= %s GROUP BY `url` ORDER BY `pv` DESC limit 100 ', strtotime('-30 days'));
+
+        ////top10入口
+        $top10Entrance = sprintf('SELECT ref_host, SUM(pv) AS pv FROM `scn_traffic_report_referrer` WHERE timestamp >= %s GROUP BY `ref_host` ORDER BY `pv` DESC limit 100', strtotime('-30 days'));
+        $content = 'select count(1) from scn_content';
+
+        $http = new Client();
+
+        try {
+            $response = $http->post($site->api_url . 'api / getdata', [
+                'form_params' => [
+                    $area,
+                    $access,
+                    $totalAccess,
+                    $top10Url,
+                    $top10Entrance,
+                    $content
+                ]
+            ]);
+            $data = json_decode($response->getBody(), true);
+            $country_data = array_reverse($data['0']);
+//            $pv_data = array_reverse($data['1']);
+            $pv_data = $data['1'];
+
+            $total_pv_data = $data['2']['0'];
+            $top_url = $data['3'];
+            $top_host = $data['4'];
+            $total_content = $data['5']['0']['count(1)'];
+            //访问地区数据
+            foreach ($country_data as $value) {
+                $country_data['country'][] = $value['region'];
+                $country_data['pv'][] = $value['pv'];
+                $country_data['uv'][] = $value['uv'];
+                $country_data['duration'][] = round($value['duration'] / $value['pv'] / 60) . '.' . ($value['duration'] / $value['pv']) % 60;
+//                dd( $value['uv'] );
+//                $country_data['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+
+                if (empty($value['uv'])) {
+                    $country_data['bounces'][] =0;
+                } else {
+                    $country_data['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+                }
+
+//                dd($country_data);
+                $country_data['pie'][] = array(
+                    'value' => $value['pv'],
+                    'name' => $value['region']
+                );
+
+            }
+            $country_data['pie_country'] = array_reverse($country_data['country']);//饼图国家倒叙
+
+            //平均在线时长
+            $total_pv_data['avg_duration'] = round($total_pv_data['duration'] / $total_pv_data['pv'] / 60) . ':' . ($total_pv_data['duration'] / $total_pv_data['pv']) % 60;
+
+
+//            $pv_data['xAxis'] = $this->getDescDayTime(13);
+            $pv_data_day = $this->getDescDayTime(13);
+
+            foreach ($pv_data_day as $day_key=>$day_value){
+
+                $is_in=0;
+
+                foreach ($pv_data as $value){
+
+                    if(substr($value['lt'],5)==$day_value){
+                        $pv_data2['pv'][] = $value['pv'];
+                        $pv_data2['uv'][] = $value['uv'];
+                        $pv_data2['duration'][] = round($value['duration'] / $value['pv'] / 60) . '.' . ($value['duration'] / $value['pv']) % 60;
+                        $pv_data2['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+                        $is_in=1;
+                    }
+
+                }
+                if($is_in==0){
+                    $pv_data2['pv'][] = '0';
+                    $pv_data2['uv'][] = '0';
+                    $pv_data2['duration'][] = '0';
+                    $pv_data2['bounces'][] = '100';
+                }
+
+            }
+
+            $pv_data=$pv_data2;
+            $pv_data['xAxis']=$pv_data_day;
+
+            //访问量数据
+//            foreach ($pv_data as $value) {
+//                $pv_data['pv'][] = $value['pv'];
+//                $pv_data['uv'][] = $value['uv'];
+//                $pv_data['duration'][] = round($value['duration'] / $value['pv'] / 60) . '.' . ($value['duration'] / $value['pv']) % 60;
+//                $pv_data['bounces'][] = round($value['bounces'] / $value['uv'] * 100) > 100 ? 100 : round($value['bounces'] / $value['uv'] * 100);
+//            }
+
+            $addition = [
+                'pv_data' => $pv_data,
+                'country_data' => $country_data,
+                'total_pv_data' => $total_pv_data,
+                'top_url' => $top_url,
+                'top_host' => $top_host,
+                'total_content' => $total_content,
+            ];
+//
+//
+        } catch (\Throwable $throwable) {
+            Log::info(var_export($throwable->getMessage(), 1));
+
+            if($siteId) {
+                return view('admin/site/tips', [
+                    'tips' => '站点信息不存在',
+                    'siteId' => $siteId
+                ]);
+            }
+
+            return view('admin/errors/tips', [
+                    'tips' => '请求错误'
+                ] + $btn);
+        }
+
+
+        return view($view, ['site' => $site, 'siteId' => $site->id] + $addition);
+    }
+
+    //获取倒序日期时间
+    public function getDescDayTime($days)
+    {
+
+        for ($i = $days; $i >= 0; $i--) {
+            $return_data[] = date("m-d", strtotime("-$i day"));
+        }
+
+        return $return_data;
+    }
+
+
+}

File diff suppressed because it is too large
+ 1466 - 0
app/Http/Controllers/Admin/WebmasterController.php


+ 354 - 0
app/Http/Controllers/Admin/WitnessProject/WitnessProjectController.php

@@ -0,0 +1,354 @@
+<?php
+
+namespace App\Http\Controllers\Admin\WitnessProject;
+
+use App\Http\Controllers\Controller;
+use App\Http\Models\Role;
+use App\Http\Models\Site;
+use App\Http\Models\WitnessProjectIntroduce;
+use App\Http\Models\WitnessProjectRenew;
+use Illuminate\Http\Request;
+use Illuminate\Contracts\View\Factory;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\DB;
+use Illuminate\View\View;
+
+class WitnessProjectController extends Controller
+{
+    const GIFT_LIST = [
+        0 => '请选择礼品名称',
+        1 => '书籍',
+        2 => '高档真丝礼盒',
+        3 => '京东E卡',
+        4 => '油卡',
+    ];
+
+    const DURATION_RENEWALS_LIST = [
+        0 => '请选择续费时长',
+        1 => '1年',
+        2 => '2年',
+        3 => '3年',
+    ];
+
+    const NUMBER_RENEWALS = [
+        0 => '请选择续费次数',
+        1 => '第1次',
+        2 => '第2次',
+        3 => '第3次',
+        4 => '第4次',
+        5 => '第5次',
+        6 => '第6次',
+        7 => '第7次',
+    ];
+
+    const ORDER_VOLUME_LIST = [
+        0 => '请选择订单',
+        1 => '第1单',
+        2 => '第2单',
+        3 => '第3单',
+    ];
+
+    const ROLE_LIST = [
+        0 => '请选择角色',
+        1 => '对接人',
+        2 => '决策人',
+        3 => '老板',
+    ];
+
+    /**
+     * 见证人 续费
+     * @param Request $request
+     * @param $siteId
+     * @return Factory|JsonResponse|View
+     */
+    public function renew(Request $request, $siteId)
+    {
+        if (!$request->ajax()) {
+            return view('admin/witness_project/renew', [
+                'siteId' => $siteId,
+            ]);
+        }
+        $list = WitnessProjectRenew::query()
+            ->where('site_id', $siteId)
+            ->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        foreach ($list as $item) {
+            $item->number_renewals = self::NUMBER_RENEWALS[$item->number_renewals] ?? '';
+            $item->duration_renewals = self::DURATION_RENEWALS_LIST[$item->duration_renewals] ?? '';
+            $item->gift_name = self::GIFT_LIST[$item->gift_name] ?? '';
+            $item->position = self::ROLE_LIST[$item->position] ?? '';
+        }
+
+        return response()->json([
+            'rows' => $list->items(),
+            'total' => $list->total()
+        ]);
+    }
+
+    /**
+     * 编辑
+     * @param Request $request
+     * @param $id
+     * @param $siteId
+     * @return Factory|View
+     */
+    public function renewAdd(Request $request, $id, $siteId)
+    {
+        if (!$request->ajax()) {
+            $info = WitnessProjectRenew::query()
+                ->where('site_id', $siteId)
+                ->where('id', $id)
+                ->first();
+
+            return view('admin/witness_project/renew_add', [
+                'info' => $info,
+                'siteId' => $siteId,
+                'roleList' => self::ROLE_LIST,
+                'giftList' => self::GIFT_LIST,
+                'durationRenewalsList' => self::DURATION_RENEWALS_LIST,
+                'numberRenewalsList' => self::NUMBER_RENEWALS,
+            ]);
+        }
+        $request = $request->all();
+        if (!preg_match("/^1[3456789]\d{9}$/", $request['tel'])) {
+            return response()->json(['message' => '手机号输入有误'], 400);
+        }
+        if (!is_numeric($request['gift_amount'])) {
+            return response()->json(['message' => '礼品金额必须为数字'], 400);
+        }
+
+        $update = [
+            'customer_name' => $request['customer_name'] ?? '',
+            'tel' => $request['tel'] ?? '',
+            'position' => $request['position'] ?? '',
+            'number_renewals' => $request['number_renewals'] ?? 0,
+            'duration_renewals' => $request['duration_renewals'] ?? 0,
+            'gift_name' => $request['gift_name'] ?? '',
+            'gift_amount' => $request['gift_amount'],
+            'gift_time' => $request['gift_time'] ?? date('Y-m-d'),
+            'remark' => $request['remark'] ?? '',
+            'site_id' => $request['site_id'] ?? 0,
+        ];
+
+        if (empty($id)) {
+            WitnessProjectRenew::query()->insert($update);
+        } else {
+            WitnessProjectRenew::query()->where('id', $id)->update($update);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 见证人 介绍
+     * @param Request $request
+     * @param $siteId
+     * @return Factory|JsonResponse|View
+     */
+    public function introduce(Request $request, $siteId)
+    {
+        if (!$request->ajax()) {
+            return view('admin/witness_project/introduce', [
+                'siteId' => $siteId
+            ]);
+        }
+        $list = WitnessProjectIntroduce::query()
+            ->where('site_id', $siteId)
+            ->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+
+        foreach ($list as $item) {
+            $item->order_volume = self::ORDER_VOLUME_LIST[$item->order_volume] ?? '';
+            $item->gift_name = self::GIFT_LIST[$item->gift_name] ?? '';
+            $item->position = self::ROLE_LIST[$item->position] ?? '';
+        }
+        return response()->json([
+            'rows' => $list->items(),
+            'total' => $list->total()
+        ]);
+    }
+
+    /**
+     * 编辑
+     * @param Request $request
+     * @param $id
+     * @param $siteId
+     * @return Factory|View
+     */
+    public function introduceAdd(Request $request, $id, $siteId)
+    {
+        if (!$request->ajax()) {
+            $info = WitnessProjectIntroduce::query()
+                ->where('site_id', $siteId)
+                ->where('id', $id)
+                ->first();
+
+            return view('admin/witness_project/introduce_add', [
+                'info' => $info,
+                'siteId' => $siteId,
+                'roleList' => self::ROLE_LIST,
+                'giftList' => self::GIFT_LIST,
+                'durationRenewalsList' => self::DURATION_RENEWALS_LIST,
+                'numberRenewalsList' => self::NUMBER_RENEWALS,
+                'orderVolumeList' => self::ORDER_VOLUME_LIST,
+            ]);
+        }
+
+        $request = $request->all();
+        if (!preg_match("/^1[3456789]\d{9}$/", $request['tel'])) {
+            return response()->json(['message' => '手机号输入有误'], 400);
+        }
+        if (!is_numeric($request['gift_amount'])) {
+            return response()->json(['message' => '礼品金额必须为数字'], 400);
+        }
+
+        $update = [
+            'customer_name' => $request['customer_name'] ?? '',
+            'tel' => $request['tel'] ?? '',
+            'position' => $request['position'] ?? 0,
+            'order_volume' => $request['order_volume'] ?? 0,
+            'gift_name' => $request['gift_name'] ?? '',
+            'gift_amount' => $request['gift_amount'],
+            'gift_time' => $request['gift_time'] ?? date('Y-m-d'),
+            'transaction_customer' => $request['transaction_customer'] ?? '',
+            'remark' => $request['remark'] ?? '',
+            'site_id' => $request['site_id'] ?? 0,
+        ];
+
+        if (empty($id)) {
+            WitnessProjectIntroduce::query()->insert($update);
+        } else {
+            WitnessProjectIntroduce::query()->where('id', $id)->update($update);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+    /**
+     * 删除
+     * @param Request $request
+     * @param $type
+     * @return JsonResponse
+     */
+    public function deleteRow(Request $request, $type)
+    {
+        $ids = $request->input('ids');
+        if ($type == 1) {
+            WitnessProjectRenew::query()->whereIn('id', $ids)->update(['deleted_at' => date('Y-m-d H:i:s')]);
+        }
+
+        if ($type == 2) {
+            WitnessProjectIntroduce::query()->whereIn('id', $ids)->update(['deleted_at' => date('Y-m-d H:i:s')]);
+        }
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    /**
+     * 汇总
+     * @param Request $request
+     * @return Factory|JsonResponse|View
+     */
+    public function renewSummary(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/witness_project/renew_summary', [
+                'services' => Role::getUsers(Role::TYPE_SERVER),
+                'sellerUsers' => Role::getUsers(Role::TYPE_SELLER),
+            ]);
+        }
+
+        /** @var $list object */
+        $list = $this->build($request, WitnessProjectRenew::query())
+            ->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $list = $this->combination($list);
+
+        return response()->json([
+            'rows' => $list->items(),
+            'total' => $list->total()
+        ]);
+    }
+
+
+    /**
+     * 查询
+     * @param $request object
+     * @param $result object
+     * @return object
+     */
+    private function build($request, $result)
+    {
+        $keyword = $request->input('keyword');
+        if (!empty($keyword)) {
+            $siteIds = Site::query()->where('cn_title', 'like', '%' . $keyword . '%')->pluck('id');
+            $result->whereIn('site_id', $siteIds);
+        }
+        $sellerId = $request->input('sellerId');
+        if (!empty($sellerId)) {
+            $siteIds = DB::table('user_has_sites')->where('user_id', $sellerId)->pluck('site_id') ?? [];
+            $result->whereIn('site_id', $siteIds);
+        }
+        $serverId = $request->input('serverId');
+        if (!empty($serverId)) {
+            $siteIds = DB::table('user_has_sites')->where('user_id', $serverId)->pluck('site_id') ?? [];
+            $result->whereIn('site_id', $siteIds);
+        }
+        return $result;
+    }
+
+    /**
+     * 组合字段
+     * @param $list object
+     * @return mixed
+     */
+    public function combination($list)
+    {
+        $siteList = Site::query()->pluck('cn_title', 'id');
+        foreach ($list->items() as $item) {
+            $item->position = self::ROLE_LIST[$item->position] ?? '';
+            if (isset($item->number_renewals)) {
+                $item->number_renewals = self::NUMBER_RENEWALS[$item->number_renewals] ?? '';
+            }
+            if (isset($item->duration_renewals)) {
+                $item->duration_renewals = self::DURATION_RENEWALS_LIST[$item->duration_renewals] ?? '';
+            }
+            if (isset($item->gift_name)) {
+                $item->gift_name = self::GIFT_LIST[$item->gift_name] ?? '';
+            }
+            if (isset($item->site_id)) {
+                $item->site = $siteList[$item->site_id] ?? '';
+            }
+            if (isset($item->order_volume)) {
+                $item->order_volume = self::ORDER_VOLUME_LIST[$item->order_volume] ?? '';
+            }
+        }
+        return $list;
+    }
+
+    /**
+     * 汇总2
+     * @param Request $request
+     * @return Factory|JsonResponse|View
+     */
+    public function introduceSummary(Request $request)
+    {
+        if (!$request->ajax()) {
+            return view('admin/witness_project/introduce_summary', [
+                'services' => Role::getUsers(Role::TYPE_SERVER),
+                'sellerUsers' => Role::getUsers(Role::TYPE_SELLER),
+            ]);
+        }
+
+        /** @var $list object */
+        $list = $this->build($request, WitnessProjectIntroduce::query())
+            ->orderByDesc('id')
+            ->paginate($request->input('pageSize') ?? TABLE_PAGE_SIZE);
+        $list = $this->combination($list);
+
+        return response()->json([
+            'rows' => $list->items(),
+            'total' => $list->total()
+        ]);
+    }
+
+
+}

+ 127 - 0
app/Http/Controllers/Admin/WorkTaskController.php

@@ -0,0 +1,127 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Models\Site;
+use Illuminate\Http\JsonResponse;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 项目管理下的详情的工作任务
+ * Class WorkTaskController
+ * @package App\Http\Controllers\Admin
+ */
+class WorkTaskController extends Controller
+{
+    //项目里面的客服任务
+    public function index($siteId)
+    {
+        [$siteStatus, $tasksList, $infoList, $optimizationPersonnel] = $this->build($siteId);
+        return view('admin/work_task/index', [
+            'siteStatus' => $siteStatus,
+            'siteId' => $siteId,
+            'tasksList' => $tasksList,
+            'infoList' => $infoList,
+            'optimizationPersonnel' => $optimizationPersonnel,
+            'date' => date('Y-m-d')
+        ]);
+    }
+
+    //菜单里面的客服任务
+    public function siteTask()
+    {
+        [$siteStatus, $tasksList, $infoList, $optimizationPersonnel] = $this->build(0);
+        return view('admin/work_task/site_task', [
+            'sites' => Site::query()->select('id', 'cn_title')->whereIn('status', [1, 2, 3])->get(),
+            'siteId' => 0,
+            'date' => date('Y-m-d'),
+            'optimizationPersonnel' => $optimizationPersonnel,
+        ]);
+    }
+
+    //异步获取每个项目的对应的客服任务
+    public function getSiteTaskInfo($siteId)
+    {
+        [$siteStatus, $tasksList, $infoList, $optimizationPersonnel] = $this->build($siteId);
+        $data = [
+            'siteStatus' => $siteStatus,
+            'tasksList' => $tasksList,
+            'infoList' => $infoList,
+            'optimizationPersonnel' => $optimizationPersonnel,
+            'siteId' => $siteId
+        ];
+        return response()->json(['data' => $data]);
+    }
+
+    /**
+     * 保存
+     * @param Request $request
+     * @param $siteId
+     * @param $status
+     * @return JsonResponse
+     */
+    public function save(Request $request, $siteId, $status)
+    {
+        $infoList = $request->input('infoList');
+        $dataList = $request->input('dataList');
+        if (empty($dataList)) {
+            return response()->json(['message' => '还没创建任务'], 400);
+        }
+        foreach ($dataList as $key => $value) {
+            $dataList[$key]['task_id'] = time() . mt_rand(1, 1000000);
+        }
+
+        $data = [
+            'infoList' => $infoList,
+            'dataList' => $dataList,
+        ];
+        $updateData = [
+            'data' => \GuzzleHttp\json_encode($data),
+            'site_id' => $siteId,
+            'status' => $status
+        ];
+        DB::transaction(function () use ($updateData, $siteId, $status) {
+
+            DB::table('tasks')
+                ->where('site_id', $siteId)
+                ->where('status', $status)
+                ->delete();
+            DB::table('tasks')->insert($updateData);
+        });
+
+        return response()->json(['message' => '操作成功']);
+    }
+
+
+    private function build($siteId)
+    {
+        $infoList = [];
+        $tasksList = [];
+
+        $siteStatus = DB::table('sites')->where('id', $siteId)->value('status') ?? 0;
+        //客服、优化、软文、外链、前端
+        $optimizationPersonnel = DB::table('users')->join('roles', 'users.role_id', '=', 'roles.id')
+            ->where('users.status', 1)
+            ->whereIn('users.role_id', [7, 14, 17, 18, 26])
+            ->whereNull('users.deleted_at')
+            ->orderBy('users.role_id', 'asc')
+            ->select('users.id', 'users.nickname')->get();
+
+        $tasksInfo = DB::table('tasks')->where([
+            ['site_id', '=', $siteId],
+            ['status', '=', $siteStatus]
+        ])->first();
+
+        if (!empty($tasksInfo->data)) {
+            $list = \GuzzleHttp\json_decode($tasksInfo->data, true);
+            $infoList = explode(',', $list['infoList']) ?? [];
+            $tasksList = $list['dataList'];
+        }
+        return [$siteStatus, $tasksList, $infoList, $optimizationPersonnel];
+
+    }
+
+
+}

+ 74 - 0
app/Http/Controllers/ApiController.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\Http\Models\SiteTempReport;
+use App\Http\Models\User;
+use DirkGroenen\Pinterest\Pinterest;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Ramsey\Uuid\Uuid;
+
+class ApiController extends Controller
+{
+    public function table(Request $request)
+    {
+
+//        header("Access-Control-Allow-Origin: *"); // 允许a.com发起的跨域请求
+//        header("Access-Control-Allow-Origin: *"); // 允许任意域名发起的跨域请求
+//        header('Access-Control-Allow-Headers: X-Requested-With,X_Requested_With');
+        $records = User::query()->paginate($request->input('results'));
+        $items = $records->items();
+
+        array_walk($items, function ($item) {
+            $item->login = (object)[
+                'uuid' => Uuid::uuid1()
+            ];
+            $item->name = (object)[
+                'title' => mt_rand(10, 99) . 'title',
+                'first' => mt_rand(10, 99) . 'first',
+                'last' => mt_rand(10, 99) . 'first'
+            ];
+        });
+        return response()->json(['total' => $records->total(), 'rows' => $records->items()]);
+    }
+
+    public function reportTemp(Request $request)
+    {
+        $inputs = $request->input();
+        if (!$inputs['old_id'] || $inputs['ym']
+            || $inputs['month_traffic'] || $inputs['month_inquiry']
+            || $inputs['month_rank'] || $inputs['month_article']) {
+            Log::warning(sprintf('reportTemp接口参数请求错误%s', var_export($inputs, 1)));
+            return;
+        }
+        SiteTempReport::query()->updateOrCreate([
+            'old_id' => $inputs['old_id'],
+            'ym' => $inputs['ym']
+
+        ], [
+            'month_traffic' => $inputs['month_traffic'],
+            'month_inquiry' => $inputs['month_inquiry'],
+            'month_rank' => $inputs['month_rank'],
+            'month_article' => $inputs['month_article']
+        ]);
+    }
+
+    public function getPinBoards(Request $request)
+    {
+        $inputs = $request->input();
+        if (empty($inputs['token'])) {
+            return response()->json([
+                'status' => 400,
+                'message' => '缺少参数'
+            ]);
+        }
+        $pin = new Pinterest($inputs['app_id'], $inputs['app_secret']);
+        $pin->auth->setOAuthToken($inputs['token']);
+        $result = $pin->users->getMeBoards();
+        return response()->json([
+            'status' => 200,
+            'boards' => $result
+        ]);
+    }
+}

+ 32 - 0
app/Http/Controllers/Auth/ForgotPasswordController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
+
+class ForgotPasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset emails and
+    | includes a trait which assists in sending these notifications from
+    | your application to your users. Feel free to explore this trait.
+    |
+    */
+
+    use SendsPasswordResetEmails;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+}

+ 39 - 0
app/Http/Controllers/Auth/LoginController.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+
+class LoginController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Login Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles authenticating users for the application and
+    | redirecting them to your home screen. The controller uses a trait
+    | to conveniently provide its functionality to your applications.
+    |
+    */
+
+    use AuthenticatesUsers;
+
+    /**
+     * Where to redirect users after login.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/home';
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest')->except('logout');
+    }
+}

+ 0 - 0
app/Http/Controllers/Auth/RegisterController.php


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