- 任命/撤销事件增加 type 字段区分类型 - 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息 - 撤销:灰色弹窗 + 灰色系统消息,无礼花 - 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏 - 系统消息加随机鼓励语(各5条轮换) - ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds) - 用户名片折叠优化:管理员视野、职务履历均可折叠 - 管理操作 + 职务操作合并为「🔧 管理操作」折叠区 - 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
299 lines
9.6 KiB
PHP
299 lines
9.6 KiB
PHP
<?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();
|
||
}
|
||
}
|