Files
chatroom/app/Http/Controllers/FeedbackController.php
lkddi 5f30220609 feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型
- 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息
- 撤销:灰色弹窗 + 灰色系统消息,无礼花
- 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏
- 系统消息加随机鼓励语(各5条轮换)
- ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds)
- 用户名片折叠优化:管理员视野、职务履历均可折叠
- 管理操作 + 职务操作合并为「🔧 管理操作」折叠区
- 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
2026-02-28 23:44:38 +08:00

299 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 文件功能:用户反馈前台控制器
* 对应独立页面 /feedback处理用户提交 Bug报告/功能建议、
* 赞同Toggle、补充评论、删除等操作
* 所有写操作均需登录chat.auth 中间件保护)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers;
use App\Models\FeedbackItem;
use App\Models\FeedbackReply;
use App\Models\FeedbackVote;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
class FeedbackController extends Controller
{
/** 每次懒加载的条数 */
private const PAGE_SIZE = 10;
/**
* 用户反馈列表页SSR首屏
* 预加载按赞同数倒序的 10 条反馈
*/
public function index(): View
{
$feedbacks = FeedbackItem::with(['replies'])
->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<int, FeedbackItem> $items
* @param array<int> $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();
}
}