Przeglądaj źródła

拿考试项目的旧版本直接覆盖

lizhen 2 tygodni temu
rodzic
commit
e6a469f6fd
73 zmienionych plików z 5119 dodań i 1330 usunięć
  1. 0 0
      addons/exam/.addonrc
  2. 2 2
      addons/exam/controller/Base.php
  3. 0 13
      addons/exam/controller/Common.php
  4. 137 11
      addons/exam/controller/Paper.php
  5. 33 46
      addons/exam/controller/Question.php
  6. 0 9
      addons/exam/controller/Room.php
  7. 1 6
      addons/exam/controller/User.php
  8. 4 2
      addons/exam/enum/UserStatus.php
  9. 19 0
      addons/exam/helper.php
  10. 1 1
      addons/exam/info.ini
  11. 137 164
      addons/exam/install.sql
  12. 39 76
      addons/exam/library/ExamService.php
  13. 1 4
      addons/exam/model/GradeModel.php
  14. 707 0
      addons/exam/tuniao-bak/tn-calendar/tn-calendar.vue
  15. 645 0
      addons/exam/tuniao-bak/tn-image-upload/tn-image-upload.vue
  16. 401 0
      addons/exam/tuniao-bak/tn-number-box/tn-number-box.vue
  17. 182 0
      addons/exam/tuniao-bak/tn-number-keyboard/tn-number-keyboard.vue
  18. 334 0
      addons/exam/tuniao-bak/tn-rate/tn-rate.vue
  19. 71 0
      addons/exam/tuniao-bak/tn-time-line-item/tn-time-line-item.vue
  20. 71 0
      addons/exam/tuniao-bak/tn-time-line-item/tn-time-line-item.vue_bk
  21. 39 0
      addons/exam/tuniao-bak/tn-time-line/tn-time-line.vue
  22. 39 0
      addons/exam/tuniao-bak/tn-time-line/tn-time-line.vue_bk
  23. 324 0
      addons/exam/tuniao-bak/tn-verification-code-input/tn-verification-code-input.vue
  24. 149 0
      addons/exam/tuniao-bak/tn-verification-code/tn-verification-code.vue
  25. 4 5
      addons/exam/uniapp/components/kz-page-my-color/kz-page-my-color.vue
  26. 340 390
      addons/exam/uniapp/components/kz-question/kz-question.vue
  27. 487 0
      addons/exam/uniapp/pages/course/detail.vue
  28. 435 0
      addons/exam/uniapp/pages/course/index.vue
  29. 3 8
      addons/exam/uniapp/pages/index/index.vue
  30. 4 4
      addons/exam/uniapp/pages/paper/index.vue
  31. 4 7
      addons/exam/uniapp/pages/room/detail.vue
  32. 1 1
      addons/exam/uniapp/pages/room/index.vue
  33. 215 234
      addons/exam/uniapp/pages/search/index.vue
  34. 155 162
      addons/exam/uniapp/pages/train/index.vue
  35. 60 61
      addons/exam/uniapp/pages/train/train.vue
  36. 21 69
      addons/exam/uniapp/pages/user/user.vue
  37. 53 54
      addons/exam/uniapp/pages/wrong/index.vue
  38. 1 1
      addons/exam/uniapp/static/appInfo.js
  39. BIN
      addons/exam/uniapp/static/components/scroll-top/icon_ad_3x.png
  40. BIN
      addons/exam/uniapp/static/components/scroll-top/icon_empty_3x.png
  41. BIN
      addons/exam/uniapp/static/img/caidan.png
  42. BIN
      addons/exam/uniapp/static/img/delete.png
  43. BIN
      addons/exam/uniapp/static/img/jiaojuan.png
  44. BIN
      addons/exam/uniapp/static/img/left.png
  45. BIN
      addons/exam/uniapp/static/img/rank-01.png
  46. BIN
      addons/exam/uniapp/static/img/rank-02.png
  47. BIN
      addons/exam/uniapp/static/img/rank-03.png
  48. BIN
      addons/exam/uniapp/static/img/right.png
  49. BIN
      addons/exam/uniapp/static/img/round_check.png
  50. BIN
      addons/exam/uniapp/static/img/round_close.png
  51. BIN
      addons/exam/uniapp/static/img/train-banner1.png
  52. BIN
      addons/exam/uniapp/static/img/train-banner2.png
  53. BIN
      addons/exam/uniapp/static/rank/Intersect.png
  54. BIN
      addons/exam/uniapp/static/rank/download.png
  55. BIN
      addons/exam/uniapp/static/rank/download2.png
  56. BIN
      addons/exam/uniapp/static/rank/icon_one.png
  57. BIN
      addons/exam/uniapp/static/rank/icon_three.png
  58. BIN
      addons/exam/uniapp/static/rank/icon_two.png
  59. BIN
      addons/exam/uniapp/static/rank/one.png
  60. BIN
      addons/exam/uniapp/static/rank/three.png
  61. BIN
      addons/exam/uniapp/static/rank/two.png
  62. BIN
      addons/exam/uniapp/static/tabbar/chaxun.png
  63. BIN
      addons/exam/uniapp/static/tabbar/chaxun1.png
  64. BIN
      addons/exam/uniapp/static/tabbar/index.png
  65. BIN
      addons/exam/uniapp/static/tabbar/index1.png
  66. BIN
      addons/exam/uniapp/static/tabbar/kaoshi.png
  67. BIN
      addons/exam/uniapp/static/tabbar/kaoshi1.png
  68. BIN
      addons/exam/uniapp/static/tabbar/tiku.png
  69. BIN
      addons/exam/uniapp/static/tabbar/tiku1.png
  70. BIN
      addons/exam/uniapp/static/tabbar/user.png
  71. BIN
      addons/exam/uniapp/static/tabbar/user1.png
  72. BIN
      addons/exam/uniapp/static/user/login_bottom_bg.jpg
  73. BIN
      addons/exam/uniapp/static/user/login_top2.jpg

Plik diff jest za duży
+ 0 - 0
addons/exam/.addonrc


+ 2 - 2
addons/exam/controller/Base.php

@@ -7,9 +7,9 @@ use think\Lang;
 
 class Base extends Api
 {
-    public function _initialize()
+    public function __construct()
     {
-        parent::_initialize();
+        parent::__construct();
 
         $this->loadCommonFile();
 

+ 0 - 13
addons/exam/controller/Common.php

@@ -32,19 +32,6 @@ class Common extends Base
         $data['papers']  = $this->indexPaperList();
         $data['rooms']   = $this->indexRoomList();
         // $data['version'] = $this->getAppVersion();
-
-        // 处理banner cdn url
-        if (!empty($data['system']['banner'])) {
-            $banners = explode(',', $data['system']['banner']);
-            foreach ($banners as &$banner) {
-                $banner = cdnurl($banner, true);
-            }
-            $data['system']['banner'] = implode(',', $banners);
-        }
-
-        // cdn域名
-        $data['cdn_url'] = cdnurl('', true);
-
         $this->success('请求成功', $data);
     }
 

+ 137 - 11
addons/exam/controller/Paper.php

@@ -11,6 +11,7 @@ use addons\exam\model\UserModel;
 use app\admin\model\exam\CateModel;
 use app\admin\model\exam\GradeModel;
 use think\Request;
+use think\Db;
 
 
 /**
@@ -34,7 +35,7 @@ class Paper extends Base
         $query = PaperModel::with([
             'cates' => function ($query) {
                 $query->withField('id, name');
-            },
+            }
         ])
             ->where('status', CommonStatus::NORMAL)
             ->where('is_only_room', 0)// 过滤仅考场使用的试卷
@@ -71,20 +72,102 @@ class Paper extends Base
         $room_id  = input('room_id/d', 0);
 
         // 验证是否需要绑定手机号
-        UserModel::isMustBindMobile($this->auth->getUser());
+//        UserModel::isMustBindMobile($this->auth->getUser());
 
         // 预创建考场考试记录
-        $room_grade_id = ExamService::preRoomGrade($room_id, $this->auth->id);
+//        $room_grade_id = ExamService::preRoomGrade($room_id, $this->auth->id);
 
         // 获取试卷题目
         $question_data = ExamService::getExamQuestion($paper_id, $room_id);
 
-        // 标记题目是否已收藏
-        $question_data['questions'] = QuestionModel::isCollected($this->auth->id, $question_data['questions']);
+//        $this->success('', array_merge($question_data, ['room_grade_id' => $room_grade_id]));
+        $this->success('', $question_data);
+    }
+
+    //开始考试接口
+    public function startpaper(){
+        $paper_id      = input('paper_id/d', 0);
+        $user_id = $this->auth->id;
+
+        //检查考试状态
+        $check = Db::name('exam_grade')->where('user_id', $user_id)->where('status',1)->find();
+        if($check){
+            $this->success('您有其他考试正在进行中,即将继续考试',0);//直接给成功,数据返回0,前端跳转
+        }
+
+        //检查试卷
+        $paper   = PaperModel::get($paper_id);
+
+        switch (true) {
+            case !$paper:
+                $this->error('试卷信息不存在');
+            case $paper->status != 'NORMAL':
+                $this->error('试卷未开启');
+            case $paper->mode == 'RANDOM' && !$paper->configs:
+                $this->error('试卷未配置');
+        }
+
+        //时间限制
+        if ($paper['start_time'] > 0 && $paper['start_time'] > time()) {
+            $this->error('该试卷未开始,不能参与考试');
+        }
+        if ($paper['end_time'] > 0 && $paper['end_time'] < time()) {
+            $this->error('该试卷已结束,不能参与考试');
+        }
+
+        //考试资格
+        if(!in_array($user_id,explode(',',$paper['user_ids']))){
+            $this->error('您不能参加该考试');
+        }
+
+        //次数限制
+        if ($paper['limit_count'] > 0){
+
+            $my_count = Db::name('exam_grade')->where('user_id', $user_id)->where('paper_id', $paper_id)->count();
+            if($my_count >= $paper['limit_count']) {
+                $this->error('该试卷您的考试次数已达上限');
+            }
+        }
+
+        //记录为已开始,计划任务倒计时之后 自动结束
+        $data = [
+            'cate_id'  => $paper['cate_id'],
+            'user_id'  => $this->auth->id,
+            'paper_id' => $paper_id,
+            'mode'     => $paper['mode'],
+
+            'total_score' => $paper['total_score'],
+            'total_count' => $paper['quantity'],
+
+            'start_time' => time(),
+            'createtime' => time(),
+            'status' => 1,
+            'limit_time' => $paper['limit_time'],  //限时N秒
+            'last_time' => $paper['limit_time'] > 0 ? (time() + $paper['limit_time']) : 0, //最后限制交卷时间,时间戳
+        ];
+
+        $grade_id = Db::name('exam_grade')->insertGetId($data);
+        $this->success('',$grade_id);
 
-        $this->success('', array_merge($question_data, ['room_grade_id' => $room_grade_id]));
     }
 
+    //进行中考试
+    public function half_examing(){
+        $user_id = $this->auth->id;
+
+        $check = Db::name('exam_grade')->where('user_id', $user_id)->where('status',1)->find();
+        if(empty($check)){
+            $this->error('您没有进行中的考试');
+        }
+        $paper_id = $check['paper_id'];
+
+        // 获取试卷题目
+        $question_data = ExamService::getExamQuestion($paper_id, 0);
+        $question_data['paper']['limit_time'] = $check['last_time'] - time();//倒计时秒数
+        $this->success('', $question_data);
+    }
+
+
     /**
      * 交卷
      */
@@ -92,16 +175,25 @@ class Paper extends Base
     {
         $request       = Request::instance();
         $user_id       = $this->auth->id;
+
         $paper_id      = $request->post('paper_id/d', 0);
         $questions     = $request->post('questions/a', []);
         $start_time    = $request->post('start_time/d', time());
-        $room_id       = $request->post('room_id/d', 0);
+        $room_id       = 0;
         $room_grade_id = $request->post('room_grade_id/d', 0);
 
         if (!$user_id || !$paper_id || !$questions) {
-            $this->error('提交数据有误' . $user_id);
+            $this->error('提交数据有误');
         }
 
+        $check = Db::name('exam_grade')->where('status',1)->where('user_id',$user_id)->where('paper_id',$paper_id)->find();
+        if(!$check){
+            $this->error('交卷有误,或者您已交卷');
+        }
+        $grade_id = $check['id'];
+        $start_time = $check['start_time'];
+
+
         // 考场考试
         if ($room_id) {
             if (!$room_grade_id) {
@@ -131,7 +223,7 @@ class Paper extends Base
             $result = ExamService::paperExam($user_id, $paper_id, $questions, $start_time, $paper);
 
             // 记录考试成绩
-            GradeModel::create(array_merge(
+            /*GradeModel::create(array_merge(
                 $result,
                 [
                     'cate_id'  => $paper['cate_id'],
@@ -141,9 +233,43 @@ class Paper extends Base
                 [
                     // 'exam_mode' => ExamMode::PAPER,
                     'date' => date('Y-m-d'),
-                ]), true);
+                ]), true);*/
+
+            $update = array_merge(
+                $result,
+                [
+                    'cate_id'  => $paper['cate_id'],
+                    'updatetime' => time(),
+                    'date' => date('Y-m-d'),
+
+                    'status' => 2,
+                    'finish_time' => time(),
+                ]);
+            unset($update['pass_score']);
+            unset($update['start_time']);
+
+            $rs = Db::name('exam_grade')->where('id',$grade_id)->update($update);
+            if($rs === false){
+                $this->error('交卷失败');
+            }
         }
-        return json($result);
+
+        $result['nickname'] = $this->auth->nickname;
+
+        //删除本试卷分数最低的试卷
+        $old_grade = Db::name('exam_grade')->where('user_id',$user_id)->where('paper_id',$paper_id)->where('id','NEQ',$grade_id)->find();
+        if(!empty($old_grade)){
+            if($old_grade['score'] <= $update['score']){
+                $delete_id = $old_grade['id'];
+            }else{
+                $delete_id = $grade_id;
+            }
+            Db::name('exam_grade')->where('id',$delete_id)->delete();
+        }
+        //删除本试卷分数最低的试卷
+
+        $this->success('',$result);
+//        return json($result);
     }
 
     /*

+ 33 - 46
addons/exam/controller/Question.php

@@ -36,7 +36,7 @@ class Question extends Base
         $param['user_id'] = $this->auth->id;
 
         // 验证是否需要绑定手机号
-        UserModel::isMustBindMobile($this->auth->getUser());
+        //UserModel::isMustBindMobile($this->auth->getUser());
 
         $list = QuestionModel::getList($param);
         // $total     = $list['total'];
@@ -88,8 +88,9 @@ class Question extends Base
     /**
      * 试题详情
      */
-    public function detail($id)
+    public function detail()
     {
+        $id = input('id');
         $this->success('', (new QuestionModel)->get($id));
     }
 
@@ -131,17 +132,19 @@ class Question extends Base
             $this->error('题目数据不存在');
         }
 
-        $res = QuestionCollectModel::updateOrCreate(
+        if ($res = QuestionCollectModel::updateOrCreate(
             [
                 'user_id'     => $this->auth->id,
-                'question_id' => $question_id,
+                'question_id' => $question_id
             ],
             [
                 'user_id'     => $this->auth->id,
-                'question_id' => $question_id,
-            ]
-        );
-        $this->success('收藏成功', $res);
+                'question_id' => $question_id
+            ])
+        ) {
+            $this->success('收藏成功', $res);
+        }
+        $this->error('收藏失败');
     }
 
     /**
@@ -152,9 +155,12 @@ class Question extends Base
         if (!$question_id = input('question_id/d', 0)) {
             $this->error('缺少题目ID');
         }
+        $result = QuestionCollectModel::where('question_id', $question_id)->where('user_id', $this->auth->id)->delete();
 
-        QuestionCollectModel::where('question_id', $question_id)->where('user_id', $this->auth->id)->delete();
-        $this->success('取消收藏成功');
+        if ($result) {
+            $this->success('取消成功');
+        }
+        $this->error('取消失败');
     }
 
     /**
@@ -165,19 +171,11 @@ class Question extends Base
         $user_id = $this->auth->id;
 
         if ($ids = input('question_ids')) {
-            $ids = explode(',', $ids);
-            // 必须是int类型
-            $ids = array_filter(array_map('intval', $ids));
-            if (!$ids) {
-                $this->error('题目ID有误');
-            }
-
             $total = QuestionWrongModel::where('user_id', $user_id)->whereIn('question_id', $ids)->count();
             $list  = $total ? QuestionWrongModel::with('question')
                 ->whereIn('question_id', $ids)
                 ->where('user_id', $user_id)
-                ->orderRaw("find_in_set(question_id, '" . implode(',', $ids) . "')")// 保持原有顺序
-                                                                                    // ->order('id desc')
+                ->order('id desc')
                 ->paginate(999, true)->toArray() : [];
         } else {
             $total = QuestionWrongModel::where('user_id', $user_id)->count();
@@ -190,13 +188,10 @@ class Question extends Base
         if (isset($list['data']) && $list['data']) {
             $questions = [];
             foreach ($list['data'] as $item) {
-                $questions[] = array_merge($item['question'], [
-                    'wrong_id'    => $item['id'],
-                    'user_answer' => $item['user_answer'],
-                    'source'      => $item['kind'],
-                ]);
+                $questions[] = array_merge($item['question'], ['wrong_id' => $item['id'], 'user_answer' => $item['user_answer']]);
             }
-            $list['data'] = \addons\exam\model\QuestionModel::isCollected($user_id, $questions);
+//            $list['data'] = \addons\exam\model\QuestionModel::isCollected($user_id, $questions);
+            $list['data'] = $questions;
         } else {
             $list['data'] = [];
         }
@@ -204,7 +199,7 @@ class Question extends Base
         $this->success('', compact('list', 'total'));
     }
 
-    /**
+    /*
      * 记录错题
      */
     public function wrongAdd()
@@ -213,22 +208,10 @@ class Question extends Base
             $this->error('缺少题目ID');
         }
 
-        $question = QuestionModel::get($question_id);
-        if (!$question) {
-            $this->error('题目数据不存在');
-        }
-
-        $source = input('source/s', 'TRAINING');
-        if (in_array($question['kind'], ['FILL', 'SHORT'])) {
-            $user_answer = input('user_answer/a', []);
-        } else {
-            $user_answer = input('user_answer/s', '');
-        }
-
-        QuestionModel::recordWrong($question['kind'], $question_id, $this->auth->id, $user_answer, $source, [
-            'cate_id' => $question['cate_id'],
-        ]);
-        $this->success('记录成功');
+        if (QuestionWrongModel::add($this->auth->id, $question_id))
+            $this->success('记录成功');
+        else
+            $this->error('记录失败');
     }
 
     /**
@@ -240,8 +223,10 @@ class Question extends Base
             $this->success('缺少错题ID');
         }
 
-        QuestionWrongModel::where('question_id', $question_id)->where('user_id', $this->auth->id)->delete();
-        $this->success('删除成功');
+        if (QuestionWrongModel::where('question_id', $question_id)->where('user_id', $this->auth->id)->delete()) {
+            $this->success('删除成功');
+        }
+        $this->error('删除失败');
     }
 
     /**
@@ -249,7 +234,9 @@ class Question extends Base
      */
     public function wrongClear()
     {
-        QuestionWrongModel::where('user_id', $this->auth->id)->delete();
-        $this->success('删除成功');
+        if (QuestionWrongModel::where('user_id', $this->auth->id)->delete()) {
+            $this->success('删除成功');
+        }
+        $this->error('删除失败');
     }
 }

+ 0 - 9
addons/exam/controller/Room.php

@@ -106,15 +106,6 @@ class Room extends Base
                     $room_grade_log_count = count($room_grade_logs);
                     if ($room_grade_log_count > 0) {
                         $can_start = $room_grade_log_count - 1 < $room['makeup_count'] ? 2 : 0;
-                        if ($can_start) {
-                            // 已有考试通过的记录,禁止补考
-                            foreach ($room_grade_logs as $room_grade_log) {
-                                if ($room_grade_log['is_pass']) {
-                                    $can_start = 0;
-                                    break;
-                                }
-                            }
-                        }
                     }
                 } else {
                     // 非补考模式只能考一次

+ 1 - 6
addons/exam/controller/User.php

@@ -91,11 +91,6 @@ class User extends Base
     {
         $user         = $this->auth->getUser()->visible($this->visibleFields)->toArray();
         $user['info'] = UserInfoModel::getUserInfo($this->auth->id);
-        if ($user && $user['avatar']) {
-            if (strpos($user['avatar'], 'http') === false) {
-                $user['avatar'] = cdnurl($user['avatar'], true);
-            }
-        }
         $this->success('', $user);
     }
 
@@ -205,7 +200,7 @@ class User extends Base
         if ($user->password != $this->auth->getEncryptPassword($password, $user->salt)) {
             fail('登录失败,账号或密码错误');
         }
-        if ($user->status != 'normal') {
+        if ($user->status != 1) {
             fail('登录失败,账号已被禁用登录');
         }
 

+ 4 - 2
addons/exam/enum/UserStatus.php

@@ -5,7 +5,9 @@ namespace addons\exam\enum;
 class UserStatus extends BaseEnum
 {
     /** 正常 */
-    const NORMAL = 'normal';
+    const NORMAL = 1;
     /** 隐藏 */
-    const HIDDEN = 'hidden';
+    const HIDDEN = 0;
+    /** 注销 */
+    const CANCEL = -1;
 }

+ 19 - 0
addons/exam/helper.php

@@ -19,6 +19,7 @@ if (!function_exists('fail')) {
             'msg'  => is_array($message) ? json_encode($message) : $message,
         ];
 
+        request_log_update($result);
         // 如果未设置类型则自动判断
         $type     = 'json';
         $response = \think\Response::create($result, $type, 200);
@@ -39,6 +40,7 @@ if (!function_exists('succ')) {
             'msg'  => $message,
         ];
 
+        request_log_update($result);
         // 如果未设置类型则自动判断
         $type     = 'json';
         $response = \think\Response::create($result, $type, 200);
@@ -291,4 +293,21 @@ if (!function_exists('generate_no')) {
         $milliseconds = round(($u_timestamp - $timestamp) * 100); // 改这里的数值控制毫秒位数
         return $pre . $date . date(preg_replace('`(?<!\\\\)u`', $milliseconds, 'u'), $timestamp);
     }
+}
+
+function request_log_update($log_result){
+
+    /*if ($this->logType === 1){
+        if (strlen(json_encode($log_result['data'])) > 1000) {
+            $log_result['data'] = '数据太多,不记录';
+        }
+        LogUtil::info('result', 'Api-Middleware-Log', 'request_log', $log_result);
+    }else{*/
+        if(defined('API_REQUEST_ID')) { //记录app正常返回结果
+            if(strlen(json_encode($log_result['data'])) > 1000) {
+                $log_result['data'] = '数据太多,不记录';
+            }
+            db('api_request_log')->where('id',API_REQUEST_ID)->update(['result'=>json_encode($log_result)]);
+        }
+    /*}*/
 }

+ 1 - 1
addons/exam/info.ini

@@ -3,7 +3,7 @@ title = 答题考试系统
 intro = 试卷答题、考场考试系统
 author = coder
 website = https://exam.jykezhi.com/master.php
-version = 1.7.0
+version = 1.5.9
 state = 1
 license = regular
 licenseto = 19079

+ 137 - 164
addons/exam/install.sql

@@ -1,28 +1,29 @@
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_cate` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-    `kind` enum('QUESTION','PAPER','ROOM','COURSE') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'QUESTION' COMMENT '种类',
-    `level` enum('1','2','3') COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT '类型',
-    `name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',
+    `kind` enum('QUESTION','PAPER','ROOM','COURSE') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'QUESTION' COMMENT '种类',
+    `level` enum('1','2','3') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '1' COMMENT '类型',
+    `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称',
+--     `icon` VARCHAR(200) NULL DEFAULT '' COMMENT '图标' COLLATE 'utf8mb4_unicode_ci',
     `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '父级',
     `sort` int(11) NOT NULL DEFAULT '1' COMMENT '排序',
-    `remark` mediumtext COLLATE utf8mb4_general_ci COMMENT '简介',
+    `remark` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '简介',
     `deletetime` bigint(16) DEFAULT NULL COMMENT '删除时间',
     `status` ENUM('0','1') NOT NULL DEFAULT '1' COMMENT '状态:0=禁用,1=启用' COLLATE 'utf8_general_ci',
     PRIMARY KEY (`id`),
     KEY `parent_id` (`parent_id`),
     KEY `kind` (`kind`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='试题分类';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='试题分类';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_config_info` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
-    `ad_config` mediumtext COLLATE utf8mb4_general_ci COMMENT '广告位配置',
-    `system_config` mediumtext COLLATE utf8mb4_general_ci COMMENT '系统配置',
-    `wx_config` mediumtext COLLATE utf8mb4_general_ci COMMENT '微信配置',
-    `page_config` mediumtext COLLATE utf8mb4_general_ci COMMENT '页面配置',
+    `ad_config` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '广告位配置',
+    `system_config` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '系统配置',
+    `wx_config` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '微信配置',
+    `page_config` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '页面配置',
     PRIMARY KEY (`id`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='参数配置';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='参数配置';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_grade` (
@@ -30,65 +31,64 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_grade` (
     `cate_id` int(11) unsigned NOT NULL COMMENT '所属分类',
     `user_id` int(11) unsigned NOT NULL COMMENT '考试用户',
     `paper_id` int(11) unsigned NOT NULL COMMENT '所属试卷',
-    `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_general_ci',
-    `score` int(11) unsigned NOT NULL COMMENT '考试分数',
-    `system_score` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '系统得分',
-    `manual_score` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '人工判分',
-    `is_pass` int(11) unsigned NOT NULL COMMENT '是否及格',
-    `pass_score` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '及格线',
-    `total_score` int(11) unsigned NOT NULL COMMENT '总分数',
-    `total_count` int(11) unsigned NOT NULL COMMENT '总题数',
-    `right_count` int(11) unsigned NOT NULL COMMENT '答对数',
-    `error_count` int(11) unsigned NOT NULL COMMENT '答错数',
-    `grade_time` bigint(16) unsigned NOT NULL COMMENT '考试用时',
+    `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_unicode_ci',
+    `score` tinyint(3) unsigned NOT NULL COMMENT '考试分数',
+    `system_score` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '系统得分',
+    `manual_score` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '人工判分',
+    `is_pass` tinyint(3) unsigned NOT NULL COMMENT '是否及格',
+    `total_score` tinyint(3) unsigned NOT NULL COMMENT '总分数',
+    `total_count` tinyint(3) unsigned NOT NULL COMMENT '总题数',
+    `right_count` tinyint(3) unsigned NOT NULL COMMENT '答对数',
+    `error_count` tinyint(3) unsigned NOT NULL COMMENT '答错数',
+    `grade_time` int(10) unsigned NOT NULL COMMENT '考试用时',
     `date` CHAR(10) NOT NULL DEFAULT '' COMMENT '考试日期',
-    `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_general_ci',
-    `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_general_ci',
-    `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci',
-    `configs` TEXT NULL DEFAULT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_unicode_ci',
+    `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_unicode_ci',
+    `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci',
+    `configs` TEXT NULL DEFAULT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `user_id` (`user_id`),
     KEY `paper_id` (`paper_id`),
     KEY `work_type_id` (`cate_id`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考试成绩';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='考试成绩';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_notice` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-    `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '标题',
-    `contents` varchar(2000) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '内容',
+    `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '标题',
+    `contents` varchar(2000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '内容',
     `weigh` int(11) NOT NULL DEFAULT '1' COMMENT '排序',
-    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='系统公告';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统公告';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_paper` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
     `cate_id` int(11) unsigned NOT NULL COMMENT '试卷分类',
-    `mode` enum('RANDOM','FIX') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式',
-    `title` varchar(3000) COLLATE utf8mb4_general_ci NOT NULL COMMENT '试卷名称',
-    `configs` varchar(3000) COLLATE utf8mb4_general_ci NOT NULL COMMENT '选题配置',
+    `mode` enum('RANDOM','FIX') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式',
+    `title` varchar(3000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '试卷名称',
+    `configs` varchar(3000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '选题配置',
     `quantity` int(10) unsigned NOT NULL COMMENT '题目数量',
     `total_score` tinyint(3) unsigned NOT NULL COMMENT '试卷总分',
     `pass_score` tinyint(3) unsigned NOT NULL COMMENT '及格线',
-    `limit_time` bigint(16) unsigned NOT NULL COMMENT '考试限时',
+    `limit_time` int(10) unsigned NOT NULL COMMENT '考试限时',
     `join_count` int(10) NOT NULL DEFAULT '0' COMMENT '参与人次',
     `day_limit_count` INT(10) NOT NULL DEFAULT '0' COMMENT '每日限制考试次数',
-    `start_time` BIGINT(16) DEFAULT NULL COMMENT '开始时间',
-    `end_time` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '过期时间',
+    `start_time` BIGINT(16) NOT NULL DEFAULT '0' COMMENT '开始时间',
+    `end_time` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '过期时间',
     `is_only_room` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '仅用于考场',
-    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     `deletetime` bigint(16) DEFAULT NULL COMMENT '删除时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `cate_id` (`cate_id`,`status`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='试卷';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='试卷';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_paper_question` (
@@ -97,98 +97,95 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_paper_question` (
     `question_id` int(11) unsigned NOT NULL COMMENT '试题',
     `score` int(10) unsigned NOT NULL COMMENT '分数',
     `sort` int(10) unsigned NOT NULL DEFAULT '1' COMMENT '排序',
-    `answer_config` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_general_ci',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `answer_config` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     `deletetime` bigint(16) DEFAULT NULL COMMENT '删除时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `paper_id` (`paper_id`),
     KEY `question_id` (`question_id`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='试卷试题';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='试卷试题';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_question` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
     `cate_id` int(11) unsigned NOT NULL COMMENT '分类',
-    `kind` enum('JUDGE','SINGLE','MULTI','FILL','SHORT','MATERIAL') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型',
-    `title` varchar(1000) COLLATE utf8mb4_general_ci NOT NULL COMMENT '题目',
-    `explain` varchar(2000) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '解析',
-    `difficulty` enum('EASY','GENERAL','HARD') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'GENERAL' COMMENT '难度',
-    `options_json` text COLLATE utf8mb4_general_ci NOT NULL COMMENT '选项',
-    `options_img` varchar(1000) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '选项图片',
-    `options_extend` TEXT NULL DEFAULT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_general_ci',
-    `answer` TEXT NOT NULL COMMENT '正确答案' COLLATE 'utf8mb4_general_ci',
-    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
+    `kind` enum('JUDGE','SINGLE','MULTI','FILL','SHORT','MATERIAL') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型',
+    `title` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '题目',
+    `explain` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '解析',
+    `difficulty` enum('EASY','GENERAL','HARD') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'GENERAL' COMMENT '难度',
+    `options_json` text COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '选项',
+    `options_img` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '选项图片',
+    `options_extend` TEXT NULL DEFAULT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_unicode_ci',
+    `answer` TEXT NOT NULL COMMENT '正确答案' COLLATE 'utf8mb4_unicode_ci',
+    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
     `is_material_child` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '属于材料题子题:0=否,1=是',
     `material_question_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '所属材料题',
-    `material_score` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '材料子题分数',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `material_score` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '材料子题分数',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     `deletetime` bigint(16) DEFAULT NULL COMMENT '删除时间',
     PRIMARY KEY (`id`),
     KEY `kind` (`kind`,`status`) USING BTREE,
     KEY `cate_id` (`cate_id`,`kind`,`status`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='试题';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='试题';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_question_collect` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
     `user_id` int(11) unsigned NOT NULL COMMENT '用户',
     `question_id` int(11) unsigned NOT NULL COMMENT '试题',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `question_id` (`question_id`) USING BTREE,
     KEY `user_id` (`user_id`) USING BTREE
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='题目收藏';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='题目收藏';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_question_wrong` (
     `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
     `user_id` INT(11) UNSIGNED NOT NULL COMMENT '用户',
     `question_id` INT(11) UNSIGNED NOT NULL COMMENT '试题',
-    `user_answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '用户答案' COLLATE 'utf8mb4_general_ci',
-    `kind` ENUM('PAPER','ROOM','TRAINING') NULL DEFAULT 'PAPER' COMMENT '来源:PAPER=试卷,ROOM=考场,TRAINING=练题' COLLATE 'utf8mb4_general_ci',
-    `cate_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '所属题库',
-    `paper_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '来源试卷',
-    `room_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '来源考场',
-    `createtime` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '创建时间',
+    `user_answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '用户答案' COLLATE 'utf8mb4_unicode_ci',
+    `kind` ENUM('PAPER','ROOM','TRAINING') NULL DEFAULT 'PAPER' COMMENT '来源:PAPER=试卷,ROOM=考场,TRAINING=练题' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` BIGINT(16) UNSIGNED NULL DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     INDEX `question_id` (`question_id`) USING BTREE,
     INDEX `kind` (`kind`) USING BTREE,
     INDEX `user_id` (`user_id`, `kind`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='错题记录';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='错题记录';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_room` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
-    `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '考场标题',
-    `contents` varchar(1000) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '考场说明',
+    `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '考场标题',
+    `contents` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '考场说明',
     `cate_id` int(11) NOT NULL COMMENT '考场分类',
     `paper_id` int(11) NOT NULL COMMENT '考试试卷',
     `people_count` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '限制考场人数',
-    `start_time` bigint(16) DEFAULT NULL COMMENT '考试开始时间',
-    `end_time` bigint(16) DEFAULT NULL COMMENT '考试结束时间',
+    `start_time` bigint(16) NOT NULL DEFAULT '0' COMMENT '考试开始时间',
+    `end_time` bigint(16) NOT NULL DEFAULT '0' COMMENT '考试结束时间',
     `weigh` int(11) NOT NULL DEFAULT '1' COMMENT '排序',
-    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
-    `signup_mode` enum('NORMAL','PASSWORD','AUDIT') COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NORMAL' COMMENT '报名方式:NORMAL=普通模式,PASSWORD=密码模式,AUDIT=审核模式',
-    `password` varchar(50) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '考场密码',
+    `status` enum('NORMAL','HIDDEN') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '状态',
+    `signup_mode` enum('NORMAL','PASSWORD','AUDIT') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'NORMAL' COMMENT '报名方式:NORMAL=普通模式,PASSWORD=密码模式,AUDIT=审核模式',
+    `password` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '考场密码',
     `is_makeup` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否允许补考:0=关闭,1=开启',
     `makeup_count` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '补考次数',
     `is_rank` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否已排名',
-    `signup_count` int(11) NOT NULL DEFAULT '0' COMMENT '报考人数',
-    `grade_count` int(11) NOT NULL DEFAULT '0' COMMENT '考试人数',
-    `pass_count` int(11) NOT NULL DEFAULT '0' COMMENT '及格人数',
+    `signup_count` tinyint(4) NOT NULL DEFAULT '0' COMMENT '报考人数',
+    `grade_count` tinyint(4) NOT NULL DEFAULT '0' COMMENT '考试人数',
+    `pass_count` tinyint(4) NOT NULL DEFAULT '0' COMMENT '及格人数',
     `pass_rate` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '及格率',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     `deletetime` bigint(16) DEFAULT NULL COMMENT '删除时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `paper_id` (`paper_id`),
     KEY `status` (`status`) USING BTREE,
     KEY `cate_id` (`status`,`cate_id`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考试考场';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='考试考场';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_room_grade` (
@@ -197,60 +194,59 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_room_grade` (
     `cate_id` int(11) unsigned NOT NULL COMMENT '所属分类',
     `room_id` int(11) unsigned NOT NULL COMMENT '所属考场',
     `paper_id` int(11) unsigned NOT NULL COMMENT '所属试卷',
-    `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_general_ci',
+    `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_unicode_ci',
     `score` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '考试分数',
     `system_score` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '系统得分',
     `manual_score` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT '人工判分',
     `is_pass` tinyint(3) NOT NULL DEFAULT '0' COMMENT '是否及格:0=不及格,1=及格',
     `is_makeup` tinyint(3) NOT NULL DEFAULT '0' COMMENT '是否是补考:0=否,1=是',
-    `pass_score` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '及格线',
     `total_score` tinyint(3) NOT NULL DEFAULT '0' COMMENT '总分数',
     `total_count` tinyint(3) NOT NULL DEFAULT '0' COMMENT '总题数',
     `right_count` tinyint(3) NOT NULL DEFAULT '0' COMMENT '答对数',
     `error_count` tinyint(3) NOT NULL DEFAULT '0' COMMENT '答错数',
     `rank` tinyint(3) NOT NULL DEFAULT '0' COMMENT '本次考试排名',
     `is_pre` tinyint(3) NOT NULL DEFAULT '0' COMMENT '是否为预载入数据',
-    `grade_time` bigint(16) unsigned NOT NULL COMMENT '考试用时',
-    `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_general_ci',
-    `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_general_ci',
-    `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci',
-    `configs` TEXT NULL DEFAULT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `grade_time` int(10) unsigned NOT NULL COMMENT '考试用时',
+    `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_unicode_ci',
+    `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_unicode_ci',
+    `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci',
+    `configs` TEXT NULL DEFAULT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `user_id` (`user_id`) USING BTREE,
     KEY `paper_id` (`paper_id`) USING BTREE,
     KEY `cate_id` (`cate_id`) USING BTREE,
     KEY `FK2_exam_room_grade_with_exam_room` (`room_id`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考场考试成绩';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='考场考试成绩';
 
 
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_room_signup` (
     `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
     `room_id` int(11) unsigned NOT NULL COMMENT '所属考场',
     `user_id` int(11) unsigned NOT NULL COMMENT '报名用户',
-    `real_name` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '真实姓名',
-    `phone` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号码',
-    `message` varchar(200) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '审核说明',
-    `status` enum('0','1','2') COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '状态:0=未审核,1=报名成功,2=报名被拒绝',
-    `createtime` bigint(16) unsigned DEFAULT NULL COMMENT '创建时间',
+    `real_name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '真实姓名',
+    `phone` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '手机号码',
+    `message` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '审核说明',
+    `status` enum('0','1','2') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '状态:0=未审核,1=报名成功,2=报名被拒绝',
+    `createtime` bigint(16) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` bigint(16) unsigned DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     KEY `room_id` (`room_id`,`status`),
     KEY `user_id` (`user_id`,`status`)
-    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考场报名';
+    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='考场报名';
 
 -- 1.0.2版本,新增考卷每日限制考试次数
 ALTER TABLE `__PREFIX__exam_paper` ADD COLUMN `day_limit_count` INT(10) NOT NULL DEFAULT '0' COMMENT '每日限制考试次数' AFTER `join_count`;
 ALTER TABLE `__PREFIX__exam_grade` ADD COLUMN `date` CHAR(10) NOT NULL DEFAULT '' COMMENT '考试日期' AFTER `grade_time`;
 
 -- 1.0.4版本,新增系统配置 - 页面配置
-ALTER TABLE `__PREFIX__exam_config_info` ADD COLUMN `page_config` MEDIUMTEXT NULL DEFAULT NULL COMMENT '页面配置' COLLATE 'utf8mb4_general_ci' AFTER `wx_config`;
+ALTER TABLE `__PREFIX__exam_config_info` ADD COLUMN `page_config` MEDIUMTEXT NULL DEFAULT NULL COMMENT '页面配置' COLLATE 'utf8mb4_unicode_ci' AFTER `wx_config`;
 
 
 -- 1.0.6版本
 -- 加大选项图片字段长度
-ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `options_img` `options_img` VARCHAR(1000) NULL DEFAULT NULL COMMENT '选项图片' COLLATE 'utf8mb4_general_ci' AFTER `options_json`;
+ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `options_img` `options_img` VARCHAR(1000) NULL DEFAULT NULL COMMENT '选项图片' COLLATE 'utf8mb4_unicode_ci' AFTER `options_json`;
 
 
 -- 1.0.9版本
@@ -260,68 +256,68 @@ ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `options_img` `options_img`
 -- 用户信息
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_user_info` (
     `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
-    `type` ENUM('NORMAL','VIP_MONTH','VIP_YEAR','VIP_LIFE') NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型:NORMAL=普通用户,VIP_MONTH=月卡会员,VIP_YEAR=年卡会员,VIP_LIFE=终身会员' COLLATE 'utf8mb4_general_ci',
+    `type` ENUM('NORMAL','VIP_MONTH','VIP_YEAR','VIP_LIFE') NOT NULL DEFAULT 'NORMAL' COMMENT '用户类型:NORMAL=普通用户,VIP_MONTH=月卡会员,VIP_YEAR=年卡会员,VIP_LIFE=终身会员' COLLATE 'utf8mb4_unicode_ci',
     `member_config_id` INT(10) NOT NULL COMMENT '开通会员类型',
     `user_id` INT(11) NOT NULL DEFAULT '0' COMMENT '用户ID',
     `score` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '积分',
     `score_inc` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '累计获得积分',
     `score_dec` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '累计支出积分',
-    `expire_time` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '过期时间',
+    `expire_time` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '过期时间',
     `createtime` BIGINT(16) NULL DEFAULT NULL COMMENT '创建时间',
     `updatetime` BIGINT(16) NULL DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     INDEX `user_id` (`user_id`) USING BTREE,
     INDEX `type` (`type`) USING BTREE,
     INDEX `member_config_id` (`member_config_id`, `expire_time`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户信息';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户信息';
 
 -- 1.0.11
 -- 试卷加开始、过期时间,仅用于考场字段
-ALTER TABLE `__PREFIX__exam_paper` ADD COLUMN `end_time` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '过期时间' AFTER `member_price`;
+ALTER TABLE `__PREFIX__exam_paper` ADD COLUMN `end_time` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '过期时间' AFTER `member_price`;
 ALTER TABLE `__PREFIX__exam_paper`
-    ADD COLUMN `start_time` BIGINT(16) DEFAULT NULL COMMENT '开始时间' AFTER `member_price`,
+    ADD COLUMN `start_time` BIGINT(16) NOT NULL DEFAULT '0' COMMENT '开始时间' AFTER `member_price`,
 	ADD COLUMN `is_only_room` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '仅用于考场' AFTER `end_time`,
 	ADD INDEX `is_only_room` (`is_only_room`),
 	ADD INDEX `start_time` (`start_time`);
 
 -- 试题新增【填空题】类型
-ALTER TABLE `__PREFIX__exam_question` ADD COLUMN `options_extend` VARCHAR(1000) NULL DEFAULT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_general_ci' AFTER `options_img`;
-ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `kind` `kind` ENUM('JUDGE','SINGLE','MULTI','FILL') NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型' COLLATE 'utf8mb4_general_ci';
-ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `answer` `answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正确答案' COLLATE 'utf8mb4_general_ci';
+ALTER TABLE `__PREFIX__exam_question` ADD COLUMN `options_extend` VARCHAR(1000) NULL DEFAULT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_unicode_ci' AFTER `options_img`;
+ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `kind` `kind` ENUM('JUDGE','SINGLE','MULTI','FILL') NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型' COLLATE 'utf8mb4_unicode_ci';
+ALTER TABLE `__PREFIX__exam_question` CHANGE COLUMN `answer` `answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '正确答案' COLLATE 'utf8mb4_unicode_ci';
 
 -- 1.0.12
 -- 考卷成绩新增答题信息
 ALTER TABLE `__PREFIX__exam_grade`
-    ADD COLUMN `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_general_ci' AFTER `date`,
-	ADD COLUMN `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_general_ci' AFTER `question_ids`,
-	ADD COLUMN `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci' AFTER `error_ids`;
+    ADD COLUMN `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_unicode_ci' AFTER `date`,
+	ADD COLUMN `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_unicode_ci' AFTER `question_ids`,
+	ADD COLUMN `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci' AFTER `error_ids`;
 
 -- 考场成绩新增答题信息
 ALTER TABLE `__PREFIX__exam_room_grade`
-    ADD COLUMN `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_general_ci' AFTER `grade_time`,
-	ADD COLUMN `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_general_ci' AFTER `question_ids`,
-	ADD COLUMN `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci' AFTER `error_ids`;
+    ADD COLUMN `question_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '试卷ID集合' COLLATE 'utf8mb4_unicode_ci' AFTER `grade_time`,
+	ADD COLUMN `error_ids` VARCHAR(2000) NULL DEFAULT '' COMMENT '错题ID集合' COLLATE 'utf8mb4_unicode_ci' AFTER `question_ids`,
+	ADD COLUMN `user_answers` TEXT NULL DEFAULT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci' AFTER `error_ids`;
 
 -- 1.0.13
 -- 考卷成绩新增考卷选题配置
 ALTER TABLE `__PREFIX__exam_grade`
-    ADD COLUMN `configs` VARCHAR(1000) NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`;
+    ADD COLUMN `configs` VARCHAR(1000) NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`;
 
 -- 考场成绩新增考卷选题配置
 ALTER TABLE `__PREFIX__exam_room_grade`
-    ADD COLUMN `configs` VARCHAR(1000) NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`;
+    ADD COLUMN `configs` VARCHAR(1000) NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`;
 
 
 -- 1.1.0
 -- 错题新增用户答案
 ALTER TABLE `__PREFIX__exam_question_wrong`
-    ADD COLUMN `user_answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '用户答案' COLLATE 'utf8mb4_general_ci' AFTER `question_id`;
+    ADD COLUMN `user_answer` VARCHAR(1000) NOT NULL DEFAULT '' COMMENT '用户答案' COLLATE 'utf8mb4_unicode_ci' AFTER `question_id`;
 
 ALTER TABLE `__PREFIX__exam_grade`
-    ADD COLUMN `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_general_ci' AFTER `paper_id`;
+    ADD COLUMN `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_unicode_ci' AFTER `paper_id`;
 
 ALTER TABLE `__PREFIX__exam_room_grade`
-    ADD COLUMN `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_general_ci' AFTER `paper_id`;
+    ADD COLUMN `mode` ENUM('RANDOM','FIX') NOT NULL DEFAULT 'RANDOM' COMMENT '选题模式' COLLATE 'utf8mb4_unicode_ci' AFTER `paper_id`;
 
 -- 1.2.1
 -- 新增公告前端跳转信息
@@ -331,22 +327,22 @@ ALTER TABLE `__PREFIX__exam_notice`
 -- 1.3.0
 -- 新增试题类型【简答题】、【材料题】
 ALTER TABLE `__PREFIX__exam_question`
-    CHANGE COLUMN `kind` `kind` ENUM('JUDGE','SINGLE','MULTI','FILL','SHORT','MATERIAL') NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型' COLLATE 'utf8mb4_general_ci' AFTER `cate_id`;
+    CHANGE COLUMN `kind` `kind` ENUM('JUDGE','SINGLE','MULTI','FILL','SHORT','MATERIAL') NOT NULL DEFAULT 'JUDGE' COMMENT '试题类型' COLLATE 'utf8mb4_unicode_ci' AFTER `cate_id`;
 ALTER TABLE `__PREFIX__exam_question`
-    CHANGE COLUMN `title` `title` VARCHAR(2000) NOT NULL COMMENT '题目' COLLATE 'utf8mb4_general_ci' AFTER `kind`;
+    CHANGE COLUMN `title` `title` VARCHAR(2000) NOT NULL COMMENT '题目' COLLATE 'utf8mb4_unicode_ci' AFTER `kind`;
 ALTER TABLE `__PREFIX__exam_question`
-    CHANGE COLUMN `answer` `answer` TEXT NOT NULL COMMENT '正确答案' COLLATE 'utf8mb4_general_ci' AFTER `options_extend`;
+    CHANGE COLUMN `answer` `answer` TEXT NOT NULL COMMENT '正确答案' COLLATE 'utf8mb4_unicode_ci' AFTER `options_extend`;
 ALTER TABLE `__PREFIX__exam_question`
-    CHANGE COLUMN `options_extend` `options_extend` TEXT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_general_ci' AFTER `options_img`;
+    CHANGE COLUMN `options_extend` `options_extend` TEXT NULL COMMENT '选项扩展' COLLATE 'utf8mb4_unicode_ci' AFTER `options_img`;
 
 -- 试卷标题、配置字段扩展长度
 ALTER TABLE `__PREFIX__exam_paper`
-    CHANGE COLUMN `title` `title` VARCHAR(3000) NOT NULL COMMENT '试卷名称' COLLATE 'utf8mb4_general_ci' AFTER `mode`,
-    CHANGE COLUMN `configs` `configs` VARCHAR(3000) NOT NULL COMMENT '选题配置' COLLATE 'utf8mb4_general_ci' AFTER `title`;
+    CHANGE COLUMN `title` `title` VARCHAR(3000) NOT NULL COMMENT '试卷名称' COLLATE 'utf8mb4_unicode_ci' AFTER `mode`,
+    CHANGE COLUMN `configs` `configs` VARCHAR(3000) NOT NULL COMMENT '选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `title`;
 
 -- 试卷固定选题新增试题答案
 ALTER TABLE `__PREFIX__exam_paper_question`
-    ADD COLUMN `answer_config` TEXT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_general_ci' AFTER `sort`;
+    ADD COLUMN `answer_config` TEXT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_unicode_ci' AFTER `sort`;
 
 -- 考试成绩新增系统得分、人工判分
 ALTER TABLE `__PREFIX__exam_grade`
@@ -371,15 +367,15 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_material_question` (
     `question_id` INT(11) UNSIGNED NOT NULL COMMENT '材料题子题目',
     `score` INT(11) UNSIGNED NOT NULL COMMENT '分数',
     `weigh` INT(10) UNSIGNED NOT NULL DEFAULT '1' COMMENT '排序',
-    `answer` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_general_ci',
-    `createtime` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '创建时间',
+    `answer` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` BIGINT(16) UNSIGNED NULL DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     UNIQUE INDEX `from_question_id` (`parent_question_id`, `question_id`) USING BTREE
 ) COMMENT='材料题关联表'
-COLLATE='utf8mb4_general_ci'
+COLLATE='utf8mb4_unicode_ci'
 ENGINE=InnoDB
-;
+ROW_FORMAT=DYNAMIC;
 
 -- 试卷成绩手动判题记录表
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_manual_grade_log` (
@@ -391,8 +387,8 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_manual_grade_log` (
     `question_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '试题ID',
     `before_score` INT(11) UNSIGNED NOT NULL COMMENT '修改前分数',
     `after_score` INT(11) UNSIGNED NOT NULL COMMENT '修改后分数',
-    `status` ENUM('0','1') NOT NULL DEFAULT '0' COMMENT '状态:0=未生效,1=已生效' COLLATE 'utf8mb4_general_ci',
-    `createtime` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '创建时间',
+    `status` ENUM('0','1') NOT NULL DEFAULT '0' COMMENT '状态:0=未生效,1=已生效' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` BIGINT(16) UNSIGNED NULL DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     INDEX `admin_id` (`admin_id`) USING BTREE,
@@ -402,8 +398,9 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_manual_grade_log` (
     INDEX `paper_id` (`paper_id`, `status`) USING BTREE,
     INDEX `question_id` (`question_id`) USING BTREE
 ) COMMENT='试卷成绩手动判题记录表'
-COLLATE='utf8mb4_general_ci'
+COLLATE='utf8mb4_unicode_ci'
 ENGINE=InnoDB
+ROW_FORMAT=DYNAMIC
 ;
 
 -- 考场成绩手动判题记录表
@@ -417,8 +414,8 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_manual_room_grade_log` (
     `question_id` INT(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '试题ID',
     `before_score` INT(11) UNSIGNED NOT NULL COMMENT '修改前分数',
     `after_score` INT(11) UNSIGNED NOT NULL COMMENT '修改后分数',
-    `status` ENUM('0','1') NOT NULL DEFAULT '0' COMMENT '状态:0=未生效,1=已生效' COLLATE 'utf8mb4_general_ci',
-    `createtime` BIGINT(16) UNSIGNED DEFAULT NULL COMMENT '创建时间',
+    `status` ENUM('0','1') NOT NULL DEFAULT '0' COMMENT '状态:0=未生效,1=已生效' COLLATE 'utf8mb4_unicode_ci',
+    `createtime` BIGINT(16) UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
     `updatetime` BIGINT(16) UNSIGNED NULL DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`id`) USING BTREE,
     INDEX `admin_id` (`admin_id`) USING BTREE,
@@ -429,22 +426,23 @@ CREATE TABLE IF NOT EXISTS `__PREFIX__exam_manual_room_grade_log` (
     INDEX `question_id` (`question_id`) USING BTREE,
     INDEX `room_id` (`room_id`) USING BTREE
 ) COMMENT='考场成绩手动判题记录表'
-COLLATE='utf8mb4_general_ci'
+COLLATE='utf8mb4_unicode_ci'
 ENGINE=InnoDB
+ROW_FORMAT=DYNAMIC
 ;
 
 -- 1.4.0
 -- 成绩记录表扩展配置长度
 ALTER TABLE `__PREFIX__exam_grade`
-    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`;
+    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`;
 ALTER TABLE `__PREFIX__exam_room_grade`
-    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`;
+    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`;
 
 -- 材料题关联表新增分数字段
 ALTER TABLE `__PREFIX__exam_material_question`
     ADD COLUMN `score` INT(11) UNSIGNED NOT NULL COMMENT '分数' AFTER `question_id`,
     ADD COLUMN `weigh` INT(10) UNSIGNED NOT NULL DEFAULT '1' COMMENT '排序' AFTER `score`,
-	ADD COLUMN `answer` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_general_ci' AFTER `weigh`;
+	ADD COLUMN `answer` TEXT NULL DEFAULT NULL COMMENT '正确答案配置' COLLATE 'utf8mb4_unicode_ci' AFTER `weigh`;
 
 -- 题目新增材料题子题字段
 ALTER TABLE `__PREFIX__exam_question`
@@ -466,13 +464,13 @@ ALTER TABLE `__PREFIX__exam_question`
 
 -- 错题记录用户答案字段扩展长度(某些旧版本没有更新表结构)
 ALTER TABLE `__PREFIX__exam_question_wrong`
-    CHANGE COLUMN `user_answer` `user_answer` TEXT NOT NULL COMMENT '用户答案' COLLATE 'utf8mb4_general_ci' AFTER `question_id`;
+    CHANGE COLUMN `user_answer` `user_answer` TEXT NOT NULL COMMENT '用户答案' COLLATE 'utf8mb4_unicode_ci' AFTER `cate_id`;
 ALTER TABLE `__PREFIX__exam_room_grade`
-    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`,
-    CHANGE COLUMN `user_answers` `user_answers` TEXT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci' AFTER `error_ids`;
+    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`,
+    CHANGE COLUMN `user_answers` `user_answers` TEXT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci' AFTER `error_ids`;
 ALTER TABLE `__PREFIX__exam_grade`
-    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_general_ci' AFTER `user_answers`,
-    CHANGE COLUMN `user_answers` `user_answers` TEXT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_general_ci' AFTER `error_ids`;
+    CHANGE COLUMN `configs` `configs` TEXT NULL COMMENT '试卷选题配置' COLLATE 'utf8mb4_unicode_ci' AFTER `user_answers`,
+    CHANGE COLUMN `user_answers` `user_answers` TEXT NULL COMMENT '用户答案集合' COLLATE 'utf8mb4_unicode_ci' AFTER `error_ids`;
 
 -- 1.5.2
 CREATE TABLE IF NOT EXISTS `__PREFIX__exam_correction_type` (
@@ -506,29 +504,4 @@ ALTER TABLE `__PREFIX__exam_cate`
 
 -- 1.5.8
 ALTER TABLE `__PREFIX__exam_paper`
-    CHANGE COLUMN `quantity` `quantity` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '题目数量' AFTER `configs`;
-
-ALTER TABLE `__PREFIX__exam_paper`
-    CHANGE COLUMN `total_score` `total_score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '试卷总分' AFTER `quantity`,
-    CHANGE COLUMN `pass_score` `pass_score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '及格线' AFTER `total_score`;
-
-ALTER TABLE `__PREFIX__exam_grade`
-    CHANGE COLUMN `score` `score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '考试分数' AFTER `mode`,
-    CHANGE COLUMN `system_score` `system_score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '系统得分' AFTER `score`,
-    CHANGE COLUMN `manual_score` `manual_score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '人工判分' AFTER `system_score`,
-    CHANGE COLUMN `total_score` `total_score` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '总分数' AFTER `is_pass`,
-    CHANGE COLUMN `total_count` `total_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '总题数' AFTER `total_score`,
-    CHANGE COLUMN `right_count` `right_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '答对数' AFTER `total_count`,
-    CHANGE COLUMN `error_count` `error_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '答错数' AFTER `right_count`;
-
-ALTER TABLE `__PREFIX__exam_grade`
-    ADD COLUMN `pass_score` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '及格线' AFTER `is_pass`;
-
-ALTER TABLE `__PREFIX__exam_room_grade`
-    ADD COLUMN `pass_score` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '及格线' AFTER `is_makeup`;
-
--- 1.7.0 错题记录表新增来源试卷和考场字段
-ALTER TABLE `__PREFIX__exam_question_wrong`
-    ADD COLUMN `cate_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '所属题库' AFTER `kind`,
-    ADD COLUMN `paper_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '来源试卷' AFTER `cate_id`,
-	ADD COLUMN `room_id` INT(11) UNSIGNED NULL DEFAULT '0' COMMENT '来源考场' AFTER `paper_id`;
+    CHANGE COLUMN `quantity` `quantity` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '题目数量' AFTER `configs`;

+ 39 - 76
addons/exam/library/ExamService.php

@@ -17,7 +17,7 @@ use addons\exam\model\RoomGradeModel;
 use addons\exam\model\RoomModel;
 use addons\exam\model\RoomSignupModel;
 use app\admin\model\exam\MaterialQuestionModel;
-
+use think\Db;
 /**
  * 考试相关服务
  */
@@ -25,7 +25,6 @@ class ExamService
 {
     /**
      * 获取试卷题目
-     *
      * @param     $paper_id
      * @param int $room_id
      * @return array`
@@ -36,7 +35,7 @@ class ExamService
             fail('缺少试卷ID');
         }
 
-        $paper = self::validPaper($paper_id, $room_id);
+        $paper = self::validPaper($paper_id, $room_id,true);
         switch ($paper['mode']) {
             case PaperMode::RANDOM:
                 $questions = self::getRandomQuestions($paper);
@@ -57,7 +56,6 @@ class ExamService
 
     /**
      * 获取试卷随机题
-     *
      * @param $paper
      * @return array
      */
@@ -103,10 +101,6 @@ class ExamService
             }
         }
 
-        if (count($questions) < intval($paper['quantity'])) {
-            fail('试卷题目数量不足,请联系管理员检查试卷配置');
-        }
-
         // 合并材料题子题目
         $questions = QuestionModel::mergeMaterialQuestions($questions);
 
@@ -115,17 +109,12 @@ class ExamService
 
     /**
      * 获取试卷固定题
-     *
      * @param $paper
      * @return array
      */
     public static function getFixQuestions($paper, $hidden = true)
     {
         $questions = QuestionModel::getFixListByPaper($paper['id'], ['materialQuestions.question']);
-        if (count($questions) < intval($paper['quantity'])) {
-            fail('试卷题目数量不足,请联系管理员检查试卷配置');
-        }
-
         // 合并材料题子题目
         $questions = QuestionModel::mergeMaterialQuestions($questions);
         if ($hidden) {
@@ -136,7 +125,6 @@ class ExamService
 
     /**
      * 试卷考试
-     *
      * @param $user_id
      * @param $paper_id
      * @param $user_questions
@@ -144,13 +132,10 @@ class ExamService
      * @param $paper
      * @return array
      */
-    public static function paperExam($user_id, $paper_id, $user_questions, $start_time, &$paper, $room = null)
+    public static function paperExam($user_id, $paper_id, $user_questions, $start_time, &$paper, $from_room = false)
     {
-        $from_room = $room ? true : false;
-        $source    = $from_room ? 'ROOM' : 'PAPER';
-
         // 验证试卷
-        $paper = self::validPaper($paper_id, $from_room ? 1 : 0);
+        $paper = self::validPaper($paper_id, $from_room ? 1 : 0,false);
         if (!$questions_ids = array_column($user_questions, 'id')) {
             fail('提交的题目数据有误');
         }
@@ -183,10 +168,7 @@ class ExamService
                         $difficulty = $material_question['difficulty'];
                         // $score = PaperModel::getSingleScore($paper['configs'], strtolower($kind), strtolower($difficulty));    // 每题分数
                         // 材料题子题目设定的分数
-                        $score = MaterialQuestionModel::where('parent_question_id', $material_ids[$key])
-                            ->where('question_id', $question['id'])
-                            ->cache(60)
-                            ->value('score');
+                        $score = MaterialQuestionModel::where('parent_question_id', $material_ids[$key])->where('question_id', $question['id'])->cache(60)->value('score');
                     }
                 } else {
                     $score = PaperModel::getSingleScore($paper['configs'], strtolower($kind), strtolower($difficulty));    // 每题分数
@@ -206,7 +188,7 @@ class ExamService
                 case 'MULTI':   // 多选题
 
                     // 答题正确
-                    if (strtoupper($answers[$key]) == strtoupper($question['answer'])) {
+                    if (strtoupper($answers[$key]) == $question['answer']) {
                         $total_score                      += $score;
                         $user_questions[$key]['is_right'] = true;
                     } else {
@@ -215,11 +197,8 @@ class ExamService
                         $user_questions[$key]['is_right'] = false;
 
                         // 记录错题
-                        QuestionModel::recordWrong($question['kind'], $question['id'], $user_id, $answers[$key], $source, [
-                            'cate_id'  => $question['cate_id'],
-                            'paper_id' => $paper_id,
-                            'room_id'  => $room['id'] ?? 0,
-                        ]);
+                        QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
+                        // $question->logWrong($user_id, $answers[$key]);
                     }
                     break;
 
@@ -246,11 +225,8 @@ class ExamService
                         $error_count++;
 
                         // 记录错题
-                        QuestionModel::recordWrong($question['kind'], $question['id'], $user_id, $answers[$key], $source, [
-                            'cate_id'  => $question['cate_id'],
-                            'paper_id' => $paper_id,
-                            'room_id'  => $room['id'] ?? 0,
-                        ]);
+                        QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
+                        // $question->logWrong($user_id, $answers[$key]);
                     }
                     break;
 
@@ -261,23 +237,25 @@ class ExamService
                     $right_score   = 0;
                     $answer_score  = [];
                     foreach ($answer_config['config'] as $answer_item) {
-                        if ($right_score < $score) {
+                        /*if ($right_score < $score) {*/
                             // 匹配答案关键词
                             if (strpos($user_answers, $answer_item['answer']) !== false) {
-                                $right_score += $answer_item['score'];
+//                                $right_score += $answer_item['score'];
+                                $right_score = $score;
                                 // 得分情况
                                 $answer_score[] = [
                                     'answer'        => $answer_item['answer'],
-                                    'score'         => min($score, $answer_item['score']),
+//                                    'score'         => min($score, $answer_item['score']), //这里不对
+                                    'score'         => $score,
                                     'keyword_score' => $answer_item['score'],
                                     'max_score'     => $score,
                                 ];
                             }
-                        }
+                        /*}*/
                     }
 
                     // 最高得分不能超过题目分数
-                    $right_score = min($right_score, $score);
+//                    $right_score = min($right_score, $score);//这里不对
 
                     // 有得分
                     if ($right_score > 0) {
@@ -289,22 +267,23 @@ class ExamService
                         $error_count++;
 
                         // 记录错题
-                        QuestionModel::recordWrong($question['kind'], $question['id'], $user_id, $answers[$key], $source, [
-                            'cate_id'  => $question['cate_id'],
-                            'paper_id' => $paper_id,
-                            'room_id'  => $room['id'] ?? 0,
-                        ]);
+                        QuestionModel::recordWrong($question['id'], $user_id, $answers[$key]);
                     }
 
                     $user_questions[$key]['answer_score'] = $answer_score;
                     break;
             }
+
+            //记录总答题次数,答对数,答错数
+            Db::name('exam_question')->where('id',$question['id'])->setInc('total_count');
+            $field = $user_questions[$key]['is_right'] ? 'right_count' : 'error_count';
+            Db::name('exam_question')->where('id',$question['id'])->setInc($field);
         }
 
         // 递增参与人次
         $paper->setInc('join_count');
 
-        return [
+        $result =  [
             'total_score'  => $paper['total_score'],                                                                        // 试卷总分
             'score'        => $total_score,                                                                                 // 考试分数
             'is_pass'      => $total_score >= $paper['pass_score'],                                                         // 是否及格
@@ -320,11 +299,11 @@ class ExamService
             'configs'      => json_encode($paper['configs']),                                                               // 试卷配置
             'mode'         => $paper['mode'],                                                                               // 试卷选题模式
         ];
+        return $result;
     }
 
     /**
      * 考场考试
-     *
      * @param                     $user_id
      * @param                     $room_id
      * @param                     $room_grade_id
@@ -341,12 +320,11 @@ class ExamService
         // 验证考场信息
         $room = self::validRoom($user_id, $room_id, $room_grade_id, $is_makeup, $room_grade_log);
 
-        return self::paperExam($user_id, $room['paper_id'], $questions, $start_time, $paper, $room);
+        return self::paperExam($user_id, $room['paper_id'], $questions, $start_time, $paper, true);
     }
 
     /**
      * 预创建考场考试记录(消耗一次考试记录,避免重复进入考场看题)
-     *
      * @param $room_id
      * @param $user_id
      * @return int
@@ -383,12 +361,11 @@ class ExamService
 
     /**
      * 验证试卷
-     *
      * @param int $paper_id 试卷ID
      * @param int $room_id  考场ID
      * @return PaperModel|null
      */
-    private static function validPaper($paper_id, $room_id = 0)
+    private static function validPaper($paper_id, $room_id = 0,$checktime = false)
     {
         $paper   = PaperModel::get($paper_id);
         $user_id = getUserId();
@@ -403,22 +380,23 @@ class ExamService
         }
 
         // 普通考试
-        if (!$room_id) {
+        /*if (!$room_id) {
             if ($user_id && $paper['day_limit_count'] > 0 && GradeModel::getUserDateGradeCount($paper_id, $user_id) >= $paper['day_limit_count']) {
                 fail('当前试卷考试次数已达今日上限,明天再来吧~');
-            }
+            }*/
 
+        if($checktime){
             if ($paper['end_time'] > 0 && $paper['end_time'] < time()) {
-                fail('该试卷已失效,不能参与考试了');
+                fail('该试卷已截止,不能参与考试了');
             }
         }
+        /*}*/
 
         return $paper;
     }
 
     /**
      * 验证考场
-     *
      * @param int                 $user_id        考试用户
      * @param int                 $room_id        试卷ID
      * @param int                 $room_grade_id  考场预创建成绩ID
@@ -447,37 +425,21 @@ class ExamService
         if ($room['is_makeup'] == 1 && $room['makeup_count'] > 0) {
             // $query = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id);
             // 考试次数
-            $room_exam_count = RoomGradeModel::where('room_id', $room_id)
-                ->where('paper_id', $room['paper_id'])
-                ->where('user_id', $user_id)
-                ->where('is_pre', 0)
-                ->count();
+            $room_exam_count = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->count();
             // 补考次数
-            $makeup_count     = RoomGradeModel::where('room_id', $room_id)
-                ->where('paper_id', $room['paper_id'])
-                ->where('user_id', $user_id)
-                ->where('is_makeup', 1)
-                ->count();
+            $makeup_count     = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->where('is_makeup', 1)->count();
             $min_makeup_count = $makeup_count - ($room_grade_id ? 1 : 0);
-            if ($min_makeup_count > $room['makeup_count']) {
-                fail("已超过本考场的补考次数,无法继续考试");
+            if ($min_makeup_count >= $room['makeup_count']) {
+                fail("已超过补考次数");
             }
 
-            $last_exam_log = RoomGradeModel::where('room_id', $room_id)
-                ->where('paper_id', $room['paper_id'])
-                ->where('user_id', $user_id)
-                ->order('id desc')
-                ->find();
+            $last_exam_log = RoomGradeModel::where('room_id', $room_id)->where('paper_id', $room['paper_id'])->where('user_id', $user_id)->order('id desc')->find();
             if ($last_exam_log && $last_exam_log['is_pass'] != 0) {
                 fail('最后一次考试已及格,不需要补考了');
             }
 
             // 考试次数大于0视为补考
-            $is_makeup = $room_exam_count >= 1 ? 1 : 0;
-
-            if ($user_id == 5134) {
-                // ddd($room_exam_count, $makeup_count, $min_makeup_count, $is_makeup);
-            }
+            $is_makeup = $room_exam_count > 1 ? 1 : 0;
         } else {
             if (RoomGradeModel::where('room_id', $room_id)->where('user_id', $user_id)->where('is_pre', 0)->count() > 0) {
                 fail('您已参加过该考场考试了');
@@ -486,6 +448,7 @@ class ExamService
             $is_makeup = 0;
         }
 
+
         // 考场预创建记录验证
         if ($room_grade_id) {
             if (!$room_grade_log = RoomGradeModel::where('id', $room_grade_id)->where('user_id', $user_id)->find()) {

+ 1 - 4
addons/exam/model/GradeModel.php

@@ -36,9 +36,6 @@ class GradeModel extends \app\admin\model\exam\GradeModel
         }
 
         $date = $date ?: date('Y-m-d');
-        return self::where('user_id', $user_id)
-            ->where('paper_id', $paper_id)
-            ->where('date', $date)
-            ->count();
+        return self::where('user_id', $user_id)->where('paper_id', $paper_id)->where('date', $date)->count();
     }
 }

+ 707 - 0
addons/exam/tuniao-bak/tn-calendar/tn-calendar.vue

@@ -0,0 +1,707 @@
+<template>
+  <tn-popup
+    v-model="value"
+    mode="bottom"
+    :popup="false"
+    length="auto"
+    :borderRadius="borderRadius"
+    :safeAreaInsetBottom="safeAreaInsetBottom"
+    :maskCloseable="maskCloseable"
+    :closeBtn="closeBtn"
+    :zIndex="elIndex"
+    @close="close"
+  >
+    <view class="tn-calendar-class tn-calendar">
+      <!-- 头部 -->
+      <view class="tn-calendar__header">
+        <view v-if="!$slots.tooltip || !$slots.$tooltip" class="tn-calendar__header__text">
+          {{ toolTips }}
+        </view>
+        <view v-else>
+          <slot name="tooltip"></slot>
+        </view>
+      </view>
+      
+      <!-- 操作提示信息 -->
+      <view class="tn-calendar__action">
+        <view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(false)">
+          <view><text class="tn-icon-left"></text></view>
+        </view>
+        <view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(false)">
+          <view><text class="tn-icon-left"></text></view>
+        </view>
+        <view class="tn-calendar__action__text">{{ dateTitle }}</view>
+        <view v-if="changeMonth" class="tn-calendar__action__icon" :style="{backgroundColor: monthArrowColor}" @tap.stop="changeMonthHandler(true)">
+          <view><text class="tn-icon-right"></text></view>
+        </view>
+        <view v-if="changeYear" class="tn-calendar__action__icon" :style="{backgroundColor: yearArrowColor}" @tap.stop="changeYearHandler(true)">
+          <view><text class="tn-icon-right"></text></view>
+        </view>
+      </view>
+      
+      <!-- 星期中文标识 -->
+      <view class="tn-calendar__week-day-zh">
+        <view v-for="(item,index) in weekDayZh" :key="index" class="tn-calendar__week-day-zh__text">{{ item }}</view>
+      </view>
+      
+      <!-- 日历主体 -->
+      <view class="tn-calendar__content">
+        <!-- 前置空白部分 -->
+        <block v-for="(item, index) in weekdayArr" :key="index">
+          <view class="tn-calendar__content__item"></view>
+        </block>
+        <view
+          v-for="(item, index) in daysArr"
+          :key="index"
+          class="tn-calendar__content__item"
+          :class="{
+            'tn-hover': disabledChoose(year, month, index + 1),
+            'tn-calendar__content--start-date': (mode === 'range' && startDate == `${year}-${month}-${index+1}`) || mode === 'date',
+            'tn-calendar__content--end-date': (mode === 'range' && endDate == `${year}-${month}-${index+1}`) || mode === 'date'
+          }"
+          :style="{
+            backgroundColor: colorValue(index, 'bg')
+          }"
+          @tap.stop="dateClick(index)"
+        >
+          <view class="tn-calendar__content__item__text" :style="{color: colorValue(index, 'text')}">
+            <view>{{ item.day }}</view>
+          </view>
+          <view class="tn-calendar__content__item__tips" :style="{color: item.color}">
+            {{ item.bottomInfo }}
+          </view>
+        </view>
+        
+        <view class="tn-calendar__content__month--bg">{{ month }}</view>
+      </view>
+      
+      <!-- 底部 -->
+      <view class="tn-calendar__bottom">
+        <view class="tn-calendar__bottom__choose">
+          <text>{{ mode === 'date' ? activeDate : startDate }}</text>
+          <text v-if="endDate">至{{ endDate }}</text>
+        </view>
+        <view class="tn-calendar__bottom__btn" :style="{backgroundColor: btnColor}" @click="handleBtnClick(false)">
+          <view class="tn-calendar__bottom__btn--text">确定</view>
+        </view>
+      </view>
+    </view>
+  </tn-popup>
+</template>
+
+<script>
+  import Calendar from '../../libs/utils/calendar.js'
+  
+  export default {
+    name: 'tn-calendar',
+    props: {
+      // 双向绑定控制组件弹出与收起
+      value: {
+        type: Boolean,
+        default: false
+      },
+      // 模式
+      // date -> 单日期 range -> 日期范围
+      mode: {
+        type: String,
+        default: 'date'
+      },
+      // 是否允许切换年份
+      changeYear: {
+        type: Boolean,
+        default: true
+      },
+      // 是否允许切换月份
+      changeMonth: {
+        type: Boolean,
+        default: true
+      },
+      // 可切换的最大年份
+      maxYear: {
+        type: [Number, String],
+        default: 2100
+      },
+      // 可切换的最小年份
+      minYear: {
+        type: [Number, String],
+        default: 1970
+      },
+      // 最小日期(不在范围被不允许选择)
+      minDate: {
+        type: String,
+        default: '1970-01-01'
+      },
+      // 最大日期,如果为空则默认为今天
+      maxDate: {
+        type: String,
+        default: ''
+      },
+      // 切换月份按钮的颜色
+      monthArrowColor: {
+        type: String,
+        default: '#AAAAAA'
+      },
+      // 切换年份按钮的颜色
+      yearArrowColor: {
+        type: String,
+        default: '#C8C8C8'
+      },
+      // 默认字体颜色
+      color: {
+        type: String,
+        default: '#080808'
+      },
+      // 选中|起始结束日期背景颜色
+      activeBgColor: {
+        type: String,
+        default: '#01BEFF'
+      },
+      // 选中|起始结束日期文字颜色
+      activeColor: {
+        type: String,
+        default: '#FFFFFF'
+      },
+      // 范围日期内的背景颜色
+      rangeBgColor: {
+        type: String,
+        default: '#E6E6E655'
+      },
+      // 范围日期内的文字颜色
+      rangeColor: {
+        type: String,
+        default: '#01BEFF'
+      },
+      // 起始日期显示的文字,mode=range时生效
+      startText: {
+        type: String,
+        default: '开始'
+      },
+      // 结束日期显示的文字,mode=range时生效
+      endText: {
+        type: String,
+        default: '结束'
+      },
+      // 按钮背景颜色
+      btnColor: {
+        type: String,
+        default: '#01BEFF'
+      },
+      // 农历文字的颜色
+      lunarColor: {
+        type: String,
+        default: '#AAAAAA'
+      },
+      // 选中日期是否有选中效果
+      isActiveCurrent: {
+        type: Boolean,
+        default: true
+      },
+      // 切换年月是否触发事件,mode=date时生效
+      isChange: {
+        type: Boolean,
+        default: false
+      },
+      // 是否显示农历
+      showLunar: {
+        type: Boolean,
+        default: true
+      },
+      // 顶部提示文字
+      toolTips: {
+        type: String,
+        default: '请选择日期'
+      },
+      // 显示圆角的大小
+      borderRadius: {
+        type: Number,
+        default: 8
+      },
+      // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+      safeAreaInsetBottom: {
+      	type: Boolean,
+      	default: false
+      },
+      // 是否可以通过点击遮罩进行关闭
+      maskCloseable: {
+      	type: Boolean,
+      	default: true
+      },
+      // zIndex
+      zIndex: {
+        type: Number,
+        default: 0
+      },
+      // 是否显示关闭按钮
+      closeBtn: {
+        type: Boolean,
+        default: false
+      },
+    },
+    computed: {
+      dateChange() {
+        return `${this.mode}-${this.minDate}-${this.maxDate}`
+      },
+      elIndex() {
+        return this.zIndex ? this.zIndex : this.$t.zIndex.popup
+      },
+      colorValue() {
+        return (index, type) => {
+          let color = type === 'bg' ? '' : this.color
+          let day = index + 1
+          let date = `${this.year}-${this.month}-${day}`
+          let timestamp = new Date(date.replace(/\-/g,'/')).getTime()
+          let start = this.startDate.replace(/\-/g,'/')
+          let end = this.endDate.replace(/\-/g,'/')
+          if ((this.mode === 'date' && this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
+            color = type === 'bg' ? this.activeBgColor : this.activeColor
+          } else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
+            color = type === 'bg' ? this.rangeBgColor : this.rangeColor
+          }
+          return color
+        }
+      }
+    },
+    data() {
+      return {
+        // 星期几,1-7
+        weekday: 1,
+        weekdayArr: [],
+        // 星期对应的中文
+        weekDayZh: ['日','一','二','三','四','五','六'],
+        // 当前月有多少天
+        days: 0,
+        daysArr: [],
+        year: 2021,
+        month: 0,
+        day: 0,
+        startYear: 0,
+        startMonth: 0,
+        startDay: 0,
+        endYear: 0,
+        endMonth: 0,
+        endDay: 0,
+        today: '',
+        activeDate: '',
+        startDate: '',
+        endDate: '',
+        min: null,
+        max: null,
+        // 日期标题
+        dateTitle: '',
+        // 标记是否已经选择了开始日期
+        chooseStart: false
+      }
+    },
+    watch: {
+      dateChange() {
+        this.init()
+      }
+    },
+    created() {
+      this.init()
+    },
+    methods: {
+      // 初始化
+      init() {
+        let now = new Date()
+        this.year = now.getFullYear()
+        this.month = now.getMonth() + 1
+        this.day = now.getDate()
+        this.today = `${this.year}-${this.month}-${this.day}`
+        this.activeDate = this.today
+        this.min = this.initDate(this.minDate)
+        this.max = this.initDate(this.maxDate || this.today)
+        this.startDate = ''
+        this.startYear = 0
+        this.startMonth = 0
+        this.startDay = 0
+        this.endDate = ''
+        this.endYear = 0
+        this.endMonth = 0
+        this.endDay = 0
+        this.chooseStart = false
+        this.changeData()
+      },
+      // 切换月份
+      changeMonthHandler(add) {
+        if (add) {
+          let month = this.month + 1
+          let year = month > 12 ? this.year + 1 : this.year
+          if (!this.checkRange(year)) {
+            this.month = month > 12 ? 1 : month
+            this.year = year
+            this.changeData()
+          }
+        } else {
+          let month = this.month - 1
+          let year = month < 1 ? this.year - 1 : this.year
+          if (!this.checkRange(year)) {
+            this.month = month < 1 ? 12 : month
+            this.year = year
+            this.changeData()
+          }
+        }
+      },
+      // 切换年份
+      changeYearHandler(add) {
+        let year = add ? this.year + 1 : this.year - 1
+        if (!this.checkRange(year)) {
+          this.year = year
+          this.changeData()
+        }
+      },
+      // 日期点击事件
+      dateClick(day) {
+        day += 1
+        if (!this.disabledChoose(this.year, this.month, day)) {
+          this.day = day
+          let date = `${this.year}-${this.month}-${day}`
+          if (this.mode === 'date') {
+            this.activeDate = date
+          } else {
+            let startTimeCompare = new Date(date.replace(/\-/g,'/')).getTime() < new Date(this.startDate.replace(/\-/g,'/')).getTime()
+            if (!this.chooseStart || startTimeCompare) {
+              this.startDate = date
+              this.startYear = this.year
+              this.startMonth = this.month
+              this.startDay = this.day
+              this.endYear = 0
+              this.endMonth = 0
+              this.endDay = 0
+              this.endDate = ''
+              this.activeDate = ''
+              this.chooseStart = true
+            } else {
+              this.endDate = date
+              this.endYear = this.year
+              this.endMonth = this.month
+              this.endDay = this.day
+              this.chooseStart = false
+            }
+          }
+          this.daysArr = this.handleDaysArr()
+        }
+      },
+      // 修改日期数据
+      changeData() {
+        this.days = this.getMonthDay(this.year, this.month)
+        this.daysArr = this.handleDaysArr()
+        this.weekday = this.getMonthFirstWeekDay(this.year, this.month)
+        this.weekdayArr = this.generateArray(1, this.weekday)
+        this.dateTitle = `${this.year}年${this.month}月`
+        if (this.isChange && this.mode === 'date') {
+          this.handleBtnClick(true)
+        }
+      },
+      // 处理按钮点击
+      handleBtnClick(show) {
+        if (!show) {
+          this.close()
+        }
+        if (this.mode === 'date') {
+          let arr = this.activeDate.split('-')
+          let year = this.isChange ? this.year : Number(arr[0])
+          let month = this.isChange ? this.month : Number(arr[1])
+          let day = this.isChange ? this.day : Number(arr[2])
+          let days = this.getMonthDay(year, month)
+          let result = `${year}-${this.formatNumber(month)}-${this.formatNumber(day)}`
+          let weekText = this.getWeekText(result)
+          let isToday = false
+          if (`${year}-${month}-${day}` === this.today) {
+            isToday = true
+          }
+          this.$emit('change', {
+            year,
+            month,
+            day,
+            days,
+            week: weekText,
+            isToday,
+            date: result,
+            // 是否为切换年月操作
+            switch: show
+          })
+        } else {
+          if (!this.startDate || !this.endDate) return
+          
+          let startMonth = this.formatNumber(this.startMonth)
+          let startDay = this.formatNumber(this.startDay)
+          let startDate = `${this.startYear}-${startMonth}-${startDay}`
+          let startWeek = this.getWeekText(startDate)
+          
+          let endMonth = this.formatNumber(this.endMonth)
+          let endDay = this.formatNumber(this.endDay)
+          let endDate = `${this.endYear}-${endMonth}-${endDay}`
+          let endWeek = this.getWeekText(endDate)
+          
+          this.$emit('change', {
+            startYear: this.startYear,
+            startMonth: this.startMonth,
+            startDay: this.startDay,
+            startDate,
+            startWeek,
+            endYear: this.endYear,
+            endMonth: this.endMonth,
+            endDay: this.endDay,
+            endDate,
+            endWeek
+          })
+        }
+      },
+      // 判断是否允许选择
+      disabledChoose(year, month, day) {
+        let flag = true
+        let date = `${year}/${month}/${day}`
+        let min = `${this.min.year}/${this.min.month}/${this.min.day}`
+        let max = `${this.max.year}/${this.max.month}/${this.max.day}`
+        let timestamp = new Date(date).getTime()
+        if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
+          flag = false
+        }
+        return flag
+      },
+      // 检查是否在日期范围内
+      checkRange(year) {
+        let overstep = false
+        if (year < this.minYear || year > this.maxYear) {
+          uni.showToast({
+            title: '所选日期超出范围',
+            icon: 'none'
+          })
+          overstep = true
+        }
+        return overstep
+      },
+      // 处理日期
+      initDate(date) {
+        let fdate = date.split('-')
+        return {
+          year: Number(fdate[0] || 1970),
+          month: Number(fdate[1] || 1),
+          day: Number(fdate[2] || 1)
+        }
+      },
+      // 处理日期数组
+      handleDaysArr() {
+        let days = this.generateArray(1, this.days)
+        let daysArr = days.map((item) => {
+          let bottomInfo = this.showLunar ? Calendar.solar2lunar(this.year, this.month, item).IDayCn : ''
+          let color = this.showLunar ? this.lunarColor : this.activeColor
+          if (
+            (this.mode === 'date' && this.day == item) || 
+            (this.mode === 'range' && (this.startDay == item || this.endDay == item))
+          ) {
+            color = this.activeColor
+          }
+          if (this.mode === 'range') {
+            if (this.startDay == item && this.startDay != this.endDay) {
+              bottomInfo = this.startText
+            }
+            if (this.endDay == item) {
+              bottomInfo = this.endText
+            }
+          }
+          
+          return {
+            day: item,
+            color: color,
+            bottomInfo: bottomInfo
+          }
+        })
+        return daysArr
+      },
+      // 获取对应月有多少天
+      getMonthDay(year, month) {
+        return new Date(year, month, 0).getDate()
+      },
+      // 获取对应月的第一天时星期几
+      getMonthFirstWeekDay(year, month) {
+        return new Date(`${year}/${month}/01 00:00:00`).getDay()
+      },
+      // 获取对应星期的文本
+      getWeekText(date) {
+        date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`)
+        let week = date.getDay()
+        return '星期' + this.weekDayZh[week]
+      },
+      // 生成日期天数数组
+      generateArray(start, end) {
+        return Array.from(new Array(end + 1).keys()).slice(start)
+      },
+      // 格式化数字
+      formatNumber(num) {
+        return num < 10 ? '0' + num : num + ''
+      },
+      // 关闭窗口
+      close() {
+        this.$emit('input', false)
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-calendar {
+    color: $tn-font-color;
+    
+    &__header {
+      width: 100%;
+      box-sizing: border-box;
+      font-size: 30rpx;
+      background-color: #FFFFFF;
+      color: $tn-main-color;
+      
+      &__text {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        margin-top: 30rpx;
+        padding: 0 60rpx;
+      }
+    }
+    
+    &__action {
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      align-items: center;
+      padding: 40rpx 0 40rpx 0;
+      
+      &__icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin: 0 16rpx;
+        width: 32rpx;
+        height: 32rpx;
+        font-size: 20rpx;
+        // line-height: 32rpx;
+        border-radius: 50%;
+        color: #FFFFFF;
+      }
+      
+      &__text {
+        padding: 0 16rpx;
+        color: $tn-font-color;
+        font-size: 32rpx;
+        font-weight: bold;
+      }
+    }
+    
+    &__week-day-zh {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+      padding: 12rpx 0;
+      overflow: hidden;
+      box-shadow: 16rpx 6rpx 8rpx 0 #E6E6E6;
+      margin-bottom: 2rpx;
+      
+      &__text {
+        flex: 1;
+        text-align: center;
+      }
+    }
+    
+    &__content {
+      display: flex;
+      flex-direction: row;
+      flex-wrap: wrap;
+      width: 100%;
+      padding: 12rpx 0;
+      box-sizing: border-box;
+      background-color: #F7F7F7;
+      position: relative;
+      
+      &__item {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        width: 14.2857%;
+        padding: 12rpx 0;
+        margin: 6rpx 0;
+        overflow: hidden;
+        position: relative;
+        z-index: 2;
+        // box-shadow: inset 0rpx 0rpx 22rpx 4rpx rgba(255,255,255, 0.52);
+        
+        &__text {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          justify-content: center;
+          height: 80rpx;
+          font-size: 32rpx;
+          position: relative;
+        }
+        
+        &__tips {
+          position: absolute;
+          width: 100%;
+          line-height: 24rpx;
+          left: 0;
+          bottom: 8rpx;
+          text-align: center;
+          z-index: 2;
+          transform-origin: center center;
+          transform: scale(0.8);
+        }
+      }
+      
+      &--start-date {
+        border-top-left-radius: 8rpx;
+        border-bottom-left-radius: 8rpx;
+      }
+      
+      &--end-date {
+        border-top-right-radius: 8rpx;
+        border-bottom-right-radius: 8rpx;
+      }
+      
+      &__month {
+        &--bg {
+          position: absolute;
+          font-size: 200rpx;
+          line-height: 200rpx;
+          left: 50%;
+          top: 50%;
+          transform: translate(-50%, -50%);
+          color: $tn-font-holder-color;
+          z-index: 1;
+        }
+      }
+    }
+    
+    &__bottom {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      background-color: #F7F7F7;
+      padding: 0 40rpx 30rpx;
+      box-sizing: border-box;
+      font-size: 24rpx;
+      color: $tn-font-sub-color;
+      
+      &__choose {
+        height: 50rpx;
+      }
+      
+      &__btn {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 60rpx;
+        border-radius: 40rpx;
+        color: #FFFFFF;
+        font-size: 28rpx;
+      }
+    }
+  }
+</style>

+ 645 - 0
addons/exam/tuniao-bak/tn-image-upload/tn-image-upload.vue

@@ -0,0 +1,645 @@
+<template>
+  <view v-if="!disabled" class="tn-image-upload-class tn-image-upload">
+    <block v-if="showUploadList">
+      <view
+        v-for="(item, index) in lists"
+        :key="index"
+        class="tn-image-upload__item tn-image-upload__item-preview"
+        :style="{
+          width: $t.string.getLengthUnitValue(width),
+          height: $t.string.getLengthUnitValue(height)
+        }"
+      >
+        <!-- 删除按钮 -->
+        <view
+          v-if="deleteable"
+          class="tn-image-upload__item-preview__delete"
+          @tap.stop="deleteItem(index)"
+          :style="{
+            borderTopColor: deleteBackgroundColor
+          }"
+        >
+          <view
+            class="tn-image-upload__item-preview__delete--icon"
+            :class="[`tn-icon-${deleteIcon}`]"
+            :style="{
+              color: deleteColor
+            }"
+          ></view>
+        </view>
+        <!-- 进度条 -->
+        <tn-line-progress
+          v-if="showProgress && item.progress > 0 && !item.error"
+          class="tn-image-upload__item-preview__progress"
+          :percent="item.progress"
+          :showPercent="false"
+          :round="false"
+          :height="8"
+        ></tn-line-progress>
+        <!-- 重试按钮 -->
+        <view v-if="item.error" class="tn-image-upload__item-preview__error-btn" @tap.stop="retry(index)">点击重试</view>
+        <!-- 图片信息 -->
+        <image
+          class="tn-image-upload__item-preview__image"
+          :src="item.url || item.path"
+          :mode="imageMode"
+          @tap.stop="doPreviewImage(item.url || item.path, index)"
+        ></image>
+      </view>
+    </block>
+    <!-- <view v-if="$slots.file || $slots.$file" style="width: 100%;">
+      
+    </view> -->
+    <!-- 自定义图片展示列表 -->
+    <slot name="file" :file="lists"></slot>
+    
+    <!-- 添加按钮 -->
+    <view v-if="maxCount > lists.length" class="tn-image-upload__add" :class="{'tn-image-upload__add--custom': customBtn}" @tap="selectFile">
+      <!-- 添加按钮 -->
+      <view
+        v-if="!customBtn"
+        class="tn-image-upload__item tn-image-upload__item-add"
+        hover-class="tn-hover-class"
+        hover-stay-time="150"
+        :style="{
+          width: $t.string.getLengthUnitValue(width),
+          height: $t.string.getLengthUnitValue(height)
+        }"
+      >
+        <view class="tn-image-upload__item-add--icon tn-icon-add"></view>
+        <view class="tn-image-upload__item-add__tips">{{ uploadText }}</view>
+      </view>
+      <!-- 自定义添加按钮 -->
+      <view>
+        <slot name="addBtn"></slot>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-image-upload',
+    props: {
+      // 已上传的文件列表
+      fileList: {
+        type: Array,
+        default() {
+          return []
+        }
+      },
+      // 上传图片地址
+      action: {
+        type: String,
+        default: ''
+      },
+      // 上传文件的字段名称
+      name: {
+        type: String,
+        default: 'file'
+      },
+      // 头部信息
+      header: {
+        type: Object,
+        default() {
+          return {}
+        }
+      },
+      // 携带的参数
+      formData: {
+        type: Object,
+        default() {
+          return {}
+        }
+      },
+      // 是否禁用
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      // 是否自动上传
+      autoUpload: {
+        type: Boolean,
+        default: true
+      },
+      // 最大上传数量
+      maxCount: {
+        type: Number,
+        default: 9
+      },
+      // 是否显示组件自带的图片预览
+      showUploadList: {
+        type: Boolean,
+        default: true
+      },
+      // 预览上传图片的裁剪模式
+      imageMode: {
+        type: String,
+        default: 'aspectFill'
+      },
+      // 点击图片是否全屏预览
+      previewFullImage: {
+        type: Boolean,
+        default: true
+      },
+      // 是否显示进度条
+      showProgress: {
+        type: Boolean,
+        default: true
+      },
+      // 是否显示删除按钮
+      deleteable: {
+        type: Boolean,
+        default: true
+      },
+      // 删除按钮图标
+      deleteIcon: {
+        type: String,
+        default: 'close'
+      },
+      // 删除按钮的背景颜色
+      deleteBackgroundColor: {
+        type: String,
+        default: ''
+      },
+      // 删除按钮的颜色
+      deleteColor: {
+        type: String,
+        default: ''
+      },
+      // 上传区域提示文字
+      uploadText: {
+        type: String,
+        default: '选择图片'
+      },
+      // 显示toast提示
+      showTips: {
+        type: Boolean,
+        default: true
+      },
+      // 自定义选择图标按钮
+      customBtn: {
+        type: Boolean,
+        default: false
+      },
+      // 预览图片和选择图片区域的宽度
+      width: {
+        type: Number,
+        default: 200
+      },
+      // 预览图片和选择图片区域的高度
+      height: {
+        type: Number,
+        default: 200
+      },
+      // 选择图片的尺寸
+      // 参考上传文档 https://uniapp.dcloud.io/api/media/image
+      sizeType: {
+        type: Array,
+        default() {
+          return ['original', 'compressed']
+        }
+      },
+      // 图片来源
+      sourceType: {
+        type: Array,
+        default() {
+          return ['album', 'camera']
+        }
+      },
+      // 是否可以多选
+      multiple: {
+        type: Boolean,
+        default: true
+      },
+      // 文件大小(byte)
+      maxSize: {
+        type: Number,
+        default: 10 * 1024 * 1024
+      },
+      // 允许上传的类型
+      limitType: {
+        type: Array,
+        default() {
+          return ['png','jpg','jpeg','webp','gif','image']
+        }
+      },
+      // 是否自定转换为json
+      toJson: {
+        type: Boolean,
+        default: true
+      },
+      // 上传前钩子函数,每个文件上传前都会执行
+      beforeUpload: {
+        type: Function,
+        default: null
+      },
+      // 删除文件前钩子函数
+      beforeRemove: {
+        type: Function,
+        default: null
+      },
+      index: {
+        type: [Number, String],
+        default: ''
+      }
+    },
+    computed: {
+      
+    },
+    data() {
+      return {
+        lists: [],
+        uploading: false
+      }
+    },
+    watch: {
+      fileList: {
+        handler(val) {
+          val.map(value => {
+            // 首先检查内部是否已经添加过这张图片,因为外部绑定了一个对象给fileList的话(对象引用),进行修改外部fileList时,
+            // 会触发watch,导致重新把原来的图片再次添加到this.lists
+            // 数组的some方法意思是,只要数组元素有任意一个元素条件符合,就返回true,而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
+            let tmp = this.lists.some(listVal => {
+              return listVal.url === value.url
+            })
+            // 如果内部没有这张图片,则添加到内部
+            !tmp && this.lists.push({ url: value.url, error: false, progress: 100 })
+          })
+        },
+        immediate: true
+      },
+      lists(val) {
+        this.$emit('on-list-change', val, this.index)
+      }
+    },
+    methods: {
+      // 清除列表
+      clear() {
+        this.lists = []
+      },
+      // 重新上传队列中上传失败所有文件
+      reUpload() {
+        this.uploadFile()
+      },
+      // 选择图片
+      selectFile() {
+        if (this.disabled) return
+        const {
+          name = '',
+          maxCount,
+          multiple,
+          maxSize,
+          sizeType,
+          lists,
+          camera,
+          compressed,
+          sourceType
+        } = this
+        let chooseFile = null
+        const newMaxCount = maxCount - lists.length
+        // 只选择图片的时候使用 chooseImage 来实现
+        chooseFile = new Promise((resolve, reject) => {
+          uni.chooseImage({
+            count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
+            sourceType,
+            sizeType,
+            success: resolve,
+            fail: reject
+          })
+        })
+        chooseFile.then(res => {
+          let file = null
+          let listOldLength = lists.length
+          res.tempFiles.map((val, index) => {
+            if (!this.checkFileExt(val)) return
+            
+            // 是否超出最大限制数量
+            if (!multiple && index >= 1) return
+            if (val.size > maxSize) {
+              this.$emit('on-oversize', val, lists, this.index)
+              this.showToast('超出可允许文件大小')
+            } else {
+              if (maxCount <= lists.length) {
+                this.$emit('on-exceed', val, lists, this.index)
+                this.showToast('超出最大允许的文件数')
+                return
+              }
+              lists.push({
+                url: val.path,
+                progress: 0,
+                error: false,
+                file: val
+              })
+            }
+          })
+          this.$emit('on-choose-complete', this.lists, this.index)
+          if (this.autoUpload) this.uploadFile(listOldLength)
+        }).catch(err => {
+          this.$emit('on-choose-fail', err)
+        })
+      },
+      // 提示用户信息
+      showToast(message, force = false) {
+        if (this.showTips || force) {
+          this.$t.message.toast(message)
+        }
+      },
+      // 手动上传,通过ref进行调用
+      upload() {
+        this.uploadFile()
+      },
+      // 对失败图片进行再次上传
+      retry(index) {
+        this.lists[index].progress = 0
+        this.lists[index].error = false
+        this.lists[index].response = null
+        this.$t.message.loading('重新上传')
+        this.uploadFile(index)
+      },
+      // 上传文件
+      async uploadFile(index = 0) {
+        if (this.disabled) return
+        if (this.uploading) return
+        // 全部上传完成
+        if (index >= this.lists.length) {
+          this.$emit('on-uploaded', this.lists, this.index)
+          return
+        }
+        // 检查是否已经全部上传或者上传中
+        if (this.lists[index].progress === 100) {
+          this.lists[index].uploadTask = null
+          if (this.autoUpload) this.uploadFile(index + 1)
+          return
+        }
+        // 执行before-upload钩子
+        if (this.beforeUpload && typeof(this.beforeUpload) === 'function') {
+          // 在微信,支付宝等环境(H5正常),会导致父组件定义的函数体中的this变成子组件的this
+          // 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
+          // 因为upload组件可能会被嵌套在其他组件内,比如tn-form,这时this.$parent其实为tn-form的this,
+          // 非页面的this,所以这里需要往上历遍,一直寻找到最顶端的$parent,这里用了this.$u.$parent.call(this)
+          let beforeResponse = this.beforeUpload.bind(this.$t.$parent.call(this))(index, this.lists)
+          // 判断是否返回了Promise
+          if (!!beforeResponse && typeof beforeResponse.then === 'function') {
+            await beforeResponse.then(res => {
+              // promise返回成功,不进行操作继续
+            }).catch(err => {
+              // 进入catch回调的话,继续下一张
+              return this.uploadFile(index + 1)
+            })
+          } else if (beforeResponse === false) {
+            // 如果返回flase,继续下一张图片上传
+            return this.uploadFile(index + 1)
+          } else {
+            // 为true的情况,不进行操作
+          }
+        }
+        // 检查上传地址
+        if (!this.action) {
+          this.showToast('请配置上传地址', true)
+          return
+        }
+        this.lists[index].error = false
+        this.uploading = true
+        // 创建上传对象
+        const task = uni.uploadFile({
+          url: this.action,
+          filePath: this.lists[index].url,
+          name: this.name,
+          formData: this.formData,
+          header: this.header,
+          success: res => {
+            // 判断啊是否为json字符串,将其转换为json格式
+            let data = this.toJson && this.$t.test.jsonString(res.data) ? JSON.parse(res.data) : res.data
+            if (![200, 201, 204].includes(res.statusCode)) {
+              this.uploadError(index, data)
+            } else {
+              this.lists[index].response = data
+              this.lists[index].progress = 100
+              this.lists[index].error = false
+              this.$emit('on-success', data, index, this.lists, this.index)
+            }
+          },
+          fail: err => {
+            this.uploadError(index, err)
+          },
+          complete: res => {
+            this.$t.message.closeLoading()
+            this.uploading = false
+            this.uploadFile(index + 1)
+            this.$emit('on-change', res, index, this.lists, this.index)
+          }
+        })
+        this.lists[index].uploadTask = task
+        task.onProgressUpdate(res => {
+          if (res.progress > 0) {
+            this.lists[index].progress = res.progress
+            this.$emit('on-progress', res, index, this.lists, this.index)
+          }
+        })
+      },
+      // 上传失败
+      uploadError(index, err) {
+        this.lists[index].progress = 0
+        this.lists[index].error = true
+        this.lists[index].response = null
+        this.showToast('上传失败,请重试')
+        this.$emit('on-error', err, index, this.lists, this.index)
+      },
+      // 删除一个图片
+      deleteItem(index) {
+        if (!this.deleteable) return
+        this.$t.message.modal(
+          '提示',
+          '您确定要删除吗?',
+          async () => {
+            // 先检查是否有定义before-remove移除前钩子
+            // 执行before-remove钩子
+            if (this.beforeRemove && typeof(this.beforeRemove) === 'function') {
+              let beforeResponse = this.beforeRemove.bind(this.$t.$parent.call(this))(index, this.lists)
+              // 判断是否返回promise 
+              if (!!beforeResponse && typeof beforeResponse.then === 'function') {
+                await beforeResponse.then(res => {
+                  // promise返回成功不进行操作
+                  this.handlerDeleteItem(index)
+                }).catch(err => {
+                  this.showToast('删除操作被中断')
+                })
+              } else if (beforeResponse === false) {
+                this.showToast('删除操作被中断')
+              } else {
+                this.handlerDeleteItem(index)
+              }
+            } else {
+              this.handlerDeleteItem(index)
+            }
+          }, true)
+      },
+      // 移除文件操作
+      handlerDeleteItem(index) {
+        // 如果文件正在上传中,终止上传任务
+        if (this.lists[index].progress < 100 && this.lists[index].progress > 0) {
+          typeof this.lists[index].uploadTask !== 'undefined' && this.lists[index].uploadTask.abort()
+        }
+        this.lists.splice(index, 1)
+        this.$forceUpdate()
+        this.$emit('on-remove', index, this.lists, this.index)
+        this.showToast('删除成功')
+      },
+      // 移除文件,通过ref手动形式进行调用
+      remove(index) {
+        if (!this.deleteable) return
+        // 判断索引合法
+        if (index >= 0 && index < this.lists.length) {
+          this.lists.splice(index, 1)
+        }
+      },
+      // 预览图片
+      doPreviewImage(url, index) {
+        if (!this.previewFullImage) return
+        const images = this.lists.map(item => item.url || item.path)
+        uni.previewImage({
+          urls: images,
+          current: url,
+          success: () => {
+            this.$emit('on-preview', url, this.lists, this.index)
+          },
+          fail: () => {
+            this.showToast('预览图片失败')
+          }
+        })
+      },
+      // 检查文件后缀是否合法
+      checkFileExt(file) {
+        // 是否为合法后缀
+        let noArrowExt = false
+        // 后缀名
+        let fileExt = ''
+        const reg = /.+\./
+        
+        // #ifdef H5
+        fileExt = file.name.replace(reg, '').toLowerCase()
+        // #endif
+        // #ifndef H5
+        fileExt = file.path.replace(reg, '').toLowerCase()
+        // #endif
+        noArrowExt = this.limitType.some(ext => {
+          return ext.toLowerCase() === fileExt
+        })
+        if (!noArrowExt) this.showToast(`不支持${fileExt}格式的文件`)
+        return noArrowExt
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-image-upload {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    align-items: center;
+    
+    &__item {
+      /* #ifndef APP-NVUE */
+      display: flex;
+      /* #endif */
+      align-items: center;
+      justify-content: center;
+      width: 200rpx;
+      height: 200rpx;
+      overflow: hidden;
+      margin: 12rpx;
+      margin-left: 0;
+      background-color: $tn-font-holder-color;
+      position: relative;
+      border-radius: 10rpx;
+      
+      &-preview {
+        border: 1rpx solid $tn-border-solid-color;
+        
+        &__delete {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          position: absolute;
+          top: 0;
+          right: 0;
+          z-index: 10;
+          border-top: 60rpx solid;
+          border-left: 60rpx solid transparent;
+          border-top-color: $tn-color-red;
+          width: 0rpx;
+          height: 0rpx;
+          
+          &--icon {
+            position: absolute;
+            top: -50rpx;
+            right: 6rpx;
+            color: #FFFFFF;
+            font-size: 24rpx;
+            line-height: 1;
+          }
+        }
+        
+        &__progress {
+          position: absolute;
+          width: auto;
+          bottom: 0rpx;
+          left: 0rpx;
+          right: 0rpx;
+          z-index: 9;
+          /* #ifdef MP-WEIXIN */
+          display: inline-flex;
+          /* #endif */
+        }
+        
+        &__error-btn {
+          position: absolute;
+          bottom: 0;
+          left: 0;
+          right: 0;
+          background-color: $tn-color-red;
+          color: #FFFFFF;
+          font-size: 20rpx;
+          padding: 8rpx 0;
+          text-align: center;
+          z-index: 9;
+          line-height: 1;
+        }
+        
+        &__image {
+          display: block;
+          width: 100%;
+          height: 100%;
+          border-radius: 10rpx;
+        }
+      }
+      
+      &-add {
+        flex-direction: column;
+        color: $tn-content-color;
+        font-size: 26rpx;
+        
+        &--icon {
+          font-size: 40rpx;
+        }
+        
+        &__tips {
+          margin-top: 20rpx;
+          line-height: 40rpx;
+        }
+      }
+    }
+    
+    &__add {
+      width: auto;
+      display: inline-block;
+      
+      &--custom {
+        width: 100%;
+      }
+    }
+  }
+</style>

+ 401 - 0
addons/exam/tuniao-bak/tn-number-box/tn-number-box.vue

@@ -0,0 +1,401 @@
+<template>
+  <view class="tn-number-box-class tn-number-box">
+    <!-- 减 -->
+    <view
+      class="tn-number-box__btn__minus"
+      :class="[
+        backgroundColorClass,
+        fontColorClass,
+        {'tn-number-box__btn--disabled': disabled || inputValue <= min}
+      ]"
+      :style="{
+        backgroundColor: backgroundColorStyle,
+        height: $t.string.getLengthUnitValue(inputHeight),
+        color: fontColorStyle,
+        fontSize: fontSizeStyle
+      }"
+      @touchstart.stop.prevent="touchStart('minus')"
+      @touchend.stop.prevent="clearTimer"
+    >
+      <view class="tn-icon-reduce"></view>
+    </view>
+    
+    <!-- 输入框 -->
+    <input
+      v-model="inputValue"
+      :disabled="disabledInput || disabled"
+      :cursor-spacing="getCursorSpacing"
+      class="tn-number-box__input"
+      :class="[
+        fontColorClass,
+        {'tn-number-box__input--disabled': disabledInput || disabled}
+      ]"
+      :style="{
+        width: $t.string.getLengthUnitValue(inputWidth),
+        height: $t.string.getLengthUnitValue(inputHeight),
+        color: fontColorStyle,
+        fontSize: fontSizeStyle,
+        backgroundColor: backgroundColorStyle
+      }"
+      @blur="blurInput"
+      @focus="focusInput"
+    />
+    
+    <!-- 加 -->
+    <view
+      class="tn-number-box__btn__plus"
+      :class="[
+        backgroundColorClass,
+        fontColorClass,
+        {'tn-number-box__btn--disabled': disabled || inputValue >= max}
+      ]"
+      :style="{
+        backgroundColor: backgroundColorStyle,
+        height: $t.string.getLengthUnitValue(inputHeight),
+        color: fontColorStyle,
+        fontSize: fontSizeStyle
+      }"
+      @touchstart.stop.prevent="touchStart('plus')"
+      @touchend.stop.prevent="clearTimer"
+    >
+      <view class="tn-icon-add"></view>
+    </view>
+  </view>
+</template>
+
+<script>
+  import componentsColor from '../../libs/mixin/components_color.js'
+  
+  export default {
+    mixins: [componentsColor],
+    name: 'tn-number-box',
+    props: {
+      value: {
+        type: Number,
+        default: 1
+      },
+      // 索引
+      index: {
+        type: [Number, String],
+        default: ''
+      },
+      // 最小值
+      min: {
+        type: Number,
+        default: 0
+      },
+      // 最大值
+      max: {
+        type: Number,
+        default: 99999
+      },
+      // 步进值
+      step: {
+        type: Number,
+        default: 1
+      },
+      // 禁用
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      // 是否禁用输入
+      disabledInput: {
+        type: Boolean,
+        default: false
+      },
+      // 输入框的宽度
+      inputWidth: {
+        type: Number,
+        default: 88
+      },
+      // 输入框的高度
+      inputHeight: {
+        type: Number,
+        default: 50
+      },
+      // 输入框和键盘之间的距离
+      cursorSpacing: {
+        type: Number,
+        default: 100
+      },
+      // 是否开启长按进行连续递增减
+      longPress: {
+        type: Boolean,
+        default: true
+      },
+      // 长按触发间隔
+      longPressTime: {
+        type: Number,
+        default: 250
+      },
+      // 是否只能输入正整数
+      positiveInteger: {
+        type: Boolean,
+        default: true
+      }
+    },
+    computed: {
+      getCursorSpacing() {
+        return Number(uni.upx2px(this.cursorSpacing))
+      }
+    },
+    data() {
+      return {
+        // 输入框的值
+        inputValue: 1,
+        // 长按定时器
+        longPressTimer: null,
+        // 标记值的改变是来自外部还是内部
+        changeFromInner: false,
+        // 内部定时器
+        innerChangeTimer: null
+      }
+    },
+    watch: {
+      value(val) {
+        // 只有value的改变是来自外部的时候,才去同步inputValue的值,否则会造成循环错误
+        if (!this.changeFromInner) {
+          this.updateInputValue()
+          // 因为inputValue变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
+          // 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
+          // 将changeFromInner设置为false
+          this.$nextTick(() => {
+          	this.changeFromInner = false
+          })
+        }
+      },
+      inputValue(newVal, oldVal) {
+        // 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
+        if (newVal === '') return
+        let value = 0
+        // 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
+        let isNumber = this.$t.test.number(newVal)
+        if (isNumber && newVal >= this.min && newVal <= this.max) value = newVal
+        else value = oldVal
+        
+        // 判断是否只能输入大于等于0的整数
+        if (this.positiveInteger) {
+          // 小于0或者带有小数点
+          if (newVal < 0 || String(newVal).indexOf('.') !== -1) {
+            value = Math.floor(newVal)
+            // 双向绑定input的值,必须要使用$nextTick修改显示的值
+            this.$nextTick(() => {
+            	this.inputValue = value
+            })
+          }
+        }
+        this.handleChange(value, 'change')
+      },
+      min() {
+        this.updateInputValue()
+      },
+      max() {
+        this.updateInputValue()
+      }
+    },
+    created() {
+      this.updateInputValue()
+    },
+    methods: {
+      // 开始点击按钮
+      touchStart(func) {
+        // 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
+        this[func]()
+        // 如果没有开启长按功能,直接返回
+        if (!this.longPress) return
+        // 清空长按定时器,防止重复注册
+        if (this.longPressTimer) {
+          clearInterval(this.longPressTimer)
+          this.longPressTimer = null
+        }
+        this.longPressTimer = setInterval(() => {
+          // 执行加减操作
+          this[func]()
+        }, this.longPressTime)
+      },
+      // 清除定时器
+      clearTimer() {
+        this.$nextTick(() => {
+          if (this.longPressTimer) {
+            clearInterval(this.longPressTimer)
+            this.longPressTimer = null
+          }
+        })
+      },
+      // 减
+      minus() {
+        this.computeValue('minus')
+      },
+      // 加
+      plus() {
+        this.computeValue('plus')
+      },
+      // 处理小数相加减出现溢出问题
+      calcPlus(num1, num2) {
+        let baseNum = 0, baseNum1 = 0, baseNum2 = 0
+        try {
+          baseNum1 = num1.toString().split('.')[1].length
+        } catch(e) {
+          baseNum1 = 0
+        }
+        try {
+          baseNum2 = num2.toString().split('.')[1].length
+        } catch(e) {
+          baseNum2 = 0
+        }
+        
+        baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
+        // 精度
+        let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
+        return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision)
+      },
+      calcMinus(num1, num2) {
+        let baseNum = 0, baseNum1 = 0, baseNum2 = 0
+        try {
+          baseNum1 = num1.toString().split('.')[1].length
+        } catch(e) {
+          baseNum1 = 0
+        }
+        try {
+          baseNum2 = num2.toString().split('.')[1].length
+        } catch(e) {
+          baseNum2 = 0
+        }
+        
+        baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
+        // 精度
+        let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
+        return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision)
+      },
+      // 处理操作后的值
+      computeValue(type) {
+        uni.hideKeyboard()
+        if (this.disabled) return
+        let value = 0
+        
+        if (type === 'minus') {
+          // 减
+          value = this.calcMinus(this.inputValue, this.step)
+        } else if (type === 'plus') {
+          // 加
+          value = this.calcPlus(this.inputValue, this.step)
+        }
+        // 判断是否比最小值小和操作最大值
+        if (value < this.min || value > this.max) return
+        
+        this.inputValue = value
+        this.handleChange(value, type)
+      },
+      // 处理用户手动输入
+      blurInput(event) {
+        let val = 0,
+            value = event.detail.value
+        // 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
+        // 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
+        if (!/(^\d+$)/.test(value) || value[0] == 0) {
+          val = this.min
+        } else {
+          val = +value
+        }
+        
+        if (val > this.max) {
+          val = this.max
+        } else if (val < this.min) {
+          val = this.min
+        }
+        this.$nextTick(() => {
+          this.inputValue = val
+        })
+        this.handleChange(val, 'blur')
+      },
+      // 获取焦点
+      focusInput() {
+        this.$emit('focus')
+      },
+      // 初始化inputValue
+      updateInputValue() {
+        let value = this.value
+        if (value <= this.min) {
+          value = this.min
+        } else if (value >= this.max) {
+          value = this.max
+        }
+        
+        this.inputValue = Number(value)
+      },
+      // 处理值改变状态
+      handleChange(value, type) {
+        if (this.disabled) return
+        // 清除定时器,防止混乱
+        if (this.innerChangeTimer) {
+          clearTimeout(this.innerChangeTimer)
+          this.innerChangeTimer = null
+        }
+        
+        // 内部修改值
+        this.changeFromInner = true
+        // 一定时间内,清除changeFromInner标记,否则内部值改变后
+        // 外部通过程序修改value值,将会无效
+        this.innerChangeTimer = setTimeout(() => {
+          this.changeFromInner = false
+        }, 150)
+        this.$emit('input', Number(value))
+        this.$emit(type, {
+          value: Number(value),
+          index: this.index
+        })
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-number-box {
+    display: inline-flex;
+    align-items: center;
+    
+    &__btn {
+      &__plus,&__minus {
+        width: 60rpx;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+        background-color: $tn-font-holder-color;
+      }
+      
+      &__plus {
+        border-radius: 0 8rpx 8rpx 0;
+      }
+      
+      &__minus {
+        border-radius: 8rpx 0 0 8rpx;
+      }
+      
+      &--disabled {
+        color: $tn-font-sub-color !important;
+        background: $tn-font-holder-color !important;
+      }
+    }
+    
+    &__input {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: center;
+      position: relative;
+      text-align: center;
+      box-sizing: border-box;
+      padding: 0 4rpx;
+      margin: 0 6rpx;
+      background-color: $tn-font-holder-color;
+      
+      &--disabled {
+        color: $tn-font-sub-color !important;
+        background: $tn-font-holder-color !important;
+      }
+    }
+  }
+</style>

+ 182 - 0
addons/exam/tuniao-bak/tn-number-keyboard/tn-number-keyboard.vue

@@ -0,0 +1,182 @@
+<template>
+  <view class="tn-number-keyboard-class tn-number-keyboard" @touchmove.stop.prevent="() => {}">
+    <view class="tn-number-keyboard__grids">
+      <view
+        v-for="(item, index) in dataList"
+        :key="index"
+        class="tn-number-keyboard__grids__item"
+        :class="{
+          'tn-bg-gray--light': showGaryBg(index),
+          'tn-border-solid-top': index <= 2,
+          'tn-border-solid-bottom': index < 9,
+          'tn-border-solid-right': (index + 1) % 3 != 0
+        }"
+        :hover-class="hoverClass(index)"
+        :hover-stay-time="150"
+        @tap="keyboardClick(item)"
+      >
+        <view class="tn-number-keyboard__grids__btn">{{ item }}</view>
+      </view>
+      
+      <view
+        class="tn-number-keyboard__grids__item tn-bg-gray--light"
+        hover-class="tn-hover"
+        :hover-stay-time="150"
+        @touchstart.stop="backspaceClick"
+        @touchend="clearTimer"
+      >
+        <view class="tn-number-keyboard__grids__btn tn-number-keyboard__back">
+          <view class="tn-icon-left-arrow tn-number-keyboard__back__icon"></view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-number-keyboard',
+    props: {
+      // 键盘类型
+      // number -> 数字键盘 card -> 身份证键盘
+      mode: {
+        type: String,
+        default: 'number'
+      },
+      // 是否显示键盘的'.'符号
+      dotEnabled: {
+        type: Boolean,
+        default: true
+      },
+      // 是否为乱序键盘
+      randomEnabled: {
+        type: Boolean,
+        default: false
+      }
+    },
+    computed: {
+      // 键盘显示的内容
+      dataList() {
+        let tmp = []
+        if (!this.dotEnabled && this.mode === 'number') {
+          if (!this.randomEnabled) {
+            return [1, 2, 3, 4, 5, 6, 7, 8, 9, '', 0]
+          } else {
+            let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
+            data.splice(-1, 0, '')
+            return data
+          }
+        } else if (this.dotEnabled && this.mode === 'number') {
+          if (!this.randomEnabled) {
+            return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]
+          } else {
+            let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
+            data.splice(-1, 0, this.dot)
+            return data
+          }
+        } else if (this.mode === 'card') {
+          if (!this.randomEnabled) {
+            return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]
+          } else {
+            let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
+            data.splice(-1, 0, this.cardX)
+            return data
+          }
+        }
+      },
+      // 按键的样式
+      keyStyle() {
+        return index => {
+          let style = {}
+          if (this.mode === 'number' && !this.dotEnabled && index === 9) style.flex = '0 0 66.6666666666%'
+          return style
+        }
+      },
+      // 是否让按键显示灰色,只在数字键盘和非乱序且在点击时
+      showGaryBg() {
+        return index => {
+          if (!this.randomEnabled && index === 9 && (this.mode !== 'number' || (this.mode === 'number' && this.dotEnabled))) return true
+          else return false
+        }
+      },
+      // 手指停留的class
+      hoverClass() {
+        return index => {
+          if (this.mode === 'number' && !this.dotEnabled && index === 9) return ''
+          if (!this.randomEnabled && index === 9 && (this.mode === 'number' && this.dotEnabled || this.mode === 'card')) return 'tn-hover'
+          else return 'tn-number-keyboard--hover'
+        }
+      }
+    },
+    data() {
+      return {
+        // 退格键内容
+        backspace: 'backspace',
+        // 点内容
+        dot: '.',
+        // 长按多次删除事件监听
+        longPressDeleteTimer: null,
+        // 身份证的X符号
+        cardX: 'X'
+      }
+    },
+    methods: {
+      // 点击退格键
+      backspaceClick() {
+        this.$emit('backspace')
+        this.clearTimer()
+        this.longPressDeleteTimer = setInterval(() => {
+          this.$emit('backspace')
+        }, 250)
+      },
+      // 获取键盘显示的内容
+      keyboardClick(value) {
+        if (this.mode === 'number' && !this.dotEnabled && value === '') return
+        // 允许键盘显示点模式和触发非点按键时,将内容转换为数字类型
+        if (this.dotEnabled && value != this.dot && value != this.cardX) value = Number(value)
+        this.$emit('change', value)
+      },
+      // 清除定时器
+      clearTimer() {
+        if (this.longPressDeleteTimer) {
+          clearInterval(this.longPressDeleteTimer)
+          this.longPressDeleteTimer = null
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .tn-number-keyboard {
+    position: relative;
+    
+    &__grids {
+      display: flex;
+      flex-direction: row;
+      flex-wrap: wrap;
+      justify-content: flex-end;
+      
+      &__item {
+        display: flex;
+        flex-direction: row;
+        flex: 0 0 33.3333333333%;
+        align-items: center;
+        justify-content: center;
+        height: 110rpx;
+        text-align: center;
+        font-size: 50rpx;
+        color: $tn-font-color;
+        font-weight: 500;
+      }
+    }
+    
+    &__back {
+      font-size: 38rpx;
+    }
+    
+    &--hover {
+      background-color: $tn-font-holder-color;
+    }
+  }
+</style>

+ 334 - 0
addons/exam/tuniao-bak/tn-rate/tn-rate.vue

@@ -0,0 +1,334 @@
+<template>
+  <view
+    :id="elId"
+    class="tn-rate-class tn-rate"
+    @touchmove.stop.prevent="touchMove"
+  >
+    <view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
+      <view
+        class="tn-rate__wrap__icon"
+        :class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
+        :style="[iconStyle(index)]"
+        @tap="click(index + 1, $event)"
+      >
+        <!-- 半图标 -->
+        <view
+          v-if="showHalfIcon(index)"
+          class="tn-rate__wrap__icon--half"
+          :class="[`tn-icon-${elActionIcon}`]"
+          :style="[halfIconStyle]"
+        ></view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-rate',
+    props: {
+      // 选中星星的数量
+      value: {
+        type: Number,
+        default: 0
+      },
+      // 显示的星星数
+      count: {
+        type: Number,
+        default: 5
+      },
+      // 最小能选择的星星数
+      minCount: {
+        type: Number,
+        default: 0
+      },
+      // 禁用状态
+      disabled: {
+        type: Boolean,
+        default: false
+      },
+      // 是否可以选择半星
+      allowHalf: {
+        type: Boolean,
+        default: false
+      },
+      // 星星大小
+      size: {
+        type: Number,
+        default: 32
+      },
+      // 被选中的图标
+      activeIcon: {
+        type: String,
+        default: 'star-fill'
+      },
+      // 未被选中的图标
+      inactiveIcon: {
+        type: String,
+        default: 'star'
+      },
+      // 被选中的颜色
+      activeColor: {
+        type: String,
+        default: '#01BEFF'
+      },
+      // 默认颜色
+      inactiveColor: {
+        type: String,
+        default: '#AAAAAA'
+      },
+      // 星星之间的距离
+      gutter: {
+        type: Number,
+        default: 10
+      },
+      // 自定义颜色
+      colors: {
+        type: Array,
+        default() {
+          return []
+        }
+      },
+      // 自定义图标
+      icons: {
+        type: Array,
+        default() {
+          return []
+        }
+      }
+    },
+    computed: {
+      // 图标显示的比例
+      showHalfIcon(index) {
+        return index => {
+          return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
+        }
+      },
+      // 被激活的图标
+      elActionIcon() {
+        const len = this.icons.length
+        // icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
+        // 5个时,用第三个图标作为激活的图标
+        if (len && len <= this.count) {
+          const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
+          if (step < 1) return this.icons[0]
+          if (step > len) return this.icons[len - 1]
+          return this.icons[step - 1]
+        }
+        return this.activeIcon
+      },
+      // 未被激活的图标
+      elInactionIcon() {
+        const len = this.icons.length
+        // icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
+        // 5个时,用第三个图标作为激活的图标
+        if (len && len <= this.count) {
+          const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
+          if (step < 1) return this.icons[0]
+          if (step > len) return this.icons[len - 1]
+          return this.icons[step - 1]
+        }
+        return this.inactiveIcon
+      },
+      // 被激活的颜色
+      elActionColor() {
+        const len = this.colors.length
+        // 如果有设置colors参数(此参数用于将图标分段,比如一共5颗星,colors传3个颜色值,那么根据一定的规则,2颗星可能为第一个颜色
+        // 4颗星为第二个颜色值,5颗星为第三个颜色值)
+        if (len && len <= this.count) {
+          const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
+          if (step < 1) return this.colors[0]
+          if (step > len) return this.colors[len - 1]
+          return this.colors[step - 1]
+        }
+        return this.activeColor
+      },
+      // 图标的样式
+      iconStyle() {
+        return index => {
+          let style = {}
+          
+          style.fontSize = this.$t.string.getLengthUnitValue(this.size)
+          style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
+          // 当前图标的颜色
+          if (this.allowHalf && this.halfIcon) {
+            style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
+          } else {
+            style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
+          }
+          return style
+        }
+      },
+      // 半图标样式
+      halfIconStyle() {
+        let style = {}
+        
+        style.fontSize = this.$t.string.getLengthUnitValue(this.size)
+        style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
+        style.color = this.elActionColor
+        return style
+      }
+    },
+    data() {
+      return {
+        // 保证控件的唯一性
+        elId: this.$t.uuid(),
+        elClass: this.$t.uuid(),
+        // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
+        starBoxLeft: 0,
+        // 当前激活的星星的序号
+        activeIndex: this.value,
+        // 每个星星的宽度
+        starWidth: 0,
+        // 每个星星最右边到盒子组件最左边的距离
+        starWidthArr: [],
+        // 标记是否为半图标
+        halfIcon: false,
+      }
+    },
+    watch: {
+      value(val) {
+        this.activeIndex = val
+        if (this.allowHalf && (val % 1 === 0.5)) {
+          this.halfIcon = true
+        } else {
+          this.halfIcon = false
+        }
+      },
+      size() {
+        // 当尺寸修改的时候重新获取布局尺寸信息
+        this.$nextTick(() => {
+          this.getElRectById()
+          this.getElRectByClass()
+        })
+      }
+    },
+    mounted() {
+      this.getElRectById()
+      this.getElRectByClass()
+    },
+    methods: {
+      // 获取评分组件盒子的布局信息
+      getElRectById() {
+        this._tGetRect('#'+this.elId).then(res => {
+          this.starBoxLeft = res.left
+        })
+      },
+      // 获取单个星星的尺寸
+      getElRectByClass() {
+        this._tGetRect('.'+this.elClass).then(res => {
+          this.starWidth = res.width
+          // 把每个星星最右边到盒子最左边的距离
+          for (let i = 0; i < this.count; i++) {
+            this.starWidthArr[i] = (i + 1) * this.starWidth
+          }
+        })
+      },
+      // 手指滑动
+      touchMove(e) {
+        if (this.disabled) return
+        if (!e.changedTouches[0]) return
+        
+        const movePageX = e.changedTouches[0].pageX
+        // 滑动点相对于评分盒子左边的距离
+        const distance = movePageX - this.starBoxLeft
+        
+        // 如果滑动到了评分盒子的左边界,设置为0星
+        if (distance <= 0) {
+          this.activeIndex = 0
+        }
+        
+        // 计算滑动的距离相当于点击多少颗星星
+        let index = Math.ceil(distance / this.starWidth)
+        if (this.allowHalf) {
+          const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
+          if (distance < iconHalfWidth) {
+            this.halfIcon = true
+            index -= 0.5
+          } else {
+            this.halfIcon = false
+          }
+        }
+        this.activeIndex = index > this.count ? this.count : index
+        
+        if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
+        
+        this.emitEvent()
+      },
+      // 通过点击直接选中
+      click(index, e) {
+        if (this.disabled) return
+        // 半星选择
+        if (this.allowHalf) {
+          if (!e.changedTouches[0]) return
+          const movePageX = e.changedTouches[0].pageX
+          // 点击点相对于当前图标左边的距离
+          const distance = movePageX - this.starBoxLeft
+          const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
+          if (distance < iconHalfWidth) {
+            this.halfIcon = true
+          } else {
+            this.halfIcon = false
+          }
+        }
+        
+        // 对第一个星星特殊处理,只有一个的时候,点击可以取消,否则无法作0星评价
+        if (index == 1) {
+          if (this.allowHalf && this.allowHalf) {
+            if ((this.activeIndex === 0.5 && this.halfIcon) || 
+                (this.activeIndex === 1 && !this.halfIcon)) {
+              this.activeIndex = 0
+            } else {
+              this.activeIndex = this.halfIcon ? 0.5 : 1
+            }
+          } else {
+            if (this.activeIndex == 1) {
+              this.activeIndex = 0
+            } else {
+              this.activeIndex = 1
+            }
+          }
+        } else {
+          this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
+        }
+        
+        if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
+        
+        this.emitEvent()
+      },
+      // 发送事件
+      emitEvent() {
+        this.$emit('change', this.activeIndex)
+        // 修改v-model的值
+        this.$emit('input', this.activeIndex)
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-rate {
+    display: inline-flex;
+    align-items: center;
+    margin: 0;
+    padding: 0;
+    
+    &__wrap {
+      
+      &__icon {
+        position: relative;
+        box-sizing: border-box;
+        
+        &--half {
+          position: absolute;
+          top: 0;
+          left: 0;
+          display: inline-block;
+          overflow: hidden;
+          width: 50%;
+        }
+      }
+    }
+  }
+</style>

+ 71 - 0
addons/exam/tuniao-bak/tn-time-line-item/tn-time-line-item.vue

@@ -0,0 +1,71 @@
+<template>
+  <view class="tn-time-line-item-class tn-time-line-item">
+    <view>
+      <slot name="content"></slot>
+    </view>
+    <view class="tn-time-line-item__node" :style="[nodeStyle]">
+      <slot name="node">
+        <view class="tn-time-line-item__node--dot"></view>
+      </slot>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-time-line-item',
+    props: {
+      // 节点左边图标的绝对定位top值
+      top: {
+        type: [String, Number],
+        default: ''
+      }
+    },
+    computed: {
+      nodeStyle() {
+        let style = {}
+        if (this.top !== '') style.top = this.top + 'rpx'
+        
+        return style
+      }
+    },
+    data() {
+      return {
+        
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-time-line-item {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    position: relative;
+    margin-bottom: 32rpx;
+    
+    &__node {
+      display: flex;
+      flex-direction: row;
+      position: absolute;
+      top: 12rpx;
+      left: -40rpx;
+      align-items: center;
+      justify-content: center;
+      font-size: 24rpx;
+      transform-origin: 0;
+      transform: translateX(-50%);
+      z-index: 1;
+      background-color: transparent;
+      
+      &--dot {
+        width: 16rpx;
+        height: 16rpx;
+        border-radius: 100rpx;
+        background-color: #AAAAAA;
+      }
+    }
+  }
+</style>

+ 71 - 0
addons/exam/tuniao-bak/tn-time-line-item/tn-time-line-item.vue_bk

@@ -0,0 +1,71 @@
+<template>
+  <view class="tn-time-line-item-class tn-time-line-item">
+    <view>
+      <slot name="content"></slot>
+    </view>
+    <view class="tn-time-line-item__node" :style="[nodeStyle]">
+      <slot name="node">
+        <view class="tn-time-line-item__node--dot"></view>
+      </slot>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-time-line-item',
+    props: {
+      // 节点左边图标的绝对定位top值
+      top: {
+        type: [String, Number],
+        default: ''
+      }
+    },
+    computed: {
+      nodeStyle() {
+        let style = {}
+        if (this.top !== '') style.top = this.top + 'rpx'
+        
+        return style
+      }
+    },
+    data() {
+      return {
+        
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-time-line-item {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    position: relative;
+    margin-bottom: 32rpx;
+    
+    &__node {
+      display: flex;
+      flex-direction: row;
+      position: absolute;
+      top: 12rpx;
+      left: -40rpx;
+      align-items: center;
+      justify-content: center;
+      font-size: 24rpx;
+      transform-origin: 0;
+      transform: translateX(-50%);
+      z-index: 1;
+      background-color: transparent;
+      
+      &--dot {
+        width: 16rpx;
+        height: 16rpx;
+        border-radius: 100rpx;
+        background-color: #AAAAAA;
+      }
+    }
+  }
+</style>

+ 39 - 0
addons/exam/tuniao-bak/tn-time-line/tn-time-line.vue

@@ -0,0 +1,39 @@
+<template>
+  <view class="tn-time-line-class tn-time-line">
+    <slot></slot>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-time-line',
+    props: {
+      
+    },
+    data() {
+      return {
+        
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-time-line {
+    padding-left: 40rpx;
+    position: relative;
+    
+    &::before {
+      content: '';
+      position: absolute;
+      width: 1px;
+      left: 0;
+      top: 12rpx;
+      bottom: 0;
+      border-left: 1px solid #AAAAAA;
+      transform-origin: 0 0;
+      transform: scaleX(0.5);
+    }
+  }
+</style>

+ 39 - 0
addons/exam/tuniao-bak/tn-time-line/tn-time-line.vue_bk

@@ -0,0 +1,39 @@
+<template>
+  <view class="tn-time-line-class tn-time-line">
+    <slot></slot>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-time-line',
+    props: {
+      
+    },
+    data() {
+      return {
+        
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-time-line {
+    padding-left: 40rpx;
+    position: relative;
+    
+    &::before {
+      content: '';
+      position: absolute;
+      width: 1px;
+      left: 0;
+      top: 12rpx;
+      bottom: 0;
+      border-left: 1px solid #AAAAAA;
+      transform-origin: 0 0;
+      transform: scaleX(0.5);
+    }
+  }
+</style>

+ 324 - 0
addons/exam/tuniao-bak/tn-verification-code-input/tn-verification-code-input.vue

@@ -0,0 +1,324 @@
+<template>
+  <view class="tn-verification-code-class tn-verification-code">
+    <view class="tn-code__container">
+      <input class="tn-code__input" :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxLength" @input="getValue" />
+      <view v-for="(item, index) in loopCharArr" :key="index">
+        <view
+          class="tn-code__item"
+          :class="[{
+            'tn-code__item--breathe': breathe && charArrLength === index,
+            'tn-code__item__box': mode === 'box',
+            'tn-code__item__box--active': mode === 'box' && charArrLength === index
+          }]"
+          :style="[itemStyle(index)]"
+        >
+          <view
+            v-if="mode !== 'middleLine'"
+            class="tn-code__item__line tn-code__item__line--placeholder"
+            :style="[placeholderLineStyle(index)]"
+          ></view>
+          <view
+            v-if="mode === 'middleLine' && charArrLength <= index"
+            class="tn-code__item__line tn-code__item__line--middle"
+            :class="[{
+              'tn-code__item__line--bold': bold,
+              'tn-code__item--breathe': breathe && charArrLength === index,
+              'tn-code__item__line--active': charArrLength === index
+            }]"
+            :style="[lineStyle(index)]"
+          ></view>
+          <view
+            v-if="mode === 'bottomLine'"
+            class="tn-code__item__line tn-code__item__line--bottom"
+            :class="[{
+              'tn-code__item__line--bold': bold,
+              'tn-code__item--breathe': breathe && charArrLength === index,
+              'tn-code__item__line--active': charArrLength === index
+            }]"
+            :style="[lineStyle(index)]"
+          ></view>
+          <block v-if="!dotFill">
+            <text>{{ charArr[index] ? charArr[index] : '' }}</text>
+          </block>
+          <block v-else>
+            <text class="tn-code__item__dot">{{ charArr[index] ? '●' : '' }}</text>
+          </block>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-verification-code',
+    props: {
+      // 验证码的值
+      value: {
+        type: [String, Number],
+        default: ''
+      },
+      // 最大输入长度
+      maxLength: {
+        type: Number,
+        default: 4
+      },
+      // 显示模式
+      // box -> 盒子 bottomLine -> 底部横线 middleLine -> 中间横线
+      mode: {
+        type: String,
+        default: 'box'
+      },
+      // 用圆点填充空白位置
+      dotFill: {
+        type: Boolean,
+        default: false
+      },
+      // 字体加粗
+      bold: {
+        type: Boolean,
+        default: false
+      },
+      // 字体大小
+      fontSize: {
+        type: [String, Number],
+        default: ''
+      },
+      // 激活时颜色
+      activeColor: {
+        type: String,
+        default: ''
+      },
+      // 未激活时颜色
+      inactiveColor: {
+        type: String,
+        default: ''
+      },
+      // 输入框宽度,单位rpx
+      inputWidth: {
+        type: Number,
+        default: 80
+      },
+      // 当前激活的item带呼吸效果
+      breathe: {
+        type: Boolean,
+        default: true
+      },
+      // 自动获取焦点
+      focus: {
+        type: Boolean,
+        default: false
+      },
+      // 隐藏原生键盘,当使用自定义键盘的时候设置该参数未true即可
+      disabledKeyboard: {
+        type: Boolean,
+        default: false
+      }
+    },
+    computed: {
+      // 拆分要显示的字符
+      charArr() {
+        return this.valueModel.split('')
+      },
+      // 当前输入字符的长度
+      charArrLength() {
+        return this.charArr.length
+      },
+      // 输入框的个数
+      loopCharArr() {
+        return new Array(this.maxLength)
+      },
+      itemStyle() {
+        return (index) => {
+          let style = {}
+          style.fontWeight = this.bold ? 'bold' : 'normal'
+          if (this.fontSize) {
+            style.fontSize = this.fontSize + 'rpx'
+          }
+          if (this.inputWidth) {
+            style.width = this.inputWidth + 'rpx'
+            style.height = this.inputWidth + 'rpx'
+            style.lineHeight = this.inputWidth + 'rpx'
+          }
+          if (this.inactiveColor) {
+            style.color = this.inactiveColor
+            style.borderColor = this.inactiveColor
+          }
+          if (this.mode === 'box' && this.charArrLength === index) {
+            style.borderColor = this.activeColor
+          }
+          return style
+        }
+      },
+      placeholderLineStyle() {
+        return (index) => {
+          let style = {}
+          style.display = this.charArrLength === index ? 'block' : 'none'
+          if (this.inputWidth) {
+            style.height = (this.inputWidth * 0.5) + 'rpx'
+          }
+          return style
+        }
+      },
+      lineStyle() {
+        return (index) => {
+          let style = {}
+          if (this.inactiveColor) {
+            style.backgroundColor = this.inactiveColor
+          }
+          if (this.charArrLength === index && this.activeColor) {
+            style.backgroundColor = this.activeColor
+          }
+          return style
+        }
+      }
+    },
+    watch: {
+      value: {
+        handler(val) {
+          // 转换为字符串
+          val = String(val)
+          // 截掉超出的部分
+          this.valueModel = val.substring(0, this.maxLength)
+        },
+        immediate: true
+      }
+    },
+    data() {
+      return {
+        valueModel: ''
+      }
+    },
+    methods: {
+      // 获取填写的值
+      getValue(e) {
+        const {
+          value
+        } = e.detail
+        this.valueModel = value
+        // 判断输入的长度是否超出了maxlength的值
+        if (String(value).length > this.maxLength) return
+        // 未达到maxlength之前,触发change事件,否则触发finish事件
+        this.$emit('change', value)
+        this.$emit('input', value)
+        if (String(value).length == this.maxLength) {
+          this.$emit('finish', value)
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  
+  .tn-verification-code {
+    text-align: center;
+    
+    .tn-code {
+      &__container {
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        flex-wrap: wrap;
+        position: relative;
+      }
+      
+      &__input {
+        position: absolute;
+        top: 0;
+        left: -100%;
+        width: 200%;
+        height: 100%;
+        text-align: left;
+        z-index: 9;
+        opacity: 0;
+        background: none;
+      }
+      
+      &__item {
+        position: relative;
+        width: 80rpx;
+        height: 80rpx;
+        line-height: 80rpx;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: center;
+        margin: 10rpx 10rpx;
+        font-size: 60rpx;
+        font-weight: bold;
+        color: #838383;
+        
+        &--breathe {
+          animation: breathe 2s infinite ease;
+        }
+        
+        &__box {
+          border: 2rpx solid #AAAAAA;
+          border-radius: 6rpx;
+          
+          &--active {
+            animation-timing-function: ease-in-out;
+            animation-duration: 1500ms;
+            animation-iteration-count: infinite;
+            animation-direction: alternate;
+            overflow: hidden;
+            border: 2rpx solid #01BEFF;
+          }
+        }
+        
+        &__line {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          transform: translate(-50%, -50%);
+          background-color: #AAAAAA;
+          
+          &--bold {
+            height: 4px !important;
+          }
+          
+          &--placeholder {
+            display: none;
+            width: 2rpx;
+            height: 40rpx;
+          }
+          
+          &--middle, &--bottom {
+            width: 80%;
+            height: 2px;
+            border-radius: 2px;
+          }
+          &--bottom {
+            top: auto !important;
+            bottom: 0;
+            transform: translateX(-50%) !important;
+          }
+          
+          &--active {
+            background-color: #01BEFF !important;
+          }
+        }
+        
+        &__dot {
+          font-size: 34rpx;
+          line-height: 34rpx;
+        }
+      }
+    }
+  }
+  
+  @keyframes breathe {
+    0% {
+      opacity: 0.3;
+    }
+    
+    50% {
+      opacity: 1;
+    }
+    
+    100% {
+      opacity: 0.3;
+    }
+  }
+</style>

+ 149 - 0
addons/exam/tuniao-bak/tn-verification-code/tn-verification-code.vue

@@ -0,0 +1,149 @@
+<template>
+  <view class="tn-code-class tn-code">
+    
+  </view>
+</template>
+
+<script>
+  export default {
+    name: 'tn-verification-code',
+    props: {
+      // 倒计时总秒数
+      seconds: {
+        type: Number,
+        default: 60
+      },
+      // 开始时提示文字
+      startText: {
+        type: String,
+        default: '获取验证码'
+      },
+      // 倒计时提示文字
+      countDownText: {
+        type: String,
+        default: 's秒后重新获取'
+      },
+      // 结束时提示文字
+      endText: {
+        type: String,
+        default: '重新获取'
+      },
+      // 是否在H5刷新或各端返回再进入时继续倒计时
+      keepRunning: {
+      	type: Boolean,
+      	default: false
+      },
+      // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+      uniqueKey: {
+      	type: String,
+      	default: ''
+      }
+    },
+    data() {
+      return {
+        timer: null,
+        secNum: this.seconds,
+        // 是否可以执行验证码操作
+        canGetCode: true
+      }
+    },
+    watch: {
+      seconds: {
+        handler(n) {
+          this.secNum = n
+        },
+        immediate: true
+      }
+    },
+    mounted() {
+      this.checkKeepRunning()
+    },
+    beforeDestroy() {
+      this.setTimeToStorage()
+      if (this.timer) {
+        clearInterval(this.timer)
+        this.timer = null
+      }
+    },
+    methods: {
+      // 检查是否继续运行
+      checkKeepRunning() {
+        // 获取上一次退出页面时的时间戳,如果没有上次保存,该值为空
+        let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$tCountDownTimestamp'))
+        if (!lastTimestamp) return this.changeEvent(this.startText)
+        // 当前秒的时间戳
+        // + new Date() 相当于 new Date().getTime()
+        let nowTimestamp = Math.floor((+ new Date()) / 1000)
+        // 判断当前的时间戳,是否小于上一次的设定结束的时间,提前于结束的时间戳
+        if (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
+          // 剩余尚未执行完倒计时秒数
+          this.secNum = lastTimestamp - nowTimestamp
+          // 清除本地保存的变量
+          uni.removeStorageSync(this.uniqueKey + '_$tCountDownTimestamp')
+          // 开始倒计时
+          this.start()
+        } else {
+          // 如果不存在需要继续上一次的倒计时,执行正常的逻辑
+          this.changeEvent(this.startText);
+        }
+      },
+      // 开始倒计时
+      start() {
+        // 防止快速点击获取验证码按钮导致产生多个定时器导致混乱
+        if (this.timer) {
+          clearInterval(this.timer)
+          this.timer = null
+        }
+        this.$emit('start')
+        this.canGetCode = false
+        
+        this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
+        this.setTimeToStorage()
+        this.timer = setInterval(() => {
+          if (--this.secNum) {
+            this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
+          } else {
+            // 倒计时结束,清空定时器、重置提示信息
+            this.reset()
+            this.$emit('end')
+          }
+        }, 1000)
+      },
+      // 重置倒计时
+      reset() {
+        this.canGetCode = true
+        if (this.timer) {
+          clearInterval(this.timer)
+          this.timer = null
+        }
+        this.secNum = this.seconds
+        this.changeEvent(this.endText)
+      },
+      // 倒计时改变事件
+      changeEvent(text) {
+        this.$emit('change', text)
+      },
+      // 保存当前时间戳
+      // 防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
+      setTimeToStorage() {
+        if (!this.keepRunning ||!this.timer) return
+        // 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
+        // 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
+        if (this.secNum > 0 && this.secNum <= this.seconds) {
+          let nowTimestamp = Math.floor((+ new Date()) / 1000)
+          // 保存本次倒计时结束时候的时间戳
+          uni.setStorageSync(this.uniqueKey + '_$tCountDownTimestamp', nowTimestamp + this.secNum)
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .tn-code {
+    width: 0;
+    height: 0;
+    position: fixed;
+    z-index: -1;
+  }
+</style>

+ 4 - 5
addons/exam/uniapp/components/kz-page-my-color/kz-page-my-color.vue

@@ -42,7 +42,7 @@
 							<view :class="[item.icon]"></view>
 						</view>
 						<view class="tn-text-center">
-							<text class="tn-text-ellipsis tn-text-md">{{item.title}}</text>
+							<text class="tn-text-ellipsis">{{item.title}}</text>
 						</view>
 					</view>
 				</view>
@@ -62,7 +62,7 @@
 										<view :class="[`${item.icon}`]"></view>
 									</view>
 									<view class="tn-color-black tn-text-df tn-text-center tn-margin-top-sm">
-										<text class="tn-text-ellipsis tn-text-md">{{ item.title }}</text>
+										<text class="tn-text-ellipsis">{{ item.title }}</text>
 									</view>
 								</view>
 							</view>
@@ -215,7 +215,7 @@
 		},
 		data() {
 			return {
-				userInfo: uni.getStorageSync('user'),
+				userInfo: null,
 				useModule: [
 					{
 						title: '看题模式',
@@ -539,7 +539,7 @@
 			&--icon {
 				width: 100rpx;
 				height: 100rpx;
-				font-size: 50rpx;
+				font-size: 60rpx;
 				border-radius: 50%;
 				margin-bottom: 18rpx;
 				position: relative;
@@ -865,4 +865,3 @@
 	}
 
 </style>
-

Plik diff jest za duży
+ 340 - 390
addons/exam/uniapp/components/kz-question/kz-question.vue


+ 487 - 0
addons/exam/uniapp/pages/course/detail.vue

@@ -0,0 +1,487 @@
+<template>
+  <view class="template-advertise">
+    <!-- 顶部自定义导航 -->
+    <tn-nav-bar fixed alpha customBack>
+      <view slot="back" class='tn-custom-nav-bar__back tn-navbg'
+        @click="goBack">
+        <text class='icon tn-icon-home-smile-fill'></text>
+      </view>
+    </tn-nav-bar>
+    
+    <!-- <view :style="{paddingTop: `${topInfo.height}px`}"></view> -->
+    
+    <view class="banner">
+      
+      <swiper class="card-swiper" :circular="true"
+        :autoplay="true" duration="500" interval="5000" @change="cardSwiper"> 
+        <swiper-item v-for="(item,index) in swiperList" :key="index" :class="cardCur==index?'cur':''">
+          <view class="swiper-item image-banner">
+            <image :src="item.url" mode="aspectFill" v-if="item.type=='image'"></image>
+          </view>
+          <view class="swiper-item-text">
+            <view class="tn-text-xxl tn-text-bold tn-color-white">{{item.title}}</view>
+            <view class="tn-text-bold tn-color-white tn-padding-top-xs" style="font-size: 60rpx;">{{item.name}}</view>
+            <view class="tn-text-sm tn-text-bold tn-color-white tn-padding-top-sm tn-padding-bottom-sm">{{item.text}}</view>
+          </view>
+        </swiper-item>
+      </swiper>
+      <view class="indication">
+          <block v-for="(item,index) in swiperList" :key="index">
+              <view class="spot" :class="cardCur==index?'active':''"></view>
+          </block>
+      </view>
+    </view>
+    <!-- <view class="tn-padding" >
+      <view class="tn-text-bold tn-text-xl tn-padding-top-sm">
+        创新设计·全球首发
+      </view>
+      <view class="tn-padding-top-sm">
+        <text class="tn-color-black tn-text-bold">北北</text>
+        <text class="tn-color-grey tn-padding-left-sm">2022-05-21 10:03:45</text>
+        <text class="tn-padding-left tn-color-indigo--dark" >投诉</text>
+      </view>
+    </view> -->
+    
+    <view class="adver-wrap tn-bg-white">
+      <view class="tn-flex tn-flex-row-between tn-padding-top-lg" @tap="showLandscape">
+        <view class="tn-flex-3 tn-padding tn-margin-left-sm">
+          <view class="tn-flex">
+            <view class="tn-flex tn-flex-row-center tn-flex-col-center">
+              <view class="tn-padding-right tn-text-ellipsis tn-text-left">
+                <view class="tn-padding-right tn-color-black tn-text-bold tn-text-xxl">
+                  让我们一同探索未知
+                </view>
+                <view class="tn-padding-right tn-padding-top tn-text-df">一同前往无限可能</view>
+              </view>
+            </view>
+          </view>
+        </view>
+        
+        <view class="tn-flex-1 justify-content-item tn-padding tn-text-center">
+          <view class="">
+            <text class="tn-icon-qr-code" style="font-size: 70rpx;"></text>
+          </view>
+          <view class=" tn-padding-top-xs">
+            探 索
+          </view>
+        </view>
+      </view>
+      
+      <view class="detail-img tn-padding">
+        <image src='https://tnuiimage.tnkjapp.com/advertise/1.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/2.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/3.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/4.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/5.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/tabbar1.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/tabbar2.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/tabbar3.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/tabbar4.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/tabbar5.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/7.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/8.jpg' mode='widthFix' class=''></image>
+        <image src='https://tnuiimage.tnkjapp.com/advertise/10.jpg' mode='widthFix' class=''></image>
+      </view>
+      
+    </view>
+    
+    
+    
+    <view id="top-info" class="tn-footerfixed tn-flex tn-flex-row-between tn-flex-col-center tn-padding tn-safe-area-inset-bottom dd-glass" :style="{transform: `translateY(${topInfoTranslateY}px)`}" @click="showModal">
+      <view class="justify-content-item tn-padding-bottom">
+        <view class="tn-flex tn-flex-col-center tn-flex-row-left">
+          <view class="user-pic">
+            <view class="user-image">
+              <view class="tn-shadow-blur" style="background-image:url('https://tnuiimage.tnkjapp.com/blogger/blogger_beibei.jpg');width: 100rpx;height: 100rpx;background-size: cover;">
+              </view>
+            </view>
+          </view>
+          <view class="tn-padding-right tn-color-black">
+            <view class="tn-padding-right tn-padding-left-sm">
+              <text class="tn-text-lg tn-text-bold">图鸟北北</text>
+              <text class="tn-padding-left-sm">打杂UI</text>
+            </view>
+            <view class="tn-padding-right tn-padding-top-xs tn-padding-left-sm tn-text-ellipsis">
+              <text class="tn-color-blue--dark tn-text-bold">抓住那只猪科技有限公司</text>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view class="justify-content-item tn-flex-col-center tn-flex-row-center tn-text-center tn-padding-bottom">
+        <view class="">
+          <text class="tn-icon-wechat-fill tn-color-green--dark" style="font-size: 50rpx;"></text>
+        </view>
+        <view class="">
+          <text class="tn-text-sm">加微信</text>
+        </view>
+      </view>
+    </view>
+    
+    <tn-modal v-model="show1" :custom="true">
+      <view class="custom-modal-content">
+        <image @tap="previewQRCodeImage" src='https://tnuiimage.tnkjapp.com/advertise/qrcode.jpg' mode='aspectFill' style="width: 100%;"></image>
+        <view class="tn-text-center tn-padding-top">作者微信:tnkewo</view>
+        <view class="tn-text-center tn-padding-top tn-text-lg">点击上图,可识别微信二维码</view>
+      </view>
+    </tn-modal>
+    
+    <!-- 压屏窗-->
+    <tn-landscape :show="show2" @close="closeLandscape">
+      <view class="tn-text-center" @click="navTuniaoUI">
+        <image src="https://tnuiimage.tnkjapp.com/landscape/2022-new-year.png" mode="widthFix"></image>
+        <text class="tn-text-lg tn-color-white tn-padding-top-lg">—— 图鸟UI,2022年已上线 ——</text>
+      </view>
+    </tn-landscape>
+        
+    <!-- <view class='tn-tabbar-height'></view> -->
+    
+    
+  </view>
+</template>
+
+<script>
+  import template_page_mixin from '@/libs/mixin/template_page_mixin.js'
+  export default {
+    name: 'templateAdvertise',
+    mixins: [template_page_mixin],
+    data(){
+      return {
+        cardCur: 0,
+        swiperList: [{
+          id: 0,
+          type: 'image',
+          title: 'BUG再多',
+          name: '也别忘了',
+          text: '我无限续杯',
+          url: 'https://tnuiimage.tnkjapp.com/shop/cup1.jpg',
+        }, {
+          id: 1,
+          type: 'image',
+          title: '图鸟南南',
+          name: '欢迎加入东东们',
+          text: '如果你也有不错的作品',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/read.jpg',
+        }, {
+          id: 2,
+          type: 'image',
+          title: '图鸟西西',
+          name: '一起玩转scss',
+          text: '用最少的代码做最骚的效果',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/deer.jpg',
+        }, {
+          id: 3,
+          type: 'image',
+          title: '图鸟北北',
+          name: '微信号 tnkewo',
+          text: '商业合作请联系作者',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/swiper3.jpg',
+        }, {
+          id: 4,
+          type: 'image',
+          title: '图鸟猪猪',
+          name: '努力成为大佬',
+          text: '一起加油吖',
+          url: 'https://tnuiimage.tnkjapp.com/shop/banner2.jpg',
+        }],
+        
+        show1: false,
+        
+        show2: false,
+        maskCloseable: true,
+        
+        topInfo: {
+          height: 0,
+        },
+        topInfoTranslateY: 0,
+        prevScrollTop: 0
+      }
+    },
+    onLoad() {
+      
+    },
+    onReady() {
+      this.getTopInfoRect()
+    },
+    onPageScroll(e) {
+      this.handlePageScroll(e.scrollTop)
+    },
+    methods: {
+      // cardSwiper
+      cardSwiper(e) {
+        this.cardCur = e.detail.current
+      },
+      // 预览作者图片
+      previewQRCodeImage() {
+        wx.previewImage({
+          urls: ['https://tnuiimage.tnkjapp.com/advertise/qrcode.jpg']
+        })
+      },
+      // 获取顶部销售信息容器相关信息
+      getTopInfoRect() {
+        this._tGetRect('#top-info').then((res) => {
+          if (!res) {
+            setTimeout(() => {
+              this.getTopInfoRect()
+            }, 50)
+            return
+          }
+          this.topInfo.height = res.height
+        })
+      },
+      // 处理页面滚动事件
+      handlePageScroll(scrollTop) {
+        console.log(scrollTop);
+        if (this.prevScrollTop > scrollTop) {
+         
+         // 下滑
+         this.topInfoTranslateY = 0
+        } else {
+          // 上滑
+          if (scrollTop > this.topInfo.height) {
+            this.topInfoTranslateY = this.topInfo.height
+          }
+
+        }
+        
+        this.prevScrollTop = scrollTop
+      },
+      
+      // 弹出模态框
+      showModal(event) {
+        this.openModal()
+      },
+      // 打开模态框
+      openModal() {
+        this.show1 = true
+      },
+      
+      // 弹出压屏窗
+      showLandscape() {
+        this.openLandscape()
+      },
+      // 打开压屏窗
+      openLandscape() {
+        this.show2 = true
+      },
+      // 关闭压屏窗
+      closeLandscape() {
+        this.show2 = false
+      },
+      
+      // 跳转到图鸟UI
+      navTuniaoUI() {
+        uni.navigateToMiniProgram({
+          appId: 'wxf3d81a452b88ff4b'
+        })
+      },
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  /* 胶囊*/
+  .tn-custom-nav-bar__back {
+    width: 36%;
+    height: 100%;
+    position: relative;
+    display: flex;
+    justify-content: space-evenly;
+    align-items: center;
+    box-sizing: border-box;
+    background-color: rgba(0, 0, 0, 0.15);
+    border-radius: 1000rpx;
+    border: 1rpx solid rgba(255, 255, 255, 0.5);
+    color: #FFFFFF;
+    font-size: 18px;
+    
+    .icon {
+      display: block;
+      flex: 1;
+      margin: auto;
+      text-align: center;
+    }
+    
+  }
+  
+  /* 渐变*/
+  .tn-navbg {
+      margin: 0; 
+      color: #fff; 
+      background: linear-gradient(-120deg, #F15BB5, #9A5CE5, #01BEFF, #00F5D4);
+      /* background: linear-gradient(-120deg,  #9A5CE5, #01BEFF, #00F5D4, #43e97b); */
+      /* background: linear-gradient(-120deg,#c471f5, #ec008c, #ff4e50,#f9d423); */
+      /* background: linear-gradient(-120deg, #0976ea, #c471f5, #f956b6, #ea7e0a); */
+      background-size: 500% 500%; 
+      animation: gradientBG 15s ease infinite; 
+  } 
+   
+  @keyframes gradientBG { 
+      0% { 
+          background-position: 0% 50%; 
+      } 
+      50% { 
+          background-position: 100% 50%; 
+      } 
+      100% { 
+          background-position: 0% 50%; 
+      } 
+  }
+  
+  /* 内容 */
+  .adver-wrap {
+    position: relative;
+    z-index: 1;
+    // padding: 20rpx 30rpx;
+    margin-top: 700rpx;  
+    border-radius: 80rpx 80rpx 0 0;
+  }
+  
+  /* 毛玻璃*/
+  .dd-glass {
+     width: 100%;
+     backdrop-filter: blur(20rpx);
+    -webkit-backdrop-filter: blur(20rpx);
+  }
+  
+  
+  /* 用户头像 start */
+  .user-image {
+    width: 90rpx;
+    height: 90rpx;
+    position: relative;
+  }
+  
+  .user-pic {
+    background-size: cover;
+    background-repeat: no-repeat;
+    // background-attachment:fixed;
+    background-position: top;
+    border-radius: 50%;
+    overflow: hidden;
+    background-color: #FFFFFF;
+  }
+  
+  /* 富文本图示意 start */
+  .detail-img {
+    z-index: -1;
+    padding-bottom: 40rpx;
+  
+    image {
+      width: 100%;
+      margin: 20rpx 0;
+      // height: 3373rpx;
+      // z-index: -1;
+    }
+  }
+  
+  
+  .banner{
+    position: fixed;
+    top: 0;
+    width: 100%;
+    transition: all 0.25s ease-out;
+    z-index: -1;
+  }
+  
+  /* 轮播视觉差 start */
+  .card-swiper {
+    height: 800rpx !important;
+  }
+    
+  .card-swiper swiper-item {
+    
+    width: 750rpx !important;
+    left: 0rpx;
+    box-sizing: border-box;
+    // padding: 0rpx 30rpx 90rpx 30rpx;
+    overflow: initial;
+  }
+    
+  .card-swiper swiper-item .swiper-item {
+    width: 100%;
+    display: block;
+    height: 100%;
+    transform: scale(1);
+    transition: all 0.2s ease-in 0s;
+    overflow: hidden;
+  }
+    
+  .card-swiper swiper-item.cur .swiper-item {
+    transform: none;
+    transition: all 0.2s ease-in 0s;
+  }
+    
+  .card-swiper swiper-item .swiper-item-text {
+    margin-top: -320rpx;
+    width: 100%;
+    display: block;
+    height: 50%;
+    border-radius: 10rpx;
+    transform: translate(100rpx, -60rpx) scale(0.9, 0.9);
+    transition: all 0.6s ease 0s;
+    overflow: hidden;
+  }
+    
+  .card-swiper swiper-item.cur .swiper-item-text {
+    margin-top: -380rpx;
+    width: 100%;
+    transform: translate(0rpx, 0rpx) scale(0.9, 0.9);
+    transition: all 0.6s ease 0s;
+  }
+  
+  .image-banner{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+  .image-banner image{
+    width: 100%;
+    height: 100%;
+  }
+  
+  /* 轮播指示点 start*/
+  .indication{
+    z-index: 9999;
+    width: 100%;
+    height: 36rpx;
+    position: absolute;
+    display:flex;
+    flex-direction:row;
+    align-items:center;
+    justify-content:center;
+  }
+  
+  .spot{
+    background-color: #FFFFFF;
+    opacity: 0.6;
+    width: 10rpx;
+    height: 10rpx;
+    border-radius: 20rpx;
+    top: -150rpx;
+    margin: 0 8rpx !important;
+    position: relative;
+  }
+  
+  .spot.active{
+    opacity: 1;
+    width: 30rpx;
+    background-color: #FFFFFF;
+  }
+  
+  /* 底部悬浮按钮 start*/
+  .tn-tabbar-height {
+  	min-height: 120rpx;
+  	height: calc(140rpx + env(safe-area-inset-bottom) / 2);
+  }
+  .tn-footerfixed {
+    position: fixed;
+    background-color: rgba(255,255,255,0.5);
+    box-shadow: 0rpx 0rpx 30rpx 0rpx rgba(0, 0, 0, 0.07);
+    bottom: 0;
+    width: 100%;
+    transition: all 0.25s ease-out;
+    z-index: 100;
+  }
+  
+ 
+</style>

+ 435 - 0
addons/exam/uniapp/pages/course/index.vue

@@ -0,0 +1,435 @@
+<template>
+  <view class="template-study tn-safe-area-inset-bottom">
+    <!-- 顶部自定义导航 -->
+    <tn-nav-bar fixed alpha customBack>
+      <view slot="back" class='tn-custom-nav-bar__back'
+        @click="goBack">
+        <text class='icon tn-icon-left'></text>
+        <text class='icon tn-icon-home-capsule-fill'></text>
+      </view>
+    </tn-nav-bar>
+    
+    
+    <swiper class="card-swiper" :circular="true"
+      :autoplay="true" duration="500" interval="18000" @change="cardSwiper"> 
+      <swiper-item v-for="(item,index) in swiperList" :key="index" :class="cardCur==index?'cur':''">
+        <view class="swiper-item image-banner">
+          <image :src="item.url" mode="aspectFill" v-if="item.type=='image'"></image>
+        </view>
+        <view class="swiper-item2 image-banner">
+          <image class="png-sussuspension" :src="item.pngurl" mode="heightFix" v-if="item.type=='image'"></image>
+        </view>
+        <view class="swiper-item-text">
+          <view class="text-sussuspension">
+            <view class="tn-text-xxl tn-text-bold tn-color-white">{{item.title}}</view>
+            <view class="tn-text-bold tn-color-white tn-padding-top-xs" style="font-size: 60rpx;">{{item.name}}</view>
+            <view class="tn-text-sm tn-text-bold tn-color-white tn-padding-top-sm tn-padding-bottom-sm">{{item.text}}</view>
+          </view>
+        </view>
+      </swiper-item>
+    </swiper>
+    <view class="indication">
+        <block v-for="(item,index) in swiperList" :key="index">
+            <view class="spot" :class="cardCur==index?'active':''"></view>
+        </block>
+    </view>
+    
+    
+    <!-- 方式10 start-->
+    <view class="tn-flex tn-margin-top">
+      <view class="tn-flex-1 tn-padding-sm tn-radius tn-margin-xs">
+        <view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
+          <view class="icon10__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-bg-blue tn-color-white">
+            <view class="tn-icon-gloves-fill"></view>
+          </view>  
+          <view class="tn-color-black tn-text-df tn-text-center tn-padding-top-xs">
+            <text class="tn-text-ellipsis">学习</text>
+          </view>
+        </view>
+      </view>
+      <view class="tn-flex-1 tn-padding-sm tn-radius tn-margin-xs">
+        <view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
+          <view class="icon10__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-bg-red tn-color-white">
+            <view class="tn-icon-live-stream-fill"></view>
+          </view>  
+          <view class="tn-color-black tn-text-df tn-text-center tn-padding-top-xs">
+            <text class="tn-text-ellipsis">视频</text>
+          </view>
+        </view>
+      </view>
+      <view class="tn-flex-1 tn-padding-sm tn-radius tn-margin-xs">
+        <view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
+          <view class="icon10__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-bg-orange tn-color-white">
+            <view class="tn-icon-image-text-fill"></view>
+          </view>  
+          <view class="tn-color-black tn-text-df tn-text-center tn-padding-top-xs">
+            <text class="tn-text-ellipsis">日志</text>
+          </view>
+        </view>
+      </view>
+      <view class="tn-flex-1 tn-padding-sm tn-radius tn-margin-xs">
+        <view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
+          <view class="icon10__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-bg-purple tn-color-white">
+            <view class="tn-icon-topics-fill"></view>
+          </view>  
+          <view class="tn-color-black tn-text-df tn-text-center tn-padding-top-xs">
+            <text class="tn-text-ellipsis">话题</text>
+          </view>
+        </view>
+      </view>
+      <view class="tn-flex-1 tn-padding-sm tn-radius tn-margin-xs">
+        <view class="tn-flex tn-flex-direction-column tn-flex-row-center tn-flex-col-center">
+          <view class="icon10__item--icon tn-flex tn-flex-row-center tn-flex-col-center tn-shadow-blur tn-bg-cyan tn-color-white">
+            <view class="tn-icon-chemistry"></view>
+          </view>  
+          <view class="tn-color-black tn-text-df tn-text-center tn-padding-top-xs">
+            <text class="tn-text-ellipsis">沉淀</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <!-- 方式10 end-->
+    
+    <view class="tn-margin study-shadow" v-for="(item, index) in 6" :key="index">
+      <view class="tn-padding tn-flex tn-flex-col-center">
+        <view class="tn-tag-content tn-shadow-blur tn-cool-bg-color-15 tn-margin-right tn-text-sm tn-text-bold">
+          <text class="tn-padding-right-sm">#</text> 前端
+        </view>
+        <view class="tn-text-bold tn-text-lg">
+          Flex前端基础布局知识一览
+        </view>
+      </view>
+      <view class="tn-flex tn-flex-col-center tn-flex-row-between tn-padding">
+        <view class="tn-flex tn-flex-2 justify-content-item tn-text-bold tn-text-lg">
+          <view class="">
+            <tn-avatar-group :lists="groupList" size="sm"></tn-avatar-group>
+          </view>
+          <view class="tn-padding-left-sm">
+            <text>286人已学习</text>
+          </view>
+        </view>
+        <view class="tn-flex-1 justify-content-item tn-text-lg tn-text-right">
+          <view class="tn-padding-bottom-xs tn-color-gray">
+            免费
+          </view>
+        </view>
+      </view>
+      
+    </view>
+    
+    
+    
+
+  </view>
+</template>
+
+<script>
+  import template_page_mixin from '@/libs/mixin/template_page_mixin.js'
+  export default {
+    name: 'TemplateStudy',
+    mixins: [template_page_mixin],
+    data(){
+      return {
+        cardCur: 0,
+        swiperList: [{
+          id: 0,
+          type: 'image',
+          title: '一起学习',
+          name: '文案之类的',
+          text: '小文案小文案小文案',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/banner-animate3.png',
+          pngurl: 'https://tnuiimage.tnkjapp.com/login/1/login_top3.png'
+        }, {
+          id: 1,
+          type: 'image',
+          title: '图鸟南南',
+          name: '欢迎加入东东们',
+          text: '如果你也有不错的作品',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/banner-animate2.png',
+          pngurl: 'https://tnuiimage.tnkjapp.com/swiper/c4d1.png'
+        }, {
+          id: 2,
+          type: 'image',
+          title: '图鸟西西',
+          name: '一起玩转scss',
+          text: '用最少的代码做最骚的效果',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/deer.jpg',
+          pngurl: 'https://tnuiimage.tnkjapp.com/swiper/c4d1.png'
+        }, {
+          id: 3,
+          type: 'image',
+          title: '图鸟北北',
+          name: '微信号 tnkewo',
+          text: '商业合作请联系作者',
+          url: 'https://tnuiimage.tnkjapp.com/swiper/banner-animate.png',
+          pngurl: 'https://tnuiimage.tnkjapp.com/swiper/c4d1.png'
+        }, {
+          id: 4,
+          type: 'image',
+          title: '图鸟猪猪',
+          name: '努力成为大佬',
+          text: '一起加油吖',
+          url: 'https://tnuiimage.tnkjapp.com/shop/banner2.jpg',
+          pngurl: 'https://tnuiimage.tnkjapp.com/swiper/c4d1.png'
+        }],
+        groupList: [
+          {src: 'https://tnuiimage.tnkjapp.com/blogger/avatar_1.jpeg'},
+          {src: 'https://tnuiimage.tnkjapp.com/blogger/avatar_2.jpeg'},
+          {src: 'https://tnuiimage.tnkjapp.com/blogger/avatar_3.jpeg'},
+          {src: 'https://tnuiimage.tnkjapp.com/blogger/avatar_4.jpeg'},
+          {src: 'https://tnuiimage.tnkjapp.com/blogger/blogger_beibei.jpg'},
+        ]
+      }
+    },
+    methods: {
+      // cardSwiper
+      cardSwiper(e) {
+        this.cardCur = e.detail.current
+      },
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+    /* 胶囊*/
+    .tn-custom-nav-bar__back {
+      width: 100%;
+      height: 100%;
+      position: relative;
+      display: flex;
+      justify-content: space-evenly;
+      align-items: center;
+      box-sizing: border-box;
+      background-color: rgba(0, 0, 0, 0.15);
+      border-radius: 1000rpx;
+      border: 1rpx solid rgba(255, 255, 255, 0.5);
+      color: #FFFFFF;
+      font-size: 18px;
+      
+      .icon {
+        display: block;
+        flex: 1;
+        margin: auto;
+        text-align: center;
+      }
+      
+      &:before {
+        content: " ";
+        width: 1rpx;
+        height: 110%;
+        position: absolute;
+        top: 22.5%;
+        left: 0;
+        right: 0;
+        margin: auto;
+        transform: scale(0.5);
+        transform-origin: 0 0;
+        pointer-events: none;
+        box-sizing: border-box;
+        opacity: 0.7;
+        background-color: #FFFFFF;
+      }
+    }
+    
+    /* 内容 */
+    .study-wrap {
+      position: relative;
+      z-index: 1;
+      margin-top: -90rpx;  
+    }
+   
+   /* 阴影 start*/
+   .study-shadow {
+     border-radius: 15rpx;
+     box-shadow: 0rpx 0rpx 50rpx 0rpx rgba(0, 0, 0, 0.07);
+   }
+   
+   /* 悬浮 */
+   .png-sussuspension{
+     animation: suspension 3s ease-in-out infinite;
+   }
+   @keyframes suspension {
+     0%, 100% {
+       transform: translate(0 , 0);
+     }
+     50% {
+       transform: translate(-0.8rem , 1rem);
+     }
+   }
+   .text-sussuspension{
+     animation: suspension2 4s ease-in-out infinite;
+   }
+   
+   @keyframes suspension2 {
+     0%, 100% {
+       transform: translate(0 , 0);
+     }
+     50% {
+       transform: translate(0rem , 1rem);
+     }
+   }
+   
+   
+   /* 轮播视觉差 start */
+   .card-swiper {
+     height: 800rpx !important;
+   }
+     
+   .card-swiper swiper-item {
+     width: 750rpx !important;
+     left: 0rpx;
+     box-sizing: border-box;
+     // padding: 0rpx 30rpx 90rpx 30rpx;
+     overflow: initial;
+   }
+     
+   .card-swiper swiper-item .swiper-item {
+     width: 100%;
+     display: block;
+     height: 100%;
+     transform: scale(1);
+     transition: all 0.2s ease-in 0s;
+     overflow: hidden;
+   }
+     
+   .card-swiper swiper-item.cur .swiper-item {
+     transform: none;
+     transition: all 0.2s ease-in 0s;
+   }
+   
+   .card-swiper swiper-item .swiper-item2 {
+     margin-top: -540rpx;
+     width: 100%;
+     display: block;
+     height: 100%;
+     border-radius: 0rpx;
+     transform: translate(140rpx, 20rpx) scale(0.3, 0.3);
+     transition: all 0.6s ease 0s;
+     overflow: hidden;
+   }
+     
+   .card-swiper swiper-item.cur .swiper-item2 {
+     margin-top: -650rpx;
+     width: 100%;
+     transform: translate(180rpx, 0rpx) scale(0.5, 0.5);
+     transition: all 0.6s ease 0s;
+   }
+     
+   .card-swiper swiper-item .swiper-item-text {
+     margin-top: -550rpx;
+     width: 100%;
+     display: block;
+     height: 50%;
+     border-radius: 10rpx;
+     transform: translate(100rpx, -60rpx) scale(0.9, 0.9);
+     transition: all 0.6s ease 0s;
+     overflow: hidden;
+   }
+     
+   .card-swiper swiper-item.cur .swiper-item-text {
+     margin-top: -610rpx;
+     width: 100%;
+     transform: translate(0rpx, 60rpx) scale(0.9, 0.9);
+     transition: all 0.6s ease 0s;
+   }
+   
+   .image-banner{
+     display: flex;
+     align-items: center;
+     justify-content: center;
+   }
+   .image-banner image{
+     width: 100%;
+     height: 100%;
+   }
+   
+   /* 轮播指示点 start*/
+   .indication{
+     z-index: 9999;
+     width: 100%;
+     height: 36rpx;
+     position: absolute;
+     display:flex;
+     flex-direction:row;
+     align-items:center;
+     justify-content:center;
+   }
+   
+   .spot{
+     background-color: #FFFFFF;
+     opacity: 0.6;
+     width: 10rpx;
+     height: 10rpx;
+     border-radius: 20rpx;
+     left: -265rpx;
+     top: -150rpx;
+     margin: 0 8rpx !important;
+     position: relative;
+   }
+   
+   .spot.active{
+     opacity: 1;
+     width: 30rpx;
+     background-color: #FFFFFF;
+   }
+   
+   /* 圆角*/
+   .study-radius{
+     border-radius: 15rpx;
+   }
+   
+   /* 阴影*/
+   .study-shadow{
+     box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.07);
+   }
+   
+   /* 图标容器10 start */
+     .icon10 {
+       &__item {
+         width: 30%;
+         background-color: #FFFFFF;
+         border-radius: 10rpx;
+         padding: 30rpx;
+         margin: 20rpx 10rpx;
+         transform: scale(1);
+         transition: transform 0.3s linear;
+         transform-origin: center center;
+         
+         &--icon {
+           width: 84rpx;
+           height: 65rpx;
+           font-size: 45rpx;
+           border-radius: 200rpx;
+           margin-bottom: 18rpx;
+           position: relative;
+           z-index: 1;
+           
+           &::after {
+             content: " ";
+             position: absolute;
+             z-index: -1;
+             width: 100%;
+             height: 100%;
+             left: 0;
+             bottom: 0;
+             border-radius: inherit;
+             opacity: 1;
+             transform: scale(1, 1);
+             background-size: 100% 100%;
+             background-image: url(https://tnuiimage.tnkjapp.com/cool_bg_image/icon_bg6.png);
+           }
+         }
+       }
+     }
+   /* 图标容器10 end */
+   
+   /* 标签内容 start*/
+   .tn-tag-content {
+       border-radius: 8rpx 25rpx 25rpx 8rpx;
+       display: inline-block;
+       line-height: 35rpx;
+       padding: 8rpx 25rpx;
+       color: rgba(255,255,255,0.8);
+   }
+   
+</style>

+ 3 - 8
addons/exam/uniapp/pages/index/index.vue

@@ -121,19 +121,14 @@
 						// 轮播图
 						if (system.banner) {
 							let banners = []
-							for (let bannerImage of (system.banner.split(','))) {
-								console.log('bannerImage', bannerImage, bannerImage.indexOf('http'))
-								if (bannerImage.indexOf('http') < 0) {
-									bannerImage = this.imgUrl + bannerImage
-								}
-
+							for (var image of (system.banner.split(','))) {
 								banners.push({
-									image: bannerImage
+									image: this.imgUrl + image
 								})
 							}
 							this.banners = banners
 							console.log('banners', this.banners)
-
+							
 							// 延迟加载:v-if导致组件未完全渲染
 							// setTimeout(() => {
 							// 	this.banners = system.banner.split(',')

+ 4 - 4
addons/exam/uniapp/pages/paper/index.vue

@@ -23,7 +23,7 @@
 			</view>
 			
 			<!-- 加载状态条 -->
-			<view class="cu-load" :class="loadFlag" v-show="!has_more"></view>
+			<view class="cu-load bg-grey" :class="loadFlag" v-show="!has_more"></view>
 		</view>
 		
 		<tabbar></tabbar>
@@ -97,10 +97,10 @@
 			},
 			// 获取题库
 			async getCate(type = '') {
-				await this.http('cate/filter', {kind: 'PAPER'}).then(res => {
+				await this.http('cate/filter', {kind: 'QUESTION'}).then(res => {
 					this.filterData = res.data
 					
-					/* if (type == 'init') {
+					if (type == 'init') {
 						let defaultCate = this.utils.getData('default_cate-QUESTION')
 						console.log('defaultCate', defaultCate)
 						if (defaultCate) {
@@ -125,7 +125,7 @@
 						}
 						
 						
-					} */
+					}
 					
 				})
 			},

+ 4 - 7
addons/exam/uniapp/pages/room/detail.vue

@@ -229,10 +229,6 @@
 				</view>
 			</view>
 
-			<view class="padding-lr" style="margin-top: 15rpx; margin-bottom: 30rpx;">
-				<text class="text-grey text-sm">{{signupTips}}</text>
-			</view>
-
 			<!-- 报名、考试按钮 -->
 			<view class="padding-xl">
 				<button class="cu-btn round block margin-tb-sm lg" :class="[signupBtnClass]" @tap="signup"
@@ -241,6 +237,10 @@
 				<button class="cu-btn round block margin-tb-sm lg bg-green" @tap="startPaper"
 					v-if="canStart">{{canStart == 1 ? '开始考试' : '开始补考'}}</button>
 			</view>
+
+			<view class="padding-lr" style="margin-top: 15rpx; margin-bottom: 30rpx;">
+				<text class="text-grey text-sm">{{signupTips}}</text>
+			</view>
 		</view>
 
 		<!-- 报名对话框 -->
@@ -494,9 +494,6 @@
 						// 考试记录
 						this.examLogs = res.data.exam_logs
 					}
-					
-					let timestampInSeconds = Math.floor(Date.now() / 1000);
-					this.canSignup = timestampInSeconds > this.detail.start_time && timestampInSeconds < this.detail.end_time
 				})
 			},
 

+ 1 - 1
addons/exam/uniapp/pages/room/index.vue

@@ -42,7 +42,7 @@
 			</view>
 
 			<!-- 加载状态条 -->
-			<view class="cu-load" :class="loadFlag" v-show="!has_more"></view>
+			<view class="cu-load bg-grey" :class="loadFlag" v-show="!has_more"></view>
 		</view>
 
 		<tabbar></tabbar>

+ 215 - 234
addons/exam/uniapp/pages/search/index.vue

@@ -1,242 +1,223 @@
 <template>
-  <view>
-    <!-- #ifdef H5 -->
-    <!-- 顶部自定义导航 -->
-    <tn-nav-bar fixed :bottomShadow="false" backTitle=" ">
-      <view class="">
-        <text class="tn-text-lg">题目搜索</text>
-        <text class="tn-text-xl tn-padding-left-sm tn-icon-group-circle"></text>
-      </view>
-    </tn-nav-bar>
-    <!-- #endif -->
-
-    <view class="cu-bar search bg-white">
-      <view class="search-form round">
-        <text class="cuIcon-search"></text>
-        <input v-model="keyword" @input="inputSearch" @confirm="search()" :adjust-position="false" type="text"
-               placeholder="请输入题目关键词进行搜索" confirm-type="search"></input>
-      </view>
-      <view class="action">
-        <button class="cu-btn bg-primary shadow-blur round" @click="search()">搜索</button>
-      </view>
-    </view>
-
-    <view class="question-list">
-      <view class="question-card" v-for="(item, index) in list" :key="index">
-        <tui-card :title="{text: item.cates.name, size: 30, color: '#7A7A7A'}"
-                  :tag="{text: item.kind_text, size: 24}" :header="item.title">
-          <template v-slot:body>
-            <view class="m-lr-20">
-              <view class="m-t-20" v-if="item.is_material_child && item.material_question_id && item.material_parent">
-                <text>材料题干:</text>
-                <!-- <rich-text :nodes="formatRichtext(item.material_parent.title)" style="font-size: 36rpx; font-weight: bold;"></rich-text> -->
-                <mp-html :content="formatRichtext(item.material_parent.title)"
-                         style="font-size: 36rpx; font-weight: bold;"></mp-html>
-              </view>
-
-              <view class="m-t-20">
-                <text>{{ item.kind_text }}:</text>
-                <!-- <rich-text :nodes="formatRichtext(item.title)" style="font-size: 36rpx; font-weight: bold;"></rich-text> -->
-                <mp-html :content="formatRichtext(item.title)" style="font-size: 36rpx; font-weight: bold;"></mp-html>
-              </view>
-              <view class="options">
-                <view v-if="item.kind == 'FILL'" v-html="getFillAnswer(item)"></view>
-
-                <view v-for="(option, i) in item.options_json"
-                      :key="i"
-                      :class="item.answer.indexOf(option.key) > -1 ? ['text-green'] : []"
-                      v-else
-                >
-                  {{ option.key }}:
-                  <mp-html :content="option.value"></mp-html>
-                  <!-- {{ option.value }} -->
-                  <block v-if="item.options_img && item.options_img[i]">
-                    <image :src="item.options_img[i].value" @click="previewOptionImage(item.options_img[i].value)"
-                           class="option-img"></image>
-                  </block>
-
-                </view>
-              </view>
-            </view>
-          </template>
-          <template v-slot:footer>
-            <text class="margin-left-sm">解析:</text>
-            <view class="explain" v-if="item.explain">
-              <!-- <rich-text :nodes="formatRichtext(item.explain)" style="border-top: solid 1px #ccc;width: 100%;display: block;" v-if="item.explain"></rich-text> -->
-              <mp-html :content="formatRichtext(item.explain)"
-                       style="border-top: solid 1px #ccc;width: 100%;display: block;" v-if="item.explain"></mp-html>
-            </view>
-            <view class="explain" v-else>无</view>
-          </template>
-        </tui-card>
-
-      </view>
-
-      <!-- 加载状态条 -->
-      <view class="cu-load bg-grey" :class="loadFlag" v-show="has_more || loadFlag == 'over'"></view>
-    </view>
-
-    <tui-loading v-if="showLoading"></tui-loading>
-    <tui-no-data imgUrl="/static/img/img_noorder_3x.png" v-if="showNodata">暂无数据</tui-no-data>
-
-    <!--流量主组件-->
-    <!-- #ifdef MP-WEIXIN -->
-    <view :class="showNodata ? ['margin-top-100'] : ['margin-top-10']" v-show="false">
-      <kz-ad ref="adSearch" kind="BANNER" :config="ad" field="question_search_banner"></kz-ad>
-      <kz-ad ref="adSearch" kind="VIDEO" :config="ad" field="question_search_video"></kz-ad>
-      <kz-ad ref="adSearch" kind="VIDEO_PATCH" :config="ad" field="question_search_video_patch"></kz-ad>
-    </view>
-    <!-- #endif -->
-
-    <login ref="login"></login>
-    <tabbar></tabbar>
-  </view>
+	<view>
+		<view class="cu-bar search bg-white">
+			<view class="search-form round">
+				<text class="cuIcon-search"></text>
+				<input @input="inputSearch" @confirm="search()" :adjust-position="false" type="text"
+					placeholder="请输入题目关键词进行搜索" confirm-type="search"></input>
+			</view>
+			<view class="action">
+				<button class="cu-btn bg-primary shadow-blur round" @click="search()">搜索</button>
+			</view>
+		</view>
+
+		<view class="question-list">
+			<view class="question-card" v-for="(item, index) in list" :key="index">
+				<tui-card :title="{text: item.cates.name, size: 30, color: '#7A7A7A'}"
+					:tag="{text: item.kind_text, size: 24}" :header="item.title">
+					<template v-slot:body>
+						<view class="m-lr-20">
+							<view class="m-t-20" v-if="item.is_material_child && item.material_question_id && item.material_parent">
+								<text>材料题干:</text>
+								<rich-text :nodes="formatRichtext(item.material_parent.title)" style="font-size: 36rpx; font-weight: bold;"></rich-text>
+							</view>
+							
+							<view class="m-t-20">
+								<text>{{item.kind_text}}:</text>
+								<rich-text :nodes="formatRichtext(item.title)" style="font-size: 36rpx; font-weight: bold;"></rich-text>
+							</view>
+							<view class="options">
+								<view v-if="item.kind == 'FILL'" v-html="getFillAnswer(item)"></view>
+								
+								<view v-for="(option, i) in item.options_json" 
+									:key="i"
+									:class="item.answer.indexOf(option.key) > -1 ? ['text-green'] : []"
+									v-else
+								>
+									{{ option.key }}:{{ option.value }} 
+									<block v-if="item.options_img && item.options_img[i]">
+										<image :src="item.options_img[i].value" @click="previewOptionImage(item.options_img[i].value)" class="option-img"></image>
+									</block>
+									
+								</view>
+							</view>
+						</view>
+					</template>
+					<template v-slot:footer>
+						<text class="margin-left-sm">解析:</text>
+						<view class="explain" v-if="item.explain" v-html="item.explain">
+							<rich-text :nodes="formatRichtext(item.explain)" style="border-top: solid 1px #ccc;width: 100%;display: block;" v-if="item.explain"></rich-text>
+						</view>
+						<view class="explain" v-else>无</view>
+					</template>
+				</tui-card>
+
+			</view>
+
+			<!-- 加载状态条 -->
+			<view class="cu-load bg-grey" :class="loadFlag" v-show="has_more || loadFlag == 'over'"></view>
+		</view>
+
+		<tui-loading v-if="showLoading"></tui-loading>
+		<tui-no-data imgUrl="/static/img/img_noorder_3x.png" v-if="showNodata">暂无数据</tui-no-data>
+
+		<!--流量主组件-->
+		<!-- #ifdef MP-WEIXIN -->
+		<view :class="showNodata ? ['margin-top-100'] : ['margin-top-10']" v-show="false">
+			<kz-ad ref="adSearch" kind="BANNER" :config="ad" field="question_search_banner"></kz-ad>
+			<kz-ad ref="adSearch" kind="VIDEO" :config="ad" field="question_search_video"></kz-ad>
+			<kz-ad ref="adSearch" kind="VIDEO_PATCH" :config="ad" field="question_search_video_patch"></kz-ad>
+		</view>
+		<!-- #endif -->
+
+		<login ref="login"></login>
+		<tabbar></tabbar>
+	</view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      showLoading: false,
-      showNodata: true,
-      keyword: '',
-      list: [],
-      loadFlag: 'loading',
-      current_page: 1,
-      has_more: false,
-      ad: {}
-    }
-  },
-  computed: {
-    // 显示填空题答案
-    getFillAnswer(item) {
-      return function (item) {
-        let answer = ''
-        if (item.answer && item.kind == 'FILL') {
-          try {
-            answer = '';
-            for (let i = 0; i < item.answer.length; i++) {
-              answer += '填空位' + (i + 1) + ':' + item.answer[i].answers.join('、') + '<br>';
-            }
-          } catch (e) {
-            console.log('answer', answer, e);
-            return value;
-          }
-        }
-        return answer
-      }
-    },
-  },
-  onLoad(e) {
-    this.ad = this.utils.getData('ad')
-    console.log('ad data', this.ad)
-    // 插屏广告
-    if (this.ad.question_search_cp_open == 1) {
-      this.adUtils.interstitial.load(this.ad.question_search_cp)
-      this.adUtils.interstitial.show()
-    }
-    if (e.keyword) {
-      this.keyword = e.keyword
-      this.search()
-    }
-  },
-  async onReachBottom() {
-    console.log("onReachBottom")
-    if (this.has_more) {
-      this.current_page++
-      this.search()
-    }
-  },
-  methods: {
-    search() {
-      if (!this.keyword) {
-        uni.showToast({
-          title: '请输入关键词',
-          icon: 'none'
-        })
-        return
-      }
-
-      this.loadFlag = 'loading'
-      this.has_more = true
-      this.http('question/search', {
-        keyword: this.keyword,
-        page: this.current_page
-      }, 'post').then(res => {
-        console.log('search res', res)
-        if (res.code == 0) {
-          uni.showToast({
-            title: res.msg,
-            icon: 'none'
-          })
-          return
-        } else {
-          this.list = res.data.list.data //this.list.concat(res.data.list.data)
-
-          this.current_page = res.data.list.current_page
-          this.has_more = res.data.list.has_more
-          this.loadFlag = !this.has_more ? 'over' : ''
-          this.showNodata = !this.list.length
-        }
-      })
-    },
-    inputSearch(e) {
-      console.log('input e', e)
-      this.keyword = e.detail.value
-    },
-    // 试题标题
-    formatRichtext(text) {
-      if (text) {
-        return this.utils.formatRichText(text)
-      }
-      return '无'
-    },
-    // 选项图片yulan
-    previewOptionImage(src) {
-      if (!src) {
-        return
-      }
-
-      uni.previewImage({
-        current: 0,
-        urls: [src]
-      });
-    }
-
-  }
-}
+	export default {
+		data() {
+			return {
+				showLoading: false,
+				showNodata: true,
+				keyword: '',
+				list: [],
+				loadFlag: 'loading',
+				current_page: 1,
+				has_more: false,
+				ad: {}
+			}
+		},
+		computed: {
+			// 显示填空题答案
+			getFillAnswer(item) {
+				return function(item) {
+					let answer = ''
+					if (item.answer && item.kind == 'FILL') {
+						try {
+							answer = '';
+							for (let i = 0; i < item.answer.length; i++) {
+								answer += '填空位' + (i + 1) + ':' + item.answer[i].answers.join('、') + '<br>';
+							}
+						} catch (e) {
+							console.log('answer', answer, e);
+							return value;
+						}
+					}
+					return answer
+				}
+			},
+		},
+		onLoad(e) {
+			this.ad = this.utils.getData('ad')
+			console.log('ad data', this.ad)
+			// 插屏广告
+			if (this.ad.question_search_cp_open == 1) {
+				this.adUtils.interstitial.load(this.ad.question_search_cp)
+				this.adUtils.interstitial.show()
+			}
+			if (e.keyword) {
+				this.keyword = e.keyword
+			}
+		},
+		async onReachBottom() {
+			console.log("onReachBottom")
+			if (this.has_more) {
+				this.current_page++
+				this.search()
+			}
+		},
+		methods: {
+			search() {
+				if (!this.keyword) {
+					uni.showToast({
+						title: '请输入关键词',
+						icon: 'none'
+					})
+					return
+				}
+
+				this.loadFlag = 'loading'
+				this.has_more = true
+				this.http('question/search', {
+					keyword: this.keyword,
+					page: this.current_page
+				}, 'post').then(res => {
+					console.log('search res', res)
+					if (res.code == 0) {
+						uni.showToast({
+							title: res.msg,
+							icon: 'none'
+						})
+						return
+					} else {
+						this.list = res.data.list.data //this.list.concat(res.data.list.data)
+
+						this.current_page = res.data.list.current_page
+						this.has_more = res.data.list.has_more
+						this.loadFlag = !this.has_more ? 'over' : ''
+						this.showNodata = !this.list.length
+					}
+				})
+			},
+			inputSearch(e) {
+				console.log('input e', e)
+				this.keyword = e.detail.value
+			},
+			// 试题标题
+			formatRichtext(text) {
+				if (text) {
+					return this.utils.formatRichText(text)
+				}
+				return '无'
+			},
+			// 选项图片yulan
+			previewOptionImage(src) {
+				if (!src) {
+					return
+				}
+				
+				uni.previewImage({
+					current: 0,
+					urls: [src]
+				});
+			}
+			
+		}
+	}
 </script>
 
 <style>
-.question-list {
-  padding-bottom: 20rpx;
-}
-
-.question-card {
-  margin: 20rpx 0;
-}
-
-.options {
-  margin: 20rpx 0;
-  line-height: 50rpx;
-}
-
-.explain {
-  margin: 20rpx 0;
-  padding: 15rpx;
-  padding-top: 0rpx;
-}
-
-.margin-top-10 {
-  margin-top: 10px;
-}
-
-.margin-top-100 {
-  margin-top: 100%;
-}
-
-.option-img {
-  width: 100rpx;
-  height: 100rpx;
-}
+	.question-list {
+		padding-bottom: 20rpx;
+	}
+
+	.question-card {
+		margin: 20rpx 0;
+	}
+
+	.options {
+		margin: 20rpx 0;
+		line-height: 50rpx;
+	}
+
+	.explain {
+		margin: 20rpx 0;
+		padding: 15rpx;
+		padding-top: 0rpx;
+	}
+
+	.margin-top-10 {
+		margin-top: 10px;
+	}
+
+	.margin-top-100 {
+		margin-top: 100%;
+	}
+	
+	.option-img {
+		width: 100rpx;
+		height: 100rpx;
+	}
 </style>

+ 155 - 162
addons/exam/uniapp/pages/train/index.vue

@@ -1,170 +1,163 @@
 <template>
-  <view>
-    <view class="card-view">
-      <image src="../../static/img/train-banner1.png" mode="aspectFill" style="width: 100%;"></image>
-    </view>
-
-    <view class="card-view">
-      <!-- <tui-divider width="80%" gradual>选择要练习的题目类型</tui-divider> -->
-
-      <view class="margin">
-        <tn-radio-group activeColor="#5677fc" v-model="mode">
-          <tn-radio name="normal">正常模式</tn-radio>
-          <tn-radio name="memory">记忆模式</tn-radio>
-          <tn-radio name="random">随机模式</tn-radio>
-        </tn-radio-group>
-      </view>
-
-      <view class="margin-left text-sm tn-color-gray">
-        <view v-if="mode == 'normal'">* 按顺序出题,不记忆当前做题题标</view>
-        <view v-if="mode == 'memory'">* 按顺序出题,记忆当前做题题标,下次进入直接跳至题标</view>
-        <view v-if="mode == 'random'">* 随机出题,不记忆当前做题题标</view>
-      </view>
-
-      <tui-cascade-selection height="200px" :itemList="cateList" @complete="complete" text="请选择题库"
-                             :defaultItemList="defaultCateList"></tui-cascade-selection>
-    </view>
-
-    <view style="width:90%; margin: 10rpx auto;" class="padding-bottom-xl">
-      <tui-button shape="circle" shadow bold preventClick :disabled="questionCount == 0" @click="goTrain">{{ btnText }}</tui-button>
-    </view>
-
-    <login ref="login" v-on:succ="getCate"></login>
-  </view>
+	<view>
+		<view class="card-view">
+			<image src="../../static/img/train-banner1.png" mode="aspectFill" style="width: 100%;"></image>
+		</view>
+
+		<view class="card-view">
+			<!-- <tui-divider width="80%" gradual>选择要练习的题目类型</tui-divider> -->
+			
+			<view class="margin">
+				<tn-radio-group activeColor="#5677fc" v-model="mode">
+					<tn-radio name="normal">正常模式</tn-radio>
+					<tn-radio name="memory">记忆模式</tn-radio>
+					<tn-radio name="random">随机模式</tn-radio>
+				</tn-radio-group>
+			</view>
+			
+			<tui-cascade-selection height="200px" :itemList="cateList" @complete="complete" text="请选择题库" :defaultItemList="defaultCateList"></tui-cascade-selection>
+		</view>
+
+		<view style="width:90%; margin: 10rpx auto;" class="padding-bottom-xl">
+			<tui-button shape="circle" shadow bold preventClick :disabled="questionCount == 0" @click="goTrain">{{btnText}}</tui-button>
+		</view>
+		
+		<login ref="login" v-on:succ="getCate"></login>
+	</view>
 </template>
 
 <script>
-export default {
-  data () {
-    return {
-      page: '',
-      cateList: [],
-      cateId: 0,
-      cateName: '',
-      questionCount: 0,
-      btnText: '开始练习',
-      mode: 'normal',
-      defaultCateList: []
-    }
-  },
-  onLoad (e) {
-    this.page = e?.page
-    this.getCate()
-  },
-  methods: {
-    // 获取分类
-    getCate () {
-      this.http('cate/getThree', {
-        kind: 'QUESTION'
-      }).then(res => {
-        this.cateList = res.data
-
-        // 模拟分类点击事件
-        if (this.cateId) {
-          this.complete({
-            value: this.cateId,
-            result: [
-              {
-                text: this.cateName
-              }
-            ]
-          })
-        } else {
-          let defaultCate = this.utils.getData('default_cate-QUESTION')
-          console.log('defaultCate', defaultCate)
-          if (defaultCate) {
-            let lastCate = [...defaultCate.result].pop()
-            console.log('lastCate', lastCate)
-
-            let defaultCateList = []
-            for (var i = 0; i < defaultCate.result.length; i++) {
-              defaultCateList.push(defaultCate.result[i].text)
-            }
-            this.defaultCateList = defaultCateList
-
-            this.cateId = lastCate.value
-            this.cateName = lastCate.text
-
-            this.complete({
-              value: this.cateId,
-              result: [
-                {
-                  text: this.cateName
-                }
-              ]
-            })
-          }
-
-          // let user = this.utils.getData('user')
-          // if (user?.info && user.info?.default_cate_ids) {
-          // 	this.defaultCateList = [...user.info.default_cate_names]
-
-          // 	this.cateId = [...user.info.default_cate_ids].pop()
-          // 	this.cateName = [...user.info.default_cate_names].pop()
-
-          // 	this.complete({
-          // 		value: this.cateId,
-          // 		result: [
-          // 			{
-          // 				text: this.cateName
-          // 			}
-          // 		]
-          // 	})
-          // }
-        }
-
-        // this.defaultCateList = ["消防","灭火救援常识",]
-      })
-    },
-    // 选择分类
-    complete (e) {
-      console.log(e);
-      console.log('您选择的数据为:', e);
-
-      this.cateId = e.value
-      this.cateName = e.result[e.result.length - 1].text
-
-      let params = {
-        cate_id: this.cateId,
-        just_get_count: 1
-      }
-
-      this.$refs.login.afterMethod = () => {
-        this.complete(e)
-      }
-      console.log('afterMethod', this.$refs.login.afterMethod)
-      this.http('question/train', params).then(res => {
-
-        if (res.code == 1) {
-          this.questionCount = res.data.total
-          if (this.questionCount) {
-            this.btnText = '开始练习(' + this.questionCount + '题)'
-          } else {
-            this.btnText = '当前分类无试题'
-          }
-        } else {
-          this.utils.toast(res.msg)
-        }
-      })
-
-    },
-    // 开始练习
-    goTrain () {
-      if (!this.cateId) {
-        this.utils.toast('请先选择练习题分类')
-        return
-      }
-
-      this.page = this.page == 'train' ? 'train' : 'look'
-      this.utils.goto(this.page + '?cateId=' + this.cateId + '&cateName=' + this.cateName + '&mode=' + this.mode)
-    }
-  }
-}
+	export default {
+		data() {
+			return {
+				page: '',
+				cateList: [],
+				cateId: 0,
+				cateName: '',
+				questionCount: 0,
+				btnText: '开始练习',
+				mode: 'normal',
+				defaultCateList: []
+			}
+		},
+		onLoad(e) {
+			this.page = e?.page
+			this.getCate()
+		},
+		methods: {
+			// 获取分类
+			getCate() {
+				this.http('cate/getThree', {
+					kind: 'QUESTION'
+				}).then(res => {
+					this.cateList = res.data
+					
+					// 模拟分类点击事件
+					if (this.cateId) {
+						this.complete({
+							value: this.cateId,
+							result: [
+								{
+									text: this.cateName
+								}
+							]
+						})
+					} else {
+						let defaultCate = this.utils.getData('default_cate-QUESTION')
+						console.log('defaultCate', defaultCate)
+						if (defaultCate) {
+							let lastCate = [...defaultCate.result].pop()
+							console.log('lastCate', lastCate)
+							
+							let defaultCateList = []
+							for (var i = 0; i < defaultCate.result.length; i++) {
+								defaultCateList.push(defaultCate.result[i].text)
+							}
+							this.defaultCateList = defaultCateList
+							
+							this.cateId = lastCate.value
+							this.cateName = lastCate.text
+							
+							this.complete({
+								value: this.cateId,
+								result: [
+									{
+										text: this.cateName
+									}
+								]
+							})
+						}
+						
+						// let user = this.utils.getData('user')
+						// if (user?.info && user.info?.default_cate_ids) {
+						// 	this.defaultCateList = [...user.info.default_cate_names]
+							
+						// 	this.cateId = [...user.info.default_cate_ids].pop()
+						// 	this.cateName = [...user.info.default_cate_names].pop()
+							
+						// 	this.complete({
+						// 		value: this.cateId,
+						// 		result: [
+						// 			{
+						// 				text: this.cateName
+						// 			}
+						// 		]
+						// 	})
+						// }
+					}
+					
+					// this.defaultCateList = ["消防","灭火救援常识",]
+				})
+			},
+			// 选择分类
+			complete(e) {
+				console.log(e);
+				console.log('您选择的数据为:', e);
+
+				this.cateId = e.value
+				this.cateName = e.result[e.result.length - 1].text
+
+				let params = {
+					cate_id: this.cateId,
+					just_get_count: 1,
+				}
+				
+				this.$refs.login.afterMethod = () => {
+					this.complete(e)
+				}
+				console.log('afterMethod', this.$refs.login.afterMethod)
+				this.http('question/train', params).then(res => {
+					
+					if (res.code == 1) {
+						this.questionCount = res.data.total
+						if (this.questionCount) {
+							this.btnText = '开始练习(' + this.questionCount + '题)'
+						} else {
+							this.btnText = '当前分类无试题'
+						}
+					} else {
+						this.utils.toast(res.msg)
+					}
+				})
+
+			},
+			// 开始练习
+			goTrain() {
+				if (!this.cateId) {
+					this.utils.toast('请先选择练习题分类')
+					return
+				}
+				
+				this.page = this.page == 'train' ? 'train' : 'look'
+				this.utils.goto(this.page + '?cateId=' + this.cateId + '&cateName=' + this.cateName + '&mode=' + this.mode)
+			}
+		}
+	}
 </script>
 
 <style>
-.tn-radio__label {
-  color: #333 !important;
-  font-size: 15px !important;
-}
+	.tn-radio__label {
+		color: #333 !important;
+		font-size: 15px !important;
+	}
 </style>

+ 60 - 61
addons/exam/uniapp/pages/train/train.vue

@@ -1,68 +1,67 @@
 <template>
-  <view>
-    <!-- 答题组件 -->
-    <kz-question v-if="questions" mode="TRAINING" :title="cateName" :questions="questions" :questionCount="questionCount" :pageCount="pageCount"
-                 :currentPage="currentPage" :viewMode="mode" @loadQuestion="getQuestion"></kz-question>
-  </view>
+	<view>
+		<!-- 答题组件 -->
+		<kz-question v-if="questions" mode="TRAINING" :title="cateName" :questions="questions" :questionCount="questionCount" :pageCount="pageCount" :currentPage="currentPage" :viewMode="mode" @loadQuestion="getQuestion"></kz-question>
+	</view>
 </template>
 
 <script>
-export default {
-  data () {
-    return {
-      cateId: 0,
-      cateName: '',
-      questions: [],
-      questionCount: 0,
-      pageCount: 1000,
-      currentPage: 0,
-      lastPage: 0,
-      mode: 'normal'
-    }
-  },
-  onLoad (e) {
-    this.cateId = e.cateId
-    this.cateName = e?.cateName
-    this.mode = e.mode
-    this.getQuestion()
-  },
-  methods: {
-    // 获取试题(包括延迟获取)
-    getQuestion (page = 1, callback = null) {
-      console.log('getQuestion', page, this.currentPage, this.lastPage)
-      // 避免重复获取
-      if (page <= this.currentPage || (this.lastPage && page > this.lastPage)) {
-        return
-      }
-
-      // 请求参数
-      let params = {
-        page: page,
-        page_count: this.pageCount,
-        cate_id: this.cateId,
-        mode: this.mode
-      }
-
-      this.http('question/train', params).then(res => {
-        // 默认分页方式
-
-        if (this.mode == 'normal') {
-          this.questionCount = res.data.total
-          this.currentPage = res.data.current_page
-          this.lastPage = res.data.last_page
-          this.questions = this.questions.concat(res.data.data)
-        } else {
-          // 记忆、随机模式暂时只能获取全部题目了
-          this.questions = res.data.data
-        }
-
-        if (callback) {
-          callback()
-        }
-      })
-    }
-  }
-}
+	export default {
+		data() {
+			return {
+				cateId: 0,
+				cateName: '',
+				questions: [],
+				questionCount: 0,
+				pageCount: 100,
+				currentPage: 0,
+				lastPage: 0,
+				mode: 'normal',
+			}
+		},
+		onLoad(e) {
+			this.cateId = e.cateId
+			this.cateName = e?.cateName
+			this.mode = e.mode
+			this.getQuestion()
+		},
+		methods: {
+			// 获取试题(包括延迟获取)
+			getQuestion(page = 1, callback = null) {
+				console.log('getQuestion', page, this.currentPage, this.lastPage)
+				// 避免重复获取
+				if (page <= this.currentPage || (this.lastPage && page > this.lastPage)) {
+					return
+				}
+				
+				// 请求参数
+				let params = {
+					page: page,
+					page_count: this.pageCount,
+					cate_id: this.cateId,
+					mode: this.mode
+				}
+				
+				this.http('question/train', params).then(res => {
+					// 默认分页方式
+				
+					if (this.mode == 'normal') {
+						this.questionCount = res.data.total
+						this.currentPage = res.data.current_page
+						this.lastPage = res.data.last_page
+						this.questions = this.questions.concat(res.data.data)
+					} else {
+						// 记忆、随机模式暂时只能获取全部题目了
+						this.questions = res.data.data
+					}
+					
+					if (callback) {
+						callback()
+					}
+				})
+			}
+		}
+	}
 </script>
 
 <style>

+ 21 - 69
addons/exam/uniapp/pages/user/user.vue

@@ -39,6 +39,26 @@ import { nextTick } from "vue";
 		},
 		onLoad() {
 
+			//判断用户是否登录 , 如果没有登录的话就去登录
+
+
+			this.ad = this.utils.getData('ad')
+			// 插屏广告
+			if (this.ad.my_cp_open == 1) {
+				this.adUtils.interstitial.load(this.ad.my_cp)
+				this.adUtils.interstitial.show()
+			}
+
+			let page = this.utils.getData('page')
+			if (page) {
+				// 页面风格
+				this.pageStyle = page.page_my_style ? page.page_my_style : 'simple'
+				// this.pageStyle = 'simple'
+				// 大嘴鸟
+				this.showMonster = page.page_my_monster_btn == 1
+			}
+
+
 		},
 		onShow() {
 			uni.$on('login_success', (data) => {
@@ -63,9 +83,6 @@ import { nextTick } from "vue";
 		},
 		methods: {
 			ajax() {
-				// 获取配置
-				this.getSetting()
-				
 				if (uni.getStorageSync('token')) {
 					userApi.getUserInfo(this).then(res => {
 						if (res.code == 1) {
@@ -84,69 +101,6 @@ import { nextTick } from "vue";
 				// 	console.log('user', this.user)
 				// }, 500)
 			},
-			
-			loadSetting() {
-				let page = this.utils.getData('page')
-				if (page) {
-					// 页面风格
-					this.pageStyle = page.page_my_style ? page.page_my_style : 'color2'
-					// 大嘴鸟
-					this.showMonster = page.page_my_monster_btn == 1
-				}
-				
-				// #ifdef MP-WEIXIN
-				this.ad = this.utils.getData('ad')
-				// 插屏广告
-				if (this.ad.my_cp_open == 1) {
-					this.adUtils.interstitial.load(this.ad.my_cp)
-					this.adUtils.interstitial.show()
-				}
-				// #endif
-			},
-			
-			getSetting() {
-				this.http('common/index', '', 'get').then(res => {
-					if (!res.code) {
-						uni.showToast({
-							title: '获取数据失败,请刷新重试',
-							icon: 'error'
-						})
-						return
-					}
-				
-					// 系统配置数据
-					let system = res.data.system
-					if (system) {
-						this.system = system
-						uni.setStorageSync('system', system)
-					}
-				
-					// 页面配置数据
-					let page = res.data.page
-					if (page) {
-						this.page = page
-						uni.setStorageSync('page', page)
-					}
-					
-					// #ifdef MP-WEIXIN
-					// 流量主数据
-					let ad = res.data.ad;
-					if (ad) {
-						this.ad = ad
-						uni.setStorageSync('ad', ad);
-					
-						// 插屏广告
-						if (this.ad.index_cp_open == 1) {
-							this.adUtils.interstitial.load(this.ad.index_cp)
-							this.adUtils.interstitial.show()
-						}
-					}
-					// #endif
-					
-					this.loadSetting()
-				})
-			},
-			
 			login() {
 				console.log('on login')
 				if (!uni.getStorageSync('token')) {
@@ -197,9 +151,7 @@ import { nextTick } from "vue";
 											uni.hideLoading();
 										}, 1000);
 									}
-								}),
-								this.getSetting()
-							) :
+								})) :
 							t.cancel && console.log('取消');
 					}
 				});

+ 53 - 54
addons/exam/uniapp/pages/wrong/index.vue

@@ -1,61 +1,60 @@
 <template>
-  <view>
-    <!-- 答题组件 -->
-    <kz-question mode="VIEW" pageType="WRONG" title="我的错题" :questions="list" v-show="!showNodata" :canDeleteWrong="true"
-                 v-on:refresh="refresh"></kz-question>
-
-    <!-- 暂无数据 -->
-    <tui-no-data imgUrl="/static/img/img_noorder_3x.png" v-if="showNodata">暂无数据</tui-no-data>
-
-    <login ref="login" v-on:succ="ajax"></login>
-  </view>
+	<view>
+		<!-- 答题组件 -->
+		<kz-question mode="VIEW" title="我的错题" :questions="list" v-show="!showNodata" :canDeleteWrong="true" v-on:refresh="refresh"></kz-question>
+		
+		<!-- 暂无数据 -->
+		<tui-no-data imgUrl="/static/img/img_noorder_3x.png" v-if="showNodata">暂无数据</tui-no-data>
+		
+		<login ref="login" v-on:succ="ajax"></login>
+	</view>
 </template>
 
 <script>
-export default {
-  data () {
-    return {
-      list: [],
-      total: 0,
-      page: 1,
-      over: 0,
-      questionIds: [],
-      showNodata: false
-    }
-  },
-  onLoad (e) {
-    this.questionIds = e.question_ids
-    this.ajax()
-  },
-  methods: {
-    ajax () {
-      this.http('question/wrongList', { page: this.page, question_ids: this.questionIds }, 'post').then(res => {
-        this.total = res.data.total;
-        // this.choice[2] = res.data.total;
-        var list = res.data.list.data;
-        for (var i in list) {
-          list[i]['log_id'] = list[i]['id']
-          list[i] = Object.assign(list[i], list[i]['question']);
-        }
-
-        this.list = this.list || [];
-        this.list = this.list.concat(list);
-        this.page++;
-
-        if (!res.data.list.has_more) {
-          this.over = 1;
-        }
-
-        this.showNodata = this.list.length == 0
-      });
-    },
-    refresh () {
-      this.page = 1
-      this.list = []
-      this.ajax()
-    }
-  }
-}
+	export default {
+		data() {
+			return {
+				list: [],
+				total: 0,
+				page: 1,
+				over: 0,
+				questionIds: [],
+				showNodata: false,
+			}
+		},
+		onLoad(e) {
+			this.questionIds = e.question_ids
+			this.ajax()
+		},
+		methods: {
+			ajax() {
+				this.http('question/wrongList', {page:this.page, question_ids: this.questionIds}, 'post').then(res => {
+					this.total = res.data.total;
+					// this.choice[2] = res.data.total;
+					var list = res.data.list.data;
+					for (var i in list) {
+						list[i]['log_id'] = list[i]['id']
+						list[i] = Object.assign(list[i], list[i]['question']);
+					}
+					
+					this.list = this.list || [];
+					this.list = this.list.concat(list);
+					this.page++;
+					
+					if (!res.data.list.has_more) {
+						this.over = 1;
+					}
+					
+					this.showNodata = this.list.length == 0
+				});
+			},
+			refresh() {
+				this.page = 1
+				this.list = []
+				this.ajax()
+			}
+		}
+	}
 </script>
 
 <style>

+ 1 - 1
addons/exam/uniapp/static/appInfo.js

@@ -1,5 +1,5 @@
 var app_info = {
   api_host: "https://您的接口域名/",
-  version: "1.7.0",
+  version: "1.5.9",
 };
 module.exports = app_info;

BIN
addons/exam/uniapp/static/components/scroll-top/icon_ad_3x.png


BIN
addons/exam/uniapp/static/components/scroll-top/icon_empty_3x.png


BIN
addons/exam/uniapp/static/img/caidan.png


BIN
addons/exam/uniapp/static/img/delete.png


BIN
addons/exam/uniapp/static/img/jiaojuan.png


BIN
addons/exam/uniapp/static/img/left.png


BIN
addons/exam/uniapp/static/img/rank-01.png


BIN
addons/exam/uniapp/static/img/rank-02.png


BIN
addons/exam/uniapp/static/img/rank-03.png


BIN
addons/exam/uniapp/static/img/right.png


BIN
addons/exam/uniapp/static/img/round_check.png


BIN
addons/exam/uniapp/static/img/round_close.png


BIN
addons/exam/uniapp/static/img/train-banner1.png


BIN
addons/exam/uniapp/static/img/train-banner2.png


BIN
addons/exam/uniapp/static/rank/Intersect.png


BIN
addons/exam/uniapp/static/rank/download.png


BIN
addons/exam/uniapp/static/rank/download2.png


BIN
addons/exam/uniapp/static/rank/icon_one.png


BIN
addons/exam/uniapp/static/rank/icon_three.png


BIN
addons/exam/uniapp/static/rank/icon_two.png


BIN
addons/exam/uniapp/static/rank/one.png


BIN
addons/exam/uniapp/static/rank/three.png


BIN
addons/exam/uniapp/static/rank/two.png


BIN
addons/exam/uniapp/static/tabbar/chaxun.png


BIN
addons/exam/uniapp/static/tabbar/chaxun1.png


BIN
addons/exam/uniapp/static/tabbar/index.png


BIN
addons/exam/uniapp/static/tabbar/index1.png


BIN
addons/exam/uniapp/static/tabbar/kaoshi.png


BIN
addons/exam/uniapp/static/tabbar/kaoshi1.png


BIN
addons/exam/uniapp/static/tabbar/tiku.png


BIN
addons/exam/uniapp/static/tabbar/tiku1.png


BIN
addons/exam/uniapp/static/tabbar/user.png


BIN
addons/exam/uniapp/static/tabbar/user1.png


BIN
addons/exam/uniapp/static/user/login_bottom_bg.jpg


BIN
addons/exam/uniapp/static/user/login_top2.jpg


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików