邃芒慧语、照片说话(PC) https://photo.metavatar.cc/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

723 rivejä
18 KiB

  1. <template>
  2. <div class="login-container">
  3. <div class="Version">Version 1.0.2</div>
  4. <div class="videoBox">
  5. <video
  6. src="https://video.metavatar.cc/sv/4aaaaa5e-18832dbb5e6/4aaaaa5e-18832dbb5e6.mp4"
  7. muted
  8. autoplay
  9. loop
  10. ></video>
  11. </div>
  12. <el-form
  13. v-if="isLogin"
  14. ref="loginFormRef"
  15. :model="loginData"
  16. :rules="loginRules"
  17. class="login-form"
  18. >
  19. <div class="flex text-white items-center py-4">
  20. <span class="text-2xl flex-1 text-center">{{
  21. $t("login.title") + " " + $t("login.login")
  22. }}</span>
  23. <lang-select style="color: #fff" />
  24. </div>
  25. <el-form-item prop="email">
  26. <div class="p-2 text-white">
  27. <svg-icon icon-class="user" />
  28. </div>
  29. <el-input
  30. class="flex-1"
  31. ref="email"
  32. size="large"
  33. v-model="loginData.email"
  34. :placeholder="$t('login.email')"
  35. name="email"
  36. />
  37. </el-form-item>
  38. <el-form-item prop="password">
  39. <span class="p-2 text-white">
  40. <svg-icon icon-class="password" />
  41. </span>
  42. <el-input
  43. class="flex-1"
  44. v-model="loginData.password"
  45. :placeholder="$t('login.password')"
  46. :type="passwordVisible === false ? 'password' : 'input'"
  47. size="large"
  48. name="password"
  49. @keyup.enter="handleLogin"
  50. />
  51. <span class="mr-2" @click="passwordVisible = !passwordVisible">
  52. <svg-icon
  53. :icon-class="passwordVisible === false ? 'eye' : 'eye-open'"
  54. class="text-white cursor-pointer"
  55. />
  56. </span>
  57. </el-form-item>
  58. <!-- 验证码 -->
  59. <el-form-item prop="verifyCode">
  60. <span class="p-2 text-white">
  61. <svg-icon icon-class="verify_code" />
  62. </span>
  63. <el-input
  64. v-model="loginData.verifyCode"
  65. auto-complete="off"
  66. :placeholder="$t('login.verifyCode')"
  67. class="flex-1"
  68. @keyup.enter="handleLogin"
  69. />
  70. <span class="captcha">
  71. <img v-if="showCodeImg" :src="verifyCodeUrl" @click="getCode" />
  72. </span>
  73. </el-form-item>
  74. <el-button
  75. size="default"
  76. :loading="loading"
  77. type="primary"
  78. class="loginBtn"
  79. @click.prevent="handleLogin"
  80. >
  81. <el-icon
  82. v-if="isLogining"
  83. style="margin-right: 10px"
  84. class="is-loading"
  85. >
  86. <svg
  87. viewBox="0 0 1024 1024"
  88. xmlns="http://www.w3.org/2000/svg"
  89. data-v-ea893728=""
  90. >
  91. <path
  92. fill="currentColor"
  93. 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"
  94. ></path>
  95. </svg>
  96. </el-icon>
  97. {{ $t("login.loginBtn") }}
  98. </el-button>
  99. <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'">
  100. <div style="margin-bottom: 5px" @click="loginFlag">
  101. <span class="join">{{ $t("login.signUp") }}</span>
  102. </div>
  103. <div>
  104. <span class="join">{{ $t("login.forgetPwd") }}</span>
  105. </div>
  106. </div>
  107. </el-form>
  108. <el-form
  109. v-if="!isLogin"
  110. ref="loginFormRef"
  111. :model="loginData2"
  112. :rules="loginRules"
  113. class="login-form"
  114. >
  115. <div class="flex text-white items-center py-4">
  116. <span class="text-2xl flex-1 text-center">{{
  117. $t("login.title") + " " + $t("login.register")
  118. }}</span>
  119. <lang-select style="color: #fff" />
  120. </div>
  121. <el-form-item prop="email">
  122. <div class="p-2 text-white">
  123. <svg-icon icon-class="user" />
  124. </div>
  125. <el-input
  126. class="flex-1"
  127. ref="email"
  128. size="large"
  129. v-model="loginData2.email"
  130. :placeholder="$t('login.email')"
  131. name="email"
  132. />
  133. </el-form-item>
  134. <el-form-item prop="password">
  135. <span class="p-2 text-white">
  136. <svg-icon icon-class="password" />
  137. </span>
  138. <el-input
  139. class="flex-1"
  140. v-model="loginData2.password"
  141. :placeholder="$t('login.password')"
  142. :type="passwordVisible === false ? 'password' : 'input'"
  143. size="large"
  144. name="password"
  145. />
  146. <span class="mr-2" @click="passwordVisible = !passwordVisible">
  147. <svg-icon
  148. :icon-class="passwordVisible === false ? 'eye' : 'eye-open'"
  149. class="text-white cursor-pointer"
  150. />
  151. </span>
  152. </el-form-item>
  153. <el-form-item prop="confirmPassword">
  154. <span class="p-2 text-white">
  155. <svg-icon icon-class="password" />
  156. </span>
  157. <el-input
  158. class="flex-1"
  159. v-model="loginData2.confirmPassword"
  160. :placeholder="$t('login.confirmPassword')"
  161. :type="passwordVisible === false ? 'password' : 'input'"
  162. size="large"
  163. name="confirmPassword"
  164. @keyup.enter="handleRegister"
  165. />
  166. <span class="mr-2" @click="passwordVisible = !passwordVisible">
  167. <svg-icon
  168. :icon-class="passwordVisible === false ? 'eye' : 'eye-open'"
  169. class="text-white cursor-pointer"
  170. />
  171. </span>
  172. </el-form-item>
  173. <el-button
  174. size="default"
  175. :loading="loading"
  176. type="primary"
  177. class="loginBtn"
  178. @click.prevent="handleRegister"
  179. >
  180. <el-icon
  181. v-if="isRegistering"
  182. style="margin-right: 10px"
  183. class="is-loading"
  184. >
  185. <svg
  186. viewBox="0 0 1024 1024"
  187. xmlns="http://www.w3.org/2000/svg"
  188. data-v-ea893728=""
  189. >
  190. <path
  191. fill="currentColor"
  192. 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"
  193. ></path>
  194. </svg>
  195. </el-icon>
  196. {{ $t("login.registerBtn") }}
  197. </el-button>
  198. <div :class="currentLan == 'zh-cn' ? 'bottomBtnCN' : 'bottomBtnEN'">
  199. <div style="margin-bottom: 5px" @click="loginFlag">
  200. <span class="join">{{ $t("login.signIn") }}</span>
  201. </div>
  202. <div>
  203. <span class="join">{{ $t("login.forgetPwd") }}</span>
  204. </div>
  205. </div>
  206. </el-form>
  207. </div>
  208. </template>
  209. <script setup lang="ts">
  210. import LangSelect from "@/components/LangSelect/index.vue";
  211. import SvgIcon from "@/components/SvgIcon/index.vue";
  212. import {
  213. register,
  214. login,
  215. sendRegisterEmail,
  216. getCodeImgUrl,
  217. } from "@/apis/login";
  218. import axios from "axios";
  219. // 状态管理依赖
  220. import { useUserStore } from "@/store/modules/user";
  221. // API依赖
  222. import {
  223. LocationQuery,
  224. LocationQueryValue,
  225. useRoute,
  226. useRouter,
  227. } from "vue-router";
  228. import { useI18n } from "vue-i18n";
  229. const { locale } = useI18n();
  230. const currentLan = ref(locale);
  231. const userStore = useUserStore();
  232. const route = useRoute();
  233. const router = useRouter();
  234. /**
  235. * 按钮loading
  236. */
  237. const loading = ref(false);
  238. /**
  239. * 密码是否可见
  240. */
  241. const passwordVisible = ref(false);
  242. /**
  243. * 验证码图片Base64字符串
  244. */
  245. const verifyCodeUrl = ref();
  246. /**
  247. * 登录/注册
  248. */
  249. const isLogin = ref(true);
  250. const showCodeImg = ref(true);
  251. /**
  252. * 登录表单引用
  253. */
  254. const loginFormRef = ref(ElForm);
  255. const isLogining = ref(false);
  256. const isRegistering = ref(false);
  257. const loginData = ref({
  258. email: "",
  259. password: "",
  260. verifyCode: "",
  261. });
  262. const loginData2 = ref({
  263. email: "",
  264. password: "",
  265. confirmPassword: "",
  266. });
  267. const loginRules = ref({
  268. email: [
  269. {
  270. required: true,
  271. trigger: "blur",
  272. validator: emailValidator,
  273. },
  274. ],
  275. password: [{ required: true, trigger: "blur", validator: passwordValidator }],
  276. confirmPassword: [
  277. { required: true, trigger: "blur", validator: confirmPasswordValidator },
  278. ],
  279. verifyCode: [
  280. {
  281. required: true,
  282. trigger: "blur",
  283. message:
  284. currentLan.value == "zh-cn"
  285. ? "请输入验证码"
  286. : "Please input verifyCode",
  287. },
  288. ],
  289. });
  290. watch(currentLan, (newValue, oldValue) => {
  291. const loginRule = loginRules.value;
  292. loginRule.verifyCode[0].message =
  293. newValue == "zh-cn" ? "请输入验证码" : "Please input verifyCode";
  294. setTimeout(() => {
  295. loginFormRef.value.resetFields();
  296. getInfo();
  297. }, 100);
  298. // 切换语言刷新验证码
  299. getCode();
  300. });
  301. /**
  302. * @description: 切换登录/注册状态
  303. */
  304. function loginFlag() {
  305. isLogin.value = !isLogin.value;
  306. // 切换登录/注册状态刷新验证码
  307. getCode();
  308. setTimeout(() => {
  309. loginFormRef.value.resetFields();
  310. }, 100);
  311. }
  312. /**
  313. * @description: 将账户/密码存储在本地
  314. * @param:email,password
  315. */
  316. function saveInfo(data: any) {
  317. const userInfo = JSON.stringify({
  318. email: data.email,
  319. password: data.password,
  320. });
  321. // 将用户信息加密
  322. const enCodeUserInfo = encodeURI(userInfo);
  323. localStorage.setItem("userInfo", enCodeUserInfo);
  324. }
  325. /**
  326. * @description: 将存储在本地的账户/密码恢复至登录表单
  327. */
  328. function getInfo() {
  329. const data = localStorage.getItem("userInfo");
  330. // 将用户信息解密
  331. const deCodeUserInfo = decodeURI(data as any);
  332. const userInfo = JSON.parse(deCodeUserInfo);
  333. if (userInfo) {
  334. loginData.value.email = userInfo.email ? userInfo.email : "";
  335. loginData.value.password = userInfo.password ? userInfo.password : "";
  336. }
  337. }
  338. /**
  339. * 邮箱校验器
  340. */
  341. function emailValidator(rule: any, value: any, callback: any) {
  342. const msg =
  343. currentLan.value == "zh-cn"
  344. ? "邮箱不能为空!"
  345. : "The E-amil can not be empty";
  346. const msg2 =
  347. currentLan.value == "zh-cn" ? "邮箱格式不正确!" : "Incorrect email format";
  348. const emailReg =
  349. /^(([^<>()[\]\\.,;:\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,}))$/;
  350. if (!value) {
  351. callback(new Error(msg));
  352. } else if (!emailReg.test(value)) {
  353. callback(new Error(msg2));
  354. } else {
  355. callback();
  356. }
  357. }
  358. /**
  359. * 密码校验器
  360. */
  361. function passwordValidator(rule: any, value: any, callback: any) {
  362. const msg =
  363. currentLan.value == "zh-cn"
  364. ? "密码不能为空!"
  365. : "The password can not be empty";
  366. const msg2 =
  367. currentLan.value == "zh-cn"
  368. ? "密码至少为6位!"
  369. : "The password can not be less than 6 digits";
  370. if (!value) {
  371. callback(new Error(msg));
  372. } else if (value.length < 6) {
  373. callback(new Error(msg2));
  374. } else {
  375. callback();
  376. }
  377. }
  378. /**
  379. * 确认密码校验器
  380. */
  381. function confirmPasswordValidator(rule: any, value: any, callback: any) {
  382. const msg =
  383. currentLan.value == "zh-cn"
  384. ? "确认密码不能为空!"
  385. : "The Confirm password can not be empty";
  386. const msg2 =
  387. currentLan.value == "zh-cn"
  388. ? "两次输入密码不一致!"
  389. : "Confirm Passward entered should match";
  390. if (!value) {
  391. callback(new Error(msg));
  392. } else if (value !== loginData2.value.password) {
  393. callback(new Error(msg2));
  394. } else {
  395. callback();
  396. }
  397. }
  398. // 注册行为
  399. function handleRegister() {
  400. loginFormRef.value.validate((val: any) => {
  401. if (val) {
  402. const data = {
  403. email: loginData2.value.email,
  404. password: loginData2.value.password,
  405. };
  406. isRegistering.value = true;
  407. doRegister(data);
  408. } else {
  409. const msg =
  410. currentLan.value == "zh-cn"
  411. ? "请检查信息格式!"
  412. : "Please check the format !";
  413. ElMessage.error(msg);
  414. return;
  415. }
  416. });
  417. }
  418. // 登录行为
  419. function handleLogin() {
  420. loginFormRef.value.validate((val: any) => {
  421. if (val) {
  422. const data = {
  423. email: loginData.value.email,
  424. password: loginData.value.password,
  425. code: loginData.value.verifyCode,
  426. };
  427. isLogining.value = true;
  428. doLogin(data);
  429. } else {
  430. const msg =
  431. currentLan.value == "zh-cn"
  432. ? "请检查信息格式!"
  433. : "Please check the format !";
  434. ElMessage.error(msg);
  435. return;
  436. }
  437. });
  438. }
  439. function getCode() {
  440. showCodeImg.value = false;
  441. axios({
  442. url: getCodeImgUrl,
  443. method: "get",
  444. }).then(() => {
  445. verifyCodeUrl.value = getCodeImgUrl;
  446. showCodeImg.value = true;
  447. });
  448. }
  449. /**
  450. * @description:注册
  451. * @param {Object} data
  452. * @return: data
  453. */
  454. async function doRegister(data: object) {
  455. const msg =
  456. currentLan.value == "zh-cn"
  457. ? "确认信息已发送至您的邮箱,请注意查收!"
  458. : "An email has been send to you, Please pay attention to confirm !";
  459. await register(data)
  460. .then((res) => {
  461. console.log(res, "res");
  462. isRegistering.value = false;
  463. ElMessage.success(msg);
  464. // 注册成功将账号密码保存到本地
  465. saveInfo(data);
  466. })
  467. .catch((err) => {
  468. // 注册失败刷新验证码
  469. getCode();
  470. isRegistering.value = false;
  471. console.log(err, "err");
  472. ElMessage.error(err.message);
  473. });
  474. }
  475. /**
  476. * @description:登录
  477. * @param {Object} data
  478. * @return: data
  479. */
  480. async function doLogin(data: any) {
  481. const msg =
  482. currentLan.value == "zh-cn" ? "登录成功!" : "Welcome to Metavatar!";
  483. await login(data)
  484. .then((res) => {
  485. console.log(res, "res");
  486. localStorage.setItem("AccessToken", res.data.token);
  487. ElMessage.success(msg);
  488. router.push("/createVideo");
  489. // 注册成功将账号密码保存到本地
  490. saveInfo(data);
  491. isLogining.value = false;
  492. })
  493. .catch((err) => {
  494. console.log(err, "err");
  495. // 登录失败刷新验证码
  496. getCode();
  497. ElMessage.error(err.message);
  498. isLogining.value = false;
  499. if (err.code == 2112) {
  500. reSendEmail(data.email);
  501. }
  502. });
  503. }
  504. /**
  505. * @description:重新发送邮件
  506. * @param {string} email
  507. * @return: data
  508. */
  509. async function reSendEmail(email: string) {
  510. const msg =
  511. currentLan.value == "zh-cn"
  512. ? "确认信息已发送至您的邮箱,请注意查收!"
  513. : "An email has been send to you, Please pay attention to confirm !";
  514. await sendRegisterEmail(email)
  515. .then((res) => {
  516. console.log(res, "res");
  517. ElMessage.success(msg);
  518. })
  519. .catch((err) => {
  520. console.log(err, "err");
  521. ElMessage.error(err.message);
  522. });
  523. }
  524. onMounted(() => {
  525. getCode();
  526. getInfo();
  527. });
  528. </script>
  529. <style lang="scss" scoped>
  530. .login-container {
  531. position: relative;
  532. width: 100%;
  533. min-height: 100%;
  534. overflow: hidden;
  535. // background-color: #2d3a4b;
  536. .Version {
  537. position: absolute;
  538. bottom: 20px;
  539. right: 20px;
  540. color: #ffffff;
  541. }
  542. .login-form {
  543. position: absolute;
  544. top: 46%;
  545. left: 10%;
  546. width: 520px;
  547. max-width: 100%;
  548. padding: 20px;
  549. overflow: hidden;
  550. backdrop-filter: blur(5px);
  551. background: linear-gradient(
  552. 123.64deg,
  553. rgba(188, 193, 196, 0.1) -22.71%,
  554. rgba(255, 255, 255, 0.039) 70.04%
  555. );
  556. border-radius: 20px;
  557. transform: translateY(-50%);
  558. border: 1px solid #cacaca;
  559. .captcha {
  560. position: absolute;
  561. top: 0;
  562. right: 0;
  563. img {
  564. width: 120px;
  565. height: 48px;
  566. cursor: pointer;
  567. }
  568. }
  569. }
  570. }
  571. .loginBtn {
  572. width: 100%;
  573. height: 40px;
  574. background: linear-gradient(270deg, #4b61dc, #15d1b8);
  575. transition: all 0.3s;
  576. &:hover {
  577. transform: scale(1.02);
  578. }
  579. }
  580. .bottomBtnCN {
  581. display: flex;
  582. justify-content: space-between;
  583. align-items: center;
  584. padding: 0 10px;
  585. color: #fff;
  586. text-align: center;
  587. margin-top: 20px;
  588. .join {
  589. cursor: pointer;
  590. border-bottom: 2px solid #ffffff00;
  591. padding: 0 3px;
  592. padding-bottom: 2px;
  593. transition: all 0.3s;
  594. &:hover {
  595. border-bottom: 2px solid #ffffff;
  596. }
  597. }
  598. }
  599. .bottomBtnEN {
  600. padding: 0 10px;
  601. color: #fff;
  602. text-align: center;
  603. margin-top: 20px;
  604. .join {
  605. cursor: pointer;
  606. border-bottom: 2px solid #ffffff00;
  607. padding: 0 3px;
  608. padding-bottom: 2px;
  609. transition: all 0.3s;
  610. &:hover {
  611. border-bottom: 2px solid #ffffff;
  612. }
  613. }
  614. }
  615. .videoBox {
  616. position: absolute;
  617. top: 0;
  618. z-index: -1;
  619. width: 100%;
  620. height: 100%;
  621. opacity: 0.8;
  622. }
  623. .el-form-item {
  624. background: rgb(0 0 0 / 10%);
  625. border: 1px solid #ffffff2d;
  626. border-radius: 5px;
  627. }
  628. .el-input {
  629. background: transparent;
  630. // 子组件 scoped 无效,使用 :deep
  631. :deep(.el-input__wrapper) {
  632. padding: 0;
  633. background: transparent;
  634. box-shadow: none;
  635. .el-input__inner {
  636. color: #fff;
  637. background: transparent;
  638. border: 0;
  639. border-radius: 0;
  640. caret-color: #fff;
  641. &:-webkit-autofill {
  642. box-shadow: 0 0 0 1000px transparent inset !important;
  643. -webkit-text-fill-color: #fff !important;
  644. }
  645. // 设置输入框自动填充的延迟属性
  646. &:-webkit-autofill,
  647. &:-webkit-autofill:hover,
  648. &:-webkit-autofill:focus,
  649. &:-webkit-autofill:active {
  650. transition: color 99999s ease-out, background-color 99999s ease-out;
  651. transition-delay: 99999s;
  652. }
  653. }
  654. }
  655. }
  656. @keyframes rotate {
  657. from {
  658. transform: rotate(0deg);
  659. }
  660. to {
  661. transform: rotate(360deg);
  662. }
  663. }
  664. </style>