Explorar el Código

feat(auth): 重构登录模块并添加主题切换功能

添加主题切换相关组件和样式文件
重构登录页面为 auth 模块,支持账号密码和手机号登录
更新路由和权限配置以适配新的 auth 路径
修改 token 存储键名并添加暗黑主题支持
piks hace 1 semana
padre
commit
57563aab31

+ 1 - 0
components.d.ts

@@ -25,5 +25,6 @@ declare module 'vue' {
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    ThemeToggle: typeof import('./src/components/ThemeToggle.vue')['default']
   }
 }

BIN
src/assets/images/auth-bg.png


+ 0 - 0
src/components/ThemeToggle.vue


+ 0 - 0
src/composables/useTheme.ts


+ 1 - 0
src/main.ts

@@ -5,6 +5,7 @@ import { createPinia } from 'pinia'
 import ElementPlus from 'element-plus'
 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'
 
 // ⚠️ 路由守卫必须在 createApp 后、mount 前引入

+ 5 - 5
src/permission.ts

@@ -3,7 +3,7 @@ import { useUserStore } from '@/store/modules/user'
 import { usePermissionStore } from '@/store/modules/permission'
 
 // 不需要登录即可访问的路径白名单
-const whiteList = ['/login', '/404']
+const whiteList = ['/auth', '/404']
 
 router.beforeEach(async (to, _from, next) => {
   const userStore = useUserStore()
@@ -13,7 +13,7 @@ router.beforeEach(async (to, _from, next) => {
 
   if (token) {
     // ── 已登录 ──
-    if (to.path === '/login') {
+    if (to.path === '/auth') {
       // 已登录不允许访问登录页
       next('/')
       return
@@ -34,7 +34,7 @@ router.beforeEach(async (to, _from, next) => {
         accessRoutes.forEach(route => {
           router.addRoute('Root', route)
         })
-        
+
         // ④ 最后注册 404 路由(确保在权限路由之后)
         router.addRoute(notFoundRoute)
 
@@ -46,7 +46,7 @@ router.beforeEach(async (to, _from, next) => {
         // Token 失效或接口异常 → 清除 Token,跳转登录
         console.error('[权限守卫] 初始化失败:', error)
         userStore.resetToken()
-        next(`/login?redirect=${encodeURIComponent(to.path)}`)
+        next(`/auth?redirect=${encodeURIComponent(to.path)}`)
       }
     }
   } else {
@@ -54,7 +54,7 @@ router.beforeEach(async (to, _from, next) => {
     if (whiteList.includes(to.path)) {
       next()
     } else {
-      next(`/login?redirect=${encodeURIComponent(to.path)}`)
+      next(`/auth?redirect=${encodeURIComponent(to.path)}`)
     }
   }
 })

+ 3 - 3
src/router/index.ts

@@ -8,9 +8,9 @@ import Layout from '@/layout/index.vue'
  */
 export const constantRoutes: RouteRecordRaw[] = [
   {
-    path: '/login',
-    name: 'Login',
-    component: () => import('@/views/login/index.vue'),
+    path: '/auth',
+    name: 'auth',
+    component: () => import('@/views/auth/index.vue'),
     meta: { hidden: true, title: '登录' }
   },
   {

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


+ 0 - 0
src/styles/theme.scss


+ 1 - 1
src/utils/auth.ts

@@ -1,4 +1,4 @@
-const TokenKey: string = 'Admin-Token'
+const TokenKey: string = 'token'
 
 export function getToken(): string | null {
   return localStorage.getItem(TokenKey)

+ 138 - 0
src/views/auth/components/login.vue

@@ -0,0 +1,138 @@
+<template>
+  <div class="login-page">
+    <h1>欢迎登录</h1>
+    <div class="login-tabs">
+      <div class="tab-item" :class="{ active: activeTab === 'account' }" @click="activeTab = 'account'">
+        账号密码登录
+      </div>
+      <div class="tab-item" :class="{ active: activeTab === 'phone' }" @click="activeTab = 'phone'">
+        手机号登录
+      </div>
+    </div>
+    <el-form class="login-form">
+      <template v-if="activeTab === 'account'">
+        <el-form-item>
+          <el-input v-model="accountForm.username" placeholder="请输入手机号或账号" size="large">
+            <template #prefix>
+              <div class="input-prefix">
+                <el-icon :size="16">
+                  <User />
+                </el-icon>
+                <span>账号</span>
+              </div>
+            </template>
+          </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-form-item>
+      </template>
+      <template v-else>
+        <el-form-item>
+          <el-input v-model="phoneForm.phone" placeholder="请输入手机号" :prefix-icon="Iphone" size="large" />
+        </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>
+    </el-form>
+  </div>
+</template>
+<script setup lang="ts">
+import { User, Lock, Iphone, CircleCheck } from '@element-plus/icons-vue'
+
+type TabType = 'account' | 'phone'
+const activeTab = ref<TabType>('account')
+
+const accountForm = reactive({
+  username: '',
+  password: ''
+})
+
+const phoneForm = reactive({
+  phone: '',
+  code: ''
+})
+</script>
+<style lang="scss" scoped>
+.login-page {
+  margin-top: 242px;
+  width: 400px;
+  display: flex;
+  flex-direction: column;
+}
+
+.login-tabs {
+  display: flex;
+  margin-top: 30px;
+  margin-bottom: 20px;
+
+  .tab-item {
+    padding: 10px 20px;
+    cursor: pointer;
+    color: #999;
+    font-size: 16px;
+    border-bottom: 2px solid transparent;
+    transition: all 0.3s;
+
+    &:hover {
+      color: #666;
+    }
+
+    &.active {
+      color: #fff;
+      border-bottom-color: #fff;
+    }
+  }
+}
+
+.login-form {
+  :deep(.el-input__wrapper) {
+    background: rgba(255, 255, 255, 0.1);
+    box-shadow: none;
+    border: 1px solid transparent;
+    border-radius: 8px;
+
+    &.is-focus {
+      border-color: #7c3aed;
+    }
+  }
+
+  :deep(.el-input__inner) {
+    color: #fff;
+
+    &::placeholder {
+      color: #999;
+    }
+  }
+
+  :deep(.el-input__icon) {
+    color: #999;
+  }
+
+  :deep(.el-input-group__append) {
+    background: #7c3aed;
+    border: none;
+    border-radius: 0 8px 8px 0;
+    padding: 0;
+
+    .el-button {
+      color: #fff;
+      border: none;
+      background: transparent;
+      padding: 0 15px;
+      height: 100%;
+      border-radius: 0 8px 8px 0;
+
+      &:hover {
+        background: #6d28d9;
+      }
+    }
+  }
+}
+</style>

+ 64 - 0
src/views/auth/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="auth-page">
+    <div class="auth-box">
+      <Login />
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import Login from './components/login.vue'
+import { ElMessage } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import { useUserStore } from '@/store/modules/user'
+
+const router = useRouter()
+const route = useRoute()
+const userStore = useUserStore()
+
+const formRef = ref<FormInstance>()
+const loading = ref(false)
+
+const form = reactive({ username: 'admin', password: '123456' })
+
+const rules: FormRules = {
+  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
+  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
+}
+
+async function handleLogin() {
+  const valid = await formRef.value?.validate().catch(() => false)
+  if (!valid) return
+
+  loading.value = true
+  try {
+    await userStore.login(form.username, form.password)
+    const redirect = (route.query.redirect as string) || '/'
+    await router.replace(redirect)
+    ElMessage.success('登录成功')
+  } catch (error: unknown) {
+    ElMessage.error(error instanceof Error ? error.message : '登录失败')
+  } finally {
+    loading.value = false
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.auth-page {
+  width: 100%;
+  height: 100vh;
+  background: url('@/assets/images/auth-bg.png') no-repeat center center;
+  background-size: cover;
+  display: flex;
+  justify-content: end;
+
+  .auth-box {
+    width: 40%;
+    height: 100%;
+    background: rgba(6, 5, 25, 0.80);
+    backdrop-filter: blur(40px);
+    display: flex;
+    justify-content: center;
+  }
+}
+</style>

+ 0 - 112
src/views/login/index.vue

@@ -1,112 +0,0 @@
-<script setup lang="ts">
-import { ref, reactive } from 'vue'
-import { useRouter, useRoute } from 'vue-router'
-import { ElMessage } from 'element-plus'
-import type { FormInstance, FormRules } from 'element-plus'
-import { useUserStore } from '@/store/modules/user'
-
-const router = useRouter()
-const route = useRoute()
-const userStore = useUserStore()
-
-const formRef = ref<FormInstance>()
-const loading = ref(false)
-
-const form = reactive({ username: 'admin', password: '123456' })
-
-const rules: FormRules = {
-  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
-}
-
-async function handleLogin() {
-  const valid = await formRef.value?.validate().catch(() => false)
-  if (!valid) return
-
-  loading.value = true
-  try {
-    await userStore.login(form.username, form.password)
-    const redirect = (route.query.redirect as string) || '/'
-    await router.replace(redirect)
-    ElMessage.success('登录成功')
-  } catch (error: unknown) {
-    ElMessage.error(error instanceof Error ? error.message : '登录失败')
-  } finally {
-    loading.value = false
-  }
-}
-</script>
-
-<template>
-  <div class="login-page">
-    <div class="login-box">
-      <h2 class="login-title">后台管理系统</h2>
-
-      <el-form ref="formRef" :model="form" :rules="rules" size="large">
-        <el-form-item prop="username">
-          <el-input v-model="form.username" placeholder="用户名" prefix-icon="User" />
-        </el-form-item>
-        <el-form-item prop="password">
-          <el-input
-            v-model="form.password"
-            type="password"
-            placeholder="密码"
-            prefix-icon="Lock"
-            show-password
-            @keyup.enter="handleLogin"
-          />
-        </el-form-item>
-        <el-form-item>
-          <el-button
-            type="primary"
-            :loading="loading"
-            style="width: 100%"
-            @click="handleLogin"
-          >
-            登 录
-          </el-button>
-        </el-form-item>
-      </el-form>
-
-      <div class="login-hint">
-        <p>管理员:admin / 123456 &nbsp;(可见所有菜单)</p>
-        <p>编辑员:editor / 123456 &nbsp;(仅内容管理)</p>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style scoped lang="scss">
-.login-page {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  height: 100vh;
-  background: #0d0d12;
-}
-
-.login-box {
-  width: 420px;
-  padding: 48px 40px;
-  background: #1a1a24;
-  border-radius: 12px;
-  border: 1px solid rgba(255, 255, 255, 0.06);
-}
-
-.login-title {
-  color: #fff;
-  text-align: center;
-  margin: 0 0 32px;
-  font-size: 22px;
-  font-weight: 600;
-}
-
-.login-hint {
-  margin-top: 16px;
-  text-align: center;
-  font-size: 12px;
-  color: rgba(255, 255, 255, 0.3);
-  line-height: 1.8;
-  p { margin: 0; }
-}
-</style>