2026-03-01 14:00:38 +08:00
|
|
|
|
{{--
|
|
|
|
|
|
文件功能:后台禁用用户名管理页面(仅站长 id=1 可访问)
|
|
|
|
|
|
|
|
|
|
|
|
管理 username_blacklist.type=permanent 的永久禁止词列表。
|
|
|
|
|
|
用户在注册或使用改名卡时,系统自动查询此表进行拦截。
|
|
|
|
|
|
|
|
|
|
|
|
@extends admin.layouts.app
|
|
|
|
|
|
--}}
|
|
|
|
|
|
@extends('admin.layouts.app')
|
|
|
|
|
|
|
|
|
|
|
|
@section('title', '禁用用户名管理')
|
|
|
|
|
|
|
|
|
|
|
|
@section('content')
|
2026-04-26 18:10:37 +08:00
|
|
|
|
@php require resource_path('views/admin/partials/list-theme.php'); @endphp
|
2026-03-01 14:00:38 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<div class="{{ $adminListPageClass }}">
|
|
|
|
|
|
<div class="{{ $adminListHeaderCardClass }}">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2 class="{{ $adminListHeaderTitleClass }}">🚫 禁用用户名管理</h2>
|
|
|
|
|
|
<p class="{{ $adminListHeaderSubtitleClass }}">
|
|
|
|
|
|
以下词语永久禁止注册或改名使用。临时保留的旧昵称(改名后 30 天)不在此列表中显示。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2" x-data="{
|
2026-03-01 14:04:28 +08:00
|
|
|
|
tab: 'single',
|
2026-03-01 14:00:38 +08:00
|
|
|
|
username: '',
|
2026-03-01 14:04:28 +08:00
|
|
|
|
words: '',
|
2026-03-01 14:00:38 +08:00
|
|
|
|
reason: '',
|
|
|
|
|
|
saving: false,
|
|
|
|
|
|
msg: '',
|
|
|
|
|
|
msgOk: true,
|
2026-03-01 14:04:28 +08:00
|
|
|
|
async addSingle() {
|
2026-03-01 14:00:38 +08:00
|
|
|
|
if (!this.username.trim()) return;
|
|
|
|
|
|
this.saving = true;
|
|
|
|
|
|
this.msg = '';
|
|
|
|
|
|
const res = await fetch('{{ route('admin.forbidden-usernames.store') }}', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ username: this.username, reason: this.reason }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
this.saving = false;
|
|
|
|
|
|
this.msgOk = data.status === 'success';
|
|
|
|
|
|
this.msg = data.message;
|
|
|
|
|
|
if (this.msgOk) { this.username = '';
|
|
|
|
|
|
this.reason = '';
|
|
|
|
|
|
setTimeout(() => location.reload(), 800); }
|
2026-03-01 14:04:28 +08:00
|
|
|
|
},
|
|
|
|
|
|
async addBatch() {
|
|
|
|
|
|
if (!this.words.trim()) return;
|
|
|
|
|
|
this.saving = true;
|
|
|
|
|
|
this.msg = '';
|
|
|
|
|
|
const res = await fetch('{{ route('admin.forbidden-usernames.batch') }}', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ words: this.words, reason: this.reason }),
|
|
|
|
|
|
});
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
this.saving = false;
|
|
|
|
|
|
this.msgOk = data.status === 'success';
|
|
|
|
|
|
this.msg = data.message;
|
|
|
|
|
|
if (this.msgOk) { this.words = '';
|
|
|
|
|
|
this.reason = '';
|
|
|
|
|
|
setTimeout(() => location.reload(), 1200); }
|
2026-03-01 14:00:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}">
|
|
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<div class="{{ $adminListCardClass }} p-5">
|
|
|
|
|
|
{{-- Tab 切换 --}}
|
|
|
|
|
|
<div class="mb-4 -mx-5 flex gap-1 border-b border-gray-200 px-5">
|
|
|
|
|
|
<button @click="tab='single'; msg=''"
|
|
|
|
|
|
:class="tab === 'single' ? 'border-b-2 border-indigo-600 text-indigo-700 font-bold' :
|
|
|
|
|
|
'text-gray-500 hover:text-gray-700'"
|
|
|
|
|
|
class="px-1 pb-2 text-sm transition">➕ 单个添加</button>
|
|
|
|
|
|
<button @click="tab='batch'; msg=''"
|
|
|
|
|
|
:class="tab === 'batch' ? 'border-b-2 border-indigo-600 text-indigo-700 font-bold' :
|
|
|
|
|
|
'text-gray-500 hover:text-gray-700'"
|
|
|
|
|
|
class="px-1 pb-2 text-sm transition">📋 批量添加</button>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
</div>
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
|
{{-- 单个模式 --}}
|
|
|
|
|
|
<div x-show="tab==='single'">
|
|
|
|
|
|
<label class="{{ $adminListFilterLabelClass }}">禁用词 <span class="text-red-500">*</span></label>
|
|
|
|
|
|
<input x-model="username" type="text" maxlength="50" placeholder="如:admin、习近平、fuck…"
|
|
|
|
|
|
@keydown.enter="addSingle()"
|
|
|
|
|
|
class="w-full {{ $adminListFilterInputClass }}">
|
|
|
|
|
|
</div>
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
{{-- 批量模式 --}}
|
|
|
|
|
|
<div x-show="tab==='batch'">
|
|
|
|
|
|
<label class="{{ $adminListFilterLabelClass }}">
|
|
|
|
|
|
批量词语 <span class="text-red-500">*</span>
|
|
|
|
|
|
<span class="text-gray-400 font-normal">(每行一个,或用逗号/空格分隔)</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<textarea x-model="words" rows="6" maxlength="5000"
|
|
|
|
|
|
placeholder="admin root fuck 操,草,傻 …(每行一个或逗号分隔)"
|
|
|
|
|
|
class="w-full resize-y font-mono {{ $adminListFilterInputClass }}"></textarea>
|
|
|
|
|
|
<p class="mt-1 text-xs text-gray-400">
|
|
|
|
|
|
自动去重,跳过已存在的词语,超过50字符的词语忽略
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{{-- 共用原因 --}}
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="{{ $adminListFilterLabelClass }}">禁用原因(选填)</label>
|
|
|
|
|
|
<input x-model="reason" type="text" maxlength="100" placeholder="如:国家领导人姓名 / 攻击性词汇"
|
|
|
|
|
|
class="w-full {{ $adminListFilterInputClass }}">
|
|
|
|
|
|
</div>
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
{{-- 提交按钮 --}}
|
|
|
|
|
|
<button x-show="tab==='single'" @click="addSingle()" :disabled="saving || !username.trim()"
|
|
|
|
|
|
class="w-full rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-indigo-700 disabled:opacity-40">
|
|
|
|
|
|
<span x-text="saving ? '添加中…' : '➕ 添加到禁用列表'"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button x-show="tab==='batch'" @click="addBatch()" :disabled="saving || !words.trim()"
|
|
|
|
|
|
class="w-full rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-indigo-700 disabled:opacity-40">
|
|
|
|
|
|
<span x-text="saving ? '处理中…' : '📋 批量添加到禁用列表'"></span>
|
|
|
|
|
|
</button>
|
2026-03-01 14:04:28 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<p x-show="msg" x-text="msg" :class="msgOk ? 'text-green-600' : 'text-red-500'"
|
|
|
|
|
|
class="text-xs font-bold"></p>
|
|
|
|
|
|
</div>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="{{ $adminListCardClass }} p-5">
|
|
|
|
|
|
<h3 class="{{ $adminListSectionTitleClass }} mb-3">🔍 搜索禁用词</h3>
|
|
|
|
|
|
<form method="GET" action="{{ route('admin.forbidden-usernames.index') }}" class="space-y-3">
|
|
|
|
|
|
<input type="text" name="q" value="{{ $q }}" placeholder="输入关键词搜索…"
|
|
|
|
|
|
class="w-full {{ $adminListFilterInputClass }}">
|
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
|
<button type="submit"
|
|
|
|
|
|
class="flex-1 rounded-lg bg-gray-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-gray-700">
|
|
|
|
|
|
搜索
|
|
|
|
|
|
</button>
|
|
|
|
|
|
@if ($q)
|
|
|
|
|
|
<a href="{{ route('admin.forbidden-usernames.index') }}"
|
|
|
|
|
|
class="{{ $adminListSecondaryButtonClass }}">
|
|
|
|
|
|
✕ 清除
|
|
|
|
|
|
</a>
|
|
|
|
|
|
@endif
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<p class="mt-3 text-xs text-gray-400">
|
|
|
|
|
|
共 <strong>{{ $items->total() }}</strong> 条永久禁用词
|
2026-03-01 14:00:38 +08:00
|
|
|
|
@if ($q)
|
2026-04-26 18:10:37 +08:00
|
|
|
|
(当前筛选"{{ $q }}")
|
2026-03-01 14:00:38 +08:00
|
|
|
|
@endif
|
2026-04-26 18:10:37 +08:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<div class="{{ $adminListCardClass }}">
|
|
|
|
|
|
<div class="{{ $adminListSectionHeadClass }}">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3 class="{{ $adminListSectionTitleClass }}">禁用词列表</h3>
|
|
|
|
|
|
<p class="{{ $adminListSectionDescClass }}">表格、按钮与分页统一到用户管理页风格,保留高风险词的红色提示。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="{{ $adminListTableWrapClass }}">
|
|
|
|
|
|
<table class="{{ $adminListTableClass }}">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr class="{{ $adminListTableHeadRowClass }}">
|
|
|
|
|
|
<th class="{{ $adminListTableHeadCellClass }}">词语</th>
|
|
|
|
|
|
<th class="{{ $adminListTableHeadCellClass }}">禁用原因</th>
|
|
|
|
|
|
<th class="{{ $adminListTableHeadCellClass }}">添加时间</th>
|
|
|
|
|
|
<th class="{{ $adminListTableHeadCellClass }} text-center">操作</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody class="{{ $adminListTableBodyClass }}">
|
|
|
|
|
|
@forelse ($items as $item)
|
|
|
|
|
|
<tr x-data="{
|
2026-03-01 14:00:38 +08:00
|
|
|
|
editing: false,
|
|
|
|
|
|
reason: @js($item->reason ?? ''),
|
|
|
|
|
|
saving: false,
|
|
|
|
|
|
async saveReason() {
|
|
|
|
|
|
this.saving = true;
|
|
|
|
|
|
await fetch('/admin/forbidden-usernames/{{ $item->id }}', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
'X-HTTP-Method-Override': 'PUT',
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ reason: this.reason, _method: 'PUT' }),
|
|
|
|
|
|
});
|
|
|
|
|
|
this.saving = false;
|
|
|
|
|
|
this.editing = false;
|
|
|
|
|
|
},
|
|
|
|
|
|
async destroy() {
|
|
|
|
|
|
if (!confirm('确定要从禁用列表中移除「{{ $item->username }}」吗?')) return;
|
|
|
|
|
|
await fetch('/admin/forbidden-usernames/{{ $item->id }}', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content,
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Accept': 'application/json',
|
|
|
|
|
|
'X-HTTP-Method-Override': 'DELETE',
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ _method: 'DELETE' }),
|
|
|
|
|
|
});
|
|
|
|
|
|
location.reload();
|
|
|
|
|
|
}
|
2026-04-26 18:10:37 +08:00
|
|
|
|
}" class="{{ $adminListTableRowClass }}">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
{{-- 词语 --}}
|
|
|
|
|
|
<td class="px-4 py-3">
|
|
|
|
|
|
<span
|
2026-04-26 18:10:37 +08:00
|
|
|
|
class="inline-flex items-center rounded-full border border-red-200 bg-red-50 px-2 py-0.5 text-xs font-semibold text-gray-800">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
{{ $item->username }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
{{-- 原因(点击编辑) --}}
|
|
|
|
|
|
<td class="px-4 py-3 text-gray-600">
|
|
|
|
|
|
<span x-show="!editing" @click="editing = true" class="cursor-pointer hover:text-indigo-600"
|
|
|
|
|
|
x-text="reason || '(点击添加原因)'"></span>
|
|
|
|
|
|
<div x-show="editing" class="flex items-center gap-2">
|
|
|
|
|
|
<input x-model="reason" type="text" maxlength="100" @keydown.enter="saveReason()"
|
|
|
|
|
|
@keydown.escape="editing = false"
|
2026-04-26 18:10:37 +08:00
|
|
|
|
class="w-48 rounded border border-indigo-300 px-2 py-1 text-xs outline-none focus:ring-1 focus:ring-indigo-400">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
<button @click="saveReason()" :disabled="saving"
|
2026-04-26 18:10:37 +08:00
|
|
|
|
class="{{ $adminListActionButtonClass }} bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
<span x-text="saving ? '…' : '保存'"></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button @click="editing = false"
|
|
|
|
|
|
class="text-xs text-gray-400 hover:text-gray-600">取消</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
{{-- 时间 --}}
|
|
|
|
|
|
<td class="px-4 py-3 text-gray-400 text-xs whitespace-nowrap">
|
|
|
|
|
|
{{ $item->created_at?->format('Y-m-d H:i') ?? '-' }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
{{-- 操作 --}}
|
|
|
|
|
|
<td class="px-4 py-3 text-center">
|
|
|
|
|
|
<button @click="destroy()"
|
2026-04-26 18:10:37 +08:00
|
|
|
|
class="{{ $adminListActionButtonClass }} border border-red-200 text-red-600 hover:border-red-400 hover:bg-red-50">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
🗑 删除
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
@empty
|
|
|
|
|
|
<tr>
|
2026-04-26 18:10:37 +08:00
|
|
|
|
<td colspan="4" class="{{ $adminListEmptyClass }}">
|
2026-03-01 14:00:38 +08:00
|
|
|
|
<p class="text-3xl mb-2">🚫</p>
|
|
|
|
|
|
<p class="font-bold">暂无禁用词</p>
|
|
|
|
|
|
<p class="text-xs mt-1">使用左侧表单添加第一条禁用词</p>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
@endforelse
|
2026-04-26 18:10:37 +08:00
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
|
2026-04-26 18:10:37 +08:00
|
|
|
|
@if ($items->hasPages())
|
|
|
|
|
|
<div class="{{ $adminListPaginationClass }}">
|
|
|
|
|
|
{{ $items->links() }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
@endif
|
2026-03-01 14:00:38 +08:00
|
|
|
|
</div>
|
2026-04-26 18:10:37 +08:00
|
|
|
|
</div>
|
2026-03-01 14:00:38 +08:00
|
|
|
|
|
|
|
|
|
|
@endsection
|