feat: 任命/撤销通知系统 + 用户名片UI优化

- 任命/撤销事件增加 type 字段区分类型
- 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息
- 撤销:灰色弹窗 + 灰色系统消息,无礼花
- 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏
- 系统消息加随机鼓励语(各5条轮换)
- ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds)
- 用户名片折叠优化:管理员视野、职务履历均可折叠
- 管理操作 + 职务操作合并为「🔧 管理操作」折叠区
- 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
This commit is contained in:
2026-02-28 23:44:38 +08:00
parent a599047cf0
commit 5f30220609
80 changed files with 8579 additions and 473 deletions
@@ -0,0 +1,55 @@
<?php
/**
* 文件功能:开发日志表迁移
* 记录功能开发、Bug修复、优化迭代等版本变更历史
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建开发日志表
*/
public function up(): void
{
Schema::create('dev_changelogs', function (Blueprint $table): void {
$table->id();
// 版本号,按日期格式如 2026-02-28,可自由输入
$table->string('version', 30)->comment('版本号,格式: 2026-02-28');
// 日志标题,简洁描述本次更新重点
$table->string('title', 200)->comment('日志标题');
// 类型:新功能/修复/优化/其他
$table->enum('type', ['feature', 'fix', 'improve', 'other'])
->default('feature')
->comment('类型: feature=新功能 fix=修复 improve=优化 other=其他');
// Markdown 格式的详细内容
$table->longText('content')->comment('详细内容(Markdown 格式)');
// 是否已发布(0=草稿,不在前台显示)
$table->boolean('is_published')->default(false)->comment('是否已发布,0=草稿');
// 发布时是否向 Room ID=1 的大厅广播通知
$table->boolean('notify_chat')->default(true)->comment('发布时是否通知 Room 1 大厅');
// 首次发布时间,编辑时不更新
$table->timestamp('published_at')->nullable()->comment('首次发布时间,编辑操作不更新此字段');
$table->timestamps();
// 复合索引:前台查询已发布日志并按时间排序
$table->index(['is_published', 'published_at'], 'idx_published');
});
}
/**
* 回滚:删除开发日志表
*/
public function down(): void
{
Schema::dropIfExists('dev_changelogs');
}
};
@@ -0,0 +1,99 @@
<?php
/**
* 文件功能:用户反馈相关表迁移
* 包含三张关联表:
* - feedback_items:反馈主表(Bug报告/功能建议)
* - feedback_votes:赞同记录(每人每条唯一,支持取消)
* - feedback_replies:补充评论(支持管理员官方回复标记)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建用户反馈相关三张表
*/
public function up(): void
{
// ——— 表1:反馈主表 ———
Schema::create('feedback_items', function (Blueprint $table): void {
$table->id();
// 提交人信息(冗余 username,防止用户改名后记录混乱)
$table->unsignedBigInteger('user_id')->comment('提交人 ID');
$table->string('username', 50)->comment('提交人用户名(提交时快照)');
// 反馈类型
$table->enum('type', ['bug', 'suggestion'])
->comment('类型: bug=缺陷报告 suggestion=功能建议');
// 反馈内容
$table->string('title', 200)->comment('反馈标题(一句话概括)');
$table->text('content')->comment('详细描述内容');
// 处理状态(7种)
$table->enum('status', [
'pending', // 待处理(默认)
'accepted', // 已接受(纳入计划)
'in_progress', // 开发中/修复中
'fixed', // Bug已修复
'done', // 建议已实现
'rejected', // 暂不同意
'shelved', // 已搁置
])->default('pending')->comment('处理状态');
// 管理员回复(前台显示为特殊"开发者回复"区块)
$table->text('admin_remark')->nullable()->comment('管理员官方回复(公开显示)');
// 冗余计数字段,配合排序(避免每次 COUNT JOIN)
$table->unsignedInteger('votes_count')->default(0)->comment('赞同人数(冗余,方便排序)');
$table->unsignedInteger('replies_count')->default(0)->comment('补充评论数(冗余)');
$table->timestamps();
// 索引:按类型+状态筛选、按赞同数排序
$table->index(['type', 'status'], 'idx_type_status');
$table->index('user_id', 'idx_user_id');
$table->index('votes_count', 'idx_votes');
});
// ——— 表2:赞同记录 ———
Schema::create('feedback_votes', function (Blueprint $table): void {
$table->id();
$table->unsignedBigInteger('feedback_id')->comment('对应反馈 ID');
$table->unsignedBigInteger('user_id')->comment('赞同用户 ID');
$table->timestamp('created_at')->nullable();
// 联合唯一索引:每个用户每条反馈只能赞同一次
$table->unique(['feedback_id', 'user_id'], 'uq_vote');
$table->index('feedback_id', 'idx_feedback_id');
});
// ——— 表3:补充评论 ———
Schema::create('feedback_replies', function (Blueprint $table): void {
$table->id();
$table->unsignedBigInteger('feedback_id')->comment('对应反馈 ID');
// 回复人信息(冗余 username
$table->unsignedBigInteger('user_id')->comment('回复人 ID');
$table->string('username', 50)->comment('回复人用户名(快照)');
// 评论内容(纯文本,不支持 Markdown,防止滥用)
$table->text('content')->comment('补充内容(纯文本)');
// 管理员官方回复标记(前台特殊高亮展示)
$table->boolean('is_admin')->default(false)->comment('是否为 id=1 管理员的官方回复');
$table->timestamps();
$table->index('feedback_id', 'idx_feedback_id');
});
}
/**
* 回滚:删除用户反馈相关三张表
*/
public function down(): void
{
Schema::dropIfExists('feedback_replies');
Schema::dropIfExists('feedback_votes');
Schema::dropIfExists('feedback_items');
}
};
@@ -0,0 +1,41 @@
<?php
/**
* 文件功能:创建部门表迁移
* 部门是职务的上级分类(办公厅 / 迎宾部 / 聊务部 / 宣传部等)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建部门表
*/
public function up(): void
{
Schema::create('departments', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->comment('部门名称');
$table->unsignedTinyInteger('rank')->default(0)->comment('部门位阶(0~9999 最高)');
$table->string('color', 10)->nullable()->comment('展示颜色 hex(如 #8B0000');
$table->tinyInteger('sort_order')->default(0)->comment('后台列表排序');
$table->string('description', 255)->nullable()->comment('部门描述');
$table->timestamps();
});
}
/**
* 回滚:删除部门表
*/
public function down(): void
{
Schema::dropIfExists('departments');
}
};
@@ -0,0 +1,44 @@
<?php
/**
* 文件功能:创建职务表迁移
* 职务属于某个部门,包含 rank 位阶、对应 user_level、人数上限、奖励上限和展示图标
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建职务表
*/
public function up(): void
{
Schema::create('positions', function (Blueprint $table) {
$table->id();
$table->foreignId('department_id')->constrained('departments')->cascadeOnDelete()->comment('所属部门');
$table->string('name', 50)->comment('职务名称');
$table->string('icon', 10)->nullable()->comment('职务图标(emoji),展示在聊天室用户列表');
$table->unsignedTinyInteger('rank')->default(0)->comment('职务位阶(0~99,跨全局排序,99 最高)');
$table->tinyInteger('level')->default(1)->comment('对应 user_level,任命后同步写入 users.user_level');
$table->unsignedTinyInteger('max_persons')->nullable()->comment('人数上限(null=不限)');
$table->unsignedInteger('max_reward')->nullable()->comment('单次奖励上限金币(null=不限)');
$table->tinyInteger('sort_order')->default(0)->comment('后台列表排序');
$table->timestamps();
});
}
/**
* 回滚:删除职务表
*/
public function down(): void
{
Schema::dropIfExists('positions');
}
};
@@ -0,0 +1,48 @@
<?php
/**
* 文件功能:创建职务任命权限中间表迁移
* 记录"职务 A 的持有者可以将用户任命到职务 B"的多对多关系
* 某职务若无记录则视为无任命权
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建职务任命权限表
*/
public function up(): void
{
Schema::create('position_appoint_limits', function (Blueprint $table) {
// 任命人的职务(该职务的持有者有权任命)
$table->foreignId('appointer_position_id')
->constrained('positions')
->cascadeOnDelete()
->comment('任命人职务 ID');
// 可被任命到的目标职务
$table->foreignId('appointable_position_id')
->constrained('positions')
->cascadeOnDelete()
->comment('可任命的目标职务 ID');
$table->primary(['appointer_position_id', 'appointable_position_id']);
});
}
/**
* 回滚:删除职务任命权限表
*/
public function down(): void
{
Schema::dropIfExists('position_appoint_limits');
}
};
@@ -0,0 +1,66 @@
<?php
/**
* 文件功能:创建用户职务关联表迁移
* 记录每个用户的当前在职职务及全部历史任职记录(职务履历核心表)
* is_active=true 表示当前在职,false 表示历史记录(永久保留)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建用户职务关联表
*/
public function up(): void
{
Schema::create('user_positions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained('users')
->cascadeOnDelete()
->comment('用户 ID');
$table->foreignId('position_id')
->constrained('positions')
->cascadeOnDelete()
->comment('职务 ID');
// 任命信息
$table->unsignedBigInteger('appointed_by_user_id')->nullable()->comment('任命人 user_idnull=系统初始化)');
$table->timestamp('appointed_at')->comment('任命时间');
$table->string('remark', 255)->nullable()->comment('任命备注');
// 撤销信息
$table->timestamp('revoked_at')->nullable()->comment('撤销时间(null=仍在职)');
$table->unsignedBigInteger('revoked_by_user_id')->nullable()->comment('撤销人 user_id');
// 状态:true=当前在职;false=历史记录
$table->boolean('is_active')->default(true)->index()->comment('是否当前在职');
$table->timestamps();
// 同一用户同一时刻只能有一条在职记录(通过业务层保证,索引辅助查询)
$table->index(['user_id', 'is_active']);
$table->index(['position_id', 'is_active']);
// 外键约束(任命人/撤销人)
$table->foreign('appointed_by_user_id')->references('id')->on('users')->nullOnDelete();
$table->foreign('revoked_by_user_id')->references('id')->on('users')->nullOnDelete();
});
}
/**
* 回滚:删除用户职务关联表
*/
public function down(): void
{
Schema::dropIfExists('user_positions');
}
};
@@ -0,0 +1,80 @@
<?php
/**
* 文件功能:创建职务权限使用记录表迁移
* 记录职务持有者每一次行使职权的操作(任命、撤销、奖励、警告、踢出、禁言、封IP等)
* 操作人必须有在职职务才会写入此表
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建职务权限使用记录表
*/
public function up(): void
{
Schema::create('position_authority_logs', function (Blueprint $table) {
$table->id();
// 操作人信息
$table->foreignId('user_id')
->constrained('users')
->cascadeOnDelete()
->comment('操作人 user_id');
$table->foreignId('user_position_id')
->constrained('user_positions')
->cascadeOnDelete()
->comment('操作时使用的职务记录 ID');
// 操作类型
$table->enum('action_type', [
'appoint', // 任命他人到某职务
'revoke', // 撤销他人职务
'reward', // 奖励金币
'warn', // 警告用户
'kick', // 踢出用户
'mute', // 禁言用户
'banip', // 封锁 IP
'other', // 其他操作
])->comment('操作类型');
// 操作对象
$table->foreignId('target_user_id')
->constrained('users')
->cascadeOnDelete()
->comment('操作对象 user_id');
$table->foreignId('target_position_id')
->nullable()
->constrained('positions')
->nullOnDelete()
->comment('任命/撤销时的目标职务 ID');
// 附加数据
$table->unsignedInteger('amount')->nullable()->comment('奖励金额(reward 操作时填写)');
$table->string('remark', 255)->nullable()->comment('操作备注/理由');
$table->timestamp('created_at')->comment('操作时间');
// 查询索引(手动指定短名称,避免超过 MySQL 64 字符限制)
$table->index(['user_position_id', 'action_type', 'created_at'], 'pal_up_id_action_created_idx');
$table->index(['user_id', 'created_at'], 'pal_user_created_idx');
$table->index(['target_user_id', 'created_at'], 'pal_target_created_idx');
});
}
/**
* 回滚:删除职务权限使用记录表
*/
public function down(): void
{
Schema::dropIfExists('position_authority_logs');
}
};
@@ -0,0 +1,56 @@
<?php
/**
* 文件功能:创建在职登录记录表迁移
* 记录职务持有者每次进入聊天室的登录时间、在线时长和登出时间
* 用于统计在职期间的出勤数据(勤务台四榜 + 个人履历)
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* 创建在职登录记录表
*/
public function up(): void
{
Schema::create('position_duty_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')
->constrained('users')
->cascadeOnDelete()
->comment('用户 ID');
$table->foreignId('user_position_id')
->constrained('user_positions')
->cascadeOnDelete()
->comment('关联的在职记录 IDuser_positions.id');
$table->timestamp('login_at')->comment('登录/进房时间');
$table->timestamp('logout_at')->nullable()->comment('退出时间(null=尚未退出)');
$table->unsignedInteger('duration_seconds')->nullable()->comment('在线时长(秒)');
$table->string('ip_address', 45)->nullable()->comment('登录 IP');
$table->unsignedInteger('room_id')->nullable()->comment('进入的房间 ID');
$table->timestamps();
// 常用查询索引
$table->index(['user_position_id', 'login_at']);
$table->index(['user_id', 'login_at']);
});
}
/**
* 回滚:删除在职登录记录表
*/
public function down(): void
{
Schema::dropIfExists('position_duty_logs');
}
};