diff --git a/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue b/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue index 4428888..ea6a1f1 100644 --- a/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/SkinSiteAdminView.vue @@ -6,6 +6,7 @@ import { skinSiteUser } from '@/composables/skin-site-session' import AdminBroadcastPage from './admin/AdminBroadcastPage.vue' import AdminPlayersPage from './admin/AdminPlayersPage.vue' +import AdminReviewPage from './admin/AdminReviewPage.vue' import AdminSummaryPage from './admin/AdminSummaryPage.vue' import AdminUsersPage from './admin/AdminUsersPage.vue' @@ -160,6 +161,7 @@ const current = ref('summary') +

{{ current }}

diff --git a/apps/app-frontend/src/components/ui/skin-site/admin/AdminReviewPage.vue b/apps/app-frontend/src/components/ui/skin-site/admin/AdminReviewPage.vue new file mode 100644 index 0000000..3d17779 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/admin/AdminReviewPage.vue @@ -0,0 +1,466 @@ + + diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index 254f213..0824a3c 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -1032,3 +1032,113 @@ export function skinSiteAdminUserFrequencyLimit( 'admin/users/' + encodeURIComponent(uuid) + '/frequency-limit', ) } + +export type SkinSiteReviewResult = 'PENDING' | 'PASSED' | 'REJECTED' +export type SkinSiteReviewMode = 'AUTO' | 'MANUAL' + +export interface SkinSiteReviewAnswer { + id: string + questionId?: string + values?: string[] + text?: string + score?: number | null + [key: string]: unknown +} + +export interface SkinSiteReviewSubmission { + id: number + reviewVersion: number + revision: number + applicantName: string + username?: string | null + qq?: string | null + userUuid?: string | null + registeredAt?: number | null + mode: SkinSiteReviewMode + result: SkinSiteReviewResult + score?: number | null + targetPermissions: string[] + automaticResults: Record + inviteCode?: string | null + inviteUsed: boolean + reviewerUuid?: string | null + reviewerName?: string | null + createdAt: number + reviewedAt?: number | null + answers: SkinSiteReviewAnswer[] + decisionHistory?: Array<{ + previousResult: SkinSiteReviewResult + result: SkinSiteReviewResult + reviewer: string + reason: string + at: number + permissionNote: string + }> + context?: { + ip: string | null + ipSource: string + userAgent: string + activity?: { + durationMs: number + hiddenMs: number + screenSwitches: number + } | null + } | null +} + +export function skinSiteReviewSubmissions(params?: { + result?: SkinSiteReviewResult | 'COMPLETED' + limit?: number + offset?: number + paperId?: string +}): Promise> { + const query = new URLSearchParams({ + limit: String(params?.limit ?? 20), + offset: String(params?.offset ?? 0), + }) + if (params?.result) query.set('result', params.result) + if (params?.paperId) query.set('paperId', params.paperId) + return skinSiteRequest('review/admin/submissions?' + query.toString()) +} + +export function skinSiteReviewSubmissionCount(params?: { + result?: SkinSiteReviewResult | 'COMPLETED' + paperId?: string +}): Promise> { + const query = new URLSearchParams() + if (params?.result) query.set('result', params.result) + if (params?.paperId) query.set('paperId', params.paperId) + const suffix = query.toString() ? '?' + query.toString() : '' + return skinSiteRequest('review/admin/submissions/count' + suffix) +} + +export function skinSiteDecideReviewSubmission( + id: number, + accepted: boolean, + manualScores: Record = {}, + expectedRevision?: number, + reason = '', +): Promise> { + const body: Record = { accepted, manualScores, reason } + if (expectedRevision !== undefined) body.expectedRevision = expectedRevision + return skinSiteRequest('review/admin/submissions/' + id + '/decision', { + method: 'POST', + body, + }) +} + +export interface SkinSiteReviewInviteDelivery { + email: string + inviteCode: string + emailSent: boolean + emailAttempted: boolean +} + +export function skinSiteRegenerateReviewInvite( + id: number, +): Promise> { + return skinSiteRequest( + 'review/admin/submissions/' + id + '/invite/regenerate', + { method: 'POST' }, + ) +} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 0bc699c..987c2eb 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -11886,5 +11886,48 @@ "app.skin-site.admin.users.permissions": {"message": "Permissions"}, "app.skin-site.admin.users.permissions-hint": {"message": "Comma separated, e.g. WEB_ADMIN, SERVER_ADMIN."}, "app.skin-site.admin.users.no-players": {"message": "No linked players."}, - "app.skin-site.admin.users.registered": {"message": "Registered"} + "app.skin-site.admin.users.registered": {"message": "Registered"}, + "app.skin-site.admin.review.title": {"message": "Player review"}, + "app.skin-site.admin.review.loading": {"message": "Loading…"}, + "app.skin-site.admin.review.failed": {"message": "Could not load submissions."}, + "app.skin-site.admin.review.empty": {"message": "No submissions found."}, + "app.skin-site.admin.review.total": {"message": "{count} submissions"}, + "app.skin-site.admin.review.filter-all": {"message": "All"}, + "app.skin-site.admin.review.filter-pending": {"message": "Pending"}, + "app.skin-site.admin.review.filter-passed": {"message": "Passed"}, + "app.skin-site.admin.review.filter-rejected": {"message": "Rejected"}, + "app.skin-site.admin.review.result-pending": {"message": "Pending"}, + "app.skin-site.admin.review.result-passed": {"message": "Passed"}, + "app.skin-site.admin.review.result-rejected": {"message": "Rejected"}, + "app.skin-site.admin.review.mode-auto": {"message": "Auto"}, + "app.skin-site.admin.review.mode-manual": {"message": "Manual"}, + "app.skin-site.admin.review.applicant": {"message": "Applicant"}, + "app.skin-site.admin.review.submitted-at": {"message": "Submitted"}, + "app.skin-site.admin.review.score": {"message": "Score"}, + "app.skin-site.admin.review.reviewer": {"message": "Reviewer"}, + "app.skin-site.admin.review.details": {"message": "Details"}, + "app.skin-site.admin.review.hide": {"message": "Hide"}, + "app.skin-site.admin.review.answers": {"message": "Answers"}, + "app.skin-site.admin.review.no-answers": {"message": "No answers."}, + "app.skin-site.admin.review.auto-checks": {"message": "Automatic checks"}, + "app.skin-site.admin.review.context": {"message": "Submission context"}, + "app.skin-site.admin.review.ip": {"message": "IP"}, + "app.skin-site.admin.review.user-agent": {"message": "User agent"}, + "app.skin-site.admin.review.activity": {"message": "Activity"}, + "app.skin-site.admin.review.duration": {"message": "Duration"}, + "app.skin-site.admin.review.hidden-time": {"message": "Hidden time"}, + "app.skin-site.admin.review.screen-switches": {"message": "Screen switches"}, + "app.skin-site.admin.review.history": {"message": "Decision history"}, + "app.skin-site.admin.review.accept": {"message": "Accept"}, + "app.skin-site.admin.review.reject": {"message": "Reject"}, + "app.skin-site.admin.review.reason": {"message": "Reason"}, + "app.skin-site.admin.review.accept-done": {"message": "Submission accepted."}, + "app.skin-site.admin.review.reject-done": {"message": "Submission rejected."}, + "app.skin-site.admin.review.regen-invite": {"message": "Regenerate invite"}, + "app.skin-site.admin.review.invite-regenerated": {"message": "Invite code regenerated."}, + "app.skin-site.admin.review.invite-sent": {"message": "Email sent."}, + "app.skin-site.admin.review.invite-not-sent": {"message": "Email not sent."}, + "app.skin-site.admin.review.prev": {"message": "Previous"}, + "app.skin-site.admin.review.next": {"message": "Next"}, + "app.skin-site.admin.review.page": {"message": "Page {page}"} } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 097222b..f056b0c 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -11717,5 +11717,48 @@ "app.skin-site.admin.users.permissions": {"message": "权限"}, "app.skin-site.admin.users.permissions-hint": {"message": "以逗号分隔,例如 WEB_ADMIN, SERVER_ADMIN。"}, "app.skin-site.admin.users.no-players": {"message": "没有绑定的角色。"}, - "app.skin-site.admin.users.registered": {"message": "注册时间"} + "app.skin-site.admin.users.registered": {"message": "注册时间"}, + "app.skin-site.admin.review.title": {"message": "玩家审核"}, + "app.skin-site.admin.review.loading": {"message": "加载中…"}, + "app.skin-site.admin.review.failed": {"message": "无法加载申请列表。"}, + "app.skin-site.admin.review.empty": {"message": "没有找到申请。"}, + "app.skin-site.admin.review.total": {"message": "共 {count} 份申请"}, + "app.skin-site.admin.review.filter-all": {"message": "全部"}, + "app.skin-site.admin.review.filter-pending": {"message": "待处理"}, + "app.skin-site.admin.review.filter-passed": {"message": "已通过"}, + "app.skin-site.admin.review.filter-rejected": {"message": "已拒绝"}, + "app.skin-site.admin.review.result-pending": {"message": "待处理"}, + "app.skin-site.admin.review.result-passed": {"message": "已通过"}, + "app.skin-site.admin.review.result-rejected": {"message": "已拒绝"}, + "app.skin-site.admin.review.mode-auto": {"message": "自动"}, + "app.skin-site.admin.review.mode-manual": {"message": "人工"}, + "app.skin-site.admin.review.applicant": {"message": "申请人"}, + "app.skin-site.admin.review.submitted-at": {"message": "提交时间"}, + "app.skin-site.admin.review.score": {"message": "得分"}, + "app.skin-site.admin.review.reviewer": {"message": "审核人"}, + "app.skin-site.admin.review.details": {"message": "详情"}, + "app.skin-site.admin.review.hide": {"message": "收起"}, + "app.skin-site.admin.review.answers": {"message": "作答内容"}, + "app.skin-site.admin.review.no-answers": {"message": "没有作答记录。"}, + "app.skin-site.admin.review.auto-checks": {"message": "自动检查"}, + "app.skin-site.admin.review.context": {"message": "提交环境"}, + "app.skin-site.admin.review.ip": {"message": "IP"}, + "app.skin-site.admin.review.user-agent": {"message": "浏览器标识"}, + "app.skin-site.admin.review.activity": {"message": "行为记录"}, + "app.skin-site.admin.review.duration": {"message": "时长"}, + "app.skin-site.admin.review.hidden-time": {"message": "切出时长"}, + "app.skin-site.admin.review.screen-switches": {"message": "切屏次数"}, + "app.skin-site.admin.review.history": {"message": "审核记录"}, + "app.skin-site.admin.review.accept": {"message": "通过"}, + "app.skin-site.admin.review.reject": {"message": "拒绝"}, + "app.skin-site.admin.review.reason": {"message": "理由"}, + "app.skin-site.admin.review.accept-done": {"message": "申请已通过。"}, + "app.skin-site.admin.review.reject-done": {"message": "申请已拒绝。"}, + "app.skin-site.admin.review.regen-invite": {"message": "重发邀请码"}, + "app.skin-site.admin.review.invite-regenerated": {"message": "邀请码已重新生成。"}, + "app.skin-site.admin.review.invite-sent": {"message": "邮件已发送。"}, + "app.skin-site.admin.review.invite-not-sent": {"message": "邮件未发送。"}, + "app.skin-site.admin.review.prev": {"message": "上一页"}, + "app.skin-site.admin.review.next": {"message": "下一页"}, + "app.skin-site.admin.review.page": {"message": "第 {page} 页"} } diff --git a/scripts/axolotl/i18n-check.mjs b/scripts/axolotl/i18n-check.mjs index bc2fd15..d3255ff 100644 --- a/scripts/axolotl/i18n-check.mjs +++ b/scripts/axolotl/i18n-check.mjs @@ -32,6 +32,7 @@ const allowedUntranslatedMessages = new Set([ 'LogShare.CN', 'UUID', 'QQ', + 'IP', 'LogShare AI', 'NeoForge', 'OptiFine',