Files
chatroom/resources/views/admin/appointments/index.blade.php
lkddi 5f30220609 feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型
- 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息
- 撤销:灰色弹窗 + 灰色系统消息,无礼花
- 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏
- 系统消息加随机鼓励语(各5条轮换)
- ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds)
- 用户名片折叠优化:管理员视野、职务履历均可折叠
- 管理操作 + 职务操作合并为「🔧 管理操作」折叠区
- 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
2026-02-28 23:44:38 +08:00

251 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:后台任命管理页面
展示当前所有在职人员,支持新增任命和撤销职务
任命时可搜索用户并选择目标职务
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '任命管理')
@section('content')
<div x-data="{
showForm: false,
username: '',
position_id: '',
remark: '',
searchResults: [],
showDropdown: false,
searching: false,
searchTimer: null,
openAppoint() {
this.username = '';
this.position_id = '';
this.remark = '';
this.searchResults = [];
this.showDropdown = false;
this.showForm = true;
},
async doSearch(val) {
this.username = val;
clearTimeout(this.searchTimer);
if (val.length < 1) {
this.searchResults = [];
this.showDropdown = false;
return;
}
this.searching = true;
this.searchTimer = setTimeout(async () => {
const res = await fetch(`{{ route('admin.appointments.search-users') }}?q=${encodeURIComponent(val)}`);
this.searchResults = await res.json();
this.showDropdown = this.searchResults.length > 0;
this.searching = false;
}, 250);
},
selectUser(u) {
this.username = u.username;
this.showDropdown = false;
this.searchResults = [];
}
}">
{{-- 头部 --}}
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-lg font-bold text-gray-800">任命管理</h2>
<p class="text-sm text-gray-500">管理当前所有在职职位人员,执行任命或撤销操作</p>
</div>
<div class="flex space-x-2">
<a href="{{ route('admin.appointments.history') }}"
style="background-color:#e5e7eb;color:#374151;padding:0.5rem 1rem;border-radius:0.5rem;font-weight:700;font-size:0.875rem;display:inline-flex;align-items:center;text-decoration:none;"
onmouseover="this.style.backgroundColor='#d1d5db'" onmouseout="this.style.backgroundColor='#e5e7eb'">
历史记录
</a>
<button @click="openAppoint()"
style="background-color:#f97316;color:#fff;padding:0.5rem 1.25rem;border-radius:0.5rem;font-weight:700;border:none;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.1);"
onmouseover="this.style.backgroundColor='#ea580c'" onmouseout="this.style.backgroundColor='#f97316'">
+ 新增任命
</button>
</div>
</div>
@if (session('success'))
<div class="mb-4 px-4 py-3 bg-green-50 border border-green-200 text-green-700 rounded-lg text-sm">
{{ session('success') }}</div>
@endif
@if (session('error'))
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm">
{{ session('error') }}</div>
@endif
{{-- 在职人员列表 --}}
<div class="bg-white rounded-xl shadow-sm border overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600 text-xs">
<tr>
<th class="px-4 py-3 text-left">用户</th>
<th class="px-4 py-3 text-left">部门·职务</th>
<th class="px-4 py-3 text-center">等级</th>
<th class="px-4 py-3 text-left">任命人</th>
<th class="px-4 py-3 text-center">任命时间</th>
<th class="px-4 py-3 text-center">在职天数</th>
<th class="px-4 py-3 text-right">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse ($activePositions as $up)
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3">
<div class="font-bold text-gray-800">{{ $up->user->username }}</div>
<div class="text-xs text-gray-400">Lv.{{ $up->user->user_level }}</div>
</td>
<td class="px-4 py-3">
<div class="flex items-center space-x-1">
<span class="text-lg">{{ $up->position->icon }}</span>
<div>
<div class="text-xs text-gray-400">{{ $up->position->department->name }}</div>
<div class="font-bold" style="color: {{ $up->position->department->color }}">
{{ $up->position->name }}
</div>
</div>
</div>
</td>
<td class="px-4 py-3 text-center">
<span class="text-xs bg-orange-100 text-orange-700 px-2 py-0.5 rounded font-mono">
Lv.{{ $up->position->level }}
</span>
</td>
<td class="px-4 py-3 text-gray-600">{{ $up->appointedBy?->username ?? '系统' }}</td>
<td class="px-4 py-3 text-center text-gray-500">
{{ $up->appointed_at->format('Y-m-d') }}
</td>
<td class="px-4 py-3 text-center">
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded">
{{ $up->duration_days }}
</span>
</td>
<td class="px-4 py-3 text-right space-x-1">
<a href="{{ route('admin.appointments.duty-logs', $up->id) }}"
class="text-xs bg-blue-50 text-blue-600 font-bold px-2 py-1 rounded hover:bg-blue-600 hover:text-white transition">
登录日志
</a>
<a href="{{ route('admin.appointments.authority-logs', $up->id) }}"
class="text-xs bg-purple-50 text-purple-600 font-bold px-2 py-1 rounded hover:bg-purple-600 hover:text-white transition">
操作日志
</a>
<form action="{{ route('admin.appointments.revoke', $up->id) }}" method="POST"
class="inline"
onsubmit="return confirm('确定撤销【{{ $up->user->username }}】的【{{ $up->position->name }}】职务?撤销后其等级将归 1。')">
@csrf @method('DELETE')
<button type="submit"
class="text-xs bg-red-50 text-red-600 font-bold px-2 py-1 rounded hover:bg-red-600 hover:text-white transition">
撤销职务
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-12 text-center text-gray-400">暂无在职人员</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- 新增任命弹窗 --}}
<div x-show="showForm" style="display: none;"
class="fixed inset-0 z-50 bg-black/60 flex items-center justify-center p-4">
<div @click.away="showForm = false" class="bg-white rounded-xl shadow-2xl w-full max-w-md" x-transition>
<div
style="background-color:#c2410c;padding:1rem 1.5rem;display:flex;justify-content:space-between;align-items:center;border-radius:0.75rem 0.75rem 0 0;">
<h3 style="font-weight:700;font-size:1.125rem;color:#fff;margin:0;">新增任命</h3>
<button @click="showForm = false"
style="color:#fed7aa;background:none;border:none;font-size:1.5rem;cursor:pointer;line-height:1;"
onmouseover="this.style.color='#fff'" onmouseout="this.style.color='#fed7aa'">&times;</button>
</div>
<div class="p-6">
<form action="{{ route('admin.appointments.store') }}" method="POST">
@csrf
<div class="space-y-4">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">用户名</label>
<div class="relative">
<input type="text" name="username" x-model="username" required
placeholder="输入关键字搜索用户..." @input="doSearch($event.target.value)"
@blur="setTimeout(() => showDropdown = false, 200)"
@focus="if(username.length > 0) doSearch(username)"
class="w-full border rounded-md p-2 text-sm" autocomplete="off">
{{-- 搜索中指示 --}}
<span x-show="searching"
class="absolute right-2 top-2.5 text-gray-400 text-xs">搜索中…</span>
{{-- 下拉结果 --}}
<div x-show="showDropdown" style="display:none;"
class="absolute z-50 w-full bg-white border rounded-md shadow-lg mt-1 max-h-48 overflow-y-auto">
<template x-for="u in searchResults" :key="u.id">
<div @mousedown="selectUser(u)"
class="px-3 py-2 hover:bg-orange-50 cursor-pointer flex justify-between items-center">
<span class="font-bold text-sm" x-text="u.username"></span>
<span class="text-xs text-gray-400" x-text="'Lv.' + u.user_level"></span>
</div>
</template>
<div x-show="searchResults.length === 0 && !searching"
class="px-3 py-2 text-xs text-gray-400">无匹配用户(或已有职务)</div>
</div>
</div>
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">目标职务</label>
<select name="position_id" x-model="position_id" required
class="w-full border rounded-md p-2 text-sm">
<option value="">-- 请选择职务 --</option>
@foreach ($departments as $dept)
<optgroup label="{{ $dept->name }}">
@foreach ($dept->positions as $pos)
@php
$current = $pos->active_user_positions_count ?? 0;
$max = $pos->max_persons;
$isFull = $max && $current >= $max;
$cap = $max
? "在职 {$current}/{$max}" . ($isFull ? ' ⚠️满' : '')
: "在职 {$current} 人·不限额";
@endphp
<option value="{{ $pos->id }}"
{{ $isFull ? 'style=color:#dc2626' : '' }}>
{{ $pos->icon }}
{{ $pos->name }}Lv.{{ $pos->level }}{{ $cap }}
</option>
@endforeach
</optgroup>
@endforeach
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">任命备注(可选)</label>
<input type="text" name="remark" x-model="remark" maxlength="255"
placeholder="例:表现优秀,特此提拔" class="w-full border rounded-md p-2 text-sm">
</div>
</div>
<div
style="display:flex;justify-content:flex-end;gap:0.75rem;padding-top:1rem;margin-top:1rem;border-top:1px solid #e5e7eb;">
<button type="button" @click="showForm = false"
style="padding:0.5rem 1rem;border:1px solid #d1d5db;border-radius:0.375rem;font-weight:500;color:#4b5563;background:#fff;cursor:pointer;"
onmouseover="this.style.background='#f9fafb'"
onmouseout="this.style.background='#fff'">取消</button>
<button type="submit"
style="padding:0.5rem 1rem;background-color:#f97316;color:#fff;border-radius:0.375rem;font-weight:700;border:none;cursor:pointer;box-shadow:0 1px 2px rgba(0,0,0,.1);"
onmouseover="this.style.backgroundColor='#ea580c'"
onmouseout="this.style.backgroundColor='#f97316'">
确认任命
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection