功能:禁用词管理支持批量添加

- 新增 ForbiddenUsernameController::batchStore()
  支持换行、逗号、中文逗号、空格多种分隔格式
  自动去重、跳过已存在词语、忽略超长词
  返回成功数/跳过数详细提示
- 新增路由 POST /admin/forbidden-usernames/batch
- View 新增卡片加「单个/批量」两 Tab 切换
  批量 Tab 使用 textarea 多行输入
This commit is contained in:
2026-03-01 14:04:28 +08:00
parent fc495ccceb
commit 632a4240c4
3 changed files with 133 additions and 8 deletions
@@ -25,12 +25,14 @@
{{-- 新增表单 + 搜索栏 --}}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6" x-data="{
tab: 'single',
username: '',
words: '',
reason: '',
saving: false,
msg: '',
msgOk: true,
async addWord() {
async addSingle() {
if (!this.username.trim()) return;
this.saving = true;
this.msg = '';
@@ -50,28 +52,84 @@
if (this.msgOk) { this.username = '';
this.reason = '';
setTimeout(() => location.reload(), 800); }
},
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); }
}
}">
{{-- 新增卡片 --}}
{{-- 新增卡片(单个 + 批量 Tab --}}
<div class="bg-white rounded-xl border border-gray-200 shadow-sm p-5">
<h3 class="font-bold text-gray-700 text-sm mb-3"> 新增禁用词</h3>
{{-- Tab 切换 --}}
<div class="flex border-b border-gray-200 mb-4 -mx-5 px-5 gap-1">
<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="pb-2 text-sm px-1 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="pb-2 text-sm px-1 transition">📋 批量添加</button>
</div>
<div class="space-y-3">
<div>
<label class="text-xs text-gray-500 mb-1 block">禁用词(用户名)<span class="text-red-500">*</span></label>
{{-- 单个模式 --}}
<div x-show="tab==='single'">
<label class="text-xs text-gray-500 mb-1 block">禁用词 <span class="text-red-500">*</span></label>
<input x-model="username" type="text" maxlength="50" placeholder="如:admin、习近平、fuck…"
@keydown.enter="addWord()"
@keydown.enter="addSingle()"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-400 outline-none">
</div>
{{-- 批量模式 --}}
<div x-show="tab==='batch'">
<label class="text-xs text-gray-500 mb-1 block">
批量词语 <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&#10;root&#10;fuck&#10;操,草,傻&#10;…(每行一个或逗号分隔)"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-400 outline-none resize-y font-mono"></textarea>
<p class="text-xs text-gray-400 mt-1">
自动去重,跳过已存在的词语,超过50字符的词语忽略
</p>
</div>
{{-- 共用原因 --}}
<div>
<label class="text-xs text-gray-500 mb-1 block">禁用原因(备注,选填)</label>
<label class="text-xs text-gray-500 mb-1 block">禁用原因(选填)</label>
<input x-model="reason" type="text" maxlength="100" placeholder="如:国家领导人姓名 / 攻击性词汇"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-400 outline-none">
</div>
<button @click="addWord()" :disabled="saving || !username.trim()"
{{-- 提交按钮 --}}
<button x-show="tab==='single'" @click="addSingle()" :disabled="saving || !username.trim()"
class="w-full bg-indigo-600 hover:bg-indigo-700 disabled:opacity-40 text-white rounded-lg px-4 py-2 text-sm font-bold transition">
<span x-text="saving ? '添加中…' : ' 添加到禁用列表'"></span>
</button>
<button x-show="tab==='batch'" @click="addBatch()" :disabled="saving || !words.trim()"
class="w-full bg-indigo-600 hover:bg-indigo-700 disabled:opacity-40 text-white rounded-lg px-4 py-2 text-sm font-bold transition">
<span x-text="saving ? '处理中…' : '📋 批量添加到禁用列表'"></span>
</button>
<p x-show="msg" x-text="msg" :class="msgOk ? 'text-green-600' : 'text-red-500'"
class="text-xs font-bold"></p>
</div>