Vue3入门 - 登录功能开发(Vue3+ts+Pinia+Element Plus)

        Vue3中实现登录功能,通常涉及到创建一个表单,用户输入用户名和密码,然后将信息发送到后端进行验证,得到响应结果后作出相应操作。

一、创建项目

        这里他用pnpm进行项目的创建的,所以需要事先全局安装pnpm(在安装pnpm前,先确认本地电脑上是否已安装nodejs),命令如下:

npm install -g pnpm

1.1 创建vue

        使用pnpm创建vue项目命令如下:

pnpm create vue

        执行后作出相应选择,选择你项目中需要的选项即可。示例如下:

D:\workspace\vscode>pnpm create vue

Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vue-project
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是
√ 是否引入 Prettier 用于代码格式化? ... 否 / 是
√ 是否引入 Vue DevTools 7 扩展用于调试? (试验阶段) ... 否 / 是

正在初始化项目 D:\workspace\vscode\vue-project...

项目初始化完成,可执行以下命令:

  cd vue-project
  pnpm install
  pnpm format
  pnpm dev

1.2 初始化项目

        当项目创建完成后,根据提示命令对项目进行初始化,先输入命令进入项目目录:

cd vue-project

        然后执行install命令,初始化项目:

D:\workspace\vscode\vue-project>pnpm install

   ╭──────────────────────────────────────────────────────────────────╮
   │                                                                  │
   │                Update available! 9.7.0 → 9.10.0.                 │
   │   Changelog: https://github.com/pnpm/pnpm/releases/tag/v9.10.0   │
   │                Run "pnpm add -g pnpm" to update.                 │
   │                                                                  │
   │         Follow @pnpmjs for updates: https://x.com/pnpmjs         │
   │                                                                  │
   ╰──────────────────────────────────────────────────────────────────╯

 WARN  5 deprecated subdependencies found: @humanwhocodes/config-array@0.11.14, 
@humanwhocodes/object-schema@2.0.3, glob@7.2.3, inflight@1.0.6, rimraf@3.0.2
Packages: +425
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++
Progress: resolved 463, reused 272, downloaded 153, added 425, done
node_modules/.pnpm/esbuild@0.21.5/node_modules/esbuild: Running postinstall script...
node_modules/.pnpm/esbuild@0.21.5/node_modules/esbuild: Running postinstall script, 
done in 341msstinstall script, done
in 209ms

dependencies:
+ pinia 2.2.2
+ vue 3.5.3
+ vue-router 4.4.3

devDependencies:
+ @rushstack/eslint-patch 1.10.4
+ @tsconfig/node20 20.1.4
+ @types/jsdom 21.1.7
+ @types/node 20.16.5 (22.5.4 is available)
+ @vitejs/plugin-vue 5.1.3
+ @vitejs/plugin-vue-jsx 4.0.1
+ @vue/eslint-config-prettier 9.0.0
+ @vue/eslint-config-typescript 13.0.0
+ @vue/test-utils 2.4.6
+ @vue/tsconfig 0.5.1
+ eslint 8.57.0 (9.10.0 is available)
+ eslint-plugin-vue 9.28.0
+ jsdom 24.1.3 (25.0.0 is available)
+ npm-run-all2 6.2.2
+ prettier 3.3.3
+ typescript 5.4.5 (5.6.2 is available)
+ vite 5.4.3
+ vite-plugin-vue-devtools 7.4.4
+ vitest 1.6.0 (2.0.5 is available)
+ vue-tsc 2.1.6

Done in 9.8s

        初始化项目后,则可以运行项目了,当执行pnpm dev后您会发现,瞬间项目就运行起来了,比vue2+webpack时快了很多。

D:\workspace\vscode\vue-project>pnpm dev

> vue-project@0.0.0 dev D:\workspace\vscode\vue-project
> vite


  VITE v5.4.3  ready in 1285 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  Vue DevTools: Open http://localhost:5173/__devtools__/ as a separate window
  ➜  Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools

  ➜  press h + enter to show help

二、Pinia

        在创建项目时,我们就已经选择安装了Pinia,这里就不重复安装了。

        pinia是Vue的存储库,它允许您跨组件/页面共享状态。它Vuex的替代品,其功能和性能更优于vuex,在后期使用中会逐渐被大家发现。

        这里我们先使用pinia全局存储用户信息、接口访问令牌、菜单列表等数据,当然这些只是作为一个项目的基础部分,其他共享信息可根据你们的项目需求进行添加。

        关于这部分,就不细讲了,之前一篇已详细讲解过,地址:Vue3入门 - Pinia使用(替代Vuex)-CSDN博客

        Pinia官方文档:Pinia 中文文档

三、路由创建

        在创建Vue项目里,如果你已在选项中选择安装了vue-router,则此时您的目录中肯定已有router/index.ts文件,代码如下:

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (About.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import('../views/AboutView.vue')
    }
  ]
})

export default router

        此时,我们将目录中多余的文件删除,创建首页、登录页面、404页面、layout文件等,如下图:

  • components/LayoutIndex.vue:主界面公共框架页面
  • views/NotFound.vue:404页面
  • views/HomeIndex.vue:首页
  • views/LoginIndex.vue:登录界面

3.1 App.vue

        首页清理App.vue中代码,将多余代码全部清除,保留RouterView视图组件,代码如下:

<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>

<template>
  <RouterView />
</template>

<style>
*{ margin: 0; padding: 0; }

#app{
  width: 100%;
  min-height: 100%;
  font-size: 12px;
  font-family: 'Segoe UI', Tahoma, Verdana, sans-serif;
}
</style>

3.2 LayoutIndex.vue

        在components目录中创建LayoutIndex.vue组件,用于编写主界面框,及主界面头部、左侧导航后期都将在此组件中引用并展示。由于该篇只讲登录界面,还未涉及主界面,所以先添加视图组件留位占坑,代码如下:

<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<template>
  <RouterView></RouterView>
</template>

3.3 NotFound.vue

        当路由错误,找不到对应页面时,通过路由匹配跳转到404页面,显示页面不存在等错误提示。由于是演示界面,样式就随便写了,大家可自行优化。代码如下:

<template>
<div class="error-wrap">
    <h3>错误:页面不存在~</h3>
    <p class="intro">404</p>
</div>
</template>
<style scoped>
.error-wrap{ 
    width: 100%; 
    min-height: 100vh; 
    text-align: center; 
    display: flex; 
    justify-content: center; 
    align-items: center; 
    flex-direction: column; 
}
.error-wrap h3{ font-size: 30px; margin-bottom: 20px; }
.error-wrap p.intro{ font-size: 62px; font-weight: bold; }
</style>

3.4 HomeIndex.vue

        首页也先留坑占位,等后续讲了主界面时,再详细说明。代码如下:

<template>
  <div>
    首页
  </div>
</template>

3.5 LoginIndex.vue

        登录界面也先留坑,待下面讲到“登录界面开发”时再细讲,代码如下:

<template>
<div>
    <h3>登录页面</h3>
</div>
</template>

3.6 路由配置

        当基本页面创建完成后,我们就可以进入router/index.ts文件中,对路由进行配置了。代码如下:

import { createRouter, createWebHistory } from 'vue-router'
import LayoutIndex from '@/components/LayoutIndex.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'index',
      component: LayoutIndex,
      redirect: "/home",
      children: [
        { 
          path: '/home', 
          name: 'home', 
          meta: {
            requiresAuth: true,        // 标识该路由需要校验用户是否登录
            title: "首页"
          },
          component: () => import('@/views/HomeIndex.vue') 
        }
      ]
    },
    {
      path: '/login',
      name: 'login',
      meta: {
        title: "登录界面"
      },
      component: () => import('@/views/LoginIndex.vue')
    }, 
    {
      path: '/:catchAll(.*)',
      name: "NotFound",
      component: () => import('@/views/NotFound.vue')
    }
  ]
})

export default router

        当上述代码配置完成后,则可以通过浏览器地址栏,进行路由跳转了。

注意:Vue3中是使用 :pathMach(.*) 或者 :catchAll(.*) 匹配所有路径,Vue2中是使用"*"号。如果在Vue3中使用星号,则会报错:Error: Catch all routes ("*") must now be defined using a param with a custom regexp.

3.7 导航守卫

        正如其名,vue-router提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的、单个路由独享的、或者组件级的。

        这里使用是全局前置守卫,可以使用router.beforeEach注册,示例如下:

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // 返回 false 以取消导航
  return false
})

        守卫方法参数:

序号字段说明
1to即将要进入的目标
2from当前导航正要离开的路由
3next可选参数。建议使用Vue3新写法,通过返回值进行跳转。

        返回的值如下:

序号返回值说明
1false取消当前的导航。如果浏览器URL改变了(可能是用户手动或者浏览器后退按钮),那么URL地址会重置到from路由对应的址址。
2路由地址通过一个路由地址重写向到一个不同的地址,如果调用router.push(),且可以传入诸如replace: true或name: 'home' 之类的选项。它会中断当前的导航,同时用相同的from创建一个新导航。

3.7.1 新增是否登录校验

        这里我们在stores/modules/login.ts文件中,新增校验用户是否登录的计算属性,代码如下:

import { defineStore } from 'pinia'
import type { UserInfoType } from '@/types/global'
import { useUserInfoStore, useAccessTokenStore } from '../global'
import { computed } from 'vue'

// 登录统一处理用户信息和访问令牌
export const useLoginStore = defineStore('login', () => {
  const { userInfoChange, user_info } = useUserInfoStore()
  const { accessTokenChange, access_token } = useAccessTokenStore()
  // 定义getters,判断用户是否登录(当用户id不等于undefined存在时,并且访问令牌不为空时,表示用户已登录)
  const is_login = computed(() => 'undefined' !== typeof user_info.value['id'] && access_token.value != '')
  // 定义actions修改用户信息和访问令牌令牌
  const loginChange = (_userInfo: UserInfoType, _token: string) => {
    userInfoChange(_userInfo)
    accessTokenChange(_token)
  }
  return { is_login, loginChange }
})

3.7.2 守卫校验登录状态

        在router/index.ts文件中,引入useLoginStore,并在导航卫士执行时,判断该用户是否登录并作出相应操作,代码如下:

import { createRouter, createWebHistory } from 'vue-router'
import LayoutIndex from '@/components/LayoutIndex.vue'
import { useLoginStore } from '@/stores'

const router = createRouter({
  // 略...
})

// 登录路由地址
const loginPath = '/login'
// 路由卫士
router.beforeEach((to) => {
  const stores = useLoginStore() // 获取用户状态库
  if(to.meta){
    // 修改界面标题
    if('undefined' !== typeof to.meta.title) document.title = to.meta.title
    // 该路由需要校验登录状态,并且用户未登录,则跳转到登录界面
    if (to.meta.requiresAuth && !stores.is_login && to.path != loginPath) return loginPath
  }
})

export default router

        此时上述代码在 document.title = to.meta.title处会报错:Type "{} | null" cannot be assigned to type "string".Cannot assign type "null" to type "string"。意思这里可能会给document.title赋值一个{}或null值,将其作以下修改:

document.title = to.meta.title || ''

        此时则会报错:Cannot assign type '{}' to type 'string'。意思不能将类型"{}"分配给类型“string”。官方给出的解决方式是扩展RouteMeta接口来输入meta字段。代码如下:

// 在 src/types目录下, 新建 vue-router.d.ts类型声明文件
import "vue-router"
declare module 'vue-router' {
  interface RouteMeta {
    title?: string        // 可选
  }
}

         上述问题都解决后,输入不存在路由则会跳转到404页面;当输入“/home”主页路径则会直接跳转到登录界面,这是因为首页路由在meta中添加requiresAuth为true,当进入该页面前,导航守卫中判断用户是否登录,未登录则会跳转到登录界面。

四、Element Plus

        在讲axios请求前,我们先讲下Element Plus的安装;写过vue2的朋友肯定知道,在Vue2中一般是使用Element-UI组件库,在Vue3中开始使用Element Plus。

4.1 安装

pnpm add element-plus

4.2 引用

        打开main.ts文件,在内部添加Element Plus的样式引入,以及组件安装。代码如下:

import { createApp } from 'vue'
import pinia from '@/stores'
import App from './App.vue'
import router from './router'
// 引入Element Plus样式
import 'element-plus/dist/index.css'
// 引入Element Plus
import ElementPlus from 'element-plus'

const app = createApp(App)
app.use(pinia)
app.use(router)
app.use(ElementPlus)

app.mount('#app')

4.3 自动导入

        按需导入需要使用额外的插件来导入要使用的组件,首先需要安装unplugin-vue-components 和 unplugin-auto-import这两款插件,代码如下:

pnpm add -D unplugin-vue-components unplugin-auto-import

        本次演示项目使用的是vite,配置如下:

// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

        如果使用webpack可去官方查看,地址:https://element-plus.org/zh-CN/guide/quickstart.html

        当上述配置完成后,我们在components目录中按驼峰模式命名的组件,则会按需直接在页面使用(注意:页面使用时,将驼峰模式修改为 - 分隔使用),代码如下:

<header-compt></header-compt>

五、axios封装

        对于一个项目,与后台交互,进行Http请求是基本需求。在Vue3中使用axios普遍,所以要进行后续开发,需要先安装Axios。

5.1 安装axios

pnpm add axios

5.2 封装axios

        在项目中,可以在组件中直接引用axios发送请求,但通常将axios作为底层库,再将其封装一个axios实例对象导出。代码如下:

import axios from 'axios'
import { ElMessage } from 'element-plus'
import { useAccessTokenStore } from '@/stores'

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'

const instance = axios.create({
  baseURL: 'API请求地址',
  timeout: 1000 * 60
})

// 添加请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 获取访问令牌
    const { access_token } = useAccessTokenStore()
    // 如果令牌存在,则追加到header中
    if (access_token) config.headers['accesstoken'] = access_token
    return config
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error)
  }
)

// 添加响应拦截器
instance.interceptors.response.use(
  (response) => {
    if (response.data.code == 200) {
      return response.data
    }
    ElMessage.error(response.data.message) // 错误提示
    return Promise.reject(response.data)
  },
  (error) => {
    // 对响应错误做点什么
    return Promise.reject(error)
  }
)
export default instance

5.2 创建API

        当axios封装好后,在src目录下创建api文件夹,用于定义项目中所有api请求函数。示例如下:

import Request from '@/utils/request'

/**
 * 登录接口
 * @param {*} params 
 * @returns 
 */
export const loginService = (params) => {
  return Request.post("/login", params)
}

六、SASS安装

        接下来就要编写登录界面了,这里我们将要使用到CSS的预编译工具,例如有Styus、Less、Sass等,这里将使用Sass进行样式编写。安装命令如下:

pnpm add sass

        安装成功后,我们将3.3 NotFound.vue中的样式重新书写,在标签style的属性lang中赋值scss即可。代码如下:

<template>
<div class="error-wrap">
    <h3>错误:页面不存在~</h3>
    <p class="intro">404</p>
</div>
</template>
<style lang="scss" scoped>
.error-wrap{ 
    width: 100%; 
    min-height: 100vh; 
    text-align: center; 
    display: flex; 
    justify-content: center; 
    align-items: center; 
    flex-direction: column; 
    h3{ 
        font-size: 30px; 
        margin-bottom: 20px; 
    }
    p.intro{ 
        font-size: 62px; 
        font-weight: bold; 
    }
}
</style>

七、登录界面开发

7.1 页面编写

        这里css单独定义scss文件,通过@import引入到登录界面,代码如下:

.login-wrap{ width: 100%; 
    .right-large-thumb, .login-box{ 
        height: 100vh; display: flex; justify-content: center; align-items: center; 
    }
    .right-large-thumb{  background-color: aqua;
        h3{ font-size: 36px; }
    }

    .login-box{ padding: 30px; box-sizing: border-box;
        .ruleForm-box{ width: 50%; }
    }
}

vue页面,代码如下:

<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { ComponentSize, FormInstance, FormRules } from 'element-plus'
import type { RuleForm } from '@/interface/user'
import { loginService } from '@/api/index'

defineOptions({
  name: 'LoginIndex'
})

const formSize = ref<ComponentSize>('default')
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive<RuleForm>({
  name: '',
  password: ''
})

const rules = reactive<FormRules<RuleForm>>({
  name: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
})

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      // do something...
    } else {
      console.log('error submit!', fields)
    }
  })
}

const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
}
</script>
<template>
  <div class="login-wrap">
    <el-row>
      <el-col :span="12">
        <div class="right-large-thumb">
          <h3>DEMO系统</h3>
        </div>
      </el-col>
      <el-col :span="12">
        <div class="login-box">
          <el-form
            ref="ruleFormRef"
            :model="ruleForm"
            :rules="rules"
            label-width="auto"
            class="ruleForm-box"
            :size="formSize"
            status-icon
          >
            <el-form-item label="用户" prop="name">
              <el-input v-model="ruleForm.name" />
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input type="password" v-model="ruleForm.password" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="submitForm(ruleFormRef)"> 登录 </el-button>
              <el-button @click="resetForm(ruleFormRef)">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<style lang="scss" scoped>
@import './index.scss';
</style>

        页面样式效果如下:

7.2 API请求

        上述代码中,完成登录界面框架搭建、输入框校验等操作,当用户输入符合要求的登录信息后,将输入信息提交到后台校验,待接口响应最终结果。如果登录失败则提示用户, 要是登录成功将接口返回的用户信息和访问令牌存储到useStore中,完成登录操作步骤。

        上篇中已详细讲解过用户信息和访问令牌的useStore定义和数据持久化(地址:Vue3入门 - Pinia使用(替代Vuex)-CSDN博客),并且定义的userLoginStore统一存储用户信息和访问令牌,这里不再阐述。

代码如下:

import { reactive, ref } from 'vue'
import type { ComponentSize, FormInstance, FormRules } from 'element-plus'
import type { RuleForm } from '@/interface/user'
import { loginService } from '@/api/index'
import { useLoginStore } from '@/stores/index'
import { useRouter } from 'vue-router'

// 略...

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('submit!')
      loginService(ruleForm)
        .then((res) => {
          console.log('success', res)
          if(res.code == 200){
            useLoginStore(res.data.userInfo, res.data.accesstoken)
            setTimeout(() => {
              const router = useRouter()
              // 返回首页
              router.push('/')
            }, 1200)
          }
        })
        .catch((e) => {
          console.error('e', e)
        })
    } else {
      console.log('error submit!', fields)
    }
  })
}

// 略...

        当用户信息缓存后,则在3.7.1中讲到的is_login则为true,此时经过导航守卫则校验通过,会直接跳转到主界面。

        登录部分就讲到这,希望对大家有所帮助~

要搭建一个vue3 ts pinia element-plus后台管理系统框架,可以按照以下步骤进行: 1. 安装Node.js和Vue CLI Vue CLI是用于创建和管理Vue项目的官方工具,需要先安装Node.js和Vue CLI。 2. 创建基于Vue3和TypeScript的项目 使用Vue CLI创建一个基于Vue3和TypeScript的项目,输入以下命令: ``` vue create my-project --preset=@vue/cli-plugin-typescript ``` 3. 安装Pinia Pinia是一个Vue 3状态管理库,可以用于管理应用程序的状态。在项目中安装Pinia,输入以下命令: ``` npm install pinia ``` 4. 安装Element Plus Element Plus是一个基于Vue.js 3的UI库,可用于构建后台管理系统。在项目中安装Element Plus,输入以下命令: ``` npm install element-plus ``` 5. 创建页面和组件 根据项目需求,创建页面和组件。 6. 配置Pinia 在main.ts中引入Pinia,并在创建Vue实例时使用它。示例代码如下: ```typescript import { createApp } from 'vue' import App from './App.vue' import { createPinia } from 'pinia' const app = createApp(App) app.use(createPinia()) app.mount('#app') ``` 7. 配置Element Plus 在main.ts中引入Element Plus,并在创建Vue实例时使用它。示例代码如下: ```typescript import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' const app = createApp(App) app.use(ElementPlus) app.mount('#app') ``` 8. 编写页面和组件 使用Vue3和TypeScript编写页面和组件。 9. 运行项目 运行项目,输入以下命令: ``` npm run serve ``` 以上就是搭建一个vue3 ts pinia element-plus后台管理系统框架的步骤。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值