diff --git a/app/Admin/Controllers/Train/BookController.php b/app/Admin/Controllers/Train/BookController.php index 49d569b..5f79a71 100644 --- a/app/Admin/Controllers/Train/BookController.php +++ b/app/Admin/Controllers/Train/BookController.php @@ -35,14 +35,16 @@ class BookController extends AdminController ->columnRatio(3) ->clearable(), amisMake()->TextControl()->name('search')->label(__('train_book.title'))->columnRatio(3)->clearable(), + amisMake()->SelectControl()->options(BookType::options())->name('type')->label(__('train_book.type'))->columnRatio(3)->clearable(), ]), ])) ->columns([ amisMake()->TableColumn()->name('id')->label(__('train_book.id')), amisMake()->TableColumn()->name('category.name')->label(__('train_book.category_id')), - amisMake()->TableColumn()->name('title')->label(__('train_book.title')), - amisMake()->TableColumn()->name('description')->label(__('train_book.description')), amisMake()->TableColumn()->name('cover_image')->label(__('train_book.cover_image'))->set('type', 'image')->set('width', '80px')->set('height', '60px'), + amisMake()->TableColumn()->name('title')->label(__('train_book.title')), + // amisMake()->TableColumn()->name('description')->label(__('train_book.description')), + amisMake()->TableColumn()->name('type')->label(__('train_book.type'))->set('type', 'mapping')->map(BookType::options()), amisMake()->TableColumn()->name('created_at')->label(__('train_book.created_at')), $this->rowActions([ $this->rowShowButton()->visible(Admin::user()->can('admin.train.books.view')), @@ -79,17 +81,17 @@ class BookController extends AdminController amisMake()->Link()->body('${name}')->href('${url}')->blank() ) ); - return $this->baseDetail()->title('')->body(amisMake()->Property()->items([ + $items = [ ['label' => __('train_book.category_id'), 'content' => '${category.name}'], ['label' => __('train_book.title'), 'content' => '${title}'], ['label' => __('train_book.description'), 'content' => '${description}'], ['label' => __('train_book.cover_image'), 'content' => amisMake()->Image()->name('cover_image')->width('80px')->height('60px')], ['label' => __('train_book.type'), 'content' => amisMake()->Mapping()->map(BookType::options())->name('type')], ['label' => __('train_book.created_at'), 'content' => '${created_at}'], - ['label' => __('train_book.content'), 'content' => Components::make()->fuEditorControl('content', false, 'auto')->static(), 'span' => 3], - ['label' => __('train_book.video'), 'content' => amisMake()->Video()->src('${video}'), 'span' => 3], - // amisMake()->Each()->name('files')->items(amisMake()->Link()->body('${name}')->href('${url}')->blank()) - ['label' => __('train_book.files'), 'content' => $list, 'span' => 3], - ])); + ['label' => __('train_book.content'), 'content' => Components::make()->fuEditorControl('content', false, 'auto')->static(), 'span' => 3, 'visibleOn' => '${type == '.BookType::Text->value.'}'], + ['label' => __('train_book.video'), 'content' => amisMake()->Video()->src('${video}'), 'span' => 3, 'visibleOn' => '${type == '.BookType::Video->value.'}'], + ['label' => __('train_book.files'), 'content' => $list, 'span' => 3, 'visibleOn' => '${type == '.BookType::File->value.'}'], + ]; + return $this->baseDetail()->title('')->body(amisMake()->Property()->items($items)); } } diff --git a/app/Admin/Controllers/Train/ExaminationController.php b/app/Admin/Controllers/Train/ExaminationController.php index da3604d..1171d5c 100644 --- a/app/Admin/Controllers/Train/ExaminationController.php +++ b/app/Admin/Controllers/Train/ExaminationController.php @@ -8,7 +8,9 @@ use Slowlyo\OwlAdmin\Admin; use Slowlyo\OwlAdmin\Renderers\Form; use Slowlyo\OwlAdmin\Renderers\Page; use App\Enums\{ExamStatus, QuestionCate}; -use App\Models\Train\Question; +use App\Models\Train\{Question, Examination}; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; /** * 考试管理 @@ -40,9 +42,39 @@ class ExaminationController extends AdminController 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')), + $this->rowShowButton() + ->visible(Admin::user()->can('admin.train.examinations.view')), + $this->rowEditButton() + ->visible(Admin::user()->can('admin.train.examinations.update')) + ->visibleOn('${exam_status == '.ExamStatus::None->value.'}'), + $this->rowDeleteButton() + ->visible(Admin::user()->can('admin.train.examinations.delete')) + ->visibleOn('${exam_status == '.ExamStatus::None->value.'}'), + amisMake()->DialogAction()->dialog( + amisMake()->Dialog()->title('选择参考员工')->size('lg')->body( + amisMake()->Form()->api('post:' . admin_url('train/examinations/${id}/publish'))->body([ + amisMake()->TransferControl() + ->name('employee_id') + ->source(admin_url('api/employees?enable=1&store_id_gt=0')) + ->joinValues(false) + ->extractValue() + ->labelField('name') + ->valueField('id') + ->searchable() + ->required(), + ]) + ) + ) + ->label(__('train_examination.publish')) + ->level('link') + ->visible(Admin::user()->can('admin.train.examinations.publish')) + ->visibleOn('${exam_status == '.ExamStatus::None->value.'}'), + amisMake()->AjaxAction()->api('post:' . admin_url('train/examinations/${id}/cancel')) + ->label(__('train_examination.cancel')) + ->level('link') + ->confirmText('删除该考试下所有试卷记录, 是否确定?') + ->visible(Admin::user()->can('admin.train.examinations.cancel')) + ->visibleOn('${exam_status == '.ExamStatus::Published->value.'}'), ]), ]); @@ -57,6 +89,7 @@ class ExaminationController extends AdminController ->addable() ->editable() ->removable() + ->showIndex() ->needConfirm(false) ->columns([ amisMake()->SelectControl() @@ -95,4 +128,43 @@ class ExaminationController extends AdminController ]); return $this->baseDetail()->title('')->body([$detail, amisMake()->Divider(), $question]); } + + public function publish($id, Request $request) + { + $request->validate([ + 'employee_id' => 'required' + ]); + $info = Examination::findOrFail($id); + try { + DB::beginTransaction(); + + if (!$this->service->publish($info, $request->input('employee_id'))) { + return $this->response()->fail($this->service->getError()); + } + DB::commit(); + return $this->response()->success(); + } catch (\Exception $e) { + DB::rollBack(); + return $this->response()->fail($e->getMessage()); + } + + } + + public function cancel($id, Request $request) + { + $info = Examination::findOrFail($id); + + try { + DB::beginTransaction(); + + if (!$this->service->cancel($info)) { + return $this->response()->fail($this->service->getError()); + } + DB::commit(); + return $this->response()->success(); + } catch (\Exception $e) { + DB::rollBack(); + return $this->response()->fail($e->getMessage()); + } + } } diff --git a/app/Admin/Controllers/Train/PaperController.php b/app/Admin/Controllers/Train/PaperController.php new file mode 100644 index 0000000..7f59942 --- /dev/null +++ b/app/Admin/Controllers/Train/PaperController.php @@ -0,0 +1,68 @@ +baseCRUD() + ->tableLayout('fixed') + ->headerToolbar([ + ...$this->baseHeaderToolBar(), + ]) + ->bulkActions([]) + ->filter($this->baseFilter()->body([ + amis()->GroupControl()->mode('horizontal')->body([ + amisMake()->TextControl()->name('examination_search')->label(__('train_paper.examination_id'))->columnRatio(3)->clearable(), + amisMake()->TextControl()->name('employee_search')->label(__('train_paper.employee_id'))->columnRatio(3)->clearable(), + ]), + ])) + ->columns([ + amisMake()->TableColumn()->name('id')->label(__('train_paper.id')), + amisMake()->TableColumn()->name('examination.name')->label(__('train_paper.examination_id')), + amisMake()->TableColumn()->name('employee.name')->label(__('train_paper.employee_id')), + amisMake()->TableColumn()->name('mark')->label(__('train_paper.mark')), + amisMake()->TableColumn()->name('finished_at')->label(__('train_paper.finished_at')), + $this->rowActions([ + $this->rowShowButton(), + ]), + ]); + + return $this->baseList($crud); + } + + public function detail(): Form + { + $detail = amisMake()->Property()->column(2)->items([ + ['label' => __('train_paper.examination_id'), 'content' => '${examination.name}'], + ['label' => __('train_paper.employee_id'), 'content' => '${employee.name}'], + ['label' => __('train_paper.mark'), 'content' => '${mark}'], + ['label' => __('train_paper.finished_at'), 'content' => '${finished_at}'], + ]); + $question = amisMake()->Table()->columnsTogglable(false)->source('${content}')->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}', + 'desc' => '${selected ? "已选择" : ""}', + ]), + amisMake()->TableColumn()->name('score')->label(__('train_question.score')), + amisMake()->TableColumn()->name('user_score')->label(__('train_question.user_score')), + ]); + return $this->baseDetail()->title('')->body([$detail, amisMake()->Divider(), $question]); + } +} diff --git a/app/Admin/Filters/EmployeeFilter.php b/app/Admin/Filters/EmployeeFilter.php index 038a413..df14fdc 100644 --- a/app/Admin/Filters/EmployeeFilter.php +++ b/app/Admin/Filters/EmployeeFilter.php @@ -40,7 +40,7 @@ class EmployeeFilter extends ModelFilter public function storeIdGt($key) { - $this->where('store_id', '>', 0); + $this->where('store_id', '>', $key); } public function masterStoreId($key) diff --git a/app/Admin/Filters/TrianBookFilter.php b/app/Admin/Filters/TrianBookFilter.php index 521a521..9c7ea7a 100644 --- a/app/Admin/Filters/TrianBookFilter.php +++ b/app/Admin/Filters/TrianBookFilter.php @@ -18,6 +18,11 @@ class TrianBookFilter extends ModelFilter return $this->where('category_id', $key); } + public function type($key) + { + $this->where('type', $key); + } + public function dateRange($dates) { $dates = explode(',', $dates); diff --git a/app/Admin/Filters/TrianPaperFilter.php b/app/Admin/Filters/TrianPaperFilter.php new file mode 100644 index 0000000..c8d8f54 --- /dev/null +++ b/app/Admin/Filters/TrianPaperFilter.php @@ -0,0 +1,19 @@ + [ + 'examination_search' => 'search', + ], + 'employee' => [ + 'employee_name' => 'name', + 'employee_search' => 'search', + ], + ]; +} diff --git a/app/Admin/Services/Train/BookService.php b/app/Admin/Services/Train/BookService.php index 223c53b..3938ce5 100644 --- a/app/Admin/Services/Train/BookService.php +++ b/app/Admin/Services/Train/BookService.php @@ -20,24 +20,38 @@ class BookService extends BaseService { if (isset($data['cover_image']) && $data['cover_image']) { $image = $data['cover_image']; - $data['cover_image'] = Str::startsWith($image, ['http://', 'https://']) ? $image : Storage::url($image); + $data['cover_image'] = $this->formatUrl($image); } if (isset($data['video']) && $data['video']) { - $image = $data['video']; - $data['video'] = Str::startsWith($image, ['http://', 'https://']) ? $image : Storage::url($image); + $video = $data['video']; + $data['video'] = $this->formatUrl($video); } if (isset($data['files']) && $data['files']) { $files = []; - foreach ($data['files'] as $value) { - $path = data_get($value, 'value'); - $value['url'] = Str::startsWith($path, ['http://', 'https://']) ? $path : Storage::url($path); - array_push($files, $value); + foreach ($data['files'] as $key => $value) { + if (is_array($value)) { + $path = data_get($value, 'value'); + $value['url'] = $this->formatUrl($path); + array_push($files, $value); + } else { + array_push($files, [ + 'id' => $key + 1, + 'name' => $value, + 'url' => $this->formatUrl($value), + 'state' => 'uploaded' + ]); + } } $data['files'] = $files; } return $data; } + protected function formatUrl($str) + { + return Str::startsWith($str, ['http://', 'https://']) ? $str : Storage::url($str); + } + public function validate($data, $model = null) { $createRules = [ diff --git a/app/Admin/Services/Train/ExaminationService.php b/app/Admin/Services/Train/ExaminationService.php index 41849c8..7dce4a4 100644 --- a/app/Admin/Services/Train/ExaminationService.php +++ b/app/Admin/Services/Train/ExaminationService.php @@ -8,6 +8,7 @@ use App\Admin\Services\BaseService; use Illuminate\Support\Str; use Illuminate\Support\Facades\{Validator, Storage}; use App\Enums\ExamStatus; +use App\Models\Employee; class ExaminationService extends BaseService { @@ -45,10 +46,42 @@ class ExaminationService extends BaseService return $data; } - public function publish(Examination $examination) + public function publish(Examination $examination, array $ids) { if ($examination->exam_status == ExamStatus::Published) { return $this->setError('已经发布了'); } + + $questions = []; + foreach($examination->questions as $question) { + $question['options'] = array_map(function ($option) { + $option['selected'] = false; + return $option; + }, $question['options']); + array_push($questions, $question); + } + $employees = Employee::whereIn('id', $ids)->get(); + // 为员工生成考卷 + foreach ($employees as $employee) { + $examination->papers()->create([ + 'employee_id' => $employee->id, + 'content' => $questions, + ]); + } + + $examination->update(['exam_status' => ExamStatus::Published, 'published_at' => now()]); + + return true; + } + + public function cancel(Examination $examination) + { + if ($examination->exam_status == ExamStatus::None) { + return $this->setError('已经取消了'); + } + $examination->papers()->delete(); + $examination->update(['exam_status' => ExamStatus::None, 'published_at' => null]); + + return true; } } diff --git a/app/Admin/Services/Train/PaperService.php b/app/Admin/Services/Train/PaperService.php new file mode 100644 index 0000000..a9be23d --- /dev/null +++ b/app/Admin/Services/Train/PaperService.php @@ -0,0 +1,56 @@ +finished_at) { + return $this->setError('已经答过了'); + } + $content = []; + $mark = 0; + foreach ($paper->content as $index => $item) { + $options = []; + $item['user_answer'] = data_get($answers, $index, []); + $score = $item['score']; + foreach ($item['options'] as $subIndex => $option) { + $option['selected'] = in_array($subIndex, $item['user_answer']); + if ((!$option['is_true'] && $option['selected']) || ($option['is_true'] && !$option['selected'])) { + $score = 0; + } + array_push($options, $option); + } + $item['user_score'] = $score; + $item['options'] = $options; + $item['user_right'] = $score == $item['score']; + array_push($content, $item); + $mark += $score; + } + + $paper->update(['content' => $content, 'mark' => $mark, 'finished_at' => now()]); + + return true; + } + +} \ No newline at end of file diff --git a/app/Admin/routes.php b/app/Admin/routes.php index 5d8118b..69c4be4 100644 --- a/app/Admin/routes.php +++ b/app/Admin/routes.php @@ -198,7 +198,10 @@ Route::group([ // 题库管理 $router->resource('questions', \App\Admin\Controllers\Train\QuestionController::class); // 考试管理 + $router->post('examinations/{id}/publish', [\App\Admin\Controllers\Train\ExaminationController::class, 'publish'])->name('examinations.publish'); + $router->post('examinations/{id}/cancel', [\App\Admin\Controllers\Train\ExaminationController::class, 'cancel'])->name('examinations.cancel'); $router->resource('examinations', \App\Admin\Controllers\Train\ExaminationController::class); + $router->resource('papers', \App\Admin\Controllers\Train\PaperController::class)->only(['index', 'show']); }); $router->post('agreement/download', [AgreementController::class, 'download'])->name('agreement.download'); diff --git a/app/Http/Controllers/Api/Train/BookController.php b/app/Http/Controllers/Api/Train/BookController.php new file mode 100644 index 0000000..73aa9a0 --- /dev/null +++ b/app/Http/Controllers/Api/Train/BookController.php @@ -0,0 +1,30 @@ +all()) + ->orderBy('created_at', 'desc') + ->paginate($request->input('per_page')); + + return TrainBookResource::collection($list); + } + + public function show($id) + { + $info = Book::with(['category'])->findOrFail($id); + + return TrainBookResource::make($info); + } +} diff --git a/app/Http/Controllers/Api/Train/ExaminationController.php b/app/Http/Controllers/Api/Train/ExaminationController.php new file mode 100644 index 0000000..3494967 --- /dev/null +++ b/app/Http/Controllers/Api/Train/ExaminationController.php @@ -0,0 +1,60 @@ +guard()->user(); + $list = Paper::with(['examination']) + ->where('employee_id', $user->id) + ->whereHas('examination', fn($q) => $q->where('exam_status', ExamStatus::Published)) + ->orderBy('created_at', 'desc') + ->paginate($request->input('per_page')); + + return TrainPaperResource::collection($list); + } + + public function show($id) + { + $user = $this->guard()->user(); + $info = Paper::with(['examination'])->where('employee_id', $user->id)->findOrFail($id); + + return TrainPaperResource::make($info); + } + + public function answer($id, Request $request, PaperService $service) + { + $request->validate([ + 'answers' => ['required', 'array'] + ]); + $user = $this->guard()->user(); + $info = Paper::with(['examination'])->where('employee_id', $user->id)->findOrFail($id); + + try { + DB::beginTransaction(); + if (!$service->answer($info, $request->input('answers'))) { + throw new RuntimeException($service->getError()); + } + + DB::commit(); + return TrainPaperResource::make($info); + } catch (\Exception $e) { + DB::rollBack(); + throw new RuntimeException($e->getMessage()); + } + } +} diff --git a/app/Http/Resources/TrainBookResource.php b/app/Http/Resources/TrainBookResource.php new file mode 100644 index 0000000..72f2613 --- /dev/null +++ b/app/Http/Resources/TrainBookResource.php @@ -0,0 +1,31 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'category_id' => $this->category_id, + 'category' => KeywordResource::make($this->whenLoaded('category')), + 'title' => $this->title, + 'cover_image' => $this->cover_image, + 'description' => $this->description, + 'type' => $this->type, + 'content' => $this->content, + 'video' => $this->video, + 'files' => $this->files, + 'created_at' => $this->created_at->timestamp, + ]; + } +} diff --git a/app/Http/Resources/TrainExaminationResource.php b/app/Http/Resources/TrainExaminationResource.php new file mode 100644 index 0000000..1ef1e9f --- /dev/null +++ b/app/Http/Resources/TrainExaminationResource.php @@ -0,0 +1,26 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + // 'questions' => $this->questions, + 'total_questions' => $this->total_questions, + 'total_score' => $this->total_score, + 'published_at' => $this->published_at?->timestamp, + ]; + } +} diff --git a/app/Http/Resources/TrainPaperResource.php b/app/Http/Resources/TrainPaperResource.php new file mode 100644 index 0000000..b0638f1 --- /dev/null +++ b/app/Http/Resources/TrainPaperResource.php @@ -0,0 +1,28 @@ + + */ + public function toArray(Request $request): array + { + return [ + 'id' => $this->id, + 'examination_id' => $this->examination_id, + 'examination' => TrainExaminationResource::make($this->whenLoaded('examination')), + 'employee_id' => $this->employee_id, + 'employee' => EmployeeResource::make($this->whenLoaded('employee')), + 'content' => $this->content, + 'mark' => $this->mark, + 'finished_at' => $this->finished_at?->timestamp + ]; + } +} diff --git a/app/Models/Train/Book.php b/app/Models/Train/Book.php index aee9977..f080eb0 100644 --- a/app/Models/Train/Book.php +++ b/app/Models/Train/Book.php @@ -15,8 +15,8 @@ class Book extends Model protected $table = 'train_books'; - protected $guarded = []; - + protected $fillable = ['category_id', 'title', 'cover_image', 'description', 'type', 'content', 'video', 'files']; + protected $casts = [ 'type' => \App\Enums\BookType::class, 'files' => 'json', diff --git a/app/Models/Train/Examination.php b/app/Models/Train/Examination.php index 0155e77..8c5f6ed 100644 --- a/app/Models/Train/Examination.php +++ b/app/Models/Train/Examination.php @@ -15,7 +15,7 @@ class Examination extends Model protected $table = 'train_examinations'; - protected $guarded = []; + protected $fillable = ['name', 'questions', 'total_questions', 'total_score', 'remarks', 'published_at', 'exam_status']; protected $casts = [ // [{title: 题目, cate: 类型, options: 选项, score: 分值}] @@ -28,4 +28,9 @@ class Examination extends Model { return \App\Admin\Filters\TrainExaminationFilter::class; } + + public function papers() + { + return $this->hasMany(Paper::class, 'examination_id'); + } } diff --git a/app/Models/Train/Paper.php b/app/Models/Train/Paper.php new file mode 100644 index 0000000..34ea136 --- /dev/null +++ b/app/Models/Train/Paper.php @@ -0,0 +1,41 @@ + 'json', + 'finished_at' => 'datetime', + ]; + + public function modelFilter() + { + return \App\Admin\Filters\TrianPaperFilter::class; + } + + public function examination() + { + return $this->belongsTo(Examination::class, 'examination_id'); + } + + public function employee() + { + return $this->belongsTo(Employee::class, 'employee_id'); + } +} diff --git a/database/factories/BookFactory.php b/database/factories/BookFactory.php new file mode 100644 index 0000000..9a2a035 --- /dev/null +++ b/database/factories/BookFactory.php @@ -0,0 +1,37 @@ + + */ +class BookFactory extends Factory +{ + protected $model = Book::class; + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'title' => $this->faker->sentence, + 'category_id' => Keyword::where('parent_key', 'book_category')->inRandomOrder()->value('key'), + 'cover_image' => 'https://via.placeholder.com/100x100.png', + 'description' => $this->faker->paragraph, + 'type' => $this->faker->randomElement(BookType::class), + 'content' => '

富文本 H1

', + 'video' => 'https://qiniu.abcdefg.fun/mp4-1.mp4', + 'files' => [ + ['id' => '1', 'name' => '1.png', 'url' => 'https://qiniu.abcdefg.fun/avatar/avatar.png', 'value' => 'https://qiniu.abcdefg.fun/avatar/avatar.png', "state" => "uploaded"], + ['id' => '2','name' => '2.png', 'url' => 'https://qiniu.abcdefg.fun/avatar/avatar2.png', 'value' => 'https://qiniu.abcdefg.fun/avatar/avatar.png', "state" => "uploaded"], + ], + ]; + } +} 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 1d524fc..eda6643 100644 --- a/database/migrations/2024_04_09_123559_create_train_table.php +++ b/database/migrations/2024_04_09_123559_create_train_table.php @@ -17,10 +17,10 @@ return new class extends Migration $table->string('category_id')->comment('分类(book_category), keywords.key'); $table->string('title')->comment('标题'); $table->string('cover_image')->nullable()->comment('封面图'); - $table->string('description')->nullable()->comment('描述'); + $table->text('description')->nullable()->comment('描述'); $table->unsignedInteger('type')->default(BookType::Text->value)->comment('课件类型'); $table->text('content')->nullable()->comment('文章内容'); - $table->json('video')->nullable()->comment('视频'); + $table->string('video')->nullable()->comment('视频'); $table->json('files')->nullable()->comment('附件'); $table->timestamps(); @@ -57,8 +57,7 @@ return new class extends Migration $table->foreignId('employee_id')->comment('考生'); $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('答题结束时间'); + $table->timestamp('finished_at')->nullable()->comment('答题结束时间'); $table->timestamps(); $table->comment('培训-考卷记录'); diff --git a/database/seeders/AdminPermissionSeeder.php b/database/seeders/AdminPermissionSeeder.php index 926d563..96ce86c 100644 --- a/database/seeders/AdminPermissionSeeder.php +++ b/database/seeders/AdminPermissionSeeder.php @@ -315,6 +315,16 @@ class AdminPermissionSeeder extends Seeder 'icon' => 'material-symbols:checkbook-outline', 'uri' => '/train/examinations', 'resource' => true, + 'children' => [ + 'publish' => '生成考卷', + 'cancel' => '取消考试', + ] + ], + 'papers' => [ + 'name' => '考试结果', + 'icon' => 'material-symbols:checkbook-outline', + 'uri' => '/train/papers', + 'resource' => ['list', 'view'] ], ] ], diff --git a/database/seeders/EmployeeSeeder.php b/database/seeders/EmployeeSeeder.php index 015d457..143d864 100644 --- a/database/seeders/EmployeeSeeder.php +++ b/database/seeders/EmployeeSeeder.php @@ -28,8 +28,5 @@ class EmployeeSeeder extends Seeder // EmployeeSign::truncate(); // EmployeeSignLog::truncate(); // EmployeeSignLog::factory()->count(100)->create(); - - Question::truncate(); - (new QuestionFactory)->count(20)->create(); } } diff --git a/database/seeders/TrainSeeder.php b/database/seeders/TrainSeeder.php new file mode 100644 index 0000000..eab67aa --- /dev/null +++ b/database/seeders/TrainSeeder.php @@ -0,0 +1,23 @@ +count(50)->create(); + + Question::truncate(); + (new QuestionFactory)->count(20)->create(); + } +} diff --git a/lang/zh_CN/train_examination.php b/lang/zh_CN/train_examination.php index a4b5f32..3f467d8 100644 --- a/lang/zh_CN/train_examination.php +++ b/lang/zh_CN/train_examination.php @@ -14,4 +14,7 @@ return [ 'exam_status' => '状态', 'question_id' => '考题', 'score' => '分值', + + 'publish' => '生成考卷', + 'cancel' => '取消考试', ]; diff --git a/lang/zh_CN/train_paper.php b/lang/zh_CN/train_paper.php new file mode 100644 index 0000000..2c613af --- /dev/null +++ b/lang/zh_CN/train_paper.php @@ -0,0 +1,14 @@ + 'ID', + 'created_at' => '创建时间', + 'updated_at' => '更新时间', + + 'employee_id' => '员工', + 'examination_id' => '考试', + 'content' => '试卷', + 'mark' => '得分', + 'finished_at' => '答题时间', + 'is_finished' => '是否完成答题', +]; diff --git a/lang/zh_CN/train_question.php b/lang/zh_CN/train_question.php index b3abf7a..aa73f00 100644 --- a/lang/zh_CN/train_question.php +++ b/lang/zh_CN/train_question.php @@ -10,4 +10,7 @@ return [ 'cate' => '类型', 'is_true' => '正确答案', 'text' => '选项', + 'score' => '分值', + 'user_score' => '得分', + 'user_answer' => '回答', ]; diff --git a/routes/api.php b/routes/api.php index dc63764..50e8d82 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,6 +19,7 @@ Route::get('keyword', [\App\Http\Controllers\Api\KeywordController::class, 'inde // 字典表 Route::get('keywords', [KeywordController::class, 'index']); + Route::group([ 'middleware' => ['auth:api'], ], function () { @@ -95,4 +96,13 @@ Route::group([ Route::get('workflow/{id}/logs', [\App\Http\Controllers\Api\WorkflowController::class, 'logs']); Route::post('workflow/{id}/check', [\App\Http\Controllers\Api\WorkflowController::class, 'check']); Route::post('workflow/{id}/cancel', [\App\Http\Controllers\Api\WorkflowController::class, 'cancel']); + + // 培训-课件 + Route::get('train/books', [\App\Http\Controllers\Api\Train\BookController::class, 'index']); + Route::get('train/books/{id}', [\App\Http\Controllers\Api\Train\BookController::class, 'show']); + + // 培训-考试 + Route::get('train/examinations', [\App\Http\Controllers\Api\Train\ExaminationController::class, 'index']); + Route::get('train/examinations/{id}', [\App\Http\Controllers\Api\Train\ExaminationController::class, 'show']); + Route::post('train/examinations/{id}/answer', [\App\Http\Controllers\Api\Train\ExaminationController::class, 'answer']); });