培训管理-课件管理

main
panliang 2024-04-09 14:57:01 +08:00
parent f017c9c5ad
commit 9a9dca2681
11 changed files with 411 additions and 0 deletions

View File

@ -0,0 +1,95 @@
<?php
namespace App\Admin\Controllers\Train;
use App\Admin\Controllers\AdminController;
use App\Admin\Services\Train\BookService;
use Slowlyo\OwlAdmin\Admin;
use Slowlyo\OwlAdmin\Renderers\Form;
use Slowlyo\OwlAdmin\Renderers\Page;
use App\Enums\BookType;
use App\Admin\Components;
/**
* 课件管理
*/
class BookController extends AdminController
{
protected string $serviceName = BookService::class;
public function list(): Page
{
$crud = $this->baseCRUD()
->tableLayout('fixed')
->headerToolbar([
$this->createTypeButton('drawer', 'xl')->visible(Admin::user()->can('admin.train.books.create')),
...$this->baseHeaderToolBar(),
])
->bulkActions([])
->filter($this->baseFilter()->body([
amis()->GroupControl()->mode('horizontal')->body([
amisMake()->SelectControl()->name('category_id')->label(__('train_book.category_id'))
->source(admin_url('api/keywords/tree-list?parent_key=book_category'))
->labelField('name')
->valueField('key')
->columnRatio(3)
->clearable(),
amisMake()->TextControl()->name('search')->label(__('train_book.title'))->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('created_at')->label(__('train_book.created_at')),
$this->rowActions([
$this->rowShowButton()->visible(Admin::user()->can('admin.train.books.view')),
$this->rowEditButton()->visible(Admin::user()->can('admin.train.books.update')),
$this->rowDeleteButton()->visible(Admin::user()->can('admin.train.books.delete')),
]),
]);
return $this->baseList($crud);
}
public function form($edit): Form
{
return $this->baseForm()->data(['type' => BookType::Text->value])->title('')->body([
amisMake()->SelectControl()->name('category_id')->label(__('train_book.category_id'))
->source(admin_url('api/keywords/tree-list?parent_key=book_category'))
->labelField('name')
->valueField('key')
->required(),
amisMake()->TextControl()->name('title')->label(__('train_book.title'))->required(),
amisMake()->ImageControl()->name('cover_image')->label(__('train_book.cover_image')),
amisMake()->TextControl()->name('description')->label(__('train_book.description')),
amisMake()->RadiosControl()->options(BookType::options())->name('type')->label(__('train_book.type')),
Components::make()->fuEditorControl('content', __('train_book.content'))->visibleOn('${type == '.BookType::Text->value.'}'),
amisMake()->FileControl()->name('video')->label(__('train_book.video'))->visibleOn('${type == '.BookType::Video->value.'}'),
amisMake()->FileControl()->multiple()->joinValues(false)->name('files')->label(__('train_book.files'))->visibleOn('${type == '.BookType::File->value.'}'),
]);
}
public function detail(): Form
{
$list = amisMake()->ListRenderer()->source('${files}')->listItem(
amisMake()->ListItem()->body(
amisMake()->Link()->body('${name}')->href('${url}')->blank()
)
);
return $this->baseDetail()->title('')->body(amisMake()->Property()->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],
]));
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Admin\Filters;
use Carbon\Carbon;
use EloquentFilter\ModelFilter;
class TrianBookFilter extends ModelFilter
{
public function search($key)
{
$condition = '%'.$key.'%';
$this->where(fn ($q) => $q->where('title', 'like', $condition)->orWhere('description', 'like', $condition));
}
public function category($key)
{
return $this->where('category_id', $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]);
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Admin\Services\Train;
use App\Admin\Filters\TrianBookFilter;
use App\Models\Train\Book;
use App\Admin\Services\BaseService;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\{Validator, Storage};
class BookService extends BaseService
{
protected array $withRelationships = ['category'];
protected string $modelName = Book::class;
protected string $modelFilterName = TrianBookFilter::class;
public function resloveData($data, $model = null)
{
if (isset($data['cover_image']) && $data['cover_image']) {
$image = $data['cover_image'];
$data['cover_image'] = Str::startsWith($image, ['http://', 'https://']) ? $image : Storage::url($image);
}
if (isset($data['video']) && $data['video']) {
$image = $data['video'];
$data['video'] = Str::startsWith($image, ['http://', 'https://']) ? $image : Storage::url($image);
}
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);
}
$data['files'] = $files;
}
return $data;
}
public function validate($data, $model = null)
{
$createRules = [
'title' => ['required'],
'catgeory_id' => ['required'],
'type' => ['required'],
'files' => ['array'],
];
$updateRules = [
'files' => ['array'],
];
$validator = Validator::make($data, $model ? $updateRules : $createRules);
if ($validator->fails()) {
return $validator->errors()->first();
}
return true;
}
}

View File

@ -181,6 +181,15 @@ Route::group([
$router->resource('ads', \App\Admin\Controllers\AdController::class);
/*
|--------------------------------------------------------------------------
| 培训管理
|--------------------------------------------------------------------------
*/
$router->group(['prefix' => 'train', 'as' => 'train.'], function (Router $router) {
$router->resource('books', \App\Admin\Controllers\Train\BookController::class);
});
$router->post('agreement/download', [AgreementController::class, 'download'])->name('agreement.download');
$router->resource('agreement', AgreementController::class);

View File

@ -0,0 +1,36 @@
<?php
namespace App\Enums;
/**
* 课件类型
*/
enum BookType: int
{
/**
* 文章
*/
case Text = 1;
/**
* 视频
*/
case Video = 2;
/**
* 附件
*/
case File = 3;
public static function options()
{
return [
self::Text->value => '文章',
self::Video->value => '视频',
self::File->value => '附件',
];
}
public function text()
{
return data_get(self::options(), $this->value);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Enums;
/**
* 考题类型
*/
enum QuestionCate: int
{
/**
* 单选
*/
case Radio = 1;
/**
* 多选
*/
case Checkbox = 2;
public static function options()
{
return [
self::Radio->value => '单选',
self::Checkbox->value => '多选',
];
}
public function text()
{
return data_get(self::options(), $this->value);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Models\Train;
use Illuminate\Database\Eloquent\Model;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;
/**
* 培训管理-课件
*/
class Book extends Model
{
use HasDateTimeFormatter, Filterable;
protected $table = 'train_books';
protected $guarded = [];
protected $casts = [
'type' => \App\Enums\BookType::class,
'files' => 'json',
];
public function modelFilter()
{
return \App\Admin\Filters\TrianBookFilter::class;
}
public function category()
{
return $this->belongsTo(\App\Models\Keyword::class, 'category_id', 'key');
}
}

View File

@ -0,0 +1,85 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Enums\{BookType, QuestionCate};
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('train_books', function (Blueprint $table) {
$table->id();
$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->unsignedInteger('type')->default(BookType::Text->value)->comment('课件类型');
$table->text('content')->nullable()->comment('文章内容');
$table->json('video')->nullable()->comment('视频');
$table->json('files')->nullable()->comment('附件');
$table->timestamps();
$table->comment('培训-课件');
});
Schema::create('train_question_banks', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('题库名称');
$table->string('remarks')->nullable()->comment('备注');
$table->timestamps();
$table->comment('培训-题库');
});
Schema::create('train_questions', function (Blueprint $table) {
$table->id();
$table->foreignId('bank_id')->comment('题库');
$table->text('title')->comment('题目');
$table->tinyInteger('cate')->default(QuestionCate::Radio->value)->comment('类型');
$table->json('options')->nullable()->comment('选项[{text: 选项1, is_true: 是否正确答案}]');
$table->timestamps();
$table->comment('培训-考题');
});
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->string('remarks')->nullable()->comment('备注');
$table->timestamps();
$table->comment('培训-考试记录');
});
Schema::create('train_examination_papers', function ($table) {
$table->id();
$table->foreignId('employee_id')->comment('考生');
$table->foreignId('examination_id')->comment('考试, train_examinations.id');
$table->json('content')->comment('考卷内容[{title: 题目, cate: 类型, options: [选项1, 选项2], score: 分值, answer: 正确选项, user_score: 得分, user_answer: 回答}]');
$table->decimal('mark', 12, 2)->nullable()->comment('分数');
$table->timestamp('start_at')->nullable()->comment('答题开始时间');
$table->timestamp('end_at')->nullable()->comment('答题结束时间');
$table->timestamps();
$table->comment('培训-考卷记录');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('train_books');
Schema::dropIfExists('train_questions');
Schema::dropIfExists('train_question_banks');
Schema::dropIfExists('train_examination_papers');
Schema::dropIfExists('train_examinations');
}
};

View File

@ -250,6 +250,19 @@ class AdminPermissionSeeder extends Seeder
],
],
'train' => [
'name' => '培训管理',
'icon' => '',
'uri' => '/train',
'children' => [
'book' => [
'name' => '课件管理',
'icon' => '',
'uri' => '/train/books',
'resource' => true,
]
]
],
'agreement' => [
'name' => '合同管理',
'icon' => '',

View File

@ -94,6 +94,11 @@ class KeywordSeeder extends Seeder
'name' => '报销类型',
'children' => ['门店日常支出', '门店活动支出', '日常费用', '器材费', '差旅费', '车辆类报销', '会议费'],
],
[
'key' => 'book_category',
'name' => '课件分类',
'children' => ['专业知识', '服务知识', '专业技巧', '企业文化'],
]
];
$this->insertKeywors($keywords);

View File

@ -0,0 +1,16 @@
<?php
return [
'id' => 'ID',
'created_at' => '创建时间',
'updated_at' => '更新时间',
'title' => '标题',
'category_id' => '分类',
'cover_image' => '封面图',
'description' => '描述',
'type' => '类型',
'content' => '内容',
'video' => '视频',
'files' => '附件',
];