Kaynağa Gözat

增加统计接口(数据上报统计、科普视频、科普统计、社心、新媒体访问统计)
修改四进活动
增加管理后台统计
sso用户逻辑
sso 健康科普

shawdonH 2 gün önce
ebeveyn
işleme
9b530370c1
94 değiştirilmiş dosya ile 4320 ekleme ve 26 silme
  1. 21 0
      app/Admin/Actions/Activity/Report.php
  2. 29 0
      app/Admin/Controllers/AdminConfigController.php
  3. 78 8
      app/Admin/Controllers/FourActivityController.php
  4. 11 3
      app/Admin/Controllers/HomeController.php
  5. 74 0
      app/Admin/Controllers/NewMediaCountController.php
  6. 1 0
      app/Admin/Controllers/ScaleCategoryController.php
  7. 27 2
      app/Admin/Controllers/ScaleController.php
  8. 2 1
      app/Admin/bootstrap.php
  9. 16 1
      app/Admin/routes.php
  10. 11 0
      app/Facades/DashbordFacade.php
  11. 12 0
      app/Facades/SmsFacade.php
  12. 112 0
      app/Http/Controllers/Api/V1/DashboardController.php
  13. 21 0
      app/Http/Controllers/Api/V1/MpUser/MpUserController.php
  14. 223 0
      app/Http/Controllers/StatisticsApi/IndexController.php
  15. 91 0
      app/Http/Controllers/Web/VideoCategoryController.php
  16. 73 0
      app/Http/Controllers/Web/VideoController.php
  17. 148 0
      app/Http/Helpers/ApiResponse.php
  18. 122 0
      app/Http/Helpers/ExceptionReport.php
  19. 65 0
      app/Http/Helpers/Tencent/MediaTencent.php
  20. 90 0
      app/Http/Helpers/Tencent/RoomTencent.php
  21. 75 0
      app/Http/Helpers/Tencent/SignTencent.php
  22. 84 0
      app/Http/Helpers/Tencent/StsTencent.php
  23. 34 0
      app/Http/Helpers/Tencent/TlsEventSig.php
  24. 66 0
      app/Http/Helpers/Tencent/TrtcTencent.php
  25. 6 0
      app/Http/Kernel.php
  26. 348 0
      app/Http/Middleware/PsyUserSso.php
  27. 43 0
      app/Http/Requests/DashboardReportRequest.php
  28. 11 0
      app/Models/AdminConfig.php
  29. 36 0
      app/Models/NewMediaCount.php
  30. 11 0
      app/Models/NewMediaCountRecord.php
  31. 8 0
      app/Models/Scale.php
  32. 4 0
      app/Models/ScaleCategory.php
  33. 11 0
      app/Models/SmsRecord.php
  34. 9 1
      app/Models/SpecialistInfo.php
  35. 6 0
      app/Providers/RepositoryServiceProvider.php
  36. 9 0
      app/Providers/RouteServiceProvider.php
  37. 21 0
      app/Repositories/Contracts/DashboardInterface.php
  38. 13 0
      app/Repositories/Contracts/SmsInterface.php
  39. 78 0
      app/Repositories/Eloquent/AliSmsFacadeRepository.php
  40. 76 0
      app/Repositories/Eloquent/BaseRepository copy.php
  41. 115 0
      app/Repositories/Eloquent/DashboardFacadeRepository.php
  42. 11 0
      app/Repositories/Eloquent/ScaleCategoryFacadeRepository.php
  43. 3 0
      app/Repositories/Eloquent/ScaleFacadeRepository.php
  44. 40 0
      app/Services/ExpertService.php
  45. 62 0
      app/Services/GroupTherapyService.php
  46. 114 0
      app/Services/LectureService.php
  47. 12 0
      app/Services/LiveService.php
  48. 135 0
      app/Services/LiveWebRecordService.php
  49. 145 0
      app/Services/RoomWebRecordService.php
  50. 49 0
      app/Services/TencentRequestService.php
  51. 43 0
      app/Services/VideoService.php
  52. 8 0
      app/Traits/PageDefaultInterface.php
  53. 21 0
      app/Traits/PageTrait.php
  54. 1 1
      config/cors.php
  55. 16 0
      config/dashboard.php
  56. 10 0
      config/sms.php
  57. 51 0
      public/assets/css/common.css
  58. 0 0
      public/assets/css/element_ui.css
  59. BIN
      public/assets/css/fonts/element-icons.ttf
  60. BIN
      public/assets/css/fonts/element-icons.woff
  61. BIN
      public/assets/images/dashboard/bak/._left_1b.png.bak
  62. BIN
      public/assets/images/dashboard/bak/._right_icon1.png
  63. BIN
      public/assets/images/dashboard/bak/._right_icon2.png
  64. BIN
      public/assets/images/dashboard/bak/._right_icon3.png
  65. BIN
      public/assets/images/dashboard/bak/._right_icon4.png
  66. BIN
      public/assets/images/dashboard/bak/left_1b.png.bak
  67. BIN
      public/assets/images/dashboard/bak/right_icon1.png
  68. BIN
      public/assets/images/dashboard/bak/right_icon2.png
  69. BIN
      public/assets/images/dashboard/bak/right_icon3.png
  70. BIN
      public/assets/images/dashboard/bak/right_icon4.png
  71. BIN
      public/assets/images/dashboard/left_1b.png
  72. BIN
      public/assets/images/dashboard/left_icon1.png
  73. BIN
      public/assets/images/dashboard/left_icon2.png
  74. BIN
      public/assets/images/dashboard/left_icon3.png
  75. BIN
      public/assets/images/dashboard/right_icon1.png
  76. BIN
      public/assets/images/dashboard/right_icon2.png
  77. BIN
      public/assets/images/dashboard/right_icon3.png
  78. BIN
      public/assets/images/dashboard/right_icon4.png
  79. 0 0
      public/assets/js/element_ui.js
  80. 7 0
      public/assets/js/highcharts.js
  81. 10 0
      public/assets/js/vue.js
  82. BIN
      resources/assets/css/fonts/element-icons.ttf
  83. BIN
      resources/assets/css/fonts/element-icons.woff
  84. 1 0
      resources/assets/js/app.js
  85. 28 0
      resources/assets/js/bootstrap.js
  86. 10 0
      resources/assets/js/vue.js
  87. 4 0
      resources/views/admin/partials/create_button.blade.php
  88. 736 0
      resources/views/admin/partials/dashboard.blade.php
  89. 19 0
      resources/views/admin/partials/footer.blade.php
  90. 11 9
      resources/views/admin/sso/index.blade.php
  91. 273 0
      resources/views/layouts/app.blade.php
  92. 189 0
      resources/views/psycenter/video.blade.php
  93. 13 0
      routes/api.php
  94. 7 0
      routes/statisticsApi.php

+ 21 - 0
app/Admin/Actions/Activity/Report.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Admin\Actions\Activity;
+
+use Encore\Admin\Actions\Action;
+use Illuminate\Http\Request;
+
+class Report extends Action
+{
+    protected $selector = '.report';
+    public $name = "数据上报";
+
+   
+
+    public function html()
+    {
+        return <<<HTML
+        <a class="btn btn-sm btn-success  report "  href="/admin/activities/create"><i class="fa fa-plus">&nbsp;&nbsp;</i>数据上报</a>
+HTML;
+    }
+}

+ 29 - 0
app/Admin/Controllers/AdminConfigController.php

@@ -0,0 +1,29 @@
+<?php
+namespace App\Admin\Controllers;
+
+use App\Models\AdminConfig;
+use Encore\Admin\Controllers\AdminController;
+use Encore\Admin\Layout\Content;
+use Encore\Admin\Show;
+use Encore\Admin\Widgets\Box;
+
+class AdminConfigController extends AdminController
+{
+    protected $title = '管理后台配置';
+    public function index(Content $content)
+    {
+        $box = new Box('Box标题', 'Box内容');
+
+        $box->removable();
+
+        $box->collapsable();
+
+        $box->style('info');
+
+        $box->solid();
+
+        $box->scrollable();
+
+        return $box;
+    }
+}

+ 78 - 8
app/Admin/Controllers/FourActivityController.php

@@ -1,6 +1,7 @@
 <?php
 namespace App\Admin\Controllers;
 
+use App\Admin\Actions\Activity\Report;
 use App\Admin\Actions\ReportAudit;
 use App\Facades\RemoteSsoFacade;
 use App\Models\FourActivity;
@@ -9,7 +10,11 @@ use Encore\Admin\Facades\Admin;
 use Encore\Admin\Form;
 use Encore\Admin\Grid;
 use Encore\Admin\Show;
+use GuzzleHttp\Psr7\Request as Psr7Request;
 use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Http\Client\Request;
+use Illuminate\Http\Request as HttpRequest;
+use Illuminate\Support\Facades\Request as FacadesRequest;
 
 /**
  * 四进活动相关控制器操作
@@ -20,17 +25,20 @@ class FourActivityController extends AdminController
      * 访问标题
      * @var string
      */
-    protected $title = '四进活动';
+    public  $title = '四进活动';
 
     /**
      * 远程oss首页
      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
      */
-    public function ssoIndex()
+    public function ssoIndex(HttpRequest $request)
     {
-        $indexUrl = "/admin/activities";
+        $url = $request->getRequestUri();
+        $params = explode('?', $url);
+        $indexUrl = "/admin/activity/create";
         $thirdOpenid = Admin::user()->third_openid;
         $menuResult = RemoteSsoFacade::getUserMenuWebsiteData($thirdOpenid);
+        // var_dump($menuResult);exit;
 //        if (isset($menuResult["menu"])){
 //            foreach ($menuResult["menu"] as &$val){
 //                if($val["id"]==1000000000000016){
@@ -38,8 +46,7 @@ class FourActivityController extends AdminController
 //                }
 //            }
 //        }
-
-        return view('admin/sso/index', ['menuResult' => $menuResult,"indexUrl"=>$indexUrl]);
+        return view('admin/sso/index', ['menuResult' => $menuResult,"indexUrl"=>$indexUrl, "paramUrl" => $params[1]]);
     }
     /**
      * Make a grid builder.
@@ -130,6 +137,12 @@ class FourActivityController extends AdminController
 
             }
         });
+        $grid->disableCreateButton();
+        $grid->disableExport();
+        $grid->disableColumnSelector();
+        $grid->tools(function (Grid\Tools $tools) {
+            $tools->append(new Report);
+        });
         
         
 
@@ -172,14 +185,65 @@ class FourActivityController extends AdminController
      *
      * @return Form
      */
-    protected function form()
+    protected function form(HttpRequest $request)
     {
+        var_dump($request->all());exit;
         $form = new Form(new FourActivity());
+        $form->setTitle('工作上报');
         $form->hidden('workstation_id')->value(Admin::user()->workstation_id);
         $form->hidden('workstation_name')->value(Admin::user()->workstation_name);
         $form->radio("type","活动类型")
             ->options(FourActivity::TYPE_MAP)
-            ->when("in",array(FourActivity::TYPE_SCIENCE_POPULARIZATION_BASE,FourActivity::TYPE_PROMOTION_ACTIVITIES),function (Form $form) {
+            ->default(FourActivity::TYPE_SCIENCE_POPULARIZATION_BASE)
+            ->when("=",FourActivity::TYPE_SCIENCE_POPULARIZATION_BASE,function (Form $form) {
+                $form->select('activities_type', __('四进类型'))->options(function(){
+                    return FourActivity::ACTIVITIES_TYPE_MAP;
+                });
+                $form->datetime('active_time', __('活动时间'));
+                $form->text('place', __('活动地点'));
+                $form->select('audience_crowd', __('受众人群'))->options(function(){
+                    return FourActivity::AUDIENCE_CROWD_MAP;
+                });
+                $form->text('title', __('主题'));
+                $form->text('anchor', __('讲者/主持'));
+                $form->text('educational_materials', __('发放宣教材料(份)'));
+                $form->text('publicity_board', __('宣传栏(期)'));
+                $form->text('member_num', __('受宣人次数'));
+                $form->text('planned_number', __('计划人数'));
+                $form->text('actual_number', __('实到人数'));
+                $form->text('filling_people', __('填表人'));
+                $form->datetime('filling_time', __('填表时间'));
+                $form->select('mode', __('方式'))->options(function(){
+                    return FourActivity::MODE_MAP;
+                })->when("in",array(
+                    FourActivity::MODE_HEALTH_EDUCATION_LECTURE,
+                    FourActivity::MODE_TRAIN,
+                    FourActivity::MODE_GROUP_DISCUSSION_AND_DISCUSSION
+                ),function (Form $form) {
+                    //开展讲座、培训,需同步提交通知、课件、签到表及现场照片
+                    $form->html("<p style='color: red'>备注:需同步提交通知、课件、签到表及现场照片</p>");
+                })->when("in",array(
+                    FourActivity::MODE_HEALTH_PROMOTION_ACTIVITIES,
+                ),function (Form $form) {
+                    //开展健康宣传活动,需同步提交活动照片、活动总结
+                    $form->html("<p style='color: red'>备注:需同步提交活动照片、活动总结</p>");
+                })->when("in",array_values(array_diff(array_keys(FourActivity::MODE_MAP),array(
+                        FourActivity::MODE_HEALTH_EDUCATION_LECTURE,
+                        FourActivity::MODE_TRAIN,
+                        FourActivity::MODE_GROUP_DISCUSSION_AND_DISCUSSION,
+                        FourActivity::MODE_HEALTH_PROMOTION_ACTIVITIES
+                    )
+                )),function (Form $form) {
+                    //其它形式活动根据实际情况提交影像资料
+                    $form->html("<p style='color: red'>备注:需要根据实际情况提交影像资料</p>");
+                });
+                //多文件
+                $form->multipleFile('ext', __('附件资料'))->removable();
+                $form->textarea('content', __('活动内容'));
+                $form->html('<div style="color:red"><p>1.开展讲座、培训,需同步提交通知、课件、签到表及现场照片;</p><p>2.开展宣传活动,需同步提交活动照片、活动总结;</p><p>3.其它形式活动根据实际情况提交影像资料。</p></div>','注意事项');
+
+            })
+            ->when("=",FourActivity::TYPE_PROMOTION_ACTIVITIES,function (Form $form) {
                 $form->select('activities_type', __('四进类型'))->options(function(){
                     return FourActivity::ACTIVITIES_TYPE_MAP;
                 });
@@ -224,6 +288,8 @@ class FourActivityController extends AdminController
                 //多文件
                 $form->multipleFile('ext', __('附件资料'))->removable();
                 $form->textarea('content', __('活动内容'));
+                $form->html('<div style="color:red"><p>1.开展讲座、培训,需同步提交通知、课件、签到表及现场照片;</p><p>2.开展宣传活动,需同步提交活动照片、活动总结;</p><p>3.其它形式活动根据实际情况提交影像资料。</p></div>','注意事项');
+
             })
             ->when("=",FourActivity::TYPE_FEATURED_SERVICES,function (Form $form){
                 $form->text('title', __('项目名称'));
@@ -236,6 +302,8 @@ class FourActivityController extends AdminController
                 $form->file('final_report', __('结题报告书'))->removable();
                 $form->html("<a style='color: red' onclick='window.open(this.href); return false;' href='https://cydsyy-api.qingerai.com/vendor/excel/附件3:朝阳区特色项目结题报告书.doc'><button  class='btn btn-warning'>下载结题报告书模版</button></a>");
                 $form->multipleFile('ext', __('其他'))->removable();
+                $form->html('<div style="color:red"><p>1.开展讲座、培训,需同步提交通知、课件、签到表及现场照片;</p><p>2.开展宣传活动,需同步提交活动照片、活动总结;</p><p>3.其它形式活动根据实际情况提交影像资料。</p></div>','注意事项');
+
             })
             ->when("=",FourActivity::TYPE_FEATURED_REPORT_RECEIPT,function (Form $form){
                 $form->text('title', __('回执名称'));
@@ -243,11 +311,14 @@ class FourActivityController extends AdminController
                 $form->file('return_receipt', __('回执单'))->removable();
                 $form->html("<a style='color: red' onclick='window.open(this.href); return false;' href='https://cydsyy-api.qingerai.com/vendor/excel/回执单.xlsx'><button  class='btn btn-warning'>下载回执单模版</button></a>");
                 $form->multipleFile('ext', __('其他'))->removable();
+                $form->html('<div style="color:red"><p>1.开展讲座、培训,需同步提交通知、课件、签到表及现场照片;</p><p>2.开展宣传活动,需同步提交活动照片、活动总结;</p><p>3.其它形式活动根据实际情况提交影像资料。</p></div>','注意事项');
+
             })
             ->required();
         $form->tools(function (Form\Tools $tools) {
             // 去掉`列表`按钮
             $tools->disableList();
+            $tools->add('<a class="btn btn-sm" style="background-color:#13d5e8;color:#fff" href="/admin/activities"><i class="fa fa-list"></i>&nbsp;&nbsp;查看上报</a>');
 
             // // 去掉`删除`按钮
             // $tools->disableDelete();
@@ -256,7 +327,6 @@ class FourActivityController extends AdminController
             // $tools->disableView();
 
         });
-
         return $form;
     }
 }

+ 11 - 3
app/Admin/Controllers/HomeController.php

@@ -8,6 +8,7 @@ use Encore\Admin\Layout\Column;
 use Encore\Admin\Layout\Content;
 use Encore\Admin\Layout\Row;
 use App\Facades\ScaleFacade;
+use Encore\Admin\Facades\Admin;
 
 class HomeController extends Controller
 {
@@ -15,9 +16,16 @@ class HomeController extends Controller
     {
         //同步量表
 //           return ScaleFacade::createThirdScale(200, 200, '');
+        // Admin::js('https://cdn.jsdelivr.net/npm/vue@2');
+        // Admin::js('https://cdn.staticfile.net/jquery/2.1.4/jquery.min.js');
+        // Admin::js('https://code.highcharts.com/highcharts.js');
+        // Admin::js('https://unpkg.com/element-ui/lib/index.js');
+
+        // Admin::css('https://unpkg.com/element-ui/lib/theme-chalk/index.css');
+        // Admin::css('/assets/css/common.css');
         return $content
-            ->title('欢迎页')
-            ->description('')
-            ;
+            ->title('页')
+            ->description('统计')
+            ->view('admin/partials/dashboard');
     }
 }

+ 74 - 0
app/Admin/Controllers/NewMediaCountController.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Models\NewMediaAccount;
+use App\Models\NewMediaCount;
+use Encore\Admin\Controllers\AdminController;
+use Encore\Admin\Form;
+use Encore\Admin\Grid;
+use Encore\Admin\Show;
+
+
+class NewMediaCountController extends AdminController
+{
+    /**
+     * Title for current resource.
+     *
+     * @var string
+     */
+    protected $title = '新媒体账号播放统计';
+    /**
+     * Make a grid builder.
+     *
+     * @return Grid
+     */
+    protected function grid()
+    {
+        $grid = new Grid(new NewMediaCount());
+
+        $grid->column('id', __('Id'));
+        $grid->column('day', __('月份'));
+        $grid->column('num', __('总计播放量'));
+        $grid->column('created_at', __('导入时间'));
+       
+        return $grid;
+    }
+
+    /**
+     * Make a show builder.
+     *
+     * @param mixed $id
+     * @return Show
+     */
+    protected function detail($id)
+    {
+        $show = new Show(NewMediaAccount::findOrFail($id));
+
+        $show->field('id', __('Id'));
+        $show->field('account_name', __('账号名称'));
+        $show->field('account_platform', __('账号平台'))->using(NewMediaAccount::PLATFORM_MAP);
+        $show->field('fans_num', __('粉丝数'));
+        $show->field('created_at', __('Created at'));
+        $show->field('updated_at', __('Updated at'));
+
+        return $show;
+    }
+
+    /**
+     * Make a form builder.
+     *
+     * @return Form
+     */
+    protected function form()
+    {
+        $form = new Form(new NewMediaCount());
+
+        $form->select('year', __('年份'))->options(array_combine(range(2024,date('Y')), range(2024,date('Y'))));
+        $form->select('month','月份')->options(array_combine(range(1,12), range(1,12)));
+        $form->file('count_file','统计文件')->rules('mimes:xlsx');
+        
+        $form->keyValue('count_data','统计数据');
+        return $form;
+    }
+}

+ 1 - 0
app/Admin/Controllers/ScaleCategoryController.php

@@ -88,6 +88,7 @@ class ScaleCategoryController extends AdminController
         $form = new Form(new ScaleCategory());
         $form->text('name', __('分类名称'));
         $form->text('info', __('分类首页简介'));
+        $form->select('pid', __('上级分类'))->options(ScaleCategory::where('deleted_at', null)->pluck('name', 'id'))->default(0);
         $form->text('list_info', __('分类列表简介'));
         $form->image('pic', __('首页图片'));
         $form->image('pic2', __('分类图片'));

+ 27 - 2
app/Admin/Controllers/ScaleController.php

@@ -9,6 +9,7 @@ use Encore\Admin\Grid;
 use Encore\Admin\Show;
 use App\Models\ScaleCategory;
 use App\Models\ThirdScale;
+use Illuminate\Http\Request;
 
 class ScaleController extends AdminController
 {
@@ -38,6 +39,15 @@ class ScaleController extends AdminController
             }
             return '';
         });
+        $grid->column('second_id', __('二级分类'))->display(function ($pid) {
+            if ($pid>0){
+                $scaleCategoryData = ScaleCategory::find($pid);
+                if ($scaleCategoryData){
+                    return  $scaleCategoryData->name;
+                }
+            }
+            return '';
+        });
         $grid->column('title', __('标题'));
         $grid->column('time', __('测试时间'));
         $grid->column('suitable_for_the_crowd', __('适合人群'));
@@ -93,8 +103,17 @@ class ScaleController extends AdminController
 
         $thirdScale = ThirdScale::get(['id', 'title as name'])->pluck('name','id');
         $form->select('third_id', __('远程量表'))->options($thirdScale)->required();
-        $cloumns = ScaleCategory::get(['id','name'])->pluck('name','id');
-        $form->select('category_id', __('分类'))->options($cloumns)->required();
+        $cloumns = ScaleCategory::where('pid', 0)->where('deleted_at',null)->get(['id','name'])->pluck('name','id');
+        $form->select('category_id', __('分类'))->options($cloumns)->rules("required")->load('second_id', '/admin/scale_second_category');
+        $second = [];
+        if($form->isEditing()){
+            $model = Scale::find(request()->route()->parameters()['scale']);
+            if($model) {
+                $second = ScaleCategory::where('pid','=',$model->category_id)->where('deleted_at',null)->get(['id','name'])->pluck('name','id');
+
+            }
+        }
+        $form->select('second_id', __('二级分类'))->options($second);;
         $form->text('title', __('标题'))->required();
         $form->text('subtitle', __('副标题'))->required();
         $form->image('pic', __('图片'))->required();
@@ -110,4 +129,10 @@ class ScaleController extends AdminController
 
         return $form;
     }
+
+    public function getSecondCategory(Request $request )
+    {
+        $pid = $request->input('q');
+        return ScaleCategory::where('pid', $pid)->select('name as text','id')->get()->toArray();
+    }
 }

+ 2 - 1
app/Admin/bootstrap.php

@@ -27,4 +27,5 @@ use Encore\Admin\Admin;
 // Encore\Admin\Form::extend('wangeditor', WangEditor::class);
 // Encore\Admin\Form::extend('largefile', \Encore\LargeFileUpload\LargeFileField::class);
 // Encore\Admin\Form::extend('chunk_file', \Encore\ChunkFileUpload\ChunkFileField::class);
-Admin::js('https://cdn.bootcss.com/vue/2.6.10/vue.min.js');
+app('view')->prependNamespace('admin', resource_path('views/admin'));
+// Admin::js('https://cdn.bootcss.com/vue/2.6.10/vue.min.js');

+ 16 - 1
app/Admin/routes.php

@@ -1,5 +1,6 @@
 <?php
 
+use App\Admin\Controllers\FourActivityController;
 use App\Admin\Controllers\OpenWeixinController;
 use App\Admin\Controllers\RemoteSsoController;
 use EasyWeChat\Factory;
@@ -54,6 +55,7 @@ Route::group([
     //量表分类
     $router->resource('scale-categories', 'ScaleCategoryController');
     //量表
+    $router->get('/scale_second_category', 'ScaleController@getSecondCategory');
     $router->resource('scales', 'ScaleController');
     //系统配制
     $router->resource('sys-configs', 'SysConfigController');
@@ -67,12 +69,25 @@ Route::group([
     $router->resource('chat', 'ChatController');
     //远程授权登录
     $router->get('remoteSso', "RemoteSsoController@login");
-    $router->get('ssoIndex', "FourActivityController@ssoIndex");
+    $router->get('admin_config', "AdminConfigController@index");
 //     $router->post('send_im_message', 'SendImMessageController@sendImMessage')->name('send_im_message');
 //     $router->get('get_im_message_list', 'SendImMessageController@getImMessageList')->name('get_im_message_list');
 //     $router->get('get_im_message_detail', 'SendImMessageController@getImMessageDetail')->name('get_im_message_detail');
 });
 
+// Route::group([
+//     'prefix'        => config('admin.route.prefix'),
+//     'namespace'     => config('admin.route.namespace'),
+//     'middleware'    => ['psyUserSso'],
+//     'as'            => config('admin.route.prefix') . '.',
+// ], function (Router $router) {
+//     $router->resource('activities', 'FourActivityController');
+
+// });
+
+Route::get('/admin/ssoIndex', [FourActivityController::class, 'ssoIndex'])->middleware('psyUserSso');
+Route::resource('/admin/activity', FourActivityController::class)->middleware(['psyUserSso','web']);
+
 //测试
 Route::get('admin/openweixin/index', OpenWeixinController::class.'@index');
 Route::post('admin/openweixin/index', OpenWeixinController::class.'@index');

+ 11 - 0
app/Facades/DashbordFacade.php

@@ -0,0 +1,11 @@
+<?php
+namespace App\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class DashbordFacade extends Facade
+{
+    protected static function getFacadeAccessor(){
+        return 'DashboardFacadeRepository';
+    }
+}

+ 12 - 0
app/Facades/SmsFacade.php

@@ -0,0 +1,12 @@
+<?php
+namespace App\Facades;
+
+use Illuminate\Support\Facades\Facade;
+
+class SmsFacade extends Facade
+{
+    protected static function getFacadeAccessor()
+    {
+        return 'AliSmsFacadeRepository';
+    }
+}

+ 112 - 0
app/Http/Controllers/Api/V1/DashboardController.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Http\Controllers\Api\V1;
+
+use App\Facades\DashbordFacade;
+use App\Facades\RemoteSsoFacade;
+use App\Http\Controllers\Controller;
+use App\Http\Requests\DashboardReportRequest;
+use App\Models\FourActivity;
+use App\Models\SpecialistInfo;
+use Illuminate\Http\Request;
+
+class DashboardController extends Controller
+{
+    
+    /**
+     * 获取时间段内注册用户统计(默认最近七天)
+     */
+    public function getPeriodRegisiter(Request $request) 
+    {
+        $startTime = $request->input('start_time', date('Y-m-d', strtotime('-1 week')));
+        $endTime = $request->input('end_time', date('Y-m-d'));
+        $dateType = $request->input('date_type', 0);
+        return response()->json([
+            'data' =>DashbordFacade::getPeriodRegisiter($startTime, $endTime, $dateType),
+            'code' => 200,
+            'message' => 'success'
+        ]);
+    }
+    //
+    /**
+     * 视频统计
+     * @return \Illuminate\Http\Response
+     */
+
+    public function videoCount()
+    {
+        $videoCount = SpecialistInfo::where('deleted_at',null)->count();
+        $viewsTotal = SpecialistInfo::where('deleted_at',null)->sum('number_of_studies');
+        return response()->json([
+            'data' =>[
+                'video_count' => $videoCount,
+                'views' => $viewsTotal,
+            ],
+            'code' => 200,
+            'message' => 'success'
+        ]);
+        
+       
+    }
+    public function video(Request $request)
+    {
+        $sortable = $request->input('sortable', 'latest');
+        $query = SpecialistInfo::where('deleted_at',null)->select('id','name', 'first_id', 'second_id', 'cover','number_of_studies as views','created_at');
+        switch ($sortable) {
+            case 'latest':
+                $query->orderBy('created_at', 'desc');
+                break;
+            case 'hot':
+                $query->orderBy('views', 'desc');
+                break;
+            default:
+                $query->orderBy('id', 'desc');
+                break;
+        }
+        return response()->json([
+            'data' => $query->limit(3)->get(),
+            'code' => 200,
+            'message' => 'success'
+        ]);
+    }
+
+    public function report(DashboardReportRequest $request)
+    {
+        $period = $request->input('period', '7days');
+        $workstationId = $request->input('workstation_id', '');
+        $startTime = strtotime('-'.$period);
+        $query = FourActivity::where('deleted_at',null)->where('created_at','>=',date('Y-m-d', $startTime));
+        $ret = [];
+
+        //初始化活动类型数据
+        $index = array_values(FourActivity::TYPE_MAP);
+        $fillValue = array_fill(0, count(FourActivity::TYPE_MAP), 0);
+
+        while ($startTime < time()) {
+            $ret[date('Y-m-d', $startTime)] = [
+                'day' => date('Y-m-d', $startTime),
+                'y_field' => $index,
+                'y_data' => $fillValue
+            ];
+            $startTime += 86400;
+        }
+        
+        $childWorkstation = RemoteSsoFacade::getChildWorksation($workstationId);
+        if($childWorkstation){
+            $ocIdArr = array_column($childWorkstation,'open_oc_id');
+            array_push($ocIdArr, $workstationId);
+            $query->whereIn('workstation_id',$ocIdArr);
+        
+            $data = $query->select('id','created_at','type')->get()->toArray();
+            if($data) {
+                foreach ($data as $item) {
+                    $date = date('Y-m-d', strtotime($item['created_at']));
+                    if (isset($ret[$date])) {
+                        $ret[$date]['y_data'][$item['type']-1]++;
+                    }
+                }
+            }
+        }
+        return response()->json(array_values($ret));
+    }
+}

+ 21 - 0
app/Http/Controllers/Api/V1/MpUser/MpUserController.php

@@ -23,6 +23,27 @@ class MpUserController extends Controller
         return UserFacade::wxMPLogin($code);
     }
 
+    public function sendSms(Request $request)
+    {
+        $mobile = request()->input('mobile');
+        if(!$mobile) {
+            return response()->json(['code'=> 9999, 'msg'=> '手机号不能为空']);
+        }
+        return UserFacade::sendSms($mobile);
+    }
+    public function bindMobile(Request $request)
+    {
+        $mobile = request()->input('mobile');
+        $code = request()->input('code');
+        if(!$mobile) {
+            return response()->json(['code'=> 9999, 'msg'=> '手机号不能为空']);
+        }
+        if(!$code) {
+            return response()->json(['code'=> 9999, 'msg'=> '验证码不能为空']);
+        }
+        return UserFacade::bindMobile($mobile, $code);
+    }
+
     /**
      * Store a newly created resource in storage.
      *

+ 223 - 0
app/Http/Controllers/StatisticsApi/IndexController.php

@@ -0,0 +1,223 @@
+<?php
+
+namespace App\Http\Controllers\StatisticsApi;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use App\Models\User;
+use App\Models\BrowseRecord;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Artisan;
+
+class IndexController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        //关注人数
+        $users = User::count();
+        
+        //测评总人次
+        $cp = $this->_cp();
+        
+        //科普总人次
+        $kp = $this->_kp();
+        
+        //训练总人次
+        $xl = $this->_xl();
+        
+        //挂号总人次
+        $gh = $this->_gh();
+        
+        //测评人次统计
+        $cpTj = $this->_cpTj(request('cp_type', 1), 1);
+        
+        //科普人次统计
+        $kpTj = $this->_cpTj(request('kp_type', 1), 2);
+        
+        //训练人次统计
+        $xlTj = $this->_cpTj(request('xl_type', 1), 3);
+        
+        $result = [
+            'years' => $this->_getYearArr(),
+            'users' => $users,
+            'cp' => $cp,
+            'kp' => $kp,
+            'xl' => $xl,
+            'gh' => $gh,
+            'cp_tj' => $cpTj,
+            'kp_tj' => $kpTj,
+            'xl_tj' => $xlTj
+        ];
+        return $result;
+    }
+    
+    /**
+     * 测评人次统计
+     *
+     * @param int $id 1:近7天 其它:具体的一年
+     * @return []
+     */
+    private function _cpTj($id,$column=1) {
+        $categories = $this->_getTjCategory($id);
+        
+        return [
+            'categories'    => $categories,
+            'series'        => $this->_getTjSeries($column, $id, $categories)
+        ];
+    }
+    
+    /**
+     * 获取统计数据
+     *
+     * @param   int $column 1.测评人次统计 2.科普人次统计 3.训练人次统计
+     * @param   int $type   1:近7天 其它:具体的一年
+     * @param   array $categories 日期 近七天还是月
+     * @return  []
+     */
+    
+    private function _getTjSeries(int $column, int $type, $categories) {
+        $result = [];
+        
+        $funcName = $this->_getTypeFunc($column);
+        
+        foreach ($categories as $k=>$v){
+            if($type ===1){
+                $getDay = strtotime($v);
+                $start = date("Y-m-d 00:00:00" ,$getDay);
+                $end_day = date("Y-m-d 23:59:59" ,$getDay);
+                $nums =  $this->$funcName([$start,$end_day]);
+            }else{
+                $times = $this->_trunDate($v);
+                $nums =  $this->$funcName($times);
+            }
+            $result[$k] = $nums;
+        }
+        return $result;
+    }
+    
+    private function _getTypeFunc($column){
+        switch ($column){
+            case 1://
+                $funcName="_cp" ;
+                break;
+            case 2://
+                $funcName="_kp" ;
+                break;
+            case 3://
+                $funcName="_xl" ;
+                break;
+            default :$funcName="_cp" ;break;
+        }
+        
+        return $funcName;
+    }
+    /*
+     * 月份转换时间
+     * */
+    private function _trunDate($getMonth){
+        $UpNum =['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二'];
+        $num = ['01','02','03','04','05','06','07','08','09','10','11',"12"];
+        $numMonth =mb_substr($getMonth,0,-1);
+        // var_dump($numMonth);
+        foreach ($UpNum as $k=>$v){
+            if($v===$numMonth){
+                $month = $num[$k];
+            }
+        }
+        //            $monthString = $month;
+        $year = date('Y');// 获取当前年份
+        //            $month = date('m', strtotime($monthString));// 将月份字符串转换为对应的数字
+        $startDate = $year . '-' . $month . '-01 00:00:00';
+        $endDay= date("Y-m-d 00:00:00",strtotime('+1 month', strtotime($startDate .'-01')));
+        return [$startDate,$endDay];
+        
+    }
+    
+    /**
+     * 测评总人次
+     *
+     */
+    private function _cp($time_arr =null) {
+        if($time_arr ===null){
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_1)->count();
+        }else{
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_1)->whereBetween("created_at",$time_arr)->count();
+        }
+    }
+    
+    /**
+     * 科普总人次
+     *
+     */
+    private function _kp($time_arr =null) {
+        
+        if($time_arr ===null){
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_3)->count();
+        }else{
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_3)->whereBetween("created_at",$time_arr)->count();
+        }
+    }
+    
+    /**
+     * 训练总人次
+     *
+     */
+    private function _xl($time_arr =null) {
+        //        return BrowseRecord::where('column', BrowseRecord::COLUMN_2)->count();
+        if($time_arr ===null){
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_2)->count();
+        }else{
+            return BrowseRecord::where('column', BrowseRecord::COLUMN_2)->whereBetween("created_at",$time_arr)->count();
+        }
+    }
+    
+    /**
+     * 挂号总人次
+     *
+     */
+    private function _gh() {
+        return 185;
+    }
+    
+    /**
+     * 数据的x轴日期的处理
+     *
+     * @param int $type 1:近7日 2:年
+     * @return []
+     */
+    private function _getTjCategory(int $type) {
+        
+        $result = [];
+        if ($type === 1) {
+            for ($i = 7; $i >= 0; $i--) {
+                $result[] = Carbon::now()->subDays($i)->toDateString();
+            }
+        }else{
+            $result =  ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
+        }
+        
+        return $result;
+    }
+    
+    /**
+     * 获取近7日和历年
+     *
+     * @return []
+     */
+    private function _getYearArr() {
+        $startYear = 2023;
+        $nowYear = date('Y');
+        
+        $yearArr = [['id'=>1, 'name'=>'近7日']];
+        for ($i = $nowYear; $i >= $startYear; $i--) {
+            $yearArr[] = ['id'=>$i, 'name'=>$i.'年'];
+        }
+        
+        return $yearArr;
+    }
+}

+ 91 - 0
app/Http/Controllers/Web/VideoCategoryController.php

@@ -0,0 +1,91 @@
+<?php
+
+namespace App\Http\Controllers\Web;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\CategoryRequest;
+use App\Models\Video;
+use App\Models\VideoCategory;
+use Illuminate\Http\Request;
+
+class VideoCategoryController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CategoryRequest $request)
+    {
+        //
+        $request->merge(['pid'=>0]);
+        $request->merge(['is_show'=>1]);
+        $category = new VideoCategory();
+        if ($request->has('id')){
+            $category = VideoCategory::find($request->input('id'));
+            $result = $category->fill($request->all());
+            $res = $result->save();
+        }else{
+            $result = $category->fill($request->all());
+            $res = $result->save();
+        }
+        return $res ? $this->success($result) : $this->failed('保存失败');
+        
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+        VideoCategory::where('id', $id)->firstOrFail();
+        $result = Video::where('first_id', $id)->where('deleted_at', null)->where('platform', 'psy_center')->first();
+        if($result) {
+            return $this->failed('该分类下有视频,请先删除视频');
+        }
+        $res = VideoCategory::destroy($id);
+        if($res) {
+            return $this->success('');
+        }else {
+            return $this->failed('删除失败');
+        }
+    }
+}

+ 73 - 0
app/Http/Controllers/Web/VideoController.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Http\Controllers\Web;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\VideoRequest;
+use App\Models\Video;
+use App\Models\VideoCategory;
+use App\Services\VideoService;
+use Illuminate\Http\Request;
+
+class VideoController extends Controller
+{
+    //
+    public function index(Request $request, VideoService $service){        
+        $category = VideoCategory::select('id', 'name','is_show','rank')->where('is_show', 1)->where('pid', 0)->orderBy('rank','DESC')->get()->toArray();
+        
+        $condition['keywords'] = $request->input('keywords', '');
+        $condition['category_id'] = $request->input('category_id', 0);
+        // $condition['platform'] = 'psy_center';
+        $condition['type'] = 3;
+        $condition['platform'] = 'psy_center';
+        $video = $service->getList($condition);
+        // var_dump($video);exit;
+        // print_r($category);exit;
+
+        return view('psycenter.video', ['categories'=>$category, 'video'=>$video,'condition'=>$condition, 'title'=>'视频']);
+    }
+    public function store(VideoRequest $request)
+    {
+    
+        $category = new Video();
+        $request->merge(['type' => 3]);
+        $request->merge(['platform'=>'psy_center']);
+        if ($request->has('id')){
+            $category = Video::find($request->input('id'));
+            $result = $category->fill($request->all());
+            $res = $result->save();
+        }else{
+            $result = $category->fill($request->all());
+            $res = $result->save();
+        }
+        return $res ? $this->success($result) : $this->failed('保存失败');
+    }
+
+    public function show($id)
+    {
+        $video = Video::where('id', $id)->firstOrFail();
+        return $this->success($video);
+    }
+    public function destroy($id)
+    {
+    
+        Video::where('id', $id)->firstOrFail();
+        $res = Video::destroy($id);
+        if($res) {
+            return $this->success('');
+        }else {
+            return $this->failed('删除失败');
+        }
+    }
+
+    public function delete()
+    {
+        $ids = request()->input('ids');
+        $res = Video::whereIn('id', $ids)->delete();
+        if($res) {
+            return $this->success('');
+        }else {
+            return $this->failed('删除失败');
+        }
+    }
+}

+ 148 - 0
app/Http/Helpers/ApiResponse.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace App\Http\Helpers;
+
+use Symfony\Component\HttpFoundation\Response as FoundationResponse;
+
+trait ApiResponse
+{
+    /**
+     * @var int
+     */
+    protected $statusCode = FoundationResponse::HTTP_OK;
+    protected $token = '';
+
+    /**
+     * @return mixed
+     */
+    public function getStatusCode()
+    {
+        return $this->statusCode;
+    }
+
+    /**
+     * @param $statusCode
+     * @return $this
+     */
+    public function setStatusCode($statusCode)
+    {
+        $this->statusCode = $statusCode;
+        return $this;
+    }
+
+    /**
+     * @param $token
+     * @return $this
+     */
+    public function setToken($token)
+    {
+        $this->token = $token;
+        return $this;
+    }
+
+    /**
+     * @param $data
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function respond($data)
+    {
+        $response = response()->json($data, $this->getStatusCode());
+        if ($this->token) {
+            $response->headers->set('Authorization', 'Bearer ' . $this->token);
+        }
+        return $response;
+    }
+
+    /**
+     * @param $status
+     * @param array $data
+     * @param null $code
+     * @return mixed
+     */
+    public function status($status, array $data, $code = null)
+    {
+        if ($code) {
+            $this->setStatusCode($code);
+        }
+        $status = [
+            'status' => $status,
+            'code' => $this->statusCode
+        ];
+
+        $data = array_merge($status, $data);
+        return $this->respond($data);
+    }
+
+    /**
+     * @param $message
+     * @param int $code
+     * @param string $status
+     * @return mixed
+     */
+    /*
+     * 格式
+     * data:
+     *  code:422
+     *  message:xxx
+     *  status:'error'
+     */
+    public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST, $status = 'error')
+    {
+        return $this->setStatusCode($code)->message($message, $status);
+    }
+
+    /**
+     * @param $message
+     * @param string $status
+     * @return mixed
+     */
+    public function message($message, $status = "success")
+    {
+        if(!is_array($message)) {
+            $message = [$message];
+        }
+        return $this->status($status, [
+            'message' => $message
+        ]);
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function internalError($message = "Internal Error!")
+    {
+        return $this->failed($message, FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function created($message = "created")
+    {
+        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
+            ->message($message);
+    }
+
+    /**
+     * @param $data
+     * @param string $status
+     * @return mixed
+     */
+    public function success($data, $status = "success")
+    {
+        return $this->status($status, compact('data'));
+    }
+
+    /**
+     * @param string $message
+     * @return mixed
+     */
+    public function notFond($message = 'Not Fond!')
+    {
+        return $this->failed($message, Foundationresponse::HTTP_NOT_FOUND);
+    }
+}
+
+

+ 122 - 0
app/Http/Helpers/ExceptionReport.php

@@ -0,0 +1,122 @@
+<?php
+
+namespace App\Http\Helpers;
+
+use App\Exceptions\IsAlreadyException;
+use App\Exceptions\MeetExpiredException;
+use App\Exceptions\MeetNoPermissionException;
+use App\Exceptions\MeetNotStartException;
+use ErrorException;
+use Exception;
+use Illuminate\Auth\Access\AuthorizationException;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
+use Illuminate\Database\QueryException;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
+use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
+use Tymon\JWTAuth\Exceptions\TokenInvalidException;
+
+class ExceptionReport
+{
+    use ApiResponse;
+
+    /**
+     * @var Exception
+     */
+    public $exception;
+    /**
+     * @var Request
+     */
+    public $request;
+
+    /**
+     * @var
+     */
+    protected $report;
+
+    /**
+     * ExceptionReport constructor.
+     * @param Request $request
+     * @param Exception $exception
+     */
+    function __construct(Request $request, Exception $exception)
+    {
+        $this->request = $request;
+        $this->exception = $exception;
+    }
+
+    /**
+     * @var array
+     */
+    //当抛出这些异常时,可以使用我们定义的错误信息与HTTP状态码
+    //可以把常见异常放在这里
+    public $doReport = [
+        AuthenticationException::class => ['未登录或登录状态失效', 401],
+        ModelNotFoundException::class => ['数据不存在', 404],
+        AuthorizationException::class => ['没有此权限', 403],
+        ValidationException::class => [],
+        UnauthorizedHttpException::class => ['未登录或登录状态失效', 401],
+        TokenInvalidException::class => ['未登录或登录状态失效', 401],
+        NotFoundHttpException::class => ['没有找到该页面', 404],
+        MethodNotAllowedHttpException::class => ['访问方式不正确', 405],
+        ErrorException::class => ['服务器内部错误', 500],
+        QueryException::class => ['参数错误', 500],   
+        IsAlreadyException::class => ['数据已经存在', 500],   
+        MeetNotStartException::class => ['讲座未开始', 500],   
+        MeetExpiredException::class => ['讲座已结束', 500],   
+        MeetNoPermissionException::class => ['您没有权限参加该讲座', 500],   
+    ];
+
+    public function register($className, callable $callback)
+    {
+
+        $this->doReport[$className] = $callback;
+    }
+
+    /**
+     * @return bool
+     */
+    public function shouldReturn()
+    {
+        foreach (array_keys($this->doReport) as $report) {
+            if ($this->exception instanceof $report) {
+                $this->report = $report;
+                return true;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * @param Exception $e
+     * @return static
+     */
+    public static function make(Exception $e)
+    {
+
+        return new static(\request(), $e);
+    }
+
+    /**
+     * @return mixed
+     */
+    public function report()
+    {
+        if ($this->exception instanceof ValidationException) {
+            return $this->failed(current($this->exception->errors()), $this->exception->status);
+        }
+        $message = $this->doReport[$this->report];
+        return $this->failed($message[0], $message[1]);
+    }
+
+    public function prodReport()
+    {
+        return $this->failed('服务器错误', '500');
+    }
+}
+

+ 65 - 0
app/Http/Helpers/Tencent/MediaTencent.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/25  14:09.
+ * Tencent VOD SDK.
+ * 目前主要用于点播平台文件删除.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+use App\Services\TencentRequestService;
+use TencentCloud\Common\Credential;
+use TencentCloud\Common\Exception\TencentCloudSDKException;
+use TencentCloud\Common\Profile\ClientProfile;
+use TencentCloud\Common\Profile\HttpProfile;
+use TencentCloud\Vod\V20180717\VodClient;
+
+class MediaTencent
+{
+    public string $secretId;
+    public string $secretKey;
+    public string $region;
+    public string $endpoint = 'vod.tencentcloudapi.com';
+    public object $client;
+    public int $sdkAppId;
+
+    public array $resultArr = ['code' => 200, 'msg' => 'success', 'data' => []];
+
+    public function __construct($action, $params)
+    {
+        $this->secretId = config('tencent.tencent_cloud_secret_id');
+        $this->secretKey = config('tencent.tencent_cloud_secret_key');
+        $this->region = config('tencent.trtc.region');
+        $this->sdkAppId = (int) config('tencent.trtc.app_id');
+
+        try {
+            $cred = new Credential($this->secretId, $this->secretKey);
+            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+            $httpProfile = new HttpProfile();
+            $httpProfile->setEndpoint($this->endpoint);
+
+            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+            $clientProfile = new ClientProfile();
+            $clientProfile->setHttpProfile($httpProfile);
+            // 实例化要请求产品的client对象,clientProfile是可选的
+            $this->client = new VodClient($cred, $this->region, $clientProfile);
+
+            // 实例化一个请求对象,每个接口都会对应一个request对象
+            $respClass = 'TencentCloud\\'.ucfirst('vod').'\\V20180717\\Models\\'.ucfirst($action).'Request';
+            $req = new $respClass();
+            $req->fromJsonString(json_encode($params));
+            // 返回的resp是一个RemoveUserResponse的实例,与请求对象对应
+            $resp = $this->client->{$action}($req);
+
+            $this->resultArr['data'] = json_decode($resp->toJsonString(), true);
+        } catch (TencentCloudSDKException $e) {
+            $this->resultArr['code'] = -1;
+            $this->resultArr['msg'] = $e->getMessage();
+        }
+
+        // 记录日志
+        TencentRequestService::create('vod', $action, $params, $this->resultArr);
+    }
+}

+ 90 - 0
app/Http/Helpers/Tencent/RoomTencent.php

@@ -0,0 +1,90 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/6  10:35.
+ * Tencent ROOM SDK.
+ * 目前主要用于判断房间是否存在.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+use App\Services\TencentRequestService;
+use Illuminate\Support\Facades\Log;
+use Random\RandomException;
+
+class RoomTencent
+{
+    public const GET_ROOM_INFO = 'get_room_info';
+
+    public string $endpoint = 'https://console.tim.qq.com/v4/room_engine_http_srv/';
+    public int $sdkAppId;
+    public string $identifier;
+    public string $action;
+    public array $resultArr = ['code' => 200, 'msg' => 'success', 'data' => []];
+
+    /**
+     * @param mixed $action
+     *
+     * @throws RandomException
+     */
+    public function __construct($action)
+    {
+        $this->sdkAppId = config('tencent.trtc.app_id');
+        $this->identifier = config('tencent.trtc.admin');
+        $this->action = $action;
+        $sign = new SignTencent();
+        $userSig = $sign->genUserSign($this->identifier);
+
+        $query = [
+            'sdkappid' => $this->sdkAppId,
+            'identifier' => $this->identifier,
+            'usersig' => $userSig,
+            'random' => random_int(1, 9999),
+            'contenttype' => 'json',
+        ];
+
+        $this->endpoint .= $action.'?'.http_build_query($query);
+    }
+
+    public function getRoomInfo($roomId): void
+    {
+        $params = [
+            'RoomId' => (string) $roomId,
+        ];
+        $payload = json_encode($params);
+        $this->httpRequest($this->endpoint, $payload);
+    }
+
+    public function httpRequest($url, $payload): void
+    {
+        try {
+            $ch = curl_init();
+            curl_setopt($ch, CURLOPT_URL, $url);
+            curl_setopt($ch, CURLOPT_POST, true);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
+            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+            $response = curl_exec($ch);
+            curl_close($ch);
+
+            $res = json_decode($response, true);
+
+            Log::channel('tencent')->info('response: '.$response);
+
+            if (0 !== $res['ErrorCode']) {
+                $this->resultArr['code'] = $res['ErrorCode'];
+                $this->resultArr['msg'] = $res['ErrorInfo'];
+            } else {
+                if (isset($res['Response'])) {
+                    $this->resultArr['data'] = $res['Response'];
+                }
+            }
+        } catch (\Exception $err) {
+            $this->resultArr['code'] = -1;
+            $this->resultArr['msg'] = $err->getMessage();
+        }
+
+        // 记录日志
+        // TencentRequestService::create('room', $this->action, $payload, $this->resultArr);
+    }
+}

+ 75 - 0
app/Http/Helpers/Tencent/SignTencent.php

@@ -0,0 +1,75 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/5  11:47.
+ * Tencent SDK.
+ * 目前主要用于获取用户签名.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+use Tencent\TLSSigAPIv2;
+
+class SignTencent
+{
+    private int $sdkAppId;
+    private string $sdkSecretKey;
+
+    public function __construct()
+    {
+        $this->sdkAppId = config('tencent.trtc.app_id');
+        $this->sdkSecretKey = config('tencent.trtc.secret_key');
+    }
+
+    // 签名
+    public function genUserSign($identifier, $expire = 86400 * 180): string
+    {
+        try {
+            $api = new TLSSigAPIv2($this->sdkAppId, $this->sdkSecretKey);
+            return $api->genUserSig($identifier, $expire);
+        } catch (\Exception $e) {
+            echo $e;
+        }
+    }
+
+    /**
+     * userbuf签名.
+     */
+    public function genSigWithUserBuf($identifier, $expire, $userbuf): string
+    {
+        try {
+            $api = new TLSSigAPIv2($this->sdkAppId, $this->sdkSecretKey);
+
+            return $api->genPrivateMapKeyWithStringRoomID($identifier, $expire,'', $userbuf);
+        } catch (\Exception $e) {
+            echo $e;
+        }
+    }
+
+    public function verifySigWithUserBuf($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg)
+    {
+        try {
+            $api = new TLSSigAPIv2($this->sdkAppId, $this->sdkSecretKey);
+
+            return $api->verifySigWithUserBuf($sig, $identifier, $init_time, $expire_time, $userbuf, $error_msg);
+        } catch (\Exception $e) {
+            echo $e;
+        }
+    }
+
+    /**
+     * 批量用户生成签名.
+     *
+     * @return array
+     */
+    public function genUserSigns($userIds, $expire)
+    {
+        $retData = [];
+        foreach ($userIds as $id) {
+            $retData[$id] = $this->genUserSign($id, $expire);
+        }
+
+        return $retData;
+    }
+}

+ 84 - 0
app/Http/Helpers/Tencent/StsTencent.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/28  下午2:12.
+ * Tencent SDK.
+ * 目前主要用于获取联合身份临时访问凭证.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+use App\Services\TencentRequestService;
+use TencentCloud\Common\Credential;
+use TencentCloud\Common\Exception\TencentCloudSDKException;
+use TencentCloud\Common\Profile\ClientProfile;
+use TencentCloud\Common\Profile\HttpProfile;
+use TencentCloud\Sts\V20180813\StsClient;
+
+class StsTencent
+{
+    public string $secretId;
+    public string $secretKey;
+    public string $region;
+    public string $endpoint = 'sts.tencentcloudapi.com';
+    public string $version = '2018-08-13';
+    public object $client;
+    public int $sdkAppId;
+    public array $policy = [
+        'version' => '3.0',
+        'statement' => [
+            [
+                'action' => 'sts:*',
+                'effect' => 'allow',
+                'resource' => '*',
+            ],
+            [
+                'action' => 'asr:*',
+                'effect' => 'allow',
+                'resource' => '*',
+            ],
+        ],
+    ];
+
+    public array $resultArr = ['code' => 200, 'msg' => 'success', 'data' => []];
+
+    public function __construct($action, $params)
+    {
+        $this->secretId = config('tencent.tencent_cloud_secret_id');
+        $this->secretKey = config('tencent.tencent_cloud_secret_key');
+        $this->region = config('tencent.trtc.region');
+        $this->sdkAppId = (int) config('tencent.trtc.app_id');
+
+        try {
+            $cred = new Credential($this->secretId, $this->secretKey);
+            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+            $httpProfile = new HttpProfile();
+            $httpProfile->setEndpoint($this->endpoint);
+
+            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+            $clientProfile = new ClientProfile();
+            $clientProfile->setHttpProfile($httpProfile);
+            // 实例化要请求产品的client对象,clientProfile是可选的
+            $this->client = new StsClient($cred, $this->region, $clientProfile);
+
+            $params['Policy'] = urlencode(json_encode($this->policy, JSON_UNESCAPED_UNICODE));
+            $params['DurationSeconds'] = 7200;
+
+            // 实例化一个请求对象,每个接口都会对应一个request对象
+            $respClass = 'TencentCloud\\'.ucfirst('sts').'\\V20180813\\Models\\'.ucfirst($action).'Request';
+            $req = new $respClass();
+            $req->fromJsonString(json_encode($params));
+            // 返回的resp是一个RemoveUserResponse的实例,与请求对象对应
+            $resp = $this->client->{$action}($req);
+
+            $this->resultArr['data'] = json_decode($resp->toJsonString(), true);
+        } catch (TencentCloudSDKException $e) {
+            $this->resultArr['code'] = -1;
+            $this->resultArr['msg'] = $e->getMessage();
+        }
+
+        // 记录日志
+        TencentRequestService::create('sts', $action, $params, $this->resultArr);
+    }
+}

+ 34 - 0
app/Http/Helpers/Tencent/TlsEventSig.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/14  16:29.
+ * Tencent SDK.
+ * 目前主要用于腾讯事件回调验签.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+class TlsEventSig
+{
+    private string $key;
+    private string $body;
+
+    public function __construct($key, $body)
+    {
+        $this->key = $key;
+        $this->body = $body;
+    }
+
+    private function __hmacsha256()
+    {
+        $hash = hash_hmac('sha256', $this->body, $this->key, true);
+
+        return base64_encode($hash);
+    }
+
+    public function genEventSig()
+    {
+        return $this->__hmacsha256();
+    }
+}

+ 66 - 0
app/Http/Helpers/Tencent/TrtcTencent.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/5  14:09.
+ * Tencent TRTC SDK.
+ * 目前主要用于云端录制、页面录制等相关API.
+ */
+
+namespace App\Http\Helpers\Tencent;
+
+use App\Services\TencentRequestService;
+use TencentCloud\Common\Credential;
+use TencentCloud\Common\Exception\TencentCloudSDKException;
+use TencentCloud\Common\Profile\ClientProfile;
+use TencentCloud\Common\Profile\HttpProfile;
+use TencentCloud\Trtc\V20190722\TrtcClient;
+
+class TrtcTencent
+{
+    public string $secretId;
+    public string $secretKey;
+    public string $region;
+    public string $endpoint = 'trtc.tencentcloudapi.com';
+    public object $client;
+    public int $sdkAppId;
+
+    public array $resultArr = ['code' => 200, 'msg' => 'success', 'data' => []];
+
+    public function __construct($action, $params)
+    {
+        $this->secretId = config('tencent.tencent_cloud_secret_id');
+        $this->secretKey = config('tencent.tencent_cloud_secret_key');
+        $this->region = config('tencent.trtc.region');
+        $this->sdkAppId = config('tencent.trtc.app_id');
+
+        try {
+            $cred = new Credential($this->secretId, $this->secretKey);
+            // 实例化一个http选项,可选的,没有特殊需求可以跳过
+            $httpProfile = new HttpProfile();
+            $httpProfile->setEndpoint($this->endpoint);
+
+            // 实例化一个client选项,可选的,没有特殊需求可以跳过
+            $clientProfile = new ClientProfile();
+            $clientProfile->setHttpProfile($httpProfile);
+            // 实例化要请求产品的client对象,clientProfile是可选的
+            $this->client = new TrtcClient($cred, $this->region, $clientProfile);
+
+            $params['SdkAppId'] = $this->sdkAppId;
+            // 实例化一个请求对象,每个接口都会对应一个request对象
+            $respClass = 'TencentCloud\\'.ucfirst('trtc').'\\V20190722\\Models\\'.ucfirst($action).'Request';
+            $req = new $respClass();
+            $req->fromJsonString(json_encode($params));
+            // 返回的resp是一个RemoveUserResponse的实例,与请求对象对应
+            $resp = $this->client->{$action}($req);
+
+            $this->resultArr['data'] = json_decode($resp->toJsonString(), true);
+        } catch (TencentCloudSDKException $e) {
+            $this->resultArr['code'] = -1;
+            $this->resultArr['msg'] = $e->getMessage();
+        }
+
+        // 记录日志
+        TencentRequestService::create('trtc', $action, $params, $this->resultArr);
+    }
+}

+ 6 - 0
app/Http/Kernel.php

@@ -42,6 +42,10 @@ class Kernel extends HttpKernel
             'throttle:600,1',
             \Illuminate\Routing\Middleware\SubstituteBindings::class,
         ],
+        'statistics_api' => [
+            'throttle:600,1',
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
     ];
 
     /**
@@ -66,5 +70,7 @@ class Kernel extends HttpKernel
         'apiSign'=>\App\Http\Middleware\ApiSign::class,
         'token'=>\App\Http\Middleware\CheckToken::class,
         'psy_user'=>\App\Http\Middleware\PsyUserVerify::class,
+        'psyUserSso' => \App\Http\Middleware\PsyUserSso::class
+
     ];
 }

+ 348 - 0
app/Http/Middleware/PsyUserSso.php

@@ -0,0 +1,348 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+use Encore\Admin\Facades\Admin;
+use Illuminate\Log\Logger;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
+use Illuminate\Support\Facades\Validator;
+use Monolog\Handler\RotatingFileHandler;
+
+//站点用户验证
+
+class PsyUserSso
+{
+    const USER_DATA_URL = "/api.php";
+    private $url = "/admin/ssoIndex";
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if (Admin::guard()->check()){
+            return $next($request);
+        }
+        $sign = $request->get("sign");
+        $signData = $request->only(array("task","appid","open_id","version","go_url"));
+        $config = config("console.remote_sso");
+        if ($config["appid"] != $signData["appid"]){
+            throw new \Exception("appid非法参数:{$signData["appid"]}");
+        }
+        $paramsData = $request->except(array("appid","op", "task","version","sign","go_url"));
+        $userModelClass = config('admin.database.users_model');
+        
+        $userModel = new $userModelClass();
+
+        if (Admin::guard()->check() && Admin::user()->third_openid == $signData["open_id"]){
+            //更新用户信息
+
+            $res = DB::table($userModel::query()->getModel()->getTable())->where(
+                array("id"=>Admin::user()->id)
+            )->update(array(
+                "third_openid"=>$signData["open_id"],
+                "workstation_id"=>$paramsData['outside_c_id'],
+                "workstation_name"=>$paramsData['visit_title']
+            ));
+            return $next($request);
+        } 
+       
+        $userData = $this->getUserData($signData["open_id"]);//下面需要获取用户账号、昵称、头像
+        // $nowSign=$this->getSign(
+        //     $paramsData,
+        //     "core",
+        //     $signData["task"],
+        //     $signData["version"]
+        // );
+        // if ($nowSign != $sign){
+        //     throw new \Exception("签名验证不能通过:sign:{$sign}、nowSign:{$nowSign}");
+        // }
+        $checkUserData = $userModel::query()
+            ->where(
+                array(
+                    "third_openid"=>$signData["open_id"],
+                )
+            )
+            ->orderBy("id","desc")->first();
+        try {
+            DB::beginTransaction();
+            $plaintext = "";
+            $defaultPassword = "";
+            if (!empty($checkUserData)){
+                $plaintext = "{$checkUserData->username}@123456";
+                $defaultPassword = bcrypt($plaintext);
+            }
+            if (empty($checkUserData)){
+                //开始菜单授权
+                $permissionModelClass = config('admin.database.permissions_model');
+                $roleModelClass = config('admin.database.roles_model');
+                $userAvatar = config('admin.default_avatar');
+                $name = "";//名称
+                $userName="";//登录账号
+                $userData = $this->getUserData($signData["open_id"]);//下面需要获取用户账号、昵称、头像
+                if (isset($userData["user"])){
+                    if (isset($userData["user"]["name"])){
+                        $name = $userData["user"]["name"];
+                    }
+                    $name = empty($name)?$userData["user"]["username"]:$name;
+                    $userName = $userData["user"]["username"];
+                }
+                $plaintext = "{$userName}@123456";
+                $defaultPassword = bcrypt($plaintext);
+                $checkUserData = $userModel->create(array(
+                    "username"=>$userName,
+                    "password"=>$defaultPassword,
+                    "name"=>$name,
+                    "avatar"=>null,
+                ));
+                if (empty($checkUserData)){
+                    throw new \RuntimeException("新增用户信息失败");
+                }
+                DB::table($userModel::query()->getModel()->getTable())->where(
+                    array("id"=>$checkUserData->id)
+                )->update(array(
+                    "third_openid"=>$signData["open_id"],
+                    "workstation_id"=>$paramsData['outside_c_id'],
+                    "workstation_name"=>$paramsData['visit_title']
+                ));
+                $roleSlugName = array("RemoteSso");//角色名称 slug
+                $permissionsSlugName = array("RemoteSso");//权限名称 slug
+                $permissionModel = new $permissionModelClass();
+                $roleModel = new $roleModelClass();
+                foreach ($roleSlugName as $val){
+                    $roleData = $roleModel::query()
+                        ->where(
+                            array(
+                                "slug"=>$val
+                            )
+                        )
+                        ->orderBy("id","desc")->first();
+                    if (!empty($roleData)){
+                        DB::table("admin_role_users")->insert(
+                            array(
+                                "role_id"=>$roleData->id,
+                                "user_id"=>$checkUserData->id,
+                                "created_at"=>date("Y-m-d H:i:s")
+                            )
+                        );
+                    }
+                }
+                foreach ($permissionsSlugName as $val){
+                    $permissionData = $permissionModel::query()
+                        ->where(
+                            array(
+                                "slug"=>$val
+                            )
+                        )
+                        ->orderBy("id","desc")->first();
+                    if (!empty($permissionData)){
+                        DB::table("admin_user_permissions")->insert(
+                            array(
+                                "permission_id"=>$permissionData->id,
+                                "user_id"=>$checkUserData->id,
+                                "created_at"=>date("Y-m-d H:i:s")
+                            )
+                        );
+                    }
+                }
+            }
+            DB::commit();
+
+            Admin::guard()->login(
+                $checkUserData
+            );
+            auth('admin')->login(
+                $checkUserData
+            );
+            // echo 'aaaaaaa';exit;
+
+            $key = "RemoteSso:".$_SERVER['HTTP_HOST'].":".$checkUserData->id;
+            Redis::SET($key, true);
+            $goUrl = $signData['go_url'] ?: $this->url;
+
+            // Log::info("登录成功",[$checkUserData->id,$checkUserData->username, $goUrl]);
+            return $next($request);
+
+        }catch (\Exception $exception){
+            // Log::error("远程授权登录出错",[$exception->getMessage(),$exception->getTrace()]);
+            DB::rollBack();
+            return "登录失败";
+        }
+    }
+
+
+    public function getUserData(string $openId)
+    {
+        if (empty($openId)){
+            throw new \Exception("openId不能为空");
+        }
+        $config = config("console.remote_sso");
+        $url = $config["url"].self::USER_DATA_URL;//http://www.baidu.com/api.php
+        $query = array(
+            "op"=>"user",
+            "task"=>"userData",
+            "version"=>"1.0.0",
+            "appid"=>$config["appid"]
+        );
+        $sendData = array(
+            "open_id"=>$openId
+        );
+        $postData = $sendData;
+        $query["sign"] = $this->getSign($postData,$query["op"],$query["task"],$query["version"]);
+//        $client = new Client();
+        $url = $url."?".http_build_query($query);
+        $resultStr = $this->curlPost($url,$postData);
+//        $response = $client->request('post', $url, [
+//            'query' => $query,
+//            "headers"=>array(
+//                "Content-type"=>"application/json"
+//            ),
+//            "form_params"=>$postData
+//        ]);
+//        $resultStr = $response->getBody()->getContents();
+        return $this->getResult($resultStr);
+    }
+
+    public function getSign(array $sendData, string $op = "user", string $task = "login", string $version = "1.0.0")
+    {
+        $config = config("console.remote_sso");
+        $appid = $config["appid"];
+        $secret = $config["secret"];
+        ksort($sendData);
+        $str = "";
+        foreach($sendData as $key =>$value){
+            $str.= stripslashes($value);
+        }
+        $sign = md5($appid. $op . $task . $version . $str. $secret);
+        return $sign;
+    }
+    public function getSsoSign(array $data)
+    {
+        $config = config("console.remote_sso");
+        $appid = $config["appid"];
+        $secret = $config["secret"];
+        ksort($sendData);
+        $str = "";
+        foreach($sendData as $key =>$value){
+            $str.= stripslashes($value);
+        }
+        $sign = md5($appid. $str. $secret);
+        return $sign;
+    }
+    /**
+     * 得到结果数据
+     * @param string $resultStr
+     * @return mixed
+     * @throws \Exception
+     */
+    private function getResult(string $resultStr){
+        $result = json_decode($resultStr,true);
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            // $this->log("数据格式响应错误",$resultStr);
+            throw new \Exception("数据格式响应错误".$resultStr);
+        }
+        if (empty($result["status"])){
+            $content = $result["message"] ?? $resultStr;
+            // $this->log("结果信息发生错误",$content);
+            throw new \Exception("结果信息发生错误".$content);
+        }
+        if (!isset($result["data"])){
+            $content = $resultStr;
+            // $this->log("结果信息格式发送错误",$content);
+            throw new \Exception("结果信息格式发送错误".$content);
+        }
+        return $result["data"];
+    }
+
+    private function curlPost($url, $data=null, $file=null)
+    {
+        $ch = curl_init($url);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 60); //设置超时
+
+//        if(!empty($file)){
+//            foreach ($file as $k=>$v){
+//                if(is_array($v)){
+//                    //多个文件
+//                    foreach ($v as $key=>$val){
+//                        $data[$k.'['.$key.']'] = new CURLFile($val);
+//                    }
+//                }else{
+//                    //一维
+//                    $data[$k] = new CURLFile($v);
+//                }
+//            }
+//        }
+
+        if (0 === strpos(strtolower($url), "https")) {
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //对认证证书来源的检查
+            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); //从证书中检查SSL加密算法是否存在
+        }
+        curl_setopt($ch, CURLOPT_POST, true);
+
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+
+//         curl_setopt($ch, CURLOPT_HTTPHEADER);
+        $rtn = curl_exec($ch);//CURLOPT_RETURNTRANSFER 不设置  curl_exec返回TRUE 设置  curl_exec返回json(此处) 失败都返回FALSE
+        curl_close($ch);
+        return $rtn;
+    }
+    /**
+     * json 形式传参
+     * @param unknown $url
+     * @param unknown $data
+     */
+    private function curlPostJson($url, $data = null)
+    {
+        $headers = array(
+            "Content-type: application/json;charset='utf-8'",
+            "Accept: application/json",
+            "Cache-Control: no-cache",
+            "Pragma: no-cache",
+        );
+        $ch = curl_init($url);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 60); //设置超时
+
+        if (0 === strpos(strtolower($url), "https")) {
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); //对认证证书来源的检查
+            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); //从证书中检查SSL加密算法是否存在
+        }
+        curl_setopt($ch, CURLOPT_POST, true);
+
+        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
+
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+        curl_setopt($ch, CURLOPT_HEADER, 1);//打印响应header
+        $rtn = curl_exec($ch);//CURLOPT_RETURNTRANSFER 不设置  curl_exec返回TRUE 设置  curl_exec返回json(此处) 失败都返回FALSE
+
+        $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+
+        // 根据头大小去获取头信息内容
+        $header = substr($rtn, 0, $header_size);
+
+        $data = substr($rtn, $header_size);
+
+        $is_gzipped = false;
+
+        if (preg_match("/Content-Encoding: gzip/", $header)) {
+            $is_gzipped = true;
+        }
+
+        if($is_gzipped){
+            $data = gzdecode($data);
+        }
+
+        curl_close($ch);
+
+        return $data;
+    }
+}

+ 43 - 0
app/Http/Requests/DashboardReportRequest.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace App\Http\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class DashboardReportRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            //
+            'workstation_id' => 'required|string',
+            'period' => 'required|in:7days,30days',
+        ];
+    }
+
+    public function messages()
+    {
+        return [
+            'workstation_id.required' => '请选择站点',
+            'workstation_id.string' => 'workstation_id类型不对',
+            'period.required' => '请选择时间',
+            'period.in' => '请选择时间',
+        ];
+    }
+
+}

+ 11 - 0
app/Models/AdminConfig.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class AdminConfig extends Model
+{
+    //
+    protected $table = 'admin_config';
+}

+ 36 - 0
app/Models/NewMediaCount.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class NewMediaCount extends Model
+{
+    //
+    protected $table = 'new_media_count';
+    protected $fillable = ['day','year','month'];
+    protected $appends = ['month','year', 'count_data'];
+    protected $casts = [
+        'count_data' => 'array',
+    ];
+    // protected $guarded = ['year','month'];
+    protected static function booting()
+    {
+        static::creating(function ($model) {
+            $model->day = $model->year . '-' . $model->month ;
+            unset($model->year, $model->month);
+        });
+    }
+    protected function getMonthAttribute()
+    {
+        return date('m', strtotime($this->day));
+    }
+    protected function getYearAttribute()
+    {
+        return date('Y', strtotime($this->day));
+    }
+    protected function getCountDataAttribute()
+    {
+        return @NewMediaCountRecord::where('nmc_id', $this->id)->get()->toArray();
+    }
+}

+ 11 - 0
app/Models/NewMediaCountRecord.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class NewMediaCountRecord extends Model
+{
+    //
+    protected $table = 'new_media_count_record';
+}

+ 8 - 0
app/Models/Scale.php

@@ -5,6 +5,14 @@ namespace App\Models;
 class Scale extends SoftBaseModel
 {
     protected $casts = ['comment_tag'=>'array'];
+    protected $fillable = [
+        'title',
+        'pic',
+        'third_id',
+        'comment_tag',
+        'rank',
+        'second_id'
+    ];
     
     public function getPicAttribute()
     {

+ 4 - 0
app/Models/ScaleCategory.php

@@ -20,4 +20,8 @@ class ScaleCategory extends SoftBaseModel
     {
         return isset($this->attributes['pic2']) ? config('console.pic_path').$this->attributes['pic2'] : NULL;
     }
+    public function Childrens()
+    {
+        return $this->hasMany(ScaleCategory::class, 'pid');
+    }
 }

+ 11 - 0
app/Models/SmsRecord.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class SmsRecord extends Model
+{
+    //
+    protected $table = 'sms_record';
+}

+ 9 - 1
app/Models/SpecialistInfo.php

@@ -28,14 +28,22 @@ class SpecialistInfo extends SoftBaseModel
         self::TYPE_3     => '视频',
     ];
 
+    public $category_id = 0;
+    public $thumb = '';
+
     protected $casts = ['cover'=>'array','comment_tag'=>'array'];
 
-    protected $appends = ['category_id'];
+    protected $appends = ['category_id', 'thumb'];
 
     public function getCategoryIdAttribute()
     {
         return $this->attributes['second_id'] ? $this->attributes['second_id'] : $this->attributes['first_id'];
     }
+    public function getThumbAttribute()
+    {
+        return $this->cover ? asset('storage/'.$this->cover[0]) : '';
+    }
+
 
     public function firstCloumn(){
         return $this->belongsTo(SpecialistCloumn::class, 'first_id', 'id');

+ 6 - 0
app/Providers/RepositoryServiceProvider.php

@@ -115,6 +115,12 @@ class RepositoryServiceProvider extends ServiceProvider
         $this->app->singleton('RemoteSsoFacadeRepository', function ($app) {
             return new \App\Repositories\Eloquent\RemoteSsoFacadeRepository();
         });
+        $this->app->singleton('DashboardFacadeRepository', function ($app) {
+            return new \App\Repositories\Eloquent\DashboardFacadeRepository();
+        });
+        $this->app->singleton('AliSmsFacadeRepository', function ($app) {
+            return \App\Repositories\Eloquent\AliSmsFacadeRepository::client();
+        });
     }
 
     /**

+ 9 - 0
app/Providers/RouteServiceProvider.php

@@ -46,6 +46,8 @@ class RouteServiceProvider extends ServiceProvider
 
         $this->mapWebRoutes();
 
+        $this->mapStatisticsApiRoutes();
+
         //
     }
 
@@ -62,6 +64,13 @@ class RouteServiceProvider extends ServiceProvider
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
     }
+    protected function mapStatisticsApiRoutes()
+    {
+        Route::prefix('statistics_api')
+        ->middleware('api')
+        ->namespace($this->namespace)
+        ->group(base_path('routes/statisticsApi.php'));
+    }
 
     /**
      * Define the "api" routes for the application.

+ 21 - 0
app/Repositories/Contracts/DashboardInterface.php

@@ -0,0 +1,21 @@
+<?php
+namespace App\Repositories\Contracts;
+
+/**
+ * 仪表盘统计接口
+ * 
+ * @author huangxiaodong
+ *        
+ */
+interface DashboardInterface
+{
+    /**
+     * 获取时间段内注册用户统计
+     *
+     * @param string    $startTime      开始时间    2024-01-01
+     * @param string    $endTime        结束时间    2024-01-30
+     * 
+     * 
+     */
+    public function getPeriodRegisiter($startTime, $endTime, $dateType);
+}

+ 13 - 0
app/Repositories/Contracts/SmsInterface.php

@@ -0,0 +1,13 @@
+<?php
+namespace App\Repositories\Contracts;
+
+/**
+ *
+ * @author lilin
+ *        
+ */
+interface SmsInterface
+{
+    function getClient();
+    function send();
+}

+ 78 - 0
app/Repositories/Eloquent/AliSmsFacadeRepository.php

@@ -0,0 +1,78 @@
+<?php
+namespace App\Repositories\Eloquent;
+
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi;
+use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest;
+use AlibabaCloud\Tea\Exception\TeaUnableRetryError;
+use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions;
+use App\Repositories\Contracts\SmsInterface;
+use Darabonba\OpenApi\Models\Config;
+
+class AliSmsFacadeRepository extends BaseRepository implements SmsInterface
+{
+    private static $instance = null;
+    private $client;
+    public $request;
+
+    private function __construct($config)
+    {
+        $config = new Config($config);
+        $config->endpoint = "dysmsapi.ap-southeast-1.aliyuncs.com";
+        $this->client = new Dysmsapi($config);
+        $this->request = new SendSmsRequest();
+    }
+
+    public static function client($config = [])
+    {
+        if (self::$instance === null) {
+            if(!$config) {
+                $config = [
+                            // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
+                    "accessKeyId" => config('sms.alisms.accessKeyId'),
+                    // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
+                    "accessKeySecret" => config('sms.alisms.accessKeySecret'),
+                ];
+            }
+            self::$instance = new self($config);
+        }
+        return self::$instance;
+    }
+
+    public function getClient()
+    {
+        return $this->client;
+    }
+
+    public function setRequest($phoneNumber, $templateCode, $templateParam)
+    {
+        $this->request->phoneNumbers = $phoneNumber; // 接收短信的手机号码
+        $this->request->signName = config('sms.alisms.signName'); // 短信签名名称
+        $this->request->templateCode = $templateCode; // 短信模板ID
+        $this->request->templateParam = json_encode($templateParam); // 短信模板变量对应的实际值
+
+        return $this;
+    }
+    public function send()
+    {
+        
+        $client = $this->getClient();
+        try{
+            $runtime                 = new RuntimeOptions();
+            $runtime->maxIdleConns   = 3;
+            $runtime->connectTimeout = 1000;
+            $runtime->readTimeout    = 1000;
+           $res =  $client->sendSms($this->request, $runtime);
+           var_dump($res);exit;
+
+        }catch (TeaUnableRetryError $e) {
+            // 获取报错数据
+            var_dump($e->getErrorInfo());
+            // 获取报错信息
+            var_dump($e->getMessage());
+            // 获取最后一次报错的 Exception 实例
+            var_dump($e->getLastException());
+            // 获取最后一次请求的 Request 实例
+            var_dump($e->getLastRequest());
+        }
+    }
+}

+ 76 - 0
app/Repositories/Eloquent/BaseRepository copy.php

@@ -0,0 +1,76 @@
+<?php
+namespace App\Repositories\Eloquent;
+
+use App\Exceptions\HOError;
+use Illuminate\Support\Facades\Log;
+
+class BaseRepository
+{
+    protected $startTime;
+    protected $error;
+
+    const SUCCESS_CODE = 1000;
+
+    public function startTime()
+    {
+        Log::debug('获取程序开始时间');
+        $this->startTime = microtime(TRUE);
+        return $this->startTime;
+    }
+
+    /**
+     * 成功返回
+     */
+    public function response($data=null, $msg = '成功')
+    {
+        return response()->horesp(1000, $data, $msg);
+    }
+
+
+    public function fail($data=null, $msg = '失败')
+    {
+        return response()->horesp(9999, $data, $msg);
+    }
+
+    /**
+     * 异常错误
+     *
+     * @return \App\Exceptions\HOError
+     */
+    public function error()
+    {
+        $this->error = new HOError();
+        return $this->error;
+    }
+
+    /**
+     * 统一格式输出日志
+     *
+     * @param string    $typeName   类别名
+     * @param string    $startTime  开始时间
+     * @param array     $log        日志详情
+     * @param string    $ac         指定动作
+     */
+    public function setLog(string $typeName, $startTime = 0.00, $log = [], $ac = 'info')
+    {
+        //传入的时间
+        $sTime = $startTime;
+        //当前时间,相当于结束时间
+        $eTime = microtime(TRUE);
+        //计算时间差
+        $diffTime = (($eTime - $sTime) * 1000);
+        $msg = '执行时间:'.round($diffTime, 3).'ms'.' 执行内容:'.$typeName;
+        switch ($ac){
+            case 'debug':
+                Log::debug($msg, $log);
+            break;
+            case 'info':
+                Log::info($msg, $log);
+                break;
+            case 'error':
+                Log::error($msg, $log);
+                break;
+        }
+    }
+}
+

+ 115 - 0
app/Repositories/Eloquent/DashboardFacadeRepository.php

@@ -0,0 +1,115 @@
+<?php
+namespace App\Repositories\Eloquent;
+
+use App\Models\BrowseRecord;
+use App\Models\NewMediaAccount;
+use App\Models\User;
+use App\Repositories\Contracts\DashboardInterface;
+
+class DashboardFacadeRepository extends BaseRepository implements DashboardInterface
+{
+    /**
+     * 获取指定时间段内的注册数据
+     *
+     * @param string $start 开始时间
+     * @param string $end 结束时间
+     * @param string $dateType 日期类型 0:按照起始时间:1日 日 当日+往前6天 2周 当前周+往前6周 3月 起始时间所在月,往前+5个月的数据
+     * @return array 返回包含总数据、计数数据和按日期分组的数据数组
+     */
+    public function getPeriodRegisiter($start, $end, $dateType = 0)
+    {
+        //从配置中获取x轴字段,默认为['社心小程序', '新媒体']
+        $xField = config('dashboard.UG.xField',  ['社心小程序', '新媒体']);
+        $ret = [];
+        $mpCount = 0;
+        $nmCount = 0;
+        if ($dateType == 0) {
+            //查询指定时间段内的浏览记录
+            $result = BrowseRecord::whereBetween('created_at', [$start, $end])->get();
+
+            //初始化数据结构,为每个日期创建一个数据项,并将x_field关联的数据初始化为0
+            while (strtotime($start) <= strtotime($end)) {
+                $ret[$start] = [
+                    'x_data' => $start,
+                    'y_field' => $xField,
+                    'y_data' => array_fill(0, count($xField), 0),
+                ];
+                $startTime = date('Y-m-d', strtotime("+1 day", strtotime($start)));
+            }
+
+            //遍历查询结果,统计每个日期的社心小程序浏览量
+            foreach ($result as $item) {
+                $mpCount++;
+                $ret[date('Y-m-d', strtotime($item->created_at))]['y_data'][array_search('社心小程序', $xField)]++;
+            }
+            
+        } elseif ($dateType == 1) {
+            $endTime = date('Y-m-d');
+            $startTime = date('Y-m-d', strtotime("-6 days", strtotime($endTime)));
+            //查询指定时间段内的浏览记录
+            $result = BrowseRecord::whereBetween('created_at', [$startTime, $endTime])->get();
+
+            //初始化数据结构,为每个日期创建一个数据项,并将x_field关联的数据初始化为0
+            while (strtotime($startTime) <= strtotime($endTime)) {
+                $ret[$startTime] = [
+                    'x_data' => $startTime,
+                    'y_field' => $xField,
+                    'y_data' => array_fill(0, count($xField), 0),
+                ];
+                $startTime = date('Y-m-d', strtotime("+1 day", strtotime($startTime)));
+            }
+
+            //遍历查询结果,统计每个日期的社心小程序浏览量
+            foreach ($result as $item) {
+                $mpCount++;
+                $ret[date('Y-m-d', strtotime($item->created_at))]['y_data'][array_search('社心小程序', $xField)]++;
+            }
+        } elseif ($dateType == 2){
+            //当前周+往前6周
+            $week = 0;
+            while($week <7) {
+                 // 获取当前日期所在周的周日
+                $endTime = date('Y-m-d', strtotime('sunday -'.$week.' week', strtotime($end)));
+                // 获取六个自然周前的周一
+                $startTime = date('Y-m-d', strtotime('monday -'.($week+1).' week', strtotime($end)));
+
+                $result = BrowseRecord::whereBetween('created_at', [$startTime, $endTime])->get();
+
+                $ret[] = [
+                    'x_data' => $startTime.'~'.$endTime,
+                    'y_field' => $xField,
+                    'y_data' => [$result->count(), 0],
+                ];
+                $mpCount+= $result->count();
+                $week++;
+            }
+
+        }elseif ($dateType == 3){
+            //当前周+往前6周
+            $month = 0;
+            while($month <6) {
+                $endTime = date('Y-m-t', strtotime(' -'.$month.' month', strtotime($end)));
+                $startTime = date('Y-m-1', strtotime(' -'.$month.' month', strtotime($end)));
+
+                $result = BrowseRecord::whereBetween('created_at', [$startTime, $endTime])->get();
+
+                $ret[] = [
+                    'x_data' => date('Y-m', strtotime($startTime)),
+                    'y_field' => $xField,
+                    'y_data' => [$result->count(), 0],
+                ];
+                $mpCount+= $result->count();
+                $month++;
+            }
+
+        }
+       
+
+        //返回数据包括:总数据、计数数据和按日期分组的数据
+        return [
+            'total' => [['label' => '社心小程序', 'value' => User::count()], ['label' => '新媒体', 'value' => NewMediaAccount::sum('fans_num')]],
+            'count' => [['label' => '社心小程序', 'value' => $mpCount], ['label' => '新媒体', 'value' => 0]],
+            'data' => array_values($ret),
+        ];
+    }
+}

+ 11 - 0
app/Repositories/Eloquent/ScaleCategoryFacadeRepository.php

@@ -17,11 +17,22 @@ class ScaleCategoryFacadeRepository extends BaseRepository implements ScaleCateg
             if (isset($conditions['uid'])){
                 $query->where('uid', $conditions['uid']);
             }
+            if (isset($conditions['enable'])){
+                $query->where('is_enable', $conditions['enable']);
+            }
         })->orderByRaw($sort)->paginate($limit, $fields);
         
         return $this->response($result);
     }
     
+    public function tree()
+    {
+        $result = ScaleCategory::where(function($query){
+            $query->where('pid', 0);
+        })->with(['childrens'])->orderBy('rank', 'desc')->get(['id', 'name', 'pid','pic']);
+        return $result ? $this->response($result) : $this->error()->dataDoesNotExist();
+    }
+    
     public function findBy(array $conditions, array $fields){
         $result = ScaleCategory::where(function($query) use($conditions){
             if (isset($conditions['id'])){

+ 3 - 0
app/Repositories/Eloquent/ScaleFacadeRepository.php

@@ -23,6 +23,9 @@ class ScaleFacadeRepository extends BaseRepository implements ScaleInterface
             if (isset($conditions['category_id']) && $conditions['category_id']){
                 $query->where('category_id', $conditions['category_id']);
             }
+            if (isset($conditions['second_id']) && $conditions['second_id']){
+                $query->where('second_id', $conditions['second_id']);
+            }
             if (isset($conditions['search']) && $conditions['search']){
                 $query->where('title', 'like' , '%'.$conditions['search'].'%');
             }

+ 40 - 0
app/Services/ExpertService.php

@@ -0,0 +1,40 @@
+<?php
+namespace App\Services;
+
+use App\Models\Expert;
+use App\Traits\PageTrait;
+
+class ExpertService
+{
+    use PageTrait;
+    public function getList($condition, $sort = 'id', $sortable = 'desc')
+    {
+        $query = Expert::query();
+        $query->select(
+            'id',
+            'username',
+            'avatar',
+            'name',
+            'status'
+        );
+        // 使用 whereIn 方法筛选 lecture_id 在 $lectureIdArr 数组中的记录
+        $query->where('is_expert', 1);
+
+        // 搜索
+        if (isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('name', 'like', '%' . $condition['keywords'] . '%');
+        }
+        if (isset($condition['status']) && $condition['keywords'] !== '') {
+            $query->where('name', 'like', '%' . $condition['keywords'] . '%');
+        }
+
+        // 排序
+        $query->orderBy($sort, $sortable);
+
+        // 执行查询并返回结果
+        return self::JsonPage($query);
+    }
+
+   
+
+}

+ 62 - 0
app/Services/GroupTherapyService.php

@@ -0,0 +1,62 @@
+<?php
+namespace App\Services;
+
+use App\Models\GroupTherapyRecord;
+use App\Models\UserLectureRelation;
+use App\Models\WorkstationLectureRelation;
+use App\Models\Lecture; // 确保引入 Lecture 模型
+use App\Traits\PageTrait;
+
+class GroupTherapyService
+{
+    use PageTrait;
+
+    public function getList($condition, $sort = 'id', $sortable = 'desc')
+    {
+        $query = GroupTherapyRecord::query();
+        $query->select(
+            'id',
+            'open_vc_id',
+            'type',
+            'customer_data',
+            'created_at',
+            'workstation_name',
+            'workstation_id',
+            'open_vt_id',
+            'vt_title'
+        );
+        // 使用 whereIn 方法筛选 lecture_id 在 $lectureIdArr 数组中的记录
+        // 搜索
+        if (isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('customer_data', 'like', '%' . $condition['keywords'] . '%');
+        }
+
+        // 排序
+        $query->orderBy($sort, $sortable);
+
+        // 执行查询并返回结果
+        return self::JsonPage($query);
+    }
+    public function report($data)  
+    {
+        if($data['customer_data']) {
+            $customerData = json_decode($data['customer_data'], true);
+            foreach($customerData as $v){
+                $insertData[]=[
+                    'open_vc_id'    =>$v['open_vc_id'],
+                    'type'    =>$data['type'],
+                    'customer_data' =>json_encode($v),
+                    'created_at' =>date('Y-m-d H:i:s'),
+                    'updated_at' =>date('Y-m-d H:i:s'),
+                    'workstation_id'=>auth('grider')->user()->workstation_id,
+                    'workstation_name'=>auth('grider')->user()->workstation_name,
+                    'open_vt_id' => $v['vt_id'],
+                    'vt_title' => $v['vt_title'],
+                ];
+            }
+            $res = GroupTherapyRecord::insert($insertData);
+            return $res;
+        } 
+        return true;
+    }
+}

+ 114 - 0
app/Services/LectureService.php

@@ -0,0 +1,114 @@
+<?php
+namespace App\Services;
+
+use App\Models\UserLectureRelation;
+use App\Models\WorkstationLectureRelation;
+use App\Models\Lecture; // 确保引入 Lecture 模型
+use App\Traits\PageTrait;
+
+class LectureService
+{
+    use PageTrait;
+
+    public function getList($condition, $sort = 'id', $sortable = 'desc')
+    {
+        $query = Lecture::query();
+        $query->select(
+            'id',
+            'title',
+            'start_time',
+            'end_time',
+            'status',
+            'master_id'
+        );
+        // 使用 whereIn 方法筛选 lecture_id 在 $lectureIdArr 数组中的记录
+        // 搜索
+        if (isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('title', 'like', '%' . $condition['keywords'] . '%');
+        }
+
+        // 排序
+        $query->orderBy($sort, $sortable);
+
+        // 执行查询并返回结果
+        return self::JsonPage($query);
+    }
+    public function getListForExpert($condition, $sort = 'id', $sortable = 'desc')
+    {
+        $userId = auth('expert')->user()->id;
+        $userLectureIdArr = UserLectureRelation::query()->where('user_id', $userId)->pluck('lecture_id')->toArray();
+        if(empty($userLectureIdArr)){
+            return [
+                'list' => [],
+                'count' => 0,
+                'current_page' => 1,
+                'page_size' => 15,
+            ];
+        }
+        $query = Lecture::query();
+        $query->select(
+            'id',
+            'title',
+            'start_time',
+            'end_time',
+            'status'
+        );
+        // 使用 whereIn 方法筛选 lecture_id 在 $lectureIdArr 数组中的记录
+        $query->whereIn('id', $userLectureIdArr);
+
+        // 搜索
+        if (isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('title', 'like', '%' . $condition['keywords'] . '%');
+        }
+        if (isset($condition['status']) && $condition['status'] !== NULL) {
+            $query->where('status',  $condition['status']);
+        }
+
+        // 排序
+        $query->orderBy($sort, $sortable);
+
+        // 执行查询并返回结果
+        return self::JsonPage($query);
+    }
+
+
+    public function getListForGrider($condition, $sort = 'id', $sortable = 'desc')
+    {
+        $userId = auth('grider')->user()->id;
+        $userLectureIdArr = UserLectureRelation::query()->where('user_id', $userId)->pluck('lecture_id')->toArray();
+        $workstationLectureIdArr = WorkstationLectureRelation::query()->where('workstation_id', 'abc')->pluck('lecture_id')->toArray();
+        $lectureIdArr = array_merge($userLectureIdArr, $workstationLectureIdArr);
+        if(empty($lectureIdArr)){
+            return [
+                'list' => [],
+                'count' => 0,
+                'current_page' => 1,
+                'page_size' => 15,
+            ];
+        }
+        $query = Lecture::query();
+        $query->select(
+            'id',
+            'title',
+            'start_time',
+            'end_time',
+            'status'
+        );
+        // 使用 whereIn 方法筛选 lecture_id 在 $lectureIdArr 数组中的记录
+        $query->whereIn('id', $lectureIdArr);
+
+        // 搜索
+        if (isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('title', 'like', '%' . $condition['keywords'] . '%');
+        }
+        if (isset($condition['status']) && $condition['status'] !== NULL) {
+            $query->where('status',  $condition['status']);
+        }
+
+        // 排序
+        $query->orderBy($sort, $sortable);
+
+        // 执行查询并返回结果
+        return self::JsonPage($query);
+    }
+}

+ 12 - 0
app/Services/LiveService.php

@@ -0,0 +1,12 @@
+<?php
+namespace App\Services;
+
+use App\Models\LiveRecord;
+
+class LiveService
+{
+    public function records($liveId)
+    {
+       return  LiveRecord::query()->where('course_id', $liveId)->firstOrFail();
+    }
+}

+ 135 - 0
app/Services/LiveWebRecordService.php

@@ -0,0 +1,135 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/12  11:36.
+ */
+
+namespace App\Services;
+
+use App\Http\Helpers\Tencent\MediaTencent;
+use App\Http\Helpers\Tencent\SignTencent;
+use App\Http\Helpers\Tencent\TrtcTencent;
+use App\Models\LectureRecord;
+use App\Models\LiveRecord;
+use App\Models\MeetingCentre;
+use App\Models\Room as RoomModel;
+use App\Models\RoomWebRecord;
+
+class LiveWebRecordService
+{
+    public array $storageParams = [
+        'CloudVod' => [
+            'TencentVod' => [
+                'StorageRegion' => 'ap-chongqing',
+                'Procedure' => 'STD-H264-MP4-1080P',
+                'ClassId' => 1174322,
+                'SubAppId' => 1327013090,
+                'UserDefineRecordId' => '',
+            ],
+        ],
+    ];
+
+    public function startWebRecord($request)
+    {
+        $roomId = $request['room_id'];
+        $roomName = $request['room_name'];
+        $otherParams = $request['other_params'];
+        $userId = $request['account_id'];
+        // 录制机器人的UserId,用于进房发起录制任务
+        $recordUserId = 'recorder_'.$roomId.'_'.$userId;
+
+        $this->storageParams['CloudVod']['TencentVod']['UserDefineRecordId'] = $recordUserId;
+        $sign = new SignTencent();
+        $userSign = $sign->genUserSign($recordUserId);
+        $recordInsertData = [
+            'lecture_id' => $roomId,
+            'room_name' => $roomName,
+            'user_id' => $userId,
+            'record_user_id' => $recordUserId,
+            'user_sign' => $userSign,
+        ];
+
+        $maxDurationLimit = config('tencent.web.max_duration_limit.region');
+        $tParams = [
+            'RecordUrl' => $this->webRecordUrl($recordInsertData, $otherParams),
+            'MaxDurationLimit' => $maxDurationLimit,
+            'StorageParams' => $this->storageParams,
+        ];
+        // print_r($tParams);exit;
+        $tRoom = new TrtcTencent('StartWebRecord', $tParams);
+        $retData = $tRoom->resultArr;
+        $taskId = $retData['data']['TaskId'] ?? '';
+        if (!$taskId) {
+            return false;
+        }
+
+        $recordInsertData['task_id'] = $taskId;
+
+        $roomRecord = new LectureRecord();
+        $roomRecord->fill($recordInsertData)->save();
+
+        return $taskId;
+    }
+
+    public function describeWebRecord($taskId)
+    {
+        $params['TaskId'] = $taskId;
+        $tRoom = new TrtcTencent('DescribeWebRecord', $params);
+        $retData = $tRoom->resultArr;
+
+        return $retData['data'];
+    }
+
+    public function stopWebRecord($taskId)
+    {
+        $params['TaskId'] = $taskId;
+        $tRoom = new TrtcTencent('StopWebRecord', $params);
+        $retData = $tRoom->resultArr;
+
+        return $retData['data'];
+    }
+
+    public function deleteMedia($fileId)
+    {
+        if (!$fileId) {
+            return false;
+        }
+        $params['FileId'] = $fileId;
+        $tMedia = new MediaTencent('DeleteMedia', $params);
+        $retData = $tMedia->resultArr;
+
+        return $retData['data'];
+    }
+
+    // 构建Web录制页面的URL
+    public function webRecordUrl($data, $otherParams): string
+    {
+        $url = getenv('WEB_URL').'Room?';
+        $params = [
+            'user_id' => $data['record_user_id'],
+            'username' => $data['record_user_id'],
+            'room_id' => (string) $data['lecture_id'],
+            'room_name' => $data['room_name'],
+            'user_sign' => $data['user_sign'],
+            'sdk_app_id' => (int)config('tencent.trtc.app_id'),
+            'room_exist' => true,
+            'is_record' => true,
+        ];
+
+        $extraInfo = [
+            
+        ];
+        // 其他参数,主要控制页面录制时,页面需要渲染成什么样子
+        if (\is_array($otherParams)) {
+            foreach ($otherParams as $k => $v) {
+                $params[$k] = $v;
+            }
+        }
+        // print_r($params);exit;
+        $url .= 'roomInfo='.urlencode(json_encode($params, JSON_UNESCAPED_UNICODE));
+        $url .= '&extraInfo='.urlencode(json_encode($extraInfo, JSON_UNESCAPED_UNICODE));
+        $url.='&is_record=true&room_id='.$data['lecture_id'];
+        return $url;
+    }
+}

+ 145 - 0
app/Services/RoomWebRecordService.php

@@ -0,0 +1,145 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/6/12  11:36.
+ */
+
+namespace App\Services;
+
+use App\Http\Helpers\Tencent\TrtcTencent as TencentTrtcTencent;
+use App\Models\MeetingCentre;
+use App\Models\Room as RoomModel;
+use App\Models\RoomWebRecord;
+use App\Tencent\MediaTencent;
+use App\Tencent\SignTencent;
+use App\Tencent\TrtcTencent;
+
+class RoomWebRecordService
+{
+    public array $storageParams = [
+        'CloudVod' => [
+            'TencentVod' => [
+                'StorageRegion' => 'ap-chongqing',
+                'Procedure' => 'STD-H264-MP4-1080P',
+                'ClassId' => 1174322,
+                'SubAppId' => 1327013090,
+                'UserDefineRecordId' => '',
+            ],
+        ],
+    ];
+
+    public function startWebRecord($request)
+    {
+        $roomId = $request['room_id'];
+        $roomName = $request['room_name'];
+        $otherParams = $request['other_params'];
+        $userId = $request['account_id'];
+        $patientId = RoomModel::query()
+            ->where('id', $roomId)
+            ->value('patient_id');
+        $transcribeType=1;
+        if (empty($patientId)){
+            $patientId = MeetingCentre::query()
+                ->where('room_id',$roomId)
+                ->value('patient_id');
+            if (!empty($patientId)){
+                $transcribeType=2;
+            }
+        }
+        // 录制机器人的UserId,用于进房发起录制任务
+        $recordUserId = 'recorder_'.$roomId.'_'.$userId;
+
+        $this->storageParams['CloudVod']['TencentVod']['UserDefineRecordId'] = $recordUserId;
+        $sign = new SignTencent();
+        $userSign = $sign->genUserSign($recordUserId);
+        $recordInsertData = [
+            'room_id' => $roomId,
+            'room_name' => $roomName,
+            'user_id' => $userId,
+            'record_user_id' => $recordUserId,
+            'user_sign' => $userSign,
+            'patient_id' => $patientId,
+            'transcribe_type'=>$transcribeType,
+        ];
+
+        $maxDurationLimit = config('tencent.web.max_duration_limit.region');
+        $tParams = [
+            'RecordUrl' => $this->webRecordUrl($recordInsertData, $otherParams),
+            'MaxDurationLimit' => $maxDurationLimit,
+            'StorageParams' => $this->storageParams,
+        ];
+        $tRoom = new TrtcTencent('StartWebRecord', $tParams);
+        $retData = $tRoom->resultArr;
+        $taskId = $retData['data']['TaskId'] ?? '';
+        if (!$taskId) {
+            return false;
+        }
+
+        $recordInsertData['task_id'] = $taskId;
+
+        $roomRecord = new RoomWebRecord();
+        $roomRecord->fill($recordInsertData)->save();
+
+        return $taskId;
+    }
+
+    public function describeWebRecord($taskId)
+    {
+        $params['TaskId'] = $taskId;
+        $tRoom = new TrtcTencent('DescribeWebRecord', $params);
+        $retData = $tRoom->resultArr;
+
+        return $retData['data'];
+    }
+
+    public function stopWebRecord($taskId)
+    {
+        $params['TaskId'] = $taskId;
+        $tRoom = new TencentTrtcTencent('StopWebRecord', $params);
+        $retData = $tRoom->resultArr;
+
+        return $retData['data'];
+    }
+
+    public function deleteMedia($fileId)
+    {
+        if (!$fileId) {
+            return false;
+        }
+        $params['FileId'] = $fileId;
+        $tMedia = new MediaTencent('DeleteMedia', $params);
+        $retData = $tMedia->resultArr;
+
+        return $retData['data'];
+    }
+
+    // 构建Web录制页面的URL
+    public function webRecordUrl($data, $otherParams): string
+    {
+        $url = getenv('WEB_URL').'Room?';
+        $params = [
+            'user_id' => $data['record_user_id'],
+            'room_id' => (string) $data['room_id'],
+            'room_name' => $data['room_name'],
+            'user_sign' => $data['user_sign'],
+            'sdk_app_id' => config('tencent.trtc.app_id'),
+            'room_exist' => false,
+            'is_record' => true,
+        ];
+
+        $extraInfo = [
+            'patient_id' => $data['patient_id'],
+        ];
+        // 其他参数,主要控制页面录制时,页面需要渲染成什么样子
+        if (\is_array($otherParams)) {
+            foreach ($otherParams as $k => $v) {
+                $params[$k] = $v;
+            }
+        }
+        $url .= 'roomInfo='.urlencode(json_encode($params, JSON_UNESCAPED_UNICODE));
+        $url .= '&extraInfo='.urlencode(json_encode($extraInfo, JSON_UNESCAPED_UNICODE));
+
+        return $url;
+    }
+}

+ 49 - 0
app/Services/TencentRequestService.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * Created by KLXQ
+ * user: youyi
+ * Date:2024/7/02  10:36.
+ */
+
+namespace App\Services;
+
+use App\Models\TencentRequest;
+use Illuminate\Support\Facades\Log;
+
+class TencentRequestService
+{
+    public static function create($server, $action, $request, $resultArr): void
+    {
+        $request = \is_array($request) ? json_encode($request) : $request;
+        $statusInfo = 'success';
+        Log::channel('tencent')->info($action.': '.$request);
+        if (200 === $resultArr['code']) {
+            Log::channel('tencent')->info($action.': '.$resultArr['msg']);
+        } else {
+            $statusInfo = 'error';
+            Log::channel('tencent')->error($action.': '.$resultArr['msg']);
+        }
+
+        if (!config('tencent.tencent_log')) {
+            return;
+        }
+
+        // 回调会使用,此时没有用户信息
+        $userId = 0;
+        $username = 'system';
+        if (auth('api')->user()) {
+            $userId = auth('api')->user()->id;
+            $username = auth('api')->user()->name;
+        }
+        TencentRequest::query()->create([
+            'server' => $server,
+            'action' => $action,
+            'u_id' => $userId,
+            'request' => $request,
+            'username' => $username,
+            'msg' => $resultArr['msg'],
+            'status_info' => $statusInfo,
+            'response' => json_encode($resultArr['data']),
+        ]);
+    }
+}

+ 43 - 0
app/Services/VideoService.php

@@ -0,0 +1,43 @@
+<?php
+namespace App\Services;
+
+use App\Models\Video;
+use App\Traits\PageTrait;
+
+class VideoService
+{
+    use PageTrait;
+
+    public function getList($condition, $sort = 'rank', $sortable = 'desc')
+    {
+    
+        $query = Video::query();
+        $query->select(
+            'id',
+            'name',
+            'cover',
+            'subtitle',
+            'content',
+            'rank',
+            'first_id'
+        );
+        //->where('platform','psy_center');
+        //搜索
+        if(isset($condition['keywords']) && $condition['keywords']) {
+            $query->where('name', 'like', '%'.$condition['keywords'].'%');
+        }
+        if(isset($condition['category_id']) && $condition['category_id']) {
+            $query->where('first_id', $condition['category_id']);
+        }
+        if(isset($condition['platform']) && $condition['platform']) {
+            $query->where('platform', $condition['platform']);
+        }
+        if(isset($condition['type']) && $condition['type']) {
+            $query->where('type', $condition['type']);
+        }
+        // $query->where('status', 0);
+        //排序
+        $query->orderBy($sort, $sortable);
+        return self::JsonPage($query);
+    }
+}

+ 8 - 0
app/Traits/PageDefaultInterface.php

@@ -0,0 +1,8 @@
+<?php
+namespace App\Traits;
+
+interface PageDefaultInterface
+{
+    const PAGE_SIZE = 15;
+    const PAGE_NAME = 'page';
+}

+ 21 - 0
app/Traits/PageTrait.php

@@ -0,0 +1,21 @@
+<?php
+namespace App\Traits;
+
+use App\Traits\PageDefaultInterface;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Pagination\Paginator;
+
+trait PageTrait
+{
+    public static function JsonPage(Builder $builder, $pageSize = PageDefaultInterface::PAGE_SIZE, $pageName = PageDefaultInterface::PAGE_NAME)
+    {
+        $page =  Paginator::resolveCurrentPage($pageName);
+        $offset = $page == 1 ? 0 : ($page-1) * $pageSize;
+        return [
+            'count'         => $builder->count(),
+            'list'          => $builder->offset($offset)->limit($pageSize)->get()->toArray(),
+            'current_page'  => $page, 
+            'page_size'     => $pageSize
+        ];
+    }
+}

+ 1 - 1
config/cors.php

@@ -15,7 +15,7 @@ return [
     |
     */
 
-    'paths' => ['api/*'],
+    'paths' => ['api/*','statistics_api/*'],
 
     'allowed_methods' => ['*'],
 

+ 16 - 0
config/dashboard.php

@@ -0,0 +1,16 @@
+<?php
+/**
+ * 仪表盘数据统计
+ * 
+ * Author: huangxiaodong
+ * Date: 2019/6/26
+ * Time: 16:08
+ */
+return [
+    /**
+     * 用户增长统计图标字段
+     */
+    'UG' => [
+        'xField' =>  ['社心小程序', '新媒体']
+    ],
+];

+ 10 - 0
config/sms.php

@@ -0,0 +1,10 @@
+<?php
+
+return [
+    'alisms' => [
+        'accessKeyId' => env('SMS_ACCESS_KEY'),
+        'accessKeySecret' => env('SMS_ACCESS_SECRET'),
+        'signName' => env('SMS_SIGN_NAME'),
+        'loginTemplate' => env('SMS_LOGIN_TEMPLATE')
+    ],
+];

+ 51 - 0
public/assets/css/common.css

@@ -0,0 +1,51 @@
+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0}
+fieldset,img{border:0;}
+/*看情况要不要用*/
+:focus{outline:0}
+/*触发ie6*/
+a ,a:hover {zoom:1}
+address,caption,cite,code,dfn,em,th,var,optgroup{font-style:normal;font-weight:normal}
+h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}
+abbr,acronym{border:0;font-variant:normal}
+input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit}
+code,kbd,samp,tt{font-size:100%}
+input,button,textarea,select{*font-size:100%}
+
+html{font-size: 12px;}
+body{line-height:150%; font-size:10px; letter-spacing:1px;font-family:"Microsoft YaHei"; color: #333333 }
+ol,ul{list-style:none}
+table{border-collapse:collapse;border-spacing:0}
+caption,th{text-align:left}
+sup,sub{font-size:100%;vertical-align:baseline}
+:link,:visited ,ins{text-decoration:none}
+blockquote,q{quotes:none}
+blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}
+a{ color:#333333}
+a:hover{color:#027B3D}
+/*快捷CLASS*/
+.f{ float:left}
+.r{ float:right}
+.tl { text-align:left}
+.tc { text-align:center}
+.tr { text-align:right}
+.cer{ margin:0px auto;}
+.c{ clear:both}
+.ovh{ overflow:hidden}
+.curp{ cursor:pointer}
+.disb{ display:block}
+.disn{ display:none}
+.word_wrap{word-wrap:break-word; word-break:normal;}/*强制换行*/
+.resize{resize: none;}/*textarea 文本框禁止拖动改变大小*/
+.w{ width:100%}
+.h{ height:100%}
+.vh{height: 100vh;}
+
+.t8{ font-size:8px}
+.t12{ font-size:12px}
+.t14{ font-size:14px}
+.t16{ font-size:16px}
+.t18{ font-size:18px}
+.t20{ font-size:20px}
+.t24{ font-size:24px}
+.b{ font-weight:bold}
+.clearfloat{clear:both;height:0;font-size: 1px;line-height: 0px;}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/css/element_ui.css


BIN
public/assets/css/fonts/element-icons.ttf


BIN
public/assets/css/fonts/element-icons.woff


BIN
public/assets/images/dashboard/bak/._left_1b.png.bak


BIN
public/assets/images/dashboard/bak/._right_icon1.png


BIN
public/assets/images/dashboard/bak/._right_icon2.png


BIN
public/assets/images/dashboard/bak/._right_icon3.png


BIN
public/assets/images/dashboard/bak/._right_icon4.png


BIN
public/assets/images/dashboard/bak/left_1b.png.bak


BIN
public/assets/images/dashboard/bak/right_icon1.png


BIN
public/assets/images/dashboard/bak/right_icon2.png


BIN
public/assets/images/dashboard/bak/right_icon3.png


BIN
public/assets/images/dashboard/bak/right_icon4.png


BIN
public/assets/images/dashboard/left_1b.png


BIN
public/assets/images/dashboard/left_icon1.png


BIN
public/assets/images/dashboard/left_icon2.png


BIN
public/assets/images/dashboard/left_icon3.png


BIN
public/assets/images/dashboard/right_icon1.png


BIN
public/assets/images/dashboard/right_icon2.png


BIN
public/assets/images/dashboard/right_icon3.png


BIN
public/assets/images/dashboard/right_icon4.png


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/js/element_ui.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 7 - 0
public/assets/js/highcharts.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 10 - 0
public/assets/js/vue.js


BIN
resources/assets/css/fonts/element-icons.ttf


BIN
resources/assets/css/fonts/element-icons.woff


+ 1 - 0
resources/assets/js/app.js

@@ -0,0 +1 @@
+require('./bootstrap');

+ 28 - 0
resources/assets/js/bootstrap.js

@@ -0,0 +1,28 @@
+window._ = require('lodash');
+
+/**
+ * We'll load the axios HTTP library which allows us to easily issue requests
+ * to our Laravel back-end. This library automatically handles sending the
+ * CSRF token as a header based on the value of the "XSRF" token cookie.
+ */
+
+window.axios = require('axios');
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+
+/**
+ * Echo exposes an expressive API for subscribing to channels and listening
+ * for events that are broadcast by Laravel. Echo and event broadcasting
+ * allows your team to easily build robust real-time web applications.
+ */
+
+// import Echo from 'laravel-echo';
+
+// window.Pusher = require('pusher-js');
+
+// window.Echo = new Echo({
+//     broadcaster: 'pusher',
+//     key: process.env.MIX_PUSHER_APP_KEY,
+//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
+//     forceTLS: true
+// });

Dosya farkı çok büyük olduğundan ihmal edildi
+ 10 - 0
resources/assets/js/vue.js


+ 4 - 0
resources/views/admin/partials/create_button.blade.php

@@ -0,0 +1,4 @@
+    <!-- resources/views/admin/partials/create_button.blade.php -->
+    <a class="btn btn-create" href="{{ admin_url('create') }}" style="background-color: #4CAF50; color: white; border-radius: 5px; padding: 10px 20px; font-size: 16px;">
+        创建11111
+    </a>

+ 736 - 0
resources/views/admin/partials/dashboard.blade.php

@@ -0,0 +1,736 @@
+<script src="/assets/js/vue.js"></script>
+<script src="/assets/js/highcharts.js"></script>
+<script src="/assets/js/element_ui.js"></script>
+<!-- <link rel="stylesheet" href="/assets/css/common.css"> -->
+<link rel="stylesheet" href="/assets/css/element_ui.css">
+    <div id="container" style="height:100%">
+        <template>
+          <div class="main w vh" >
+            <div class="left">
+              <div class="content_1">
+               
+                <div class="section_3">
+                  <div class="section_3_con">
+                    <div class="section_3_1"></div>
+                    <div class="section_3_2">
+                      <em>关注人数:[[pageData.users]]人</em>
+                      <span>注册时间:2023年09月28日</span>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="content_2">
+                <div class="section h">
+                  <img src="assets/images/dashboard/left_1b.png" style="max-width: 100%;">
+                </div>
+              </div>
+            </div>
+            <!--右侧开始-->
+            <div class="right">
+              
+              <div class="section_2">
+                <div class="section_2_1">
+                  <div class="section_2_1_l">
+                    <div class="content">
+                      <div class="section_2_head">
+                        <div class="title">心理评估人次</div>
+                        <el-select class="select" size='mini' clearable v-model="cpColumnId" filterable placeholder="近7日" @change="cpChange($event)">
+                          <el-option v-for="item in columnList" :key="item.id" :label="item.name" :value="item.id">
+                          </el-option>
+                        </el-select>
+                      </div>
+                      <div class="data" style="height: 95%;">
+                        <div id="chart5" :options="chartOptions5" style="width: 100%; height: 100%; border-radius: 15px;">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="section_2_1_r">
+                    <div class="li">
+                      <div class="li_content">
+                        <img src="/assets/images/dashboard/right_icon1.png" alt="" srcset="" class="img">
+                        <div class="name">评估总人次</div>
+                        <div class="number">[[pageData.cp]]<span>人</span></div>
+                      </div>
+                    </div>
+                    <div class="li">
+                      <div class="li_content">
+                        <img src="/assets/images/dashboard/right_icon2.png" alt="" srcset="" class="img">
+                        <div class="name">科普总人次</div>
+                        <div class="number">[[pageData.kp]]<span>人</span></div>
+                      </div>
+                    </div>
+                    <div class="li">
+                      <div class="li_content">
+                        <img src="/assets/images/dashboard/right_icon3.png" alt="" srcset="" class="img">
+                        <div class="name">疗愈总人次</div>
+                        <div class="number">[[pageData.xl]]<span>人</span></div>
+                      </div>
+                    </div>
+                    <div class="li">
+                      <div class="li_content">
+                        <img src="/assets/images/dashboard/right_icon4.png" alt="" srcset="" class="img">
+                        <div class="name">挂号总人次</div>
+                        <div class="number">[[pageData.gh]]<span>人</span></div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+                <div class="section_2_2">
+                  <div class="section_2_2_l">
+                    <div class="content">
+                      <div class="section_2_head">
+                        <div class="title">心理科普人次</div>
+                        <el-select class="select" size='mini' clearable v-model="cpColumnId" filterable placeholder="近7日" @change="cpChange($event)">
+                          <el-option v-for="item in columnList" :key="item.id" :label="item.name" :value="item.id">
+                          </el-option>
+                        </el-select>
+                      </div>
+                      <div class="data"  style="height: 95%;">
+                        <div id="chart3" :options="chartOptions3" style="width: 100%; height: 100%; border-radius: 15px;">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <div class="section_2_2_r">
+                    <div class="content">
+                      <div class="section_2_head">
+                        <div class="title">心理疗愈人次</div>
+                        <el-select class="select" size='mini' clearable v-model="cpColumnId" filterable placeholder="近7日" @change="cpChange($event)">
+                          <el-option v-for="item in columnList" :key="item.id" :label="item.name" :value="item.id">
+                          </el-option>
+                        </el-select>
+                      </div>
+                      <div class="data"  style="height: 95%;">
+                        <div id="chart1" :options="chartOptions1" style="width: 100%; height: 100%; border-radius: 15px;">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </template>
+    </div>
+
+    <script>
+        // 创建 Vue 实例
+        const app = new Vue({
+            delimiters:['[[',']]'],
+            el: '#container',
+            data() {
+                return {
+                    apiUrl: '{{config("app.url")}}', // 替换为实际 API URL
+                    successCode: '200', // 替换为实际成功代码
+                    chartOptions1: { accessibility: { enabled: false }},
+                    chartOptions3: { accessibility: { enabled: false }},
+                    chartOptions5: { accessibility: { enabled: false }},
+                    cpColumnId: 1,
+                    kpColumnId: 1,
+                    xlColumnId: 1,
+                    columnList: [{
+                      "id": 1,
+                      "name": "近7日"
+                    }, {
+                      "id": 2,
+                      "name": "2023年"
+                    }],
+
+                    pageData: {},
+                };
+            },
+            mounted() {
+                this.getPageData();
+            },
+            methods: {
+              cpChange:function(value){
+                let _this = this;
+                _this.cpColumnId = value;
+                _this.getPageData();
+              },
+              kpChange:function(value){
+                let _this = this;
+                _this.kpColumnId = value;
+                _this.getPageData();
+              },
+              xlChange:function(value){
+                let _this = this;
+                _this.xlColumnId = value;
+                _this.getPageData();
+              },
+              //获取页面数据
+              getPageData: function() {
+                let _this = this;
+                // this.$http.get(this.apiUrl + 'statistics_api/index', {
+                //   params: {
+                //     cp_type:_this.cpColumnId,
+                //     kp_type:_this.kpColumnId,
+                //     xl_type:_this.xlColumnId,
+                //   },
+                // }).then(
+                //   function(res) {
+                //     _this.pageData = res.body;
+                //     _this.columnList = _this.pageData.years;
+
+                //     this.tj1(_this.pageData.xl_tj.categories, _this.pageData.xl_tj.series);
+                //     this.tj3(_this.pageData.kp_tj.categories, _this.pageData.kp_tj.series);
+                //     this.tj5(_this.pageData.cp_tj.categories, _this.pageData.cp_tj.series);
+
+                //   },
+                //   function(res) {
+                //     console.log(res);
+                //     // 响应错误回调
+                //   }
+                // );
+
+                fetch(`${this.apiUrl}statistics_api/index?cp_type=${this.cpColumnId}&kp_type=${this.kpColumnId}&xl_type=${this.xlColumnId}`)
+                    .then(response => {
+                        if (!response.ok) {
+                            throw new Error(`HTTP error! status: ${response.status}`);
+                        }
+                        return response.json();
+                    })
+                    .then(data => {
+                        this.pageData = data;
+                        this.columnList = this.pageData.years;
+                        // this.updateCharts();
+                        // _this.pageData = data.body;
+                        // _this.columnList = _this.pageData.years;
+
+                        this.tj1(_this.pageData.xl_tj.categories, _this.pageData.xl_tj.series);
+                        this.tj3(_this.pageData.kp_tj.categories, _this.pageData.kp_tj.series);
+                        this.tj5(_this.pageData.cp_tj.categories, _this.pageData.cp_tj.series);
+                        console.log(data);
+                    })
+                    .catch(error => {
+                        console.error('Fetch error:', error);
+                        
+                    });
+
+
+
+              },
+
+              // 训练人次统计
+              tj1: function(categories, series) {
+                this.chartOptions1 = {
+                  exporting: {
+                    enabled: false //用来设置是否显示‘打印’,'导出'等
+                  },
+                  title: {
+                    text: '',
+                    align: 'left',
+                    style: {
+                      color: '#2AB1FE',
+                      fontSize: '15px',
+                      fontWeight: '900'
+                    }
+                  },
+                  credits: {
+                    enabled: false,
+                  },
+                  yAxis: {
+                    title: {
+                      text: ''
+                    }
+                  },
+                  xAxis: {
+                    categories: categories
+                  },
+
+                  legend: {
+                    layout: 'vertical',
+                    align: 'right',
+                    verticalAlign: 'middle',
+                    enabled: false
+                  },
+                  plotOptions: {
+                    // series: {
+                    // 	borderWidth: 0,
+                    // 	dataLabels: {
+                    // 		enabled: true,
+                    // 		format: '{point.y:.1f}%'
+                    // 	}
+                    // }
+                  },
+                  series: [{
+                    type: 'column',
+                    name: '人次',
+                    colorByPoint: true,
+                    data: series,
+                    colors: ['#2AB1FE ', '#2AB1FE', '#2AB1FE', '#2AB1FE', '#2AB1FE', ' #2AB1FE', ' #2AB1FE']
+                  }],
+                  responsive: {
+                    rules: [{
+                      condition: {
+                        // maxWidth: 500
+                      },
+                      chartOptions: {
+                        legend: {
+                          layout: 'horizontal',
+                          align: 'center',
+                          verticalAlign: 'bottom'
+                        }
+                      }
+                    }]
+                  }
+                }
+                $('#chart1').highcharts(this.chartOptions1)
+              },
+              // 科普人次统计
+              tj3: function(categories, series) {
+                this.chartOptions3 = {
+                  exporting: {
+                    enabled: false //用来设置是否显示‘打印’,'导出'等
+                  },
+                  title: {
+                    text: '',
+                    align: 'left',
+                    style: {
+                      color: '#2AB1FE',
+                      fontSize: '15px',
+                      fontWeight: '900'
+                    }
+                  },
+                  credits: {
+                    enabled: false,
+                  },
+                  yAxis: {
+                    title: {
+                      text: ''
+                    }
+                  },
+                  xAxis: {
+                    categories: categories
+                  },
+
+                  legend: {
+                    layout: 'vertical',
+                    align: 'right',
+                    verticalAlign: 'middle',
+                    enabled: false
+                  },
+                  plotOptions: {
+                    // series: {
+                    // 	borderWidth: 0,
+                    // 	dataLabels: {
+                    // 		enabled: true,
+                    // 		format: '{point.y:.1f}%'
+                    // 	}
+                    // }
+                  },
+                  series: [{
+                    type: 'column',
+                    name: '人次',
+                    colorByPoint: true,
+                    data: series,
+                    colors: ['#2AB1FE ', '#2AB1FE', '#2AB1FE', '#2AB1FE', '#2AB1FE', ' #2AB1FE', ' #2AB1FE']
+                  }],
+                  responsive: {
+                    rules: [{
+                      condition: {
+                        // maxWidth: 500
+                      },
+                      chartOptions: {
+                        legend: {
+                          layout: 'horizontal',
+                          align: 'center',
+                          verticalAlign: 'bottom'
+                        }
+                      }
+                    }]
+                  }
+                }
+                $('#chart3').highcharts(this.chartOptions3)
+
+              },
+              // 测评人次统计
+              tj5: function(categories, series) {
+                this.chartOptions5 = {
+                  exporting: {
+                    enabled: false //用来设置是否显示‘打印’,'导出'等
+                  },
+                  title: {
+                    text: '',
+                    align: 'left',
+                    style: {
+                      color: '#2AB1FE',
+                      fontSize: '15px',
+                      fontWeight: '900'
+                    }
+                  },
+                  credits: {
+                    enabled: false,
+                  },
+                  yAxis: {
+                    title: {
+                      text: ''
+                    }
+                  },
+                  xAxis: {
+                    categories: categories
+                  },
+                  legend: {
+                    layout: 'vertical',
+                    align: 'right',
+                    verticalAlign: 'middle',
+                    enabled: false
+                  },
+                  plotOptions: {
+                    series: {
+                      label: {
+                        connectorAllowed: true
+                      },
+                    }
+                  },
+                  series: [{
+                    name: '人次',
+                    data: series,
+                    color: '#2AB1FE',
+                    dataLabels: {
+                      enabled: true,
+                    },
+                    areaStyle: {}
+                  }],
+                  responsive: {
+                    rules: [{
+                      condition: {
+                        // maxWidth: 500
+                      },
+                      chartOptions: {
+                        legend: {
+                          layout: 'horizontal',
+                          align: 'center',
+                          verticalAlign: 'bottom'
+                        }
+                      }
+                    }]
+                  }
+                }
+                $('#chart5').highcharts(this.chartOptions5)
+
+              },
+             
+            }
+        });
+    </script>
+
+<style>
+  .el-input__inner,.el-select-dropdown__item.selected{
+    color:#2AB1FE
+  }
+  .el-select .el-input.is-focus .el-input__inner {
+      border-color: #2AB1FE;
+  }
+</style>
+
+<style scoped="scoped" lang="scss">
+  .main {
+    display: flex;
+    flex-direction: row;
+    background-color: #f0f4f7;
+    height:100%;
+
+    .left {
+      width: 24%;
+      background: #E3E9ED;
+      display: flex;
+      flex-direction: column;
+
+      .content_1 {
+        padding: 26px 26px 0 26px;
+
+        .section_1 {
+          font-size: 18px;
+          line-height: 18px;
+          font-weight: bold;
+          color: #181917;
+          margin-bottom: 8px;
+        }
+
+        .section_2 {
+          font-size: 12px;
+          line-height: 12px;
+          font-weight: 400;
+          color: #565954;
+          margin-bottom: 23px;
+        }
+
+        .section_3 {
+          width: 100%;
+          height: 88%;
+          background: #FFFFFF;
+          border-radius: 14px;
+          margin-bottom: 18px;
+
+          .section_3_con {
+            padding: 0 18px;
+            display: flex;
+            flex-direction: row;
+
+            .section_3_1 {
+              width: 70px;
+              height: 70px;
+              background: url("/assets/images/dashboard/left_icon1.png") no-repeat;
+              background-size: 100%;
+              margin-right: 17px;
+            }
+
+            .section_3_2 {
+              display: flex;
+              flex-direction: column;
+              align-items: center;
+              justify-content: center;
+
+              em {
+                font-size: 16px;
+                line-height: 16px;
+                font-weight: bold;
+                color: #565954;
+                margin-bottom: 9px;
+              }
+
+              span {
+                font-size: 12px;
+                line-height: 12px;
+                font-weight: 400;
+                color: #939F8F;
+              }
+            }
+          }
+        }
+      }
+
+      .content_2 {
+        padding: 0 20px 20px 20px;
+        flex-grow: 1;
+
+        .section {
+          /* height: 100%; */
+          display: flex;
+          justify-content: center;
+          background: url("/assets/images/dashboard/left_1b.png") no-repeat center;
+          background-size: auto 100%;
+        }
+      }
+    }
+
+    .right {
+      display: flex;
+      flex-direction: column;
+      width: 76%;
+
+      .section_1 {
+        display: flex;
+        flex-direction: row;
+        justify-content: space-between;
+        align-items: center;
+        padding: 0 26px;
+        height: 70px;
+        background: #2AB1FE;
+        color: #FFFFFF;
+        font-size: 12px;
+
+        .logo {
+          display: flex;
+          width: 300px;
+          height: 60px;
+          background-image: url("./dashboard/images/logo2.png");
+          background-repeat: no-repeat;
+          background-size: 100%;
+          background-position: 0 center;
+        }
+
+        .fn {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
+
+          .head {
+            display: flex;
+            flex-direction: row;
+            align-items: center;
+            margin-right: 25px;
+
+            .img {
+              background: url("./dashboard/images/head.png") no-repeat;
+              background-size: 26px;
+              width: 26px;
+              height: 26px;
+              margin-right: 5px;
+            }
+          }
+
+          .login_out {
+            display: flex;
+            flex-direction: row;
+            align-items: center;
+
+            .img {
+              background: url("./dashboard/images/login_out.png") no-repeat;
+              background-size: 14px;
+              width: 14px;
+              height: 14px;
+              margin-right: 5px;
+            }
+          }
+        }
+      }
+
+      .section_2 {
+        padding: 26px;
+        flex-grow: 1;
+        display: flex;
+        flex-direction: column;
+
+        .section_2_head {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          height: 25px;
+          margin-bottom: 10px;
+
+          .title {
+            font-size: 16px;
+            font-weight: bold;
+            color: #181917;
+          }
+
+          .select {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 88px;
+            height: 25px;
+            font-weight: 400;
+          }
+        }
+
+        .section_2_1 {
+          display: flex;
+          flex-direction: row;
+          justify-content: space-between;
+          margin-bottom: 26px;
+          height: 403px;
+
+          .section_2_1_l {
+            display: flex;
+            width: 67%;
+            height: 403px;
+            background: #FFFFFF;
+            border-radius: 14px;
+            margin-right: 26px;
+
+            .content {
+              width:100%;
+              display: flex;
+              flex-direction: column;
+              flex: 1;
+              padding: 26px;
+
+              .data {
+                flex-grow: 1;
+              }
+            }
+          }
+
+          .section_2_1_r {
+            display: flex;
+            width: 33%;
+            flex-wrap: wrap;
+            justify-content: space-between;
+            align-items: center;
+
+            .li {
+              display: flex;
+              width: 48%;
+              height: 193px;
+              border-radius: 14px;
+              background: #FFFFFF;
+
+              .li_content {
+                padding: 26px;
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+
+                .img {
+                  width: 53px;
+                  height: 53px;
+                  margin-bottom: 10px;
+                }
+
+                .name {
+                  font-size: 14px;
+                  font-weight: 400;
+                  color: #565954;
+                  margin-bottom: 20px;
+                }
+
+                .number {
+                  font-size: 32px;
+                  font-family: Arial;
+                  font-weight: bold;
+                  color: #2AB1FE;
+
+                  span {
+                    font-size: 12px;
+                    font-family: Arial;
+                    color: #2AB1FE;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        .section_2_2 {
+          display: flex;
+          flex-direction: row;
+          flex-grow: 1;
+          justify-content: space-between;
+
+          .section_2_2_l {
+            display: flex;
+            width: 49%;
+            background: #FFFFFF;
+
+            .content {
+              width:100%;
+              display: flex;
+              flex-direction: column;
+              flex: 1;
+              padding: 26px;
+
+              .data {
+                flex-grow: 1;
+              }
+            }
+          }
+
+          .section_2_2_r {
+            display: flex;
+            width: 49%;
+            background: #FFFFFF;
+
+            .content {
+              width:100%;
+
+              display: flex;
+              flex-direction: column;
+              flex: 1;
+              padding: 26px;
+
+              .data {
+                flex-grow: 1;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+</style>
+

+ 19 - 0
resources/views/admin/partials/footer.blade.php

@@ -0,0 +1,19 @@
+<!-- Main Footer -->
+<footer class="main-footer">
+    <!-- To the right -->
+    <!-- <div class="pull-right hidden-xs">
+        @if(config('admin.show_environment'))
+            <strong>Env</strong>&nbsp;&nbsp; {!! config('app.env') !!}
+        @endif
+
+        &nbsp;&nbsp;&nbsp;&nbsp;
+
+        @if(config('admin.show_version'))
+        <strong>Version</strong>&nbsp;&nbsp; {!! \Encore\Admin\Admin::VERSION !!}
+        @endif
+
+    </div> -->
+    <div style="font-size:12px; margin:auto, 5px;text-align:center">技术支持:成都快乐小清智能科技有限公司</div>
+    <!-- Default to the left -->
+    <!-- <strong>Powered by <a href="https://github.com/z-song/laravel-admin" target="_blank">laravel-admin</a></strong> -->
+</footer>

+ 11 - 9
resources/views/admin/sso/index.blade.php

@@ -4,7 +4,7 @@
 	<meta name="apple-mobile-web-app-status-bar-style" content="black">
 	<meta content="telephone=no" name="format-detection">
 	<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover">
-	<title>{{config('admin.name')}}</title>
+	<title>{{$menuResult["data"]['title']}}</title>
 	<link rel="stylesheet" href="/oss/css/style.css">
 	<script src="/oss/js/jquery-3.7.1.min.js"></script>
 </head>
@@ -12,9 +12,9 @@
 
 <div class="header sp_ends">
 	<div class="vertical_dq">
-		<div class="logo"><img src="/oss/images/logo2.png"></div>
+		<div class="logo"><img src="{{$menuResult['data']['logo']}}"></div>
 	</div>
-	<div class="title">{{config('admin.name')}}</div>
+	<div class="title">{{$menuResult["data"]["title"]}}</div>
 	<div class="vertical_dq">
 		<div class="pulldown nav">
 			<div class="dt vertical_dq">
@@ -23,7 +23,7 @@
 			</div>
 			<div class="dd">
 				<ul>
-					<li><a href="{{admin_url('auth/logout') }}">退出登录</a></li>
+					<li><a href="{{$menuResult['data']['login_out_url']}}">退出登录</a></li>
 				</ul>
 			</div>
 		</div>
@@ -46,14 +46,16 @@
 				@if (isset($item["sub"]))<i></i>@endif
 			</li>
 			@if (isset($item["sub"]))
-				@foreach($item["sub"] as $val)
-					<div class="menu-con">
-						<a url="{{$val["menu_url"]}}" class="Level_2">{{$val['title']}}</a>
+					<div class="menu-con">				
+						@foreach($item["sub"] as $val)
+							<a url="{{$val["menu_url"]}}" class="Level_2">{{$val['title']}}</a>
+						@endforeach
+
 					</div>
-				@endforeach
 			@else
 			@endif
 		@endforeach
+		
 	</ul>
 </div>
 <div class="main-r">
@@ -94,7 +96,7 @@
 					name="iframe"
 					height="100%"
 					width="100%"
-					src="{{$indexUrl}}"
+					src="{{$indexUrl}}?{{$paramUrl}}"
 					scrolling="auto"
 					frameborder="0"
 					onload="changeFrameHeight()">

+ 273 - 0
resources/views/layouts/app.blade.php

@@ -0,0 +1,273 @@
+<?php
+
+use App\Facades\RemoteSsoFacade;
+use Encore\Admin\Facades\Admin;
+
+$userModelClass = config('admin.database.users_model');
+
+// $userModel = new $userModelClass();
+
+// $checkUserData = $userModel::query()
+// // ->where(
+// //     array(
+// //         "third_openid"=>$signData["open_id"],
+// //     )
+// // )
+// ->orderBy("id","desc")->first();
+// Admin::guard()->login(
+//     $checkUserData
+// );
+$menuResult = RemoteSsoFacade::getUserMenuWebsiteData(Admin::user()->third_openid);//$thirdOpenid);
+// print_r($menuResult);exit;
+?>
+<!DOCTYPE html>
+<!-- saved from url=(0058)https://whdx-psy.qingerai.com/index.php?ap=visit&task=main -->
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+<title>武汉大学中南医院</title>
+<link rel="icon" type="image/png" href="https://whdx-psy.qingerai.com/image/favicon.png">
+<meta name="apple-mobile-web-app-title" content="武汉大学中南医院">
+<meta name="csrf-token" content="{{ csrf_token() }}">
+
+
+<script type="text/javascript" src="https://whdx-psy.qingerai.com/include/js/jquery-3.6.0.min.js"></script>
+<script type="text/javascript" src="https://whdx-psy.qingerai.com/include/js/jquery-3.6.0.min.jslayer.js"></script>
+<link rel="stylesheet" href="{{ asset('css/layer.css')}}" id="layuicss-layer">
+<script type="text/javascript" src="{{ asset('js/public.js')}}"></script>
+<link rel="stylesheet" href="{{ asset('css/style.css')}}">
+<!-- <link rel="stylesheet" href="{{ asset('css/style_new.css')}}"> -->
+<link rel="stylesheet" href="{{ asset('css/sso_style.css')}}">
+<link rel="bookmark" type="image/x-icon" href="https://whdx-psy.qingerai.com/web/_subsite/1/template/web/zh-cn/default/visit/image/favicon.ico">
+<link rel="shortcut icon" type="image/x-icon" href="https://whdx-psy.qingerai.com/web/_subsite/1/template/web/zh-cn/default/visit/image/favicon.ico">
+<script>
+    var js_include_dir = '/include/js/',
+        is_start_picture_distribution = "1";
+</script>
+<script type="text/javascript">
+function openNotice(data)
+{
+    //检查是否掉登录
+    var checkLanding = $().checkLanding({
+        jump_url:'/index.php?ap=visit&task=loginOut',
+        check_url:'/index.php?ap=visit&task=checkLanding',
+        second:3
+    });
+    if(checkLanding.success){
+        if(checkLanding.is_out) return false;
+    }else{
+        layer.msg(checkLanding.msg);
+        return false;
+    }
+
+    //示范一个公告层
+    layer.open({
+        type: 1
+        ,title: false //不显示标题栏
+        ,closeBtn: false
+        ,area: '300px;'
+        ,shade: 0.8
+        ,id: 'relogin' //设定一个id,防止重复弹出
+        ,btn: ['立即重新登陆']
+        ,btnAlign: 'c'
+        ,moveType: 1 //拖拽模式,0或者1
+        ,content: '<div style="padding: 50px; line-height: 22px; background-color: #393D49; color: #fff; font-weight: 300;">恭喜您已完成更新<br><br>系统将自动为您重新登录</div>'
+        ,yes: function(){
+            window.location.href="/index.php?ap=visit&task=gridTokenLogin&ocu_id="+data.id+"&token="+data.token+"&check_time="+data.check_time
+        }
+    });
+}
+</script></head>
+<body class="home-bg">
+
+<div class="header sp_ends">
+  <div class="vertical_dq">
+  <div class="logo" style="height:53px"><img src="{{$menuResult['data']['logo']}}"></div>
+  </div>
+  <div class="title">{{$menuResult["data"]["title"]}}</div>
+  <div class="vertical_dq">
+    <div class="pulldown nav">
+        <div class="dt vertical_dq">
+            <img class="wh_30 bor_circle" src="{{asset('image/tx.png')}}">
+            <div>{{Admin::user()->name}}</div>
+        </div>
+        <div class="dd">
+            <ul>
+            <li><a href="{{$menuResult['data']['login_out_url']}}">退出登录</a></li>
+            </ul>
+        </div>
+    </div>
+    <div class="byl-icon-bell_alt mar_l16"><i></i></div>
+  </div>
+</div>
+
+<div class="menu-box">
+  <div class="menu-logo">
+    <img src="{{$menuResult['data']['site_logo']?:''}}">
+    <div class="txt" style = "line-height: 1.2;font-size: 16px;color: #049985;font-weight: bold;text-align:center">{{Admin::user()->workstation_name}}</div>
+
+</div>
+  <ul class="new_menu">
+  @foreach($menuResult["menu"] as $key=>$item)
+			<li class="Level_1" url="{{$item["menu_url"]}}"><span class="{{$item["menu_style"]}}" ></span>{{$item['title']}}
+				@if (isset($item["sub"]))<i></i>@endif
+			</li>
+			@if (isset($item["sub"]))
+					<div class="menu-con">
+                        @foreach($item["sub"] as $val)
+
+						<a url="{{$val["menu_url"]}}" class="Level_2">{{$val['title']}}</a>
+                        @endforeach
+
+					</div>
+			@else
+			@endif
+    @endforeach
+  </ul>
+</div>
+
+<div class="main-r right-header">
+<div >
+    <!-- <ul>
+
+    </ul> -->
+</div>
+
+<br/>
+@yield('content')
+</div>
+
+
+<script type="text/javascript">
+  function changeFrameHeight() {
+		var iframe = document.getElementById("iframe");
+		iframe.height = document.documentElement.clientHeight;
+	}
+	//onresize属性可以用来获取或设置当前窗口的resize事件的事件处理函数
+	//onresize事件会在窗口或框架被调整大小时发生
+	window.onresize = function() {
+		changeFrameHeight();
+	}
+	// 折叠菜单
+	$(".menu-box li").click(function(){
+		$(this).toggleClass("active").next(".menu-con").slideToggle(200).siblings(".menu-con").slideUp("slow");
+		$(this).siblings().removeClass("active");
+		$(".menu-con a").removeClass("active");
+	});
+	$(".menu-con a").click(function(){
+		$(this).addClass('active').siblings().removeClass('active');
+	})
+	$(".Level_1").click(function(){
+		var _this = $(this);
+		var url = _this.attr("url");
+		if (!_this.next().is(".menu-con")){
+			window.location.href = url;
+		}
+	})
+	$(".Level_2").click(function(){
+		var url = $(this).attr("url");
+		window.location.href = url;
+	})
+	$(".pulldown").each(function(){
+		var s=$(this);
+		var dt=$(this).children(".dt");
+		var dd=$(this).children(".dd");
+		var _show=function(){dd.slideDown(200);dt.addClass("cur");};
+		var _hide=function(){dd.slideUp(200);dt.removeClass("cur");};
+		dt.click(function(){dd.is(":hidden")?_show():_hide();});
+		$("body").click(function(i){ !$(i.target).parents(".pulldown").first().is(s) ? _hide():"";
+		});
+	});
+</script>
+
+    <!-- <ul class="left-nav scroll-bar" id="ul_content">
+        <li id="" class="menu_li active" data-menu_id="1"><a href="https://whdx-psy.qingerai.com/index.php?ap=visit&task=main" target="main"><div class="ico icon4"></div><div class="txt">工作站管理</div></a></li>
+        <li id="customerListMenu" class="menu_li" data-menu_id="2"><a href="https://whdx-psy.qingerai.com/index.php?ap=visit&task=main" target="main"><div id="menu_visit_interflow" style=""></div><div class="ico icon2"></div><div class="txt">心理档案</div></a></li>
+        <li id="screeningTaskList" class="menu_li" data-menu_id="3"><a href="https://whdx-psy.qingerai.com/index.php?ap=visit&task=main" target="main"><div id="menu_visit_interflow" style="display:none;"><div class="number">0</div></div><div class="ico icon5"></div><div class="txt">筛查任务</div></a></li>
+        <li id="" class="menu_li" data-menu_id="4"><a href="https://whdx-psy.qingerai.com/index.php?ap=visit&task=main" target="main"><div class="ico icon1"></div><div class="txt">质控管理</div></a></li>
+        <li id="" class="menu_li" data-menu_id="5"><a href="https://whdx-psy.qingerai.com/index.php?ap=visit&task=main" target="main"><div class="ico icon6"></div><div class="txt">统计分析</div></a></li>
+        <div class="disable"><a href="javascript:void(0);"><div class="byl-icon-lock" title="无权限操作"></div><div class="ico icon5"></div><div class="txt">在线咨询</div></a></div>
+    </ul> -->
+    <!-- <div class="header sp_ends">
+        <div class="vertical_dq">
+            <div class="logo"><img src="/oss/images/logo2.png"></div>
+        </div>
+        <div class="title">{{config('admin.name')}}</div>
+        <div class="vertical_dq">
+            <div class="pulldown nav">
+                <div class="dt vertical_dq">
+                    <img class="wh_30 bor_circle" src="{{Admin::user()->avatar}}">
+                    <div>{{Admin::user()->name}}</div>
+                </div>
+                <div class="dd">
+                    <ul>
+                        <li><a href="{{admin_url('auth/logout') }}">退出登录</a></li>
+                    </ul>
+                </div>
+            </div>
+            @if (isset($menuResult["msg"]) && $menuResult["msg"]["num"]>0)
+                <a href="{{$menuResult["msg"]["url"]}}">
+                    <div class="byl-icon-bell_alt mar_l16"><i></i></div>
+                </a>
+            @endif
+        </div>
+    </div>
+
+<div class="menu-box">
+<div class="menu-logo">
+        <img src="https://hw-bj-resource.qingerai.com/group1/M00/00/04/CgAAAmckZvSARO8-AAAbJnMNjrc539.jpg">
+        <div class="txt" style = "line-height: 1.2;font-size: 16px;color: #049985;font-weight: bold;text-align:center">{{Admin::user()->workstation_name}}</div>
+    </div>
+	<ul class="new_menu">
+		@foreach($menuResult["menu"] as $key=>$item)
+			<li class="Level_1" url="{{$item["menu_url"]}}"><span class="{{$item["menu_style"]}}" ></span>{{$item['title']}}
+				@if (isset($item["sub"]))<i></i>@endif
+			</li>
+			@if (isset($item["sub"]))
+				@foreach($item["sub"] as $val)
+					<div class="menu-con">
+						<a url="{{$val["menu_url"]}}" class="Level_2">{{$val['title']}}</a>
+					</div>
+				@endforeach
+			@else
+			@endif
+		@endforeach
+	</ul>
+</div>
+
+    <div class="right-main">
+        <div class="right-header">
+            <div class="top sp_ends">
+                <div class="float-l vertical_dq">
+                    <div class="logo float-l"><img src="https://hw-fdfs.qingerai.com/group1/M00/A8/FA/rBcAAmbzZa6AeVPPAAAgmgbyoZA040.png"></div>
+                    <div class="title float-l">体检中心</div>-->
+                <!-- </div>
+                <div style="display: flex; align-items: center;">
+                                    <em></em>
+                    <div class="user">
+                        <div class="tx"><img src="https://whdx-psy.qingerai.com//web/_subsite/1/template/web/zh-cn/default/component/image/tx.jpg"></div>
+                        <div class="flex" style="float:left;">
+                            <div class="font-bold font-15">xiaoqing</div>
+                            <div class="color-666 font-12"></div>
+                        </div>
+                    </div>
+
+                    <em></em>
+                    <a href="https://whdx-psy.qingerai.com/index.php?task=loginOut" class="color-orange" target="_top"><i class="ico icon19"></i>退出</a>
+                </div>
+            </div>
+            <div class="tab">
+                <ul> -->
+
+                <!-- </ul>
+            </div>
+            <div class="guide vertical_dq"> -->
+                <!-- <a class="ico icon20 go_back" style="display: none;"></a>
+                <div class="flex">当前位置:<a href="https://whdx-psy.qingerai.com/index.php?task=welcome">首页</a>  &gt; <a href="https://whdx-psy.qingerai.com/index.php?op=team&amp;task=team&amp;mk=4">团体体检</a>  &gt; <a href="https://whdx-psy.qingerai.com/index.php?op=team&amp;task=team&amp;mk=4">体检单位</a> </div>
+                <a class="ico icon21 go_start" style="display: none;"></a> -->
+            <!-- </div>
+        </div> -->
+    <!-- </div>  -->
+
+
+</body>
+</html>

+ 189 - 0
resources/views/psycenter/video.blade.php

@@ -0,0 +1,189 @@
+<!-- resources/views/lectures/index.blade.php -->
+@extends('layouts.app')
+
+@section('title', 'Lecture List')
+
+
+
+@section('tab_nav')
+<li class="active" style ="border-radius:10px 10px 0 0;">
+    <a href="https://whdx-psy.qingerai.com/index.php?op=team&amp;task=team&amp;mk=4">
+        <div id="menu_25" style="display:none;"><div class="number">0</div></div>
+        健康科普
+    </a>
+</li>
+<!-- <li>
+    <a href="https://whdx-psy.qingerai.com/index.php?op=test&amp;task=testList&amp;mk=4">
+        <div id="menu_39" style="display:none;"><div class="number">0</div></div>
+        
+    </a>
+</li> -->
+@endsection
+
+@section('content')
+<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
+<link rel="stylesheet" href="{{asset('css/list.css')}}">
+<div class="container">
+        <!-- 左侧分类区域 -->
+        <div class="left">
+            <div class="head">
+                <div class="title">分类</div>
+                <div class="add" onclick="handleAdd()">新增</div>
+            </div>
+            <div class="search">
+                <input type="text" id="searchInput" placeholder="搜索分类名称" onkeyup="filterCategories()">
+            </div>
+            <div class="list" id="categoryList">
+                <!-- 分类列表将通过JS动态渲染 -->
+                @foreach ($categories as $category)
+                    <!-- <div class="item " 
+                        onclick="">
+                        <img src="https://teen.qingerai.com/js/zTree/css/metroStyle/img/icon-file.gif" alt="icon">
+                        <span style="font-size: 12px;">{{$category['name']}}</span>
+                        
+                            <div class="operations">
+                                <div class="edit" onclick="event.stopPropagation();handleEdit({{$category['id']}})">
+                                    <img src="https://teen.qingerai.com/js/zTree/css/metroStyle/img/icon21.png" alt="edit">
+                                </div>
+                                <div class="delete" onclick="event.stopPropagation();handleDelete({{$category['id']}})">
+                                    <img src="https://teen.qingerai.com/js/zTree/css/metroStyle/img/icon23.png" alt="delete">
+                                </div>
+                            </div>
+                    </div> -->
+                @endforeach
+            </div>
+        </div>
+
+        <!-- 右侧内容区域 -->
+        <div class="right" >
+            <div class="head">
+                <div class="plate-bt">
+                    <div class="plate-bt-item" onclick="handleAddCourse()">新增课程</div>
+                    <div class="plate-bt-item" onclick="handleBatchDelete()">批量删除</div>
+                </div>
+                <div class="plate-nr">
+
+                    <div class="plate-nr-item">
+                        <input type="text" id="courseSearchInput" value ="{{$condition['keywords']}}" placeholder="请输入课程名称">
+                    </div>
+                    <div class="plate-nr-item">
+                        <div class="search-btn" onclick="searchCourses()">搜索</div>
+                    </div>
+                </div>
+            </div>
+            <div class="main">
+                <div class="main-wrap" id="courseList">
+                    <!-- 课程列表将通过JS动态渲染 -->
+                </div>
+                <div id="paginationContainer"></div>
+            </div>
+        </div>
+
+        <!-- 分类编辑弹窗 -->
+        <div class="dialog" id="categoryDialog">
+            <div class="dialog-content">
+                <h3 id="categoryDialogTitle">新增分类</h3>
+                <form id="categoryForm">
+                    <div class="form-item">
+                        <label>分类标题</label>
+                        <input type="text" name="title" placeholder="请输入分类标题">
+                    </div>
+                    <div class="form-item">
+                        <label>排序</label>
+                        <input type="number" name="sort" min="0">
+                        <input type="hidden" name="_token" value="{{ csrf_token() }}" />
+                    </div>
+                    <div class="dialog-footer">
+                        <button type="button" onclick="closeDialog('categoryDialog')">取消</button>
+                        <button type="button" onclick="submitCategoryForm()">确定</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+
+        <!-- 课程编辑弹窗 -->
+        <div class="dialog" id="courseDialog">
+            <div class="dialog-content">
+                <h3 id="courseDialogTitle">新增课程</h3>
+                <form id="courseForm">
+                    <div class="form-item">
+                        <label>文章分类</label>
+                        <select name="categoryId" required>
+                            <!-- 分类选项将通过JS动态渲染 -->
+                        </select>
+                    </div>
+                    <div class="form-item">
+                        <label>文章标题</label>
+                        <input type="text" name="title" required placeholder="请输入文章标题">
+                    </div>
+                    <div class="form-item">
+                        <label>封面图</label>
+                        <div class="upload-box">
+                            <label class="upload-btn" id="coverUploadBtn">
+                                <input type="file" name="coverImage" accept="image/*" style="display: none;" onchange="handleImageUpload(this)">
+                                <i class="el-icon-upload"></i> 点击上传封面图
+                            </label>
+                            <input type="hidden" id="coverImageUrl" name="cover">
+                            <div class="preview-box" id="previewBox" style="display: none;">
+                                <img id="previewImage" src="" alt="预览图">
+                                <div class="remove-btn" onclick="removeImage()" title="删除图片">×</div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-item">
+                        <label>视频文件</label>
+                        <div class="upload-box">
+                            <label class="upload-btn" id="videoUploadBtn">
+                                <input type="file" name="videoFile" accept="video/*" style="display: none;" onchange="handleVideoUpload(this)">
+                                <i class="el-icon-upload"></i> 点击上传视频
+                            </label>
+                            <input type="hidden" id="videoUrl" name="video_url">
+                            <div class="video-preview" id="videoPreviewBox" style="display: none;">
+                                <video id="previewVideo" controls style="width: 100%; max-width: 400px;"></video>
+                                <div class="remove-btn" onclick="removeVideo()" title="删除视频">×</div>
+                                <div class="upload-progress" id="videoProgress" style="display: none;">
+                                    <div class="progress-bar"></div>
+                                    <span class="progress-text">0%</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="form-item">
+                        <label>文章内容</label>
+                        <textarea name="content" rows="6" required placeholder="请输入文章内容"></textarea>
+                    </div>
+                    <div class="form-item">
+                        <label>排序</label>
+                        <input type="number" name="sort" min="0">
+                        <input type="hidden" name="_token" value="{{ csrf_token() }}" />
+
+                    </div>
+                    <div class="dialog-footer">
+                        <button type="button" onclick="closeDialog('courseDialog')">取消</button>
+                        <button type="button" onclick="submitCourseForm()">确定</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+
+        <!-- 课程详情弹窗 -->
+        <div class="dialog" id="viewDialog">
+            <div class="dialog-content">
+                <h3>课程详情</h3>
+                <div class="course-detail" id="courseDetail">
+                    <!-- 课程详情将通过JS动态渲染 -->
+                </div>
+                <div class="dialog-footer">
+                    <button onclick="closeDialog('viewDialog')">关闭</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <script>
+        var categoryData = @json($categories);
+        var currentCategoryId = {{ $condition['category_id'] }};
+        var videoData = @json($video['list']);
+    </script>
+    <script src="{{asset('js/script.js')}}"></script>
+
+@endsection

+ 13 - 0
routes/api.php

@@ -1,5 +1,8 @@
 <?php
 
+use App\Http\Controllers\Api\V1\DashboardController;
+use App\Http\Controllers\Api\V1\Mentality\ScaleCategoryController;
+use App\Models\Scale;
 use Illuminate\Support\Facades\Route;
 
 /*
@@ -241,6 +244,14 @@ Route::group(['namespace' => 'Api'], function(){
                 Route::resource('browse_record', 'BrowseRecordController');
             });
         });
+        //数据统计
+        Route::prefix('dashboard/')->group(function () {
+            Route::get('period_register',  'DashboardController@getPeriodRegisiter');
+            Route::get('/video_count', [DashboardController::class, 'videoCount']);
+            Route::get('/video', [DashboardController::class, 'video']);
+            Route::get('/report', [DashboardController::class, 'report']);
+        });
+        
         Route::group(['prefix' => 'workstation'], function(){
             Route::post('login', 'FourActivityController@loginOrRegister')->middleware('psy_user');
             Route::group(['prefix' => 'report'], function(){
@@ -252,6 +263,8 @@ Route::group(['namespace' => 'Api'], function(){
             Route::post('upload', 'FourActivityController@upload');
 
         });
+        Route::get('/sc/tree', [ScaleCategoryController::class, 'tree']);
 
     });
+
 });

+ 7 - 0
routes/statisticsApi.php

@@ -0,0 +1,7 @@
+<?php
+use Illuminate\Support\Facades\Route;
+
+Route::group(['namespace' => 'StatisticsApi'], function(){
+    //统计
+    Route::get('index', 'IndexController@index');
+});

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor