diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index bc43254..3c07d15 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -141,6 +141,7 @@ import {
isNetworkMetered,
setRestartAfterPendingUpdate,
} from '@/helpers/utils.js'
+import { installUiScale } from '@/helpers/ui-scale'
import { start_join_server, start_join_singleplayer_world } from '@/helpers/worlds.ts'
import i18n, { resolveInitialLocale } from '@/i18n.config'
import {
@@ -1273,6 +1274,8 @@ provide('previewMinecraftCrashModal', () => minecraftCrashModal.value?.showPrevi
provide('showLauncherPopup', (_request: unknown) => {})
provide('showStartupAnnouncement', () => startupAnnouncementModal.value?.show())
+installUiScale()
+
const stateFailed = ref(false)
stateInitialization
.then(() => {
diff --git a/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue b/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
index c6ff9f8..506a848 100644
--- a/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
+++ b/apps/app-frontend/src/components/ui/settings/AppearanceSettings.vue
@@ -26,6 +26,7 @@ import { computed, ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.ts'
import { getOS } from '@/helpers/utils'
+import { setUiScale, uiScale } from '@/helpers/ui-scale'
import { useTheming } from '@/store/state'
import {
type AccentColor,
@@ -209,6 +210,23 @@ const messages = defineMessages({
defaultMessage:
'Enables advanced rendering such as blur effects that may cause performance issues without hardware-accelerated rendering.',
},
+ uiScaleTitle: {
+ id: 'app.appearance-settings.ui-scale.title',
+ defaultMessage: 'Launcher scale',
+ },
+ uiScaleDescription: {
+ id: 'app.appearance-settings.ui-scale.description',
+ defaultMessage:
+ 'Scale the whole launcher interface. A smaller scale fits more content, which helps when embedded pages are cut off.',
+ },
+ uiScaleCustom: {
+ id: 'app.appearance-settings.ui-scale.custom',
+ defaultMessage: 'Custom',
+ },
+ uiScalePercent: {
+ id: 'app.appearance-settings.ui-scale.percent',
+ defaultMessage: '{value}%',
+ },
pageTransitionsTitle: {
id: 'app.appearance-settings.page-transitions.title',
defaultMessage: 'Page transition animations',
@@ -1020,6 +1038,43 @@ watch(
/>
+
+
+
+ {{ formatMessage(messages.uiScaleTitle) }}
+
+
+ {{ formatMessage(messages.uiScaleDescription) }}
+
+
+
+
+
+
setUiScale(Number(value))"
+ />
+
+
+
diff --git a/apps/app-frontend/src/helpers/ui-scale.ts b/apps/app-frontend/src/helpers/ui-scale.ts
new file mode 100644
index 0000000..1e606eb
--- /dev/null
+++ b/apps/app-frontend/src/helpers/ui-scale.ts
@@ -0,0 +1,60 @@
+import { ref, watch } from 'vue'
+
+/**
+ * 启动器界面缩放(百分比)。
+ * 这是纯本地的显示偏好,用 localStorage 保存即可,无需写入后端设置数据库。
+ */
+const UI_SCALE_STORAGE_KEY = 'starlight-ui-scale'
+const MIN_UI_SCALE = 50
+const MAX_UI_SCALE = 200
+const DEFAULT_UI_SCALE = 100
+
+/** 把缩放值限制在允许范围内。 */
+function clampUiScale(value: number): number {
+ if (!Number.isFinite(value)) return DEFAULT_UI_SCALE
+ return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(value)))
+}
+
+/** 从 localStorage 读取已保存的缩放值。 */
+function readStoredUiScale(): number {
+ try {
+ const raw = localStorage.getItem(UI_SCALE_STORAGE_KEY)
+ if (raw === null) return DEFAULT_UI_SCALE
+ return clampUiScale(Number(raw))
+ } catch {
+ return DEFAULT_UI_SCALE
+ }
+}
+
+export const uiScale = ref(readStoredUiScale())
+
+export function setUiScale(value: number): void {
+ uiScale.value = clampUiScale(value)
+}
+
+export function resetUiScale(): void {
+ setUiScale(DEFAULT_UI_SCALE)
+}
+
+export { DEFAULT_UI_SCALE, MAX_UI_SCALE, MIN_UI_SCALE }
+
+/** 把缩放应用到文档根节点,并在变化时持久化。 */
+export function installUiScale(): void {
+ const apply = (value: number) => {
+ const factor = String(value / 100)
+ const root = document.documentElement
+ root.style.setProperty('--ui-scale', factor)
+ // 用 CSS zoom 整体缩放界面(含内嵌网页),在 WebView2/WebKit 上最可靠。
+ root.style.setProperty('zoom', factor)
+ }
+
+ apply(uiScale.value)
+ watch(uiScale, (value) => {
+ apply(value)
+ try {
+ localStorage.setItem(UI_SCALE_STORAGE_KEY, String(value))
+ } catch (error) {
+ console.warn('保存界面缩放失败', error)
+ }
+ })
+}
\ No newline at end of file