From 081ce71626dd214cb4931b8b02654ade387a58fe Mon Sep 17 00:00:00 2001 From: panliang <1163816051@qq.com> Date: Tue, 9 Apr 2024 17:40:56 +0800 Subject: [PATCH] =?UTF-8?q?admin=20=E5=9F=B9=E8=AE=AD-=E8=80=83=E8=AF=95?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Train/ExaminationController.php | 98 +++++++++++++++++++ .../Controllers/Train/QuestionController.php | 18 ++-- app/Admin/Filters/TrainExaminationFilter.php | 28 ++++++ .../Services/Train/ExaminationService.php | 54 ++++++++++ app/Admin/routes.php | 2 + app/Enums/ExamStatus.php | 32 ++++++ app/Models/Train/Examination.php | 31 ++++++ app/Models/Train/Question.php | 4 +- database/factories/QuestionFactory.php | 40 ++++++++ .../2024_04_09_123559_create_train_table.php | 8 +- database/seeders/AdminPermissionSeeder.php | 6 ++ database/seeders/EmployeeSeeder.php | 12 ++- lang/zh_CN/train_examination.php | 17 ++++ 13 files changed, 334 insertions(+), 16 deletions(-) create mode 100644 app/Admin/Controllers/Train/ExaminationController.php create mode 100644 app/Admin/Filters/TrainExaminationFilter.php create mode 100644 app/Admin/Services/Train/ExaminationService.php create mode 100644 app/Enums/ExamStatus.php create mode 100644 app/Models/Train/Examination.php create mode 100644 database/factories/QuestionFactory.php create mode 100644 lang/zh_CN/train_examination.php diff --git a/app/Admin/Controllers/Train/ExaminationController.php b/app/Admin/Controllers/Train/ExaminationController.php new file mode 100644 index 0000000..da3604d --- /dev/null +++ b/app/Admin/Controllers/Train/ExaminationController.php @@ -0,0 +1,98 @@ +baseCRUD() + ->tableLayout('fixed') + ->headerToolbar([ + $this->createButton()->visible(Admin::user()->can('admin.train.examinations.create')), + ...$this->baseHeaderToolBar(), + ]) + ->bulkActions([]) + ->filter($this->baseFilter()->body([ + amis()->GroupControl()->mode('horizontal')->body([ + amisMake()->TextControl()->name('search')->label(__('train_examination.name'))->columnRatio(3)->clearable(), + amisMake()->SelectControl()->options(ExamStatus::options())->name('exam_status')->label(__('train_examination.exam_status'))->columnRatio(3)->clearable(), + ]), + ])) + ->columns([ + amisMake()->TableColumn()->name('id')->label(__('train_examination.id')), + amisMake()->TableColumn()->name('name')->label(__('train_examination.name')), + amisMake()->TableColumn()->name('exam_status')->label(__('train_examination.exam_status'))->set('type', 'mapping')->set('map', ExamStatus::options()), + amisMake()->TableColumn()->name('published_at')->label(__('train_examination.published_at')), + amisMake()->TableColumn()->name('total_questions')->label(__('train_examination.total_questions')), + amisMake()->TableColumn()->name('total_score')->label(__('train_examination.total_score')), + $this->rowActions([ + $this->rowShowButton()->visible(Admin::user()->can('admin.train.examinations.view')), + $this->rowEditButton()->visible(Admin::user()->can('admin.train.examinations.update')), + $this->rowDeleteButton()->visible(Admin::user()->can('admin.train.examinations.delete')), + ]), + ]); + + return $this->baseList($crud); + } + + public function form($edit): Form + { + return $this->baseForm()->title('')->body([ + amisMake()->TextControl()->name('name')->label(__('train_examination.name'))->required(), + amisMake()->TableControl() + ->addable() + ->editable() + ->removable() + ->needConfirm(false) + ->columns([ + amisMake()->SelectControl() + ->options(Question::get()) + ->labelField('title') + ->valueField('id') + ->searchable() + ->name('question_id') + ->label(__('train_examination.question_id')) + ->required(), + amisMake()->NumberControl()->min(0)->step(1)->precision(0)->name('score')->label(__('train_examination.score'))->required(), + ]) + ->name('questions') + ->label(__('train_examination.questions')), + ]); + } + + public function detail(): Form + { + $detail = amisMake()->Property()->items([ + ['label' => __('train_examination.name'), 'content' => '${name}'], + ['label' => __('train_examination.exam_status'), 'content' => amisMake()->Mapping()->map(ExamStatus::options())->name('exam_status')], + ['label' => __('train_examination.published_at'), 'content' => '${published_at}'], + ['label' => __('train_examination.total_questions'), 'content' => '${total_questions}'], + ['label' => __('train_examination.total_score'), 'content' => '${total_score}'], + ['label' => __('train_examination.created_at'), 'content' => '${created_at}'], + ]); + $question = amisMake()->Table()->source('${questions}')->columns([ + amisMake()->TableColumn()->name('title')->label(__('train_question.title')), + amisMake()->TableColumn()->name('cate')->label(__('train_question.cate'))->set('type', 'mapping')->map(QuestionCate::options()), + amisMake()->TableColumn()->name('options')->label(__('train_question.options'))->set('type', 'list')->source('${options}')->listItem([ + 'titleClassName' => 'text-${IF(is_true, "success", "danger")}', + 'title' => '${text}', + ]), + amisMake()->TableColumn()->name('score')->label(__('train_examination.score')), + ]); + return $this->baseDetail()->title('')->body([$detail, amisMake()->Divider(), $question]); + } +} diff --git a/app/Admin/Controllers/Train/QuestionController.php b/app/Admin/Controllers/Train/QuestionController.php index 1f877a2..6c248fa 100644 --- a/app/Admin/Controllers/Train/QuestionController.php +++ b/app/Admin/Controllers/Train/QuestionController.php @@ -21,7 +21,7 @@ class QuestionController extends AdminController $crud = $this->baseCRUD() ->tableLayout('fixed') ->headerToolbar([ - $this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.train.questions.create')), + $this->createButton()->visible(Admin::user()->can('admin.train.questions.create')), ...$this->baseHeaderToolBar(), ]) ->bulkActions([]) @@ -35,10 +35,14 @@ class QuestionController extends AdminController amisMake()->TableColumn()->name('id')->label(__('train_question.id')), amisMake()->TableColumn()->name('title')->label(__('train_question.title')), amisMake()->TableColumn()->name('cate')->label(__('train_question.cate'))->set('type', 'mapping')->set('map', QuestionCate::options()), + amisMake()->TableColumn()->name('options')->label(__('train_question.options'))->set('type', 'list')->source('${options}')->listItem([ + 'titleClassName' => 'text-${IF(is_true, "success", "danger")}', + 'title' => '${text}', + ]), amisMake()->TableColumn()->name('created_at')->label(__('train_book.created_at')), $this->rowActions([ - $this->rowShowTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.train.questions.view')), - $this->rowEditTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.train.questions.update')), + $this->rowShowButton()->visible(Admin::user()->can('admin.train.questions.view')), + $this->rowEditButton()->visible(Admin::user()->can('admin.train.questions.update')), $this->rowDeleteButton()->visible(Admin::user()->can('admin.train.questions.delete')), ]), ]); @@ -69,11 +73,11 @@ class QuestionController extends AdminController { return $this->baseDetail()->title('')->body(amisMake()->Property()->items([ ['label' => __('train_question.title'), 'content' => '${title}'], - ['label' => __('train_question.cate'), 'content' => '${cate}'], + ['label' => __('train_question.cate'), 'content' => amisMake()->Mapping()->map(QuestionCate::options())->name('cate')], ['label' => __('train_question.created_at'), 'content' => '${created_at}'], - ['label' => __('train_question.options'), 'content' => amisMake()->Table()->source('${options}')->columns([ - ['name' => 'text', 'label' => __('train_question.text')], - ['name' => 'is_true', 'label' => __('train_question.is_true'), 'type' => 'status'], + ['label' => __('train_question.options'), 'content' => amisMake()->ListRenderer()->source('${options}')->listItem([ + 'titleClassName' => 'text-${IF(is_true, "success", "danger")}', + 'title' => '${text}', ]), 'span' => 3], ])); } diff --git a/app/Admin/Filters/TrainExaminationFilter.php b/app/Admin/Filters/TrainExaminationFilter.php new file mode 100644 index 0000000..728a7ff --- /dev/null +++ b/app/Admin/Filters/TrainExaminationFilter.php @@ -0,0 +1,28 @@ +where('name', 'like', $condition); + } + + public function examStatus($key) + { + return $this->where('exam_status', $key); + } + + public function dateRange($dates) + { + $dates = explode(',', $dates); + $start = Carbon::createFromTimestamp(data_get($dates, 0, time()))->startOfDay(); + $end = Carbon::createFromTimestamp(data_get($dates, 1, time()))->endOfDay(); + $this->whereBetween('created_at', [$start, $end]); + } +} diff --git a/app/Admin/Services/Train/ExaminationService.php b/app/Admin/Services/Train/ExaminationService.php new file mode 100644 index 0000000..41849c8 --- /dev/null +++ b/app/Admin/Services/Train/ExaminationService.php @@ -0,0 +1,54 @@ +get(); + foreach ($data['questions'] as $question) { + $model = $questionList->firstWhere('id', $question['question_id']); + if ($model) { + // title: 题目, cate: 类型, options: 选项, score: 分值 + $question['title'] = $model->title; + $question['cate'] = $model->cate; + $question['options'] = $model->options; + array_push($questions, $question); + + $totalQuestions++; + $totalScore+=$question['score']; + } + } + $data['questions'] = $questions; + $data['total_questions'] = $totalQuestions; + $data['total_score'] = $totalScore; + } + return $data; + } + + public function publish(Examination $examination) + { + if ($examination->exam_status == ExamStatus::Published) { + return $this->setError('已经发布了'); + } + } +} diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 7b85203..5b7e1bb 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -191,6 +191,8 @@ Route::group([ $router->resource('books', \App\Admin\Controllers\Train\BookController::class); // 题库管理 $router->resource('questions', \App\Admin\Controllers\Train\QuestionController::class); + // 考试管理 + $router->resource('examinations', \App\Admin\Controllers\Train\ExaminationController::class); }); $router->post('agreement/download', [AgreementController::class, 'download'])->name('agreement.download'); diff --git a/app/Enums/ExamStatus.php b/app/Enums/ExamStatus.php new file mode 100644 index 0000000..25877fd --- /dev/null +++ b/app/Enums/ExamStatus.php @@ -0,0 +1,32 @@ +value => '未发布', + self::Published->value => '已发布', + ]; + } + + public function text() + { + return data_get(self::options(), $this->value); + } +} diff --git a/app/Models/Train/Examination.php b/app/Models/Train/Examination.php new file mode 100644 index 0000000..0155e77 --- /dev/null +++ b/app/Models/Train/Examination.php @@ -0,0 +1,31 @@ + 'json', + 'published_at' => 'datetime', + 'exam_status' => \App\Enums\ExamStatus::class, + ]; + + public function modelFilter() + { + return \App\Admin\Filters\TrainExaminationFilter::class; + } +} diff --git a/app/Models/Train/Question.php b/app/Models/Train/Question.php index 4b5ed10..fa2ac3c 100644 --- a/app/Models/Train/Question.php +++ b/app/Models/Train/Question.php @@ -2,7 +2,6 @@ namespace App\Models\Train; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use App\Traits\HasDateTimeFormatter; use EloquentFilter\Filterable; @@ -12,7 +11,7 @@ use EloquentFilter\Filterable; */ class Question extends Model { - use HasFactory, HasDateTimeFormatter, Filterable; + use HasDateTimeFormatter, Filterable; protected $table = 'train_questions'; @@ -20,6 +19,7 @@ class Question extends Model protected $casts = [ 'cate' => \App\Enums\QuestionCate::class, + // [{text: 选项1, is_true: true}] 'options' => 'json', ]; } diff --git a/database/factories/QuestionFactory.php b/database/factories/QuestionFactory.php new file mode 100644 index 0000000..b4b44f5 --- /dev/null +++ b/database/factories/QuestionFactory.php @@ -0,0 +1,40 @@ + + */ +class QuestionFactory extends Factory +{ + protected $model = Question::class; + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $cate = $this->faker->randomElement(QuestionCate::class); + $options = []; + $max = 4; + $index = []; + if ($cate == QuestionCate::Radio) { + $index = [$this->faker->randomElement(range(0, $max- 1))]; + } else if ($cate == QuestionCate::Checkbox) { + $index = $this->faker->randomElements(range(0, $max- 1), $this->faker->numberBetween(2, 4)); + } + for($i = 0; $i < $max; $i++) { + array_push($options, ['text' => $this->faker->word, 'is_true' => in_array($i, $index)]); + } + return [ + 'title' => $this->faker->sentence, + 'cate' => $cate, + 'options' => $options, + ]; + } +} diff --git a/database/migrations/2024_04_09_123559_create_train_table.php b/database/migrations/2024_04_09_123559_create_train_table.php index c59276f..1d524fc 100644 --- a/database/migrations/2024_04_09_123559_create_train_table.php +++ b/database/migrations/2024_04_09_123559_create_train_table.php @@ -3,7 +3,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -use App\Enums\{BookType, QuestionCate}; +use App\Enums\{BookType, QuestionCate, ExamStatus}; return new class extends Migration { @@ -40,10 +40,12 @@ return new class extends Migration Schema::create('train_examinations', function (Blueprint $table) { $table->id(); $table->string('name')->comment('考试名称'); - $table->json('questions')->comment('考题内容[{title: 题目, cate: 类型, options: [选项1, 选项2], score: 分值, answer: 正确选项}]'); + $table->json('questions')->comment('考题内容[{title: 题目, cate: 类型, options: 选项, score: 分值}]'); $table->unsignedInteger('total_questions')->default(0)->comment('总题数'); $table->unsignedInteger('total_score')->default(0)->comment('总分数'); $table->string('remarks')->nullable()->comment('备注'); + $table->timestamp('published_at')->nullable()->comment('发布时间'); + $table->unsignedInteger('exam_status')->default(ExamStatus::None->value)->comment('状态(1: 未发布, 2: 已发布)'); $table->timestamps(); $table->comment('培训-考试记录'); @@ -53,7 +55,7 @@ return new class extends Migration $table->id(); $table->foreignId('examination_id')->comment('考试, train_examinations.id'); $table->foreignId('employee_id')->comment('考生'); - $table->json('content')->comment('考卷内容[{title: 题目, cate: 类型, options: [选项1, 选项2], score: 分值, answer: 正确选项, user_score: 得分, user_answer: 回答}]'); + $table->json('content')->comment('考卷内容[{title: 题目, cate: 类型, options: 选项, score: 分值 , user_score: 得分, user_answer: 回答}]'); $table->unsignedInteger('mark')->nullable()->comment('分数'); $table->timestamp('start_at')->nullable()->comment('答题开始时间'); $table->timestamp('end_at')->nullable()->comment('答题结束时间'); diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index f09b64e..061b39f 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -267,6 +267,12 @@ class AdminPermissionSeeder extends Seeder 'uri' => '/train/questions', 'resource' => true, ], + 'examinations' => [ + 'name' => '考试管理', + 'icon' => '', + 'uri' => '/train/examinations', + 'resource' => true, + ], ] ], 'agreement' => [ diff --git a/database/seeders/EmployeeSeeder.php b/database/seeders/EmployeeSeeder.php index a83c136..015d457 100644 --- a/database/seeders/EmployeeSeeder.php +++ b/database/seeders/EmployeeSeeder.php @@ -6,7 +6,8 @@ use App\Models\Employee; use App\Models\EmployeeSign; use App\Models\EmployeeSignLog; use App\Models\Store; -use Database\Factories\EmployeeFactory; +use App\Models\Train\{Question}; +use Database\Factories\{EmployeeFactory, QuestionFactory}; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\DB; @@ -24,8 +25,11 @@ class EmployeeSeeder extends Seeder // Store::truncate(); // Store::factory()->count(10)->create(); - EmployeeSign::truncate(); - EmployeeSignLog::truncate(); - EmployeeSignLog::factory()->count(100)->create(); + // EmployeeSign::truncate(); + // EmployeeSignLog::truncate(); + // EmployeeSignLog::factory()->count(100)->create(); + + Question::truncate(); + (new QuestionFactory)->count(20)->create(); } } diff --git a/lang/zh_CN/train_examination.php b/lang/zh_CN/train_examination.php new file mode 100644 index 0000000..a4b5f32 --- /dev/null +++ b/lang/zh_CN/train_examination.php @@ -0,0 +1,17 @@ + 'ID', + 'created_at' => '创建时间', + 'updated_at' => '更新时间', + + 'name' => '名称', + 'questions' => '考题内容', + 'total_questions' => '总题数', + 'total_score' => '总分数', + 'remarks' => '备注', + 'published_at' => '发布时间', + 'exam_status' => '状态', + 'question_id' => '考题', + 'score' => '分值', +];