From d52835886731b80fa7569bf351705528750eb72e Mon Sep 17 00:00:00 2001 From: congsh Date: Sun, 19 Apr 2026 13:03:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=8E=A9=E5=AE=B6=E9=BB=91=E5=90=8D?= =?UTF-8?q?=E5=8D=95=20-=20=E8=AE=B0=E5=BD=95=E5=A4=96=E9=83=A8=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=E5=9D=91=E7=8E=A9=E5=AE=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 player_blacklist collection 迁移 - 添加 PlayerTag/PlayerBlacklistEntry 类型定义和 API - 创建 PlayerBlacklistMain + CreatePlayerBlacklistDialog 组件 - BlacklistView 支持 Tab 切换游戏/玩家黑名单 - 支持搜索、标签筛选、严重程度筛选、实时订阅 Co-Authored-By: Claude Opus 4.7 --- .../1776530001_created_player_blacklist.js | 124 ++++ frontend/src/api/playerBlacklist.ts | 59 ++ .../CreatePlayerBlacklistDialog.vue | 220 ++++++ .../playerBlacklist/PlayerBlacklistMain.vue | 694 ++++++++++++++++++ frontend/src/types/index.ts | 33 + frontend/src/views/BlacklistView.vue | 76 +- 6 files changed, 1202 insertions(+), 4 deletions(-) create mode 100644 backend/pb_migrations/1776530001_created_player_blacklist.js create mode 100644 frontend/src/api/playerBlacklist.ts create mode 100644 frontend/src/components/playerBlacklist/CreatePlayerBlacklistDialog.vue create mode 100644 frontend/src/components/playerBlacklist/PlayerBlacklistMain.vue diff --git a/backend/pb_migrations/1776530001_created_player_blacklist.js b/backend/pb_migrations/1776530001_created_player_blacklist.js new file mode 100644 index 0000000..676677d --- /dev/null +++ b/backend/pb_migrations/1776530001_created_player_blacklist.js @@ -0,0 +1,124 @@ +/// +migrate((db) => { + const collection = new Collection({ + "id": "pblacklist_col", + "created": "2026-04-19 10:00:01.000Z", + "updated": "2026-04-19 10:00:01.000Z", + "name": "player_blacklist", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "pb_group", + "name": "group", + "type": "relation", + "required": true, + "options": { + "collectionId": "es63bkyiblpnxdf", + "cascadeDelete": true, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "pb_reporter", + "name": "reporter", + "type": "relation", + "required": true, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": true, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + }, + { + "system": false, + "id": "pb_playerid", + "name": "playerId", + "type": "text", + "required": true, + "options": { + "min": 1, + "max": 200, + "pattern": "" + } + }, + { + "system": false, + "id": "pb_platform", + "name": "platform", + "type": "text", + "required": true, + "options": { + "min": 1, + "max": 100, + "pattern": "" + } + }, + { + "system": false, + "id": "pb_tags", + "name": "tags", + "type": "select", + "required": true, + "options": { + "maxSelect": 5, + "values": ["afk", "feeder", "toxic", "cheater", "quitter", "noob", "fragile", "other"] + } + }, + { + "system": false, + "id": "pb_customtag", + "name": "customTag", + "type": "text", + "required": false, + "options": { + "min": null, + "max": 50, + "pattern": "" + } + }, + { + "system": false, + "id": "pb_desc", + "name": "description", + "type": "text", + "required": true, + "options": { + "min": 1, + "max": 500, + "pattern": "" + } + }, + { + "system": false, + "id": "pb_severity", + "name": "severity", + "type": "select", + "required": true, + "options": { + "maxSelect": 1, + "values": ["mild", "medium", "severe"] + } + } + ], + "indexes": [], + "listRule": "@request.auth.id != \"\" && group.members ~ @request.auth.id", + "viewRule": "@request.auth.id != \"\" && group.members ~ @request.auth.id", + "createRule": "@request.auth.id != \"\" && group.members ~ @request.auth.id", + "updateRule": null, + "deleteRule": "reporter = @request.auth.id || group.owner = @request.auth.id", + "options": {} + }); + + return Dao(db).saveCollection(collection); +}, (db) => { + const dao = new Dao(db); + const collection = dao.findCollectionByNameOrId("pblacklist_col"); + return dao.deleteCollection(collection); +}) diff --git a/frontend/src/api/playerBlacklist.ts b/frontend/src/api/playerBlacklist.ts new file mode 100644 index 0000000..e14c83f --- /dev/null +++ b/frontend/src/api/playerBlacklist.ts @@ -0,0 +1,59 @@ +import { pb } from './pocketbase' +import type { PlayerBlacklistEntry } from '@/types' + +export async function createPlayerBlacklistEntry(data: { + group: string + playerId: string + platform: string + tags: string[] + customTag?: string + description: string + severity: string +}): Promise { + const user = pb.authStore.model + if (!user) throw new Error('未登录') + + const payload: Record = { + group: data.group, + reporter: user.id, + playerId: data.playerId, + platform: data.platform, + tags: data.tags, + description: data.description, + severity: data.severity, + } + if (data.customTag) payload.customTag = data.customTag + + const record = await pb.collection('player_blacklist').create(payload) + return record as unknown as PlayerBlacklistEntry +} + +export async function listPlayerBlacklist( + groupId: string, + options?: { tag?: string; severity?: string } +): Promise { + let filter = `group="${groupId}"` + if (options?.tag) filter += ` && tags~"${options.tag}"` + if (options?.severity) filter += ` && severity="${options.severity}"` + + const result = await pb.collection('player_blacklist').getFullList({ + filter, + sort: '-created', + expand: 'reporter', + $autoCancel: false, + }) + return result as unknown as PlayerBlacklistEntry[] +} + +export async function deletePlayerBlacklistEntry(entryId: string): Promise { + await pb.collection('player_blacklist').delete(entryId) +} + +export async function subscribePlayerBlacklist( + groupId: string, + callback: (data: any) => void +): Promise<() => void> { + return pb.collection('player_blacklist').subscribe('*', (data) => { + if (data.record?.group === groupId) callback(data) + }) +} diff --git a/frontend/src/components/playerBlacklist/CreatePlayerBlacklistDialog.vue b/frontend/src/components/playerBlacklist/CreatePlayerBlacklistDialog.vue new file mode 100644 index 0000000..f4e7df3 --- /dev/null +++ b/frontend/src/components/playerBlacklist/CreatePlayerBlacklistDialog.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/frontend/src/components/playerBlacklist/PlayerBlacklistMain.vue b/frontend/src/components/playerBlacklist/PlayerBlacklistMain.vue new file mode 100644 index 0000000..fdb7c46 --- /dev/null +++ b/frontend/src/components/playerBlacklist/PlayerBlacklistMain.vue @@ -0,0 +1,694 @@ + + + + + diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 2c20b8c..75a03b3 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -345,6 +345,20 @@ export const BlacklistReasonMap: Record = { other: '其他' } +// 玩家黑名单标签 +export type PlayerTag = 'afk' | 'feeder' | 'toxic' | 'cheater' | 'quitter' | 'noob' | 'fragile' | 'other' + +export const PlayerTagMap: Record = { + afk: '挂机', + feeder: '送人头', + toxic: '喷人', + cheater: '外挂', + quitter: '始乱终弃', + noob: '坑货', + fragile: '玻璃心', + other: '其他' +} + // 黑名单严重程度 export type BlacklistSeverity = 'mild' | 'medium' | 'severe' @@ -373,6 +387,25 @@ export interface BlacklistEntry { } } +// 玩家黑名单 +export interface PlayerBlacklistEntry { + id: string + group: string + reporter: string + playerId: string + platform: string + tags: PlayerTag[] + customTag?: string + description: string + severity: BlacklistSeverity + created: string + updated: string + expand?: { + reporter?: User + group?: Group + } +} + // 竞猜状态 export type BetStatus = 'open' | 'closed' | 'settled' diff --git a/frontend/src/views/BlacklistView.vue b/frontend/src/views/BlacklistView.vue index 3133043..0459557 100644 --- a/frontend/src/views/BlacklistView.vue +++ b/frontend/src/views/BlacklistView.vue @@ -1,9 +1,10 @@