Bläddra i källkod

605注册忘记密码

tangjunhao 3 månader sedan
förälder
incheckning
17e49f7e06
3 ändrade filer med 491 tillägg och 70 borttagningar
  1. 73 35
      src/views/login/SlideToUnlock.vue
  2. 19 8
      src/views/login/index.vue
  3. 399 27
      src/views/login/register.vue

+ 73 - 35
src/views/login/SlideToUnlock.vue

@@ -23,86 +23,124 @@
  
 <script setup lang="ts">
 import { DArrowRight } from "@element-plus/icons-vue"
- 
+import { onMounted, onUnmounted } from "vue"
+
 const emit = defineEmits(["verifySuccess"])
- 
+
 // DOM 引用
-const containerRef = ref<HTMLElement | null>(null) // 容器元素引用
-const sliderRef = ref<HTMLElement | null>(null) // 滑块元素引用
- 
+const containerRef = ref<HTMLElement | null>(null)
+const sliderRef = ref<HTMLElement | null>(null)
+
 // 状态变量
-const sliderLeft = ref("0px") // 滑块左侧位置
-const sliderMaskWidth = ref("0px") // 遮罩层宽度
-const verified = ref(false) // 验证状态
- 
+const sliderLeft = ref("0px")
+const sliderMaskWidth = ref("0px")
+const verified = ref(false)
+
 // 拖动相关变量
-let startX = 0 // 开始拖动时的 X 坐标
-let sliderLeft_temp = 0 // 临时存储滑块位置
- 
+let startX = 0
+let sliderLeft_temp = 0
+
+// 监听窗口大小变化
+let resizeObserver: ResizeObserver | null = null
+
+/**
+ * 更新滑块位置(用于窗口大小变化时重新计算)
+ */
+const updateSliderPosition = () => {
+  if (!containerRef.value || !sliderRef.value) return
+
+  const containerWidth = containerRef.value.offsetWidth
+  const sliderWidth = sliderRef.value.offsetWidth
+  const maxLeft = containerWidth - sliderWidth
+
+  if (verified.value) {
+    // 如果已验证,滑块固定在最右侧
+    sliderLeft.value = `${maxLeft}px`
+    sliderMaskWidth.value = `${maxLeft + sliderWidth / 2}px`
+  } else {
+    // 否则重置
+    sliderLeft.value = "0px"
+    sliderMaskWidth.value = "0px"
+  }
+}
+
+// 初始化 ResizeObserver
+onMounted(() => {
+  resizeObserver = new ResizeObserver(updateSliderPosition)
+  if (containerRef.value) {
+    resizeObserver.observe(containerRef.value)
+  }
+})
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (resizeObserver) {
+    resizeObserver.disconnect()
+  }
+  document.removeEventListener("mousemove", handleDragMove)
+  document.removeEventListener("mouseup", handleDragEnd)
+})
+
 /**
  * 开始拖动处理
- * @param e 鼠标事件对象
  */
 const handleDragStart = (e: MouseEvent) => {
-  if (verified.value) return // 如果已验证通过,不再处理
+  if (verified.value) return
   startX = e.clientX
   sliderLeft_temp = parseInt(sliderLeft.value)
-  // 添加鼠标移动和松开事件监听
   document.addEventListener("mousemove", handleDragMove)
   document.addEventListener("mouseup", handleDragEnd)
 }
- 
+
 /**
  * 拖动过程处理
- * @param e 鼠标事件对象
  */
 const handleDragMove = (e: MouseEvent) => {
   if (verified.value) return
   const container = containerRef.value
   if (!container) return
- 
-  // 计算拖动距离
+
   const diff = e.clientX - startX
   const containerWidth = container.offsetWidth
   const sliderWidth = sliderRef.value?.offsetWidth || 0
   const maxLeft = containerWidth - sliderWidth
- 
-  // 计算新位置并限制范围
+
   let newLeft = sliderLeft_temp + diff
   if (newLeft < 0) newLeft = 0
   if (newLeft > maxLeft) newLeft = maxLeft
- 
-  // 更新滑块和遮罩层位置
+
   sliderLeft.value = newLeft + "px"
   sliderMaskWidth.value = newLeft + sliderWidth / 2 + "px"
- 
-  // 检查是否验证成功
+
   if (newLeft === maxLeft) {
     verified.value = true
-    emit("verifySuccess", true) // 触发验证成功
+    emit("verifySuccess", true)
     handleDragEnd()
   }
 }
- 
+
+/**
+ * 重置滑块
+ */
+const reset = () => {
+  verified.value = false
+  sliderLeft.value = "0px"
+  sliderMaskWidth.value = "0px"
+}
+
 /**
  * 结束拖动处理
  */
 const handleDragEnd = () => {
   if (!verified.value) {
-    // 如果未验证成功,重置位置
     sliderLeft.value = "0px"
     sliderMaskWidth.value = "0px"
   }
-  // 移除事件监听
   document.removeEventListener("mousemove", handleDragMove)
   document.removeEventListener("mouseup", handleDragEnd)
 }
- 
-// 组件卸载时清理事件监听
-onUnmounted(() => {
-  document.removeEventListener("mousemove", handleDragMove)
-  document.removeEventListener("mouseup", handleDragEnd)
-})
+
+defineExpose({ reset })
 </script>
  
 <style scoped>

+ 19 - 8
src/views/login/index.vue

@@ -24,8 +24,7 @@
             <el-form-item prop="username" label="邮箱:" style="margin-bottom: 20px;">
               <el-input
                 v-model="loginForm.username"
-                placeholder="请输入用户名"
-                prefix-icon="User"
+                placeholder="请输入邮箱"
                 clearable
               />
             </el-form-item>
@@ -34,7 +33,6 @@
               <el-input
                 v-model="loginForm.password"
                 placeholder="请输入密码"
-                prefix-icon="Lock"
                 show-password
                 clearable
               />
@@ -43,12 +41,12 @@
             <el-form-item prop="remember" style="margin-bottom: 20px;">
               <div style="width: 100%; display: flex; justify-content: space-between;">
                 <el-checkbox v-model="loginForm.remember">记住密码</el-checkbox>
-                <el-link type="info">忘记密码</el-link>
+                <el-link type="info" @click="goToforgetPwd">忘记密码</el-link>
               </div>
             </el-form-item>
 
             <el-form-item>
-              <SlideToUnlock @verifySuccess="verifySuccess" />
+              <SlideToUnlock ref="sliderRef" @verifySuccess="verifySuccess" />
             </el-form-item>
 
             <el-form-item>
@@ -89,6 +87,8 @@ import loginLogo from '@/assets/img/login-logo.png'
 // 表单引用
 const loginFormRef = ref(null)
 
+const sliderRef = ref(null)
+
 // 登录表单数据
 const loginForm = reactive({
   username: secureStorage.get('savedUsername') || '',
@@ -139,7 +139,7 @@ const handleLogin = () => {
       password: enPassword(loginForm.password),
       type: "",
     }
-    console.log(params)
+    // console.log(params)
     request(params)
     .then((res) => {
       setToken(res.clientToken)
@@ -149,6 +149,10 @@ const handleLogin = () => {
     .catch((err) => {
       console.error('错误信息:',err);
       ElMessage.error(err.returnMsg);
+      loginFormRef.value?.resetFields()
+      isverify.value = false
+      sliderRef.value?.reset()
+
       loading.value = false;
     })
     .finally(() => {
@@ -160,8 +164,15 @@ const handleLogin = () => {
 
 // 跳转到注册页面
 const goToRegister = () => {
-  router.push({ path: "/register" })
+  router.push({ path: "/register", query:{ page: 'register' } })
+}
+
+const goToforgetPwd = () => {
+  router.push({ path: "/register", query:{ page: 'forgetPwd' } })
 }
+
+
+
 </script>
 
 <style lang="scss" scoped>
@@ -220,7 +231,7 @@ const goToRegister = () => {
   
   // 设置输入框文字大小
   :deep(.el-input__inner) {
-    font-size: 18px;
+    font-size: 16px;
     height: 42px; /* 可选:调整输入框高度 */
     line-height: 42px; /* 保持垂直居中 */
   }

+ 399 - 27
src/views/login/register.vue

@@ -6,36 +6,54 @@
     <!-- 登录框 -->
     <div class="register-form-container">
       <div class="register-box">
-        <el-card class="register-card" shadow="never">
+        <el-card v-if="pagechange === 'register'" class="register-card" shadow="never">
           <template #header>
             <div class="register-header">
               <img :src="loginLogo" alt="logo" class="login-logo"/>
-              <h2>注册账号</h2>
+              <h2 >注册账号</h2>
             </div>
           </template>
 
+          
           <el-form
             :model="registerForm"
             :rules="registerRules"
             ref="registerFormRef"
+            hide-required-asterisk
             @keyup.enter="handleRegister"
+            label-position = "left"
           >
-            <el-form-item prop="company" label="单位名称:" style="margin-bottom: 20px;">
+            <el-form-item prop="company" label="单位名称:" :label-width = "registerlabel" style="margin-bottom: 20px;">
               <el-input
                 v-model="registerForm.company"
                 placeholder="请输入单位名称"
                 clearable
               />
             </el-form-item>
-            <el-form-item prop="username" label="邮箱:" style="margin-bottom: 20px;">
+            <el-form-item prop="username" label="邮箱:" :label-width = "registerlabel" style="margin-bottom: 20px;">
               <el-input
                 v-model="registerForm.username"
-                placeholder="请输入用户名"
+                placeholder="请输入邮箱"
                 clearable
               />
             </el-form-item>
 
-            <el-form-item prop="password" label="密码:">
+            <div class="authcode" >
+              <el-input
+                v-for="(value, index) in codeList"
+                :key="index"
+                v-model="codeList[index]"
+                maxlength="1"
+                style="width: 50px; text-align: center;"
+                @input="onInput(index)"
+                @keydown.backspace="onBackspace(index, $event)"
+                @paste="onPaste"
+                :ref="el => inputRefs[index] = el"
+              />
+              <el-button @click="getAuthCode">获取验证码</el-button>
+            </div>
+
+            <el-form-item prop="password" label="设置密码:" :label-width = "registerlabel" style="margin-bottom: 20px;">
               <el-input
                 v-model="registerForm.password"
                 placeholder="请输入密码"
@@ -44,13 +62,21 @@
               />
             </el-form-item>
 
+            <el-form-item prop="repassword" label="确认密码:" :label-width = "registerlabel">
+              <el-input
+                v-model="registerForm.repassword"
+                placeholder="请确认密码"
+                show-password
+                clearable
+              />
+            </el-form-item>
+
             <el-form-item>
               <el-button
                 type="primary"
                 class="register-btn"
                 :loading="loading"
                 @click="handleRegister"
-                :disabled="!isverify"
               >
                 立 即 注 册
               </el-button>
@@ -61,8 +87,134 @@
             <div class="login-prompt">已有账号?点击</div>
             <el-link type="primary" @click="goToLogin">登录</el-link>
           </div>
+          
+        </el-card>
+
+        <el-card v-else-if="pagechange === 'registerSuccess'" class="register-card" shadow="never">
+          <template #header>
+            <div class="register-header">
+              <img :src="loginLogo" alt="logo" class="login-logo"/>
+              <h2>注册成功!</h2>
+            </div>
+          </template>
+
+          <div class="register-success">
+            <div style="font-size: 18px;">登录账号: </div>
+            <div style="font-size: 16px;">{{ registerForm.username }}</div>
+            <el-button
+                class="register-btn"
+                @click="goToLogin"
+              >
+                立 即 登 录
+            </el-button>
+
+            <div class="register-footer">
+              <div class="login-prompt">5秒后,自动跳转至登录界面</div>
+            </div>
+          </div>
+        </el-card>
+
+        <el-card v-else-if="pagechange === 'forgetPwd'" class="register-card" shadow="never">
+          <template #header>
+            <div class="register-header">
+              <img :src="loginLogo" alt="logo" class="login-logo"/>
+              <h2 >找回密码</h2>
+            </div>
+          </template>
+
+          
+          <el-form
+            :model="registerForm"
+            :rules="registerRules"
+            ref="registerFormRef"
+            hide-required-asterisk
+            @keyup.enter="handleRegister"
+            label-position = "left"
+          >
+            <el-form-item prop="username" label="邮箱:" :label-width = "registerlabel" style="margin-bottom: 20px;">
+              <el-input
+                v-model="registerForm.username"
+                placeholder="请输入邮箱"
+                clearable
+              />
+            </el-form-item>
+
+            <div class="authcode" >
+              <el-input
+                v-for="(value, index) in codeList"
+                :key="index"
+                v-model="codeList[index]"
+                maxlength="1"
+                style="width: 50px; text-align: center;"
+                @input="onInput(index)"
+                @keydown.backspace="onBackspace(index, $event)"
+                @paste="onPaste"
+                :ref="el => inputRefs[index] = el"
+              />
+              <el-button @click="getAuthCode2">获取验证码</el-button>
+            </div>
+
+            <el-form-item prop="password" label="设置密码:" :label-width = "registerlabel" style="margin-bottom: 20px;">
+              <el-input
+                v-model="registerForm.password"
+                placeholder="请输入密码"
+                show-password
+                clearable
+              />
+            </el-form-item>
+
+            <el-form-item prop="repassword" label="确认密码:" :label-width = "registerlabel">
+              <el-input
+                v-model="registerForm.repassword"
+                placeholder="请确认密码"
+                show-password
+                clearable
+              />
+            </el-form-item>
+
+            <el-form-item>
+              <el-button
+                type="primary"
+                class="register-btn"
+                :loading="loading"
+                @click="handleRePwd"
+              >
+                立 即 找 回
+              </el-button>
+            </el-form-item>
+          </el-form>
+
+          <div class="register-footer">
+            <div class="login-prompt">没有账号?点击</div>
+            <el-link type="primary" @click="goToRegister">注册</el-link>
+          </div>
+          
+        </el-card>
 
+        <el-card v-else-if="pagechange === 'rePwdSuccess'" class="register-card" shadow="never">
+          <template #header>
+            <div class="register-header">
+              <img :src="loginLogo" alt="logo" class="login-logo"/>
+              <h2>找回密码成功!</h2>
+            </div>
+          </template>
+
+          <div class="register-success">
+            <div style="font-size: 18px;">登录账号: </div>
+            <div style="font-size: 16px;">{{ registerForm.username }}</div>
+            <el-button
+                class="register-btn"
+                @click="goToLogin"
+              >
+                立 即 登 录
+            </el-button>
+
+            <div class="register-footer">
+              <div class="login-prompt">5秒后,自动跳转至登录界面</div>
+            </div>
+          </div>
         </el-card>
+        
       </div>
       
     </div>
@@ -72,6 +224,7 @@
 <script setup>
 import { ElMessage } from 'element-plus'
 import router from "@/router"
+import { useRoute } from 'vue-router'
 import { request,enPassword } from "@/utils/request"
 
 import { getToken, setToken, setUserId} from "@/utils/token"
@@ -81,58 +234,170 @@ import loginLogo from '@/assets/img/login-logo.png'
 // 表单引用
 const registerFormRef = ref(null)
 
+const registerlabel = ref(100)
+
+const route = useRoute()
+const pagechange = computed(()=> route.query.page )
+
+const timer = ref(null); // 存储定时器
+
+// 初始化验证码数组
+const codeLength = 6
+const codeList = ref(Array(codeLength).fill('')) // ['','','','','','']
+
+// 获取多个 el-input 的引用
+const inputRefs = ref([])
+
 // 注册表单数据
 const registerForm = reactive({
   company: '',
   username: '',
-  password: ''
+  password: '',
+  repassword: ''
 })
 
 // 加载状态
 const loading = ref(false)
 
-const isverify = ref(false)
-
 // 表单验证规则
 const registerRules = reactive({
   username: [
-    { required: true, message: '请输入用户名', trigger: 'blur' },
-    { min: 3, max: 18, message: '长度在 3 到 18 个字符', trigger: 'blur' }
+    { required: true, message: '', trigger: 'blur' },
+    {
+      pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/,
+      message: '请输入正确的邮箱格式',
+      trigger: 'blur'
+    },
+    { min: 1, max: 30, message: '长度在 1 到 30 个字符', trigger: 'blur' }
   ],
   password: [
-    { required: true, message: '请输入密码', trigger: 'blur' },
+    { required: true, message: '', trigger: 'blur' },
     { min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur' }
+  ],
+  repassword: [
+    { required: true, message: '', trigger: 'blur' },
+    { min: 6, max: 18, message: '长度在 6 到 18 个字符', trigger: 'blur' },
+    {
+      validator: (rule, value, callback) => {
+        if (value !== registerForm.password) {
+          callback(new Error('两次输入的密码不一致'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'blur'
+    }
   ]
 })
 
-// 滑动验证成功
-const verifySuccess = () => {
-  isverify.value = true
+
+
+// 输入时自动跳转到下一个
+const onInput = (index) => {
+  if (codeList.value[index].length === 1 && index < codeLength - 1) {
+    nextTick(() => inputRefs.value[index + 1]?.focus())
+  }
+}
+
+// 处理退格键自动跳转
+const onBackspace = (index, e) => {
+  if (codeList.value[index] === '' && index > 0) {
+    nextTick(() => inputRefs.value[index - 1]?.focus())
+  }
+}
+
+
+const onPaste = (event) => {
+  const paste = event.clipboardData?.getData('text') || ''
+  const chars = paste.replace(/\s+/g, '').slice(0, codeLength).split('')
+
+  chars.forEach((char, idx) => {
+    codeList.value[idx] = char
+  })
+
+  nextTick(() => {
+    const nextFocusIndex = chars.length >= codeLength ? codeLength - 1 : chars.length
+    inputRefs.value[nextFocusIndex]?.focus()
+  })
+
+  event.preventDefault()
+}
+
+
+// 获取验证码(注册)
+const getAuthCode = () => {
+  if(!registerForm.username) {
+    return;
+  }
+  const params = {
+    transCode: "A00001",
+    mailOrPhone: registerForm.username,
+    type: '1',
+    channel: '2'
+  }
+  request(params)
+  .then((res) => {
+
+  })
+  .catch((err) => {
+    ElMessage.error("获取验证码失败");
+  })
+}
+// 获取验证码(找回密码)
+const getAuthCode2 = () => {
+  if(!registerForm.username) {
+    return;
+  }
+  const params = {
+    transCode: "A00001",
+    mailOrPhone: registerForm.username,
+    type: '2',
+    channel: '2'
+  }
+  request(params)
+  .then((res) => {
+
+  })
+  .catch((err) => {
+    ElMessage.error("获取验证码失败");
+  })
 }
 
 // 注册方法
 const handleRegister = () => {
   registerFormRef.value.validate(valid => {
     if (!valid) return
+
+    // 判断验证码是否完整(每一位都不为空)
+    const code = codeList.value.join('')
+    const isCodeValid = codeList.value.every(c => c.trim() !== '')
+    if (!isCodeValid) {
+      ElMessage.warning("请输入完整的验证码")
+      return
+    }
     
     loading.value = true
     
     const params = {
-      transCode: "A00002",
-      loginName: loginForm.username,
-      password: enPassword(loginForm.password),
-      type: "",
+      transCode: "A00003",
+      userName: registerForm.username,
+      password: enPassword(registerForm.password),
+      mailOrPhone: registerForm.username,
+      channel: "2",
+      verificationCode: code,
     }
-    console.log(params)
     request(params)
     .then((res) => {
-      setToken(res.clientToken)
-      setUserId(res.userId)
-      router.push({ path: "/" })
+      pagechange.value = 'registerSuccess';
+      loginTimer();
     })
     .catch((err) => {
       console.error('错误信息:',err);
-      ElMessage.error(err.returnMsg);
+      if(err.returnMsg==="验证码错误!"){
+        ElMessage.error(err.returnMsg);
+      }else {
+        ElMessage.error("注册失败");
+      }
       loading.value = false;
     })
     .finally(() => {
@@ -142,9 +407,86 @@ const handleRegister = () => {
   })
 }
 
+const loginTimer = () => {
+  // 先清除之前的定时器(避免重复)
+  if (timer.value) {
+    clearTimeout(timer.value);
+  }
+  
+  timer.value = setTimeout(() => {
+    try {
+      goToLogin();
+    } catch (error) {
+      console.error('跳转登录失败:', error);
+    }
+  }, 5000);
+};
+
+
 const goToLogin = () => {
+  if (timer.value) {
+    clearTimeout(timer.value); // 跳转前清除定时器
+    timer.value = null;
+  }
   router.push({ path: '/login' })
 }
+
+const goToRegister = () => {
+  registerFormRef.value?.resetFields();
+  codeList.value = ['', '', '', '', '', '']
+
+  router.push({ path: "/register", query:{ page: 'register' } })
+}
+
+// 找回密码
+const handleRePwd = () => {
+  registerFormRef.value.validate(valid => {
+    if (!valid) return
+
+    // 判断验证码是否完整(每一位都不为空)
+    const code = codeList.value.join('')
+    const isCodeValid = codeList.value.every(c => c.trim() !== '')
+    if (!isCodeValid) {
+      ElMessage.warning("请输入完整的验证码")
+      return
+    }
+    
+    loading.value = true
+    
+    const params = {
+      transCode: "B00026",
+      newPassword: enPassword(registerForm.password),
+      mailOrPhone: registerForm.username,
+      verificationCode: code,
+    }
+    request(params)
+    .then((res) => {
+      router.push({ path: "/register", query:{ page: 'rePwdSuccess' } })
+      loginTimer();
+    })
+    .catch((err) => {
+      console.error('错误信息:',err);
+      if(err.returnMsg==="验证码错误!"){
+        ElMessage.error(err.returnMsg);
+      }else {
+        ElMessage.error("找回密码失败");
+      }
+      loading.value = false;
+    })
+    .finally(() => {
+      loading.value = false
+    });
+    
+  })
+}
+
+
+// 组件卸载时清除定时器
+onUnmounted(() => {
+  if (timer.value) {
+    clearTimeout(timer.value);
+  }
+});
 </script>
 
 <style lang="scss" scoped>
@@ -152,7 +494,7 @@ const goToLogin = () => {
   position: relative;
   width: 100%;
   height: 100vh;
-  min-width: 1200px;
+  min-width: 1500px;
   min-height: 600px;
   overflow: auto;
 }
@@ -203,7 +545,7 @@ const goToLogin = () => {
   
   // 设置输入框文字大小
   :deep(.el-input__inner) {
-    font-size: 18px;
+    font-size: 16px;
     height: 42px; /* 可选:调整输入框高度 */
     line-height: 42px; /* 保持垂直居中 */
   }
@@ -245,6 +587,7 @@ const goToLogin = () => {
   height: 42px;
   margin-top: 10px;
   font-size: 16px;
+  color: white;
   background-color: #2267B1;
 }
 
@@ -258,4 +601,33 @@ const goToLogin = () => {
   margin-right: 5px;
   font-size: 13px;
 }
+
+.authcode {
+  display: flex; 
+  gap: 10px;
+  margin-bottom: 20px;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.authcode .el-button {
+  height: 42px;
+  font-size: 16px;
+  color: white;
+  background-color: #2267B1;
+}
+
+.register-success {
+  display: flex;
+  flex-direction: column;
+  gap: 30px;
+
+  font-weight: 400;
+  color: #333333;
+  letter-spacing: 1px;
+  text-align: left;
+  font-style: normal;
+  text-transform: none;
+}
+
 </style>