|
@@ -2,35 +2,33 @@
|
|
|
import { useRouter } from "vue-router";
|
|
import { useRouter } from "vue-router";
|
|
|
import { message } from "@/utils/message";
|
|
import { message } from "@/utils/message";
|
|
|
import { useNav } from "@/layout/hooks/useNav";
|
|
import { useNav } from "@/layout/hooks/useNav";
|
|
|
-import TypeIt from "@/components/ReTypeit";
|
|
|
|
|
import { useEventListener } from "@vueuse/core";
|
|
import { useEventListener } from "@vueuse/core";
|
|
|
import type { FormInstance } from "element-plus";
|
|
import type { FormInstance } from "element-plus";
|
|
|
import { useLayout } from "@/layout/hooks/useLayout";
|
|
import { useLayout } from "@/layout/hooks/useLayout";
|
|
|
import { useUserStoreHook } from "@/store/modules/user";
|
|
import { useUserStoreHook } from "@/store/modules/user";
|
|
|
import { initRouter, getTopMenu } from "@/router/utils";
|
|
import { initRouter, getTopMenu } from "@/router/utils";
|
|
|
-import { bg, avatar, illustration } from "./utils/static";
|
|
|
|
|
-import { ref, reactive } from "vue";
|
|
|
|
|
|
|
+import { avatar } from "./utils/static";
|
|
|
|
|
+import { ref, reactive, computed } from "vue";
|
|
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
|
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
|
|
|
|
+import ReInteractiveHoverButton from "@/components/ReInteractiveHoverButton";
|
|
|
|
|
+import LoginCharacters from "./components/LoginCharacters.vue";
|
|
|
|
|
|
|
|
import dayIcon from "@/assets/svg/day.svg?component";
|
|
import dayIcon from "@/assets/svg/day.svg?component";
|
|
|
import darkIcon from "@/assets/svg/dark.svg?component";
|
|
import darkIcon from "@/assets/svg/dark.svg?component";
|
|
|
import Lock from "~icons/ri/lock-fill";
|
|
import Lock from "~icons/ri/lock-fill";
|
|
|
import User from "~icons/ri/user-3-fill";
|
|
import User from "~icons/ri/user-3-fill";
|
|
|
|
|
|
|
|
-// RSA 加密
|
|
|
|
|
import { JSEncrypt } from "jsencrypt";
|
|
import { JSEncrypt } from "jsencrypt";
|
|
|
|
|
|
|
|
const encryptPassword = (password: string) => {
|
|
const encryptPassword = (password: string) => {
|
|
|
const encryptor = new JSEncrypt();
|
|
const encryptor = new JSEncrypt();
|
|
|
- const publicKey = import.meta.env.VITE_PUBLIC_KEY || '';
|
|
|
|
|
|
|
+ const publicKey = import.meta.env.VITE_PUBLIC_KEY || "";
|
|
|
encryptor.setPublicKey(publicKey);
|
|
encryptor.setPublicKey(publicKey);
|
|
|
return encryptor.encrypt(password) || password;
|
|
return encryptor.encrypt(password) || password;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-defineOptions({
|
|
|
|
|
- name: "Login"
|
|
|
|
|
-});
|
|
|
|
|
|
|
+defineOptions({ name: "Login" });
|
|
|
|
|
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
@@ -43,27 +41,33 @@ const { dataTheme, themeMode, dataThemeChange } = useDataThemeChange();
|
|
|
dataThemeChange(themeMode.value);
|
|
dataThemeChange(themeMode.value);
|
|
|
const { title } = useNav();
|
|
const { title } = useNav();
|
|
|
|
|
|
|
|
-const ruleForm = reactive({
|
|
|
|
|
- username: "",
|
|
|
|
|
- password: ""
|
|
|
|
|
-});
|
|
|
|
|
|
|
+const ruleForm = reactive({ username: "", password: "" });
|
|
|
|
|
+
|
|
|
|
|
+// 角色动画状态
|
|
|
|
|
+const isTyping = ref(false);
|
|
|
|
|
+const showPassword = ref(false);
|
|
|
|
|
+const passwordLength = computed(() => ruleForm.password.length);
|
|
|
|
|
+
|
|
|
|
|
+// 监听密码可见性 (Element Plus show-password 切换)
|
|
|
|
|
+function onPasswordToggle() {
|
|
|
|
|
+ showPassword.value = !showPassword.value;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
|
|
if (!formEl) return;
|
|
if (!formEl) return;
|
|
|
- await formEl.validate(valid => {
|
|
|
|
|
|
|
+ await formEl.validate((valid) => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
useUserStoreHook()
|
|
useUserStoreHook()
|
|
|
.loginByUsername({
|
|
.loginByUsername({
|
|
|
mobilePhone: ruleForm.username,
|
|
mobilePhone: ruleForm.username,
|
|
|
- password: encryptPassword(ruleForm.password)
|
|
|
|
|
|
|
+ password: encryptPassword(ruleForm.password),
|
|
|
})
|
|
})
|
|
|
- .then(res => {
|
|
|
|
|
|
|
+ .then((res) => {
|
|
|
if (res.success) {
|
|
if (res.success) {
|
|
|
return initRouter().then(() => {
|
|
return initRouter().then(() => {
|
|
|
disabled.value = true;
|
|
disabled.value = true;
|
|
|
const topMenu = getTopMenu(true);
|
|
const topMenu = getTopMenu(true);
|
|
|
- // 默认跳转到运营管理仪表盘
|
|
|
|
|
const targetPath = topMenu?.path || "/admin/dashboard";
|
|
const targetPath = topMenu?.path || "/admin/dashboard";
|
|
|
router
|
|
router
|
|
|
.push(targetPath)
|
|
.push(targetPath)
|
|
@@ -76,7 +80,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
|
|
message("登录失败", { type: "error" });
|
|
message("登录失败", { type: "error" });
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
- .catch(error => {
|
|
|
|
|
|
|
+ .catch((error) => {
|
|
|
message(error?.message || "登录失败", { type: "error" });
|
|
message(error?.message || "登录失败", { type: "error" });
|
|
|
})
|
|
})
|
|
|
.finally(() => (loading.value = false));
|
|
.finally(() => (loading.value = false));
|
|
@@ -85,82 +89,62 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
useEventListener(document, "keydown", ({ code }) => {
|
|
useEventListener(document, "keydown", ({ code }) => {
|
|
|
- if (
|
|
|
|
|
- ["Enter", "NumpadEnter"].includes(code) &&
|
|
|
|
|
- !disabled.value &&
|
|
|
|
|
- !loading.value
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ if (["Enter", "NumpadEnter"].includes(code) && !disabled.value && !loading.value)
|
|
|
onLogin(ruleFormRef.value);
|
|
onLogin(ruleFormRef.value);
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="select-none">
|
|
|
|
|
- <img :src="bg" class="wave" />
|
|
|
|
|
- <div class="flex-c absolute right-5 top-3">
|
|
|
|
|
- <!-- 主题切换 -->
|
|
|
|
|
- <el-switch
|
|
|
|
|
- v-model="dataTheme"
|
|
|
|
|
- inline-prompt
|
|
|
|
|
- :active-icon="dayIcon"
|
|
|
|
|
- :inactive-icon="darkIcon"
|
|
|
|
|
- @change="dataThemeChange"
|
|
|
|
|
- />
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="login-container">
|
|
|
|
|
- <div class="img">
|
|
|
|
|
- <component :is="illustration" />
|
|
|
|
|
|
|
+ <div class="login-page">
|
|
|
|
|
+ <div class="login-brand-panel">
|
|
|
|
|
+ <div class="login-brand-logo">
|
|
|
|
|
+ <component :is="avatar" />
|
|
|
|
|
+ <span>{{ title }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="login-characters-area">
|
|
|
|
|
+ <LoginCharacters
|
|
|
|
|
+ :is-typing="isTyping"
|
|
|
|
|
+ :show-password="showPassword"
|
|
|
|
|
+ :password-length="passwordLength"
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="login-box">
|
|
|
|
|
- <div class="login-form">
|
|
|
|
|
- <avatar class="avatar" />
|
|
|
|
|
- <h2 class="outline-hidden">
|
|
|
|
|
- <TypeIt
|
|
|
|
|
- :options="{ strings: [title], cursor: false, speed: 100 }"
|
|
|
|
|
- />
|
|
|
|
|
- </h2>
|
|
|
|
|
|
|
+ <div class="login-brand-footer">
|
|
|
|
|
+ <span>© {{ new Date().getFullYear() }} {{ title }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="login-brand-grid" />
|
|
|
|
|
+ <div class="login-brand-orb login-brand-orb--1" />
|
|
|
|
|
+ <div class="login-brand-orb login-brand-orb--2" />
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <el-form
|
|
|
|
|
- ref="ruleFormRef"
|
|
|
|
|
- :model="ruleForm"
|
|
|
|
|
- size="large"
|
|
|
|
|
- >
|
|
|
|
|
- <el-form-item
|
|
|
|
|
- :rules="[{ required: true, message: '请输入用户名', trigger: 'blur' }]"
|
|
|
|
|
- prop="username"
|
|
|
|
|
- >
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="ruleForm.username"
|
|
|
|
|
- clearable
|
|
|
|
|
- placeholder="请输入用户名"
|
|
|
|
|
- :prefix-icon="useRenderIcon(User)"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div class="login-form-panel">
|
|
|
|
|
+ <div class="absolute right-5 top-3" style="z-index: 10">
|
|
|
|
|
+ <el-switch
|
|
|
|
|
+ v-model="dataTheme"
|
|
|
|
|
+ inline-prompt
|
|
|
|
|
+ :active-icon="dayIcon"
|
|
|
|
|
+ :inactive-icon="darkIcon"
|
|
|
|
|
+ @change="dataThemeChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="login-form-wrap">
|
|
|
|
|
+ <div class="login-card">
|
|
|
|
|
+ <div class="login-mobile-logo">
|
|
|
|
|
+ <component :is="avatar" />
|
|
|
|
|
+ <span>{{ title }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="login-header">
|
|
|
|
|
+ <h1>欢迎回来</h1>
|
|
|
|
|
+ <p>请输入您的账号信息</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-form ref="ruleFormRef" :model="ruleForm" class="login-form" size="large">
|
|
|
|
|
+ <el-form-item :rules="[{ required: true, message: '请输入用户名', trigger: 'blur' }]" prop="username">
|
|
|
|
|
+ <el-input v-model="ruleForm.username" clearable placeholder="请输入用户名" :prefix-icon="useRenderIcon(User)" @focus="isTyping = true" @blur="isTyping = false" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
-
|
|
|
|
|
- <el-form-item
|
|
|
|
|
- :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]"
|
|
|
|
|
- prop="password"
|
|
|
|
|
- >
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model="ruleForm.password"
|
|
|
|
|
- clearable
|
|
|
|
|
- show-password
|
|
|
|
|
- placeholder="请输入密码"
|
|
|
|
|
- :prefix-icon="useRenderIcon(Lock)"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-form-item :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]" prop="password">
|
|
|
|
|
+ <el-input v-model="ruleForm.password" clearable show-password placeholder="请输入密码" :prefix-icon="useRenderIcon(Lock)" @click="onPasswordToggle" />
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
-
|
|
|
|
|
<el-form-item>
|
|
<el-form-item>
|
|
|
- <el-button
|
|
|
|
|
- class="w-full"
|
|
|
|
|
- size="default"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- :loading="loading"
|
|
|
|
|
- :disabled="disabled"
|
|
|
|
|
- @click="onLogin(ruleFormRef)"
|
|
|
|
|
- >
|
|
|
|
|
- 登 录
|
|
|
|
|
- </el-button>
|
|
|
|
|
|
|
+ <ReInteractiveHoverButton :loading="loading" :disabled="disabled" @click="onLogin(ruleFormRef)">登录</ReInteractiveHoverButton>
|
|
|
</el-form-item>
|
|
</el-form-item>
|
|
|
</el-form>
|
|
</el-form>
|
|
|
</div>
|
|
</div>
|
|
@@ -169,6 +153,6 @@ useEventListener(document, "keydown", ({ code }) => {
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
-<style scoped>
|
|
|
|
|
|
|
+<style>
|
|
|
@import url("@/style/login.css");
|
|
@import url("@/style/login.css");
|
|
|
</style>
|
|
</style>
|