feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型 - 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息 - 撤销:灰色弹窗 + 灰色系统消息,无礼花 - 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏 - 系统消息加随机鼓励语(各5条轮换) - ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds) - 用户名片折叠优化:管理员视野、职务履历均可折叠 - 管理操作 + 职务操作合并为「🔧 管理操作」折叠区 - 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
This commit is contained in:
@@ -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~99,99 最高)');
|
||||
$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_id(null=系统初始化)');
|
||||
$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('关联的在职记录 ID(user_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');
|
||||
}
|
||||
};
|
||||
240
database/seeders/DepartmentPositionSeeder.php
Normal file
240
database/seeders/DepartmentPositionSeeder.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:部门与职务数据填充器
|
||||
* 预填所有部门(办公厅 / 迎宾部 / 聊务部 / 宣传部)
|
||||
* 及各部门下的完整职务数据(含图标、rank、level、人数/奖励上限)
|
||||
* 并写入默认的任命权限白名单(position_appoint_limits)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\Position;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DepartmentPositionSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* 执行数据填充
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// ── 1. 创建部门 ────────────────────────────────────────────────────
|
||||
$depts = [];
|
||||
foreach ($this->departmentsData() as $data) {
|
||||
$depts[$data['name']] = Department::firstOrCreate(
|
||||
['name' => $data['name']],
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
// ── 2. 创建职务 ────────────────────────────────────────────────────
|
||||
$positions = []; // key = "部门名::职务名"
|
||||
foreach ($this->positionsData() as $row) {
|
||||
$dept = $depts[$row['department']];
|
||||
$position = Position::firstOrCreate(
|
||||
['department_id' => $dept->id, 'name' => $row['name']],
|
||||
[
|
||||
'department_id' => $dept->id,
|
||||
'name' => $row['name'],
|
||||
'icon' => $row['icon'],
|
||||
'rank' => $row['rank'],
|
||||
'level' => $row['level'],
|
||||
'max_persons' => $row['max_persons'],
|
||||
'max_reward' => $row['max_reward'],
|
||||
'sort_order' => $row['sort_order'],
|
||||
]
|
||||
);
|
||||
$positions["{$row['department']}::{$row['name']}"] = $position;
|
||||
}
|
||||
|
||||
// ── 3. 设置任命权限白名单 ────────────────────────────────────────────
|
||||
foreach ($this->appointLimitsData($positions) as [$appointer, $appointableList]) {
|
||||
/** @var Position $appointerPosition */
|
||||
$appointerPosition = $positions[$appointer] ?? null;
|
||||
if (! $appointerPosition) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 同步任命白名单(不附加旧数据,完整覆盖)
|
||||
$appointableIds = collect($appointableList)
|
||||
->map(fn ($key) => $positions[$key] ?? null)
|
||||
->filter()
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$appointerPosition->appointablePositions()->sync($appointableIds);
|
||||
}
|
||||
|
||||
$this->command->info('部门职务 Seeder 完成:'.count($positions).' 个职务已写入。');
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门基础数据
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function departmentsData(): array
|
||||
{
|
||||
return [
|
||||
['name' => '办公厅', 'rank' => 99, 'color' => '#8B0000', 'sort_order' => 1, 'description' => '站级最高指挥机构'],
|
||||
['name' => '迎宾部', 'rank' => 80, 'color' => '#1a5276', 'sort_order' => 2, 'description' => '负责新用户迎接与接待工作'],
|
||||
['name' => '聊务部', 'rank' => 75, 'color' => '#196F3D', 'sort_order' => 3, 'description' => '负责聊天室日常管理与氛围维护'],
|
||||
['name' => '宣传部', 'rank' => 70, 'color' => '#7D6608', 'sort_order' => 4, 'description' => '负责聊天室宣传推广与活动策划'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 职务完整数据
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function positionsData(): array
|
||||
{
|
||||
// [部门, 职务名, icon, rank, level, max_persons, max_reward, sort_order]
|
||||
$rows = [
|
||||
// ── 办公厅 ──────────────────────────────────────────────────────
|
||||
['办公厅', '站长', '👑', 99, 100, 1, null, 1],
|
||||
['办公厅', '执行站长', '⭐', 97, 99, 1, null, 2],
|
||||
['办公厅', '常务副站长', '📜', 95, 98, 1, 2000, 3],
|
||||
['办公厅', '副站长', '🔴', 93, 97, 1, 1500, 4],
|
||||
['办公厅', '秘书长', '✏️', 91, 96, 2, 1200, 5],
|
||||
['办公厅', '三部总长', '🪖', 89, 95, 1, 1000, 6],
|
||||
// ── 迎宾部 ──────────────────────────────────────────────────────
|
||||
['迎宾部', '迎宾部长', '🏆', 87, 94, 1, 800, 1],
|
||||
['迎宾部', '迎宾政委', '👤', 87, 94, 1, 800, 2],
|
||||
['迎宾部', '迎宾常务副部长', '📋', 85, 93, 1, 500, 3],
|
||||
['迎宾部', '迎宾副部长', '🔵', 83, 92, 1, 300, 4],
|
||||
['迎宾部', '迎宾副政委', '🔵', 81, 91, 1, 300, 5],
|
||||
['迎宾部', '金牌迎宾员', '🥇', 70, 90, 2, null, 6],
|
||||
['迎宾部', '银牌迎宾员', '🥈', 50, 80, 3, null, 7],
|
||||
['迎宾部', '铜牌迎宾员', '🥉', 30, 70, 4, null, 8],
|
||||
['迎宾部', '实习迎宾员', '🌱', 10, 60, 4, null, 9],
|
||||
// ── 聊务部 ──────────────────────────────────────────────────────
|
||||
['聊务部', '聊务部部长', '🏆', 87, 94, 1, 800, 1],
|
||||
['聊务部', '聊务部政委', '👤', 87, 94, 1, 800, 2],
|
||||
['聊务部', '聊务部常务副部长', '📋', 85, 93, 1, 500, 3],
|
||||
['聊务部', '聊务部副部长', '🔵', 83, 92, 1, 300, 4],
|
||||
['聊务部', '聊务部副政委', '🔵', 81, 91, 1, 300, 5],
|
||||
['聊务部', '金牌聊务员', '🥇', 70, 90, 2, null, 6],
|
||||
['聊务部', '银牌聊务员', '🥈', 50, 80, 3, null, 7],
|
||||
['聊务部', '铜牌聊务员', '🥉', 30, 70, 4, null, 8],
|
||||
['聊务部', '实习聊务员', '🌱', 10, 60, 4, null, 9],
|
||||
// ── 宣传部 ──────────────────────────────────────────────────────
|
||||
['宣传部', '宣传部部长', '🏆', 87, 94, 1, 800, 1],
|
||||
['宣传部', '宣传部政委', '👤', 87, 94, 1, 800, 2],
|
||||
['宣传部', '宣传部常务副部长', '📋', 85, 93, 1, 500, 3],
|
||||
['宣传部', '宣传部副部长', '🔵', 83, 92, 1, 300, 4],
|
||||
['宣传部', '宣传部副政委', '🔵', 81, 91, 1, 300, 5],
|
||||
['宣传部', '金牌宣传员', '🥇', 70, 90, 2, null, 6],
|
||||
['宣传部', '银牌宣传员', '🥈', 50, 80, 3, null, 7],
|
||||
['宣传部', '铜牌宣传员', '🥉', 30, 70, 4, null, 8],
|
||||
['宣传部', '实习宣传员', '🌱', 10, 60, 4, null, 9],
|
||||
];
|
||||
|
||||
return array_map(fn ($r) => [
|
||||
'department' => $r[0],
|
||||
'name' => $r[1],
|
||||
'icon' => $r[2],
|
||||
'rank' => $r[3],
|
||||
'level' => $r[4],
|
||||
'max_persons' => $r[5],
|
||||
'max_reward' => $r[6],
|
||||
'sort_order' => $r[7],
|
||||
], $rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认任命权限白名单
|
||||
* key = "任命人职务对应的 positions key"
|
||||
* value = [可被任命的职务 key 列表]
|
||||
*
|
||||
* @param array<string, Position> $positions
|
||||
* @return array<int, array{0: string, 1: list<string>}>
|
||||
*/
|
||||
private function appointLimitsData(array $positions): array
|
||||
{
|
||||
// 全三部门的所有职务 key 列表(不含办公厅)
|
||||
$allThreeDepts = array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($k) => ! str_starts_with($k, '办公厅::'),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
));
|
||||
|
||||
// 三部门所有部长/政委及以下(全部)
|
||||
$allThreeAll = $allThreeDepts;
|
||||
|
||||
// 三部门常务副部长及以下(level <= 93)
|
||||
$allThreeBelowZhangzhang = array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($p, $k) => ! str_starts_with($k, '办公厅::') && $p->level <= 93,
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
// 三部门副部长/副政委及以下(level <= 92)
|
||||
$allThreeBelowFubu = array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($p, $k) => ! str_starts_with($k, '办公厅::') && $p->level <= 92,
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
// 三部门金牌及以下(level <= 90)
|
||||
$allThreeBelowGold = array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($p, $k) => ! str_starts_with($k, '办公厅::') && $p->level <= 90,
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
// 三部门银牌及以下(level <= 80)
|
||||
$allThreeBelowSilver = array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($p, $k) => ! str_starts_with($k, '办公厅::') && $p->level <= 80,
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
/**
|
||||
* 便捷闭包:获取指定部门、指定最大 level 的职务 key 列表
|
||||
*/
|
||||
$deptBelow = fn (string $dept, int $maxLevel) => array_keys(array_filter(
|
||||
$positions,
|
||||
fn ($p, $k) => str_starts_with($k, "{$dept}::") && $p->level <= $maxLevel,
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
return [
|
||||
// 办公厅
|
||||
['办公厅::站长', array_merge(
|
||||
['办公厅::执行站长', '办公厅::常务副站长', '办公厅::副站长', '办公厅::秘书长', '办公厅::三部总长'],
|
||||
$allThreeAll
|
||||
)],
|
||||
['办公厅::执行站长', $allThreeAll], // 三部所有职务
|
||||
['办公厅::常务副站长', $allThreeBelowZhangzhang], // 三部常务副部长及以下
|
||||
['办公厅::副站长', $allThreeBelowFubu], // 三部副部长级及以下
|
||||
['办公厅::秘书长', $allThreeBelowGold], // 三部金牌及以下
|
||||
['办公厅::三部总长', $allThreeBelowSilver], // 三部银牌及以下
|
||||
// 迎宾部
|
||||
['迎宾部::迎宾部长', $deptBelow('迎宾部', 90)],
|
||||
['迎宾部::迎宾政委', $deptBelow('迎宾部', 90)],
|
||||
['迎宾部::迎宾常务副部长', $deptBelow('迎宾部', 80)],
|
||||
['迎宾部::迎宾副部长', $deptBelow('迎宾部', 70)],
|
||||
['迎宾部::迎宾副政委', $deptBelow('迎宾部', 70)],
|
||||
// 聊务部
|
||||
['聊务部::聊务部部长', $deptBelow('聊务部', 90)],
|
||||
['聊务部::聊务部政委', $deptBelow('聊务部', 90)],
|
||||
['聊务部::聊务部常务副部长', $deptBelow('聊务部', 80)],
|
||||
['聊务部::聊务部副部长', $deptBelow('聊务部', 70)],
|
||||
['聊务部::聊务部副政委', $deptBelow('聊务部', 70)],
|
||||
// 宣传部
|
||||
['宣传部::宣传部部长', $deptBelow('宣传部', 90)],
|
||||
['宣传部::宣传部政委', $deptBelow('宣传部', 90)],
|
||||
['宣传部::宣传部常务副部长', $deptBelow('宣传部', 80)],
|
||||
['宣传部::宣传部副部长', $deptBelow('宣传部', 70)],
|
||||
['宣传部::宣传部副政委', $deptBelow('宣传部', 70)],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user