From bc37e5d436dd775778521873ece5fa7c1a7299f3 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Wed, 21 Oct 2026 22:19:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9A=AE=E8=82=A4=E7=AB=99=E7=8E=A9?= =?UTF-8?q?=E5=AE=B6=E7=AE=A1=E7=90=86=EF=BC=88=E6=96=B0=E5=BB=BA/?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D/=E5=88=A0=E9=99=A4/=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E8=A7=92=E8=89=B2/=E5=88=87=E6=8D=A2=E6=AD=A3?= =?UTF-8?q?=E7=89=88=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - skin-site-api.ts 补 setDefault/clearDefault/defaultPlayer/switchMojang/adaptLegacyInternal,并恢复 deleteLauncherRelease - SkinSiteProfileView 增加玩家管理 UI 与状态 - en-US / zh-CN 各补 15 个 profile-view 文案 --- .../ui/skin-site/SkinSiteProfileView.vue | 280 ++++++++++++++++-- .../app-frontend/src/helpers/skin-site-api.ts | 74 +++-- .../app-frontend/src/locales/en-US/index.json | 15 + .../app-frontend/src/locales/zh-CN/index.json | 15 + 4 files changed, 346 insertions(+), 38 deletions(-) diff --git a/apps/app-frontend/src/components/ui/skin-site/SkinSiteProfileView.vue b/apps/app-frontend/src/components/ui/skin-site/SkinSiteProfileView.vue index c4e9fb9..3162ca4 100644 --- a/apps/app-frontend/src/components/ui/skin-site/SkinSiteProfileView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/SkinSiteProfileView.vue @@ -12,9 +12,16 @@ import { skinSiteUser, } from '@/composables/skin-site-session' import { + skinSiteAddPlayer, + skinSiteClearDefaultPlayer, + skinSiteDefaultPlayer, + skinSiteDeletePlayer, skinSiteDeleteTexture, - type SkinSitePlayerSkin, skinSitePlayerSkin, + type SkinSitePlayerSkin, + skinSiteRenamePlayer, + skinSiteSetDefaultPlayer, + skinSiteSwitchMojang, skinSiteUploadTexture, type SkinTextureType, } from '@/helpers/skin-site-api' @@ -51,6 +58,36 @@ const messages = defineMessages({ uploaded: { id: 'app.skin-site.profile-view.uploaded', defaultMessage: 'Texture updated.' }, removed: { id: 'app.skin-site.profile-view.removed', defaultMessage: 'Texture removed.' }, back: { id: 'app.skin-site.profile-view.back', defaultMessage: 'Back to home' }, + addPlayer: { id: 'app.skin-site.profile-view.add-player', defaultMessage: 'New player' }, + playerNamePlaceholder: { + id: 'app.skin-site.profile-view.player-name-placeholder', + defaultMessage: 'Player name', + }, + confirm: { id: 'app.skin-site.profile-view.confirm', defaultMessage: 'Confirm' }, + cancel: { id: 'app.skin-site.profile-view.cancel', defaultMessage: 'Cancel' }, + rename: { id: 'app.skin-site.profile-view.rename', defaultMessage: 'Rename' }, + delete: { id: 'app.skin-site.profile-view.delete', defaultMessage: 'Delete' }, + setDefault: { id: 'app.skin-site.profile-view.set-default', defaultMessage: 'Set as default' }, + clearDefault: { + id: 'app.skin-site.profile-view.clear-default', + defaultMessage: 'Clear default', + }, + toggleMojang: { + id: 'app.skin-site.profile-view.toggle-mojang', + defaultMessage: 'Toggle Mojang', + }, + isDefault: { id: 'app.skin-site.profile-view.is-default', defaultMessage: 'Default' }, + added: { id: 'app.skin-site.profile-view.added', defaultMessage: 'Player added.' }, + renamed: { id: 'app.skin-site.profile-view.renamed', defaultMessage: 'Player renamed.' }, + deleted: { id: 'app.skin-site.profile-view.deleted', defaultMessage: 'Player deleted.' }, + defaultSet: { + id: 'app.skin-site.profile-view.default-set', + defaultMessage: 'Default player updated.', + }, + mojangToggled: { + id: 'app.skin-site.profile-view.mojang-toggled', + defaultMessage: 'Mojang status updated.', + }, }) const selectedId = ref(null) const texture = ref({}) @@ -93,6 +130,141 @@ watch(selectedId, (id) => { else texture.value = {} }) +// --- Player management --- +const defaultUuid = ref(null) +const busy = ref(null) + +async function loadDefaultPlayer() { + if (!signedIn.value) { + defaultUuid.value = null + return + } + try { + const res = await skinSiteDefaultPlayer() + defaultUuid.value = res.success ? (res.payload?.uuid ?? null) : null + } catch { + defaultUuid.value = null + } +} + +const showAdd = ref(false) +const newName = ref('') +const renamingId = ref(null) +const renameValue = ref('') +const confirmingDeleteId = ref(null) + +function resetDialogs() { + showAdd.value = false + newName.value = '' + renamingId.value = null + renameValue.value = '' + confirmingDeleteId.value = null +} + +async function submitAdd() { + const name = newName.value.trim() + if (!name || busy.value) return + busy.value = 'add' + try { + const res = await skinSiteAddPlayer(name) + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.added) }) + resetDialogs() + await requestSkinSitePlayers() + await loadDefaultPlayer() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + +function startRename(player: { uuid: string; name: string }) { + renamingId.value = player.uuid + renameValue.value = player.name +} + +async function submitRename() { + const name = renameValue.value.trim() + if (!renamingId.value || !name || busy.value) return + busy.value = 'rename' + try { + const res = await skinSiteRenamePlayer(renamingId.value, name) + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.renamed) }) + resetDialogs() + await requestSkinSitePlayers() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + +async function submitDelete(uuid: string) { + if (busy.value) return + busy.value = 'delete' + try { + const res = await skinSiteDeletePlayer(uuid) + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.deleted) }) + resetDialogs() + if (selectedId.value === uuid) selectedId.value = null + await requestSkinSitePlayers() + await loadDefaultPlayer() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + +async function setDefaultPlayer(uuid: string) { + if (busy.value) return + busy.value = 'default' + try { + const res = await skinSiteSetDefaultPlayer(uuid) + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.defaultSet) }) + await loadDefaultPlayer() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + +async function clearDefaultPlayer() { + if (busy.value) return + busy.value = 'default' + try { + const res = await skinSiteClearDefaultPlayer() + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.defaultSet) }) + await loadDefaultPlayer() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + +async function toggleMojang(uuid: string) { + if (busy.value) return + busy.value = 'mojang' + try { + const res = await skinSiteSwitchMojang(uuid) + if (!res.success) throw new Error(res.errorMessage) + addNotification({ type: 'success', title: formatMessage(messages.mojangToggled) }) + await requestSkinSitePlayers() + await loadDefaultPlayer() + } catch (error) { + handleError(error) + } finally { + busy.value = null + } +} + async function onUpload(type: SkinTextureType, event: Event) { const input = event.target as HTMLInputElement const file = input.files?.[0] @@ -135,7 +307,10 @@ async function removeTexture(type: SkinTextureType) { } onMounted(() => { - if (signedIn.value) void requestSkinSitePlayers().catch(() => {}) + if (signedIn.value) { + void requestSkinSitePlayers().catch(() => {}) + void loadDefaultPlayer() + } }) watch(signedIn, (value) => { @@ -161,9 +336,46 @@ watch(signedIn, (value) => {
-

- {{ formatMessage(messages.title) }} -

+
+

+ {{ formatMessage(messages.title) }} +

+ +
+ +
+ +
+ + +
+
@@ -172,18 +384,17 @@ watch(signedIn, (value) => {

{{ formatMessage(messages.noPlayers) }}

- +
+ + + + +
+
diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index 3f55443..130094c 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -336,6 +336,40 @@ export function skinSiteRenamePlayer( { method: 'POST', type: 'skin' }, ) } + +/** Mark a player as the account default (used by launcher login). */ +export function skinSiteSetDefaultPlayer(uuid: string): Promise> { + return skinSiteRequest(`player/default/set?uuid=${encodeURIComponent(uuid)}`, { + method: 'POST', + type: 'skin', + }) +} + +/** Clear the account default player. */ +export function skinSiteClearDefaultPlayer(): Promise> { + return skinSiteRequest('player/default', { method: 'DELETE', type: 'skin' }) +} + +/** Fetch the account default player, if one is set. */ +export function skinSiteDefaultPlayer(): Promise> { + return skinSiteRequest('player/default', { type: 'skin' }) +} + +/** Toggle the Mojang (official) status of a player and resync its profile. */ +export function skinSiteSwitchMojang(uuid: string): Promise> { + return skinSiteRequest(`player/${encodeURIComponent(uuid)}/switchMojang`, { + method: 'GET', + type: 'skin', + }) +} + +/** Convert a legacy internal player so it can log in through the modern flow. */ +export function skinSiteAdaptLegacyInternal(uuid: string): Promise> { + return skinSiteRequest(`player/${encodeURIComponent(uuid)}/adaptLegacyInternal`, { + method: 'POST', + type: 'skin', + }) +} // --------------------------------------------------------------------------- // Registration & password reset // --------------------------------------------------------------------------- @@ -414,8 +448,8 @@ export function skinSitePublicDonationSummary(): Promise< return skinSiteRequest('donation/summary', { auth: false }) } -export function skinSiteDonationList(offset = 0): Promise> { - return skinSiteRequest(`donation/list?limit=20&offset=${offset}`, { +export function skinSiteDonationList(offset = 0): Promise> { + return skinSiteRequest(`donation/list?limit=20&offset=${offset}`, { auth: false, }) } @@ -1217,7 +1251,6 @@ export function skinSiteActivateLauncherRelease( export function skinSiteDeleteLauncherRelease(id: number): Promise> { return skinSiteRequest('launcher/admin/' + id, { method: 'DELETE' }) } - export interface SkinSiteLauncherUploadStatus { id: string chunkSize: number @@ -1865,7 +1898,6 @@ export function skinSiteWikiUpdateSettings( }) } - export interface SkinSiteReviewPaperSummary { id: string enabled: boolean @@ -1908,9 +1940,7 @@ export interface SkinSiteReviewConfigData { prerequisitePermissions: string[] } -export function skinSiteReviewPaperList(): Promise< - SkinSiteResponse -> { +export function skinSiteReviewPaperList(): Promise> { return skinSiteRequest('review/admin/papers') } @@ -1991,9 +2021,7 @@ export interface SkinSiteInviteCode { code: string } -export function skinSiteAdminBotInfo( - userUuid: string, -): Promise> { +export function skinSiteAdminBotInfo(userUuid: string): Promise> { return skinSiteRequest('admin/users/' + encodeURIComponent(userUuid) + '/bot') } @@ -2047,7 +2075,9 @@ export function skinSiteAdminUserAudit(params?: { return skinSiteRequest('admin/users/audit' + suffix) } -export function skinSiteAdminInviteUser(email: string): Promise> { +export function skinSiteAdminInviteUser( + email: string, +): Promise> { return skinSiteRequest('admin/inviteCode', { method: 'POST', body: { email }, @@ -2381,10 +2411,10 @@ export function skinSiteChatVoiceConfig(): Promise< export function skinSiteChatVoiceConfigUpdate( data: SkinSiteChatVoiceGlobalConfig, ): Promise> { - return skinSiteRequest( - 'chat/voice/admin/config', - { method: 'PUT', body: data }, - ) + return skinSiteRequest('chat/voice/admin/config', { + method: 'PUT', + body: data, + }) } export function skinSiteChatVoiceLogs( @@ -2503,7 +2533,11 @@ export function skinSiteWikiMigrationCandidates( export function skinSiteWikiMigrationDecide( candidateId: number, - data: { decision: SkinSiteWikiMigrationDecision; targetTitle?: string | null; overwrite?: boolean }, + data: { + decision: SkinSiteWikiMigrationDecision + targetTitle?: string | null + overwrite?: boolean + }, ): Promise> { return skinSiteRequest('wiki/admin/migration/candidates/' + candidateId, { method: 'POST', @@ -2520,9 +2554,7 @@ export function skinSiteWikiMigrationPublish( ) } -export function skinSiteWikiMigrationRollback( - jobId: number, -): Promise> { +export function skinSiteWikiMigrationRollback(jobId: number): Promise> { return skinSiteRequest('wiki/admin/migration/' + jobId + '/rollback', { method: 'POST', }) @@ -2672,7 +2704,9 @@ export function skinSiteWikiPages( return skinSiteRequest('wiki/pages?' + query.toString()) } -export function skinSiteWikiRecent(before?: number): Promise> { +export function skinSiteWikiRecent( + before?: number, +): Promise> { const query = new URLSearchParams() if (before != null) query.set('before', String(before)) return skinSiteRequest('wiki/recent?' + query.toString()) diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index a2512e0..cc8935b 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -65,24 +65,39 @@ "app.skin-site.map.open": { "message": "Open in browser" }, "app.skin-site.map.server": { "message": "Server" }, "app.skin-site.map.title": { "message": "Server map" }, + "app.skin-site.profile-view.add-player": { "message": "New player" }, + "app.skin-site.profile-view.added": { "message": "Player added." }, "app.skin-site.profile-view.back": { "message": "Back to home" }, + "app.skin-site.profile-view.cancel": { "message": "Cancel" }, "app.skin-site.profile-view.cape": { "message": "Cape" }, + "app.skin-site.profile-view.clear-default": { "message": "Clear default" }, + "app.skin-site.profile-view.confirm": { "message": "Confirm" }, + "app.skin-site.profile-view.default-set": { "message": "Default player updated." }, + "app.skin-site.profile-view.delete": { "message": "Delete" }, + "app.skin-site.profile-view.deleted": { "message": "Player deleted." }, "app.skin-site.profile-view.elytra": { "message": "Elytra" }, + "app.skin-site.profile-view.is-default": { "message": "Default" }, "app.skin-site.profile-view.loading": { "message": "Loading players…" }, + "app.skin-site.profile-view.mojang-toggled": { "message": "Mojang status updated." }, "app.skin-site.profile-view.no-players": { "message": "No players yet." }, + "app.skin-site.profile-view.player-name-placeholder": { "message": "Player name" }, "app.skin-site.profile-view.readonly": { "message": "Mojang accounts are managed by Mojang; textures cannot be edited here." }, "app.skin-site.profile-view.remove": { "message": "Remove" }, "app.skin-site.profile-view.removed": { "message": "Texture removed." }, + "app.skin-site.profile-view.rename": { "message": "Rename" }, + "app.skin-site.profile-view.renamed": { "message": "Player renamed." }, "app.skin-site.profile-view.select-hint": { "message": "Select a player to preview and edit its textures." }, + "app.skin-site.profile-view.set-default": { "message": "Set as default" }, "app.skin-site.profile-view.signed-out": { "message": "Sign in to manage your players." }, "app.skin-site.profile-view.skin": { "message": "Skin" }, "app.skin-site.profile-view.title": { "message": "Your players" }, + "app.skin-site.profile-view.toggle-mojang": { "message": "Toggle Mojang" }, "app.skin-site.profile-view.upload": { "message": "Upload" }, "app.skin-site.profile-view.uploaded": { "message": "Texture updated." }, "app.skin-site.profile.avatar": { "message": "Avatar" }, diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index dcba070..531c97c 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -61,24 +61,39 @@ "app.skin-site.map.open": { "message": "在浏览器中打开" }, "app.skin-site.map.server": { "message": "服务器" }, "app.skin-site.map.title": { "message": "服务器地图" }, + "app.skin-site.profile-view.add-player": { "message": "新建角色" }, + "app.skin-site.profile-view.added": { "message": "角色已添加。" }, "app.skin-site.profile-view.back": { "message": "返回主页" }, + "app.skin-site.profile-view.cancel": { "message": "取消" }, "app.skin-site.profile-view.cape": { "message": "披风" }, + "app.skin-site.profile-view.clear-default": { "message": "取消默认" }, + "app.skin-site.profile-view.confirm": { "message": "确认" }, + "app.skin-site.profile-view.default-set": { "message": "默认角色已更新。" }, + "app.skin-site.profile-view.delete": { "message": "删除" }, + "app.skin-site.profile-view.deleted": { "message": "角色已删除。" }, "app.skin-site.profile-view.elytra": { "message": "鞘翅" }, + "app.skin-site.profile-view.is-default": { "message": "默认" }, "app.skin-site.profile-view.loading": { "message": "正在加载角色…" }, + "app.skin-site.profile-view.mojang-toggled": { "message": "正版状态已更新。" }, "app.skin-site.profile-view.no-players": { "message": "暂无角色。" }, + "app.skin-site.profile-view.player-name-placeholder": { "message": "角色名" }, "app.skin-site.profile-view.readonly": { "message": "Mojang 账号由 Mojang 管理,无法在此修改材质。" }, "app.skin-site.profile-view.remove": { "message": "移除" }, "app.skin-site.profile-view.removed": { "message": "材质已移除。" }, + "app.skin-site.profile-view.rename": { "message": "重命名" }, + "app.skin-site.profile-view.renamed": { "message": "角色已重命名。" }, "app.skin-site.profile-view.select-hint": { "message": "选择角色以预览和修改其材质。" }, + "app.skin-site.profile-view.set-default": { "message": "设为默认" }, "app.skin-site.profile-view.signed-out": { "message": "登录后管理你的角色。" }, "app.skin-site.profile-view.skin": { "message": "皮肤" }, "app.skin-site.profile-view.title": { "message": "我的角色" }, + "app.skin-site.profile-view.toggle-mojang": { "message": "切换正版" }, "app.skin-site.profile-view.upload": { "message": "上传" }, "app.skin-site.profile-view.uploaded": { "message": "材质已更新。" }, "app.skin-site.profile.avatar": { "message": "头像" },