Feat: 实现全屏特效系统(烟花/下雨/雷电),管理员一键触发全房间广播
This commit is contained in:
68
app/Events/EffectBroadcast.php
Normal file
68
app/Events/EffectBroadcast.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室全屏特效广播事件
|
||||
*
|
||||
* 管理员触发烟花/下雨/雷电等特效后,
|
||||
* 通过 WebSocket 广播给房间内所有在线用户,前端收到后播放对应 Canvas 动画。
|
||||
*
|
||||
* @package App\Events
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class EffectBroadcast implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* 支持的特效类型列表(用于校验)
|
||||
*/
|
||||
public const TYPES = ['fireworks', 'rain', 'lightning'];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param int $roomId 房间 ID
|
||||
* @param string $type 特效类型:fireworks / rain / lightning
|
||||
* @param string $operator 触发特效的管理员用户名
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $roomId,
|
||||
public readonly string $type,
|
||||
public readonly string $operator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 广播频道:向房间内所有在线用户推送
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PresenceChannel('room.' . $this->roomId),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播数据:特效类型和操作者
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'operator' => $this->operator,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -373,6 +373,38 @@ class AdminCommandController extends Controller
|
||||
return response()->json(['status' => 'success', 'message' => '已执行全员清屏']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员触发全屏特效(烟花/下雨/雷电)
|
||||
*
|
||||
* 向房间内所有用户广播 EffectBroadcast 事件,前端收到后播放对应 Canvas 动画。
|
||||
* 仅 superlevel 等级管理员可触发。
|
||||
*
|
||||
* @param Request $request 请求对象,需包含 room_id, type
|
||||
* @return JsonResponse 操作结果
|
||||
*/
|
||||
public function effect(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'room_id' => 'required|integer',
|
||||
'type' => 'required|in:fireworks,rain,lightning',
|
||||
]);
|
||||
|
||||
$admin = Auth::user();
|
||||
$roomId = $request->input('room_id');
|
||||
$type = $request->input('type');
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
|
||||
// 仅 superlevel 等级可触发特效
|
||||
if ($admin->user_level < $superLevel) {
|
||||
return response()->json(['status' => 'error', 'message' => '仅站长可触发特效'], 403);
|
||||
}
|
||||
|
||||
// 广播特效事件给房间内所有在线用户
|
||||
broadcast(new \App\Events\EffectBroadcast($roomId, $type, $admin->username));
|
||||
|
||||
return response()->json(['status' => 'success', 'message' => "已触发特效:{$type}"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限检查:管理员是否可对目标用户执行指定操作
|
||||
*
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('username', 50)->unique()->comment('用户名');
|
||||
$table->string('password')->comment('密码 (MD5 or Bcrypt)');
|
||||
$table->string('email', 250)->nullable()->comment('邮箱');
|
||||
$table->tinyInteger('sex')->default(0)->comment('性别 (0保密 1男 2女)');
|
||||
$table->tinyInteger('user_level')->default(1)->comment('用户等级');
|
||||
$table->dateTime('log_time')->nullable()->comment('登录时间');
|
||||
$table->integer('visit_num')->default(0)->comment('访问次数');
|
||||
$table->dateTime('in_time')->nullable()->comment('进房时间');
|
||||
$table->tinyInteger('out_info')->default(0)->comment('退出信息');
|
||||
$table->dateTime('out_time')->nullable()->comment('退出时间');
|
||||
$table->integer('exp_num')->default(0)->index()->comment('经验值');
|
||||
$table->tinyInteger('f_size')->nullable()->comment('字体大小');
|
||||
$table->tinyInteger('l_height')->nullable()->comment('行高');
|
||||
$table->tinyInteger('n_color')->nullable()->comment('名称颜色');
|
||||
$table->integer('s_color')->nullable()->comment('发言颜色');
|
||||
$table->string('remand', 250)->nullable()->comment('密码提示问题');
|
||||
$table->string('answer', 250)->nullable()->comment('密码提示答案');
|
||||
$table->string('bgcolor', 50)->nullable()->comment('背景颜色');
|
||||
$table->string('temppass', 20)->nullable()->comment('临时密码');
|
||||
$table->string('oicq', 30)->nullable()->comment('QQ号');
|
||||
$table->tinyInteger('saved')->nullable()->comment('是否保存');
|
||||
$table->string('first_ip', 50)->nullable()->comment('首次IP');
|
||||
$table->string('last_ip', 50)->nullable()->comment('最后IP');
|
||||
$table->string('aihaos', 250)->nullable()->comment('爱好');
|
||||
$table->string('friends', 250)->nullable()->comment('好友列表');
|
||||
$table->integer('headface')->nullable()->comment('头像');
|
||||
$table->integer('room_id')->default(0)->index()->comment('所在房间');
|
||||
$table->tinyInteger('auto_update')->nullable()->comment('自动刷新');
|
||||
$table->string('ppass', 50)->nullable()->comment('二级密码');
|
||||
$table->integer('jjb')->default(0)->comment('交友币/金币');
|
||||
$table->string('love', 50)->nullable()->comment('伴侣');
|
||||
$table->string('gzdw', 50)->nullable()->comment('工作单位');
|
||||
$table->integer('photo')->nullable()->comment('照片 (对应原表中文列名照片)');
|
||||
$table->integer('hj')->default(0)->comment('呼叫状态');
|
||||
$table->string('djname', 50)->nullable()->comment('等级名称');
|
||||
$table->string('usersf', 50)->nullable()->comment('用户身份');
|
||||
$table->integer('yh')->nullable()->comment('隐身状态');
|
||||
$table->text('userpassword')->nullable()->comment('备用密码');
|
||||
$table->string('huiyuan', 50)->nullable()->comment('会员组别');
|
||||
$table->dateTime('hy_time')->nullable()->comment('会员到期时间');
|
||||
$table->string('tuijian', 50)->nullable()->comment('推荐人');
|
||||
$table->string('tuijian_ip', 50)->nullable()->comment('推荐IP');
|
||||
$table->string('xiaohai', 50)->nullable()->comment('小孩');
|
||||
$table->string('qingren', 50)->nullable()->comment('情人');
|
||||
$table->string('zhufang', 50)->nullable()->comment('住房');
|
||||
$table->string('zuoqiimg', 50)->nullable()->comment('坐骑图片');
|
||||
$table->string('zuoqi', 50)->nullable()->comment('坐骑名称');
|
||||
$table->integer('guanli')->nullable()->comment('管理员标记');
|
||||
$table->integer('meili')->nullable()->comment('魅力值');
|
||||
$table->integer('teshu')->nullable()->comment('特殊权限');
|
||||
$table->dateTime('xr_time')->nullable()->comment('仙人时间');
|
||||
$table->dateTime('yx_time')->nullable()->comment('英雄时间');
|
||||
$table->integer('q1')->nullable();
|
||||
$table->integer('q2')->nullable();
|
||||
$table->integer('q3')->nullable();
|
||||
$table->string('hua', 255)->nullable()->comment('鲜花');
|
||||
$table->dateTime('sj')->nullable()->comment('注册/更新时间');
|
||||
$table->string('pig', 255)->nullable()->comment('宠物');
|
||||
$table->text('qianming')->nullable()->comment('个性签名');
|
||||
$table->dateTime('q3_time')->nullable();
|
||||
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
$table->text('user_agent')->nullable();
|
||||
$table->longText('payload');
|
||||
$table->integer('last_activity')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('users');
|
||||
Schema::dropIfExists('password_reset_tokens');
|
||||
Schema::dropIfExists('sessions');
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration')->index();
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration')->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('cache');
|
||||
Schema::dropIfExists('cache_locks');
|
||||
}
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
$table->unsignedTinyInteger('attempts');
|
||||
$table->unsignedInteger('reserved_at')->nullable();
|
||||
$table->unsignedInteger('available_at');
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
$table->integer('pending_jobs');
|
||||
$table->integer('failed_jobs');
|
||||
$table->longText('failed_job_ids');
|
||||
$table->mediumText('options')->nullable();
|
||||
$table->integer('cancelled_at')->nullable();
|
||||
$table->integer('created_at');
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->longText('exception');
|
||||
$table->timestamp('failed_at')->useCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jobs');
|
||||
Schema::dropIfExists('job_batches');
|
||||
Schema::dropIfExists('failed_jobs');
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('rooms', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('room_name', 10)->unique()->comment('房间标识/名称');
|
||||
$table->string('room_auto', 10)->nullable()->comment('房间别名/自动属性');
|
||||
$table->string('room_owner', 10)->nullable()->comment('房主');
|
||||
$table->string('room_des', 250)->nullable()->comment('房间描述');
|
||||
$table->string('room_top', 100)->nullable()->comment('置顶信息');
|
||||
$table->string('room_title', 250)->nullable()->comment('房间标题');
|
||||
$table->tinyInteger('room_keep')->default(0)->comment('是否保留');
|
||||
$table->dateTime('room_time')->nullable()->comment('建立/最后时间');
|
||||
$table->tinyInteger('room_tt')->default(0)->comment('相关开关');
|
||||
$table->tinyInteger('room_html')->default(0)->comment('是否允许HTML');
|
||||
$table->integer('room_exp')->default(0)->comment('所需经验');
|
||||
$table->dateTime('build_time')->nullable()->comment('建立时间');
|
||||
$table->tinyInteger('permit_level')->default(1)->comment('允许进入的最低等级');
|
||||
$table->tinyInteger('door_open')->default(1)->comment('大门是否开启 (1开 0关)');
|
||||
$table->integer('ooooo')->nullable()->comment('未知/扩展属性o5');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('rooms');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('audit_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->dateTime('occ_time')->nullable()->index()->comment('发生时间');
|
||||
$table->text('occ_env')->nullable()->comment('发生环境/详情');
|
||||
$table->tinyInteger('stype')->default(0)->comment('类别标识');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('audit_logs');
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('guestbooks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('who', 50)->index()->comment('留言人');
|
||||
$table->string('towho', 50)->nullable()->index()->comment('给谁留言');
|
||||
$table->tinyInteger('secret')->default(0)->comment('是否悄悄话 (1是 0否)');
|
||||
$table->string('ip', 50)->nullable()->comment('留言者IP');
|
||||
$table->string('email', 250)->nullable()->comment('留言者邮箱');
|
||||
$table->string('web', 250)->nullable()->comment('留言者网站');
|
||||
$table->string('addr', 250)->nullable()->comment('留言者地址');
|
||||
$table->dateTime('post_time')->nullable()->index()->comment('留言时间');
|
||||
$table->string('text_title', 250)->nullable()->comment('留言标题');
|
||||
$table->text('text_body')->nullable()->comment('留言内容');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('guestbooks');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ip_locks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ip', 20)->unique()->comment('封锁的IP');
|
||||
$table->dateTime('end_time')->nullable()->comment('封禁结束时间');
|
||||
$table->tinyInteger('act_level')->unsigned()->default(1)->comment('封禁级别');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ip_locks');
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('room_id')->index()->comment('房间ID');
|
||||
$table->string('from_user', 50)->index()->comment('发送者');
|
||||
$table->string('to_user', 50)->nullable()->index()->comment('接收者');
|
||||
$table->text('content')->comment('消息内容');
|
||||
$table->tinyInteger('is_secret')->default(0)->comment('是否私聊');
|
||||
$table->string('font_color', 50)->nullable()->comment('字体颜色');
|
||||
$table->string('action', 50)->nullable()->comment('动作');
|
||||
$table->dateTime('sent_at')->useCurrent()->index()->comment('发送时间');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('messages');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('sys_params', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('alias', 50)->unique()->comment('参数别名');
|
||||
$table->text('guidetxt')->nullable()->comment('参数说明');
|
||||
$table->text('body')->nullable()->comment('参数值');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sys_params');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('actions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('act_name', 50)->unique()->comment('动作名称');
|
||||
$table->string('alias', 50)->nullable()->comment('动作别名');
|
||||
$table->string('toall', 200)->nullable()->comment('对所有人表现');
|
||||
$table->string('toself', 200)->nullable()->comment('对自己的表现');
|
||||
$table->string('toother', 200)->nullable()->comment('对其他人的表现');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('actions');
|
||||
}
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('admin_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('username', 50)->index()->comment('管理员账号');
|
||||
$table->integer('uu_level')->nullable()->comment('管理员等级');
|
||||
$table->dateTime('in_time')->nullable()->comment('进入时间');
|
||||
$table->integer('out_time')->nullable()->comment('离开时间');
|
||||
$table->text('caozuo')->nullable()->comment('操作详情');
|
||||
$table->integer('fs_sl')->default(0)->comment('封杀数量');
|
||||
$table->text('fs_name')->nullable()->comment('封杀名单');
|
||||
$table->integer('tr_sl')->default(0)->comment('踢人数量');
|
||||
$table->text('tr_name')->nullable()->comment('踢人名单');
|
||||
$table->integer('jg_sl')->default(0)->comment('警告数量');
|
||||
$table->text('jg_name')->nullable()->comment('警告名单');
|
||||
$table->integer('dj_sl')->default(0)->comment('冻结数量');
|
||||
$table->text('dj_name')->nullable()->comment('冻结名单');
|
||||
$table->integer('yjdj_sl')->default(0)->comment('永久冻结数量');
|
||||
$table->text('yjdj_name')->nullable()->comment('永久冻结名单');
|
||||
$table->integer('fip_sl')->default(0)->comment('封IP数量');
|
||||
$table->text('fip_name')->nullable()->comment('封IP名单');
|
||||
$table->integer('fjj_sl')->default(0)->comment('发奖金数量');
|
||||
$table->integer('fjy_sl')->default(0)->comment('发经验数量');
|
||||
$table->integer('flh_sl')->default(0)->comment('发老虎数量');
|
||||
$table->dateTime('jl_time')->nullable()->index()->comment('记录时间');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('admin_logs');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('friend_calls', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('who', 50)->index()->comment('呼叫人');
|
||||
$table->string('towho', 50)->index()->comment('被呼叫人');
|
||||
$table->text('callmess')->nullable()->comment('呼叫信息');
|
||||
$table->dateTime('calltime')->nullable()->index()->comment('呼叫时间');
|
||||
$table->tinyInteger('read')->default(0)->comment('是否已读 (1是 0否)');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('friend_calls');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('friend_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('who', 50)->index()->comment('申请人');
|
||||
$table->string('towho', 50)->index()->comment('被申请人');
|
||||
$table->dateTime('sub_time')->nullable()->index()->comment('申请时间');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('friend_requests');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('ip_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ip', 50)->index()->comment('登录IP');
|
||||
$table->dateTime('sdate')->nullable()->index()->comment('登录时间');
|
||||
$table->string('uuname', 50)->index()->comment('登录用户名');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ip_logs');
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('marriages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('hyname', 50)->index()->comment('夫');
|
||||
$table->string('hyname1', 50)->index()->comment('妻');
|
||||
$table->dateTime('hytime')->nullable()->comment('结婚时间');
|
||||
$table->string('hygb', 50)->nullable()->comment('婚姻状态/广播');
|
||||
$table->string('hyjb', 50)->nullable()->comment('婚姻级别');
|
||||
$table->integer('i')->nullable()->comment('亲密度/属性');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('marriages');
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('room_descriptions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('alias', 10)->unique()->comment('房间别名关联');
|
||||
$table->string('describ', 254)->nullable()->comment('房间详细描述文本');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('room_descriptions');
|
||||
}
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('scroll_ads', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ad_title', 250)->comment('广告/公告标题');
|
||||
$table->string('ad_link', 250)->nullable()->comment('链接地址');
|
||||
$table->tinyInteger('ad_new_flag')->default(0)->comment('新窗口打开标识 (1是 0否)');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('scroll_ads');
|
||||
}
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('user_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name', 50)->index()->comment('使用者');
|
||||
$table->dateTime('times')->nullable()->index()->comment('获得时间');
|
||||
$table->text('gg')->nullable()->comment('道具类型/说明');
|
||||
$table->integer('dayy')->nullable()->comment('道具天数/数量');
|
||||
$table->string('lx', 50)->nullable()->comment('道具种类');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('user_items');
|
||||
}
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:为 rooms 表添加 visit_num(人气/访问量)字段
|
||||
* 用于记录房间的总访问人次,每次用户进入房间时递增
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 添加 visit_num 字段到 rooms 表
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('rooms', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('visit_num')->default(0)->after('ooooo')
|
||||
->comment('房间人气(总访问人次)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:移除 visit_num 字段
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('rooms', function (Blueprint $table) {
|
||||
$table->dropColumn('visit_num');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:创建系统参数表(sysparam)
|
||||
* 复刻原版 ASP 聊天室的 sysparam 系统配置表
|
||||
* 存储等级-经验阈值、系统开关等管理员可配置项
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 创建 sysparam 表并插入默认等级经验配置
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('sysparam', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('alias', 100)->unique()->comment('参数别名(唯一标识)');
|
||||
$table->text('body')->nullable()->comment('参数值');
|
||||
$table->string('guidetxt', 500)->nullable()->comment('参数说明(后台显示)');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 插入默认的等级经验配置(管理员可在后台修改)
|
||||
// 格式:逗号分隔,每项为"等级所需的累计经验值"
|
||||
// 例如:等级1需要10经验,等级2需要50,等级3需要150...
|
||||
DB::table('sysparam')->insert([
|
||||
[
|
||||
'alias' => 'levelexp',
|
||||
'body' => '10,50,150,400,800,1500,3000,5000,8000,12000,18000,25000,35000,50000,80000',
|
||||
'guidetxt' => '等级经验阈值配置(逗号分隔)。第N个数字表示升到N级所需的累计经验值。例如:10,50,150 表示1级需10经验,2级需50经验,3级需150经验。',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'alias' => 'exp_per_heartbeat',
|
||||
'body' => '1',
|
||||
'guidetxt' => '每次心跳(约60秒)增加的经验值',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'alias' => 'superlevel',
|
||||
'body' => '16',
|
||||
'guidetxt' => '管理员级别(= 最高等级 + 1,拥有最高权限的等级阈值)',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'alias' => 'maxlevel',
|
||||
'body' => '15',
|
||||
'guidetxt' => '用户最高可达等级',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除 sysparam 表
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sysparam');
|
||||
}
|
||||
};
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:创建自动动作事件表(autoact)
|
||||
* 复刻原版 ASP 聊天室的 autoact 表
|
||||
* 存储系统随机事件(好运/坏运/经验/金币奖惩等)
|
||||
* 管理员可在后台增删改这些事件
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 创建 autoact 表并插入默认随机事件
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('autoact', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('text_body', 500)->comment('事件文本内容(支持变量替换)');
|
||||
$table->string('event_type', 20)->default('neutral')->comment('事件类型:good=好运, bad=坏运, neutral=中性');
|
||||
$table->integer('exp_change')->default(0)->comment('经验变化值(正=奖励, 负=惩罚)');
|
||||
$table->integer('jjb_change')->default(0)->comment('金币变化值(正=奖励, 负=惩罚)');
|
||||
$table->boolean('enabled')->default(true)->comment('是否启用');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 插入默认随机事件
|
||||
$events = [
|
||||
// 好运事件(奖励经验/金币)
|
||||
['text_body' => '🎉 恭喜【{username}】路遇仙人指点,获得 100 经验值!', 'event_type' => 'good', 'exp_change' => 100, 'jjb_change' => 0],
|
||||
['text_body' => '💰 【{username}】在路边捡到一袋金币,获得 500 金币!', 'event_type' => 'good', 'exp_change' => 0, 'jjb_change' => 500],
|
||||
['text_body' => '🌟 天降祥瑞!【{username}】获得 200 经验 + 200 金币!', 'event_type' => 'good', 'exp_change' => 200, 'jjb_change' => 200],
|
||||
['text_body' => '🎊 【{username}】参加武林大会获胜,奖励 300 经验!', 'event_type' => 'good', 'exp_change' => 300, 'jjb_change' => 0],
|
||||
['text_body' => '🏆 【{username}】完成了一个神秘任务,获得 150 金币!', 'event_type' => 'good', 'exp_change' => 0, 'jjb_change' => 150],
|
||||
['text_body' => '✨ 【{username}】领悟了一招绝学,经验暴增 500!', 'event_type' => 'good', 'exp_change' => 500, 'jjb_change' => 0],
|
||||
['text_body' => '🎁 系统随机赠送【{username}】100 金币,请查收!', 'event_type' => 'good', 'exp_change' => 0, 'jjb_change' => 100],
|
||||
|
||||
// 坏运事件(扣除经验/金币)
|
||||
['text_body' => '💀 【{username}】不小心踩到陷阱,损失 50 经验!', 'event_type' => 'bad', 'exp_change' => -50, 'jjb_change' => 0],
|
||||
['text_body' => '😱 【{username}】遭遇山贼打劫,被抢走 100 金币!', 'event_type' => 'bad', 'exp_change' => 0, 'jjb_change' => -100],
|
||||
['text_body' => '🌧️ 【{username}】在雨中迷路,消耗 80 经验值。', 'event_type' => 'bad', 'exp_change' => -80, 'jjb_change' => 0],
|
||||
['text_body' => '🐍 【{username}】被毒蛇咬了一口,损失 30 经验和 50 金币!', 'event_type' => 'bad', 'exp_change' => -30, 'jjb_change' => -50],
|
||||
|
||||
// 中性事件(纯文字,不奖惩)
|
||||
['text_body' => '🔮 星海小博士:【{username}】今天运势不错,继续加油!', 'event_type' => 'neutral', 'exp_change' => 0, 'jjb_change' => 0],
|
||||
['text_body' => '📜 星海小博士:有人说"坚持就是胜利",【{username}】觉得呢?', 'event_type' => 'neutral', 'exp_change' => 0, 'jjb_change' => 0],
|
||||
['text_body' => '🌈 星海小博士:【{username}】今天心情如何?记得多喝水哦!', 'event_type' => 'neutral', 'exp_change' => 0, 'jjb_change' => 0],
|
||||
['text_body' => '🎵 星海小博士:送给【{username}】一首歌,祝你天天开心!', 'event_type' => 'neutral', 'exp_change' => 0, 'jjb_change' => 0],
|
||||
];
|
||||
|
||||
$now = now();
|
||||
foreach ($events as &$event) {
|
||||
$event['enabled'] = true;
|
||||
$event['created_at'] = $now;
|
||||
$event['updated_at'] = $now;
|
||||
}
|
||||
|
||||
DB::table('autoact')->insert($events);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除 autoact 表
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('autoact');
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:为 rooms 表添加 announcement(公告/祝福语)字段
|
||||
* 有权限的用户可以设置房间公告,显示在聊天室顶部滚动条
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 添加 announcement 字段
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('rooms', function (Blueprint $table) {
|
||||
$table->string('announcement', 500)->nullable()->after('room_des')->comment('房间公告/祝福语(滚动显示)');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('rooms', function (Blueprint $table) {
|
||||
$table->dropColumn('announcement');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:将 users 表的 s_color 字段从 integer 改为 varchar
|
||||
* 以便直接存储 hex 颜色字符串(如 #ff0000),与前端颜色选择器格式一致
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 执行迁移:s_color 从 integer 改为 varchar(10)
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('s_color', 10)->nullable()->comment('发言颜色(hex,如 #ff0000)')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:s_color 从 varchar 改回 integer
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->integer('s_color')->nullable()->comment('发言颜色')->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:补充缺失的 sysparam 系统参数记录
|
||||
* 代码中引用了多个参数但原始迁移只种子了 4 条,
|
||||
* 此迁移使用 insertOrIgnore 补充缺失的参数配置
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 补充缺失的系统参数记录(使用 insertOrIgnore 避免重复)
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
DB::table('sysparam')->insertOrIgnore([
|
||||
[
|
||||
'alias' => 'level_kick',
|
||||
'body' => '10',
|
||||
'guidetxt' => '踢人所需等级(管理员可在聊天室踢出用户的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'level_mute',
|
||||
'body' => '8',
|
||||
'guidetxt' => '禁言所需等级(管理员可在聊天室禁言用户的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'level_ban',
|
||||
'body' => '12',
|
||||
'guidetxt' => '封号所需等级(管理员可封禁用户账号的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'level_banip',
|
||||
'body' => '14',
|
||||
'guidetxt' => '封IP所需等级(管理员可封禁用户IP地址的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'level_announcement',
|
||||
'body' => '10',
|
||||
'guidetxt' => '设置公告所需等级(可修改房间公告/祝福语的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'auto_event_chance',
|
||||
'body' => '10',
|
||||
'guidetxt' => '随机事件触发概率(百分比,1-100,每次心跳时按此概率触发随机事件)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除补充的参数记录
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')->whereIn('alias', [
|
||||
'level_kick',
|
||||
'level_mute',
|
||||
'level_ban',
|
||||
'level_banip',
|
||||
'level_announcement',
|
||||
'auto_event_chance',
|
||||
])->delete();
|
||||
}
|
||||
};
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:将等级系统从 15 级扩展到 99 级
|
||||
* - maxlevel: 15 → 99(用户最高等级)
|
||||
* - superlevel: 16 → 100(管理员等级)
|
||||
* - levelexp: 重新生成 99 级的经验阶梯
|
||||
* - 管理权限等级按比例调整到新体系
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 更新等级系统参数至 99 级体系
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 生成 99 级经验阶梯(使用 level^2.5 的幂次曲线,确保平滑递增)
|
||||
$thresholds = [];
|
||||
for ($level = 1; $level <= 99; $level++) {
|
||||
// 公式:10 * level^2.5,取整到十位数,保持数据整洁
|
||||
$exp = (int) (ceil(10 * pow($level, 2.5) / 10) * 10);
|
||||
$thresholds[] = $exp;
|
||||
}
|
||||
$levelExpStr = implode(',', $thresholds);
|
||||
|
||||
$now = now();
|
||||
|
||||
// 更新核心等级参数
|
||||
DB::table('sysparam')->where('alias', 'maxlevel')
|
||||
->update(['body' => '99', 'guidetxt' => '用户最高可达等级', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'superlevel')
|
||||
->update(['body' => '100', 'guidetxt' => '管理员级别(= 最高等级 + 1,拥有最高权限的等级阈值)', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'levelexp')
|
||||
->update(['body' => $levelExpStr, 'updated_at' => $now]);
|
||||
|
||||
// 按比例调整管理权限等级(原体系 /15,新体系 /99)
|
||||
// 禁言:8/15 ≈ 53% → 50级
|
||||
DB::table('sysparam')->where('alias', 'level_mute')
|
||||
->update(['body' => '50', 'updated_at' => $now]);
|
||||
|
||||
// 踢人:10/15 ≈ 67% → 60级
|
||||
DB::table('sysparam')->where('alias', 'level_kick')
|
||||
->update(['body' => '60', 'updated_at' => $now]);
|
||||
|
||||
// 设公告:10/15 ≈ 67% → 60级
|
||||
DB::table('sysparam')->where('alias', 'level_announcement')
|
||||
->update(['body' => '60', 'updated_at' => $now]);
|
||||
|
||||
// 封号:12/15 = 80% → 80级
|
||||
DB::table('sysparam')->where('alias', 'level_ban')
|
||||
->update(['body' => '80', 'updated_at' => $now]);
|
||||
|
||||
// 封IP:14/15 ≈ 93% → 90级
|
||||
DB::table('sysparam')->where('alias', 'level_banip')
|
||||
->update(['body' => '90', 'updated_at' => $now]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:恢复到原始 15 级体系
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
DB::table('sysparam')->where('alias', 'maxlevel')
|
||||
->update(['body' => '15', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'superlevel')
|
||||
->update(['body' => '16', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'levelexp')
|
||||
->update(['body' => '10,50,150,400,800,1500,3000,5000,8000,12000,18000,25000,35000,50000,80000', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'level_mute')
|
||||
->update(['body' => '8', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'level_kick')
|
||||
->update(['body' => '10', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'level_announcement')
|
||||
->update(['body' => '10', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'level_ban')
|
||||
->update(['body' => '12', 'updated_at' => $now]);
|
||||
|
||||
DB::table('sysparam')->where('alias', 'level_banip')
|
||||
->update(['body' => '14', 'updated_at' => $now]);
|
||||
}
|
||||
};
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:添加钓鱼系统参数到 sysparam 表
|
||||
* 配置钓鱼花费金币、冷却时间、等待上钩时间范围
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 插入钓鱼相关系统参数
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
DB::table('sysparam')->insertOrIgnore([
|
||||
[
|
||||
'alias' => 'fishing_cost',
|
||||
'body' => '5',
|
||||
'guidetxt' => '每次钓鱼花费金币数量',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'fishing_cooldown',
|
||||
'body' => '300',
|
||||
'guidetxt' => '钓鱼冷却时间(秒,默认300秒=5分钟)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'fishing_wait_min',
|
||||
'body' => '8',
|
||||
'guidetxt' => '钓鱼最短等待上钩时间(秒)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'fishing_wait_max',
|
||||
'body' => '15',
|
||||
'guidetxt' => '钓鱼最长等待上钩时间(秒)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除钓鱼参数
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')->whereIn('alias', [
|
||||
'fishing_cost',
|
||||
'fishing_cooldown',
|
||||
'fishing_wait_min',
|
||||
'fishing_wait_max',
|
||||
])->delete();
|
||||
}
|
||||
};
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:创建 vip_levels 会员等级配置表
|
||||
* 存储会员等级名称、图标、颜色、倍率、专属模板等配置
|
||||
* 后台可完整 CRUD 管理
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 创建 vip_levels 表并填充默认数据
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('vip_levels', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name', 50)->comment('会员名称(如:白银会员)');
|
||||
$table->string('icon', 20)->default('⭐')->comment('等级图标/emoji');
|
||||
$table->string('color', 10)->default('#f59e0b')->comment('等级颜色(hex)');
|
||||
$table->decimal('exp_multiplier', 3, 1)->default(1.0)->comment('经验倍率');
|
||||
$table->decimal('jjb_multiplier', 3, 1)->default(1.0)->comment('金币倍率');
|
||||
$table->text('join_templates')->nullable()->comment('进入聊天室专属欢迎语 JSON 数组');
|
||||
$table->text('leave_templates')->nullable()->comment('离开聊天室专属提示语 JSON 数组');
|
||||
$table->tinyInteger('sort_order')->default(0)->comment('排序(越大越高级)');
|
||||
$table->integer('price')->default(0)->comment('赞助金额(元,供展示参考)');
|
||||
$table->integer('duration_days')->default(30)->comment('有效天数(0=永久)');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 默认种子数据(后台可随时修改/删除/新增)
|
||||
$now = now();
|
||||
DB::table('vip_levels')->insert([
|
||||
[
|
||||
'name' => '白银会员',
|
||||
'icon' => '🥈',
|
||||
'color' => '#94a3b8',
|
||||
'exp_multiplier' => 1.5,
|
||||
'jjb_multiplier' => 1.2,
|
||||
'join_templates' => json_encode([
|
||||
'白银贵宾{username}乘坐银色马车缓缓驶入,气质不凡!',
|
||||
'白银贵宾{username}手持银色令牌,大步流星走了进来!',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'leave_templates' => json_encode([
|
||||
'白银贵宾{username}挥了挥手,潇洒离去',
|
||||
'白银贵宾{username}骑着银色骏马飘然而去',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'sort_order' => 1,
|
||||
'price' => 10,
|
||||
'duration_days' => 30,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'name' => '黄金会员',
|
||||
'icon' => '🥇',
|
||||
'color' => '#f59e0b',
|
||||
'exp_multiplier' => 2.0,
|
||||
'jjb_multiplier' => 1.5,
|
||||
'join_templates' => json_encode([
|
||||
'黄金贵宾{username}踩着金光闪闪的地毯,王者归来!',
|
||||
'黄金贵宾{username}开着金色豪车呼啸而至,全场瞩目!',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'leave_templates' => json_encode([
|
||||
'黄金贵宾{username}乘金色祥云腾空而去,霸气侧漏!',
|
||||
'黄金贵宾{username}微微拱手,金光一闪消失不见',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'sort_order' => 2,
|
||||
'price' => 30,
|
||||
'duration_days' => 30,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'name' => '钻石会员',
|
||||
'icon' => '💎',
|
||||
'color' => '#3b82f6',
|
||||
'exp_multiplier' => 3.0,
|
||||
'jjb_multiplier' => 2.0,
|
||||
'join_templates' => json_encode([
|
||||
'钻石贵宾{username}踏着星辰大海从天而降,万众敬仰!',
|
||||
'钻石贵宾{username}驾驭钻石巨龙破空而来,威震四方!',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'leave_templates' => json_encode([
|
||||
'钻石贵宾{username}化作一道星光冲天而去,令人叹服!',
|
||||
'钻石贵宾{username}乘坐钻石飞船消失在银河尽头',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'sort_order' => 3,
|
||||
'price' => 50,
|
||||
'duration_days' => 30,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'name' => '至尊会员',
|
||||
'icon' => '👑',
|
||||
'color' => '#a855f7',
|
||||
'exp_multiplier' => 5.0,
|
||||
'jjb_multiplier' => 3.0,
|
||||
'join_templates' => json_encode([
|
||||
'👑至尊{username}御驾亲临!九天雷鸣,万物俯首!众卿接驾!',
|
||||
'👑至尊{username}脚踏七彩祥云,身披紫金龙袍,驾临此地!天地为之变色!',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'leave_templates' => json_encode([
|
||||
'👑至尊{username}龙袍一甩,紫气东来,瞬间消失在九天之上!',
|
||||
'👑至尊{username}御驾回宫,百鸟齐鸣相送!',
|
||||
], JSON_UNESCAPED_UNICODE),
|
||||
'sort_order' => 4,
|
||||
'price' => 100,
|
||||
'duration_days' => 30,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除 vip_levels 表
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('vip_levels');
|
||||
}
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:在 users 表添加 vip_level_id 外键
|
||||
* 关联 vip_levels 表,标识用户的会员等级
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 添加 vip_level_id 字段(如果不存在)并建立外键
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
// 列可能已在之前失败的迁移中创建,仅在不存在时添加
|
||||
if (! Schema::hasColumn('users', 'vip_level_id')) {
|
||||
$table->unsignedBigInteger('vip_level_id')->nullable()->after('huiyuan')->comment('会员等级ID');
|
||||
}
|
||||
$table->foreign('vip_level_id')->references('id')->on('vip_levels')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除 vip_level_id 字段
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropForeign(['vip_level_id']);
|
||||
$table->dropColumn('vip_level_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:创建 AI 厂商配置表和 AI 使用日志表
|
||||
*
|
||||
* ai_provider_configs — 存储多个 AI 厂商的 API 配置(密钥、端点、模型等)
|
||||
* ai_usage_logs — 记录每次 AI 接口调用的 token 消耗和响应信息
|
||||
*
|
||||
* @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
|
||||
{
|
||||
/**
|
||||
* 执行迁移:创建 AI 相关的两张数据表
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// AI 厂商配置表
|
||||
Schema::create('ai_provider_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('provider', 50)->index()->comment('厂商标识(如 deepseek, qwen, openai)');
|
||||
$table->string('name', 100)->comment('厂商显示名称');
|
||||
$table->text('api_key')->comment('API Key(加密存储)');
|
||||
$table->string('api_endpoint', 255)->comment('API 端点地址');
|
||||
$table->string('model', 100)->comment('使用的模型名称');
|
||||
$table->decimal('temperature', 3, 2)->default(0.30)->comment('温度参数');
|
||||
$table->integer('max_tokens')->default(2048)->comment('最大 Token 数');
|
||||
$table->boolean('is_enabled')->default(true)->index()->comment('是否启用');
|
||||
$table->boolean('is_default')->default(false)->comment('是否为默认厂商');
|
||||
$table->integer('sort_order')->default(0)->comment('排序(故障转移时按此顺序)');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// AI 使用日志表
|
||||
Schema::create('ai_usage_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete()->comment('使用者');
|
||||
$table->string('provider', 50)->comment('AI 厂商标识');
|
||||
$table->string('model', 100)->comment('使用的模型');
|
||||
$table->string('action', 50)->default('chatbot')->comment('操作类型');
|
||||
$table->integer('prompt_tokens')->default(0)->comment('输入 Token 数');
|
||||
$table->integer('completion_tokens')->default(0)->comment('输出 Token 数');
|
||||
$table->integer('total_tokens')->default(0)->comment('总 Token 数');
|
||||
$table->decimal('cost', 10, 6)->default(0)->comment('费用');
|
||||
$table->integer('response_time_ms')->default(0)->comment('响应时间(毫秒)');
|
||||
$table->boolean('success')->default(true)->comment('是否成功');
|
||||
$table->text('error_message')->nullable()->comment('错误信息');
|
||||
$table->timestamps();
|
||||
|
||||
// 索引
|
||||
$table->index(['provider', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚迁移:删除 AI 相关的两张数据表
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('ai_usage_logs');
|
||||
Schema::dropIfExists('ai_provider_configs');
|
||||
}
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:补充警告(level_warn)和冻结(level_freeze)所需等级的系统参数
|
||||
*
|
||||
* 使管理员各操作均有独立的等级门槛配置。
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 插入警告和冻结等级参数
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$now = now();
|
||||
|
||||
DB::table('sysparam')->insertOrIgnore([
|
||||
[
|
||||
'alias' => 'level_warn',
|
||||
'body' => '5',
|
||||
'guidetxt' => '警告所需等级(管理员可在聊天室警告用户的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
[
|
||||
'alias' => 'level_freeze',
|
||||
'body' => '14',
|
||||
'guidetxt' => '冻结账号所需等级(管理员可冻结用户账号的最低等级)',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除补充的参数记录
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')->whereIn('alias', [
|
||||
'level_warn',
|
||||
'level_freeze',
|
||||
])->delete();
|
||||
}
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:为用户表添加缺失的字段(sign、question、answer)
|
||||
*
|
||||
* 用于个性签名和密码保护问题功能。
|
||||
* 使用 hasColumn 检查避免重复添加。
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 添加 sign、question、answer 字段(已存在则跳过)
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('users', 'sign')) {
|
||||
$table->string('sign', 255)->nullable()->after('sex')->comment('个性签名');
|
||||
}
|
||||
if (! Schema::hasColumn('users', 'question')) {
|
||||
$table->string('question', 100)->nullable()->after('email')->comment('密保问题');
|
||||
}
|
||||
if (! Schema::hasColumn('users', 'answer')) {
|
||||
$table->string('answer', 100)->nullable()->after('question')->comment('密保答案');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除字段
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$columns = [];
|
||||
foreach (['sign', 'question', 'answer'] as $col) {
|
||||
if (Schema::hasColumn('users', $col)) {
|
||||
$columns[] = $col;
|
||||
}
|
||||
}
|
||||
if ($columns) {
|
||||
$table->dropColumn($columns);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:将 users 表中 usersf(头像文件名)的大写后缀 .GIF 全部转为小写 .gif
|
||||
*
|
||||
* 统一文件名大小写,避免 Linux 服务器上因大小写敏感导致图片加载失败。
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 将 usersf 列中的 .GIF 后缀转为 .gif
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('users')
|
||||
->where('usersf', 'LIKE', '%.GIF')
|
||||
->update(['usersf' => DB::raw("REPLACE(usersf, '.GIF', '.gif')")]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:将 .gif 转回 .GIF
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('users')
|
||||
->where('usersf', 'LIKE', '%.gif')
|
||||
->update(['usersf' => DB::raw("REPLACE(usersf, '.gif', '.GIF')")]);
|
||||
}
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:向 sysparam 表插入排行榜显示人数配置项
|
||||
*
|
||||
* 新增 leaderboard_limit 参数,后台可动态配置排行榜每个榜单显示人数。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 插入排行榜显示人数配置记录
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 避免重复插入
|
||||
$exists = DB::table('sysparam')->where('alias', 'leaderboard_limit')->exists();
|
||||
|
||||
if (! $exists) {
|
||||
DB::table('sysparam')->insert([
|
||||
'alias' => 'leaderboard_limit',
|
||||
'body' => '20',
|
||||
'guidetxt' => '🏆 排行榜每榜显示人数',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除配置记录
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')->where('alias', 'leaderboard_limit')->delete();
|
||||
}
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:向 sysparam 表插入金币奖励和经验奖励相关配置项
|
||||
*
|
||||
* 新增 jjb_per_heartbeat(每次心跳金币奖励)配置,
|
||||
* 并更新 exp_per_heartbeat 的描述以说明支持范围格式。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 插入金币奖励配置记录,更新经验描述
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 金币奖励配置(默认0=关闭,可设为 "1" 或 "1-5" 范围格式)
|
||||
if (! DB::table('sysparam')->where('alias', 'jjb_per_heartbeat')->exists()) {
|
||||
DB::table('sysparam')->insert([
|
||||
'alias' => 'jjb_per_heartbeat',
|
||||
'body' => '1-3',
|
||||
'guidetxt' => '💰 每次心跳金币奖励(支持固定值如"1",或范围如"1-5";设为0关闭)',
|
||||
]);
|
||||
}
|
||||
|
||||
// 更新经验配置描述(说明支持范围格式)
|
||||
DB::table('sysparam')
|
||||
->where('alias', 'exp_per_heartbeat')
|
||||
->update(['guidetxt' => '⚡ 每次心跳经验奖励(支持固定值如"1",或范围如"1-10";设为0关闭)']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除金币配置记录
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')->where('alias', 'jjb_per_heartbeat')->delete();
|
||||
|
||||
DB::table('sysparam')
|
||||
->where('alias', 'exp_per_heartbeat')
|
||||
->update(['guidetxt' => '每次心跳经验值']);
|
||||
}
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:创建 gifts 礼物表并填充默认鲜花数据
|
||||
*
|
||||
* 礼物系统支持后台管理多种花/礼物类型,每种有不同的金币消耗和魅力增量。
|
||||
* 图片存放在 public/images/gifts/ 目录下。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 创建 gifts 表并填充默认礼物数据
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('gifts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name', 50)->comment('礼物名称');
|
||||
$table->string('emoji', 10)->comment('显示图标/emoji');
|
||||
$table->string('image', 100)->nullable()->comment('礼物图片路径(相对 /images/gifts/)');
|
||||
$table->unsignedInteger('cost')->default(0)->comment('消耗金币数');
|
||||
$table->unsignedInteger('charm')->default(1)->comment('增加魅力值');
|
||||
$table->unsignedTinyInteger('sort_order')->default(0)->comment('排序(越小越靠前)');
|
||||
$table->boolean('is_active')->default(true)->comment('是否启用');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// 填充默认鲜花数据(图片文件名与 sort_order 对应)
|
||||
DB::table('gifts')->insert([
|
||||
['name' => '小雏菊', 'emoji' => '🌼', 'image' => 'daisy.png', 'cost' => 5, 'charm' => 1, 'sort_order' => 1, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '玫瑰花', 'emoji' => '🌹', 'image' => 'rose.png', 'cost' => 10, 'charm' => 2, 'sort_order' => 2, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '向日葵', 'emoji' => '🌻', 'image' => 'sunflower.png', 'cost' => 20, 'charm' => 5, 'sort_order' => 3, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '樱花束', 'emoji' => '🌸', 'image' => 'sakura.png', 'cost' => 50, 'charm' => 12, 'sort_order' => 4, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '满天星', 'emoji' => '💐', 'image' => 'bouquet.png', 'cost' => 100, 'charm' => 30, 'sort_order' => 5, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '蓝色妖姬', 'emoji' => '🪻', 'image' => 'bluerose.png', 'cost' => 200, 'charm' => 66, 'sort_order' => 6, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
['name' => '钻石花冠', 'emoji' => '👑', 'image' => 'crown.png', 'cost' => 520, 'charm' => 188, 'sort_order' => 7, 'is_active' => true, 'created_at' => now(), 'updated_at' => now()],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除 gifts 表
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('gifts');
|
||||
}
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:填充聊天魅力值相关的系统参数
|
||||
*
|
||||
* 包括:异性聊天魅力值、同性聊天魅力值、每小时魅力上限
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* 插入聊天魅力相关的 sysparam 配置项
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('sysparam')->insert([
|
||||
[
|
||||
'alias' => 'charm_cross_sex',
|
||||
'body' => '2',
|
||||
'guidetxt' => '异性聊天每条消息增加的魅力值(男→女 或 女→男)',
|
||||
],
|
||||
[
|
||||
'alias' => 'charm_same_sex',
|
||||
'body' => '1',
|
||||
'guidetxt' => '同性聊天每条消息增加的魅力值(男→男 或 女→女)',
|
||||
],
|
||||
[
|
||||
'alias' => 'charm_hourly_limit',
|
||||
'body' => '20',
|
||||
'guidetxt' => '每小时通过聊天获取的魅力值上限(防刷屏)',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚:删除聊天魅力相关配置
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
DB::table('sysparam')
|
||||
->whereIn('alias', ['charm_cross_sex', 'charm_same_sex', 'charm_hourly_limit'])
|
||||
->delete();
|
||||
}
|
||||
};
|
||||
91
public/js/effects/effect-manager.js
Normal file
91
public/js/effects/effect-manager.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 文件功能:聊天室特效管理器
|
||||
*
|
||||
* 统一管理全屏 Canvas 特效的入口、防重入和资源清理。
|
||||
* 使用方式:EffectManager.play('fireworks' | 'rain' | 'lightning')
|
||||
*/
|
||||
|
||||
const EffectManager = (() => {
|
||||
// 当前正在播放的特效名称(防止同时播放两个特效)
|
||||
let _current = null;
|
||||
// 全屏 Canvas 元素引用
|
||||
let _canvas = null;
|
||||
|
||||
/**
|
||||
* 获取或创建全屏 Canvas 元素
|
||||
* 属性:fixed 定位,覆盖全屏,pointer-events:none 不阻止用户交互
|
||||
*/
|
||||
function _getCanvas() {
|
||||
if (_canvas && document.body.contains(_canvas)) {
|
||||
return _canvas;
|
||||
}
|
||||
const c = document.createElement("canvas");
|
||||
c.id = "effect-canvas";
|
||||
c.style.cssText = [
|
||||
"position:fixed",
|
||||
"top:0",
|
||||
"left:0",
|
||||
"width:100vw",
|
||||
"height:100vh",
|
||||
"z-index:99999",
|
||||
"pointer-events:none",
|
||||
].join(";");
|
||||
c.width = window.innerWidth;
|
||||
c.height = window.innerHeight;
|
||||
document.body.appendChild(c);
|
||||
_canvas = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* 特效结束后清理 Canvas,重置状态
|
||||
*/
|
||||
function _cleanup() {
|
||||
if (_canvas && document.body.contains(_canvas)) {
|
||||
document.body.removeChild(_canvas);
|
||||
}
|
||||
_canvas = null;
|
||||
_current = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定特效
|
||||
*
|
||||
* @param {string} type 特效类型:fireworks / rain / lightning
|
||||
*/
|
||||
function play(type) {
|
||||
// 防重入:同时只允许一个特效
|
||||
if (_current) {
|
||||
console.log(
|
||||
`[EffectManager] 特效 ${_current} 正在播放,忽略 ${type}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const canvas = _getCanvas();
|
||||
_current = type;
|
||||
|
||||
switch (type) {
|
||||
case "fireworks":
|
||||
if (typeof FireworksEffect !== "undefined") {
|
||||
FireworksEffect.start(canvas, _cleanup);
|
||||
}
|
||||
break;
|
||||
case "rain":
|
||||
if (typeof RainEffect !== "undefined") {
|
||||
RainEffect.start(canvas, _cleanup);
|
||||
}
|
||||
break;
|
||||
case "lightning":
|
||||
if (typeof LightningEffect !== "undefined") {
|
||||
LightningEffect.start(canvas, _cleanup);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.warn(`[EffectManager] 未知特效类型:${type}`);
|
||||
_cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
return { play };
|
||||
})();
|
||||
141
public/js/effects/fireworks.js
Normal file
141
public/js/effects/fireworks.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 文件功能:聊天室烟花特效
|
||||
*
|
||||
* 使用 Canvas 粒子系统在全屏播放多发烟花爆炸动画。
|
||||
* 特效总时长约 4 秒,结束后自动清理并回调。
|
||||
*/
|
||||
|
||||
const FireworksEffect = (() => {
|
||||
// 粒子类:模拟一个爆炸后的发光粒子
|
||||
class Particle {
|
||||
constructor(x, y, color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
// 随机方向和速度
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const speed = Math.random() * 6 + 2;
|
||||
this.vx = Math.cos(angle) * speed;
|
||||
this.vy = Math.sin(angle) * speed;
|
||||
this.alpha = 1;
|
||||
this.gravity = 0.12;
|
||||
this.decay = Math.random() * 0.012 + 0.012; // 透明度每帧衰减量
|
||||
this.radius = Math.random() * 3 + 1;
|
||||
}
|
||||
|
||||
/** 每帧更新粒子位置和状态 */
|
||||
update() {
|
||||
this.vy += this.gravity;
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
this.vx *= 0.98; // 空气阻力
|
||||
this.vy *= 0.98;
|
||||
this.alpha -= this.decay;
|
||||
}
|
||||
|
||||
/** 绘制粒子 */
|
||||
draw(ctx) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = Math.max(0, this.alpha);
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// 预定义烟花颜色组
|
||||
const COLORS = [
|
||||
"#ff4444",
|
||||
"#ff8800",
|
||||
"#ffdd00",
|
||||
"#44ff44",
|
||||
"#44ddff",
|
||||
"#8844ff",
|
||||
"#ff44cc",
|
||||
"#ffffff",
|
||||
"#ffaaaa",
|
||||
"#aaffaa",
|
||||
"#aaaaff",
|
||||
"#ffffaa",
|
||||
];
|
||||
|
||||
/**
|
||||
* 发射一枚烟花,返回粒子数组
|
||||
*
|
||||
* @param {number} x 爆炸中心 x
|
||||
* @param {number} y 爆炸中心 y
|
||||
* @param {number} count 粒子数量
|
||||
* @returns {Particle[]}
|
||||
*/
|
||||
function _burst(x, y, count) {
|
||||
const color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
const particles = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
particles.push(new Particle(x, y, color));
|
||||
}
|
||||
return particles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动烟花特效
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas 全屏 Canvas
|
||||
* @param {Function} onEnd 特效结束回调
|
||||
*/
|
||||
function start(canvas, onEnd) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const DURATION = 4500; // 总时长(ms)
|
||||
|
||||
let particles = [];
|
||||
let animId = null;
|
||||
let launchCount = 0;
|
||||
const MAX_LAUNCHES = 8; // 总共发射几枚烟花
|
||||
|
||||
// 定时发射烟花
|
||||
const launchInterval = setInterval(() => {
|
||||
if (launchCount >= MAX_LAUNCHES) {
|
||||
clearInterval(launchInterval);
|
||||
return;
|
||||
}
|
||||
const x = w * (0.15 + Math.random() * 0.7); // 避免贴近边缘
|
||||
const y = h * (0.1 + Math.random() * 0.5); // 在屏幕上半区爆炸
|
||||
const count = Math.floor(Math.random() * 40) + 60;
|
||||
particles = particles.concat(_burst(x, y, count));
|
||||
launchCount++;
|
||||
}, 500);
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
// 动画循环
|
||||
function animate(now) {
|
||||
// 用半透明黑色覆盖,产生运动拖尾效果
|
||||
ctx.fillStyle = "rgba(0, 0, 0, 0.18)";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// 更新并绘制存活粒子
|
||||
particles = particles.filter((p) => p.alpha > 0.02);
|
||||
particles.forEach((p) => {
|
||||
p.update();
|
||||
p.draw(ctx);
|
||||
});
|
||||
|
||||
if (now - startTime < DURATION) {
|
||||
animId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
// 特效结束:清空 canvas 后回调
|
||||
clearInterval(launchInterval);
|
||||
cancelAnimationFrame(animId);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
return { start };
|
||||
})();
|
||||
126
public/js/effects/lightning.js
Normal file
126
public/js/effects/lightning.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 文件功能:聊天室雷电特效
|
||||
*
|
||||
* 使用递归分叉算法在 Canvas 上绘制真实感闪电路径,
|
||||
* 配合全屏闪白效果模拟雷闪。总持续约 5 秒,结束后回调。
|
||||
*/
|
||||
|
||||
const LightningEffect = (() => {
|
||||
/**
|
||||
* 递归绘制闪电路径(分裂算法)
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
* @param {number} x1 起点 x
|
||||
* @param {number} y1 起点 y
|
||||
* @param {number} x2 终点 x
|
||||
* @param {number} y2 终点 y
|
||||
* @param {number} depth 当前递归深度(控制分叉层数)
|
||||
* @param {number} width 线条宽度
|
||||
*/
|
||||
function _drawBolt(ctx, x1, y1, x2, y2, depth, width) {
|
||||
if (depth <= 0) return;
|
||||
|
||||
// 中点随机偏移(越深层偏移越小,产生流畅感)
|
||||
const mx = (x1 + x2) / 2 + (Math.random() - 0.5) * 80 * depth;
|
||||
const my = (y1 + y2) / 2 + (Math.random() - 0.5) * 20 * depth;
|
||||
const glow = ctx.createLinearGradient(x1, y1, x2, y2);
|
||||
glow.addColorStop(0, "rgba(200, 220, 255, 0.9)");
|
||||
glow.addColorStop(1, "rgba(150, 180, 255, 0.6)");
|
||||
|
||||
ctx.save();
|
||||
ctx.strokeStyle = glow;
|
||||
ctx.lineWidth = width;
|
||||
ctx.shadowColor = "#aaccff";
|
||||
ctx.shadowBlur = 18;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.quadraticCurveTo(mx, my, x2, y2);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
// 递归绘制两段子路径
|
||||
_drawBolt(ctx, x1, y1, mx, my, depth - 1, width * 0.65);
|
||||
_drawBolt(ctx, mx, my, x2, y2, depth - 1, width * 0.65);
|
||||
|
||||
// 随机在中途分叉一条小支路(50% 概率)
|
||||
if (depth > 1 && Math.random() > 0.5) {
|
||||
const bx = mx + (Math.random() - 0.5) * 120;
|
||||
const by = my + Math.random() * 80 + 40;
|
||||
_drawBolt(ctx, mx, my, bx, by, depth - 2, width * 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染一次闪电 + 闪屏效果
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
function _flash(canvas, ctx) {
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
// 闪屏:全屏短暂泛白
|
||||
ctx.fillStyle = "rgba(220, 235, 255, 0.55)";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// 绘制 1-3 条主闪电(从随机顶部位置向下延伸)
|
||||
const boltCount = Math.floor(Math.random() * 2) + 1;
|
||||
for (let i = 0; i < boltCount; i++) {
|
||||
const x1 = w * (0.2 + Math.random() * 0.6);
|
||||
const y1 = 0;
|
||||
const x2 = x1 + (Math.random() - 0.5) * 300;
|
||||
const y2 = h * (0.5 + Math.random() * 0.4);
|
||||
_drawBolt(ctx, x1, y1, x2, y2, 4, 3);
|
||||
}
|
||||
|
||||
// 50ms 后让画布逐渐消退(模拟闪电短促感)
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
}, 80);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动雷电特效
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas 全屏 Canvas
|
||||
* @param {Function} onEnd 特效结束回调
|
||||
*/
|
||||
function start(canvas, onEnd) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const FLASHES = 5; // 总闪电次数
|
||||
const DURATION = 5000; // 总时长(ms)
|
||||
let count = 0;
|
||||
|
||||
// 间隔不规则触发多次闪电(模拟真实雷电节奏)
|
||||
function nextFlash() {
|
||||
if (count >= FLASHES) {
|
||||
// 全部闪完,结束特效
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
onEnd();
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
_flash(canvas, ctx);
|
||||
count++;
|
||||
// 下次闪电间隔:800ms ~ 1200ms 之间随机
|
||||
const delay = 700 + Math.random() * 500;
|
||||
setTimeout(nextFlash, delay);
|
||||
}
|
||||
|
||||
// 短暂延迟后开始第一次闪电
|
||||
setTimeout(nextFlash, 300);
|
||||
|
||||
// 安全兜底:超时强制结束
|
||||
setTimeout(() => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
onEnd();
|
||||
}, DURATION + 500);
|
||||
}
|
||||
|
||||
return { start };
|
||||
})();
|
||||
108
public/js/effects/rain.js
Normal file
108
public/js/effects/rain.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 文件功能:聊天室下雨特效
|
||||
*
|
||||
* 使用 Canvas 绘制斜向雨线,模拟真实降雨视觉效果。
|
||||
* 特效总时长约 8 秒,结束后自动清理并回调。
|
||||
*/
|
||||
|
||||
const RainEffect = (() => {
|
||||
// 雨滴类:一条从顶部往下落的斜线
|
||||
class Drop {
|
||||
constructor(w, h) {
|
||||
this.reset(w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置/初始化雨滴位置
|
||||
*
|
||||
* @param {number} w Canvas 宽度
|
||||
* @param {number} h Canvas 高度
|
||||
*/
|
||||
reset(w, h) {
|
||||
this.x = Math.random() * w;
|
||||
this.y = Math.random() * -h; // 从屏幕上方随机位置开始
|
||||
this.len = Math.random() * 20 + 10; // 雨线长度
|
||||
this.speed = Math.random() * 8 + 6; // 下落速度
|
||||
this.angle = (Math.PI / 180) * (75 + Math.random() * 10); // 倾斜角(接近竖直偏右)
|
||||
this.alpha = Math.random() * 0.3 + 0.2; // 透明度
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
/** 每帧更新雨滴位置 */
|
||||
update() {
|
||||
this.x += Math.cos(this.angle) * this.speed * 0.3;
|
||||
this.y += Math.sin(this.angle) * this.speed;
|
||||
// 落出屏幕后重置
|
||||
if (this.y > this.h + this.len) {
|
||||
this.reset(this.w, this.h);
|
||||
}
|
||||
}
|
||||
|
||||
/** 绘制雨滴线段 */
|
||||
draw(ctx) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = `rgba(155, 200, 255, ${this.alpha})`;
|
||||
ctx.lineWidth = 0.8;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.x, this.y);
|
||||
ctx.lineTo(
|
||||
this.x + Math.cos(this.angle) * this.len,
|
||||
this.y + Math.sin(this.angle) * this.len,
|
||||
);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动下雨特效
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas 全屏 Canvas
|
||||
* @param {Function} onEnd 特效结束回调
|
||||
*/
|
||||
function start(canvas, onEnd) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const DURATION = 8000; // 总时长(ms)
|
||||
const DROP_COUNT = 180; // 雨滴数量
|
||||
|
||||
// 初始化所有雨滴,随机分布在屏幕各处(避免开始时从顶部一起落)
|
||||
const drops = Array.from({ length: DROP_COUNT }, () => {
|
||||
const d = new Drop(w, h);
|
||||
d.y = Math.random() * h; // 初始 Y 随机,不全部从顶部开始
|
||||
return d;
|
||||
});
|
||||
|
||||
let animId = null;
|
||||
const startTime = performance.now();
|
||||
|
||||
// 画"乌云"背景遮罩(让画面有阴暗感但不完全遮住聊天)
|
||||
ctx.fillStyle = "rgba(30, 40, 60, 0.18)";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
function animate(now) {
|
||||
// 用极轻微的透明背景刷新(保留少量拖尾感)
|
||||
ctx.fillStyle = "rgba(30, 40, 60, 0.08)";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
drops.forEach((d) => {
|
||||
d.update();
|
||||
d.draw(ctx);
|
||||
});
|
||||
|
||||
if (now - startTime < DURATION) {
|
||||
animId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
cancelAnimationFrame(animId);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
return { start };
|
||||
})();
|
||||
@@ -63,6 +63,11 @@ export function initChat(roomId) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("chat:screen-cleared", { detail: e }),
|
||||
);
|
||||
})
|
||||
// 监听管理员触发的全屏特效(烟花/下雨/雷电)
|
||||
.listen("EffectBroadcast", (e) => {
|
||||
console.log("特效播放:", e);
|
||||
window.dispatchEvent(new CustomEvent("chat:effect", { detail: e }));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,13 @@
|
||||
|
||||
{{-- ═══════════ 聊天室交互脚本(独立文件维护) ═══════════ --}}
|
||||
@include('chat.partials.user-actions')
|
||||
|
||||
{{-- 全屏特效系统:管理员烟花/下雨/雷电 --}}
|
||||
<script src="/js/effects/effect-manager.js"></script>
|
||||
<script src="/js/effects/fireworks.js"></script>
|
||||
<script src="/js/effects/rain.js"></script>
|
||||
<script src="/js/effects/lightning.js"></script>
|
||||
|
||||
@include('chat.partials.scripts')
|
||||
|
||||
|
||||
|
||||
@@ -77,6 +77,16 @@
|
||||
<button type="button" onclick="adminClearScreen()"
|
||||
style="font-size: 11px; padding: 1px 6px; background: #dc2626; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🧹
|
||||
清屏</button>
|
||||
{{-- 全屏特效按钮组(仅管理员可见) --}}
|
||||
<button type="button" onclick="triggerEffect('fireworks')" title="全屏烟花"
|
||||
style="font-size: 11px; padding: 1px 6px; background: #ea580c; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🎆
|
||||
烟花</button>
|
||||
<button type="button" onclick="triggerEffect('rain')" title="全屏下雨"
|
||||
style="font-size: 11px; padding: 1px 6px; background: #2563eb; color: #fff; border: none; border-radius: 2px; cursor: pointer;">🌧️
|
||||
下雨</button>
|
||||
<button type="button" onclick="triggerEffect('lightning')" title="全屏雷电"
|
||||
style="font-size: 11px; padding: 1px 6px; background: #7c3aed; color: #fff; border: none; border-radius: 2px; cursor: pointer;">⚡
|
||||
雷电</button>
|
||||
@endif
|
||||
|
||||
<button type="button" onclick="location.reload()"
|
||||
|
||||
@@ -614,6 +614,38 @@
|
||||
// DOMContentLoaded 后启动,确保 Vite 编译的 JS 已加载
|
||||
document.addEventListener('DOMContentLoaded', setupScreenClearedListener);
|
||||
|
||||
// ── 全屏特效事件监听(烟花/下雨/雷电)───────────────
|
||||
window.addEventListener('chat:effect', (e) => {
|
||||
const type = e.detail?.type;
|
||||
if (type && typeof EffectManager !== 'undefined') {
|
||||
EffectManager.play(type);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 管理员点击特效按钮,向后端 POST /command/effect
|
||||
*
|
||||
* @param {string} type 特效类型:fireworks / rain / lightning
|
||||
*/
|
||||
function triggerEffect(type) {
|
||||
const roomId = window.chatContext?.roomId;
|
||||
if (!roomId) return;
|
||||
fetch('/command/effect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room_id: roomId,
|
||||
type
|
||||
}),
|
||||
}).then(r => r.json()).then(data => {
|
||||
if (data.status !== 'success') alert(data.message);
|
||||
}).catch(err => console.error('特效触发失败:', err));
|
||||
}
|
||||
window.triggerEffect = triggerEffect;
|
||||
|
||||
// ── 发送消息(Enter 发送) ───────────────────────
|
||||
document.getElementById('content').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
|
||||
@@ -99,6 +99,7 @@ Route::middleware(['chat.auth'])->group(function () {
|
||||
Route::get('/command/whispers/{username}', [AdminCommandController::class, 'viewWhispers'])->name('command.whispers');
|
||||
Route::post('/command/announce', [AdminCommandController::class, 'announce'])->name('command.announce');
|
||||
Route::post('/command/clear-screen', [AdminCommandController::class, 'clearScreen'])->name('command.clear_screen');
|
||||
Route::post('/command/effect', [AdminCommandController::class, 'effect'])->name('command.effect');
|
||||
});
|
||||
|
||||
// 强力特权层中间件:同时验证 chat.auth 登录态 和 chat.level:super 特权(superlevel 由 sysparam 配置)
|
||||
|
||||
Reference in New Issue
Block a user