@@ -5,4 +5,4 @@ NODE_ENV='development' | |||
VITE_APP_TITLE = 'Metavatar-PC' | |||
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_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", | |||
"scripts": { | |||
"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" | |||
}, | |||
"config": { | |||
@@ -27,7 +27,7 @@ | |||
"screenfull": "^6.0.0", | |||
"vue": "^3.2.45", | |||
"vue-i18n": "9", | |||
"vue-router": "^4.1.6" | |||
"vue-router": "^4.0.13" | |||
}, | |||
"devDependencies": { | |||
"@commitlint/cli": "^17.5.1", | |||
@@ -71,4 +71,4 @@ | |||
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git", | |||
"author": "有来开源组织", | |||
"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: { | |||
title: "Metavatar-PC Sign in", | |||
title: "Metavatar-PC", | |||
username: "Username", | |||
email: "E-mail", | |||
password: "Password", | |||
confirmPassword: "Confirm Password", | |||
login: "Sign in", | |||
register: "Sign up", | |||
loginBtn: "Sign in", | |||
registerBtn: "Sign up", | |||
verifyCode: "Verify Code", | |||
signIn: "Already got an account? Sign in here", | |||
signUp: "Don't have an account? Sign up here", | |||
forgetPwd: "I forgot my password", | |||
}, | |||
@@ -79,4 +85,16 @@ export default { | |||
WeChatPay: "WeChatPay", | |||
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: { | |||
title: "Metavatar-PC 登录", | |||
title: "Metavatar-PC", | |||
username: "用户名", | |||
email: "电子邮件", | |||
password: "密码", | |||
login: "登 录", | |||
confirmPassword: "确认密码", | |||
login: "登录", | |||
register: "注册", | |||
loginBtn: "登 录", | |||
registerBtn: "注 册", | |||
verifyCode: "验证码", | |||
signIn: "已有账号?现在登录!", | |||
signUp: "没有账号?现在加入!", | |||
forgetPwd: "忘记密码", | |||
}, | |||
@@ -79,4 +85,15 @@ export default { | |||
WeChatPay: "微信支付", | |||
Alipay: "支付宝支付", | |||
}, | |||
signSucceed: { | |||
title: "确认邮箱", | |||
succeed: "注册成功", | |||
welcome: "欢迎加入Metavatar !", | |||
createSuccess: "您的账户已成功创建", | |||
expired: | |||
"您的通行密钥已过期。稍后,您可以点击下面的按钮重新发送一封新的邮件。", | |||
sendSuccess: "您的邮件已重新发送,请注意查收。", | |||
goLogin: "即将登录", | |||
reSend: "重新发送", | |||
}, | |||
}; |
@@ -123,14 +123,8 @@ function logout() { | |||
confirmButtonText: confirm, | |||
cancelButtonText: cancel, | |||
}).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, | |||
() => { | |||
addTags(); | |||
moveToCurrentTag(); | |||
// moveToCurrentTag(); | |||
}, | |||
{ | |||
//初始化立即执行 | |||
@@ -18,7 +18,7 @@ | |||
<!--主页面--> | |||
<app-main /> | |||
<!-- 设置面板 --> | |||
<!-- 设置面板 -->- | |||
<RightPanel v-if="showSettings"> | |||
<settings /> | |||
</RightPanel> | |||
@@ -30,12 +30,15 @@ | |||
import { computed, watchEffect } from "vue"; | |||
import { useWindowSize } from "@vueuse/core"; | |||
import { AppMain, Navbar, Settings, TagsView } from "./components/index"; | |||
import SignSucceed from "../views/signSucceed/index.vue"; | |||
import Sidebar from "./components/Sidebar/index.vue"; | |||
import RightPanel from "@/components/RightPanel/index.vue"; | |||
import { useAppStore } from "@/store/modules/app"; | |||
import { useSettingsStore } from "@/store/modules/settings"; | |||
import { useRoute, useRouter } from "vue-router"; | |||
const route = useRoute(); | |||
const { params } = route; | |||
const { width } = useWindowSize(); | |||
/** | |||
@@ -80,6 +83,8 @@ watchEffect(() => { | |||
function handleOutsideClick() { | |||
appStore.closeSideBar(false); | |||
} | |||
onMounted(() => {}); | |||
</script> | |||
<style lang="scss" scoped> | |||
@@ -25,6 +25,7 @@ setupDirective(app); | |||
setupStore(app); | |||
// element | |||
import ElementPlus from "element-plus"; | |||
app.use(ElementPlus); | |||
app.use(router).use(i18n).mount("#app"); |
@@ -9,53 +9,53 @@ NProgress.configure({ showSpinner: false }); // 进度条 | |||
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"), | |||
meta: { hidden: true }, | |||
}, | |||
{ | |||
path: "/signSucceed", | |||
component: () => import("@/views/signSucceed/index.vue"), | |||
meta: { hidden: true }, | |||
}, | |||
// 创建视频 | |||
{ | |||
path: "/", | |||
@@ -78,7 +83,7 @@ export const constantRoutes: RouteRecordRaw[] = [ | |||
}, | |||
}, | |||
], | |||
},// 修改密码 | |||
}, // 修改密码 | |||
{ | |||
path: "/", | |||
component: Layout, | |||
@@ -116,6 +121,7 @@ export const constantRoutes: RouteRecordRaw[] = [ | |||
}, | |||
], | |||
}, | |||
// { | |||
// path: "/dashboard", | |||
// component: Layout, | |||
@@ -5,7 +5,6 @@ declare global { | |||
const ElForm: typeof import('element-plus/es')['ElForm'] | |||
const ElMessage: typeof import('element-plus/es')['ElMessage'] | |||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] | |||
const ElTree: typeof import('element-plus/es')['ElTree'] | |||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] | |||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] | |||
const computed: typeof import('vue')['computed'] | |||
@@ -275,7 +274,6 @@ declare module 'vue' { | |||
readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']> | |||
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> | |||
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 autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']> | |||
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 实例 | |||
const service = axios.create({ | |||
baseURL: import.meta.env.VITE_APP_BASE_API, | |||
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( | |||
(response: AxiosResponse) => { | |||
const { code, msg } = response.data; | |||
if (code === '00000') { | |||
if (code === "00000") { | |||
return response.data; | |||
} | |||
// 响应数据为二进制流处理(Excel导出) | |||
@@ -34,23 +34,23 @@ service.interceptors.response.use( | |||
return response; | |||
} | |||
ElMessage.error(msg || '系统出错'); | |||
return Promise.reject(new Error(msg || 'Error')); | |||
ElMessage.error(msg || "系统出错"); | |||
return Promise.reject(new Error(msg || "Error")); | |||
}, | |||
(error: any) => { | |||
if (error.response.data) { | |||
const { code, msg } = error.response.data; | |||
// token 过期,重新登录 | |||
if (code === 'A0230') { | |||
ElMessageBox.confirm('当前页面已失效,请重新登录', '提示', { | |||
confirmButtonText: '确定', | |||
type: 'warning' | |||
if (code === "A0230") { | |||
ElMessageBox.confirm("当前页面已失效,请重新登录", "提示", { | |||
confirmButtonText: "确定", | |||
type: "warning", | |||
}).then(() => { | |||
localStorage.clear(); | |||
window.location.href = '/'; | |||
window.location.href = "/"; | |||
}); | |||
} else { | |||
ElMessage.error(msg || '系统出错'); | |||
ElMessage.error(msg || "系统出错"); | |||
} | |||
} | |||
return Promise.reject(error.message); | |||
@@ -1,8 +1,9 @@ | |||
<template> | |||
<div class="login-container"> | |||
<div class="Version">Version 1.0.4 test</div> | |||
<div class="videoBox"> | |||
<video | |||
src="../../assets/video/loginVideo.mp4" | |||
src="https://video.metavatar.cc/sv/4aaaaa5e-18832dbb5e6/4aaaaa5e-18832dbb5e6.mp4" | |||
muted | |||
autoplay | |||
loop | |||
@@ -12,54 +13,50 @@ | |||
v-if="isLogin" | |||
ref="loginFormRef" | |||
:model="loginData" | |||
:rules="loginRules" | |||
class="login-form" | |||
> | |||
<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" /> | |||
</div> | |||
<el-form-item prop="username"> | |||
<el-form-item prop="email"> | |||
<div class="p-2 text-white"> | |||
<svg-icon icon-class="user" /> | |||
</div> | |||
<el-input | |||
class="flex-1" | |||
ref="username" | |||
ref="email" | |||
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-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"> | |||
@@ -70,13 +67,13 @@ | |||
v-model="loginData.verifyCode" | |||
auto-complete="off" | |||
:placeholder="$t('login.verifyCode')" | |||
class="w-[60%]" | |||
class="flex-1" | |||
@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-button | |||
@@ -85,11 +82,28 @@ | |||
type="primary" | |||
class="loginBtn" | |||
@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> | |||
<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> | |||
</div> | |||
<div> | |||
@@ -101,72 +115,70 @@ | |||
<el-form | |||
v-if="!isLogin" | |||
ref="loginFormRef" | |||
:model="loginData" | |||
:model="loginData2" | |||
:rules="loginRules" | |||
class="login-form" | |||
> | |||
<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" /> | |||
</div> | |||
<el-form-item prop="username"> | |||
<el-form-item prop="email"> | |||
<div class="p-2 text-white"> | |||
<svg-icon icon-class="user" /> | |||
</div> | |||
<el-input | |||
class="flex-1" | |||
ref="username" | |||
ref="email" | |||
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-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"> | |||
<svg-icon icon-class="verify_code" /> | |||
<svg-icon icon-class="password" /> | |||
</span> | |||
<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-button | |||
@@ -174,13 +186,30 @@ | |||
:loading="loading" | |||
type="primary" | |||
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> | |||
<div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'"> | |||
<div style="margin-bottom: 5px" @click="loginFlag"> | |||
<span class="join">{{ $t("login.signUp") }}</span> | |||
<span class="join">{{ $t("login.signIn") }}</span> | |||
</div> | |||
<div> | |||
<span class="join">{{ $t("login.forgetPwd") }}</span> | |||
@@ -191,36 +220,36 @@ | |||
</template> | |||
<script setup lang="ts"> | |||
import router from "@/router"; | |||
import LangSelect from "@/components/LangSelect/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"; | |||
// 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"; | |||
const { locale } = useI18n(); | |||
const { locale } = useI18n(); | |||
const currentLan = ref(locale); | |||
const userStore = useUserStore(); | |||
const route = useRoute(); | |||
const router = useRouter(); | |||
/** | |||
* 按钮loading | |||
*/ | |||
const loading = ref(false); | |||
/** | |||
* 是否大写锁定 | |||
*/ | |||
const isCapslock = ref(false); | |||
/** | |||
* 密码是否可见 | |||
*/ | |||
@@ -229,106 +258,313 @@ const passwordVisible = ref(false); | |||
/** | |||
* 验证码图片Base64字符串 | |||
*/ | |||
const captchaBase64 = ref(); | |||
const verifyCodeUrl = ref(); | |||
/** | |||
* 登录/注册 | |||
*/ | |||
const isLogin = ref(true); | |||
const showCodeImg = ref(true); | |||
/** | |||
* 登录表单引用 | |||
*/ | |||
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 }], | |||
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: 切换登录/注册状态 | |||
*/ | |||
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 { | |||
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() { | |||
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(() => { | |||
getCaptcha(); | |||
getCode(); | |||
getInfo(); | |||
}); | |||
</script> | |||
@@ -340,6 +576,13 @@ onMounted(() => { | |||
overflow: hidden; | |||
// background-color: #2d3a4b; | |||
.Version { | |||
position: absolute; | |||
bottom: 20px; | |||
right: 20px; | |||
color: #ffffff; | |||
} | |||
.login-form { | |||
position: absolute; | |||
top: 46%; | |||
@@ -348,7 +591,7 @@ onMounted(() => { | |||
max-width: 100%; | |||
padding: 20px; | |||
overflow: hidden; | |||
backdrop-filter: blur(5px); | |||
backdrop-filter: blur(3px); | |||
background: linear-gradient( | |||
123.64deg, | |||
rgba(188, 193, 196, 0.1) -22.71%, | |||
@@ -357,6 +600,7 @@ onMounted(() => { | |||
border-radius: 20px; | |||
transform: translateY(-50%); | |||
border: 1px solid #cacaca; | |||
transition: all 0.15s; | |||
.captcha { | |||
position: absolute; | |||
@@ -369,6 +613,9 @@ onMounted(() => { | |||
cursor: pointer; | |||
} | |||
} | |||
&:hover { | |||
backdrop-filter: blur(7px); | |||
} | |||
} | |||
} | |||
@@ -423,10 +670,15 @@ onMounted(() => { | |||
.videoBox { | |||
position: absolute; | |||
top: 0; | |||
z-index: -1; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
opacity: 0.8; | |||
z-index: -1; | |||
video { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: fill; | |||
} | |||
} | |||
.el-form-item { | |||
@@ -3,7 +3,7 @@ | |||
</template> | |||
<script setup lang="ts"> | |||
import { useRoute, useRouter } from 'vue-router'; | |||
import { useRoute, useRouter } from "vue-router"; | |||
const route = useRoute(); | |||
const router = useRouter(); | |||
@@ -11,5 +11,5 @@ const router = useRouter(); | |||
const { params, query } = route; | |||
const { path } = params; | |||
router.replace({ path: '/' + path, query }); | |||
router.replace({ path: "/" + path, query }); | |||
</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, // 运行是否自动打开浏览器 | |||
proxy: { | |||
// 反向代理解决跨域 | |||
[env.VITE_APP_BASE_API]: { | |||
target: "http://vapi.youlai.tech", // 线上接口地址 | |||
// target: 'http://localhost:8989', // 本地接口地址 | |||
"/api": { | |||
target: env.VITE_APP_BASE_API, // 线上接口地址 | |||
changeOrigin: true, | |||
rewrite: (path) => | |||
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""), // 替换 /dev-api 为 target 接口地址 | |||
rewrite: (path) => path.replace(/^\/api/, ""), | |||
}, | |||
}, | |||
}, | |||