| @@ -5,4 +5,4 @@ NODE_ENV='development' | |||||
| VITE_APP_TITLE = 'Metavatar-PC' | VITE_APP_TITLE = 'Metavatar-PC' | ||||
| VITE_APP_PORT = 3000 | VITE_APP_PORT = 3000 | ||||
| VITE_APP_BASE_API = '/dev-api' | |||||
| VITE_APP_BASE_API = 'https://photo.metavatar.cc/C' | |||||
| @@ -1,5 +1,8 @@ | |||||
| ## 生产环境 | ## 生产环境 | ||||
| # 变量必须以 VITE_ 为前缀才能暴露给外部读取 | |||||
| NODE_ENV='production' | |||||
| VITE_APP_TITLE = 'Metavatar-PC' | VITE_APP_TITLE = 'Metavatar-PC' | ||||
| VITE_APP_PORT = 3000 | VITE_APP_PORT = 3000 | ||||
| VITE_APP_BASE_API = '/prod-api' | |||||
| VITE_APP_BASE_API = 'https://photo.metavatar.cc/C' | |||||
| @@ -1,6 +0,0 @@ | |||||
| ## 模拟环境 | |||||
| NODE_ENV='staging' | |||||
| VITE_APP_TITLE = 'vue3-element-admin' | |||||
| VITE_APP_PORT = 3000 | |||||
| VITE_APP_BASE_API = '/prod--api' | |||||
| @@ -0,0 +1,8 @@ | |||||
| ## 开发环境 | |||||
| # 变量必须以 VITE_ 为前缀才能暴露给外部读取 | |||||
| NODE_ENV='test' | |||||
| VITE_APP_TITLE = 'Metavatar-PC' | |||||
| VITE_APP_PORT = 3000 | |||||
| VITE_APP_BASE_API = 'https://photo.metavatar.cc/C' | |||||
| @@ -5,7 +5,7 @@ | |||||
| "type": "module", | "type": "module", | ||||
| "scripts": { | "scripts": { | ||||
| "dev": "vite serve --mode development", | "dev": "vite serve --mode development", | ||||
| "build:dev": "vite build --mode development &&vue-tsc --noEmit", | |||||
| "build:dev": "vite build --mode test &&vue-tsc --noEmit", | |||||
| "build:prod": "vite build --mode production &&vue-tsc --noEmit" | "build:prod": "vite build --mode production &&vue-tsc --noEmit" | ||||
| }, | }, | ||||
| "config": { | "config": { | ||||
| @@ -27,7 +27,7 @@ | |||||
| "screenfull": "^6.0.0", | "screenfull": "^6.0.0", | ||||
| "vue": "^3.2.45", | "vue": "^3.2.45", | ||||
| "vue-i18n": "9", | "vue-i18n": "9", | ||||
| "vue-router": "^4.1.6" | |||||
| "vue-router": "^4.0.13" | |||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@commitlint/cli": "^17.5.1", | "@commitlint/cli": "^17.5.1", | ||||
| @@ -71,4 +71,4 @@ | |||||
| "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", | "repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", | ||||
| "author": "有来开源组织", | "author": "有来开源组织", | ||||
| "license": "MIT" | "license": "MIT" | ||||
| } | |||||
| } | |||||
| @@ -0,0 +1,52 @@ | |||||
| import request from "@/utils/request.js"; | |||||
| const baseURL = import.meta.env.VITE_APP_BASE_API | |||||
| /** | |||||
| * @description 注册 | |||||
| * @params email,password | |||||
| * @returns data | |||||
| */ | |||||
| export function register(data) { | |||||
| return request({ | |||||
| url: `/api/user/doRegisterByEmail`, | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description 登录 | |||||
| * @params email,password,code | |||||
| * @returns data | |||||
| */ | |||||
| export function login(data) { | |||||
| return request({ | |||||
| url: `/api/user/loginByEmail`, | |||||
| method: 'post', | |||||
| data | |||||
| }) | |||||
| } | |||||
| /** | |||||
| /** | |||||
| * @description:重新发送邮件 | |||||
| * @param {string} data | |||||
| * @return: data | |||||
| */ | |||||
| export function sendRegisterEmail(email) { | |||||
| return request({ | |||||
| url: `/api/user/sendRegisterEmail`, | |||||
| method: 'post', | |||||
| data: { | |||||
| } | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * @description:获取图形验证码 | |||||
| * @return: 验证码地址 | |||||
| */ | |||||
| export const getCodeImgUrl = baseURL + '/api/user/captcha.jpg' | |||||
| @@ -0,0 +1,16 @@ | |||||
| import request from "@/utils/request.js"; | |||||
| /** | |||||
| * @description 通过ticket激活账户 | |||||
| * @params ticket | |||||
| * @returns data | |||||
| */ | |||||
| export function activateByTicket(ticket) { | |||||
| return request({ | |||||
| url: `/api/user/doActivation`, | |||||
| method: 'post', | |||||
| data: { | |||||
| ticket | |||||
| } | |||||
| }) | |||||
| } | |||||
| @@ -7,11 +7,17 @@ export default { | |||||
| // 登录页面国际化 | // 登录页面国际化 | ||||
| login: { | login: { | ||||
| title: "Metavatar-PC Sign in", | |||||
| title: "Metavatar-PC", | |||||
| username: "Username", | username: "Username", | ||||
| email: "E-mail", | |||||
| password: "Password", | password: "Password", | ||||
| confirmPassword: "Confirm Password", | |||||
| login: "Sign in", | login: "Sign in", | ||||
| register: "Sign up", | |||||
| loginBtn: "Sign in", | |||||
| registerBtn: "Sign up", | |||||
| verifyCode: "Verify Code", | verifyCode: "Verify Code", | ||||
| signIn: "Already got an account? Sign in here", | |||||
| signUp: "Don't have an account? Sign up here", | signUp: "Don't have an account? Sign up here", | ||||
| forgetPwd: "I forgot my password", | forgetPwd: "I forgot my password", | ||||
| }, | }, | ||||
| @@ -79,4 +85,16 @@ export default { | |||||
| WeChatPay: "WeChatPay", | WeChatPay: "WeChatPay", | ||||
| Alipay: "Alipay", | Alipay: "Alipay", | ||||
| }, | }, | ||||
| signSucceed: { | |||||
| title: "Confirm your email", | |||||
| succeed: "Sign Succeed !", | |||||
| welcome: "Welcome to Metavatar !", | |||||
| createSuccess: | |||||
| "Your account has been created successfully. Enjoy yourself !", | |||||
| expired: | |||||
| "Your ticket is expired. You can click the button to resend new email then", | |||||
| sendSuccess: "The email has been resent, you can check it now", | |||||
| goLogin: "Login Now", | |||||
| reSend: "Resend", | |||||
| }, | |||||
| }; | }; | ||||
| @@ -7,11 +7,17 @@ export default { | |||||
| // 登录页面国际化 | // 登录页面国际化 | ||||
| login: { | login: { | ||||
| title: "Metavatar-PC 登录", | |||||
| title: "Metavatar-PC", | |||||
| username: "用户名", | username: "用户名", | ||||
| email: "电子邮件", | |||||
| password: "密码", | password: "密码", | ||||
| login: "登 录", | |||||
| confirmPassword: "确认密码", | |||||
| login: "登录", | |||||
| register: "注册", | |||||
| loginBtn: "登 录", | |||||
| registerBtn: "注 册", | |||||
| verifyCode: "验证码", | verifyCode: "验证码", | ||||
| signIn: "已有账号?现在登录!", | |||||
| signUp: "没有账号?现在加入!", | signUp: "没有账号?现在加入!", | ||||
| forgetPwd: "忘记密码", | forgetPwd: "忘记密码", | ||||
| }, | }, | ||||
| @@ -79,4 +85,15 @@ export default { | |||||
| WeChatPay: "微信支付", | WeChatPay: "微信支付", | ||||
| Alipay: "支付宝支付", | Alipay: "支付宝支付", | ||||
| }, | }, | ||||
| signSucceed: { | |||||
| title: "确认邮箱", | |||||
| succeed: "注册成功", | |||||
| welcome: "欢迎加入Metavatar !", | |||||
| createSuccess: "您的账户已成功创建", | |||||
| expired: | |||||
| "您的通行密钥已过期。稍后,您可以点击下面的按钮重新发送一封新的邮件。", | |||||
| sendSuccess: "您的邮件已重新发送,请注意查收。", | |||||
| goLogin: "即将登录", | |||||
| reSend: "重新发送", | |||||
| }, | |||||
| }; | }; | ||||
| @@ -123,14 +123,8 @@ function logout() { | |||||
| confirmButtonText: confirm, | confirmButtonText: confirm, | ||||
| cancelButtonText: cancel, | cancelButtonText: cancel, | ||||
| }).then(() => { | }).then(() => { | ||||
| userStore | |||||
| .logout() | |||||
| .then(() => { | |||||
| tagsViewStore.delAllViews(); | |||||
| }) | |||||
| .then(() => { | |||||
| router.push(`/login?redirect=${route.fullPath}`); | |||||
| }); | |||||
| router.push(`/login`); | |||||
| localStorage.removeItem("AccessToken"); | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -95,7 +95,7 @@ watch( | |||||
| route, | route, | ||||
| () => { | () => { | ||||
| addTags(); | addTags(); | ||||
| moveToCurrentTag(); | |||||
| // moveToCurrentTag(); | |||||
| }, | }, | ||||
| { | { | ||||
| //初始化立即执行 | //初始化立即执行 | ||||
| @@ -18,7 +18,7 @@ | |||||
| <!--主页面--> | <!--主页面--> | ||||
| <app-main /> | <app-main /> | ||||
| <!-- 设置面板 --> | |||||
| <!-- 设置面板 -->- | |||||
| <RightPanel v-if="showSettings"> | <RightPanel v-if="showSettings"> | ||||
| <settings /> | <settings /> | ||||
| </RightPanel> | </RightPanel> | ||||
| @@ -30,12 +30,15 @@ | |||||
| import { computed, watchEffect } from "vue"; | import { computed, watchEffect } from "vue"; | ||||
| import { useWindowSize } from "@vueuse/core"; | import { useWindowSize } from "@vueuse/core"; | ||||
| import { AppMain, Navbar, Settings, TagsView } from "./components/index"; | import { AppMain, Navbar, Settings, TagsView } from "./components/index"; | ||||
| import SignSucceed from "../views/signSucceed/index.vue"; | |||||
| import Sidebar from "./components/Sidebar/index.vue"; | import Sidebar from "./components/Sidebar/index.vue"; | ||||
| import RightPanel from "@/components/RightPanel/index.vue"; | import RightPanel from "@/components/RightPanel/index.vue"; | ||||
| import { useAppStore } from "@/store/modules/app"; | import { useAppStore } from "@/store/modules/app"; | ||||
| import { useSettingsStore } from "@/store/modules/settings"; | import { useSettingsStore } from "@/store/modules/settings"; | ||||
| import { useRoute, useRouter } from "vue-router"; | |||||
| const route = useRoute(); | |||||
| const { params } = route; | |||||
| const { width } = useWindowSize(); | const { width } = useWindowSize(); | ||||
| /** | /** | ||||
| @@ -80,6 +83,8 @@ watchEffect(() => { | |||||
| function handleOutsideClick() { | function handleOutsideClick() { | ||||
| appStore.closeSideBar(false); | appStore.closeSideBar(false); | ||||
| } | } | ||||
| onMounted(() => {}); | |||||
| </script> | </script> | ||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||
| @@ -25,6 +25,7 @@ setupDirective(app); | |||||
| setupStore(app); | setupStore(app); | ||||
| // element | // element | ||||
| import ElementPlus from "element-plus"; | import ElementPlus from "element-plus"; | ||||
| app.use(ElementPlus); | app.use(ElementPlus); | ||||
| app.use(router).use(i18n).mount("#app"); | app.use(router).use(i18n).mount("#app"); | ||||
| @@ -9,53 +9,53 @@ NProgress.configure({ showSpinner: false }); // 进度条 | |||||
| const permissionStore = usePermissionStoreHook(); | const permissionStore = usePermissionStoreHook(); | ||||
| // 白名单路由 | // 白名单路由 | ||||
| const whiteList = ["/login"]; | |||||
| const whiteList = ["/login", "/signSucceed"]; | |||||
| router.beforeEach(async (to, from, next) => { | |||||
| NProgress.start(); | |||||
| const hasToken = localStorage.getItem("accessToken"); | |||||
| if (hasToken) { | |||||
| if (to.path === "/login") { | |||||
| // 如果已登录,跳转首页 | |||||
| next({ path: "/" }); | |||||
| NProgress.done(); | |||||
| } else { | |||||
| const userStore = useUserStoreHook(); | |||||
| const hasRoles = userStore.roles && userStore.roles.length > 0; | |||||
| if (hasRoles) { | |||||
| // 未匹配到任何路由,跳转404 | |||||
| if (to.matched.length === 0) { | |||||
| from.name ? next({ name: from.name }) : next("/404"); | |||||
| } else { | |||||
| next(); | |||||
| } | |||||
| } else { | |||||
| try { | |||||
| const { roles } = await userStore.getInfo(); | |||||
| const accessRoutes = await permissionStore.generateRoutes(roles); | |||||
| accessRoutes.forEach((route) => { | |||||
| router.addRoute(route); | |||||
| }); | |||||
| next({ ...to, replace: true }); | |||||
| } catch (error) { | |||||
| // 移除 token 并跳转登录页 | |||||
| await userStore.resetToken(); | |||||
| next(`/login?redirect=${to.path}`); | |||||
| NProgress.done(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } else { | |||||
| // 未登录可以访问白名单页面 | |||||
| if (whiteList.indexOf(to.path) !== -1) { | |||||
| next(); | |||||
| } else { | |||||
| next(`/login?redirect=${to.path}`); | |||||
| NProgress.done(); | |||||
| } | |||||
| } | |||||
| }); | |||||
| // router.beforeEach(async (to, from, next) => { | |||||
| // NProgress.start(); | |||||
| // // const hasToken = localStorage.getItem("accessToken"); | |||||
| // // if (hasToken) { | |||||
| // // if (to.path === "/login") { | |||||
| // // // 如果已登录,跳转首页 | |||||
| // // next({ path: "/" }); | |||||
| // // NProgress.done(); | |||||
| // // } else { | |||||
| // // const userStore = useUserStoreHook(); | |||||
| // // const hasRoles = userStore.roles && userStore.roles.length > 0; | |||||
| // // if (hasRoles) { | |||||
| // // // 未匹配到任何路由,跳转404 | |||||
| // // if (to.matched.length === 0) { | |||||
| // // from.name ? next({ name: from.name }) : next("/404"); | |||||
| // // } else { | |||||
| // // next(); | |||||
| // // } | |||||
| // // } else { | |||||
| // // try { | |||||
| // // const { roles } = await userStore.getInfo(); | |||||
| // // const accessRoutes = await permissionStore.generateRoutes(roles); | |||||
| // // accessRoutes.forEach((route) => { | |||||
| // // router.addRoute(route); | |||||
| // // }); | |||||
| // // next({ ...to, replace: true }); | |||||
| // // } catch (error) { | |||||
| // // // 移除 token 并跳转登录页 | |||||
| // // await userStore.resetToken(); | |||||
| // // next(`/login?redirect=${to.path}`); | |||||
| // // NProgress.done(); | |||||
| // // } | |||||
| // // } | |||||
| // // } | |||||
| // // } else { | |||||
| // // // 未登录可以访问白名单页面 | |||||
| // // if (whiteList.indexOf(to.path) !== -1) { | |||||
| // // next(); | |||||
| // // } else { | |||||
| // // next(`/login?redirect=${to.path}`); | |||||
| // // NProgress.done(); | |||||
| // // } | |||||
| // // } | |||||
| // }); | |||||
| router.afterEach(() => { | |||||
| NProgress.done(); | |||||
| }); | |||||
| // router.afterEach(() => { | |||||
| // NProgress.done(); | |||||
| // }); | |||||
| @@ -22,6 +22,11 @@ export const constantRoutes: RouteRecordRaw[] = [ | |||||
| component: () => import("@/views/login/index.vue"), | component: () => import("@/views/login/index.vue"), | ||||
| meta: { hidden: true }, | meta: { hidden: true }, | ||||
| }, | }, | ||||
| { | |||||
| path: "/signSucceed", | |||||
| component: () => import("@/views/signSucceed/index.vue"), | |||||
| meta: { hidden: true }, | |||||
| }, | |||||
| // 创建视频 | // 创建视频 | ||||
| { | { | ||||
| path: "/", | path: "/", | ||||
| @@ -78,7 +83,7 @@ export const constantRoutes: RouteRecordRaw[] = [ | |||||
| }, | }, | ||||
| }, | }, | ||||
| ], | ], | ||||
| },// 修改密码 | |||||
| }, // 修改密码 | |||||
| { | { | ||||
| path: "/", | path: "/", | ||||
| component: Layout, | component: Layout, | ||||
| @@ -116,6 +121,7 @@ export const constantRoutes: RouteRecordRaw[] = [ | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| // { | // { | ||||
| // path: "/dashboard", | // path: "/dashboard", | ||||
| // component: Layout, | // component: Layout, | ||||
| @@ -5,7 +5,6 @@ declare global { | |||||
| const ElForm: typeof import('element-plus/es')['ElForm'] | const ElForm: typeof import('element-plus/es')['ElForm'] | ||||
| const ElMessage: typeof import('element-plus/es')['ElMessage'] | const ElMessage: typeof import('element-plus/es')['ElMessage'] | ||||
| const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] | const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] | ||||
| const ElTree: typeof import('element-plus/es')['ElTree'] | |||||
| const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] | const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] | ||||
| const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] | const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] | ||||
| const computed: typeof import('vue')['computed'] | const computed: typeof import('vue')['computed'] | ||||
| @@ -275,7 +274,6 @@ declare module 'vue' { | |||||
| readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']> | readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']> | ||||
| readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> | readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> | ||||
| readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']> | readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']> | ||||
| readonly ElTree: UnwrapRef<typeof import('element-plus/es')['ElTree']> | |||||
| readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']> | readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']> | ||||
| readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']> | readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']> | ||||
| readonly computed: UnwrapRef<typeof import('vue')['computed']> | readonly computed: UnwrapRef<typeof import('vue')['computed']> | ||||
| @@ -0,0 +1,69 @@ | |||||
| import axios from 'axios' | |||||
| import router from '@/router' | |||||
| import { ElMessage } from 'element-plus' | |||||
| ElMessage | |||||
| // 开发环境需以 "/api" 拼接,其他环境需以 "/C" 拼接 | |||||
| const env = process.env.NODE_ENV == 'development' ? "/api" : "/C" | |||||
| console.log(process.env.NODE_ENV, 'env') | |||||
| /** | |||||
| * @description:发送Axios请求 | |||||
| */ | |||||
| const request = axios.create({ | |||||
| baseURL: env, | |||||
| timeout: 5000 | |||||
| }) | |||||
| // 请求拦截器 | |||||
| request.interceptors.request.use( | |||||
| function (config) { | |||||
| // 给所有授权的请求提供token | |||||
| const AccessToken = localStorage.getItem("AccessToken") | |||||
| if (AccessToken) { | |||||
| config.headers.token = AccessToken | |||||
| } | |||||
| return config | |||||
| }, | |||||
| function (error) { | |||||
| // 操作 | |||||
| return Promise.reject(error) | |||||
| } | |||||
| ) | |||||
| // 响应拦截器 | |||||
| request.interceptors.response.use( | |||||
| function (response) { | |||||
| let code = response.data.code | |||||
| // 当token过期或损坏时 | |||||
| if (code == 1052) { | |||||
| router.push("/login"); | |||||
| ElMessage.error("登录过期,请重新登录!"); | |||||
| return | |||||
| // 弹出错误 | |||||
| } else if (code != 200) { | |||||
| if (code == 1053) { | |||||
| router.push("/login"); | |||||
| // Toast.fail("请您先登录!"); | |||||
| return | |||||
| } else if (code == 500) { | |||||
| ElMessage.error("系统异常,请稍后再试"); | |||||
| // 其他错误 | |||||
| } else { | |||||
| return Promise.reject(response.data) | |||||
| } | |||||
| } else { | |||||
| // 正常返回 | |||||
| return response.data | |||||
| } | |||||
| }, | |||||
| function (error) { | |||||
| } | |||||
| ) | |||||
| export default request | |||||
| @@ -1,11 +1,11 @@ | |||||
| import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; | |||||
| import { useUserStoreHook } from '@/store/modules/user'; | |||||
| import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios"; | |||||
| import { useUserStoreHook } from "@/store/modules/user"; | |||||
| // 创建 axios 实例 | // 创建 axios 实例 | ||||
| const service = axios.create({ | const service = axios.create({ | ||||
| baseURL: import.meta.env.VITE_APP_BASE_API, | baseURL: import.meta.env.VITE_APP_BASE_API, | ||||
| timeout: 50000, | timeout: 50000, | ||||
| headers: { 'Content-Type': 'application/json;charset=utf-8' } | |||||
| headers: { "Content-Type": "application/json;charset=utf-8" }, | |||||
| }); | }); | ||||
| // 请求拦截器 | // 请求拦截器 | ||||
| @@ -26,7 +26,7 @@ service.interceptors.request.use( | |||||
| service.interceptors.response.use( | service.interceptors.response.use( | ||||
| (response: AxiosResponse) => { | (response: AxiosResponse) => { | ||||
| const { code, msg } = response.data; | const { code, msg } = response.data; | ||||
| if (code === '00000') { | |||||
| if (code === "00000") { | |||||
| return response.data; | return response.data; | ||||
| } | } | ||||
| // 响应数据为二进制流处理(Excel导出) | // 响应数据为二进制流处理(Excel导出) | ||||
| @@ -34,23 +34,23 @@ service.interceptors.response.use( | |||||
| return response; | return response; | ||||
| } | } | ||||
| ElMessage.error(msg || '系统出错'); | |||||
| return Promise.reject(new Error(msg || 'Error')); | |||||
| ElMessage.error(msg || "系统出错"); | |||||
| return Promise.reject(new Error(msg || "Error")); | |||||
| }, | }, | ||||
| (error: any) => { | (error: any) => { | ||||
| if (error.response.data) { | if (error.response.data) { | ||||
| const { code, msg } = error.response.data; | const { code, msg } = error.response.data; | ||||
| // token 过期,重新登录 | // token 过期,重新登录 | ||||
| if (code === 'A0230') { | |||||
| ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { | |||||
| confirmButtonText: '确定', | |||||
| type: 'warning' | |||||
| if (code === "A0230") { | |||||
| ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", { | |||||
| confirmButtonText: "确定", | |||||
| type: "warning", | |||||
| }).then(() => { | }).then(() => { | ||||
| localStorage.clear(); | localStorage.clear(); | ||||
| window.location.href = '/'; | |||||
| window.location.href = "/"; | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| ElMessage.error(msg || '系统出错'); | |||||
| ElMessage.error(msg || "系统出错"); | |||||
| } | } | ||||
| } | } | ||||
| return Promise.reject(error.message); | return Promise.reject(error.message); | ||||
| @@ -1,8 +1,9 @@ | |||||
| <template> | <template> | ||||
| <div class="login-container"> | <div class="login-container"> | ||||
| <div class="Version">Version 1.0.4 test</div> | |||||
| <div class="videoBox"> | <div class="videoBox"> | ||||
| <video | <video | ||||
| src="../../assets/video/loginVideo.mp4" | |||||
| src="https://video.metavatar.cc/sv/4aaaaa5e-18832dbb5e6/4aaaaa5e-18832dbb5e6.mp4" | |||||
| muted | muted | ||||
| autoplay | autoplay | ||||
| loop | loop | ||||
| @@ -12,54 +13,50 @@ | |||||
| v-if="isLogin" | v-if="isLogin" | ||||
| ref="loginFormRef" | ref="loginFormRef" | ||||
| :model="loginData" | :model="loginData" | ||||
| :rules="loginRules" | |||||
| class="login-form" | class="login-form" | ||||
| > | > | ||||
| <div class="flex text-white items-center py-4"> | <div class="flex text-white items-center py-4"> | ||||
| <span class="text-2xl flex-1 text-center">{{ $t("login.title") }}</span> | |||||
| <span class="text-2xl flex-1 text-center">{{ | |||||
| $t("login.title") + " " + $t("login.login") | |||||
| }}</span> | |||||
| <lang-select style="color: #fff" /> | <lang-select style="color: #fff" /> | ||||
| </div> | </div> | ||||
| <el-form-item prop="username"> | |||||
| <el-form-item prop="email"> | |||||
| <div class="p-2 text-white"> | <div class="p-2 text-white"> | ||||
| <svg-icon icon-class="user" /> | <svg-icon icon-class="user" /> | ||||
| </div> | </div> | ||||
| <el-input | <el-input | ||||
| class="flex-1" | class="flex-1" | ||||
| ref="username" | |||||
| ref="email" | |||||
| size="large" | size="large" | ||||
| v-model="loginData.username" | |||||
| :placeholder="$t('login.username')" | |||||
| name="username" | |||||
| v-model="loginData.email" | |||||
| :placeholder="$t('login.email')" | |||||
| name="email" | |||||
| /> | /> | ||||
| </el-form-item> | </el-form-item> | ||||
| <el-tooltip | |||||
| :disabled="isCapslock === false" | |||||
| content="Caps lock is On" | |||||
| placement="right" | |||||
| > | |||||
| <el-form-item prop="password"> | |||||
| <span class="p-2 text-white"> | |||||
| <svg-icon icon-class="password" /> | |||||
| </span> | |||||
| <el-input | |||||
| class="flex-1" | |||||
| v-model="loginData.password" | |||||
| placeholder="密码" | |||||
| :type="passwordVisible === false ? 'password' : 'input'" | |||||
| size="large" | |||||
| name="password" | |||||
| @keyup="checkCapslock" | |||||
| @keyup.enter="handleLogin" | |||||
| <el-form-item prop="password"> | |||||
| <span class="p-2 text-white"> | |||||
| <svg-icon icon-class="password" /> | |||||
| </span> | |||||
| <el-input | |||||
| class="flex-1" | |||||
| v-model="loginData.password" | |||||
| :placeholder="$t('login.password')" | |||||
| :type="passwordVisible === false ? 'password' : 'input'" | |||||
| size="large" | |||||
| name="password" | |||||
| @keyup.enter="handleLogin" | |||||
| /> | |||||
| <span class="mr-2" @click="passwordVisible = !passwordVisible"> | |||||
| <svg-icon | |||||
| :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" | |||||
| class="text-white cursor-pointer" | |||||
| /> | /> | ||||
| <span class="mr-2" @click="passwordVisible = !passwordVisible"> | |||||
| <svg-icon | |||||
| :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" | |||||
| class="text-white cursor-pointer" | |||||
| /> | |||||
| </span> | |||||
| </el-form-item> | |||||
| </el-tooltip> | |||||
| </span> | |||||
| </el-form-item> | |||||
| <!-- 验证码 --> | <!-- 验证码 --> | ||||
| <el-form-item prop="verifyCode"> | <el-form-item prop="verifyCode"> | ||||
| @@ -70,13 +67,13 @@ | |||||
| v-model="loginData.verifyCode" | v-model="loginData.verifyCode" | ||||
| auto-complete="off" | auto-complete="off" | ||||
| :placeholder="$t('login.verifyCode')" | :placeholder="$t('login.verifyCode')" | ||||
| class="w-[60%]" | |||||
| class="flex-1" | |||||
| @keyup.enter="handleLogin" | @keyup.enter="handleLogin" | ||||
| /> | /> | ||||
| <div class="captcha"> | |||||
| <img :src="captchaBase64" @click="getCaptcha" /> | |||||
| </div> | |||||
| <span class="captcha"> | |||||
| <img v-if="showCodeImg" :src="verifyCodeUrl" @click="getCode" /> | |||||
| </span> | |||||
| </el-form-item> | </el-form-item> | ||||
| <el-button | <el-button | ||||
| @@ -85,11 +82,28 @@ | |||||
| type="primary" | type="primary" | ||||
| class="loginBtn" | class="loginBtn" | ||||
| @click.prevent="handleLogin" | @click.prevent="handleLogin" | ||||
| >{{ $t("login.login") }} | |||||
| > | |||||
| <el-icon | |||||
| v-if="isLogining" | |||||
| style="margin-right: 10px" | |||||
| class="is-loading" | |||||
| > | |||||
| <svg | |||||
| viewBox="0 0 1024 1024" | |||||
| xmlns="http://www.w3.org/2000/svg" | |||||
| data-v-ea893728="" | |||||
| > | |||||
| <path | |||||
| fill="currentColor" | |||||
| d="M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32zm448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32zm-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32zM195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248zm452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248zM828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0zm-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0z" | |||||
| ></path> | |||||
| </svg> | |||||
| </el-icon> | |||||
| {{ $t("login.loginBtn") }} | |||||
| </el-button> | </el-button> | ||||
| <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'"> | <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'"> | ||||
| <div style="margin-bottom: 5px"> | |||||
| <div style="margin-bottom: 5px" @click="loginFlag"> | |||||
| <span class="join">{{ $t("login.signUp") }}</span> | <span class="join">{{ $t("login.signUp") }}</span> | ||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| @@ -101,72 +115,70 @@ | |||||
| <el-form | <el-form | ||||
| v-if="!isLogin" | v-if="!isLogin" | ||||
| ref="loginFormRef" | ref="loginFormRef" | ||||
| :model="loginData" | |||||
| :model="loginData2" | |||||
| :rules="loginRules" | |||||
| class="login-form" | class="login-form" | ||||
| > | > | ||||
| <div class="flex text-white items-center py-4"> | <div class="flex text-white items-center py-4"> | ||||
| <span class="text-2xl flex-1 text-center">{{ $t("login.title") }}</span> | |||||
| <span class="text-2xl flex-1 text-center">{{ | |||||
| $t("login.title") + " " + $t("login.register") | |||||
| }}</span> | |||||
| <lang-select style="color: #fff" /> | <lang-select style="color: #fff" /> | ||||
| </div> | </div> | ||||
| <el-form-item prop="username"> | |||||
| <el-form-item prop="email"> | |||||
| <div class="p-2 text-white"> | <div class="p-2 text-white"> | ||||
| <svg-icon icon-class="user" /> | <svg-icon icon-class="user" /> | ||||
| </div> | </div> | ||||
| <el-input | <el-input | ||||
| class="flex-1" | class="flex-1" | ||||
| ref="username" | |||||
| ref="email" | |||||
| size="large" | size="large" | ||||
| v-model="loginData.username" | |||||
| :placeholder="$t('login.username')" | |||||
| name="username" | |||||
| v-model="loginData2.email" | |||||
| :placeholder="$t('login.email')" | |||||
| name="email" | |||||
| /> | /> | ||||
| </el-form-item> | </el-form-item> | ||||
| <el-tooltip | |||||
| :disabled="isCapslock === false" | |||||
| content="Caps lock is On" | |||||
| placement="right" | |||||
| > | |||||
| <el-form-item prop="password"> | |||||
| <span class="p-2 text-white"> | |||||
| <svg-icon icon-class="password" /> | |||||
| </span> | |||||
| <el-input | |||||
| class="flex-1" | |||||
| v-model="loginData.password" | |||||
| placeholder="密码" | |||||
| :type="passwordVisible === false ? 'password' : 'input'" | |||||
| size="large" | |||||
| name="password" | |||||
| @keyup="checkCapslock" | |||||
| @keyup.enter="handleLogin" | |||||
| <el-form-item prop="password"> | |||||
| <span class="p-2 text-white"> | |||||
| <svg-icon icon-class="password" /> | |||||
| </span> | |||||
| <el-input | |||||
| class="flex-1" | |||||
| v-model="loginData2.password" | |||||
| :placeholder="$t('login.password')" | |||||
| :type="passwordVisible === false ? 'password' : 'input'" | |||||
| size="large" | |||||
| name="password" | |||||
| /> | |||||
| <span class="mr-2" @click="passwordVisible = !passwordVisible"> | |||||
| <svg-icon | |||||
| :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" | |||||
| class="text-white cursor-pointer" | |||||
| /> | /> | ||||
| <span class="mr-2" @click="passwordVisible = !passwordVisible"> | |||||
| <svg-icon | |||||
| :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" | |||||
| class="text-white cursor-pointer" | |||||
| /> | |||||
| </span> | |||||
| </el-form-item> | |||||
| </el-tooltip> | |||||
| </span> | |||||
| </el-form-item> | |||||
| <!-- 验证码 --> | |||||
| <el-form-item prop="verifyCode"> | |||||
| <el-form-item prop="confirmPassword"> | |||||
| <span class="p-2 text-white"> | <span class="p-2 text-white"> | ||||
| <svg-icon icon-class="verify_code" /> | |||||
| <svg-icon icon-class="password" /> | |||||
| </span> | </span> | ||||
| <el-input | <el-input | ||||
| v-model="loginData.verifyCode" | |||||
| auto-complete="off" | |||||
| :placeholder="$t('login.verifyCode')" | |||||
| class="w-[60%]" | |||||
| @keyup.enter="handleLogin" | |||||
| class="flex-1" | |||||
| v-model="loginData2.confirmPassword" | |||||
| :placeholder="$t('login.confirmPassword')" | |||||
| :type="passwordVisible === false ? 'password' : 'input'" | |||||
| size="large" | |||||
| name="confirmPassword" | |||||
| @keyup.enter="handleRegister" | |||||
| /> | /> | ||||
| <div class="captcha"> | |||||
| <img :src="captchaBase64" @click="getCaptcha" /> | |||||
| </div> | |||||
| <span class="mr-2" @click="passwordVisible = !passwordVisible"> | |||||
| <svg-icon | |||||
| :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" | |||||
| class="text-white cursor-pointer" | |||||
| /> | |||||
| </span> | |||||
| </el-form-item> | </el-form-item> | ||||
| <el-button | <el-button | ||||
| @@ -174,13 +186,30 @@ | |||||
| :loading="loading" | :loading="loading" | ||||
| type="primary" | type="primary" | ||||
| class="loginBtn" | class="loginBtn" | ||||
| @click.prevent="handleLogin" | |||||
| >{{ $t("login.login") }} | |||||
| @click.prevent="handleRegister" | |||||
| > | |||||
| <el-icon | |||||
| v-if="isRegistering" | |||||
| style="margin-right: 10px" | |||||
| class="is-loading" | |||||
| > | |||||
| <svg | |||||
| viewBox="0 0 1024 1024" | |||||
| xmlns="http://www.w3.org/2000/svg" | |||||
| data-v-ea893728="" | |||||
| > | |||||
| <path | |||||
| fill="currentColor" | |||||
| d="M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32zm448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32zm-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32zM195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248zm452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248zM828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0zm-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0z" | |||||
| ></path> | |||||
| </svg> | |||||
| </el-icon> | |||||
| {{ $t("login.registerBtn") }} | |||||
| </el-button> | </el-button> | ||||
| <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'"> | <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'"> | ||||
| <div style="margin-bottom: 5px" @click="loginFlag"> | <div style="margin-bottom: 5px" @click="loginFlag"> | ||||
| <span class="join">{{ $t("login.signUp") }}</span> | |||||
| <span class="join">{{ $t("login.signIn") }}</span> | |||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <span class="join">{{ $t("login.forgetPwd") }}</span> | <span class="join">{{ $t("login.forgetPwd") }}</span> | ||||
| @@ -191,36 +220,36 @@ | |||||
| </template> | </template> | ||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||
| import router from "@/router"; | |||||
| import LangSelect from "@/components/LangSelect/index.vue"; | import LangSelect from "@/components/LangSelect/index.vue"; | ||||
| import SvgIcon from "@/components/SvgIcon/index.vue"; | import SvgIcon from "@/components/SvgIcon/index.vue"; | ||||
| import { | |||||
| register, | |||||
| login, | |||||
| sendRegisterEmail, | |||||
| getCodeImgUrl, | |||||
| } from "@/apis/login"; | |||||
| import axios from "axios"; | |||||
| // 状态管理依赖 | // 状态管理依赖 | ||||
| import { useUserStore } from "@/store/modules/user"; | import { useUserStore } from "@/store/modules/user"; | ||||
| // API依赖 | // API依赖 | ||||
| import { LocationQuery, LocationQueryValue, useRoute } from "vue-router"; | |||||
| import { getCaptchaApi } from "@/api/auth"; | |||||
| import { LoginData } from "@/api/auth/types"; | |||||
| import { | |||||
| LocationQuery, | |||||
| LocationQueryValue, | |||||
| useRoute, | |||||
| useRouter, | |||||
| } from "vue-router"; | |||||
| import { useI18n } from "vue-i18n"; | import { useI18n } from "vue-i18n"; | ||||
| const { locale } = useI18n(); | |||||
| const { locale } = useI18n(); | |||||
| const currentLan = ref(locale); | const currentLan = ref(locale); | ||||
| const userStore = useUserStore(); | const userStore = useUserStore(); | ||||
| const route = useRoute(); | const route = useRoute(); | ||||
| const router = useRouter(); | |||||
| /** | /** | ||||
| * 按钮loading | * 按钮loading | ||||
| */ | */ | ||||
| const loading = ref(false); | const loading = ref(false); | ||||
| /** | |||||
| * 是否大写锁定 | |||||
| */ | |||||
| const isCapslock = ref(false); | |||||
| /** | /** | ||||
| * 密码是否可见 | * 密码是否可见 | ||||
| */ | */ | ||||
| @@ -229,106 +258,313 @@ const passwordVisible = ref(false); | |||||
| /** | /** | ||||
| * 验证码图片Base64字符串 | * 验证码图片Base64字符串 | ||||
| */ | */ | ||||
| const captchaBase64 = ref(); | |||||
| const verifyCodeUrl = ref(); | |||||
| /** | /** | ||||
| * 登录/注册 | * 登录/注册 | ||||
| */ | */ | ||||
| const isLogin = ref(true); | const isLogin = ref(true); | ||||
| const showCodeImg = ref(true); | |||||
| /** | /** | ||||
| * 登录表单引用 | * 登录表单引用 | ||||
| */ | */ | ||||
| const loginFormRef = ref(ElForm); | const loginFormRef = ref(ElForm); | ||||
| const loginData = ref<LoginData>({ | |||||
| username: "admin", | |||||
| password: "123456", | |||||
| const isLogining = ref(false); | |||||
| const isRegistering = ref(false); | |||||
| const loginData = ref({ | |||||
| email: "", | |||||
| password: "", | |||||
| verifyCode: "", | |||||
| }); | |||||
| const loginData2 = ref({ | |||||
| email: "", | |||||
| password: "", | |||||
| confirmPassword: "", | |||||
| }); | }); | ||||
| const loginRules = { | |||||
| username: [{ required: true, trigger: "blur" }], | |||||
| const loginRules = ref({ | |||||
| email: [ | |||||
| { | |||||
| required: true, | |||||
| trigger: "blur", | |||||
| validator: emailValidator, | |||||
| }, | |||||
| ], | |||||
| password: [{ required: true, trigger: "blur", validator: passwordValidator }], | password: [{ required: true, trigger: "blur", validator: passwordValidator }], | ||||
| verifyCode: [{ required: true, trigger: "blur" }], | |||||
| }; | |||||
| confirmPassword: [ | |||||
| { required: true, trigger: "blur", validator: confirmPasswordValidator }, | |||||
| ], | |||||
| verifyCode: [ | |||||
| { | |||||
| required: true, | |||||
| trigger: "blur", | |||||
| message: | |||||
| currentLan.value == "zh-cn" | |||||
| ? "请输入验证码" | |||||
| : "Please input verifyCode", | |||||
| }, | |||||
| ], | |||||
| }); | |||||
| watch(currentLan, (newValue, oldValue) => { | |||||
| const loginRule = loginRules.value; | |||||
| loginRule.verifyCode[0].message = | |||||
| newValue == "zh-cn" ? "请输入验证码" : "Please input verifyCode"; | |||||
| setTimeout(() => { | |||||
| loginFormRef.value.resetFields(); | |||||
| getInfo(); | |||||
| }, 100); | |||||
| // 切换语言刷新验证码 | |||||
| getCode(); | |||||
| }); | |||||
| /** | /** | ||||
| * @description: 切换登录/注册状态 | * @description: 切换登录/注册状态 | ||||
| */ | */ | ||||
| function loginFlag() { | function loginFlag() { | ||||
| isLogin.value = !isLogin; | |||||
| console.log(isLogin.value, "isLogin.value"); | |||||
| isLogin.value = !isLogin.value; | |||||
| // 切换登录/注册状态刷新验证码 | |||||
| getCode(); | |||||
| setTimeout(() => { | |||||
| loginFormRef.value.resetFields(); | |||||
| }, 100); | |||||
| } | } | ||||
| /** | /** | ||||
| * 密码校验器 | |||||
| * @description: 将账户/密码存储在本地 | |||||
| * @param:email,password | |||||
| */ | */ | ||||
| function passwordValidator(rule: any, value: any, callback: any) { | |||||
| if (value.length < 6) { | |||||
| callback(new Error("The password can not be less than 6 digits")); | |||||
| function saveInfo(data: any) { | |||||
| const userInfo = JSON.stringify({ | |||||
| email: data.email, | |||||
| password: data.password, | |||||
| }); | |||||
| // 将用户信息加密 | |||||
| const enCodeUserInfo = encodeURI(userInfo); | |||||
| localStorage.setItem("userInfo", enCodeUserInfo); | |||||
| } | |||||
| /** | |||||
| * @description: 将存储在本地的账户/密码恢复至登录表单 | |||||
| */ | |||||
| function getInfo() { | |||||
| const data = localStorage.getItem("userInfo"); | |||||
| // 将用户信息解密 | |||||
| const deCodeUserInfo = decodeURI(data as any); | |||||
| const userInfo = JSON.parse(deCodeUserInfo); | |||||
| if (userInfo) { | |||||
| loginData.value.email = userInfo.email ? userInfo.email : ""; | |||||
| loginData.value.password = userInfo.password ? userInfo.password : ""; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * 邮箱校验器 | |||||
| */ | |||||
| function emailValidator(rule: any, value: any, callback: any) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "邮箱不能为空!" | |||||
| : "The E-amil can not be empty"; | |||||
| const msg2 = | |||||
| currentLan.value == "zh-cn" ? "邮箱格式不正确!" : "Incorrect email format"; | |||||
| const emailReg = | |||||
| /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | |||||
| if (!value) { | |||||
| callback(new Error(msg)); | |||||
| } else if (!emailReg.test(value)) { | |||||
| callback(new Error(msg2)); | |||||
| } else { | } else { | ||||
| callback(); | callback(); | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * 检查输入大小写状态 | |||||
| * 密码校验器 | |||||
| */ | */ | ||||
| function checkCapslock(e: any) { | |||||
| const { key } = e; | |||||
| isCapslock.value = key && key.length === 1 && key >= "A" && key <= "Z"; | |||||
| function passwordValidator(rule: any, value: any, callback: any) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "密码不能为空!" | |||||
| : "The password can not be empty"; | |||||
| const msg2 = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "密码至少为6位!" | |||||
| : "The password can not be less than 6 digits"; | |||||
| if (!value) { | |||||
| callback(new Error(msg)); | |||||
| } else if (value.length < 6) { | |||||
| callback(new Error(msg2)); | |||||
| } else { | |||||
| callback(); | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| * 获取验证码 | |||||
| * 确认密码校验器 | |||||
| */ | */ | ||||
| function getCaptcha() { | |||||
| getCaptchaApi().then(({ data }) => { | |||||
| const { verifyCodeBase64, verifyCodeKey } = data; | |||||
| loginData.value.verifyCodeKey = verifyCodeKey; | |||||
| captchaBase64.value = verifyCodeBase64; | |||||
| function confirmPasswordValidator(rule: any, value: any, callback: any) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "确认密码不能为空!" | |||||
| : "The Confirm password can not be empty"; | |||||
| const msg2 = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "两次输入密码不一致!" | |||||
| : "Confirm Passward entered should match"; | |||||
| if (!value) { | |||||
| callback(new Error(msg)); | |||||
| } else if (value !== loginData2.value.password) { | |||||
| callback(new Error(msg2)); | |||||
| } else { | |||||
| callback(); | |||||
| } | |||||
| } | |||||
| // 注册行为 | |||||
| function handleRegister() { | |||||
| loginFormRef.value.validate((val: any) => { | |||||
| if (val) { | |||||
| const data = { | |||||
| email: loginData2.value.email, | |||||
| password: loginData2.value.password, | |||||
| }; | |||||
| isRegistering.value = true; | |||||
| doRegister(data); | |||||
| } else { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "请检查信息格式!" | |||||
| : "Please check the format !"; | |||||
| ElMessage.error(msg); | |||||
| return; | |||||
| } | |||||
| }); | }); | ||||
| } | } | ||||
| /** | |||||
| * 登录 | |||||
| */ | |||||
| // 登录行为 | |||||
| function handleLogin() { | function handleLogin() { | ||||
| loginFormRef.value.validate((valid: boolean) => { | |||||
| if (valid) { | |||||
| loading.value = true; | |||||
| userStore | |||||
| .login(loginData.value) | |||||
| .then(() => { | |||||
| const query: LocationQuery = route.query; | |||||
| const redirect = (query.redirect as LocationQueryValue) ?? "/"; | |||||
| const otherQueryParams = Object.keys(query).reduce( | |||||
| (acc: any, cur: string) => { | |||||
| if (cur !== "redirect") { | |||||
| acc[cur] = query[cur]; | |||||
| } | |||||
| return acc; | |||||
| }, | |||||
| {} | |||||
| ); | |||||
| router.push({ path: redirect, query: otherQueryParams }); | |||||
| }) | |||||
| .catch(() => { | |||||
| // 验证失败,重新生成验证码 | |||||
| getCaptcha(); | |||||
| }) | |||||
| .finally(() => { | |||||
| loading.value = false; | |||||
| }); | |||||
| loginFormRef.value.validate((val: any) => { | |||||
| if (val) { | |||||
| const data = { | |||||
| email: loginData.value.email, | |||||
| password: loginData.value.password, | |||||
| code: loginData.value.verifyCode, | |||||
| }; | |||||
| isLogining.value = true; | |||||
| doLogin(data); | |||||
| } else { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "请检查信息格式!" | |||||
| : "Please check the format !"; | |||||
| ElMessage.error(msg); | |||||
| return; | |||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| function getCode() { | |||||
| showCodeImg.value = false; | |||||
| axios({ | |||||
| url: getCodeImgUrl, | |||||
| method: "get", | |||||
| }).then(() => { | |||||
| verifyCodeUrl.value = getCodeImgUrl; | |||||
| showCodeImg.value = true; | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * @description:注册 | |||||
| * @param {Object} data | |||||
| * @return: data | |||||
| */ | |||||
| async function doRegister(data: object) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "确认信息已发送至您的邮箱,请注意查收" | |||||
| : "An email has been send to you, Please pay attention to confirm"; | |||||
| await register(data) | |||||
| .then((res) => { | |||||
| console.log(res, "res"); | |||||
| isRegistering.value = false; | |||||
| ElMessage.success(msg); | |||||
| // 注册成功将账号密码保存到本地 | |||||
| saveInfo(data); | |||||
| }) | |||||
| .catch((err) => { | |||||
| // 注册失败刷新验证码 | |||||
| getCode(); | |||||
| isRegistering.value = false; | |||||
| console.log(err, "err"); | |||||
| ElMessage.error(err.message); | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * @description:登录 | |||||
| * @param {Object} data | |||||
| * @return: data | |||||
| */ | |||||
| async function doLogin(data: any) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" ? "登录成功!" : "Welcome to Metavatar!"; | |||||
| await login(data) | |||||
| .then((res) => { | |||||
| console.log(res, "res"); | |||||
| localStorage.setItem("AccessToken", res.data.token); | |||||
| ElMessage.success(msg); | |||||
| router.push("/createVideo"); | |||||
| // 注册成功将账号密码保存到本地 | |||||
| saveInfo(data); | |||||
| isLogining.value = false; | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err, "err"); | |||||
| // 登录失败刷新验证码 | |||||
| getCode(); | |||||
| ElMessage.error(err.message); | |||||
| isLogining.value = false; | |||||
| if (err.code == 2112) { | |||||
| reSendEmail(data.email); | |||||
| } | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * @description:重新发送邮件 | |||||
| * @param {string} email | |||||
| * @return: data | |||||
| */ | |||||
| async function reSendEmail(email: string) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "确认信息已发送至您的邮箱,请注意查收!" | |||||
| : "An email has been send to you, Please pay attention to confirm !"; | |||||
| await sendRegisterEmail(email) | |||||
| .then((res) => { | |||||
| console.log(res, "res"); | |||||
| ElMessage.success(msg); | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err, "err"); | |||||
| ElMessage.error(err.message); | |||||
| }); | |||||
| } | |||||
| onMounted(() => { | onMounted(() => { | ||||
| getCaptcha(); | |||||
| getCode(); | |||||
| getInfo(); | |||||
| }); | }); | ||||
| </script> | </script> | ||||
| @@ -340,6 +576,13 @@ onMounted(() => { | |||||
| overflow: hidden; | overflow: hidden; | ||||
| // background-color: #2d3a4b; | // background-color: #2d3a4b; | ||||
| .Version { | |||||
| position: absolute; | |||||
| bottom: 20px; | |||||
| right: 20px; | |||||
| color: #ffffff; | |||||
| } | |||||
| .login-form { | .login-form { | ||||
| position: absolute; | position: absolute; | ||||
| top: 46%; | top: 46%; | ||||
| @@ -348,7 +591,7 @@ onMounted(() => { | |||||
| max-width: 100%; | max-width: 100%; | ||||
| padding: 20px; | padding: 20px; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| backdrop-filter: blur(5px); | |||||
| backdrop-filter: blur(3px); | |||||
| background: linear-gradient( | background: linear-gradient( | ||||
| 123.64deg, | 123.64deg, | ||||
| rgba(188, 193, 196, 0.1) -22.71%, | rgba(188, 193, 196, 0.1) -22.71%, | ||||
| @@ -357,6 +600,7 @@ onMounted(() => { | |||||
| border-radius: 20px; | border-radius: 20px; | ||||
| transform: translateY(-50%); | transform: translateY(-50%); | ||||
| border: 1px solid #cacaca; | border: 1px solid #cacaca; | ||||
| transition: all 0.15s; | |||||
| .captcha { | .captcha { | ||||
| position: absolute; | position: absolute; | ||||
| @@ -369,6 +613,9 @@ onMounted(() => { | |||||
| cursor: pointer; | cursor: pointer; | ||||
| } | } | ||||
| } | } | ||||
| &:hover { | |||||
| backdrop-filter: blur(7px); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -423,10 +670,15 @@ onMounted(() => { | |||||
| .videoBox { | .videoBox { | ||||
| position: absolute; | position: absolute; | ||||
| top: 0; | top: 0; | ||||
| z-index: -1; | |||||
| left: 0; | |||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| opacity: 0.8; | |||||
| z-index: -1; | |||||
| video { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| object-fit: fill; | |||||
| } | |||||
| } | } | ||||
| .el-form-item { | .el-form-item { | ||||
| @@ -3,7 +3,7 @@ | |||||
| </template> | </template> | ||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||
| import { useRoute, useRouter } from 'vue-router'; | |||||
| import { useRoute, useRouter } from "vue-router"; | |||||
| const route = useRoute(); | const route = useRoute(); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| @@ -11,5 +11,5 @@ const router = useRouter(); | |||||
| const { params, query } = route; | const { params, query } = route; | ||||
| const { path } = params; | const { path } = params; | ||||
| router.replace({ path: '/' + path, query }); | |||||
| router.replace({ path: "/" + path, query }); | |||||
| </script> | </script> | ||||
| @@ -0,0 +1,168 @@ | |||||
| <template> | |||||
| <div class="content"> | |||||
| <div class="logo" @click="goLogin"> | |||||
| <img src="../../assets/img/left-top-logo.png" alt="" /> | |||||
| </div> | |||||
| <div class="lanSelect"> | |||||
| <lang-select | |||||
| class="navItem" | |||||
| style="color: #ffffff; transform: scale(2)" | |||||
| /> | |||||
| </div> | |||||
| <div v-if="isSuccess == 1"> | |||||
| <div class="welcome">{{ $t("signSucceed.welcome") }}</div> | |||||
| <div class="success">{{ $t("signSucceed.createSuccess") }}</div> | |||||
| <div class="login pointer" @click="goLogin"> | |||||
| {{ $t("signSucceed.goLogin") }} | |||||
| </div> | |||||
| </div> | |||||
| <div v-if="isSuccess == 2"> | |||||
| <div class="welcome">{{ $t("signSucceed.welcome") }}</div> | |||||
| <div class="success"> | |||||
| {{ $t("signSucceed.expired") }} | |||||
| </div> | |||||
| <div class="login pointer" @click="reSend"> | |||||
| {{ $t("signSucceed.reSend") }} | |||||
| </div> | |||||
| </div> | |||||
| <div v-if="isSuccess == 3"> | |||||
| <div class="welcome">{{ $t("signSucceed.welcome") }}</div> | |||||
| <div class="success">{{ $t("signSucceed.sendSuccess") }}</div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script setup lang="ts"> | |||||
| import { useRoute, useRouter } from "vue-router"; | |||||
| import { activateByTicket } from "@/apis/signSucceed"; | |||||
| import { sendRegisterEmail } from "@/apis/login"; | |||||
| import { useI18n } from "vue-i18n"; | |||||
| import LangSelect from "@/components/LangSelect/index.vue"; | |||||
| const { locale } = useI18n(); | |||||
| const currentLan = ref(locale); | |||||
| const route = useRoute(); | |||||
| const router = useRouter(); | |||||
| const { params, query } = route; | |||||
| const isSuccess = ref(0); | |||||
| function doActivateByTicket() { | |||||
| const ticket = query.ticket; | |||||
| activateByTicket(ticket) | |||||
| .then((res) => { | |||||
| console.log(res, "res"); | |||||
| localStorage.setItem("AccessToken", res.data.token); | |||||
| isSuccess.value = 1; | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err, "err"); | |||||
| isSuccess.value = 2; | |||||
| }); | |||||
| } | |||||
| function goLogin() { | |||||
| const AccessToken = localStorage.getItem("AccessToken"); | |||||
| if (AccessToken) { | |||||
| router.push("/createVideo"); | |||||
| } else { | |||||
| router.push("/login"); | |||||
| } | |||||
| } | |||||
| function reSend() { | |||||
| const email = query.email; | |||||
| reSendEmail(email as string); | |||||
| } | |||||
| /** | |||||
| * @description:重新发送邮件 | |||||
| * @param {string} email | |||||
| * @return: data | |||||
| */ | |||||
| async function reSendEmail(email: string) { | |||||
| const msg = | |||||
| currentLan.value == "zh-cn" | |||||
| ? "确认信息已发送至您的邮箱,请注意查收!" | |||||
| : "An email has been send to you, Please pay attention to confirm !"; | |||||
| await sendRegisterEmail(email) | |||||
| .then((res) => { | |||||
| console.log(res, "res"); | |||||
| ElMessage.success(msg); | |||||
| isSuccess.value = 3; | |||||
| }) | |||||
| .catch((err) => { | |||||
| console.log(err, "err"); | |||||
| if (err.code == 2113) { | |||||
| ElMessage.success(err.message); | |||||
| router.push("/login"); | |||||
| return; | |||||
| } | |||||
| ElMessage.error(err.message); | |||||
| router.push("/login"); | |||||
| }); | |||||
| } | |||||
| onMounted(() => { | |||||
| console.log(query, "query"); | |||||
| if (query.ticket || query.email) { | |||||
| doActivateByTicket(); | |||||
| } else { | |||||
| router.push("/login"); | |||||
| } | |||||
| }); | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .content { | |||||
| position: relative; | |||||
| height: 1000px; | |||||
| padding: 100px 0 0 40px; | |||||
| background-color: #000000; | |||||
| color: #ffffff; | |||||
| .logo { | |||||
| position: absolute; | |||||
| top: 20px; | |||||
| left: 20px; | |||||
| width: 200px; | |||||
| cursor: pointer; | |||||
| img { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| .lanSelect { | |||||
| position: absolute; | |||||
| top: 40px; | |||||
| right: 40px; | |||||
| } | |||||
| .success { | |||||
| font-size: 25px; | |||||
| text-align: center; | |||||
| margin-bottom: 28px; | |||||
| } | |||||
| .welcome { | |||||
| font-size: 30px; | |||||
| text-align: center; | |||||
| margin: 28px; | |||||
| } | |||||
| .login { | |||||
| width: 200px; | |||||
| height: 50px; | |||||
| line-height: 50px; | |||||
| text-align: center; | |||||
| color: #ffffff; | |||||
| background: linear-gradient(270deg, #4b61dc, #15d1b8); | |||||
| border-radius: 10px; | |||||
| transition: all 0.3s; | |||||
| margin: auto; | |||||
| &:hover { | |||||
| transform: scale(1.1); | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -42,12 +42,10 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => { | |||||
| open: true, // 运行是否自动打开浏览器 | open: true, // 运行是否自动打开浏览器 | ||||
| proxy: { | proxy: { | ||||
| // 反向代理解决跨域 | // 反向代理解决跨域 | ||||
| [env.VITE_APP_BASE_API]: { | |||||
| target: "http://vapi.youlai.tech", // 线上接口地址 | |||||
| // target: 'http://localhost:8989', // 本地接口地址 | |||||
| "/api": { | |||||
| target: env.VITE_APP_BASE_API, // 线上接口地址 | |||||
| changeOrigin: true, | changeOrigin: true, | ||||
| rewrite: (path) => | |||||
| path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址 | |||||
| rewrite: (path) => path.replace(/^\/api/, ""), | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||