orderByDesc('votes_count') ->orderByDesc('created_at') ->limit(self::PAGE_SIZE) ->get(); // 当前用户已赞同的反馈 ID 集合(前端切换按钮状态用) $myVotedIds = FeedbackVote::where('user_id', Auth::id()) ->whereIn('feedback_id', $feedbacks->pluck('id')) ->pluck('feedback_id') ->toArray(); return view('feedback.index', compact('feedbacks', 'myVotedIds')); } /** * 懒加载更多反馈(JSON API) * 支持按类型筛选(bug / suggestion) * * @param Request $request 含 after_id / type 筛选参数 */ public function loadMore(Request $request): JsonResponse { $afterId = (int) $request->input('after_id', PHP_INT_MAX); $type = $request->input('type'); // bug|suggestion|null(全部) $query = FeedbackItem::with(['replies']) ->where('id', '<', $afterId) ->orderByDesc('votes_count') ->orderByDesc('created_at'); if ($type && in_array($type, ['bug', 'suggestion'])) { $query->ofType($type); } $items = $query->limit(self::PAGE_SIZE)->get(); // 当前用户已赞同的 ID(用于切换按钮状态) $myVotedIds = FeedbackVote::where('user_id', Auth::id()) ->whereIn('feedback_id', $items->pluck('id')) ->pluck('feedback_id') ->toArray(); return response()->json([ 'items' => $this->formatItems($items, $myVotedIds), 'has_more' => $items->count() === self::PAGE_SIZE, ]); } /** * 提交新反馈(Bug报告或功能建议) * * @param Request $request 含 type/title/content 字段 */ public function store(Request $request): JsonResponse { $data = $request->validate([ 'type' => 'required|in:bug,suggestion', 'title' => 'required|string|max:200', 'content' => 'required|string|max:2000', ]); /** @var \App\Models\User $user */ $user = Auth::user(); $item = FeedbackItem::create([ 'user_id' => $user->id, 'username' => $user->username, 'type' => $data['type'], 'title' => $data['title'], 'content' => $data['content'], 'status' => 'pending', ]); return response()->json([ 'status' => 'success', 'message' => '反馈已提交,感谢您的贡献!', 'item' => $this->formatItem($item, false), ]); } /** * 赞同/取消赞同反馈(Toggle 操作) * 每人每条只能赞同一次,再次点击则取消 * 使用数据库事务保证 votes_count 冗余字段与记录一致 * * @param int $id 反馈 ID */ public function vote(int $id): JsonResponse { $feedback = FeedbackItem::findOrFail($id); $userId = Auth::id(); // 不能赞同自己提交的反馈 if ($feedback->user_id === $userId) { return response()->json([ 'status' => 'error', 'message' => '不能赞同自己的反馈', ], 422); } $voted = false; DB::transaction(function () use ($feedback, $userId, &$voted): void { $existing = FeedbackVote::where('feedback_id', $feedback->id) ->where('user_id', $userId) ->first(); if ($existing) { // 已赞同 → 取消赞同 $existing->delete(); $feedback->decrement('votes_count'); $voted = false; } else { // 未赞同 → 新增赞同 FeedbackVote::create([ 'feedback_id' => $feedback->id, 'user_id' => $userId, ]); $feedback->increment('votes_count'); $voted = true; } }); return response()->json([ 'status' => 'success', 'voted' => $voted, 'votes_count' => $feedback->fresh()->votes_count, ]); } /** * 提交补充评论 * id=1 管理员的回复自动标记 is_admin=true(前台特殊展示) * * @param Request $request 含 content 字段 * @param int $id 反馈 ID */ public function reply(Request $request, int $id): JsonResponse { $feedback = FeedbackItem::findOrFail($id); $data = $request->validate([ 'content' => 'required|string|max:1000', ]); /** @var \App\Models\User $user */ $user = Auth::user(); /** @var FeedbackReply $reply */ $reply = null; DB::transaction(function () use ($feedback, $data, $user, &$reply): void { $reply = FeedbackReply::create([ 'feedback_id' => $feedback->id, 'user_id' => $user->id, 'username' => $user->username, 'content' => $data['content'], 'is_admin' => $user->id === 1, ]); $feedback->increment('replies_count'); }); return response()->json([ 'status' => 'success', 'message' => '评论已提交', 'reply' => [ 'id' => $reply->id, 'username' => $reply->username, 'content' => $reply->content, 'is_admin' => $reply->is_admin, 'created_at' => $reply->created_at->diffForHumans(), ], ]); } /** * 删除反馈 * 普通用户:仅24小时内可删除自己的反馈 * 管理员(id=1):任意时间可删除任意反馈 * * @param int $id 反馈 ID */ public function destroy(int $id): JsonResponse { $feedback = FeedbackItem::findOrFail($id); /** @var \App\Models\User $user */ $user = Auth::user(); $isOwner = $feedback->user_id === $user->id; $isAdmin = $user->id === 1; if (! $isOwner && ! $isAdmin) { return response()->json(['status' => 'error', 'message' => '无权删除'], 403); } if ($isOwner && ! $isAdmin && ! $feedback->is_within_24_hours) { return response()->json([ 'status' => 'error', 'message' => '超过 24 小时的反馈无法删除', ], 422); } // 级联删除关联的赞同记录和评论记录 DB::transaction(function () use ($feedback): void { FeedbackVote::where('feedback_id', $feedback->id)->delete(); FeedbackReply::where('feedback_id', $feedback->id)->delete(); $feedback->delete(); }); return response()->json(['status' => 'success', 'message' => '已删除']); } // ═══════════════ 私有辅助方法 ═══════════════ /** * 格式化单条反馈数据(供 JSON 返回给前端) * * @param FeedbackItem $item 反馈实例 * @param bool $voted 当前用户是否已赞同 */ private function formatItem(FeedbackItem $item, bool $voted): array { return [ 'id' => $item->id, 'type' => $item->type, 'type_label' => $item->type_label, 'title' => $item->title, 'content' => $item->content, 'status' => $item->status, 'status_label' => $item->status_label, 'status_color' => $item->status_config['color'], 'admin_remark' => $item->admin_remark, 'votes_count' => $item->votes_count, 'replies_count' => $item->replies_count, 'username' => $item->username, 'created_at' => $item->created_at->diffForHumans(), 'voted' => $voted, 'replies' => ($item->relationLoaded('replies') ? $item->replies : collect())->map(fn ($r) => [ 'id' => $r->id, 'username' => $r->username, 'content' => $r->content, 'is_admin' => $r->is_admin, 'created_at' => $r->created_at->diffForHumans(), ])->values()->toArray(), ]; } /** * 批量格式化反馈数据集合 * * @param \Illuminate\Support\Collection $items * @param array $myVotedIds 当前用户已赞同的 ID 列表 */ private function formatItems(\Illuminate\Support\Collection $items, array $myVotedIds): array { return $items->map(fn (FeedbackItem $item) => $this->formatItem( $item, in_array($item->id, $myVotedIds) ))->values()->toArray(); } }