diff --git a/src/components/AppLayout.vue b/src/components/AppLayout.vue index 9631314..6112756 100644 --- a/src/components/AppLayout.vue +++ b/src/components/AppLayout.vue @@ -8,9 +8,26 @@ import DashboardView from './dashboard/DashboardView.vue' const { init } = useMqttBridge() const activeTab = ref<'connections' | 'presets' | 'dashboard'>('dashboard') +const isDark = ref(false) + +function toggleTheme() { + isDark.value = !isDark.value + document.documentElement.classList.toggle('dark', isDark.value) + try { + localStorage.setItem('mqttx_theme', isDark.value ? 'dark' : 'light') + } catch { /* ignore */ } +} onMounted(() => { init() + // Restore theme preference + try { + const saved = localStorage.getItem('mqttx_theme') + if (saved === 'dark') { + isDark.value = true + document.documentElement.classList.add('dark') + } + } catch { /* ignore */ } }) @@ -50,7 +67,12 @@ onMounted(() => { 预设 -
+
+ +
@@ -65,7 +87,7 @@ onMounted(() => { display: flex; flex-direction: column; height: 100vh; - background: var(--el-bg-color-page); + background: var(--comfort-bg); } /* ─── Header ─────────────────────────────────────────────────── */ @@ -74,8 +96,8 @@ onMounted(() => { align-items: center; padding: 0 16px; height: 48px; - background: var(--el-bg-color); - border-bottom: 1px solid var(--el-border-color-light); + background: var(--comfort-bg-card); + border-bottom: 1px solid var(--comfort-border); flex-shrink: 0; gap: 24px; } @@ -94,13 +116,13 @@ onMounted(() => { .brand-name { font-size: 18px; font-weight: 700; - color: var(--el-color-primary); + color: var(--comfort-primary); letter-spacing: 0.5px; } .brand-version { font-size: 11px; - color: var(--el-text-color-placeholder); - background: var(--el-fill-color); + color: var(--comfort-text-muted); + background: var(--comfort-fill-color, var(--el-fill-color)); padding: 0 5px; border-radius: 4px; line-height: 18px; @@ -110,7 +132,7 @@ onMounted(() => { .header-tabs { display: flex; gap: 2px; - background: var(--el-fill-color); + background: var(--comfort-fill-color, var(--el-fill-color)); padding: 2px; border-radius: 8px; } @@ -124,26 +146,49 @@ onMounted(() => { background: transparent; border-radius: 6px; font-size: 13px; - color: var(--el-text-color-secondary); + color: var(--comfort-text-secondary); cursor: pointer; transition: all 0.15s; font-family: inherit; white-space: nowrap; } .tab-btn:hover { - color: var(--el-text-color-primary); + color: var(--comfort-text); } .tab-btn.active { - background: var(--el-bg-color); - color: var(--el-color-primary); + background: var(--comfort-bg-card); + color: var(--comfort-primary); font-weight: 500; - box-shadow: 0 1px 2px rgba(0,0,0,0.06); + box-shadow: var(--comfort-shadow); } .tab-btn svg { flex-shrink: 0; } .header-right { flex: 1; + display: flex; + justify-content: flex-end; +} +.theme-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border: 1px solid var(--comfort-border); + border-radius: 6px; + background: var(--comfort-bg-card); + color: var(--comfort-text-secondary); + cursor: pointer; + transition: all 0.2s; +} +.theme-btn:hover { + color: var(--comfort-primary); + border-color: var(--comfort-primary-light); + background: var(--comfort-primary-bg); +} +.theme-btn svg { + flex-shrink: 0; } /* ─── Main ───────────────────────────────────────────────────── */ diff --git a/src/main.ts b/src/main.ts index ce20f7c..9460517 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { createApp } from "vue"; import ElementPlus from "element-plus"; import "element-plus/dist/index.css"; +import "./styles/comfort.css"; import App from "./App.vue"; const app = createApp(App); diff --git a/src/styles/comfort.css b/src/styles/comfort.css new file mode 100644 index 0000000..3ce3d17 --- /dev/null +++ b/src/styles/comfort.css @@ -0,0 +1,229 @@ +:root { + /* ─── Soft color palette ─────────────────────────────────────── */ + --comfort-bg: #f5f5f0; + --comfort-bg-soft: #fafaf7; + --comfort-bg-card: #ffffff; + --comfort-text: #2c2c2c; + --comfort-text-secondary: #6b6b6b; + --comfort-text-muted: #999999; + --comfort-border: #e8e8e3; + --comfort-border-light: #f0f0eb; + --comfort-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.03); + --comfort-shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04); + --comfort-primary: #5b8c5a; + --comfort-primary-light: #7dab7c; + --comfort-primary-bg: #eef5ee; + --comfort-success: #5b8c5a; + --comfort-warning: #c4a35a; + --comfort-danger: #c46a5a; + --comfort-info: #6b8a9a; + --comfort-font: "Inter", "LXGW WenKai", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; + --comfort-font-mono: "Cascadia Code", "JetBrains Mono", "Fira Code", "Consolas", monospace; + --comfort-radius: 8px; + --comfort-radius-sm: 6px; + + /* Element Plus variable overrides */ + --el-bg-color: var(--comfort-bg); + --el-bg-color-overlay: var(--comfort-bg-card); + --el-text-color-primary: var(--comfort-text); + --el-text-color-regular: var(--comfort-text); + --el-text-color-secondary: var(--comfort-text-secondary); + --el-text-color-placeholder: var(--comfort-text-muted); + --el-border-color: var(--comfort-border); + --el-border-color-light: var(--comfort-border-light); + --el-border-color-extra-light: var(--comfort-border-light); + --el-fill-color: #efefea; + --el-fill-color-light: #eaeae5; + --el-fill-color-lighter: #f0f0eb; + --el-fill-color-extra-light: #f5f5f0; + --el-color-primary: var(--comfort-primary); + --el-color-primary-light-3: var(--comfort-primary-light); + --el-color-primary-light-5: #9dc09c; + --el-color-primary-light-7: #bdd5bc; + --el-color-primary-light-8: #d0e2cf; + --el-color-primary-light-9: var(--comfort-primary-bg); + --el-color-primary-dark-2: #4a7a49; + --el-color-success: var(--comfort-success); + --el-color-warning: var(--comfort-warning); + --el-color-danger: var(--comfort-danger); + --el-color-info: var(--comfort-info); + --el-border-radius-base: var(--comfort-radius-sm); + --el-font-size-base: 13px; + --el-font-size-small: 12px; + --el-font-size-extra-small: 11px; + --el-font-family: var(--comfort-font); +} + +/* ─── Dark mode ─────────────────────────────────────────────────── */ +.dark { + --comfort-bg: #1a1a1e; + --comfort-bg-soft: #222226; + --comfort-bg-card: #2a2a2e; + --comfort-text: #e0e0e0; + --comfort-text-secondary: #a0a0a0; + --comfort-text-muted: #707070; + --comfort-border: #3a3a3e; + --comfort-border-light: #323236; + --comfort-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + --comfort-shadow-hover: 0 2px 8px rgba(0, 0, 0, 0.3); + --comfort-primary: #7dab7c; + --comfort-primary-light: #9dc09c; + --comfort-primary-bg: #2a3a2a; + --comfort-success: #7dab7c; + --comfort-warning: #c4a35a; + --comfort-danger: #c46a5a; + --comfort-info: #6b8a9a; + + --el-bg-color: var(--comfort-bg); + --el-bg-color-overlay: var(--comfort-bg-card); + --el-text-color-primary: var(--comfort-text); + --el-text-color-regular: var(--comfort-text); + --el-text-color-secondary: var(--comfort-text-secondary); + --el-text-color-placeholder: var(--comfort-text-muted); + --el-border-color: var(--comfort-border); + --el-border-color-light: var(--comfort-border-light); + --el-fill-color: #2e2e32; + --el-fill-color-light: #36363a; + --el-fill-color-lighter: #323236; + --el-fill-color-extra-light: #2a2a2e; + --el-color-primary: var(--comfort-primary); + --el-color-primary-light-3: var(--comfort-primary-light); + --el-color-primary-light-5: #bdd5bc; + --el-color-primary-light-7: #3a4a3a; + --el-color-primary-light-8: #324032; + --el-color-primary-light-9: var(--comfort-primary-bg); + --el-color-primary-dark-2: #5b8c5a; +} + +/* ─── Global comfort styles ─────────────────────────────────────── */ +body { + background: var(--comfort-bg); + color: var(--comfort-text); + font-family: var(--comfort-font); + line-height: 1.6; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +/* Softer scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: var(--comfort-border); + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: var(--comfort-text-muted); +} + +/* Subtle transitions */ +* { + transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +} + +/* Card style */ +.comfort-card { + background: var(--comfort-bg-card); + border: 1px solid var(--comfort-border); + border-radius: var(--comfort-radius); + box-shadow: var(--comfort-shadow); +} + +/* Better focus ring */ +:focus-visible { + outline: 2px solid var(--comfort-primary-light); + outline-offset: 2px; + border-radius: 2px; +} + +/* Input/textarea comfort */ +.el-input__wrapper, +.el-textarea__inner { + box-shadow: none !important; + border: 1px solid var(--comfort-border) !important; + background: var(--comfort-bg-soft) !important; + border-radius: var(--comfort-radius-sm) !important; +} +.el-input__wrapper:hover, +.el-textarea__inner:hover { + border-color: var(--comfort-primary-light) !important; +} +.el-input__wrapper.is-focus, +.el-textarea__inner:focus { + border-color: var(--comfort-primary) !important; +} + +/* Button comfort */ +.el-button { + --el-button-hover-bg-color: var(--comfort-primary-bg); + --el-button-hover-border-color: var(--comfort-primary-light); + border-radius: var(--comfort-radius-sm) !important; + font-weight: 450 !important; +} +.el-button--primary { + --el-button-bg-color: var(--comfort-primary); + --el-button-border-color: var(--comfort-primary); + --el-button-hover-bg-color: var(--comfort-primary-light); + --el-button-hover-border-color: var(--comfort-primary-light); +} + +/* Table comfort */ +.el-table { + --el-table-border-color: var(--comfort-border-light) !important; + --el-table-header-bg-color: var(--comfort-bg-soft) !important; + --el-table-row-hover-bg-color: var(--comfort-primary-bg) !important; + font-size: 13px !important; +} +.el-table th.el-table__cell { + font-weight: 600 !important; + color: var(--comfort-text-secondary) !important; + font-size: 12px !important; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +/* Dialog comfort */ +.el-dialog { + --el-dialog-bg-color: var(--comfort-bg-card); + border-radius: var(--comfort-radius) !important; + box-shadow: var(--comfort-shadow-hover) !important; +} + +/* Tag comfort */ +.el-tag { + --el-tag-bg-color: var(--comfort-primary-bg); + --el-tag-border-color: transparent; + --el-tag-text-color: var(--comfort-primary); + font-weight: 450; + border-radius: 4px; +} + +/* Select dropdown comfort */ +.el-select-dropdown { + --el-select-dropdown-bg: var(--comfort-bg-card); + border: 1px solid var(--comfort-border) !important; + box-shadow: var(--comfort-shadow-hover) !important; + border-radius: var(--comfort-radius-sm) !important; +} +.el-select-dropdown__item.hover { + background: var(--comfort-primary-bg) !important; +} + +/* Message comfort */ +.el-message { + border-radius: var(--comfort-radius-sm) !important; + box-shadow: var(--comfort-shadow-hover) !important; + border: 1px solid var(--comfort-border) !important; +} + +/* Checkbox comfort */ +.el-checkbox { + --el-checkbox-checked-bg-color: var(--comfort-primary); + --el-checkbox-checked-input-border-color: var(--comfort-primary); + --el-checkbox-input-border-color-hover: var(--comfort-primary-light); +} \ No newline at end of file