| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- <template>
- <div class="auth-wrapper">
- <!-- 左侧背景图区域 -->
- <div class="auth-left"></div>
- <!-- 右侧表单容器 -->
- <div class="auth-form-container">
- <!-- 登录表单 -->
- <el-form
- v-if="isLogin"
- ref="formRef"
- :model="form"
- :rules="rules"
- label-width="116px"
- class="login-form"
- >
- <div class="form-header">
- <h2 class="form-title">
- {{
- userType === "user" ? t("login.userTitle") : t("login.adminTitle")
- }}
- </h2>
- <el-button class="user-type-toggle" @click="toggleUserType" link>
- <el-icon><User /></el-icon>
- <span>{{ currentUserTypeName }}</span>
- </el-button>
- </div>
- <div class="input-frame">
- <el-form-item label="账号:" prop="name">
- <el-input v-model="form.name" />
- </el-form-item>
- </div>
- <div class="input-frame">
- <el-form-item label="密码:" prop="password">
- <el-input type="password" v-model="form.password" />
- </el-form-item>
- </div>
- <!-- 错误提示区域 -->
- <div v-if="showError" class="error-message">
- {{ errorMessage }}
- </div>
- <div class="language-toggle-wrapper">
- <span class="text-toggle" @click="toggleLanguage">{{
- currentLanguageName
- }}</span>
- </div>
- <el-form-item>
- <div class="button-group">
- <el-button
- type="primary"
- @click="onSubmit"
- :loading="loading"
- class="login-button"
- >
- {{ t("login.loginButton") }}
- </el-button>
- </div>
- <div class="text-link-wrapper">
- <span class="text-toggle" @click="toggleForm">{{
- t("login.registerLink")
- }}</span>
- </div>
- </el-form-item>
- </el-form>
- <!-- 注册表单 -->
- <el-form
- v-else
- ref="registerFormRef"
- :model="registerForm"
- :rules="registerRules"
- label-width="116px"
- class="login-form"
- >
- <div class="form-header">
- <h2 class="form-title">{{ t("register.title") }}</h2>
- <el-button class="user-type-toggle" @click="toggleUserType" link>
- <el-icon><User /></el-icon>
- <span>{{ currentUserTypeName }}</span>
- </el-button>
- </div>
- <div class="input-frame">
- <el-form-item label="账号:" prop="name">
- <el-input v-model="registerForm.name" />
- </el-form-item>
- </div>
- <div class="input-frame">
- <el-form-item label="密码:" prop="password">
- <el-input type="password" v-model="registerForm.password" />
- </el-form-item>
- </div>
- <div class="input-frame">
- <el-form-item label="确认密码:" prop="confirmPassword">
- <el-input type="password" v-model="registerForm.confirmPassword" />
- </el-form-item>
- </div>
- <!-- 错误提示区域 -->
- <div v-if="showError" class="error-message">
- {{ errorMessage }}
- </div>
- <div class="language-toggle-wrapper">
- <span class="text-toggle" @click="toggleLanguage">{{
- currentLanguageName
- }}</span>
- </div>
- <el-form-item>
- <div class="button-group">
- <el-button
- type="primary"
- @click="onRegister"
- :loading="loading"
- class="login-button"
- >
- {{ t("register.registerButton") }}
- </el-button>
- </div>
- <div class="button-group register-link-container">
- <span @click="toggleForm" class="register-button">{{
- t("register.backToLoginButton")
- }}</span>
- </div>
- </el-form-item>
- </el-form>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { reactive, ref, computed, watch, onMounted } from "vue";
- import { ElForm } from "element-plus";
- import type { FormRules } from "element-plus";
- import { login, register } from "@/API/users";
- import { useTokenStore } from "@/stores/mytoken";
- import { useI18n } from "vue-i18n";
- import { User } from "@element-plus/icons-vue";
- import { useRouter } from "vue-router";
- // ============ 类型定义 ============
- interface LoginForm {
- name: string;
- password: string;
- }
- interface RegisterForm {
- name: string;
- password: string;
- confirmPassword: string;
- }
- // ============ 核心实例 ============
- const store = useTokenStore();
- const { t, locale } = useI18n();
- const router = useRouter();
- // ============ 状态 ============
- const isLogin = ref(true);
- const userType = ref<"user" | "admin">("user");
- const loading = ref(false);
- const showError = ref(false);
- const errorMessage = ref("");
- const form = reactive<LoginForm>({ name: "", password: "" });
- const registerForm = reactive<RegisterForm>({
- name: "",
- password: "",
- confirmPassword: "",
- });
- const formRef = ref<InstanceType<typeof ElForm> | null>(null);
- const registerFormRef = ref<InstanceType<typeof ElForm> | null>(null);
- // ============ 表单切换 ============
- const toggleForm = () => {
- isLogin.value = !isLogin.value;
- showError.value = false; // 切换表单时隐藏错误提示
- };
- const toggleUserType = () => {
- userType.value = userType.value === "user" ? "admin" : "user";
- };
- const toggleLanguage = () => {
- locale.value = locale.value === "zh" ? "en" : "zh";
- localStorage.setItem("lang", locale.value);
- };
- // ============ 计算属性 ============
- const currentLanguageName = computed(() =>
- locale.value === "zh" ? "English" : "中文"
- );
- const currentUserTypeName = computed(() =>
- userType.value === "user" ? t("login.switchToAdmin") : t("login.switchToUser")
- );
- // ============ 显示错误消息 ============
- const showErrorMsg = (message: string) => {
- errorMessage.value = message;
- showError.value = true;
-
- // 3秒后自动隐藏消息
- setTimeout(() => {
- showError.value = false;
- }, 3000);
- };
- // ============ 登录逻辑 ============
- const onSubmit = async () => {
- if (!formRef.value) return;
-
- try {
- // 验证表单
- await formRef.value.validate();
- loading.value = true;
- showError.value = false; // 开始验证时隐藏错误提示
- // 调用登录API
- const response = await login({
- name: form.name,
- password: form.password,
- usertype: userType.value,
- });
- console.log('完整登录响应:', response);
- // 检查响应结构
- if (!response.data?.user) {
- throw new Error('后端返回的用户信息不完整');
- }
- // 提取用户信息
- const userData = response.data.user;
- if (!userData.id || !userData.name) {
- throw new Error('缺少必要的用户字段');
- }
- // 保存用户信息到store
- store.saveToken({
- userId: Number(userData.id),
- name: userData.name,
- loginType: userData.userType || userType.value, // 优先使用后端返回的userType
- });
- // 跳转到目标页面
- await router.push({ name: 'CropCadmiumPrediction' });
- } catch (error: any) {
- console.error('登录失败:', {
- error: error.message,
- stack: error.stack,
- response: error.response?.data
- });
-
- // 显示错误消息
- const errorMsg = error.response?.data.message ||
- error.message ||
- '登录失败,请检查用户名和密码';
- showErrorMsg(errorMsg);
- } finally {
- loading.value = false;
- }
- };
- // ============ 注册逻辑 ============
- const onRegister = async () => {
- if (!registerFormRef.value) return;
- try {
- await registerFormRef.value.validate();
- loading.value = true;
- showError.value = false; // 开始验证时隐藏错误提示
- const res = await register({
- name: registerForm.name,
- password: registerForm.password,
- userType: userType.value,
- });
- if (res.data?.message) {
- // 注册成功后自动登录
- try {
- // 调用登录API
- const loginResponse = await login({
- name: registerForm.name,
- password: registerForm.password,
- usertype: userType.value,
- });
- // 检查登录响应结构
- if (loginResponse.data?.user) {
- const userData = loginResponse.data.user;
-
- // 保存用户信息到store
- store.saveToken({
- userId: Number(userData.id),
- name: userData.name,
- loginType: userData.userType || userType.value,
- });
- // 跳转到目标页面
- await router.push({ name: 'CropCadmiumPrediction' });
- } else {
- showErrorMsg(loginResponse.data?.message || '自动登录失败,请手动登录');
- toggleForm(); // 返回登录页面
- }
- } catch (loginError: any) {
- console.error('自动登录失败:', loginError);
- showErrorMsg(
- loginError?.response?.data?.message || '自动登录失败,请手动登录'
- );
- toggleForm(); // 返回登录页面
- }
- } else {
- showErrorMsg(res.data?.message || t("register.registerFailed"));
- }
- } catch (error: any) {
- console.error("注册异常:", error);
- showErrorMsg(
- error?.response?.data?.message || t("register.registerFailed")
- );
- } finally {
- loading.value = false;
- }
- };
- // ============ 校验规则 ============
- const createRules = (): { rules: FormRules; registerRules: FormRules } => {
- const rules: FormRules = {
- name: [
- {
- required: true,
- message: t("validation.usernameRequired"),
- trigger: "blur",
- },
- ],
- password: [
- {
- required: true,
- message: t("validation.passwordRequired"),
- trigger: "blur",
- },
- {
- min: 3,
- max: 16,
- message: t("validation.passwordLength"),
- trigger: "blur",
- },
- ],
- };
- const registerRules: FormRules = {
- name: rules.name,
- password: rules.password,
- confirmPassword: [
- {
- required: true,
- message: t("validation.confirmPasswordRequired"),
- trigger: "blur",
- },
- {
- validator: (_rule, value: string, callback) => {
- if (value !== registerForm.password)
- callback(new Error(t("validation.passwordMismatch")));
- else callback();
- },
- trigger: "blur",
- },
- ],
- };
- return { rules, registerRules };
- };
- let { rules, registerRules } = createRules();
- watch(locale, () => {
- const newRules = createRules();
- rules = newRules.rules;
- registerRules = newRules.registerRules;
- });
- watch(
- () => registerForm.password,
- () => {
- registerFormRef.value?.validateField("confirmPassword");
- }
- );
- onMounted(() => {
- console.log("登录/注册页初始化", {
- form,
- registerForm,
- userType: userType.value,
- locale: locale.value,
- });
- });
- </script>
- <style scoped>
- .auth-wrapper {
- display: flex;
- height: 100vh;
- background-color: #f6f6f6;
- position: relative;
- }
- .auth-left {
- width: 35%;
- background: url("@/assets/login-bg.png") no-repeat center/cover;
- }
- .auth-form-container {
- width: 55%;
- padding: 0 40px 0 60px;
- display: flex;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- }
- .login-form {
- width: 100%;
- max-width: 700px;
- padding: 40px 30px;
- margin-top: 50px;
- background: rgba(255, 255, 255, 0.9);
- border-radius: 15px;
- }
- .form-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 40px;
- margin-top: 20px;
- }
- .form-title {
- font-size: 32px;
- font-weight: 600;
- color: #333;
- }
- .user-type-toggle {
- font-size: 36px;
- color: #333;
- }
- .user-type-toggle span {
- margin-left: 6px;
- font-size: 36px;
- }
- .language-toggle-wrapper {
- text-align: right;
- margin: 15px 0 20px;
- }
- :deep(.el-form-item__label) {
- float: none;
- display: block;
- text-align: left;
- font-size: 24px;
- padding-bottom: 8px;
- color: #7e7878;
- }
- :deep(.el-input .el-input__inner) {
- height: 50px;
- font-size: 20px;
- border-radius: 0;
- border: 1px solid #dcdfe6;
- background-color: #fff;
- padding: 0 15px;
- }
- .login-button {
- background: linear-gradient(to right, #8df9f0, #26b046);
- width: 100%;
- max-width: 400px;
- height: 56px;
- color: white;
- border: none;
- border-radius: 20px;
- font-size: 24px;
- cursor: pointer;
- margin-top: 10px;
- }
- .login-button:hover {
- opacity: 0.9;
- }
- .register-button {
- display: block;
- text-align: center;
- color: #478bf0;
- font-size: 18px;
- cursor: pointer;
- padding: 10px 0;
- width: 100%;
- }
- .register-button:hover {
- color: #357ae8;
- text-decoration: underline;
- }
- .button-group {
- display: flex;
- justify-content: center;
- width: 100%;
- }
- .text-toggle {
- color: #478bf0;
- font-size: 16px;
- cursor: pointer;
- }
- .text-toggle:hover {
- color: #357ae8;
- text-decoration: underline;
- }
- .text-link-wrapper {
- text-align: center;
- margin-top: 20px;
- }
- .input-frame {
- background-color: #fff;
- width: 100%;
- padding: 15px 10px;
- margin-bottom: 20px;
- max-width: 600px; /* 限制最大宽度 */
- }
- /* 错误提示样式 */
- .error-message {
- color: #f56c6c;
- font-size: 16px;
- text-align: center;
- margin: 15px 0;
- animation: fadeIn 0.3s ease-out forwards;
- }
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- </style>
|