AppearancePanel.vue 5.86 KB
<template>
  <div class="appearance-panel">
    <div class="settings-content-header">
      <h1>{{ $t("settings.appearance.title") }}</h1>
      <p>{{ $t("settings.appearance.desc") }}</p>
    </div>
    <div class="section section-theme">
      <div class="section-title">{{ $t("settings.appearance.theme") }}</div>
      <div class="theme-options">
        <div
          v-for="theme in themes"
          :key="theme.key"
          :class="[
            'theme-option',
            { active: selectedTheme === theme.key, disabled: theme.disabled },
          ]"
          @click="selectTheme(theme)"
          @mouseenter="hoveredTheme = theme.key"
          @mouseleave="hoveredTheme = null"
        >
          <div
            class="theme-preview"
            :class="theme.previewClass"
            :style="{
              boxShadow:
                hoveredTheme === theme.key && !theme.disabled
                  ? '0 0 0 3px #3399ff55'
                  : '',
            }"
          ></div>
          <span>
            {{ $t(`settings.appearance.themeLabel.${theme.key}`)
            }}<br v-if="theme.desc" />
            <small v-if="theme.desc">{{
              $t(`settings.appearance.themeDesc.${theme.key}`)
            }}</small>
          </span>
          <div v-if="selectedTheme === theme.key" class="theme-check">
            <i class="fas fa-check"></i>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";

const { t: _t } = useI18n();

interface Theme {
  key: string;
  previewClass: string;
  desc: string;
  disabled: boolean;
}

const THEME_KEY = "theme";
const selectedTheme = ref("light");
const hoveredTheme = ref<string | null>(null);

const themes: Theme[] = [
  {
    key: "dark",
    previewClass: "dark-theme",
    desc: "",
    disabled: false,
  },
  {
    key: "light",
    previewClass: "light-theme",
    desc: "",
    disabled: false,
  },
  {
    key: "custom",
    previewClass: "custom-theme",
    desc: "soon",
    disabled: true,
  },
];

const selectTheme = (theme: Theme) => {
  if (theme.disabled) return;
  if (selectedTheme.value !== theme.key) {
    selectedTheme.value = theme.key;
    if ((window as any).setTheme) {
      (window as any).setTheme(theme.key);
    }
  }
};

const applyTheme = (theme: string) => {
  const root = document.documentElement;
  root.classList.remove("theme-dark", "theme-light");
  root.classList.add(`theme-${theme}`);
};

onMounted(() => {
  const saved = localStorage.getItem(THEME_KEY);
  if (saved && ["dark", "light"].includes(saved)) {
    selectedTheme.value = saved;
    applyTheme(saved);
  } else {
    applyTheme(selectedTheme.value);
  }
});
</script>

<style scoped lang="scss">
.appearance-panel {
  width: 100%;
  max-width: 100%;
  padding: 0;
  color: var(--color-text);
  background: var(--color-bg);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.settings-content-header {
  margin-top: 24px;
  margin-bottom: 48px;
  text-align: left;
  padding-left: 0;

  h1 {
    font-size: 36px;
    font-weight: 800;
    color: var(--color-text);
    margin-bottom: 10px;
    letter-spacing: 1px;
  }

  p {
    font-size: 18px;
    color: var(--color-secondary);
    font-weight: 400;
  }
}

.section {
  width: 100%;
  margin-bottom: 56px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.section-title {
  font-size: 20px;
  font-weight: 700;
  color: var(--color-text);
  margin-bottom: 24px;
  text-align: left;
  letter-spacing: 1px;
}

.section-theme {
  align-items: flex-start;
}

.theme-options {
  display: flex;
  justify-content: flex-start;
  align-items: flex-end;
  gap: 48px;
  width: 100%;
  margin-bottom: 0;
}

.theme-option {
  flex: none;
  width: 180px;
  height: 120px;
  padding: 20px 0;
  border: 2px solid var(--color-border);
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s;
  text-align: center;
  background: var(--color-card);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  user-select: none;

  &.active {
    border-color: #3399ff;
    box-shadow: 0 4px 24px rgba(51, 153, 255, 0.1);
  }

  &:not(.disabled):hover {
    border-color: #3399ff;
    box-shadow: 0 0 0 3px #3399ff55;
    z-index: 2;
  }

  &.disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  span {
    display: block;
    font-weight: 600;
    color: var(--color-text);
    font-size: 16px;
  }

  small {
    display: block;
    font-size: 13px;
    color: var(--color-secondary);
    margin-top: 4px;
  }
}

.theme-preview {
  width: 120px;
  height: 60px;
  border-radius: 8px;
  margin-bottom: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  background: #222;
  transition: box-shadow 0.2s;
}

.dark-theme {
  background: linear-gradient(135deg, #1e1e1e 0%, #252526 100%);
}

.light-theme {
  background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
}

.theme-check {
  position: absolute;
  top: 10px;
  right: 10px;
  color: #3399ff;
  font-size: 18px;
  background: var(--color-card);
  border-radius: 50%;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(51, 153, 255, 0.1);
}

@media (max-width: 1024px) {
  .appearance-panel {
    max-width: 100%;
    padding: 0 4px 20px 4px;
  }

  .settings-content-header {
    margin-top: 24px;
    margin-bottom: 24px;

    h1 {
      font-size: 22px;
    }

    p {
      font-size: 13px;
    }
  }

  .section-title {
    font-size: 15px;
    margin-bottom: 10px;
  }

  .theme-options {
    gap: 12px;
  }

  .theme-option {
    width: 100px;
    height: 80px;
    padding: 8px 0 6px 0;
    border-radius: 6px;
  }

  .theme-preview {
    width: 60px;
    height: 32px;
    border-radius: 4px;
    margin-bottom: 6px;
  }

  .section {
    margin-bottom: 28px;
  }
}
</style>