问卷低代码平台-登录与注册

67 阅读2分钟

封装axios

src/apis/request.ts

import axios, { type InternalAxiosRequestConfig, type AxiosResponse } from 'axios'

export interface IResponseData<T = any> {
  code: number
  data: T
  message: string
}
const request = axios.create({
  timeout: 10000,
  baseURL: ''
})

request.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    return config
  },
  (error: any) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  (response: AxiosResponse) => {
    const { code, message = 'Error' } = response.data
    if (code === 200) {
      return response.data
    }
    // 返回包含错误信息的新的 Promise 对象
    return Promise.reject(new Error(message))
  },
  (error: any) => {
    const { code, message = '系统出错' } = error.response.data
    if (code !== 200) {
      ElMessage.error(message)
      return Promise.reject(error.message)
    }
  }
)

// 导出 axios 实例
export default request

安装pinia

pinia-plugin-persistedstate为pinia数据持久化

pnpm i pinia pinia-plugin-persistedstate

src/stores/modules/user.ts
showAvatar控制头像下拉框,userInfoCpd显示用户名称

import { defineStore } from 'pinia'
import { computed } from 'vue'

export const useUserStore = defineStore(
  'user',
  () => {
    const userInfo = ref<string | null>(null)
    const token = ref<string | null>(null)
    const showAvatar = computed(() => {
      return token.value !== null && userInfo.value !== null
    })
    const userInfoCpd = computed(() => {
      if (userInfo.value !== null) {
        return JSON.parse(userInfo.value)
      }
      return ''
    })
    return {
      userInfo,
      token,
      showAvatar,
      userInfoCpd
    }
  },
  // 使用数据持久化
  {
    persist: true
  }
)

注册页面

添加路由

import { useLocalStorage } from '@vueuse/core'
import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  // 定义 404 页面路由
  {
    path: '/:pathMatch(.*)',
    name: 'NotFound',
    redirect: '/404'
  },
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/notFound/index.vue')
  },
  {
    path: '/',
    redirect: '/home',
    component: () => import('@/layouts/MainLayout.vue'),
    children: [
      {
        path: '/home',
        name: 'home',
        component: () => import('@/views/home/index.vue')
      },
      {
        path: '/register',
        name: 'register',
        component: () => import('@/views/register/index.vue')
      }
    ]
  }
]

const router = createRouter({
  routes,
  history: createWebHashHistory()
})

export default router

src/views/register/index.vue

<template>
  <div class="flex justify-center items-center h-full flex-col">
    <div class="flex justify-center items-center">
      <el-icon><UserFilled /></el-icon>
      <el-text size="large">用户登录</el-text>
    </div>
    <el-form :model="registerForm" ref="registerFormRef" label-position="top">
      <el-form-item
        label="用户名"
        prop="username"
        :rules="{ required: true, message: '请输入用户名', trigger: 'blur' }"
      >
        <el-input v-model="registerForm.username" clearable></el-input>
      </el-form-item>
      <el-form-item
        label="密码"
        prop="password"
        :rules="{ required: true, message: '请输入密码', trigger: 'blur' }"
      >
        <el-input
          v-model="registerForm.password"
          clearable
          type="password"
          show-password
        ></el-input>
      </el-form-item>
      <el-form-item
        label="确认密码"
        prop="confirm"
        :rules="{ validator: validateConfirmPass, trigger: 'blur' }"
      >
        <el-input v-model="registerForm.confirm" clearable type="password" show-password></el-input>
      </el-form-item>
      <el-form-item label="昵称" prop="nickname">
        <el-input v-model="registerForm.nickname" clearable></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">注册</el-button>
        <el-link :underline="false" type="primary" class="ml-3" @click="router.push('/login')"
          >已有账户,登录</el-link
        >
      </el-form-item>
    </el-form>
  </div>
</template>

校验密码、用户名规则

<script setup lang="ts">
import { userRegister } from '@/apis/user'
import { type FormInstance } from 'element-plus'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const registerForm = ref({
  username: '',
  password: '',
  confirm: '',
  nickname: ''
})
const registerFormRef = ref<FormInstance>()
const loading = ref(false)

const onSubmit = async () => {
  await registerFormRef.value?.validate((valid) => {
    if (valid) {
      handleuserRegister()
    }
  })
}

const handleuserRegister = async () => {
  loading.value = true
  try {
    await userRegister({ ...registerForm.value })
  } catch (error) {
    console.log(error)
  } finally {
    loading.value = false
  }
}

const validateConfirmPass = (_rule: any, value: any, callback: any) => {
  if (value === '') {
    callback(new Error('请再次输入密码'))
  } else if (value !== registerForm.value.password) {
    callback(new Error('两次输入密码不一致!'))
  } else {
    callback()
  }
}

const validateName = (_rule: any, value: any, callback: any) => {
  const re = /^[a-zA-Z0-9#$%_-]{6,30}$/
  if (!re.test(value)) {
    callback(new Error('用户名只能是字母、数字或者 #、$、%、_、- 这些字符,且长度为6-30'))
  } else {
    callback()
  }
}

const validatePass = (_rule: any, value: any, callback: any) => {
  const re = /^.{6,30}$/
  if (!re.test(value)) {
    callback(new Error('密码只能6-30字符'))
  } else {
    callback()
  }
}
</script>

src/apis/user/types.ts
注册接口类型

import { type IResponseData } from '../request'

export interface IUserRegisterParams {
  username: string
  password: string
  nickname: string
}

export type TUserRegister = (pramas: IUserRegisterParams) => Promise<IResponseData<string>>

src/apis/user/index.ts
注册接口

import request from '../request'
import { type IUserRegisterParams, type TUserRegister } from './types'

// 用户注册
export const userRegister: TUserRegister = (pramas: IUserRegisterParams) => {
  return request.post('api/user/register', pramas)
}

image.png

image.png

image.png

登录页面

image.png 添加路由

import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    redirect: '/home',
    component: () => import('@/layouts/MainLayout.vue'),
    children: [
      {
        path: '/home',
        name: 'home',
        component: () => import('@/views/home/index.vue')
      },
+      {
+        path: '/login',
+        name: 'login',
+        component: () => import('@/views/login/index.vue')
+      },
      {
        path: '/register',
        name: 'register',
        component: () => import('@/views/register/index.vue')
      }
    ]
  }
]

export default router

<template>
  <div class="flex justify-center items-center h-full flex-col">
    <div class="flex justify-center items-center">
      <el-icon><UserFilled /></el-icon>
      <el-text size="large">用户登录</el-text>
    </div>
    <el-form :model="loginForm" ref="loginFormRef" label-position="top">
      <el-form-item
        label="用户名"
        prop="username"
        :rules="{ required: true, message: '请输入用户名', trigger: 'blur' }"
      >
        <el-input v-model="loginForm.username" clearable></el-input>
      </el-form-item>
      <el-form-item
        label="密码"
        prop="password"
        :rules="{ required: true, message: '请输入密码', trigger: 'blur' }"
      >
        <el-input v-model="loginForm.password" clearable></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">登录</el-button>
        <el-link :underline="false" type="primary" class="ml-3" @click="router.push('/register')"
          >注册新用户</el-link
        >
      </el-form-item>
    </el-form>
  </div>
</template>

在登录时把后端返回的token保存到userStore中

import { userLogin } from '@/apis/user'
import { useUserStore } from '@/stores'
import { type FormInstance } from 'element-plus'
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const loginForm = ref({
  username: '',
  password: ''
})
const loginFormRef = ref<FormInstance>()
const loading = ref(false)
const userStore = useUserStore()
const { token, userInfo } = storeToRefs(userStore)
const onSubmit = async () => {
  await loginFormRef.value?.validate((valid) => {
    if (valid) {
      handleUserLogin()
    }
  })
}

const handleUserLogin = async () => {
  try {
    loading.value = true
    const res = await userLogin(loginForm.value)
    if (res.data.token !== '') {
      token.value = res.data.token
      userInfo.value = JSON.stringify(loginForm.value)
      ElMessage.success('登录成功')
      router.push('/')
    }
  } catch (error) {
    console.log(error)
  } finally {
    loading.value = false
  }
}

src/router/index.ts
使用beforeEach来判断本地的token是否存在,访问页面不在白名单中并且token不存在就会跳转到登录页

router.beforeEach((to, from) => {
  const userStore = useUserStore()
  const token = userStore.token
  const whitePath = ['/login', '/register']
  // 判断是否登录
  if (!whitePath.includes(to.path) && token === null) {
    return {
      path: '/login',
      query: {
        redirect: to.path
      }
    }
  }
})

image.png

image.png