diff --git a/app/Admin/Controllers/ArticleController.php b/app/Admin/Controllers/ArticleController.php index 2682ed40..086ee5c4 100644 --- a/app/Admin/Controllers/ArticleController.php +++ b/app/Admin/Controllers/ArticleController.php @@ -29,6 +29,8 @@ class ArticleController extends AdminController // $grid->column('author_name'); $grid->column('subtitle')->limit(20); $grid->column('cover')->image(100, 100); + $grid->column('points'); + $grid->column('likes'); $grid->column('jump_type')->using([ '0'=>__('admin_message.article.jump_type.radio.0'), '1'=>__('admin_message.article.jump_type.radio.1'), @@ -127,7 +129,37 @@ class ArticleController extends AdminController ->saveFullUrl() ->removable(false) ->autoUpload(); + $form->radio('media_type') + ->when(1, function (Form $form) { + $form->multipleImage('media_content1') + ->move('articles/'.Carbon::now()->toDateString()) + ->saveFullUrl() + ->removable(false) + ->autoUpload()->autoSave(false)->customFormat(function ($v) { + $v = []; + if ($this->model()->media_type == 1) { + $v = json_decode($this->model()->media_content, true); + } + return $v; + }); + })->when([2, 3], function (Form $form) { + $form->file('media_content2')->chunked() + ->accept('mp4', 'mp4/*', 'mp3', 'mp3/*') + ->move('articles/media/'.Carbon::now()->toDateString()) + ->saveFullUrl() + ->removable(false) + ->autoUpload()->autoSave(false)->customFormat(function ($v) { + if ($this->model()->media_type == 2) { + $v = json_decode($this->model()->media_content, true)[0]; + } + return $v; + }); + }) + ->options([ + '0'=>'无', '1'=>'轮播图', '2'=>'音频', '3'=>'视频', + ])->default(1); $form->editor('content'); + $form->number('points'); $form->radio('jump_type')->options([ '0'=>__('admin_message.article.jump_type.radio.0'), '1'=>__('admin_message.article.jump_type.radio.1'), @@ -137,6 +169,29 @@ class ArticleController extends AdminController $form->switch('is_show'); $form->switch('is_recommend'); $form->number('sort')->min(0)->default(0); + $form->hidden('media_content'); + + $form->saving(function (Form $form) { + if (!is_null($form->media_type)) { + switch ($form->media_type) { + case 1: + dd(1); + $form->media_content = json_encode($form->media_content1); + break; + case 2: + $form->media_content = json_encode([$form->media_content2]); + break; + case 3: + $form->media_content = json_encode([$form->media_content2]); + break; + default: + $form->media_content = null; + break; + } + $form->deleteInput('media_content1'); + $form->deleteInput('media_content2'); + } + }); $form->display('created_at'); $form->display('updated_at'); diff --git a/app/Endpoint/Api/Http/Controllers/ArticleController.php b/app/Endpoint/Api/Http/Controllers/ArticleController.php new file mode 100644 index 00000000..031215fe --- /dev/null +++ b/app/Endpoint/Api/Http/Controllers/ArticleController.php @@ -0,0 +1,124 @@ +query('cate'); + $key = (string) $request->query('key'); + $categoryId = Arr::get(Article::$cateMap, $cate); + $query = Article::query()->with(['likesInfo'=>function ($q) use ($request) { + return $q->where('user_id', $request->user()->id); + }])->where('is_show', 1); + if ($categoryId) { + $query->where('category_id', $categoryId); + } + if ($key) { + $query->where('title', 'like', '%'.$key.'%'); + } + + $query->orderBy('is_recommend', 'desc'); + $query->orderBy('sort', 'desc'); + $query->orderBy('created_at', 'desc'); + + $list = $query->simplePaginate(PaginatorHelper::resolvePerPage('per_page', 20, 50)); + return ArticleSimpleResource::collection($list); + } + + /** + * 文章详情 + * + * @param [type] $id + * @return void + */ + public function show($id, Request $request) + { + $article = Article::with(['likesInfo'=>function ($q) use ($request) { + return $q->where('user_id', $request->user()->id); + }])->where('is_show', 1)->findOrFail($id); + return ArticleResource::make($article); + } + + /** + * 文章点赞 + * + * @param [type] $id + * @param Request $request + * @return void + */ + public function like($id, Request $request) + { + $article = Article::where('is_show', 1)->findOrFail($id); + if (ArticleLikesLog::where('user_id', $request->user()->id)->where('article_id', $article->id)->exists()) { + throw new BizException('您已点赞过这篇文章'); + } + try { + DB::beginTransaction(); + ArticleLikesLog::create([ + 'article_id' => $article->id, + 'user_id' => $request->user()->id, + ]); + $article->increment('likes'); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException('系统繁忙,请稍后再试'); + } + return response()->noContent(); + } + + /** + * 阅读加积分接口 + * + * @param [type] $id + * @return void + */ + public function read($id, Request $request, PointsService $pointsService) + { + $article = Article::where('is_show', 1)->findOrFail($id); + if ($article->points > 0) { + if (ArticlePointsLog::where('user_id', $request->user()->id)->whereDate('created_at', now())->exists()) { + throw new BizException('您今天已拿过积分了,请明天再来'); + } + try { + DB::beginTransaction(); + ArticlePointsLog::create([ + 'user_id' => $request->user()->id, + 'article_id' => $article->id, + ]); + $pointsService->sendPoints(PointsLog::TYPE_READ, $request->user(), $article->points, '阅读文章送积分'); + DB::commit(); + } catch (Throwable $th) { + DB::rollBack(); + report($th); + throw new BizException('系统繁忙,请稍后再试'); + } + } + return response()->json([ + 'points'=>$article->points, + ]); + } +} diff --git a/app/Endpoint/Api/Http/Resources/ArticleResource.php b/app/Endpoint/Api/Http/Resources/ArticleResource.php new file mode 100644 index 00000000..90102ff9 --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/ArticleResource.php @@ -0,0 +1,30 @@ + $this->title, + 'cover' => $this->cover, + 'subtitle' => $this->subtitle, + 'content'=>$this->content, + 'points'=> $this->points, + 'likes' => $this->likes, + 'like_status' => $this->whenLoaded('likesInfo', $this->likesInfo->count() > 0), + 'media_type'=> $this->media_type, + 'media_content'=> json_decode($this->media_content, true), + 'created_at' => $this->created_at->toDateTimeString(), + ]; + } +} diff --git a/app/Endpoint/Api/Http/Resources/ArticleSimpleResource.php b/app/Endpoint/Api/Http/Resources/ArticleSimpleResource.php new file mode 100644 index 00000000..843d10c9 --- /dev/null +++ b/app/Endpoint/Api/Http/Resources/ArticleSimpleResource.php @@ -0,0 +1,29 @@ + $this->title, + 'subtitle' => $this->subtitle, + 'cover' => $this->cover, + 'points'=> $this->points, + 'likes' => $this->likes, + 'like_status' => $this->whenLoaded('likesInfo', $this->likesInfo->count() > 0), + 'jump_type' => $this->jump_type, + 'jump_link' => (string) $this->jump_link, + 'created_at' => $this->created_at->toDateTimeString(), + ]; + } +} diff --git a/app/Endpoint/Api/routes.php b/app/Endpoint/Api/routes.php index 7caf32fc..de20d769 100644 --- a/app/Endpoint/Api/routes.php +++ b/app/Endpoint/Api/routes.php @@ -4,6 +4,7 @@ use App\Endpoint\Api\Http\Controllers\Account\ChangePasswordController; use App\Endpoint\Api\Http\Controllers\Account\UserController; use App\Endpoint\Api\Http\Controllers\AdController; use App\Endpoint\Api\Http\Controllers\AfterSaleController; +use App\Endpoint\Api\Http\Controllers\ArticleController; use App\Endpoint\Api\Http\Controllers\Auth\LoginController; use App\Endpoint\Api\Http\Controllers\Auth\LogoutController; use App\Endpoint\Api\Http\Controllers\Auth\RegisterController; @@ -78,10 +79,10 @@ Route::group([ //售后订单 Route::get('after-sales', [AfterSaleController::class, 'index']); Route::post('after-sales', [AfterSaleController::class, 'store']); - Route::get('after-sales/{after_sales}', [AfterSaleController::class, 'show']); - Route::put('after-sales/{after_sales}', [AfterSaleController::class, 'update']); - Route::put('after-sales/{after_sales}/agree', [AfterSaleController::class, 'agree']); - Route::delete('after-sales/{after_sales}', [AfterSaleController::class, 'cancel']); + Route::get('after-sales/{after_sale}', [AfterSaleController::class, 'show']); + Route::put('after-sales/{after_sale}', [AfterSaleController::class, 'update']); + Route::put('after-sales/{after_sale}/agree', [AfterSaleController::class, 'agree']); + Route::delete('after-sales/{after_sale}', [AfterSaleController::class, 'cancel']); //消息 Route::get('messages', [MessageController::class, 'index']); @@ -91,5 +92,11 @@ Route::group([ // 我的优惠券 Route::get('coupons', [CouponController::class, 'index']); + + //文章 + Route::get('articles', [ArticleController::class, 'index']); + Route::get('articles/{article}', [ArticleController::class, 'show']); + Route::post('articles/{article}/read', [ArticleController::class, 'read']); + Route::post('articles/{article}/like', [ArticleController::class, 'like']); }); }); diff --git a/app/Models/Article.php b/app/Models/Article.php index 4f693a78..e5c30c1f 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -10,6 +10,16 @@ class Article extends Model { use HasDateTimeFormatter; + protected const CATE_HELP = 1; + protected const CATE_AGREEMENT = 2; + protected const CATE_HEALTH = 3; + + public static $cateMap = [ + 'help'=> self::CATE_HELP, + 'agreement'=> self::CATE_AGREEMENT, + 'health'=> self::CATE_HEALTH, + ]; + protected $casts = [ 'is_show' => 'boolean', 'is_recommend' => 'boolean', @@ -19,4 +29,9 @@ class Article extends Model { return $this->belongsTo(ArticleCategory::class, 'category_id'); } + + public function likesInfo() + { + return $this->hasMany(ArticleLikesLog::class, 'article_id'); + } } diff --git a/app/Models/ArticleLikesLog.php b/app/Models/ArticleLikesLog.php new file mode 100644 index 00000000..03f70194 --- /dev/null +++ b/app/Models/ArticleLikesLog.php @@ -0,0 +1,15 @@ + $type, + 'user_id' => $user->id, + 'points' => $points, + 'old_points'=> $user->userInfo->points, + 'desc' => $desc, + ]); + + if ($points > 0) { + $user->userInfo()->increment('points', abs($points)); + } else { + $user->userInfo()->decrement('points', abs($points)); + } + $res = true; + } + return $res; + } +} diff --git a/database/migrations/2021_12_07_102419_create_coupons_table.php b/database/migrations/2021_12_07_102419_create_coupons_table.php index f7502efd..5814de37 100644 --- a/database/migrations/2021_12_07_102419_create_coupons_table.php +++ b/database/migrations/2021_12_07_102419_create_coupons_table.php @@ -18,7 +18,7 @@ class CreateCouponsTable extends Migration $table->string('name')->comment('优惠券名称'); $table->text('description')->nullable()->comment('优惠券说明'); $table->tinyInteger('type')->default(0)->unsigned()->comment('优惠券类型:1抵扣券,2折扣券'); - $table->unsignedInteger('amount')->default(0)->comment('抵扣金额:分/折扣(100)'); + $table->unsignedBigInteger('amount')->default(0)->comment('抵扣金额:分/折扣(100)'); $table->unsignedBigInteger('threshold')->default(0)->comment('使用门槛金额:分'); $table->unsignedInteger('limit')->default(0)->comment('限量'); $table->unsignedInteger('sent')->default(0)->comment('已送数量'); diff --git a/database/migrations/2021_12_13_173035_create_clicks_table.php b/database/migrations/2021_12_13_173035_create_clicks_table.php new file mode 100644 index 00000000..ecc9a32c --- /dev/null +++ b/database/migrations/2021_12_13_173035_create_clicks_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('用户ID'); + $table->integer('continue_click_times')->default(0)->comment('连续签到次数'); + $table->timestamp('last_click_at')->nullable()->comment('上次签到时间'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('clicks'); + } +} diff --git a/database/migrations/2021_12_13_173042_create_click_logs_table.php b/database/migrations/2021_12_13_173042_create_click_logs_table.php new file mode 100644 index 00000000..e9bb4d52 --- /dev/null +++ b/database/migrations/2021_12_13_173042_create_click_logs_table.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('用户ID'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('click_logs'); + } +} diff --git a/database/migrations/2021_12_13_173136_create_points_logs_table.php b/database/migrations/2021_12_13_173136_create_points_logs_table.php new file mode 100644 index 00000000..6d1de4e1 --- /dev/null +++ b/database/migrations/2021_12_13_173136_create_points_logs_table.php @@ -0,0 +1,36 @@ +id(); + $table->unsignedBigInteger('user_id')->comment('用户ID'); + $table->unsignedTinyInteger('type')->default(0)->comment('类别:1签到,2阅读'); + $table->bigInteger('points')->default(0)->comment('积分:可以为负数'); + $table->unsignedBigInteger('old_points')->default(0)->comment('变动前'); + $table->string('desc')->nullable()->comment('备注'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('points_logs'); + } +} diff --git a/database/migrations/2021_12_13_173239_add_points_to_user_infos_table.php b/database/migrations/2021_12_13_173239_add_points_to_user_infos_table.php new file mode 100644 index 00000000..75e7ac47 --- /dev/null +++ b/database/migrations/2021_12_13_173239_add_points_to_user_infos_table.php @@ -0,0 +1,34 @@ +unsignedBigInteger('points')->default(0)->comment('积分'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('user_infos', function (Blueprint $table) { + // + $table->dropColumn('points'); + }); + } +} diff --git a/database/migrations/2021_12_13_173303_add_points_and_likes_to_articles_table.php b/database/migrations/2021_12_13_173303_add_points_and_likes_to_articles_table.php new file mode 100644 index 00000000..00bf74d3 --- /dev/null +++ b/database/migrations/2021_12_13_173303_add_points_and_likes_to_articles_table.php @@ -0,0 +1,36 @@ +unsignedBigInteger('points')->default(0)->comment('积分'); + $table->unsignedBigInteger('likes')->default(0)->comment('点赞数'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('articles', function (Blueprint $table) { + // + $table->dropColumn('points'); + $table->dropColumn('likes'); + }); + } +} diff --git a/database/migrations/2021_12_13_173803_create_article_likes_logs_table.php b/database/migrations/2021_12_13_173803_create_article_likes_logs_table.php new file mode 100644 index 00000000..9da3cdae --- /dev/null +++ b/database/migrations/2021_12_13_173803_create_article_likes_logs_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('article_id')->column('文章'); + $table->unsignedBigInteger('user_id')->column('用户'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('article_likes_logs'); + } +} diff --git a/database/migrations/2021_12_13_174355_create_article_points_logs_table.php b/database/migrations/2021_12_13_174355_create_article_points_logs_table.php new file mode 100644 index 00000000..9a4cb43a --- /dev/null +++ b/database/migrations/2021_12_13_174355_create_article_points_logs_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('article_id')->column('文章'); + $table->unsignedBigInteger('user_id')->column('用户'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('article_points_logs'); + } +} diff --git a/database/migrations/2021_12_13_192446_add_media_to_articles_table.php b/database/migrations/2021_12_13_192446_add_media_to_articles_table.php new file mode 100644 index 00000000..40ae4413 --- /dev/null +++ b/database/migrations/2021_12_13_192446_add_media_to_articles_table.php @@ -0,0 +1,36 @@ +unsignedTinyInteger('media_type')->default(0)->comment('媒体类型:1轮播图,2音频,3视频'); + $table->json('media_content')->nullable()->comment('媒体内容'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('articles', function (Blueprint $table) { + // + $table->dropColumn('media_type'); + $table->dropColumn('media_content'); + }); + } +} diff --git a/resources/lang/zh_CN/article.php b/resources/lang/zh_CN/article.php index 15c9ec17..93f1cd07 100644 --- a/resources/lang/zh_CN/article.php +++ b/resources/lang/zh_CN/article.php @@ -20,6 +20,11 @@ return [ 'category' => [ 'name' => '分类', ], + 'points' => '积分', + 'likes' => '点赞', + 'media_type'=>'媒体类型', + 'media_content1'=>'轮播图', + 'media_content2'=>'音视频文件', ], 'options' => [ ],