Kaynağa Gözat

feat(theme): 实现暗色模式切换及样式优化

添加主题切换功能,包括暗色模式支持、渐变按钮样式和登录页面优化
piks 1 hafta önce
ebeveyn
işleme
0934356adc

+ 1 - 0
components.d.ts

@@ -19,6 +19,7 @@ declare module 'vue' {
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElLink: typeof import('element-plus/es')['ElLink']
     ElMenu: typeof import('element-plus/es')['ElMenu']
     ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']

+ 18 - 0
src/components/ThemeToggle.vue

@@ -0,0 +1,18 @@
+<template>
+  <el-switch v-model="isDark" :active-action-icon="Moon" :inactive-action-icon="Sunny" inline-prompt
+    @change="onToggle" />
+</template>
+
+<script setup lang="ts">
+import { Sunny, Moon } from '@element-plus/icons-vue'
+import { isDark, toggleTheme } from '@/composables/useTheme'
+
+function onToggle() {
+  // 可选:添加过渡动画
+  document.documentElement.classList.add('theme-transition')
+  toggleTheme()
+  setTimeout(() => {
+    document.documentElement.classList.remove('theme-transition')
+  }, 400)
+}
+</script>

+ 11 - 0
src/composables/useTheme.ts

@@ -0,0 +1,11 @@
+import { useDark, useToggle } from '@vueuse/core'
+
+// 自动在 <html> 上添加/移除 class="dark"
+export const isDark = useDark({
+  // 存储到 localStorage,刷新保持状态
+  storageKey: 'admin-theme',
+  valueDark: 'dark',
+  valueLight: 'light'
+})
+
+export const toggleTheme = useToggle(isDark)

+ 2 - 0
src/main.ts

@@ -7,6 +7,8 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
 import 'element-plus/dist/index.css'
 import 'element-plus/theme-chalk/dark/css-vars.css'
 import 'normalize.css/normalize.css'
+import '@/styles/theme.scss'
+import '@/styles/element.scss'
 
 // ⚠️ 路由守卫必须在 createApp 后、mount 前引入
 import './permission'

+ 18 - 0
src/styles/element.scss

@@ -0,0 +1,18 @@
+// 渐变主按钮 —— 通过额外 class 使用
+.el-button--primary.is-gradient {
+  border: none;
+  background: var(--btn-primary-gradient);
+  color: #fff;
+  // box-shadow: 0 4px 15px var(--btn-primary-shadow);
+
+  &:hover {
+    background: var(--btn-primary-gradient-hover);
+    // box-shadow: 0 6px 20px var(--btn-primary-shadow);
+  }
+
+  &.is-disabled {
+    opacity: 0.6;
+    transform: none;
+    cursor: not-allowed;
+  }
+}

+ 0 - 0
src/styles/gradient-button.scss


+ 57 - 0
src/styles/theme.scss

@@ -0,0 +1,57 @@
+// ========== 亮色模式(默认) ==========
+:root {
+  // 覆盖 Element Plus 主色
+  --el-color-primary: #409eff;
+  --el-color-primary-light-3: #79bbff;
+  --el-color-primary-light-5: #a0cfff;
+  --el-color-primary-dark-2: #337ecc;
+
+  // 自定义渐变按钮变量
+  --btn-primary-gradient: linear-gradient(91deg, #5f53ff -40.99%, #8d53ff 88.34%);
+  --btn-primary-gradient-hover: linear-gradient(95deg, #8d22ff -9.99%, #ae00ff 91.44%);
+  --btn-primary-shadow: rgba(102, 126, 234, 0.4);
+
+  // 页面级自定义变量
+  --admin-bg: #f0f2f5;
+  --admin-sidebar-bg: #ffffff;
+  --admin-header-bg: #ffffff;
+  --admin-card-bg: #ffffff;
+  --admin-text-primary: #303133;
+  --admin-text-secondary: #909399;
+  --admin-border-color: #e4e7ed;
+}
+
+// ========== 暗色模式 ==========
+html.dark {
+  // 覆盖 Element Plus 暗色下的主色(可选调整)
+  --el-color-primary: #409eff;
+  --el-color-primary-light-3: #3375b9;
+  --el-color-primary-light-5: #2a598a;
+  --el-color-primary-dark-2: #66b1ff;
+
+  // 暗色下的渐变按钮
+  --btn-primary-gradient: linear-gradient(135deg, #5a6fe0 0%, #6a3f9a 100%);
+  --btn-primary-gradient-hover: linear-gradient(135deg, #6a3f9a 0%, #5a6fe0 100%);
+  --btn-primary-shadow: rgba(90, 111, 224, 0.3);
+
+  // 页面级自定义变量
+  --admin-bg: #0a0a0a;
+  --admin-sidebar-bg: #141414;
+  --admin-header-bg: #1d1e1f;
+  --admin-card-bg: #1d1e1f;
+  --admin-text-primary: #e5eaf3;
+  --admin-text-secondary: #a3a6ad;
+  --admin-border-color: #414243;
+}
+
+// 全局平滑过渡(主题切换时)
+html.theme-transition,
+html.theme-transition *,
+html.theme-transition *::before,
+html.theme-transition *::after {
+  transition:
+    background-color 0.3s ease,
+    color 0.3s ease,
+    border-color 0.3s ease,
+    box-shadow 0.3s ease !important;
+}

+ 36 - 7
src/views/auth/components/login.vue

@@ -24,9 +24,23 @@
           </el-input>
         </el-form-item>
         <el-form-item>
-          <el-input v-model="accountForm.password" type="password" placeholder="请输入密码" :prefix-icon="Lock" size="large"
-            show-password />
+          <el-input v-model="accountForm.password" type="password" placeholder="请输入密码" size="large" show-password>
+            <template #prefix>
+              <div class="input-prefix">
+                <el-icon :size="16">
+                  <Lock />
+                </el-icon>
+                <span>密码</span>
+              </div>
+            </template>
+          </el-input>
         </el-form-item>
+        <div class="form-end">
+          <el-button size="large" style="width:100%" type="primary" class="is-gradient">发送验证码</el-button>
+          <div>
+            还没有账号?<span>去注册</span>
+          </div>
+        </div>
       </template>
       <template v-else>
         <el-form-item>
@@ -34,9 +48,6 @@
         </el-form-item>
         <el-form-item>
           <el-input v-model="phoneForm.code" placeholder="请输入验证码" :prefix-icon="CircleCheck" size="large">
-            <template #append>
-              <el-button type="primary">发送验证码</el-button>
-            </template>
           </el-input>
         </el-form-item>
       </template>
@@ -51,7 +62,8 @@ const activeTab = ref<TabType>('account')
 
 const accountForm = reactive({
   username: '',
-  password: ''
+  password: '',
+  code: ''
 })
 
 const phoneForm = reactive({
@@ -65,6 +77,7 @@ const phoneForm = reactive({
   width: 400px;
   display: flex;
   flex-direction: column;
+  color: #fff;
 }
 
 .login-tabs {
@@ -86,7 +99,7 @@ const phoneForm = reactive({
 
     &.active {
       color: #fff;
-      border-bottom-color: #fff;
+      border-bottom-color: #8077FF;
     }
   }
 }
@@ -115,6 +128,11 @@ const phoneForm = reactive({
     color: #999;
   }
 
+  /* 密码可见性切换按钮设置为白色 */
+  :deep(.el-input__password) {
+    color: #fff;
+  }
+
   :deep(.el-input-group__append) {
     background: #7c3aed;
     border: none;
@@ -134,5 +152,16 @@ const phoneForm = reactive({
       }
     }
   }
+
+  .input-prefix {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    color: #fff;
+  }
+}
+
+.form-end {
+  margin-top: 30px;
 }
 </style>