feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型 - 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息 - 撤销:灰色弹窗 + 灰色系统消息,无礼花 - 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏 - 系统消息加随机鼓励语(各5条轮换) - ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds) - 用户名片折叠优化:管理员视野、职务履历均可折叠 - 管理操作 + 职务操作合并为「🔧 管理操作」折叠区 - 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
This commit is contained in:
@@ -52,6 +52,6 @@ class Autoact extends Model
|
||||
*/
|
||||
public function renderText(string $username): string
|
||||
{
|
||||
return str_replace('{username}', $username, $this->text_body);
|
||||
return str_replace('{username}', "【{$username}】", $this->text_body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:部门模型
|
||||
* 对应 departments 表,管理聊天室部门(办公厅 / 迎宾部 / 聊务部 / 宣传部等)
|
||||
* 一个部门下有多个职务(positions)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Department extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'rank',
|
||||
'color',
|
||||
'sort_order',
|
||||
'description',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'rank' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取该部门下的所有职务(按 rank 降序)
|
||||
*/
|
||||
public function positions(): HasMany
|
||||
{
|
||||
return $this->hasMany(Position::class)->orderByDesc('rank');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门当前所有在职用户(通过职务关联)
|
||||
*/
|
||||
public function activeMembers(): Collection
|
||||
{
|
||||
return UserPosition::query()
|
||||
->whereHas('position', fn ($q) => $q->where('department_id', $this->id))
|
||||
->where('is_active', true)
|
||||
->with(['user', 'position'])
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按位阶倒序排列的查询范围
|
||||
*/
|
||||
public function scopeOrdered($query): void
|
||||
{
|
||||
$query->orderBy('sort_order')->orderByDesc('rank');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:开发日志 Model
|
||||
* 对应 dev_changelogs 表,管理版本更新记录
|
||||
* 支持草稿/已发布状态,Markdown 内容渲染
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DevChangelog extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*/
|
||||
protected $fillable = [
|
||||
'version',
|
||||
'title',
|
||||
'type',
|
||||
'content',
|
||||
'is_published',
|
||||
'notify_chat',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型自动转换
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_published' => 'boolean',
|
||||
'notify_chat' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 类型标签配置(中文名 + Tailwind 颜色类)
|
||||
*/
|
||||
public const TYPE_CONFIG = [
|
||||
'feature' => ['label' => '🆕 新功能', 'color' => 'emerald'],
|
||||
'fix' => ['label' => '🐛 修复', 'color' => 'rose'],
|
||||
'improve' => ['label' => '⚡ 优化', 'color' => 'blue'],
|
||||
'other' => ['label' => '📌 其他', 'color' => 'slate'],
|
||||
];
|
||||
|
||||
// ═══════════════ 查询作用域 ═══════════════
|
||||
|
||||
/**
|
||||
* 只查询已发布的日志
|
||||
*/
|
||||
public function scopePublished(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_published', true)->orderByDesc('published_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* 懒加载:查询比指定 ID 更旧的已发布日志(游标分页)
|
||||
*
|
||||
* @param int $afterId 已加载的最后一条 ID
|
||||
*/
|
||||
public function scopeAfter(Builder $query, int $afterId): Builder
|
||||
{
|
||||
return $query->where('id', '<', $afterId);
|
||||
}
|
||||
|
||||
// ═══════════════ 访问器 ═══════════════
|
||||
|
||||
/**
|
||||
* 获取类型对应的中文标签
|
||||
*/
|
||||
public function getTypeLabelAttribute(): string
|
||||
{
|
||||
return self::TYPE_CONFIG[$this->type]['label'] ?? '📌 其他';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型对应的 Tailwind 颜色名
|
||||
*/
|
||||
public function getTypeColorAttribute(): string
|
||||
{
|
||||
return self::TYPE_CONFIG[$this->type]['color'] ?? 'slate';
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Markdown 内容渲染为 HTML(使用 Laravel 内置 Str::markdown)
|
||||
*/
|
||||
public function getContentHtmlAttribute(): string
|
||||
{
|
||||
return Str::markdown($this->content, [
|
||||
'html_input' => 'strip', // 去掉原始 HTML,防止 XSS
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容纯文本摘要(用于列表预览,截取前 150 字)
|
||||
*/
|
||||
public function getSummaryAttribute(): string
|
||||
{
|
||||
// 去掉 Markdown 标记后截取纯文本
|
||||
$plain = strip_tags(Str::markdown($this->content));
|
||||
|
||||
return Str::limit($plain, 150);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:用户反馈主表 Model
|
||||
* 对应 feedback_items 表,管理用户提交的 Bug报告和功能建议
|
||||
* 包含7种处理状态、赞同数/评论数冗余统计
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class FeedbackItem extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'username',
|
||||
'type',
|
||||
'title',
|
||||
'content',
|
||||
'status',
|
||||
'admin_remark',
|
||||
'votes_count',
|
||||
'replies_count',
|
||||
];
|
||||
|
||||
/**
|
||||
* 处理状态配置(中文名 + 图标 + Tailwind 颜色)
|
||||
*/
|
||||
public const STATUS_CONFIG = [
|
||||
'pending' => ['label' => '待处理', 'icon' => '⏳', 'color' => 'gray'],
|
||||
'accepted' => ['label' => '已接受', 'icon' => '✅', 'color' => 'green'],
|
||||
'in_progress' => ['label' => '开发中', 'icon' => '🔧', 'color' => 'blue'],
|
||||
'fixed' => ['label' => '已修复', 'icon' => '🐛', 'color' => 'emerald'],
|
||||
'done' => ['label' => '已完成', 'icon' => '🚀', 'color' => 'emerald'],
|
||||
'rejected' => ['label' => '暂不同意', 'icon' => '❌', 'color' => 'red'],
|
||||
'shelved' => ['label' => '已搁置', 'icon' => '📦', 'color' => 'orange'],
|
||||
];
|
||||
|
||||
/**
|
||||
* 类型配置
|
||||
*/
|
||||
public const TYPE_CONFIG = [
|
||||
'bug' => ['label' => '🐛 Bug报告', 'color' => 'rose'],
|
||||
'suggestion' => ['label' => '💡 功能建议', 'color' => 'blue'],
|
||||
];
|
||||
|
||||
// ═══════════════ 关联关系 ═══════════════
|
||||
|
||||
/**
|
||||
* 关联赞同记录
|
||||
*/
|
||||
public function votes(): HasMany
|
||||
{
|
||||
return $this->hasMany(FeedbackVote::class, 'feedback_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联补充评论
|
||||
*/
|
||||
public function replies(): HasMany
|
||||
{
|
||||
return $this->hasMany(FeedbackReply::class, 'feedback_id')->orderBy('created_at');
|
||||
}
|
||||
|
||||
// ═══════════════ 查询作用域 ═══════════════
|
||||
|
||||
/**
|
||||
* 按类型筛选
|
||||
*
|
||||
* @param string $type bug|suggestion
|
||||
*/
|
||||
public function scopeOfType(Builder $query, string $type): Builder
|
||||
{
|
||||
return $query->where('type', $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按状态筛选
|
||||
*
|
||||
* @param string $status 处理状态
|
||||
*/
|
||||
public function scopeOfStatus(Builder $query, string $status): Builder
|
||||
{
|
||||
return $query->where('status', $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 待处理的反馈(用于后台徽标计数)
|
||||
*/
|
||||
public function scopePending(Builder $query): Builder
|
||||
{
|
||||
return $query->where('status', 'pending');
|
||||
}
|
||||
|
||||
// ═══════════════ 访问器 ═══════════════
|
||||
|
||||
/**
|
||||
* 获取状态对应的配置(标签/图标/颜色)
|
||||
*/
|
||||
public function getStatusConfigAttribute(): array
|
||||
{
|
||||
return self::STATUS_CONFIG[$this->status] ?? self::STATUS_CONFIG['pending'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态中文标签
|
||||
*/
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return $this->status_config['icon'].' '.$this->status_config['label'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取类型中文标签
|
||||
*/
|
||||
public function getTypeLabelAttribute(): string
|
||||
{
|
||||
return self::TYPE_CONFIG[$this->type]['label'] ?? '📌 其他';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断反馈是否在24小时内(用于普通用户自删权限)
|
||||
*/
|
||||
public function getIsWithin24HoursAttribute(): bool
|
||||
{
|
||||
return $this->created_at->diffInHours(now()) < 24;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前状态是否为已处理(已修复/已完成/暂不同意/已搁置)
|
||||
*/
|
||||
public function getIsClosedAttribute(): bool
|
||||
{
|
||||
return in_array($this->status, ['fixed', 'done', 'rejected', 'shelved']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:用户反馈补充评论 Model
|
||||
* 对应 feedback_replies 表,记录用户对反馈的补充说明和管理员官方回复
|
||||
* is_admin=1 的回复在前台以特殊「开发者回复」样式高亮展示
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class FeedbackReply extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*/
|
||||
protected $fillable = [
|
||||
'feedback_id',
|
||||
'user_id',
|
||||
'username',
|
||||
'content',
|
||||
'is_admin',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_admin' => 'boolean',
|
||||
];
|
||||
|
||||
// ═══════════════ 关联关系 ═══════════════
|
||||
|
||||
/**
|
||||
* 关联所属反馈
|
||||
*/
|
||||
public function feedback(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FeedbackItem::class, 'feedback_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:用户反馈赞同记录 Model
|
||||
* 对应 feedback_votes 表,记录用户对反馈的赞同行为
|
||||
* 每个用户每条反馈只能赞同一次(数据库层唯一索引保障)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class FeedbackVote extends Model
|
||||
{
|
||||
/**
|
||||
* 关闭 updated_at(赞同记录只有创建,无需更新时间)
|
||||
*/
|
||||
public const UPDATED_AT = null;
|
||||
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*/
|
||||
protected $fillable = [
|
||||
'feedback_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
// ═══════════════ 关联关系 ═══════════════
|
||||
|
||||
/**
|
||||
* 关联所属反馈
|
||||
*/
|
||||
public function feedback(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FeedbackItem::class, 'feedback_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:职务模型
|
||||
* 对应 positions 表,职务属于某个部门,包含等级、图标、人数上限和奖励上限
|
||||
* 任命权限通过 position_appoint_limits 中间表多对多关联定义
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Position extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'department_id',
|
||||
'name',
|
||||
'icon',
|
||||
'rank',
|
||||
'level',
|
||||
'max_persons',
|
||||
'max_reward',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'rank' => 'integer',
|
||||
'level' => 'integer',
|
||||
'max_persons' => 'integer',
|
||||
'max_reward' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 所属部门
|
||||
*/
|
||||
public function department(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Department::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该职务当前在职的用户记录(user_positions)
|
||||
*/
|
||||
public function activeUserPositions(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserPosition::class)->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该职务的所有历史任职记录
|
||||
*/
|
||||
public function userPositions(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserPosition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该职务可以任命的目标职务列表(position_appoint_limits 中间表)
|
||||
*/
|
||||
public function appointablePositions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
Position::class,
|
||||
'position_appoint_limits',
|
||||
'appointer_position_id',
|
||||
'appointable_position_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 哪些职务的持有者可以将用户任命到本职务
|
||||
*/
|
||||
public function appointedByPositions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
Position::class,
|
||||
'position_appoint_limits',
|
||||
'appointable_position_id',
|
||||
'appointer_position_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前在职人数
|
||||
*/
|
||||
public function currentCount(): int
|
||||
{
|
||||
return $this->activeUserPositions()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已满员
|
||||
*/
|
||||
public function isFull(): bool
|
||||
{
|
||||
if ($this->max_persons === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->currentCount() >= $this->max_persons;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询范围:按位阶降序
|
||||
*/
|
||||
public function scopeOrdered($query): void
|
||||
{
|
||||
$query->orderBy('sort_order')->orderByDesc('rank');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:职务权限使用记录模型
|
||||
* 对应 position_authority_logs 表,记录职务持有者每次行使职权的操作
|
||||
* 包含任命、撤销、奖励金币、警告、踢出、禁言、封IP等操作类型
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PositionAuthorityLog extends Model
|
||||
{
|
||||
/**
|
||||
* 禁用 updated_at(只有 created_at)
|
||||
*/
|
||||
public const UPDATED_AT = null;
|
||||
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'user_position_id',
|
||||
'action_type',
|
||||
'target_user_id',
|
||||
'target_position_id',
|
||||
'amount',
|
||||
'remark',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'amount' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作类型中文标签
|
||||
*/
|
||||
public static array $actionLabels = [
|
||||
'appoint' => '任命',
|
||||
'revoke' => '撤销职务',
|
||||
'reward' => '奖励金币',
|
||||
'warn' => '警告',
|
||||
'kick' => '踢出',
|
||||
'mute' => '禁言',
|
||||
'banip' => '封锁IP',
|
||||
'other' => '其他',
|
||||
];
|
||||
|
||||
/**
|
||||
* 操作人
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作时使用的在职记录
|
||||
*/
|
||||
public function userPosition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserPosition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作对象用户
|
||||
*/
|
||||
public function targetUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'target_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 任命/撤销时的目标职务
|
||||
*/
|
||||
public function targetPosition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Position::class, 'target_position_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作类型的中文标签
|
||||
*/
|
||||
public function getActionLabelAttribute(): string
|
||||
{
|
||||
return self::$actionLabels[$this->action_type] ?? $this->action_type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:在职登录记录模型
|
||||
* 对应 position_duty_logs 表,记录职务持有者每次进房的登录时间、在线时长和退出时间
|
||||
* 用于勤务台四榜统计和个人履历出勤数据展示
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class PositionDutyLog extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'user_position_id',
|
||||
'login_at',
|
||||
'logout_at',
|
||||
'duration_seconds',
|
||||
'ip_address',
|
||||
'room_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'login_at' => 'datetime',
|
||||
'logout_at' => 'datetime',
|
||||
'duration_seconds' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 对应的用户
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对应的在职记录
|
||||
*/
|
||||
public function userPosition(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserPosition::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化在线时长为"Xh Ym"字符串(如 128h 30m)
|
||||
*/
|
||||
public function getFormattedDurationAttribute(): string
|
||||
{
|
||||
$seconds = $this->duration_seconds ?? 0;
|
||||
$hours = intdiv($seconds, 3600);
|
||||
$minutes = intdiv($seconds % 3600, 60);
|
||||
|
||||
return "{$hours}h {$minutes}m";
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ namespace App\Models;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
@@ -144,4 +146,38 @@ class User extends Authenticatable
|
||||
|
||||
return $this->vipLevel?->icon ?? '';
|
||||
}
|
||||
|
||||
// ── 职务相关关联 ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 全部猎务履历(包括历史记录)
|
||||
*/
|
||||
public function positions(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserPosition::class)->with(['position.department'])->orderByDesc('appointed_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前在职职务记录(HasOne,最多一条)
|
||||
*/
|
||||
public function activePosition(): HasOne
|
||||
{
|
||||
return $this->hasOne(UserPosition::class)->where('is_active', true)->with(['position.department']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该用户在职期间的权限操作日志
|
||||
*/
|
||||
public function authorityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(PositionAuthorityLog::class)->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否有当前在职职务
|
||||
*/
|
||||
public function hasActivePosition(): bool
|
||||
{
|
||||
return $this->activePosition()->exists();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:用户职务关联模型(职务履历核心表)
|
||||
* 对应 user_positions 表,记录用户当前在职职务及全部历史任职记录
|
||||
* is_active=true 表示当前在职,false 为历史存档(永久保留)
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class UserPosition extends Model
|
||||
{
|
||||
/**
|
||||
* 允许批量赋值的字段
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'position_id',
|
||||
'appointed_by_user_id',
|
||||
'appointed_at',
|
||||
'remark',
|
||||
'revoked_at',
|
||||
'revoked_by_user_id',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* 字段类型转换
|
||||
*/
|
||||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'appointed_at' => 'datetime',
|
||||
'revoked_at' => 'datetime',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 在职用户
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 所任职务
|
||||
*/
|
||||
public function position(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Position::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任命人
|
||||
*/
|
||||
public function appointedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'appointed_by_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销人
|
||||
*/
|
||||
public function revokedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'revoked_by_user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 该任职期间的登录记录
|
||||
*/
|
||||
public function dutyLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(PositionDutyLog::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该任职期间的权限使用记录
|
||||
*/
|
||||
public function authorityLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(PositionAuthorityLog::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询范围:仅当前在职
|
||||
*/
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任职时长(天数);在职则计算至今
|
||||
*/
|
||||
public function getDurationDaysAttribute(): int
|
||||
{
|
||||
$end = $this->revoked_at ?? now();
|
||||
|
||||
return (int) $this->appointed_at->diffInDays($end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 任职期间累计在线时长(秒)
|
||||
*/
|
||||
public function getTotalOnlineSecondsAttribute(): int
|
||||
{
|
||||
return (int) $this->dutyLogs()->sum('duration_seconds');
|
||||
}
|
||||
|
||||
/**
|
||||
* 任职期间累计发放金币总量
|
||||
*/
|
||||
public function getTotalRewardedCoinsAttribute(): int
|
||||
{
|
||||
return (int) $this->authorityLogs()
|
||||
->where('action_type', 'reward')
|
||||
->sum('amount');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user