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 @@ + + + + + + + 玩家ID * + + + + + 游戏/平台 * + + + + + 标签 * + + + + + + + 自定义标签 + + + + + 严重程度 * + + + + + + + 描述 * + + + + + + 取消 + + {{ loading ? '提交中...' : '提交' }} + + + + + + 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 @@ + + + + + + 玩家黑名单 + + + + + + + + + + + + + + + + 标记玩家 + + + + + + + + + + + + + + {{ group.playerId }} + {{ group.platform }} + {{ group.entries.length }} 次标记 + + + + {{ PlayerTagMap[tag] }} + + + {{ entry }} + + + + {{ group.entries[0].description }} + + + + + + + + + + + + + + + {{ reporterName(entry).charAt(0) }} + {{ reporterName(entry) }} + + + {{ formatTime(entry.created) }} + + + + + + + + + + + + + + {{ PlayerTagMap[tag] }} + {{ entry.customTag }} + {{ BlacklistSeverityMap[entry.severity] }} + + {{ entry.description }} + + + + + + + 加载中... + + + + + + + + 暂无玩家黑名单记录 + 标记坑人的玩家,下次遇到有准备 + + + + + 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 @@
加载中...
暂无玩家黑名单记录
标记坑人的玩家,下次遇到有准备