diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6d7ae30..0000000 --- a/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -.DS_Store -dist -dist-ssr -coverage -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -*.tsbuildinfo - -.eslintcache - -# Cypress -/cypress/videos/ -/cypress/screenshots/ - -# Vitest -__screenshots__/ - -# Vite -*.timestamp-*-*.mjs - -#auto generated files -.eslintrc-auto-import.json \ No newline at end of file diff --git a/src/assets/styles/global.scss b/src/assets/styles/global.scss deleted file mode 100644 index 3f67cd8..0000000 --- a/src/assets/styles/global.scss +++ /dev/null @@ -1,43 +0,0 @@ -@import './user.scss'; -*{margin: 0;padding: 0;box-sizing: border-box;} -html,body{ - font-size: 16px; - color: black; -} -/* 过渡动画样式(name="fade" 对应前缀 fade-) */ -/* 1. 进入/离开的初始状态 */ -.user-enter-from, -.user-leave-to { - opacity: 0; - /* 初始透明 */ - transform: translateX(20px); - /* 初始右移20px */ -} - -/* 2. 进入/离开的过渡过程 */ -.user-enter-active, -.user-leave-active { - transition: all 0.5s ease; - /* 过渡时长、缓动效果 */ -} - -/* 3. 进入完成/离开开始的状态 */ -.user-enter-to, -.user-leave-from { - opacity: 1; - /* 最终不透明 */ - transform: translateX(0); - /* 最终回到原位 */ -} - -// 全局通知样式 -.global-notification { - left: 50%; - transform: translateX(-50%); - // 2. 重写动画:从顶部淡入(取消右侧滑入) - &.el-notification-fade-enter-active, - &.el-notification-fade-leave-active { - transition: all 0.3s ease-out !important; - transform: translate(-50%, -150%) !important; - } -} diff --git a/src/components/layout/Header.vue b/src/components/layout/Header.vue deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/layout/Sidebar.vue b/src/components/layout/Sidebar.vue deleted file mode 100644 index e69de29..0000000 diff --git a/src/router/index.ts b/src/router/index.ts deleted file mode 100644 index 02fff52..0000000 --- a/src/router/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { createRouter, createWebHistory } from 'vue-router'; -import { useUserStore } from '@/stores/modules/user'; - -const routes = [ - // 1. 补全根路径路由定义,避免通配符无限循环 - { - path: '/', - name: 'Home', - component: () => import('@/views/Test/index.vue'), // 替换为你的首页组件 - meta: { requiresAuth: true ,title: '首页' } // 首页需要登录 - }, - { - path: '/user', - name: 'User', - component: () => import('@/views/user/index.vue'), - redirect: '/user/login', - children: [ - { - path: 'login', - name: 'Login', - component: () => import('@/views/user/Login.vue'), - meta: { isAuthPage: true, title: '登录' } - }, - { - path: 'register', - name: 'Register', - component: () => import('@/views/user/Register.vue'), - meta: { isAuthPage: true, title: '注册' } - }, - { - path: 'forgot-password', - name: 'ForgotPassword', - component: () => import('@/views/user/ForgotPassword.vue'), - meta: { isAuthPage: true, title: '忘记密码' } - }, - { - path: 'change-password', - name: 'ChangePassword', - component: () => import('@/views/user/ChangePassword.vue'), - meta: { requiresAuth: true, title: '修改密码' } - } - ] - }, - { - path: '/:pathMatch(.*)*', - redirect: '/user/login' // 404 重定向到登录页,避免循环 - } -]; - -const router = createRouter({ - history: createWebHistory(), - routes -}); - -// 全局前置守卫 -router.beforeEach(async (to, from) => { - const userStore = useUserStore(); - const isLoggedIn = !!userStore.token; - - // 从修改密码页跳登录页时,直接放行(不跳首页) - const isFromChangePasswordToLogin = from.path === '/user/change-password' && to.path === '/user/login'; - if (isFromChangePasswordToLogin) { - return true; // 放行,跳过后续的“已登录访问授权页跳首页”逻辑 - } - // 1. 已登录用户访问授权页 → 重定向到首页 - if (isLoggedIn && to.meta.isAuthPage) { - if (to.path === '/') return true; - return '/'; - } - - // 2. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页 - if (!isLoggedIn && to.meta.requiresAuth) { - if (to.path === '/user/login') return true; - return '/user/login'; - } - - // 3. 其他情况正常放行 - return true; -}); - -// 可选:设置页面标题 -router.afterEach((to) => { - if (to.meta.title) { - document.title = to.meta.title as string; - } -}); - -export default router; \ No newline at end of file diff --git a/src/utils/sendCaptcha/index.ts b/src/utils/sendCaptcha/index.ts deleted file mode 100644 index 6e8913e..0000000 --- a/src/utils/sendCaptcha/index.ts +++ /dev/null @@ -1,214 +0,0 @@ -import type { Ref } from 'vue'; -import { setStorage, getStorage, removeStorage } from '@/utils/storage'; -// 既支持指定的固定值,也支持任意字符串 -export type CaptchaType = 'phone' | 'email' | string; - -// 本地存储的状态类型 -interface StoredCaptchaState { - type: CaptchaType; // 验证码类型 - receiver: string; // 接收方(手机号/邮箱) - sendTime: number; // 发送时间戳(毫秒) - totalSeconds: number; // 总倒计时时长(默认60) -} - -// 倒计时状态类型(供组件绑定) -export interface CaptchaCountdownState { - countdown: Ref; // 剩余秒数 - isCounting: Ref; // 是否正在倒计时 - timer: number | null; // 定时器标识 - key: string; // 唯一标识(区分不同场景/类型的验证码,如:register_phone) -} - -/** - * 初始化验证码倒计时状态(支持刷新恢复) - * @param key 唯一标识(必填,如:register_phone/register_email/forgotPassword_phone) - * @param totalSeconds 总倒计时时长(默认60秒) - * @returns 倒计时状态对象 - */ -export const initCaptchaState = (key: string, totalSeconds: number = 60): CaptchaCountdownState => { - const state: CaptchaCountdownState = { - countdown: ref(0), - isCounting: ref(false), - timer: null, - key - }; - - // 页面刷新后,从sessionStorage恢复状态 - const stored = getStorage(`captcha_${key}`, 'session'); - if (stored) { - try { - const now = Date.now(); - // 计算剩余时间 = 总时长 - (当前时间 - 发送时间戳)/1000 - const remaining = stored.totalSeconds - Math.floor((now - stored.sendTime) / 1000); - - // 剩余时间>0,恢复倒计时 - if (remaining > 0) { - state.countdown.value = remaining; - state.isCounting.value = true; - - // 重启定时器 - state.timer = window.setInterval(() => { - state.countdown.value--; - // 同步更新本地存储的剩余时间(可选,增强鲁棒性) - updateStoredCaptchaState(state.key, state.countdown.value); - - if (state.countdown.value <= 0) { - clearCaptchaTimer(state); // 倒计时结束,清除状态 - } - }, 1000); - } else { - // 剩余时间<=0,清空本地存储 - removeStorage(`captcha_${key}`, 'session'); - } - } catch (e) { - console.error('恢复验证码倒计时状态失败:', e); - removeStorage(`captcha_${key}`, 'session'); - } - } - - return state; -}; - -/** - * 更新本地存储的验证码状态(内部使用) - */ -const updateStoredCaptchaState = (key: string, remaining: number) => { - const stored = getStorage(`captcha_${key}`, 'session'); - if (stored) { - // 仅更新剩余时间(可选,核心是时间戳,剩余时间只是辅助) - setStorage(`captcha_${key}`, { - ...stored, - // 可选:记录剩余时间,方便初始化时快速读取 - remaining - }, 'session'); - } -}; - -/** - * 通用发送验证码函数(支持手机/邮箱,防刷新重复发送) - * @param type 验证码类型:phone/email - * @param receiver 接收方:手机号/邮箱地址 - * @param state 倒计时状态(由initCaptchaState生成) - * @param sendApi 实际发送验证码的接口函数(需自行实现,入参为receiver) - * @returns Promise 发送是否成功 - */ -export const sendCaptcha = async ( - type: CaptchaType, - receiver: string, - state: CaptchaCountdownState, - sendApi: (receiver: string) => Promise -): Promise => { - // ========== 核心:刷新后防重复发送的校验 ========== - const stored = getStorage(`captcha_${state.key}`, 'session'); - if (stored) { - const now = Date.now(); - const elapsed = Math.floor((now - stored.sendTime) / 1000); // 已过去的秒数 - - // 未到冷却期(60秒),禁止发送 - if (elapsed < stored.totalSeconds) { - const remaining = stored.totalSeconds - elapsed; - ElNotification({ - message: `请${remaining}秒后再发送验证码`, - type: 'warning', - duration: 2000, - customClass: 'global-notification' - }); - // 恢复倒计时(防止内存状态和本地存储不一致) - state.countdown.value = remaining; - state.isCounting.value = true; - return false; - } - } - - // ========== 原有校验 ========== - if (state.isCounting.value) { - ElNotification({ - message: '验证码已发送,请勿重复点击', - type: 'warning', - duration: 2000, - customClass: 'global-notification' - }); - return false; - } - if (!receiver) { - ElNotification({ - message: `请输入${type === 'phone' ? '手机号' : '邮箱'}`, - type: 'warning', - duration: 2000, - customClass: 'global-notification' - }); - return false; - } - - try { - // 1. 调用真实发送接口 - // await sendApi(receiver); - - // 2. 发送成功提示 - ElNotification({ - message: `验证码已发送至${type === 'phone' ? '手机' : '邮箱'},请注意查收`, - type: 'success', - duration: 2000, - customClass: 'global-notification' - }); - - // 3. 记录发送状态到本地存储(核心:防刷新) - const storedState: StoredCaptchaState = { - type, - receiver, - sendTime: Date.now(), // 发送时间戳(毫秒) - totalSeconds: 60 // 总倒计时时长 - }; - setStorage(`captcha_${state.key}`, storedState, 'session'); - - - - // 4. 启动60秒倒计时 - state.isCounting.value = true; - state.countdown.value = 60; - - // 清除旧定时器(防止重复) - if (state.timer) clearInterval(state.timer); - - // 5. 定时器逻辑(同步更新本地存储) - state.timer = window.setInterval(() => { - state.countdown.value--; - // 同步更新本地存储的剩余时间 - updateStoredCaptchaState(state.key, state.countdown.value); - - if (state.countdown.value <= 0) { - clearCaptchaTimer(state); // 倒计时结束,清除所有状态 - } - }, 1000); - - return true; - } catch (error) { - ElNotification({ - message: `发送${type}验证码失败:${(error as Error).message || '未知错误'}`, - type: 'error', - duration: 2000, - customClass: 'global-notification' - }); - console.error(`发送${type}验证码失败:`, error); - return false; - } -}; - -/** - * 清除验证码倒计时(组件卸载/倒计时结束时调用,防止内存泄漏+清空本地存储) - * @param state 倒计时状态 - */ -export const clearCaptchaTimer = (state: CaptchaCountdownState): void => { - // 1. 清除定时器 - if (state.timer) { - clearInterval(state.timer); - state.timer = null; - } - - // 2. 重置内存状态 - state.countdown.value = 0; - state.isCounting.value = false; - - // 3. 清空本地存储(核心:倒计时结束后允许重新发送) - removeStorage(`captcha_${state.key}`, 'session'); -}; \ No newline at end of file diff --git a/src/views/user/ChangePassword.vue b/src/views/user/ChangePassword.vue deleted file mode 100644 index 83fea73..0000000 --- a/src/views/user/ChangePassword.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - - -