UniApp + Vue3 实现 Token 登录认证系统:完整教程及源码分享

本文详细介绍如何使用 UniApp + Vue3 + SCSS 开发一个完整的用户登录认证系统。包含完整源码,一步步教你实现用户登录、Token 验证、自动登出等功能。

一、项目介绍

1.1 技术栈

1.2 功能特点

二、项目结构

├── pages/                # 页面文件夹

│   ├── login/           # 登录模块

│   │   └── login.vue    # 登录页面

│   └── index/           # 首页模块

│       └── index.vue    # 首页(需登录访问)

├── utils/               # 工具文件夹

│   ├── request.js       # 请求封装

│   └── api.js          # 接口管理

└── static/              # 静态资源

    └── logo.png         # 项目logo

三、详细开发流程

1. 工具层开发 (/utils)

1.1 请求封装 (/utils/request.js)

首先创建请求工具,用于统一处理所有HTTP请求:

const BASE_URL = process.env.NODE_ENV === 'development'

  ? 'http://a1/api/20250201'           // 开发环境

  : 'https://xiyueta.com/api/20250201'  // 生产环境



// 构建url参数

const buildUrl = (url, params) => {

  let queryString = ''

  

  if (params) {

    queryString = Object.keys(params)

      .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)

      .join('&')

  }

  

  return queryString ? `${url}?${queryString}` : url

}



// 请求函数

export const request = (options = {}) => {

  const token = uni.getStorageSync('token')

  

  let url = BASE_URL + options.url

  let requestParams = { ...options.data }

  

  if (options.method === 'GET' && options.data) {

    url = buildUrl(url, options.data)

    delete options.data

  }



  return new Promise((resolve, reject) => {

    // 添加请求日志

    console.log('发送请求:', {

      url: url,

      method: options.method,

      data: options.data

    })

    

    uni.request({

      url: url,

      method: options.method || 'GET',

      data: options.method === 'POST' ? requestParams : undefined,

      header: {

        'content-type': 'application/x-www-form-urlencoded'

      },

      success: (res) => {

        // 添加响应日志

        console.log('请求响应:', res)

        if (res.statusCode === 200) {

          resolve(res.data)

        } else {

          reject(new Error(`请求失败: ${res.statusCode}`))

        }

      },

      fail: (err) => {

        console.error('请求失败:', err)

        reject(new Error(`请求错误: ${err.errMsg}`))

      }

    })

  })

}

为什么这样设计:

1. 分环境配置BASE_URL,方便开发和生产环境切换

2. buildUrl函数处理GET请求参数,确保参数正确编码

3. 统一的请求格式和错误处理,减少重复代码

4. Promise封装支持async/await语法

1.2 接口管理 (/utils/api.js)

创建统一的接口管理文件:

import { request } from './request'



export const api = {

  // 登录接口

  login: (params) => {

    return request({

      url: '/test/post_login.asp',

      method: 'POST',

      data: params

    })

  },

  

  // token验证接口

  checkToken: (params) => {

    return request({

      url: '/test/check_token.asp',

      method: 'GET',

      data: {

        token: params.token,

        json: params.json || 1

      }

    })

  }

}

为什么这样设计:

1. 统一管理所有接口,便于维护

2. 接口复用,避免重复编写请求代码

3. 可以统一处理接口参数

4. 便于后期接口变更

2. 页面开发

2.1 登录页面 (/pages/login/login.vue)

登录页面是用户进入系统的入口:

<template>

  <view class="login-container">

    <view class="login-box">

      <text class="login-title">用户登录</text>

      

      <view class="form-box">

        <view class="input-item">

          <input

            v-model="username"

            type="text"

            placeholder="请输入用户名"

            placeholder-class="placeholder"

          />

        </view>

        

        <view class="input-item">

          <input

            v-model="password"

            type="password"

            placeholder="请输入密码"

            placeholder-class="placeholder"

          />

        </view>

        

        <button 

          class="login-btn"

          :loading="loading"

          @tap="handleLogin"

        >

          登录

        </button>

      </view>

    </view>

  </view>

</template>



<script setup>

import { ref } from 'vue'

import { api } from '@/utils/api.js'



const username = ref('')

const password = ref('')

const loading = ref(false)



const handleLogin = async () => {

  if (!username.value || !password.value) {

    uni.showToast({

      title: '请输入用户名和密码',

      icon: 'none'

    })

    return

  }

  

  try {

    loading.value = true

    const res = await api.login({

      username: username.value,

      password: password.value

    })

    

    if (res.status === 'y') {

      uni.setStorageSync('token', res.data.token)

      

      uni.showToast({

        title: '登录成功',

        icon: 'success'

      })

      

      console.log('准备跳转到首页')

      

      setTimeout(() => {

        console.log('开始执行跳转')

        uni.navigateTo({

          url: '/pages/index/index'

        })

      }, 1500)

      

    } else {

      uni.showToast({

        title: res.info || '登录失败',

        icon: 'none'

      })

    }

    

  } catch (error) {

    uni.showToast({

      title: '登录失败,请稍后重试',

      icon: 'none'

    })

    console.error('登录错误:', error)

  } finally {

    loading.value = false

  }

}

</script>



<style lang="scss" scoped>

.login-container {

  min-height: 100vh;

  background-color: #fff;

  padding: 40rpx;

  display: flex;

  align-items: center;

  justify-content: center;

  

  .login-box {

    width: 100%;

    padding: 0 40rpx;

    

    .login-title {

      display: block;

      font-size: 44rpx;

      font-weight: 600;

      text-align: center;

      color: #333;

      margin-bottom: 60rpx;

    }

    

    .form-box {

      .input-item {

        margin-bottom: 30rpx;

        

        input {

          width: 100%;

          height: 88rpx;

          background: #f5f5f5;

          border-radius: 12rpx;

          padding: 0 30rpx;

          font-size: 28rpx;

          color: #333;

          box-sizing: border-box;

          

          &:focus {

            background: #fff;

            box-shadow: 0 0 0 2rpx rgba(0, 122, 255, 0.2);

          }

        }

      }

      

      .placeholder {

        color: #999;

      }

      

      .login-btn {

        width: 100%;

        height: 88rpx;

        line-height: 88rpx;

        background: #007AFF;

        color: #fff;

        font-size: 32rpx;

        border-radius: 12rpx;

        margin-top: 20rpx;

        

        &:active {

          opacity: 0.8;

        }

      }

    }

  }

}

</style>

为什么这样设计:

1. 使用组合式API (setup),代码更简洁清晰

2. 响应式数据管理用户输入

3. loading状态提升用户体验

4. 统一的错误提示

5. SCSS嵌套语法提高样式可维护性

2.2 首页开发 (/pages/index/index.vue)

首页是登录后的主页面:

<template>

  <view class="container">

    <image class="logo" src="/static/logo.png"></image>

    <view class="text-area">

      <text class="title">{{title}}</text>

    </view>

    

    <!-- 退出登录按钮 -->

    <button class="logout-btn" @tap="handleLogout">退出登录</button>

  </view>

</template>



<script setup>

import { ref, onMounted } from 'vue'

import { api } from '@/utils/api.js'



const title = ref('Hello')



// 跳转到登录页

const redirectToLogin = () => {

  uni.removeStorageSync('token')

  uni.reLaunch({

    url: '/pages/login/login'

  })

}



// 检查token

const checkToken = async () => {

  console.log('开始检查token')

  const token = uni.getStorageSync('token')

  console.log('当前token:', token)

  

  if (!token) {

    uni.showToast({

      title: '请先登录',

      icon: 'none'

    })

    redirectToLogin()

    return

  }



  try {

    const res = await api.checkToken({

      token: token,

      json: 1

    })

    

    if (res.status !== 'y') {

      uni.showToast({

        title: 'token已失效,请重新登录',

        icon: 'none'

      })

      redirectToLogin()

    }

  } catch (error) {

    console.error('token检测错误:', error)

    uni.showToast({

      title: 'token验证失败,请重新登录',

      icon: 'none'

    })

    redirectToLogin()

  }

}



// 退出登录

const handleLogout = () => {

  uni.showModal({

    title: '提示',

    content: '确定要退出登录吗?',

    success: (res) => {

      if (res.confirm) {

        uni.removeStorageSync('token')

        

        uni.showToast({

          title: '已退出登录',

          icon: 'success'

        })

        

        setTimeout(() => {

          uni.reLaunch({

            url: '/pages/login/login'

          })

        }, 1500)

      }

    }

  })

}



// 页面加载时检查token

onMounted(() => {

  console.log('页面加载,执行onMounted')

  checkToken()

})



// 页面显示时也检查token

const onShow = () => {

  console.log('页面显示,执行onShow')

  checkToken()

}



defineExpose({

  onShow

})

</script>



<style lang="scss" scoped>

.container {

  min-height: 100vh;

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;

  padding: 40rpx;

  

  .logo {

    height: 200rpx;

    width: 200rpx;

    margin-bottom: 50rpx;

  }

  

  .text-area {

    display: flex;

    justify-content: center;

    

    .title {

      font-size: 36rpx;

      color: #8f8f94;

    }

  }

  

  .logout-btn {

    width: 90%;

    height: 88rpx;

    line-height: 88rpx;

    background: #ff4d4f;

    color: #fff;

    font-size: 32rpx;

    border-radius: 12rpx;

    margin-top: 60rpx;

    

    &:active {

      opacity: 0.8;

    }

  }

}

</style>

为什么这样设计:

1. onMounted钩子验证token,确保访问权限

2. onShow钩子处理页面重新显示时的token验证

3. 统一的退出登录流程

4. 清晰的错误提示和自动跳转

四、开发要点解析

1. 登录流程

const handleLogin = async () => {

  // 1. 表单验证

  if (!username.value || !password.value) {

    uni.showToast({ title: '请输入用户名和密码' })

    return

  }

  

  // 2. 发起登录请求

  try {

    loading.value = true

    const res = await api.login({

      username: username.value,

      password: password.value

    })

    

    // 3. 处理登录响应

    if (res.status === 'y') {

      // 保存token

      uni.setStorageSync('token', res.data.token)

      // 跳转首页

      uni.navigateTo({ url: '/pages/index/index' })

    }

  } catch (error) {

    // 错误处理

  }

}

2. Token验证

const checkToken = async () => {

  const token = uni.getStorageSync('token')

  

  try {

    const res = await api.checkToken({

      token: token,

      json: 1

    })

    

    if (res.status !== 'y') {

      // token无效,跳转登录页

      redirectToLogin()

    }

  } catch (error) {

    // 错误处理

  }

}

五、性能优化建议

1. 请求优化

2. 页面优化

六、常见问题解答

6.1 登录失败问题

Q:为什么登录请求发送成功但无法登录?

A:常见原因包括:

1. 接口地址配置错误

2. 请求参数格式不正确

3. 后端返回的状态码判断有误

6.2 Token验证问题

Q:为什么Token验证总是失败?

A:可能的原因:

1. Token存储时机不对

2. Token格式不正确

3. 验证接口配置有误

七、进阶优化建议

1. 安全性增强

2. 用户体验优化

AI 提示词参考

如果您希望使用 AI 生成类似的代码或项目,可以使用以下提示词:

请帮我用 uni-app + Vue3 开发一个登录系统,包含以下功能:

1. 用户名、密码输入框

2. 登录接口对接

3. Token存储和验证

4. 自动登出功能

5. 完整的错误处理

总结

本项目展示了如何使用现代化的技术栈实现一个完整的登录系统。通过:

希望这个教程能帮助您快速上手 UniApp + Vue3 的登录系统开发。

---

关于作者

如果本指南对您有所帮助,欢迎交流和探讨技术问题。

📌 QQ: 313801120

📖 更多文章: www.xiyueta.com/

希望能一起成长,共同探索更多开发技巧!

参考资料

#uni-app #Vue3 #前端开发 #登录系统