diff --git a/app/Admin/Components.php b/app/Admin/Components.php index 50b81a2..925fdba 100644 --- a/app/Admin/Components.php +++ b/app/Admin/Components.php @@ -51,12 +51,13 @@ class Components extends BaseRenderer { ->options([ "menubar" => false, "min_height" => 500, - "toolbar" => "undo redo | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | image link | outdent indent | numlist bullist | forecolor backcolor removeformat | charmap emoticons", + "plugins" => 'image link table lists charmap emoticons code table fullscreen', + "toolbar" => "undo redo | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | image link | outdent indent | numlist bullist table | forecolor backcolor removeformat | charmap emoticons | code fullscreen", + "toolbar_mode" => "wrap", "help_tabs" => [], "convert_urls" => false, - "quickbars_selection_toolbar" => "fontsize forecolor backcolor", - "toolbar_mode" => "wrap", - "quickbars_insert_toolbar" => false, + // "quickbars_selection_toolbar" => "fontsize forecolor backcolor", + // "quickbars_insert_toolbar" => false, ]) ->receiver(admin_url('upload_rich')) ->name($name) diff --git a/app/Casts/Storage.php b/app/Casts/Storage.php new file mode 100644 index 0000000..8705ee0 --- /dev/null +++ b/app/Casts/Storage.php @@ -0,0 +1,51 @@ +disk = $disk; + } + + /** + * Cast the given value. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @param mixed $value + * @param array $attributes + * @return array + */ + public function get($model, $key, $value, $attributes) + { + return $value ? (Str::startsWith($value, ['http://', 'https://']) ? $value : StorageFacades::disk($this->disk)->url($value)) : ''; + } + + /** + * Prepare the given value for storage. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @param array $value + * @param array $attributes + * @return string + */ + public function set($model, $key, $value, $attributes) + { + return $value; + } +} \ No newline at end of file diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 5552f78..91208d0 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -44,6 +44,7 @@ class Handler extends ExceptionHandler */ public function register() { + // Laravel9.x 无法捕获 ModelNotFoundException, 在父类 prepareException 方法中被转换为 Symfony\Component\HttpKernel\Exception\NotFoundHttpException $this->renderable(function (\Illuminate\Database\Eloquent\ModelNotFoundException $e, Request $request) { if ($request->acceptsJson()) { return response()->json(['status' => 404, 'msg' => '资源不存在', 'data' => null], 200); diff --git a/app/Http/Controllers/Api/ArticleController.php b/app/Http/Controllers/Api/ArticleController.php index e8cae18..e917ba0 100644 --- a/app/Http/Controllers/Api/ArticleController.php +++ b/app/Http/Controllers/Api/ArticleController.php @@ -32,6 +32,8 @@ class ArticleController extends Controller $list = $query->paginate($request->input('per_page')); + + return $this->json(ArticleResource::collection($list)); } diff --git a/app/Http/Controllers/Api/RegionController.php b/app/Http/Controllers/Api/RegionController.php new file mode 100644 index 0000000..b501f93 --- /dev/null +++ b/app/Http/Controllers/Api/RegionController.php @@ -0,0 +1,64 @@ +show()->sort()->get(); + + return $this->json(RegionCategoryResource::collection($list)); + } + + public function index(Request $request) + { + $query = Region::with(['currentPlant'])->filter($request->all(), RegionFilter::class)->sort()->show(); + + $list = $query->paginate($request->input('per_page')); + + return $this->json(RegionResource::collection($list)); + } + + public function show($id) + { + $info = Region::with(['currentPlant'])->show()->findOrFail($id); + + return $this->json(RegionResource::make($info)); + } + + public function plants($id, Request $request) + { + $info = Region::show()->findOrFail($id); + + $query = $info->plants(); + $per = $request->input('per_page'); + if ($per == -1) { + $list = $query->get(); + } else { + $list = $query->paginate($per); + } + + return $this->json(RegionPlantResource::collection($list)); + } + + public function harvests($id, Request $request) + { + $info = Region::show()->findOrFail($id); + $query = $info->harvests(); + $per = $request->input('per_page'); + if ($per == -1) { + $list = $query->get(); + } else { + $list = $query->paginate($per); + } + + return $this->json(PlantHarvestResource::collection($list)); + } +} diff --git a/app/Http/Resources/PlantHarvestResource.php b/app/Http/Resources/PlantHarvestResource.php new file mode 100644 index 0000000..69d0b3b --- /dev/null +++ b/app/Http/Resources/PlantHarvestResource.php @@ -0,0 +1,27 @@ + $this->id, + 'plant_id' => $this->plant_id, + 'region_id' => $this->region_id, + 'director' => $this->director, + 'area' => $this->area, + 'output' => $this->output, + 'harvest_at' => $this->harvest_at ? $this->harvest_at->timestamp : null, + ]; + } +} diff --git a/app/Http/Resources/RegionCategoryResource.php b/app/Http/Resources/RegionCategoryResource.php new file mode 100644 index 0000000..07dd3fd --- /dev/null +++ b/app/Http/Resources/RegionCategoryResource.php @@ -0,0 +1,24 @@ + $this->id, + 'name' => $this->name, + 'icon' => $this->icon, + 'description' => $this->description, + ]; + } +} diff --git a/app/Http/Resources/RegionPlantResource.php b/app/Http/Resources/RegionPlantResource.php new file mode 100644 index 0000000..ad6b1d5 --- /dev/null +++ b/app/Http/Resources/RegionPlantResource.php @@ -0,0 +1,27 @@ + $this->id, + 'plant_name' => $this->plant_name, + 'director' => $this->director, + 'area' => $this->area, + 'start_at' => $this->start_at ? $this->start_at->timestamp : null, + 'end_at' => $this->end_at ? $this->end_at->timestamp : null, + 'plant_state' => $this->plant_state, + ]; + } +} diff --git a/app/Http/Resources/RegionResource.php b/app/Http/Resources/RegionResource.php new file mode 100644 index 0000000..4f79e8f --- /dev/null +++ b/app/Http/Resources/RegionResource.php @@ -0,0 +1,29 @@ + $this->id, + 'name' => $this->name, + 'cover' => $this->cover, + 'director' => $this->director, + 'area' => $this->area, + 'description' => $this->description, + 'category_id' => $this->category_id, + 'category' => RegionCategoryResource::make($this->whenLoaded('category')), + 'current_plant' => RegionPlantResource::make($this->whenLoaded('currentPlant')), + ]; + } +} diff --git a/app/Models/Article.php b/app/Models/Article.php index c45a8d1..fd61ca6 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -2,12 +2,10 @@ namespace App\Models; -use Illuminate\Support\Str; +use App\Casts\Storage; use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Storage; -use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Factories\HasFactory; + /** * 文章 */ @@ -23,15 +21,9 @@ class Article extends Model 'published_at' => 'datetime:Y-m-d H:i:s', 'is_enable' => 'boolean', 'is_recommend' => 'boolean', + 'cover' => Storage::class, ]; - protected function cover(): Attribute - { - return Attribute::make( - get: fn($value) => $value ? (Str::startsWith($value, ['http://', 'https://']) ? $value : Storage::url($value)) : '', - ); - } - public function category() { return $this->belongsTo(ArticleCategory::class, 'category_id'); diff --git a/app/Models/Banner.php b/app/Models/Banner.php index 467a09f..b6d6258 100644 --- a/app/Models/Banner.php +++ b/app/Models/Banner.php @@ -2,13 +2,11 @@ namespace App\Models; +use App\Casts\Storage; use Illuminate\Support\Str; use App\Filters\BannerFilter; use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\Storage; -use Illuminate\Database\Eloquent\Casts\Attribute; -use Illuminate\Database\Eloquent\Factories\HasFactory; /** * 广告图 @@ -25,15 +23,9 @@ class Banner extends Model 'published_at' => 'datetime:Y-m-d H:i:s', 'is_enable' => 'boolean', 'link_config' => 'json', + 'picture' => Storage::class, ]; - protected function picture(): Attribute - { - return Attribute::make( - get: fn($value) => $value ? (Str::startsWith($value, ['http://', 'https://']) ? $value : Storage::url($value)) : '', - ); - } - public function place() { return $this->belongsTo(BannerPlace::class, 'place_id'); diff --git a/app/Models/Region.php b/app/Models/Region.php index e95d794..c255551 100644 --- a/app/Models/Region.php +++ b/app/Models/Region.php @@ -2,9 +2,14 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Model; +use App\Casts\Storage; use EloquentFilter\Filterable; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Factories\HasFactory; +/** + * 实验田 + */ class Region extends Model { use Filterable; @@ -14,11 +19,27 @@ class Region extends Model 'sort', 'is_recommend','is_enable' ]; + protected $casts = [ + 'cover' => Storage::class, + ]; + protected function serializeDate(\DateTimeInterface $date){ return $date->format('Y-m-d H:i:s'); } - public function category(){ + public function scopeShow($q) + { + return $q->where('is_enable', 1); + } + + public function scopeSort($q) + { + return $q->orderBy('sort', 'desc')->latest('id'); + } + + // 试验田分类 + public function category() + { return $this->belongsTo(RegionCategory::class, 'category_id'); } @@ -26,6 +47,23 @@ class Region extends Model return $this->belongsToMany(MonitorMode::class, RegionMonitor::class, 'region_id', 'monitor_id')->withTimestamps(); } + // 种植记录 + public function plants() + { + return $this->hasMany(RegionPlantLog::class, 'region_id')->orderBy('start_at', 'desc'); + } + + // 当前种植 + public function currentPlant() + { + return $this->hasOne(RegionPlantLog::class, 'region_id')->orderBy('start_at', 'desc'); + } + + // 收货记录 + public function harvests() + { + return $this->hasMany(PlantHarvestLog::class, 'region_id')->orderBy('harvest_at', 'desc'); + } public static function regionTabConfig($region = null){ $tabs = [ diff --git a/app/Models/RegionCategory.php b/app/Models/RegionCategory.php index 58fe2cb..1ac3b3a 100644 --- a/app/Models/RegionCategory.php +++ b/app/Models/RegionCategory.php @@ -5,13 +5,21 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use EloquentFilter\Filterable; +use App\Casts\Storage; +/** + * 区域分类 + */ class RegionCategory extends Model { use HasFactory; use Filterable; protected $fillable = ['name', 'icon', 'description', 'parent_id', 'level', 'sort', 'path', 'is_enable']; + + protected $casts = [ + 'icon' => Storage::class, + ]; protected function serializeDate(\DateTimeInterface $date){ return $date->format('Y-m-d H:i:s'); @@ -41,4 +49,14 @@ class RegionCategory extends Model { return $this->belongsTo(self::class, 'parent_id'); } + + public function scopeShow($q) + { + return $q->where('is_enable', 1); + } + + public function scopeSort($q) + { + return $q->orderBy('sort', 'desc')->latest('id'); + } } diff --git a/app/Models/RegionPlantLog.php b/app/Models/RegionPlantLog.php index 2a78026..089b6e9 100644 --- a/app/Models/RegionPlantLog.php +++ b/app/Models/RegionPlantLog.php @@ -6,6 +6,9 @@ use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; +/** + * 种植记录 + */ class RegionPlantLog extends Model { use Filterable; @@ -19,13 +22,15 @@ class RegionPlantLog extends Model //1进行中,2已结束 protected function plantState():Attribute - { + { return Attribute::make( - get:fn($value, $attributes) => $attributes['end_at'] ? 2:1, + get: fn($value) => $this->end_at ? 2 : 1, ); } - public function harvestes(){ + // 收获记录 + public function harvestes() + { return $this->hasMany(PlantHarvestLog::class, 'plant_id'); } } diff --git a/app/Services/Admin/CropHarvestService.php b/app/Services/Admin/CropHarvestService.php index cd0d463..e3747d7 100644 --- a/app/Services/Admin/CropHarvestService.php +++ b/app/Services/Admin/CropHarvestService.php @@ -2,7 +2,7 @@ namespace App\Services\Admin; -use App\Models\PlantHarvestLog; +use App\Models\{PlantHarvestLog, RegionPlantLog}; use App\Filters\Admin\PlantHarvestLogFilter; use Illuminate\Support\Arr; use Throwable; @@ -21,7 +21,7 @@ class CropHarvestService extends BaseService public function store($data): bool { $columns = $this->getTableColumns(); - $model = $this->getModel(); + $model = $this->getModel(); foreach ($data as $k => $v) { if (!in_array($k, $columns)) { @@ -29,6 +29,9 @@ class CropHarvestService extends BaseService } $model->setAttribute($k, $v); } + if ($model->plant_id) { + $model->region_id = RegionPlantLog::where('id', $model->plant_id)->value('region_id'); + } try{ DB::beginTransaction(); diff --git a/database/migrations/2023_03_21_111915_create_plant_harvest_logs_table.php b/database/migrations/2023_03_21_111915_create_plant_harvest_logs_table.php index 74170f4..c15fdbf 100644 --- a/database/migrations/2023_03_21_111915_create_plant_harvest_logs_table.php +++ b/database/migrations/2023_03_21_111915_create_plant_harvest_logs_table.php @@ -15,6 +15,7 @@ return new class extends Migration { Schema::create('plant_harvest_logs', function (Blueprint $table) { $table->id(); + $table->unsignedBigInteger('region_id'); $table->unsignedBigInteger('plant_id'); $table->string('director')->nullable()->comment('负责人'); $table->decimal('area')->default(0.00)->comment('面积/平米'); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d198219..0b55628 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -20,5 +20,6 @@ class DatabaseSeeder extends Seeder $this->call(KeywordSeeder::class); $this->call(BannerSeeder::class); $this->call(SettingSeeder::class); + $this->call(RegionCategorySeeder::class); } } diff --git a/routes/api.php b/routes/api.php index 09872b0..c58e131 100644 --- a/routes/api.php +++ b/routes/api.php @@ -2,6 +2,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use App\Http\Controllers\Api\{BannerController, AdminNoticeController, ArticleController, RegionController}; /* |-------------------------------------------------------------------------- @@ -14,12 +15,18 @@ use Illuminate\Support\Facades\Route; | */ -Route::get('banner', [\App\Http\Controllers\Api\BannerController::class, 'index']); +Route::get('banner', [BannerController::class, 'index']); -Route::get('notice', [\App\Http\Controllers\Api\AdminNoticeController::class, 'index']); -Route::get('notice/{id}', [\App\Http\Controllers\Api\AdminNoticeController::class, 'show']); +Route::get('notice', [AdminNoticeController::class, 'index']); +Route::get('notice/{id}', [AdminNoticeController::class, 'show']); -Route::get('article/category', [\App\Http\Controllers\Api\ArticleController::class, 'category']); -Route::get('article/tree', [\App\Http\Controllers\Api\ArticleController::class, 'tree']); -Route::get('article', [\App\Http\Controllers\Api\ArticleController::class, 'index']); -Route::get('article/{id}', [\App\Http\Controllers\Api\ArticleController::class, 'show']); +Route::get('article/category', [ArticleController::class, 'category']); +Route::get('article/tree', [ArticleController::class, 'tree']); +Route::get('article', [ArticleController::class, 'index']); +Route::get('article/{id}', [ArticleController::class, 'show']); + +Route::get('region/category', [RegionController::class, 'category']); +Route::get('region', [RegionController::class, 'index']); +Route::get('region/{id}', [RegionController::class, 'show']); +Route::get('region/{id}/plants', [RegionController::class, 'plants']); +Route::get('region/{id}/harvests', [RegionController::class, 'harvests']);