董国庆
2026-04-02 3bc0cf3e9afca263539652e1bc54b48830dfe942
feat(登录): 集成后端验证码接口并调整登录逻辑

- 移除前端生成的验证码,改为调用后端接口获取验证码图片和UUID
- 修改登录接口,增加验证码和UUID参数
- 调整登录逻辑,登录失败时刷新验证码
- 修改登录函数返回布尔值以表示登录成功状态
3个文件已修改
96 ■■■■■ 已修改文件
src/service/api/login.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/auth.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/build-in/login/components/Login/index.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/service/api/login.ts
@@ -4,6 +4,12 @@
interface login {
  username: string
  password: string
  code: string
  uuid: string
}
export function fetchCaptcha() {
  return request.Get<Service.ResponseResult<any>>('/code')
}
export function fetchLoginEncrypted(data: login) {
src/store/auth.ts
@@ -53,17 +53,19 @@
      local.remove('permissions')
    },
    async loginEncrypted(userName: string, password: string) {
    async loginEncrypted(userName: string, password: string, code: string, uuid: string) {
      try {
        const { isSuccess, data } = await fetchLoginEncrypted({ username: userName, password })
        const { isSuccess, data } = await fetchLoginEncrypted({ username: userName, password, code, uuid })
        if (!isSuccess)
          return
          return false
        await this.handleLoginInfo(data)
        return true
      }
      catch (e) {
        console.warn('[Login Error]:', e)
        window.$message?.error('登录失败,请重试')
        return false
      }
    },
src/views/build-in/login/components/Login/index.vue
@@ -1,8 +1,8 @@
<script setup lang="ts">
import type { FormInst } from 'naive-ui'
import { fetchCaptcha } from '@/service/api/login'
import { useAuthStore } from '@/store'
import { local } from '@/utils'
// import { fetchCaptcha } from '@/service/api/login'
const emit = defineEmits(['update:modelValue'])
@@ -31,8 +31,6 @@
      validator: (rule, value) => {
        if (!value)
          return new Error('请输入验证码')
        if (value.toLowerCase() !== captchaCode.value.toLowerCase())
          return new Error('验证码错误')
        return true
      },
    },
@@ -46,87 +44,51 @@
const isRemember = ref(false)
const isLoading = ref(false)
const captchaUrl = ref('')
const captchaCode = ref('')
const captchaUuid = ref('')
const formRef = ref<FormInst | null>(null)
function generateCaptcha() {
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  let code = ''
  for (let i = 0; i < 4; i++) {
    code += chars[Math.floor(Math.random() * chars.length)]
async function generateCaptcha() {
  const res = await fetchCaptcha()
  if (!res.code || res.code !== 200) {
    captchaUrl.value = ''
    captchaUuid.value = ''
    return
  }
  captchaCode.value = code
  // Create canvas for image
  const canvas = document.createElement('canvas')
  canvas.width = 120
  canvas.height = 40
  const ctx = canvas.getContext('2d')
  if (ctx) {
    // Background
    ctx.fillStyle = '#f3f4f6'
    ctx.fillRect(0, 0, 120, 40)
    // Text
    ctx.font = 'bold 24px Arial'
    ctx.fillStyle = '#333'
    ctx.textBaseline = 'middle'
    ctx.textAlign = 'center'
    // Add noise/lines
    for (let i = 0; i < 5; i++) {
      ctx.strokeStyle = `rgba(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255},0.5)`
      ctx.beginPath()
      ctx.moveTo(Math.random() * 120, Math.random() * 40)
      ctx.lineTo(Math.random() * 120, Math.random() * 40)
      ctx.stroke()
    }
    // Draw characters with rotation
    const width = 120 / 4
    for (let i = 0; i < 4; i++) {
      ctx.save()
      ctx.translate(15 + i * width, 20)
      ctx.rotate((Math.random() - 0.5) * 0.4)
      ctx.fillText(code[i], 0, 0)
      ctx.restore()
    }
    captchaUrl.value = canvas.toDataURL()
  }
  const rawImg = String(res.img || '')
  captchaUrl.value = rawImg
    ? (rawImg.startsWith('data:') ? rawImg : `data:image/png;base64,${rawImg}`)
    : ''
  captchaUuid.value = String(res.uuid || '')
}
// async function getCaptcha() {
//   const { data } = await fetchCaptcha()
//   if (data) {
//     captchaUrl.value = data.img
//     captchaId.value = data.id
//   }
// }
function handleLogin() {
  formRef.value?.validate(async (errors) => {
    if (errors) {
      generateCaptcha()
      void generateCaptcha()
      formValue.value.captcha = ''
      return
    }
    isLoading.value = true
    const { account, pwd } = formValue.value
    const { account, pwd, captcha } = formValue.value
    if (isRemember.value)
      local.set('loginAccount', { account, pwd })
    else local.remove('loginAccount')
    await authStore.loginEncrypted(account, pwd)
    const success = await authStore.loginEncrypted(account, pwd, captcha, captchaUuid.value)
    if (!success) {
      formValue.value.captcha = ''
      void generateCaptcha()
    }
    isLoading.value = false
  })
}
onMounted(() => {
  checkUserAccount()
  generateCaptcha()
  void generateCaptcha()
})
function checkUserAccount() {
  const loginAccount = local.get('loginAccount')
@@ -160,7 +122,7 @@
      <n-form-item path="captcha">
        <div class="flex items-center w-full gap-4">
          <n-input v-model:value="formValue.captcha" placeholder="请输入验证码" clearable @keyup.enter="handleLogin" />
          <div class="cursor-pointer h-40px w-120px flex items-center justify-center bg-gray-100 rounded overflow-hidden" @click="generateCaptcha">
          <div class="cursor-pointer h-40px w-120px flex items-center justify-center bg-gray-100 rounded overflow-hidden" @click="void generateCaptcha()">
            <img v-if="captchaUrl" :src="captchaUrl" alt="验证码" class="w-full h-full object-fill">
            <span v-else class="text-xs text-gray-400">加载中...</span>
          </div>