generated from liutk/owl-admin-base
培训管理-课件管理
parent
f017c9c5ad
commit
9a9dca2681
|
|
@ -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],
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -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' => '',
|
||||
|
|
|
|||
|
|
@ -94,6 +94,11 @@ class KeywordSeeder extends Seeder
|
|||
'name' => '报销类型',
|
||||
'children' => ['门店日常支出', '门店活动支出', '日常费用', '器材费', '差旅费', '车辆类报销', '会议费'],
|
||||
],
|
||||
[
|
||||
'key' => 'book_category',
|
||||
'name' => '课件分类',
|
||||
'children' => ['专业知识', '服务知识', '专业技巧', '企业文化'],
|
||||
]
|
||||
];
|
||||
|
||||
$this->insertKeywors($keywords);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'id' => 'ID',
|
||||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
|
||||
'title' => '标题',
|
||||
'category_id' => '分类',
|
||||
'cover_image' => '封面图',
|
||||
'description' => '描述',
|
||||
'type' => '类型',
|
||||
'content' => '内容',
|
||||
'video' => '视频',
|
||||
'files' => '附件',
|
||||
];
|
||||
Loading…
Reference in New Issue