feat: 添加主题管理功能,更新系统设置页面,优化用户体验

This commit is contained in:
2025-12-20 03:48:01 +07:00
parent 916cbe9d24
commit 5c06c7ce0a
14 changed files with 196 additions and 70 deletions

View File

@@ -592,7 +592,7 @@ const stickyStyle = computed(() => {
}
/* Dark mode 支持 */
@media (prefers-color-scheme: dark) {
.ion-palette-dark {
.ui-tabs {
--ui-tabs-primary: var(--ion-color-primary, #ffffff);
--ui-tabs-primary-rgb: var(--ion-color-primary-rgb, 255, 255, 255);

View File

@@ -0,0 +1,26 @@
import { usePreferredDark } from "@vueuse/core";
export type ThemeMode = "light" | "dark" | "auto";
const STORAGE_KEY = "app-theme-mode";
export function useTheme() {
const prefersDark = usePreferredDark();
const theme = useStorage<ThemeMode>(STORAGE_KEY, "auto");
const isDark = computed(() => {
if (theme.value === "auto") {
return prefersDark.value;
}
return theme.value === "dark";
});
watch(isDark, (dark) => {
document.documentElement.classList.toggle("ion-palette-dark", dark);
}, { immediate: true });
return {
theme,
isDark,
};
}

View File

@@ -315,6 +315,11 @@
"updateNow": "Update Now",
"alreadyLatest": "Already up to date",
"checkUpdateFailed": "Failed to check for updates",
"languageTitle": "Language / 语言"
"languageTitle": "Language / 语言",
"theme": "Theme",
"themeTitle": "Appearance",
"themeLight": "Light",
"themeDark": "Dark",
"themeAuto": "Auto"
}
}

View File

@@ -315,6 +315,11 @@
"updateNow": "立即更新",
"alreadyLatest": "已是最新版本",
"checkUpdateFailed": "检查更新失败",
"languageTitle": "语言 / Language"
"languageTitle": "语言 / Language",
"theme": "主题",
"themeTitle": "外观主题",
"themeLight": "浅色",
"themeDark": "深色",
"themeAuto": "跟随系统"
}
}

View File

@@ -31,8 +31,8 @@ import "@ionic/vue/css/display.css";
*/
// import "@ionic/vue/css/palettes/dark.always.css";
// import "@ionic/vue/css/palettes/dark.class.css";
import "@ionic/vue/css/palettes/dark.system.css";
import "@ionic/vue/css/palettes/dark.class.css";
// import "@ionic/vue/css/palettes/dark.system.css";
/* Theme variables */
import "./theme/index.css";

View File

@@ -65,6 +65,10 @@ const routes: Array<RouteRecordRaw> = [
path: "language",
component: () => import("@/views/system-settings/language.vue"),
},
{
path: "theme",
component: () => import("@/views/system-settings/theme.vue"),
},
],
},
{

View File

@@ -24,7 +24,7 @@ ion-datetime.ui-datetime {
border-radius: 16px;
overflow: hidden;
}
@media (prefers-color-scheme: dark) {
.ion-palette-dark {
ion-datetime.ui-datetime {
--background: rgb(15, 15, 15);
--background-rgb: 15, 15, 15;
@@ -32,4 +32,9 @@ ion-datetime.ui-datetime {
--wheel-highlight-border-radius: 48px;
--wheel-fade-background-rgb: 15, 15, 15;
}
}
:root.ios {
--ion-padding: 20px;
--ion-margin: 20px;
}

View File

@@ -1,6 +1,6 @@
/* For information on how to create your own theme, please refer to:
http://ionicframework.com/docs/theming/ */
:root {
html:root {
--ion-color-primary: #0d0d0d;
--ion-color-primary-rgb: 13, 13, 13;
--ion-color-primary-contrast: #ffffff;
@@ -120,72 +120,70 @@ http://ionicframework.com/docs/theming/ */
--ui-background-primary: linear-gradient(180deg, #615fff 0%, #9810fa 100%);
}
@media (prefers-color-scheme: dark) {
:root {
--ion-color-primary: #ffffff;
--ion-color-primary-rgb: 255, 255, 255;
--ion-color-primary-contrast: #000000;
--ion-color-primary-contrast-rgb: 0, 0, 0;
--ion-color-primary-shade: #e0e0e0;
--ion-color-primary-tint: #ffffff;
html.ion-palette-dark {
--ion-color-primary: #ffffff;
--ion-color-primary-rgb: 255, 255, 255;
--ion-color-primary-contrast: #000000;
--ion-color-primary-contrast-rgb: 0, 0, 0;
--ion-color-primary-shade: #e0e0e0;
--ion-color-primary-tint: #ffffff;
--ion-color-secondary: #ededed;
--ion-color-secondary-rgb: 237, 237, 237;
--ion-color-secondary-contrast: #000000;
--ion-color-secondary-contrast-rgb: 0, 0, 0;
--ion-color-secondary-shade: #d1d1d1;
--ion-color-secondary-tint: #efefef;
--ion-color-secondary: #ededed;
--ion-color-secondary-rgb: 237, 237, 237;
--ion-color-secondary-contrast: #000000;
--ion-color-secondary-contrast-rgb: 0, 0, 0;
--ion-color-secondary-shade: #d1d1d1;
--ion-color-secondary-tint: #efefef;
--ion-color-tertiary: #d6d6d6;
--ion-color-tertiary-rgb: 214, 214, 214;
--ion-color-tertiary-contrast: #000000;
--ion-color-tertiary-contrast-rgb: 0, 0, 0;
--ion-color-tertiary-shade: #bcbcbc;
--ion-color-tertiary-tint: #dadada;
--ion-color-tertiary: #d6d6d6;
--ion-color-tertiary-rgb: 214, 214, 214;
--ion-color-tertiary-contrast: #000000;
--ion-color-tertiary-contrast-rgb: 0, 0, 0;
--ion-color-tertiary-shade: #bcbcbc;
--ion-color-tertiary-tint: #dadada;
--ion-color-success: #2dd55b;
--ion-color-success-rgb: 45, 213, 91;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #28bb50;
--ion-color-success-tint: #42d96b;
--ion-color-success: #2dd55b;
--ion-color-success-rgb: 45, 213, 91;
--ion-color-success-contrast: #000000;
--ion-color-success-contrast-rgb: 0, 0, 0;
--ion-color-success-shade: #28bb50;
--ion-color-success-tint: #42d96b;
--ion-color-warning: #ffc409;
--ion-color-warning-rgb: 255, 196, 9;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
--ion-color-warning: #ffc409;
--ion-color-warning-rgb: 255, 196, 9;
--ion-color-warning-contrast: #000000;
--ion-color-warning-contrast-rgb: 0, 0, 0;
--ion-color-warning-shade: #e0ac08;
--ion-color-warning-tint: #ffca22;
--ion-color-danger: #ff3344;
--ion-color-danger-rgb: 255,51,68;
--ion-color-danger-contrast: #000000;
--ion-color-danger-contrast-rgb: 0,0,0;
--ion-color-danger-shade: #e02d3c;
--ion-color-danger-tint: #ff4757;
--ion-color-danger: #ff3344;
--ion-color-danger-rgb: 255,51,68;
--ion-color-danger-contrast: #000000;
--ion-color-danger-contrast-rgb: 0,0,0;
--ion-color-danger-shade: #e02d3c;
--ion-color-danger-tint: #ff4757;
--ion-color-light: #f6f8fc;
--ion-color-light-rgb: 246, 248, 252;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #d8dade;
--ion-color-light-tint: #f7f9fc;
--ion-color-light: #f6f8fc;
--ion-color-light-rgb: 246, 248, 252;
--ion-color-light-contrast: #000000;
--ion-color-light-contrast-rgb: 0, 0, 0;
--ion-color-light-shade: #d8dade;
--ion-color-light-tint: #f7f9fc;
--ion-color-medium: #5f5f5f;
--ion-color-medium-rgb: 95, 95, 95;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255, 255, 255;
--ion-color-medium-shade: #545454;
--ion-color-medium-tint: #6f6f6f;
--ion-color-medium: #5f5f5f;
--ion-color-medium-rgb: 95, 95, 95;
--ion-color-medium-contrast: #ffffff;
--ion-color-medium-contrast-rgb: 255, 255, 255;
--ion-color-medium-shade: #545454;
--ion-color-medium-tint: #6f6f6f;
--ion-color-dark: #2f2f2f;
--ion-color-dark-rgb: 47, 47, 47;
--ion-color-dark-contrast: #ffffff;
--ion-color-dark-contrast-rgb: 255, 255, 255;
--ion-color-dark-shade: #292929;
--ion-color-dark-tint: #444444;
--ion-color-dark: #2f2f2f;
--ion-color-dark-rgb: 47, 47, 47;
--ion-color-dark-contrast: #ffffff;
--ion-color-dark-contrast-rgb: 255, 255, 255;
--ion-color-dark-shade: #292929;
--ion-color-dark-tint: #444444;
--ui-input-background: #1e1e1e;
--ui-input-color: #ffffff;
}
--ui-input-background: #1e1e1e;
--ui-input-color: #ffffff;
}

View File

@@ -36,7 +36,7 @@ ion-content::part(scroll) {
.item {
@apply px-3 py-1 rounded-full text-xs transition-all;
}
@media (prefers-color-scheme: dark) {
.ion-palette-dark {
.item {
@apply bg-(--ion-color-step-800);
}

View File

@@ -68,7 +68,7 @@ const { user } = useAuth();
--background: #f4f6f8;
--border-color: #c6c5c5;
}
@media (prefers-color-scheme: dark) {
.ion-palette-dark {
.content-wrapper {
--background: #232324;
--border-color: #3a3a3b;

View File

@@ -1,12 +1,18 @@
<script lang="ts" setup>
import { alertController, toastController, useIonRouter } from "@ionic/vue";
import { checkbox, close, information, languageOutline, refresh } from "ionicons/icons";
import { checkbox, close, contrastOutline, information, languageOutline, refresh } from "ionicons/icons";
const { t } = useI18n();
const router = useIonRouter();
const { cacheSize, calculateCacheSize, clearCache } = useCacheSize();
const { isChecking, checkForUpdate } = useAppUpdate();
const { currentLanguage } = useLanguage();
const { theme } = useTheme();
const themeNames = {
light: t("settings.themeLight"),
dark: t("settings.themeDark"),
auto: t("settings.themeAuto"),
};
function handleClearCache() {
clearCache();
@@ -98,6 +104,21 @@ onMounted(() => {
</div>
</div>
</ion-item>
<ion-item button @click="router.push('/system-settings/theme')">
<div class="flex justify-between w-full items-center">
<div class="flex-center space-x-2">
<div class="icon">
<ion-icon :icon="contrastOutline" class="text-lg" />
</div>
<div class="text-sm font-semibold">
{{ t("settings.theme") }}
</div>
</div>
<div class="end">
{{ themeNames[theme] }}
</div>
</div>
</ion-item>
<ion-item button>
<div class="flex justify-between w-full items-center">
<div class="flex-center space-x-2">

View File

@@ -0,0 +1,57 @@
<script lang="ts" setup>
import type { ThemeMode } from "@/composables/useTheme";
import { useIonRouter } from "@ionic/vue";
const { t } = useI18n();
const { theme } = useTheme();
const themes: { value: ThemeMode; label: string }[] = [
{ value: "light", label: t("settings.themeLight") },
{ value: "dark", label: t("settings.themeDark") },
{ value: "auto", label: t("settings.themeAuto") },
];
function handleThemeChange(event: CustomEvent) {
const themeMode = event.detail.value as ThemeMode;
theme.value = themeMode;
}
</script>
<template>
<ion-page>
<ion-header>
<ion-toolbar class="ui-toolbar">
<ion-back-button slot="start" />
<ion-title>{{ t("settings.theme") }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding">
<ion-list lines="full">
<ion-radio-group :value="theme" @ion-change="handleThemeChange">
<ion-item v-for="item in themes" :key="item.value">
<ion-radio :value="item.value">
<div class="theme-item">
<div class="font-semibold">
{{ item.label }}
</div>
</div>
</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
@reference "tailwindcss";
.theme-item {
@apply py-1;
}
ion-radio {
width: 100%;
}
</style>

View File

@@ -178,7 +178,7 @@ function formatCardNumber(value: string) {
border-color: #ef4444;
}
@media (prefers-color-scheme: dark) {
.ion-palette-dark {
.ui-select {
border-color: #4b5563;
}