Ver Fonte

Merge branch 'master' of http://git.derkj.com:9095/waibao/NN2025081602

罗永浩 há 5 meses atrás
pai
commit
9b3a690b5b

+ 0 - 0
addons/admin/src/views/system/institution.vue


+ 1 - 5
app/Http/Controllers/Admin/IndexController.php

@@ -13,16 +13,11 @@ namespace App\Http\Controllers\Admin;
 
 use App\Services\Common\AccountService;
 use App\Services\Common\AnswerRanksService;
-use App\Services\Common\BalanceLogService;
-use App\Services\Common\DepositService;
 use App\Services\Common\ExamAccessLogsService;
-use App\Services\Common\GoodsService;
 use App\Services\Common\MemberService;
 use App\Services\Common\MenuService;
-use App\Services\Common\OrderService;
 use App\Services\Common\UserService;
 use App\Services\RedisService;
-use App\utils\TimeUtils;
 use Request;
 
 /**
@@ -114,6 +109,7 @@ class IndexController extends Backend
         RedisService::keyDel("caches:videos*");
         RedisService::keyDel("caches:config*");
         RedisService::keyDel("caches:paper*");
+        RedisService::keyDel("caches:exam*");
         RedisService::keyDel("caches:mpQrcode*");
         RedisService::keyDel("caches:order*");
         RedisService::keyDel(env('APP_NAME').'_cache:*');

+ 13 - 0
app/Http/Controllers/Api/v1/ArticleController.php

@@ -39,6 +39,19 @@ class ArticleController extends webApp
     }
 
     /**
+     * 资料分类
+     * @return array
+     */
+    public function cates()
+    {
+        $params = request()->all();
+        $type = isset($params['type'])? $params['type'] : 2;
+        $sc = isset($params['sc'])? $params['sc'] : 0;
+        $datas = ArticleService::make()->getCateList($type, $sc);
+        return message(1010, true, $datas);
+    }
+
+    /**
      * 热门咨询
      * @return array
      */

+ 17 - 0
app/Http/Controllers/Api/v1/CourseController.php

@@ -29,6 +29,23 @@ class CourseController extends webApp
     }
 
     /**
+     * 视频课列表
+     * @return array
+     */
+    public function videos()
+    {
+        try {
+            $params = request()->all();
+            $pageSize = isset($params['pageSize'])? $params['pageSize'] : 10;
+            $datas = CourseService::make()->getVideoList($params, $pageSize);
+            return message(1010, true, $datas);
+        }  catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
+    }
+
+    /**
      * 列表
      * @return array
      */

+ 37 - 0
app/Http/Controllers/Api/v1/ExamController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\v1;
 
 use App\Http\Controllers\Api\webApp;
 use App\Services\Api\ExamService;
+use App\Services\Exam\SubjectService;
 
 /**
  * 考试管理
@@ -31,6 +32,24 @@ class ExamController extends webApp
     }
 
     /**
+     * 科目
+     * @return array
+     */
+    public function subjects()
+    {
+        try {
+            $type = request()->post('type', 0);
+            $sceneType = request()->post('scene_type', 0);
+            $sc = request()->post('sc', 0);
+            $datas = SubjectService::make()->getListByAttrType($type, $sceneType, $sc);
+            return message(1010, true, $datas);
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
+    }
+
+    /**
      * 答题历史
      * @return array
      */
@@ -49,6 +68,24 @@ class ExamController extends webApp
     }
 
     /**
+     * 错题记录
+     * @return array
+     */
+    public function errors()
+    {
+        try {
+            $params = request()->post();
+            $pageSize = request()->post('pageSize', 10);
+            $params['user_id'] = $this->userId;
+            $datas = ExamService::make()->getErrorList($params, $pageSize);
+            return message(1010, true, $datas);
+        } catch (\Exception $exception) {
+            $error = ['error' => $exception->getMessage(), 'trace' => $exception->getTrace()];
+            return message(1009, false, $error);
+        }
+    }
+
+    /**
      * 每日一练
      * @return array
      */

+ 10 - 5
app/Http/Controllers/Api/v1/TestController.php

@@ -23,19 +23,24 @@ class TestController extends webApp
         //dump($result);
 //        return 66;
 
+        $result = DeepSeekService::make()->getImageTopicData('/temp/20250922/test1.png');
+        dump($result);
+        return false;
 
         $data = [
-            'answer'=> 'answer: "亲爱的小雅:↵  本人因临时有急事不得不出门,故原计划与你相约去看的话剧无法准时应约,望谅解。我们改日再约。↵友:晓芙↵2025年2月1日',
-//            'answer'=> 'https://shuati.derkj.com/uploads/temp/20250922/answer1.jpeg',
+//            'answer'=> 'answer: "亲爱的小雅:↵  本人因临时有急事不得不出门,故原计划与你相约去看的话剧无法准时应约,望谅解。我们改日再约。↵友:晓芙↵2025年2月1日',
+            'answer'=> '/temp/20250922/haoyu.jpg',
             'score'=>10,
-//            'topic'=> 'https://shuati.derkj.com/uploads/temp/20250922/topic2.png',
-            'topic'=> '四、材料写作题(共40分)<br/>(一)应用文写作(10分)<br/>29晓芙和小雅原计划2025年2月1日中午12点去看话剧,可晓芙临时有急事要出门。请你根据下面的模板以晓芙的名义给小雅写一张留言条。<br/>__:<br/>__,望谅解。我们改日再约。<br/>友:__<br/>2025年__',
+//            'topic'=> 'https://shuati.derkj.com/uploads/temp/20250922/haoyu_answer.png',
+            'topic'=> '三、简答题(每题10分,共2题)\n1.请简述一下杜甫的诗《春\n夜喜雨》中“好雨知时节,当春乃发生”中的“知”有何妙处?50字以内。',
             'type'=> 2
         ];
 
 
+        dump($data);
 //        $result = DeepSeekService::make()->upload('/temp/20250922/topic2.jpeg');
-        $result = DeepSeekService::make()->apiRequest($data,'deepseek-chat');
+        $result = DeepSeekService::make()->apiRequest($data,'deepseek-reasoner');
+//        $result = DeepSeekService::make()->apiRequest($data,'deepseek-chat');
         dump($result);
 //        $papers = [28,29];
 //        $datas = [];

+ 9 - 0
app/Models/ArticleCateModel.php

@@ -22,4 +22,13 @@ class ArticleCateModel extends BaseModel
     // 设置数据表
     protected $table = 'article_cates';
 
+    public function getIconAttribute($value)
+    {
+        return $value? get_image_url($value) : '';
+    }
+
+    public function setIconAttribute($value)
+    {
+        return $value? get_image_path($value) : '';
+    }
 }

+ 1 - 1
app/Models/ExamAccessLogModel.php

@@ -30,7 +30,7 @@ class ExamAccessLogModel extends BaseModel
             return false;
         }
 
-        RedisService::set($cacheKey."_lock:{$userId}", date('Y-m-d H:i:s'), rand(2, 3));
+        RedisService::set($cacheKey."_lock:{$userId}", date('Y-m-d H:i:s'), rand(3, 5));
         $checkId = RedisService::get($cacheKey);
         if(!$checkId){
             $checkId = self::where(['date'=> $date,'type'=>$type,'mark'=>1])

+ 13 - 1
app/Models/ExamAnswerModel.php

@@ -10,4 +10,16 @@ class ExamAnswerModel extends BaseModel
 {
     protected $table = 'exam_answers';
 
-}
+    /**
+     * 错题
+     * @return \Illuminate\Database\Eloquent\Relations\HasOne
+     */
+    public function error()
+    {
+        return $this->hasOne(ExamAnswerTopicModel::class, 'answer_log_id', 'id')
+            ->where(['accurate'=>0,'status' => 1, 'mark' => 1])
+            ->orderBy('create_time','asc');
+    }
+
+}
+

+ 10 - 0
app/Models/ExamSubjectsModel.php

@@ -22,4 +22,14 @@ class ExamSubjectsModel extends BaseModel
         'status',
         'mark'
     ];
+
+    public function getIconAttribute($value)
+    {
+        return $value ? get_image_url($value) : '';
+    }
+
+    public function setIconAttribute($value)
+    {
+        return $value ? get_image_path($value) : '';
+    }
 }

+ 33 - 9
app/Services/Api/ArticleService.php

@@ -13,6 +13,7 @@ namespace App\Services\Api;
 
 use App\Models\ArticleCateModel;
 use App\Models\ArticleModel;
+use App\Models\ExamAccessLogModel;
 use App\Services\BaseService;
 use App\Services\ConfigService;
 use App\Services\RedisService;
@@ -125,6 +126,7 @@ class ArticleService extends BaseService
         $where = ['a.mark' => 1];
         $status = isset($params['status'])? $params['status'] : 0;
         $type = isset($params['type'])? $params['type'] : 0;
+        $cateId = isset($params['cate_id'])? $params['cate_id'] : 0;
         if($status>0){
             $where['a.status'] = $status;
         }
@@ -132,6 +134,11 @@ class ArticleService extends BaseService
             $where['a.type'] = $type;
         }
 
+        if($cateId>0){
+            $where['a.cate_id'] = $cateId;
+        }
+
+
         return $this->model->from('article as a')
             ->where($where)
             ->where(function ($query) use($params){
@@ -219,12 +226,13 @@ class ArticleService extends BaseService
         }
 
         $info = $this->model->where(['id'=> $id,'status'=>1,'mark'=>1])
-            ->select(['id','title','type','cover','view_num','author','description','create_time','type','content'])
+            ->select(['id','title','type','cover','show_type','file_url','view_num','author','description','create_time','type','content'])
             ->first();
         $info = $info? $info->toArray() : [];
         if($info){
             $info['create_time'] = $info['create_time']? datetime($info['create_time'],'Y-m-d') : '';
             $info['cover'] = get_image_url($info['cover']);
+            $info['file_url'] = get_image_url($info['file_url']);
             $info['content'] = get_format_content($info['content']);
             $this->model->where(['id'=> $id])->increment('view_num',1);
             $info['view_num'] += intval($info['view_num']);
@@ -341,28 +349,44 @@ class ArticleService extends BaseService
 
     /**
      * 获取文章推荐分类
-     * @param int $type 1-普通文章分类
+     * @param int $type 2-对口资料分类,3-专升本资料分类
      * @return array|mixed
      */
-    public function getCateList($type=2)
+    public function getCateList($type=2, $sc=0)
     {
         $cacheKey = "caches:articles:cateList_{$type}";
         $datas = RedisService::get($cacheKey);
+        // 复习资料访问统计
+        if (empty($sc)) {
+            ExamAccessLogModel::saveLog(date('Y-m-d'), $type, 5);
+        }
         if($datas){
             return $datas;
         }
 
-        $limitNum = ConfigService::make()->getConfigByCode('custom_cate_num', 6);
-        $limitNum = $limitNum? $limitNum : 6;
+
         $datas = ArticleCateModel::where(['type'=> $type,'status'=>1,'mark'=>1])
-            ->select(['cate_id','name','sort','type'])
-            ->limit($limitNum)
+            ->select(['id','icon','name','pid','description','type','sort','status'])
+            ->orderBy('sort','desc')
+            ->orderBy('id','asc')
             ->get();
         $datas = $datas? $datas->toArray() : [];
+        $list = [];
         if($datas){
-            RedisService::set($cacheKey, $datas, rand(300,600));
+            foreach ($datas as $item){
+                $pid = isset($item['pid'])? $item['pid'] : 0;
+                $id = isset($item['id'])? $item['id'] : 0;
+                if($pid>0){
+                    $list[$pid] = isset($list[$pid])? $list[$pid] : [];
+                    $list[$pid]['children'][] = $item;
+                }else{
+                    $item['children'] = [];
+                    $list[$id] = $item;
+                }
+            }
+            RedisService::set($cacheKey, $list, rand(300,600));
         }
 
-        return $datas;
+        return $list;
     }
 }

+ 40 - 0
app/Services/Api/CourseService.php

@@ -87,6 +87,46 @@ class CourseService extends BaseService
     }
 
     /**
+     * 获取视频集
+     * @param int
+     * @return array|mixed
+     */
+    public function getVideoList($params,$pageSize)
+    {
+        $page = isset($params['page'])? $params['page'] : 1;
+        $type = isset($params['type'])? $params['type'] : 0;
+        $sc = isset($params['sc'])? $params['sc'] : 0;
+        $cacheKey = "caches:videos:list_by_all:{$page}_{$pageSize}_{$type}".md5(json_encode($params));
+        $datas = RedisService::get($cacheKey);
+        // 视频课访问次数统计
+        if(empty($sc)){
+            ExamAccessLogModel::saveLog(date('Y-m-d'), $type, 20);
+        }
+
+        if($datas){
+            return $datas;
+        }
+
+        $list = VideoModel::where(['type'=>$type,'status'=>1,'mark'=>1])
+            ->select(['id','video_name','poster','type','description','is_recommend','status'])
+            ->orderBy('sort','desc')
+            ->orderBy('create_time','desc')
+            ->paginate($pageSize > 0 ? $pageSize : 9999999);
+        $list = $list? $list->toArray() :[];
+        $rows = isset($list['data'])? $list['data'] : [];
+        $datas = [
+            'pageSize'=> $pageSize,
+            'total'=> isset($list['total'])? $list['total'] : 0,
+            'list'=> $rows
+        ];
+        if($rows){
+            RedisService::set($cacheKey, $datas, rand(300,600));
+        }
+
+        return $datas;
+    }
+
+    /**
      * 获取分类下视频课
      * @param int
      * @return array|mixed

+ 46 - 1
app/Services/Api/ExamService.php

@@ -176,6 +176,50 @@ class ExamService extends BaseService
     }
 
     /**
+     * 错题记录
+     * @param $params
+     * @param int $pageSize
+     * @return array|mixed
+     */
+    public function getErrorList($params, $pageSize = 10)
+    {
+        $page = isset($params['page']) ? $params['page'] : 1;
+        $userId = isset($params['user_id']) ? $params['user_id'] : 0;
+        $cacheKey = "caches:exams:{$userId}_errors_{$page}:" . md5(json_encode($params));
+        $datas = RedisService::get($cacheKey);
+        if ($datas) {
+            return $datas;
+        }
+
+        $prefix = env('DB_PREFIX','lev_');
+        $query = $this->getQuery($params);
+        $list = $query->with(['error'])->whereRaw("{$prefix}a.accurate_count < {$prefix}a.answer_count")->where('b.id','>',0)
+            ->select(['a.id', 'a.user_id', 'a.paper_id', 'a.score', 'a.accurate_count', 'a.answer_count', 'b.name', 'b.type', 'b.scene_type', 'b.topic_count', 'b.score_total', 'b.is_charge', 'a.create_time', 'a.answer_times', 'a.status'])
+            ->orderBy('a.create_time', 'desc')
+            ->paginate($pageSize > 0 ? $pageSize : 9999999);
+        $list = $list ? $list->toArray() : [];
+        if ($list) {
+            foreach ($list['data'] as &$item) {
+                $item['error_count'] = $item['answer_count'] - $item['accurate_count'];
+                $item['create_time'] = $item['create_time'] ? datetime($item['create_time'], 'Y-m-d H.i.s') : '';
+                $item['error_id'] = isset($item['error']) && isset($item['error']['topic_id'])? $item['error']['topic_id'] : 0;
+            }
+        }
+
+        $rows = isset($list['data']) ? $list['data'] : [];
+        $datas = [
+            'pageSize' => $pageSize,
+            'total' => isset($list['total']) ? $list['total'] : 0,
+            'list' => $rows
+        ];
+        if ($rows) {
+            RedisService::set($cacheKey, $datas, rand(10, 20));
+        }
+
+        return $datas;
+    }
+
+    /**
      * 每日一练目录数据
      * @param $userId 用户ID
      * @param $params
@@ -186,7 +230,8 @@ class ExamService extends BaseService
     {
         $page = isset($params['page']) ? $params['page'] : 1;
         $type = isset($params['type']) ? $params['type'] : 1;
-        $cacheKey = "caches:exams:{$userId}_practice:{$page}_" . md5(json_encode($params));
+        $sc = isset($params['sc']) ? $params['sc'] : 0;
+        $cacheKey = "caches:exam:{$userId}_practice:{$page}_" . md5(json_encode($params));
         $datas = RedisService::get($cacheKey);
         // 每日一练访问次数统计
         if (empty($sc)) {

+ 9 - 0
app/Services/Api/PaperService.php

@@ -11,6 +11,7 @@
 
 namespace App\Services\Api;
 
+use App\Models\ExamAccessLogModel;
 use App\Models\ExamAnswerModel;
 use App\Models\ExamAnswerTopicModel;
 use App\Models\ExamErrorModel;
@@ -62,6 +63,14 @@ class PaperService extends BaseService
         $page = isset($params['page'])? $params['page'] : 1;
         $cacheKey = "caches:paper:list_{$page}_{$pageSize}:".md5(json_encode($params));
         $datas = RedisService::get($cacheKey);
+
+        // 访问次数统计
+        $sc = isset($params['sc'])? $params['sc'] : 0;
+        $type = isset($params['type'])? $params['type'] : 0;
+        $sceneType = isset($params['scene_type'])? $params['scene_type'] : 0;
+        if (empty($sc) && !in_array($sceneType, [3,4,6])) {
+            ExamAccessLogModel::saveLog(date('Y-m-d'), $type, $sceneType);
+        }
         if($datas){
             return $datas;
         }

+ 73 - 15
app/Services/DeepSeekService.php

@@ -20,6 +20,7 @@ class DeepSeekService extends BaseService
     protected $apiKey = '';
     protected $apiName = '';
     protected $apiurl = '';
+    protected $chaturl = 'https://chat.deepseek.com/api';
 
     // 接口地址
     protected $apiUrls = [
@@ -27,7 +28,9 @@ class DeepSeekService extends BaseService
         'deepseek-chat' => '/chat/completions',
         'deepseek-reasoner' => '/chat/completions',
         'upload' => '/v1/upload',
+        'generations' => '/v1/images/generations',
         'generate' => '/v1/text/generate',
+        'upload_file' => '/v0/file/upload_file',
     ];
 
 
@@ -75,8 +78,24 @@ class DeepSeekService extends BaseService
         $answer = isset($params['answer'])? $params['answer'] : '';
         $topic = isset($params['topic'])? $params['topic'] : '';
         $score = isset($params['score'])? $params['score'] : 0;
+        $type = isset($params['type'])? $params['type'] : 1; // 1-文本,2-图片
         $content = '你是一个答题高手,请给描述中问题的答案评分,并尝试按 JSON:{"score": 8,"topic":"this is topic content","analysis":"this is topic analysis"}返回';
         $message = "请给题目【{$topic}】总分{$score}的答案【{$answer}】评分?";
+        $format = 'json_object';
+        if($type == 2){
+            $content = '你是一个识图专家,请读取图中中英文数字或符号内容,并尝试按 JSON:{"content":"this is photo content"}';
+//            $format = 'text';
+//            $content = '你是一个答题高手,请给描述中问题的答案评分,并尝试按 JSON:{"score": 8,"topic":"this is topic content","answer":"this is answer content","analysis":"this is topic analysis"}返回';
+            preg_match("/\.(jpg|jpeg|png)/", $answer, $extData);
+            $ext = isset($extData[0])? $extData[0] : 'jpg';
+            $ext = $ext=='png'? 'png' : 'jpeg';
+            $filePath = ATTACHMENT_PATH.get_image_path($answer);
+            $image = file_get_contents($filePath);
+            $base64 = base64_encode($image);
+            $base64 = "data:image/{$ext};base64,{$base64}";
+            $message = "请读取图片【{$base64}】内容?";
+//            $message = "请给总分为{$score}的题目【{$topic}】针对图片内【{$base64}】答案评分?";
+        }
         $data = [
             'model'=> $model,
             'messages'=> [
@@ -91,7 +110,7 @@ class DeepSeekService extends BaseService
             ],
             'stream' => false, //false 非流  true//流返回,需要前端追加和保持长连接
             'response_format'=>[
-                'type'=>'json_object' //返回格式(text,json_object)
+                'type'=> $format //返回格式(text,json_object)
             ],
             "max_tokens"=>2048, //最大返回token数
         ];
@@ -100,6 +119,7 @@ class DeepSeekService extends BaseService
         $url = $this->apiUrl.$this->apiUrls[$model];
         $this->saveLog("caches:dkApi:{$model}:request_1_".date('YmdHis'), ['url'=>$url,'data'=>$data]);
         $result = aiRequest($url, json_encode($data), 60, $headers);
+        dump($result);
         $result = $result? json_decode(trim($result),true) : [];
         if(empty($result)){
             $this->error = '答案验证失败';
@@ -131,7 +151,7 @@ class DeepSeekService extends BaseService
         }
 
         $headers = [
-//            'Content-Type: application/json',
+            'Content-Type: application/json',
 //            'Accept: application/json',
             'Authorization: Bearer ' . $this->apiKey
         ];
@@ -140,27 +160,59 @@ class DeepSeekService extends BaseService
         $filePath = ATTACHMENT_PATH.get_image_path($image);
         dump($filePath);
 
-        $url = $this->apiUrl.$this->apiUrls['generate'];
-//        $url = $this->apiUrl.$this->apiUrls['upload'];
+//        $url = $this->apiUrl.$this->apiUrls['generate'];
+        $url = $this->chaturl.$this->apiUrls['upload_file'];
 
         var_dump(basename($image));
-        $file = new \CURLFile(realpath($filePath), 'application/octet-stream', basename($image));
-        $files = ['image' => $file];
-        $data = [
-            "files"=> $files
-        ];
+//        $file = fopen($url, 'rb');
+        $filePath = ATTACHMENT_PATH.get_image_path($filePath);
+        $image = file_get_contents($filePath);
+        $base64 = base64_encode($image);
+        $base64 = "data:image/jpeg;base64,{$base64}";
+//        $file = new \CURLFile(realpath($filePath), 'application/octet-stream', basename($image));
+        $data = ['image' => $base64];
+//        $data = [
+//            "prompt"=> "解释量子计算的基本原理",
+//            "max_tokens"=> 200,
+//            "temperature"=>0.7
+//        ];
         $data = [
-            "prompt"=> "解释量子计算的基本原理",
-            "max_tokens"=> 200,
-            "temperature"=>0.7
+            'files'=> $data
         ];
         var_dump($url);
         var_dump($data);
-        $result = aiRequest($url, $data, 10, $headers);
+        $result = aiRequest($url, '', 10, $headers);
 var_dump($result);
     }
 
     /**
+     * 生成图片
+     * @param $image
+     * @return false
+     */
+    public function makeImage($prompt)
+    {
+        if(empty($this->apiUrl) || empty($this->apiKey) || empty($this->apiName)){
+            $this->error = 'AI接口参数未配置';
+            return false;
+        }
+
+        $headers = [
+            'Content-Type: application/json',
+            'Accept: application/json',
+            'Authorization: Bearer ' . $this->apiKey
+        ];
+        $url = $this->apiUrl.$this->apiUrls['deepseek-chat'];
+        $data = [
+            'prompt'=> $prompt,
+            'model'=>'deepseek-image',
+            'size'=> '1024x1024',
+        ];
+        $result = aiRequest($url, json_encode($data), 10, $headers);
+        var_dump($result);
+    }
+
+    /**
      * 获取题目内容
      * @param $imageUrl
      * @return false
@@ -173,8 +225,14 @@ var_dump($result);
         // 图片获取数据
         if(preg_match("/(images|temp)/", $imageUrl)){
             $path = get_image_path($imageUrl);
-            $ocr = new TesseractOCR();
-            return $ocr->recognize(ATTACHMENT_PATH.$path);
+//            foreach((new TesseractOCR())->availableLanguages() as $lang) echo $lang;
+            $ocr = new TesseractOCR(ATTACHMENT_PATH.$path);
+
+            $data = $ocr->lang('chi_sim','eng','chi_tra')
+                ->allowlist(range('a', 'z'), range('A', 'Z'), range(0, 9), '-_@')
+                ->run(30);
+            var_dump($data);
+            return $data;
         }
 
         return $imageUrl;

+ 45 - 0
app/Services/Exam/SubjectService.php

@@ -2,9 +2,11 @@
 
 namespace App\Services\Exam;
 
+use App\Models\ExamAccessLogModel;
 use App\Models\ExamSubjectsModel;
 use App\Models\ActionLogModel;
 use App\Services\BaseService;
+use App\Services\RedisService;
 
 /**
  * 课程管理-服务类
@@ -122,12 +124,55 @@ class SubjectService extends BaseService
                 }
             })
             ->select(['subject_name', 'id'])
+            ->orderBy('sort','desc')
+            ->orderBy('id','asc')
             ->get();
 
         return $datas ? $datas->toArray() : [];
     }
 
     /**
+     * 科目分组列表
+     * @param int $type 2-对口,3-专升本
+     * @return array|mixed
+     */
+    public function getListByAttrType($type=0,$sceneType=0, $sc=1)
+    {
+        $cacheKey = "caches:exam:subjects:{$type}";
+        $datas = RedisService::get($cacheKey);
+        // 分类入口访问统计
+        if (empty($sc)) {
+            ExamAccessLogModel::saveLog(date('Y-m-d'), $type, $sceneType);
+        }
+        if($datas){
+            return $datas;
+        }
+
+        $where = ['type'=>1,'status'=>1,'mark'=>1];
+        if($type>0){
+            $where['type'] = $type;
+        }else{
+            unset($where['type']);
+        }
+        $datas = $this->model->where($where)
+            ->select(['id','subject_name','icon','pid','type','description','attr_type','status'])
+            ->orderBy('sort','desc')
+            ->orderBy('id','asc')
+            ->get();
+        $datas = $datas? $datas->toArray() : [];
+        $list = [];
+        if($datas){
+            foreach ($datas as $item){
+                $attrType = isset($item['attr_type'])? $item['attr_type'] : 0;
+                $list[$attrType][] = $item;
+            }
+            RedisService::set($cacheKey, $list, rand(300, 600));
+        }
+
+        return $list;
+    }
+
+    /**
      * 删除七天之前标记软删除的数据
      */
     public function delete()

+ 9 - 5
config/platform.php

@@ -17,7 +17,8 @@ return [
             "id" => 3,
             "name" => '模拟试题',
             "code" => 'mock-paper',
-            'page' => '/pages/exam/index?st=3'
+            'page' => '/pages/exam/index?st=3',
+            'page1' => '/pages/exam/subject?st=3',
         ],
         [
             "id" => 98,
@@ -29,19 +30,22 @@ return [
             "id" => 5,
             "name" => '复习资料',
             "code" => 'review-materials',
-            'page' => '/pages/exam/material'
+            'page' => '/pagesArticle/pages/article/material?st=5',
+            'page1' => '/pagesArticle/pages/article/materialCate?st=5'
         ],
         [
             "id" => 4,
             "name" => '模拟考试',
             "code" => 'mock-exam',
-            'page' => '/pages/exam/index?st=4'
+            'page' => '/pages/exam/index?st=4',
+            'page1' => '/pages/exam/subject?st=4'
         ],
         [
-            "id" => 2,
+            "id" => 6,
             "name" => '考前冲刺',
             "code" => 'exam-sprint',
-            'page' => '/pages/exam/index?st=6'
+            'page' => '/pages/exam/index?st=6',
+            'page1' => '/pages/exam/subject?st=6',
         ],
         [
             "id" => 99,

BIN
public/uploads/temp/20250922/haoyu.jpg


BIN
public/uploads/temp/20250922/haoyu_answer.png


BIN
public/uploads/temp/20250922/test.png


BIN
public/uploads/temp/20250922/test1.png


BIN
public/uploads/temp/20250922/test2.png


+ 4 - 0
routes/api.php

@@ -40,6 +40,7 @@ Route::prefix('v1')->group(function() {
     // 文章
     Route::post('/article/index', [\App\Http\Controllers\Api\v1\ArticleController::class, 'index']);
     Route::get('/article/datas', [\App\Http\Controllers\Api\v1\ArticleController::class, 'datas']);
+    Route::get('/article/cates', [\App\Http\Controllers\Api\v1\ArticleController::class, 'cates']);
     Route::get('/article/hots', [\App\Http\Controllers\Api\v1\ArticleController::class, 'hots']);
     Route::get('/article/search', [\App\Http\Controllers\Api\v1\ArticleController::class, 'search']);
     Route::get('/article/info', [\App\Http\Controllers\Api\v1\ArticleController::class, 'info']);
@@ -77,6 +78,7 @@ Route::prefix('v1')->middleware('web.login')->group(function() {
     // 答题
     Route::post('/exam/index', [\App\Http\Controllers\Api\v1\ExamController::class, 'index']);
     Route::post('/exam/history', [\App\Http\Controllers\Api\v1\ExamController::class, 'history']);
+    Route::post('/exam/errors', [\App\Http\Controllers\Api\v1\ExamController::class, 'errors']);
     Route::post('/exam/ranks', [\App\Http\Controllers\Api\v1\ExamController::class, 'ranks']);
     Route::post('/exam/reset', [\App\Http\Controllers\Api\v1\ExamController::class, 'reset']);
     Route::post('/exam/updateTime', [\App\Http\Controllers\Api\v1\ExamController::class, 'updateTime']);
@@ -84,6 +86,7 @@ Route::prefix('v1')->middleware('web.login')->group(function() {
     Route::post('/exam/submit', [\App\Http\Controllers\Api\v1\ExamController::class, 'submit']);
     Route::post('/exam/info', [\App\Http\Controllers\Api\v1\ExamController::class, 'info']);
     Route::post('/exam/cards', [\App\Http\Controllers\Api\v1\ExamController::class, 'cards']);
+    Route::post('/exam/subjects', [\App\Http\Controllers\Api\v1\ExamController::class, 'subjects']);
 
 
     // 每日一练
@@ -97,6 +100,7 @@ Route::prefix('v1')->middleware('web.login')->group(function() {
 
     // 视频课
     Route::post('/course/index', [\App\Http\Controllers\Api\v1\CourseController::class, 'index']);
+    Route::post('/course/videos', [\App\Http\Controllers\Api\v1\CourseController::class, 'videos']);
     Route::post('/course/list', [\App\Http\Controllers\Api\v1\CourseController::class, 'list']);
     Route::post('/course/info', [\App\Http\Controllers\Api\v1\CourseController::class, 'info']);
     Route::post('/course/learn', [\App\Http\Controllers\Api\v1\CourseController::class, 'learn']);