vue3-admin商品管理后台项目(登录页开发和功能实现)

news2025/2/24 6:22:41

今天来实现vue3-admin商品管理后台项目的登录页功能
首先在pages文件夹下面添加一个login.vue文件,里面先写入简单的template

<template>
    <div>
        登录
    </div>
</template>

然后在router文件夹下面的Index.js里面编辑,仍然是引入页面配置路由,about页暂时没啥用,只是测试用的,所以就把它删了。

import { createRouter, createWebHashHistory } from 'vue-router'
import Index from '~/pages/index.vue'
import Login from '~/pages/login.vue'
import NotFound from '~/pages/404.vue'

const routes = [{
    // 根路由
    path:"/",
    component:Index
},{
    // 登录路由
    path:"/login",
    component:Login
},{
    //404路由 将匹配所有内容并将其放在 `$route.params.pathMatch` 下
    path: '/:pathMatch(.*)*', 
    name: 'NotFound', 
    component: NotFound
}]

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

运行项目域名输入http://127.0.0.1:5173/#/login,可以看见成功了
在这里插入图片描述

然后开始正式编写登录页代码,因为要做响应式布局,所以优先使用element plus的layout布局。然后我直接给出login.vue做完布局后的代码:结合了windi css进行布局,注释写的很详细

<template>
  <!-- el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕,-->
  <el-row class="min-h-screen bg-indigo-500">
    <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <el-col :span="16" class="flex items-center justify-center">
      <div>
        <!-- class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem -->
        <div class="font-bold text-5xl text-light-50 mb-4">欢迎光临</div>
        <div class="text-gray-200 text-sm">此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col>

    <!-- 右边布局 -->  
    <el-col
      :span="8"
      class="bg-light-50 flex items-center justify-center flex-col"
    >
    <!-- 加粗,字体3xl,颜色深灰色 -->
      <h2 class="font-bold text-3xl text-gray-800">欢迎回来</h2>
      <!-- flex布局,水平垂直居中,上下边距, 浅灰色,水平方向元素的间距 -->
      <div class="flex items-center justify-center my-5 text-gray-300 space-x-2">
        <!-- 高度,宽度,浅灰色 -->
        <span class="h-[1px] w-16 bg-gray-200"></span>
        <span>账号密码登录</span>
        <span class="h-[1px] w-16 bg-gray-200"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <el-form :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item>
          <el-input v-model="form.username" placeholder="请输入用户名"/>
        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item>
          <el-input v-model="form.password" placeholder="请输入密码"/>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive } from 'vue'

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

const onSubmit = () => {
  console.log('submit!')
}
</script>

以上代码之后,login.vue的页面最终效果就是:
在这里插入图片描述
到此登录页的样式布局就写完了。

然后我们进行登录页的响应式处理以及输入框图标的引入:
直接上代码吧 ,注释写的很明白,图标用的是element plus里面的图标引入

<template>
  <!-- el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕,-->
  <el-row class="min-h-screen bg-indigo-500">
    <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕-->
    <el-col :lg="16" :md="12" class="flex items-center justify-center">
      <div>
        <!-- class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem -->
        <div class="font-bold text-5xl text-light-50 mb-4">欢迎光临</div>
        <div class="text-gray-200 text-sm">此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="bg-light-50 flex items-center justify-center flex-col"
    >
    <!-- 加粗,字体3xl,颜色深灰色 -->
      <h2 class="font-bold text-3xl text-gray-800">欢迎回来</h2>
      <!-- flex布局,水平垂直居中,上下边距, 浅灰色,水平方向元素的间距 -->
      <div class="flex items-center justify-center my-5 text-gray-300 space-x-2">
        <!-- 高度,宽度,浅灰色 -->
        <span class="h-[1px] w-16 bg-gray-200"></span>
        <span>账号密码登录</span>
        <span class="h-[1px] w-16 bg-gray-200"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <el-form :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item>
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item>
          <el-input v-model="form.password" placeholder="请输入密码">
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

const onSubmit = () => {
  console.log('submit!')
}
</script>

用windi css 的@apply抽离样式代码
很简单其实,直接看代码就行

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <el-form :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item>
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item>
          <el-input v-model="form.password" placeholder="请输入密码">
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

const onSubmit = () => {
  console.log('submit!')
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

到这里就抽离的差不多了。

然后实现登录表单验证处理:
login.vue的代码:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }
    console.log(验证通过)
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

目前达成的效果:
在这里插入图片描述
在这里插入图片描述

接着我们引入axios请求库和登录接口交互:
项目api接口文档
首先在src目录下创建一个axios.js,然后写上以下内容

import axios from  "axios"

const service = axios.create({
    baseURL:"/api"
})

export default service

编辑vite.config.js,处理一下跨域问题

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import WindiCSS from 'vite-plugin-windicss'

import path from "path"

// https://vitejs.dev/config/
export default defineConfig({
  resolve:{
    alias:{
      "~":path.resolve(__dirname,"src")
    }
  },

  server:{
    proxy:{
      //api代替了这个接口域名
      '/api':{
        target: 'http://ceshi13.dishait.cn',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/,'')
      },
    }
  },

  plugins: [vue(),WindiCSS()]
})

在src目录下创建一个api目录,api目录里面创建manager.js,写入login的接口

import axios from '~/axios'
// 登录
export function Login(username,password) {
    return axios.post("/admin/login",{
        username,
        password
    })
}

然后在login.vue里面处理一下登录提示信息:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { Login } from '~/api/manager'
// 引入通知
import { ElNotification } from 'element-plus'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }
    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    Login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      console.log(res.data.data);
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      ElNotification({
        message: "登录成功",
        type: 'success',
        // 三秒后关闭
        duration:3000
      })

      // 跳转到后台首页
      router.push("/")
    })
    .catch(err=>{
      // 拿到报错具体信息
      ElNotification({
        message: err.response.data.msg || "请求失败",
        type: 'error',
        // 三秒后关闭
        duration:3000
      })
    })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

其中注释写的很详细,就不细说了,看注释就能明白
用户名或者密码错误时,都会弹框提示
在这里插入图片描述
登录成功时会提示成功并且跳转
在这里插入图片描述
快12点了,今天先到这,明天接着肝!

第二天了,继续肝xdm
引入cookie存储用户的token
只有用户有token才能被认定为登陆状态,我们这里使用cookie存储token
我们先来了解一个vueuse工具库,把一些不支持响应式的api转换成响应式,大大提高开发效率
vueuse工具库文档地址
我们使用其中的useCookies
先安装

npm i universal-cookie
npm i @vueuse/integrations

然后在login.vue的登录成功后面加上了cookie

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { Login } from '~/api/manager'
// 引入通知
import { ElNotification } from 'element-plus'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'
// 引入usevue里面的useCookie方法
import { useCookies } from '@vueuse/integrations/useCookies'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }
    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    Login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      console.log(res.data.data);
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      ElNotification({
        message: "登录成功",
        type: 'success',
        // 三秒后关闭
        duration:3000
      })

      // 登录成功存储用户cookie,用cookie变量接收
      const cookie = useCookies()
      cookie.set("admin-token", res.data.data.token)

      // 跳转到后台首页
      router.push("/")
    })
    .catch(err=>{
      // 拿到报错具体信息
      ElNotification({
        message: err.response.data.msg || "请求失败",
        type: 'error',
        // 三秒后关闭
        duration:3000
      })
    })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

打开控制台的application选项,查看,现在是没有cookie的,如果你有,就delete一下,然后
在这里插入图片描述
然后点击登录之后,跳转到首页,然后你再看的时候,就会发现已经有一个cookie了,这时候就代表cookie设置成功了
在这里插入图片描述

我们来继续写请求拦截器和响应拦截器:
在axios官网找到拦截器
axios拦截器
响应拦截器,这里使用响应拦截器处理响应成功和响应失败
改了这里:
在这里插入图片描述
然后这些内容都在axios.js里统一处理了,目前axios.js文件内容:

import axios from  "axios"
// 引入通知组件
import { ElNotification } from 'element-plus'

const service = axios.create({
    baseURL:"/api"
})

// 添加请求拦截器
service.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    // 这里之后login.vue里面就不需要res.data.data了,直接res就行
    return response.data.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 错误处理
    ElNotification({
        message: error.response.data.msg || "请求失败",
        type: 'error',
        // 三秒后关闭
        duration:3000
    })

    return Promise.reject(error);
  });

export default service

然后我们发现功能仍然能正常使用。
响应拦截器应用就结束了。
继续来写请求拦截器:

这里是直接在axios.js里面写了请求拦截器,目的是在header添加token
在这里插入图片描述
此时axios.js的文件:

import axios from  "axios"
// 引入通知组件
import { ElNotification } from 'element-plus'
// 引入usevue里面的useCookie方法
import { useCookies } from '@vueuse/integrations/useCookies'

const service = axios.create({
    baseURL:"/api"
})

// 添加请求拦截器
service.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    // 往header头自动添加token
    const cookie = useCookies()
    const token = cookie.get("admin-token")
    if(token){
        // 往请求头传token
        config.headers["token"] = token
    }

    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    // 这里之后login.vue里面就不需要res.data.data了,直接res就行
    return response.data.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 错误处理
    ElNotification({
        message: error.response.data.msg || "请求失败",
        type: 'error',
        // 三秒后关闭
        duration:3000
    })

    return Promise.reject(error);
  });

export default service

这样他就会自动在header里面添加token

然后我们来写获取登录用户信息的功能:
先在manager.js里面写这个接口方法:

import axios from '~/axios'
// 登录
export function login(username,password) {
    return axios.post("/admin/login",{
        username,
        password
    })
}

// 获取登录用户信息
// 因为前面的请求拦截器已经自动添加了token,所以这里不用刻意传值了
export function getinfo(){
    return axios.post("/admin/getinfo")
}

然后在login.vue里面引入getinfo方法并使用
在这里插入图片描述
在这里插入图片描述
目前的login.vue代码:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { login, getinfo } from '~/api/manager'
// 引入通知组件
import { ElNotification } from 'element-plus'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'
// 引入usevue里面的useCookie方法
import { useCookies } from '@vueuse/integrations/useCookies'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }
    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      // console.log(res.data.data);
      // 因为前面axios.js里面使用了响应拦截器,所以不需要写这么长了,直接res即可
      console.log(res)
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      ElNotification({
        message: "登录成功",
        type: 'success',
        // 三秒后关闭
        duration:3000
      })

      // 登录成功存储用户cookie,用cookie变量接收
      const cookie = useCookies()
      // 原:cookie.set("admin-token", res.data.data.token)
      // 因为前面的响应拦截器设置,这里也直接res.token就行
      cookie.set("admin-token", res.token)

      // 存储完成后获取用户信息
      getinfo().then(res2=>{
        console.log(res2)
      })

      // 跳转到后台首页
      router.push("/")
    })
    // 错误处理移动到了axios.js
    // .catch(err=>{
      // 拿到报错具体信息
      // ElNotification({
      //   message: err.response.data.msg || "请求失败",
      //   type: 'error',
      //   // 三秒后关闭
      //   duration:3000
      // })
    // })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

然后我们再来做一个小功能,用户点击登录之后如果响应时间较长,那么在登录按钮加上一个点击之后loading的功能,防止用户重复点击
login.vue里面:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
目前实现的效果:点击登录之后就有了一个加载按钮,此时用户也不能点击登录按钮了
在这里插入图片描述
至此,这个小功能也做完了。
此时的login.vue文件:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
            <!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit" :loading="loading">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { login, getinfo } from '~/api/manager'
// 引入通知组件
import { ElNotification } from 'element-plus'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'
// 引入usevue里面的useCookie方法
import { useCookies } from '@vueuse/integrations/useCookies'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
const loading = ref(false)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }

    // 在请求之前将loading状态设为true
    loading.value = true

    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      // console.log(res.data.data);
      // 因为前面axios.js里面使用了响应拦截器,所以不需要写这么长了,直接res即可
      console.log(res)
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      ElNotification({
        message: "登录成功",
        type: 'success',
        // 三秒后关闭
        duration:3000
      })

      // 登录成功存储用户cookie,用cookie变量接收
      const cookie = useCookies()
      // 原:cookie.set("admin-token", res.data.data.token)
      // 因为前面的响应拦截器设置,这里也直接res.token就行
      cookie.set("admin-token", res.token)

      // 存储完成后获取用户信息
      getinfo().then(res2=>{
        console.log(res2)
      })

      // 跳转到后台首页
      router.push("/")
    }).finally(()=>{
      // 登录成功之后loading设置为false
      loading.value = false
    })
    // 错误处理移动到了axios.js
    // .catch(err=>{
      // 拿到报错具体信息
      // ElNotification({
      //   message: err.response.data.msg || "请求失败",
      //   type: 'error',
      //   // 三秒后关闭
      //   duration:3000
      // })
    // })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

接着我们来实现常用工具类的封装(简化代码):
1.封装cookie
在src目录下面新建一个composables的目录,新建一个auth.js

// 引入usevue里面的useCookie方法
import { useCookies } from '@vueuse/integrations/useCookies'

// 定义token统一的值
const Tokenkey = "admin-token"
const cookie = useCookies()

// 获取token
export function getToken(){
    return cookie.get(Tokenkey)
}

// 设置token
export function setToken(token){
    return cookie.set(Tokenkey,token)
}

// 清除token
export function removeToken(){
    return cookie.remove(Tokenkey)
}

然后就去login.vue里面和axios.js里面用到了setToken和getToken的地方进行优化
目前login.vue代码:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
            <!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit" :loading="loading">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { login, getinfo } from '~/api/manager'
// 引入通知组件
import { ElNotification } from 'element-plus'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'
// 引入usevue里面的useCookie方法
// import { useCookies } from '@vueuse/integrations/useCookies'
// 引入auth.js里面的各种cookie方法,上面的那条引入就不需要了
import {
  setToken
} from '~/composables/auth'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
const loading = ref(false)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }

    // 在请求之前将loading状态设为true
    loading.value = true

    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      // console.log(res.data.data);
      // 因为前面axios.js里面使用了响应拦截器,所以不需要写这么长了,直接res即可
      console.log(res)
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      ElNotification({
        message: "登录成功",
        type: 'success',
        // 三秒后关闭
        duration:3000
      })

      // 登录成功存储用户cookie,用cookie变量接收
      // const cookie = useCookies()
      // 原:cookie.set("admin-token", res.data.data.token)
      // 因为前面的响应拦截器设置,这里也直接res.token就行
      // cookie.set("admin-token", res.token)
      // 这里经过auth.js里面的方法进行再次简化
      setToken(res.token)

      // 存储完成后获取用户信息
      getinfo().then(res2=>{
        console.log(res2)
      })

      // 跳转到后台首页
      router.push("/")
    }).finally(()=>{
      // 登录成功之后loading设置为false
      loading.value = false
    })
    // 错误处理移动到了axios.js
    // .catch(err=>{
      // 拿到报错具体信息
      // ElNotification({
      //   message: err.response.data.msg || "请求失败",
      //   type: 'error',
      //   // 三秒后关闭
      //   duration:3000
      // })
    // })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

axios.js现在的代码:

import axios from  "axios"
// 引入通知组件
import { ElNotification } from 'element-plus'
// 引入usevue里面的useCookie方法
// import { useCookies } from '@vueuse/integrations/useCookies'
// 这里引入auth里面的getToken方法,所以上面那条引入也不需要了
import { getToken } from '~/composables/auth'

const service = axios.create({
    baseURL:"/api"
})

// 添加请求拦截器
service.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    // 往header头自动添加token
    // 因为上面import了getToken方法,所以下面两行不需要了,直接getToken()即可
    // const cookie = useCookies()
    // const token = cookie.get("admin-token")
    const token = getToken()
    if(token){
        // 往请求头传token
        config.headers["token"] = token
    }

    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    // 这里之后login.vue里面就不需要res.data.data了,直接res就行
    return response.data.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 错误处理
    ElNotification({
        message: error.response.data.msg || "请求失败",
        type: 'error',
        // 三秒后关闭
        duration:3000
    })

    return Promise.reject(error);
  });

export default service

2.然后我们来封装提示:
在上面的composables文件夹再创建一个util.js

// 引入通知组件
import { ElNotification } from 'element-plus'

// 消息提示统一方法
// dangerouslyUseHTMLString表示是否将 message 属性作为 HTML 片段渲染出来
export function toast(message, type = "success", dangerouslyUseHTMLString = false){
    ElNotification({
        message,
        type,
        // 三秒后关闭
        duration:3000,
        dangerouslyUseHTMLString
    })
}

axios.js里面
在这里插入图片描述
在这里插入图片描述
login.vue里面
在这里插入图片描述
在这里插入图片描述
至此我们的cookie封装和消息功能封装就完成了。
此时axios.js的代码:

import axios from  "axios"
// 引入通知组件
// import { ElNotification } from 'element-plus'
// 因为util.js里面已经写了toast方法,所以就不用上面的引入了
import { toast } from '~/composables/util'
// 引入usevue里面的useCookie方法
// import { useCookies } from '@vueuse/integrations/useCookies'
// 这里引入auth里面的getToken方法,所以上面那条引入也不需要了
import { getToken } from '~/composables/auth'

const service = axios.create({
    baseURL:"/api"
})

// 添加请求拦截器
service.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    // 往header头自动添加token
    // 因为上面import了getToken方法,所以下面两行不需要了,直接getToken()即可
    // const cookie = useCookies()
    // const token = cookie.get("admin-token")
    const token = getToken()
    if(token){
        // 往请求头传token
        config.headers["token"] = token
    }

    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    // 这里之后login.vue里面就不需要res.data.data了,直接res就行
    return response.data.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 错误处理

    // 因为util的toast实现了消息的封装,所以直接像下面这样写就行
    toast(error.response.data.msg || "请求失败", "error")
    // ElNotification({
    //     message: error.response.data.msg || "请求失败",
    //     type: 'error',
    //     // 三秒后关闭
    //     duration:3000
    // })

    return Promise.reject(error);
  });

export default service

login.vue的代码:

<template>
  <el-row class="login-container">
    <el-col :lg="16" :md="12" class="left">
      <div>
        <div>欢迎光临</div>
        <div>此站是vue-admin的登录页面,作者是mzldustu</div>
      </div>
    </el-col> 

    <!-- 右边布局 -->  
    <el-col :lg="8" :md="12" class="right"
    >
      <h2 class="title">欢迎回来</h2>
      <div>
        <span class="line"></span>
        <span>账号密码登录</span>
        <span class="line"></span>
      </div>
      <!-- 这里使用的是element plus里面的典型表单 -->
      <!-- 表单宽度 -->
      <!-- 引入element里的rules ,定义一个ref-->
      <el-form ref="formRef" :rules="rules" :model="form" class="w-[250px]">
        <!-- 用户名输入框 -->
        <el-form-item prop="username">
          <el-input v-model="form.username" placeholder="请输入用户名">
            <!-- 插槽引入user图标 -->
            <template #prefix>
                <el-icon><User /></el-icon>
            </template>
          </el-input>

        </el-form-item>
        <!-- 密码输入框 -->
        <el-form-item prop="password">
          <!-- type设为password就是非明文存储,show-password就是后面的小眼睛,可以点击显示不显示密码 -->
          <el-input type="password" v-model="form.password" placeholder="请输入密码" show-password>
            <!-- 插槽引入lock图标 -->
            <template #prefix> 
                  <el-icon><Lock /></el-icon>
            </template>
          </el-input>
        </el-form-item>
        <!-- 按钮 -->
        <el-form-item>
            <!-- 圆角,宽度250px,颜色 -->
            <!-- 加一个loading状态,正常状态下是false,改变之后就是true -->
          <el-button round color="#626aef" class="w-[250px]" type="primary" @click="onSubmit" :loading="loading">登录</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script setup>
import { reactive, ref } from 'vue'
// 引入图标
import { User,Lock } from '@element-plus/icons-vue'
// 引入登录方法
import { login, getinfo } from '~/api/manager'
// 引入通知组件
// import { ElNotification } from 'element-plus'
// 因为util里面写了toast方法,所以就不用上面的引用了
import { toast } from '~/composables/util'
// 引入useRouter方便跳转
import { useRouter } from 'vue-router'
// 引入usevue里面的useCookie方法
// import { useCookies } from '@vueuse/integrations/useCookies'
// 引入auth.js里面的各种cookie方法,上面的那条引入就不需要了
import {
  setToken
} from '~/composables/auth'

// 拿到useRouter,才方便后面调用它的push方法
const router = useRouter()

// do not use same name with ref
const form = reactive({
    username:"",
    password:""
})

// 表单验证rules,要在前面指定prop
const rules = {
  username:[
    // 书写验证规则
    {
      required: true,
      message: '用户名不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
    {
      min:4,
      max:8,
      message: '用户名长度必须是4-8个字符',
      trigger: 'blur'
    },
  ],
  password:[
    {
      required: true,
      message: '密码不能为空',
      // 失去焦点的时候触发
      trigger: 'blur'
    },
  ]
}

// setup里拿到el-form节点
const formRef = ref(null)
// 定义一个loading,默认让它为false,不显示
const loading = ref(false)

const onSubmit = () => {
  formRef.value.validate((valid)=>{
    // 会输出true或者false
    // console.log(valid)
    if(!valid){
      return false
    }

    // 在请求之前将loading状态设为true
    loading.value = true

    // console.log(验证通过)
    // 参数验证通过后调用login方法
    // 通过前面的form拿到username和password
    login(form.username,form.password)
    .then(res=>{
      // 拿到响应成功结果
      // console.log(res.data.data);
      // 因为前面axios.js里面使用了响应拦截器,所以不需要写这么长了,直接res即可
      console.log(res)
      // 提示成功,存储用户token和用户相关信息,然后跳转到后台首页
      // ElNotification({
      //   message: "登录成功",
      //   type: 'success',
      //   // 三秒后关闭
      //   duration:3000
      // })
      // 因为util里面实现了toast方法,所以就不用再写相关代码了,直接登陆成功即可
      toast("登录成功")

      // 登录成功存储用户cookie,用cookie变量接收
      // const cookie = useCookies()
      // 原:cookie.set("admin-token", res.data.data.token)
      // 因为前面的响应拦截器设置,这里也直接res.token就行
      // cookie.set("admin-token", res.token)
      // 这里经过auth.js里面的方法进行再次简化
      setToken(res.token)

      // 存储完成后获取用户信息
      getinfo().then(res2=>{
        console.log(res2)
      })

      // 跳转到后台首页
      router.push("/")
    }).finally(()=>{
      // 登录成功之后loading设置为false
      loading.value = false
    })
    // 错误处理移动到了axios.js
    // .catch(err=>{
      // 拿到报错具体信息
      // ElNotification({
      //   message: err.response.data.msg || "请求失败",
      //   type: 'error',
      //   // 三秒后关闭
      //   duration:3000
      // })
    // })
  })
}
</script>

<style scoped>
.login-container{
    /*el-row会将页面分为24份,用span进行值的设定 这里面的class是windi css里面的颜色快捷方式,以及设置min-height使其占满整个屏幕 */
    @apply min-h-screen bg-indigo-500;
}

/* <!-- 左边布局 class里面实现的主要是垂直水平居中 flex-col就是垂直方向-->
    <!-- 因为要响应式布局,所以我们使用windi css里面类似媒体查询的一个功能 lg是>1200px的屏幕,md是>992px的屏幕--> */
.login-container .left, .login-container .right{
    @apply flex items-center justify-center;
}

.login-container .right{
    @apply bg-light-50 flex-col;
}

/* class里面的内容分别是:加粗,字体增大,字体加亮,与下面外间距1rem */
.left>div>div:first-child{
    @apply font-bold text-5xl text-light-50 mb-4;
}

.left>div>div:last-child{
    @apply text-gray-200 text-sm;
}
/* 加粗,字体3xl,颜色深灰色 */
.right .title{
    @apply font-bold text-3xl text-gray-800;
}

/* flex布局,水平垂直居中,上下边距,浅灰色,水平方向元素的间距 */
.right>div{
    @apply flex items-center justify-center my-5 text-gray-300 space-x-2;
}

/* 高度,宽度,浅灰色 */
.right .line{
    @apply h-[1px] w-16 bg-gray-200;
}
</style>

而运行项目我们发现我们的功能仍然是没问题的,至此,封装就写完了。

然后我们来引入vuex状态管理用户信息
我们使用的是vuex4
先安装:

npm install vuex@next --save

然后在src文件夹下面创建store文件夹,创建index.js文件编辑代码:

import { createStore } from 'vuex'

// 创建一个新的 store 实例
// 管理用户相关信息
const store = createStore({
    state () {
      return {
        // 用户信息存储在state
        user:{}
      }
    },
    // 修改用户信息
    mutations: {
        // 记录用户信息
        SET_USERINFO(state,user){
            // 通过state.user拿到上面的空对象
            state.user = user
        }
    }
  })

  export default store

然后在main.js里面引入并app.use()

import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'
// 引入store
import store from './store'
//引入注册所有图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)

app.use(store)

app.use(router)

app.use(ElementPlus)
// 引入图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

import 'virtual:windi.css'

app.mount('#app')

然后在login.vue里面引入并获取
在这里插入图片描述
然后将登录成功信息res2传给state
在这里插入图片描述
然后为了验证,我们打开index.vue
在这里插入图片描述
加上一行{{ $store.state.user }}
然后运行项目,首先首页,你发现大括号里什么都没有
在这里插入图片描述
然后再去登录,跳转之后你会发现数据全部拿到了,说明我们vuex存储获取用户数据成功了。
在这里插入图片描述
至此,vuex状态管理用户信息就完成了。

接着我们来实现全局路由守卫实现拦截实现登陆验证:
路由守卫实现的功能就是如果你没有登录,就无法进入某个页面,比如这里没有登录,就不能进入主页index.vue

我们在src下面创建permission.js处理权限验证相关的东西
main.js里面导入一下import "./permission"

permission.js:

import router from "~/router"
import { getToken } from "~/composables/auth"
import { toast } from "~/composables/util"

// 全局前置守卫
// to是即将到达的页面路径,from表示从哪个路径来
router.beforeEach((to, from, next)=>{
    // console.log("全局前置守卫");
    // 拿一下token
    const token = getToken()

    // 如果没有token而且不是在登录页,那么强制跳转回登录页
    if(!token && to.path != "/login" ){
        toast("请先登录","error")
        return next({ path:"/login" })
    }

    // 防止重复登录的判断
    if(token && to.path == "/login"){
        toast("请勿重复登录","error")
        return next({ path:from.path ? from.path : "/" })
    }
    next()
})

在这之后,如果没有登录会强制你到登录页登录,如果已经登录,那么你重复登陆它会提醒你不要重复登陆。
在这里插入图片描述

在这里插入图片描述
接着我们继续完善登录功能:
1.解决首页获取到用户信息之后刷新信息消失的问题
在store里面写action,
在这里插入图片描述
login.vue里面这一块就迁移了
在这里插入图片描述
在permisson.js里面:

import router from "~/router"
import { getToken } from "~/composables/auth"
import { toast } from "~/composables/util"
import store from "./store"

// 全局前置守卫
// to是即将到达的页面路径,from表示从哪个路径来
router.beforeEach(async (to, from, next)=>{
    // console.log("全局前置守卫");
    // 拿一下token
    const token = getToken()

    // 如果没有token而且不是在登录页,那么强制跳转回登录页
    if(!token && to.path != "/login" ){
        toast("请先登录","error")
        return next({ path:"/login" })
    }

    // 防止重复登录的判断
    if(token && to.path == "/login"){
        toast("请勿重复登录","error")
        return next({ path:from.path ? from.path : "/" })
    }

    // 如果用户登录了就自动获取用户登录信息,并存储在vuex里,这样它就会自动异步帮我们获取用户信息,刷新也没事
    if(token){
        await store.dispatch("getinfo")
    }

    next()
})

搞了一个async await的异步操作,然后它会自动异步帮你获取用户的信息。
然后刷新主页用户信息就不会被清空了,这里就解决了刷新用户信息会丢失的问题。

2.对登录相关方法进行抽离
action里面加一个login进行抽离
在这里插入图片描述
然后login.vue里面
在这里插入图片描述
这样就完成了抽离

3.接着我们实现按回车就登录的功能
在login.vue里面引入两个生命周期
在这里插入图片描述
然后直接写
在这里插入图片描述
这时候就实现了回车登录事件。
至此,登录功能完善完成。

然后我们来实现退出登录功能:
使用的是element里面的message box消息弹出框里面的确认消息,因为会在多处使用,所以我们将它封装成一个公共的方法。
首先manager.js编辑写一个logout方法:

import axios from '~/axios'
// 登录
export function login(username,password) {
    return axios.post("/admin/login",{
        username,
        password
    })
}

// 获取登录用户信息
// 因为前面的请求拦截器已经自动添加了token,所以这里不用刻意传值了
export function getinfo(){
    return axios.post("/admin/getinfo")
}

// 退出登录
// 请求栏已经有了,所以不用传任何参数
export function logout(){
    return axios.post("/admin/logout")
}

然后在index.vue里面写一个handleLogout方法

<template>
    <div>
        后台首页

        {{ $store.state.user.username }}

        <el-button @click="handleLogout">退出登录</el-button>
    </div>
</template>
<!-- 响应式api ,ref,一个变量响应式,普通类型,script里面count.value,template里面直接{{count}}-->
<!-- 响应式api , reactive,用于对象 script里面form.count++,template里面直接{{form.count}}-->

<script setup>
// 退出接口
import { logout } from "~/api/manager"
// 提示框
import { showModal, toast } from "~/composables/util"
// 方便页面跳转
import { useRouter } from "vue-router"
// 引入useStore
import { useStore } from 'vuex'

const router = useRouter()

const store = useStore()

    function handleLogout(){
        showModal("是否要退出登录?").then(res=>{
            console.log("退出登录");
            logout().finally(()=>{
                // 移出cookie里的token
                // 清除当前用户状态 vuex里的user
                // 上面的两步已经在store里面实现了

                store.dispatch("logout")
                // 跳转回登录页
                router.push("/login")
                // 提示退出登录成功
                toast("退出登录成功")
            })
        })
    }
</script>

在store里面实现user的清空,即清除token,以及清除state里面的user
在这里插入图片描述
目前store的index.js代码:

import { createStore } from 'vuex'
// 引入方法
import { login, getinfo } from '~/api/manager'
// 引入auth.js里面的各种cookie方法,上面的那条引入就不需要了
import { setToken, removeToken } from '~/composables/auth'
import { logout } from '../api/manager'

// 创建一个新的 store 实例
// 管理用户相关信息
const store = createStore({
    state () {
      return {
        // 用户信息存储在state
        user:{}
      }
    },
    // 修改用户信息
    mutations: {
        // 记录用户信息
        SET_USERINFO(state,user){
            // 通过state.user拿到上面的空对象
            state.user = user
        }
    },
    actions: {
        // 抽离登录
        login({ commit }, { username,password }){
            return new Promise((resolve,reject)=>{
                login(username,password).then(res=>{
                    setToken(res.token)
                    // 如果成功,直接resolve
                    resolve(res)
                }).catch(err=>reject(err))
            })
        },

        // 获取当前用户登录信息
        // 等同于store.commit
        getinfo({ commit }){
            return new Promise((resolve,reject)=>{
                getinfo().then(res=>{
                    commit("SET_USERINFO",res)
                    // 成功调用resolve
                    resolve(res)
                }).catch(err=>reject(err))
            })
        },

        // 退出登录
        // 解构user里的commit
        logout({ commit }){
            // 移出cookie里的token
            removeToken()
            // 清除当前用户状态 vuex,即state里的user清空
            commit("SET_USERINFO", {})
        }
    }
  })

  export default store

在这里我们再看看功能:
在这里插入图片描述
点击确认之后
在这里插入图片描述
到这里就完成了退出登录

然后我们进行全局loading进度条的实现:
我们这里要用到一个第三方库,nprogress
地址
安装

npm i nprogress

先在util.js里面写上关闭和开启nprogress方法

// 引入通知组件
import { ElNotification, ElMessageBox } from 'element-plus'
// 引入loading插件
import nProgress from 'nprogress'

// 消息提示统一方法
// dangerouslyUseHTMLString表示是否将 message 属性作为 HTML 片段渲染出来
export function toast(message, type = "success", dangerouslyUseHTMLString = false){
    ElNotification({
        message,
        type,
        // 三秒后关闭
        duration:3000,
        dangerouslyUseHTMLString
    })
}

// 消息提示框
export function showModal(content = "提示内容", type = "warning", title = ""){
    return ElMessageBox.confirm(
        content,
        title,
        {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type,
        }
      )
}

// 显示全屏loading
export function showFullLoading(){
    nProgress.start()
}

// 隐藏全屏loading
export function hideFullLoading(){
    nProgress.done()
}

默认进度条是浅蓝色,但是有点和界面颜色相同,所以在App.vue里面写样式,改成浅灰色
在这里插入图片描述
关闭就在permission.js里面,前置路由守卫引用,后置路由守卫退出。

import router from "~/router"
import { getToken } from "~/composables/auth"
import { toast, showFullLoading, hideFullLoading } from "~/composables/util"
import store from "./store"

// 全局前置守卫,路由变化就会触发守卫
// to是即将到达的页面路径,from表示从哪个路径来
router.beforeEach(async (to, from, next)=>{

    // 显示loading
    showFullLoading()
    // console.log("全局前置守卫");
    // 拿一下token
    const token = getToken()

    // 如果没有token而且不是在登录页,那么强制跳转回登录页
    if(!token && to.path != "/login" ){
        toast("请先登录","error")
        return next({ path:"/login" })
    }

    // 防止重复登录的判断
    if(token && to.path == "/login"){
        toast("请勿重复登录","error")
        return next({ path:from.path ? from.path : "/" })
    }

    // 如果用户登录了就自动获取用户登录信息,并存储在vuex里,这样它就会自动异步帮我们获取用户信息,刷新也没事
    if(token){
        await store.dispatch("getinfo")
    }

    next()
})

// 全局后置守卫
// 调用关闭进度条方法
router.afterEach((to, from) => hideFullLoading())

这里没办法截图,因为太快了。自己试试吧。

接着写动态页面标题实现:
切换路由的时候会改变页面的title标题:
首先在router文件夹下的index.js的每个路由加上meta:{ title: }
在这里插入图片描述

然后在permisson.js里面的全局路由前置守卫进行处理
在这里插入图片描述
至此,我们的登录功能就做完了,还是挺全的。涉及到了表单验证,cookie存储,axios交互,请求拦截器和响应拦截器,工具库的封装,vuex的状态管理,全局路由守卫,退出登录,全局loading,动态页面标题。门道很多的,所以网上那些5分钟写完登陆注册功能什么成分不用说了吧,那都是小打小闹。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/407621.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端开发工具】VUE3 devtools安装

背景 尤雨溪在2020年9月19日晚正式发布vue3.0 one piece。此版本相较于vue2版本&#xff0c;更快、更小、更易维护、更易于原生、让开发者更轻松&#xff1b;所以学习vue3&#xff0c;对于一个前端开发者来说是一个刻不容缓的学习趋势。 学习vue3自然也离不开debug啦~~ Vue官方…

JS——正则表达式(超详细)

正则表达式概念创建正则表达式正则表达式常用方法test(字符串)search(正则表达式&#xff09;正则表达式.exec(字符串&#xff09;字符串.match(正则表达式)字符串.replace(正则表达式&#xff0c;新的内容&#xff09;断言范围类字符类字符类取反修饰符g&#xff1a;global全文…

Web前端 | HTML嵌入JS代码的三种方式

✅作者简介&#xff1a;一位材料转码农的选手&#xff0c;希望一起努力&#xff0c;一起进步&#xff01; &#x1f4c3;个人主页&#xff1a;每天都要敲代码的个人主页 &#x1f525;系列专栏&#xff1a;Web前端 目录 一&#xff1a;JavaScript概述 二&#xff1a; HTML嵌入…

如何升级Vue的版本 vue2.9.6升级到vue3.0

背景&#xff1a;电脑使用多年&#xff0c;一直使用vue 2.x版本&#xff0c;项目开发过程中由于一个模块涉及的集成模块过多&#xff0c;导致需要进行定义的变量越来越多&#xff0c;代码出现冗余&#xff0c;因此在同事的推动下&#xff0c;鉴于vue 3.x的优化&#xff0c;故对…

HTML学生个人网站作业设计:个人主页博客web网页设计制作 (HTML+CSS) (1)

&#x1f329;️ 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f482; 作者主页: 【进入主页—&#x1f680;获取更多源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;HTML5网页期末作业 (1000套…

React函数式组件传值(父子,兄弟,祖先等)以及父子之间方法的调用。

一.前言 react作为一门前端语言&#xff0c;具有独特灵活性&#xff0c;这也是它的魅力所在。前端组件化&#xff0c;也是将整个前端分成了一个一个组件&#xff0c;本期我们讲解的就是react组件之间的传值&#xff08;包括父子&#xff0c;兄弟&#xff0c;祖先&#xff09;等…

Vue知识点总结

特出以下声明&#xff01;&#xff01;&#xff01;小编只是一个喜欢系统化总结知识的人&#xff0c;我这里的总结&#xff0c;可以理解为vue的知识框架。有一些关于语法之类的&#xff0c;建议大家去官网看&#xff0c;可以直接施展CV大法&#xff0c;哈哈哈。同时有一些知识点…

【Vue全局事件总线详解】

Vue全局事件总线详解Vue全局事件总线详解简介原理一、满足所有组件都能访问得到全局事件总线二、可以调用 $on ,和 $off 和 $emit实现全局事件总线完整实例代码总结Vue全局事件总线详解 简介 全局事件总线是一种组件间通信的方式&#xff0c;适用于任意组件间通信 全局事件…

1.启动前端项目(命令行)

1、从github或其他地方获得的前端项目&#xff0c;想要看效果 1.用管理员身份运行命令提示符&#xff08;windows的控制后台&#xff09; 2.进入前端项目安装的文件夹&#xff08;这是我的文件夹&#xff09; 3.在命令行中输入&#xff1a;npm install 4.在命令行中输入&am…

Vben Admin框架 table的使用以及相关的内容

Vben Admin框架 table的使用以及相关的内容 一、table的使用基础示例&#xff1a; Vben Admin官网链接: 官网组件页链接 这是我使用此框架的用法&#xff0c;仅供参考&#xff0c;我是vue3项目中使用此框架&#xff0c;写法是vue3的写法 第一步 引入BasicTable 组件。 // r…

【Web前端】一文带你吃透HTML(上篇)

前端学习路线小总结: 基础入门:HTML CSS JavaScript三大主流框架:VUE REACT Angular深入学习:小程序 Node jQuery TypeScript 前端工程化🍁开始前端之旅吧! 一.HTML简介1.什么是HTML?2.HTML 标签3.HTML 元素4.HTML版本5.Web 浏览器<

Vue computed 报错:Computed property ‘ ‘ was assigned to but it has no setter 错误原因分析与解决办法

错误描述 最近在封装Vue模块时&#xff0c;借助Vue的Computed属性监听传递的数据&#xff0c;但是开发调试过程中控制台取提示Computed property was assigned to but it has no setter 错误。控制台报错如下&#xff1a; 错误分析 根据控制台错误提示&#xff0c;组件中定义…

如何给网页添加icon图标?

做一些小页面或者项目的时候&#xff0c;我们会发现每个网站都有自己的小图标&#xff0c;下面我就告诉你怎么弄这个&#xff0c;超简单的&#xff01;&#x1f49c;&#x1f49c; 网站添加icon小图标网页图标favicon.ico小简介1.添加网站已有icon图标2.自定义icon图标网页图标…

VUE——使用VUE脚手架创建项目

前言 vue脚手架工具&#xff0c;对vue项目构造做了封装&#xff0c;直接使用vue-cli创建项目&#xff0c;常用配置自动帮你完成&#xff0c;不用自己像使用webpack一样配置。 目录 1、安装 npm i vue 2、创建vue项目 3、运行项目 1、安装 npm i vue ps:项目化开发中&…

前端加密,后端解密的过程及代码(密码明文传输解决,不是太保险。key在前端有显示)

在工作中经常遇到密码明文传输这个问题&#xff0c;为了让密码安全些会让加密&#xff0c;现在有个比较方便的AES加密分享给大家&#xff0c;话不多说&#xff0c;上代码 1&#xff0c;首先引入前端需要用到的js&#xff1a;crypto-js&#xff0c;下载命令 npm install cryp…

vue中computed和watch的使用场景

一、computed computed擅长处理的场景&#xff1a;一个数据受多个数据影响&#xff0c;如果一个属性是由其他属性计算而来的&#xff0c;这个属性依赖其他属性是一个多对一或者一对一&#xff0c;一般用computed&#xff1b; 如果 computed 属性值是函数&#xff0c;那么默认会…

uni-app,vue3接口请求封装

uni-app接口&#xff0c;全局方法封装 1.在根目录创建一个api文件&#xff0c;在api文件夹中创建api.js&#xff0c;baseUrl.js和http.js文件 2. baseUrl.js文件代码 export default "https://XXXX.test03.qcw800.com/api/" 3.http.js文件代码 export function h…

【微信小程序】运行机制和更新机制

&#x1f352;观众老爷们好呀&#xff0c;牛牛又更文了&#xff0c;上文我们对部分比较常用的组件进行了讲解&#xff0c;作为开发者&#xff0c;我们还需要对小程序的运行机制和更新机制做一定的了解&#xff0c;那问题来了&#xff0c;你对它们了解多少呢&#xff1f; &#…

微信小程序自定义导航栏机型适配--底部Tabbar--view高度--底部按钮适配

自定义微信小程序头部导航栏 自定义微信小程序头部导航栏&#xff0c;有几种方式 方式一 {"navigationStyle": "custom" // 将navigationStyle从默认default改为custom }定义此方法后&#xff0c;头部的导航栏会去掉&#xff0c;导航栏下的元素会直接向上…

‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序?npm i (npm install)安装不上的原因

启动项目时&#xff0c;本地有node包&#xff0c;但是终端提示&#xff1a;vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序&#xff0c;导致项目无法启动&#xff0c;如下&#xff1a; 原因之一是项目的npm版本较低&#xff0c;但是你本地的npm版本过高&am…