generated from liutk/owl-admin-base
api 培训考试结果
parent
0a9fe3dc4a
commit
9bf3c45c88
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers\Train;
|
||||
|
||||
use App\Admin\Controllers\AdminController;
|
||||
use App\Admin\Services\Train\PaperService;
|
||||
use Slowlyo\OwlAdmin\Admin;
|
||||
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||
use App\Enums\QuestionCate;
|
||||
|
||||
/**
|
||||
* 成绩管理
|
||||
*/
|
||||
class PaperController extends AdminController
|
||||
{
|
||||
protected string $serviceName = PaperService::class;
|
||||
|
||||
public function list(): Page
|
||||
{
|
||||
$crud = $this->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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Filters;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class TrianPaperFilter extends ModelFilter
|
||||
{
|
||||
public $relations = [
|
||||
'examination' => [
|
||||
'examination_search' => 'search',
|
||||
],
|
||||
'employee' => [
|
||||
'employee_name' => 'name',
|
||||
'employee_search' => 'search',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Services\Train;
|
||||
|
||||
use App\Admin\Filters\TrianPaperFilter;
|
||||
use App\Models\Train\Paper;
|
||||
use App\Admin\Services\BaseService;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\{Validator, Storage};
|
||||
|
||||
class PaperService extends BaseService
|
||||
{
|
||||
protected array $withRelationships = ['examination', 'employee'];
|
||||
|
||||
protected string $modelName = Paper::class;
|
||||
|
||||
protected string $modelFilterName = TrianPaperFilter::class;
|
||||
|
||||
/**
|
||||
* 答题
|
||||
*
|
||||
* @param Paper $paper
|
||||
* @param array $answers [[0, 1], [1], [2, 3]]
|
||||
* @return boolean
|
||||
*/
|
||||
public function answer(Paper $paper, array $answers)
|
||||
{
|
||||
if ($paper->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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Train;
|
||||
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Train\Book;
|
||||
use App\Http\Resources\TrainBookResource;
|
||||
|
||||
/**
|
||||
* 培训-课件
|
||||
*/
|
||||
class BookController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$list = Book::filter($request->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Train;
|
||||
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Train\{Paper, Examination};
|
||||
use App\Http\Resources\{TrainExaminationResource, TrainPaperResource};
|
||||
use App\Enums\ExamStatus;
|
||||
use App\Admin\Services\Train\PaperService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* 培训-考试
|
||||
*/
|
||||
class ExaminationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $this->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TrainBookResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TrainExaminationResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class TrainPaperResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Train;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Traits\HasDateTimeFormatter;
|
||||
use EloquentFilter\Filterable;
|
||||
use App\Models\Employee;
|
||||
|
||||
/**
|
||||
* 培训-试卷
|
||||
*/
|
||||
class Paper extends Model
|
||||
{
|
||||
use HasDateTimeFormatter, Filterable;
|
||||
|
||||
protected $table = 'train_examination_papers';
|
||||
|
||||
protected $fillable = ['examination_id', 'employee_id', 'content', 'mark', 'finished_at'];
|
||||
|
||||
protected $casts = [
|
||||
// [{title: 题目, cate: 类型, options: 选项, score: 分值 , user_score: 得分, user_answer: 回答}]
|
||||
'content' => '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');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use App\Models\Train\Book;
|
||||
use App\Models\Keyword;
|
||||
use App\Enums\BookType;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Model>
|
||||
*/
|
||||
class BookFactory extends Factory
|
||||
{
|
||||
protected $model = Book::class;
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
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>富文本 H1<h1><img src="https://via.placeholder.com/100x100.png" width="100%" height="100%" />',
|
||||
'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"],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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('培训-考卷记录');
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
],
|
||||
]
|
||||
],
|
||||
|
|
|
|||
|
|
@ -28,8 +28,5 @@ class EmployeeSeeder extends Seeder
|
|||
// EmployeeSign::truncate();
|
||||
// EmployeeSignLog::truncate();
|
||||
// EmployeeSignLog::factory()->count(100)->create();
|
||||
|
||||
Question::truncate();
|
||||
(new QuestionFactory)->count(20)->create();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Train\{Book, Question};
|
||||
use Database\Factories\{BookFactory, QuestionFactory};
|
||||
|
||||
class TrainSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// Book::truncate();
|
||||
// (new BookFactory)->count(50)->create();
|
||||
|
||||
Question::truncate();
|
||||
(new QuestionFactory)->count(20)->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -14,4 +14,7 @@ return [
|
|||
'exam_status' => '状态',
|
||||
'question_id' => '考题',
|
||||
'score' => '分值',
|
||||
|
||||
'publish' => '生成考卷',
|
||||
'cancel' => '取消考试',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
|
||||
'employee_id' => '员工',
|
||||
'examination_id' => '考试',
|
||||
'content' => '试卷',
|
||||
'mark' => '得分',
|
||||
'finished_at' => '答题时间',
|
||||
'is_finished' => '是否完成答题',
|
||||
];
|
||||
|
|
@ -10,4 +10,7 @@ return [
|
|||
'cate' => '类型',
|
||||
'is_true' => '正确答案',
|
||||
'text' => '选项',
|
||||
'score' => '分值',
|
||||
'user_score' => '得分',
|
||||
'user_answer' => '回答',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue