一、Vue应用案例

1、案例准备工作

(1)实现功能

实现商品类别管理,包含类别列表和新增商品类别。

(2)Mock.js概述

Mock.js用于生成模拟数据,通常用于前端开发和测试。它可以帮助开发人员模拟后端 API 的响应数据,以便在没有实际后端服务的情况下进行前端开发,测试和调试。

mock

(3)添加模拟接口示例

创建hello.js

module.exports = [
  //模拟发送Ajax请求
  {
    url: '/hello', //请求地址
    type: 'get',  //请求类型
    response: config => {
      return {
        code: 20000, //ok
        data: {
          message: 'Hello World!'
        }
      }
    }
  }
]

将组件添加到index.js中的mock数组中:

const user = require('./user')
const table = require('./table')
const hello = require('./hello')

const mocks = [
  ...user,
  ...table,
  ...hello
]

请求的地址为:http://localhost:9528/dev-api/hello

mock效果展示

(4)request.js文件

ue-element-admin在src/utils下提供了一个request.js文件,用于提供简化发送异步请求和处理响应内容的支持。

request.js中创建了一个Axios实例,并为该实例添加了请求拦截器和响应拦截器。

请求拦截器的主要作用是为已登录用户在请求中添加token(SEC部分展开介绍)。

响应拦截器的主要作用是获取响应状态码,并根据约定进行一些错误处理。

  • 20000为响应成功的状态码
  • 50008、50012、50014为身份信息异常的状态码,提示用户重新登录
  • 如果与服务器的连接出现异常,也会提示错误信息,并将错误信息输出到浏览器控制台

(5)发送异步请求示例

getHello用于发送AJAX请求

在src/api文件夹下新建helloDemo.js文件,在其中封装异步请求接口的方法

import request from '@/utils/request' //引入request.js组件

export function getHello(){
  return request({
    //返回request 发送AJAX请求
    url: "/hello",
    method: "get" //请求类型
  })
}

在views下demo里创建index.view

<template>
  <div class="grid-content bg-purple">
    <h3 style="line-height: 32px">
      {{msg}}
    </h3>
  </div>

</template>

<script>
//导入getHello方法
import {getHello} from '@/api/helloDemo'

export default {
  name: 'index',
  data(){
    return{
      msg: ""
    }
  },
  mounted() {
    //自动发送请求
    getHello().then(res =>{
      this.msg = res.data; //获取请求的数据
    }).catch()
  }
}
</script>

<style scoped>

</style>

hello.js数据:

module.exports = [
  //模拟发送Ajax请求
  {
    url: '/hello', //请求地址
    type: 'get',  //请求类型
    response: config => {
      return {
        code: 20000, //ok
        data: {
          message: 'Hello World!'
        }
      }
    }
  }
]

联系:

调用关系

请求效果展示:

异步请求效果展示

2、开发商品类别模块

(1)添加案例接口文件

将pms_category.js文件添加到mock中,这个文件的主要作用是模拟接口的js文件。

在/mock/index.js文件中导入该文件,并添加到mocks数组中

const user = require('./user')
const table = require('./table')
const hello = require('./hello')
const category = require('./pms_category')

const mocks = [
  ...user,
  ...table,
  ...hello,
  ...category
]

(2)实现商品类别列表-展示一级类别

1、新增的目录结构为:src -> views -> product -> category -> list -> index.vue。

2、【是否启用】、【是否显示在导航栏】列根据数据的enable属性的值和is_display属性的值进行显示。

3、【设置】列根据数据的is_parent属性的值决定是否显示按钮。

4、页面挂载完成后,向品类数据接口发送异步请求,获取一级类别数据,并动态显示在页面中。

5、可以参考table/index.vue中的内容进行开发。

步骤一:实现页面组件

<template>
  <div style="padding: 0 15px">
    <el-row>
      <el-col :span="8">
        <h3 style="line-height: 32px">商品类别</h3>
      </el-col>
    </el-row>
    <el-table :data="tableData">
      <el-table-column align="center" label="编号" width="60" type="index"/>
      <el-table-column align="center" label="类别名称" width="140">
        <template slot-scope="scope">{{ scope.row.name }}</template>
      </el-table-column>
      <el-table-column align="center" label="级别" width="100">
        <template slot-scope="scope">{{ scope.row.depth }}</template>
      </el-table-column>
      <el-table-column align="center" label="排序" width="60">
        <template slot-scope="scope">{{ scope.row.sort }}</template>
      </el-table-column>
      <el-table-column align="center" label="是否启用" width="140">
        <template slot-scope="scope">
          <el-switch v-model="scope.row.enable"
                     :active-value="1"
                     :inactive-value="0"
                     active-color="#30a8ac"
          />
        </template>
      </el-table-column>
      <el-table-column align="center" label="是否显示在导航栏" width="140">
        <template slot-scope="scope">
          <el-switch v-model="scope.row.is_display"
                     :active-value="1"
                     :inactive-value="0"
                     active-color="#30a8ac"
          />
        </template>
      </el-table-column>

      <el-table-column align="center" label="设置">
        <template slot-scope="scope">
          <el-button
            v-if="scope.row.is_parent"
            title="查看下级"
            size="mini"
            icon="el-icon-folder-opened"
          >查看下级
          </el-button>
        </template>
      </el-table-column>

      <el-table-column align="center" label="操作" width="170">
        <template>
          <el-button title="编辑"
                     type="warning"
                     size="mini"
                     icon="el-icon-edit"
                     circle
          ></el-button>
          <el-button title="删除"
                     type="danger"
                     size="mini"
                     icon="el-icon-delete"
                     circle
          ></el-button>
        </template>
      </el-table-column>

    </el-table>
  </div>
</template>

<script>
export default {
  name: 'index',
  data() {
    return {
      tableData: [
        {
          'id': 1,
          'name': '家用电器',
          'parent_id': 0,
          'depth': 1,
          'keywords': '无',
          'sort': 0,
          'icon': 'https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png',
          'enable': 1,
          'is_parent': 1,
          'is_display': 1,
          'gmt_create': '2022-07-08 11:30:44',
          'gmt_modified': '2022-07-08 11:30:44'
        },
        {
          'id': 2,
          'name': '电视',
          'parent_id': 1,
          'depth': 2,
          'keywords': '无',
          'sort': 0,
          'icon': 'https://cube.elemecdn.com/e/fd/0fc7d20532fdaf769a25683617711png.png',
          'enable': 1,
          'is_parent': 1,
          'is_display': 1,
          'gmt_create': '2022-07-08 11:30:44',
          'gmt_modified': '2022-07-08 11:30:44'
        }
      ]
    }
  }
}
</script>

<style scoped>

</style>

步骤二:路由导航

{
  path: '/category',
  component: Layout,
  name: 'Category',
  meta: { title: '商品类别管理', icon: 'link' },
  children: [
    {
      path: 'list',
      name: 'CategoryList',
      component: () => import('@/views/product/category/list/index'),
      meta: { title: '类别列表', icon: 'link' }
    }
  ]
},

测试效果:

类别列表测试效果

步骤三:实现异步请求

在src/api文件夹下声明category.js文件,并在该文件夹中添加发送异步请求的方法。

import request from '@/utils/request'

export function queryByParentId(param) {
  return request({
    url: '/categories/queryByParentId?parentId='+param,
    method: 'get'
  })
}

修改views/product/category/list/index.vue文件中的代码,删除测试数据,改为发送异步请求,并使用得到的响应数据。

<script>
import category from '@/api/category'
export default {
  name: 'index',
  data() {
    return {
      tableData: []
    }
  },
  methods: {
    loadCategoryList(parentId){
      category.queryByParentId(parentId).then((res) =>{
        this.tableData = res.data.items;
      })
    }
  },
  mounted() {
    //自动调用
    this.loadCategoryList(0);
  }
}
</script>

运行效果:

异步请求访问效果二

(3)实现商品类别列表-展示其他级类别

为按钮添加事件,实现查看下级功能

<el-button
  v-if="scope.row.is_parent"
  title="查看下级"
  size="mini"
  icon="el-icon-folder-opened"
  @click="loadCategoryList(scope.row.id)"
>查看下级
</el-button>

显示查看上级功能:

在/category/list/index在查看下级按钮下添加查看上级按钮

<el-button
  v-if="scope.row.parent_id != 0"
  title="查看上级"
  size="mini"
  icon="el-icon-folder-opened"
  @click="loadParentCategoryList(scope.row.parent_id)"
>查看上级
</el-button>

修改category.js的内容:

import request from '@/utils/request'

export function queryByParentId(param) {
  return request({
    url: `/categories/queryByParentId?parentId=${param}`,
    method: 'get'
  })
}

export function queryById(param){
  return request({
    url: `/categories/queryById?id=${param}`,
    method: 'get'
  })
}

在/category/list/index中新添加loadParentCategoryList方法:

methods: {
  loadCategoryList(parentId) {
    api.queryByParentId(parentId).then((res) => {
      this.tableData = res.data.items
    })
  }, 
  loadParentCategoryList(parentId) {
    api.queryById(parentId).then((res) => {
      console.log('加载父级列表:', res)
      parentId = res.data.items[0].parent_id
      this.loadCategoryList(parentId)
    })
  }
}

展示其他类别效果图

(4)实现新增商品类别

功能具体需求如下:

1、类别名称、排序序号、关键词为必填项,使用Element UI的表单验证规则进行验证。

2、可以不选择上级类别,当不选择上级类别时,parent_id的值为0。

3、上传类别图标功能无需实现,表单中的该项数据提交空字符串即可。

4、点击【返回】时返回类别列表页面。

5、点击【确认】时,先进行必填项的验证,然后提交异步请求。

6、添加成功后,提示类别添加成功,并自动跳转类别列表页面。

实现步骤:

  • 开发基础表单页面
  • 实现层级菜单展示和选择
  • 实现表单提交
  • 综合测试
开发基础表单页面

在src/views/product/category文件夹下新建add文件夹,并在该文件夹中声明index.vue,在index.vue中开发基础表单页面。

上级品类菜单项使用el-cascader组件实现层级菜单展示和选择效果

<template>
  <div style="padding: 0 15px">
    <div class="box">
      <el-row>
        <el-col :span="12">
          <el-form
            ref="ruleForm"
            :model="ruleForm"
            :rules="rules"
            label-width="180px"
          >
            <el-form-item label="上级类别" prop="parentId">
              <el-cascader v-model="ruleForm.parent_id" style="width: 100%"
                           placeholder="点击选择上级类别,否则默认一级类别" size="small"
                           :props="props"
              />
            </el-form-item>

            <el-form-item label="类别名称" prop="name">
              <el-input v-model="ruleForm.name" size="small"></el-input>
            </el-form-item>

            <el-form-item label="排序" prop="sort">
              <el-input v-model="ruleForm.sort" size="small"></el-input>
              (排序值越大越靠前)
            </el-form-item>

            <el-form-item label="是否启用" prop="enable">
              <el-switch
                v-model="ruleForm.enable"
                :active-value="1"
                :inactive-value="0"
                active-color="#1aa"
                inactive-color="#aaa"
              />
            </el-form-item>

            <el-form-item label="是否在导航栏中显示" prop="isDisplay">
              <el-switch
                v-model="ruleForm.is_display"
                :active-value="1"
                :inactive-value="0"
                active-color="#1aa"
                inactive-color="#aaa"
              />
            </el-form-item>

            <el-form-item label="类别图标" prop="icon">
              <input v-model="ruleForm.icon" type="hidden"/>
              <el-upload>
                <img v-if="imageUrl" :src="imageUrl" class="avatar" style="width: 130px"/>
                <i v-else class="el-icon-plus"></i>
              </el-upload>
            </el-form-item>

            <el-form-item label="关键词" prop="keywords">
              <el-input v-model="ruleForm.keywords" size="small"></el-input>
            </el-form-item>

            <el-form-item>
              <el-button @click="$router.go(-1)">返回</el-button>
              <el-button type="primary" @click="submitForm('ruleForm')">确认</el-button>
            </el-form-item>
          </el-form>
        </el-col>
      </el-row>
    </div>
  </div>
</template>

<script>
import * as api from '@/api/category'

export default {
  name: 'index',
  data() {
    return {
      imageUrl: '',
      ruleForm: {
        name: '',
        parent_id: [0],
        sort: 0,
        enable: 1,
        is_display: 1,
        icon: '',
        keywords: ''
      },
      rules: {
        name: [{ required: true, message: '请输入类别名称', trigger: 'blur' }],
        sort: [{ required: true, message: '请输入排序序号', trigger: 'blur' }],
        keywords: [{ required: true, message: '请输入关键词', trigger: 'blur' }]
      },
      props: {
        lazy: true, // 懒加载模式
        checkStrictly: true,//可以选择中间类别
        lazyLoad: (node, resolve) => {
          //node 是被选中的节点, resolve 是回调函数,把选择的数据返回给组件
          console.log('选择的节点是:', node)
          let parentId = 0
          //当选择的节点不是根节点 设置parentId为当前节点id
          if (!node.root) {
            parentId = node.data.value
          }
          //加载相应的子类别
          api.queryByParentId(parentId).then((res) => {
            console.log('加载子类别:', res)
            const children = res.data.items
            //定义返回的数据结构 返回的数据[{label:'类别名',value:'类别id'}]
            const nodes = [] //定义数组
            //遍历所有子类别
            children.forEach((category) => {
              //往数组中添加数据
              nodes.push({
                label: category.name,//类别名
                value: category.id, //类别id
                leaf: category.is_parent !== 1 //是否为叶子节点
              })
            })
            console.log('返回的数据结构:', nodes)
            //把数据返回给组件
            resolve(nodes)
          })
        }
      }
    }
  },
  methods: {
    //提交表单
    submitForm(formName) {
      this.$refs[formName].validate((valid) => {
        //验证成功
        if (valid) {
          console.log(this.ruleForm)
          //因为cascader返回的parentId是一个层级数组 ,重置表单中的parentId
          const parentIds = this.ruleForm.parent_id
          console.log(parentIds)
          this.ruleForm.parent_id = parentIds[parentIds.length - 1] //数组的最后一个元素
          this.ruleForm.depth = parentIds.length + 1 //级别

          //添加类别
          api.addCategory(this.ruleForm).then((res) => {
            console.log('添加类别;', res)
            this.$message({
              showClose: true,
              message: '添加类别成功',
              type: 'success'
            })
            this.$router.push({ name: 'Category' }) //添加成功 返回类别列表
          })
        } else {
          console.log('添加类别失败')
          return false
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

添加路由:

{
  path: '/category',
  component: Layout,
  name: 'Category',
  meta: { title: '商品类别管理', icon: 'link' },
  children: [
    {
      path: 'list',
      name: 'CategoryList',
      component: () => import('@/views/product/category/list/index'),
      meta: { title: '类别列表', icon: 'link' }
    },
    {
      path: 'add',
      name: 'AddCategory',
      component: () => import('@/views/product/category/add/index'),
      meta: { title: '添加类别', icon: 'form' }
    }
  ]
}

在category.js里面添加方法:

export function addCategory(data) {
  return request({
    url: `/category/add-new`,
    method: 'post',
    data
  })
}

测试效果:

添加商品测试效果

二、组件的生命周期(了解)

1、组件的生命周期和钩子函数

Vue 2组件的生命周期可以分为8个阶段,每个阶段都有对应的钩子函数,下面是具体的介绍:

  • beforeCreate:在Vue实例被创建之初,完成数据的观测和初始化之前调用,此时组件中的data和methods还未被初始化,不能访问到任何数据和方法。
  • created:在Vue实例被创建之后,完成数据的观测和初始化之后调用,此时组件中的data和methods已经被初始化,但是DOM还未被渲染。
  • beforeMount:在组件DOM挂载到页面前调用,组件的模板已编译完成,但还未生成真实的DOM节点。
  • mounted:在组件DOM挂载到页面之后调用,组件已经生成了真实的DOM节点,可以进行DOM操作。
  • beforeUpdate:在组件更新之前调用,此时组件的数据已经更新,但是DOM还未被重新渲染。
  • updated:在组件更新之后调用,此时组件的数据已经更新,并且DOM已经被重新渲染。
  • beforeDestroy:在组件被销毁之前调用,此时组件实例还存在,可以进行一些清理操作。
  • destroyed:在组件被销毁之后调用,此时组件实例已经被销毁,所有的事件监听和定时器也已经被移除。

2、钩子函数的应用场景

Vue的生命周期钩子函数在不同的应用场景中可以用于解决各种问题。

1、beforeCreate:

适用场景:在组件实例初始化之前,通常用于执行一些配置、数据初始化或全局事件注册。

示例:在这个阶段可以设置初始数据、获取应用程序配置信息等。

2、created:

适用场景:在组件实例已经创建完成,但还未挂载到 DOM 之前,通常用于数据获取、异步操作等。

示例:在这个阶段可以发送 AJAX 请求获取数据,然后更新组件的状态。

3、beforeMount:

适用场景:在模板编译之前被调用,用于执行一些准备工作。

示例:可以在这个阶段进行一些模板编译前的数据处理。

4、mounted:

适用场景:在组件已经挂载到 DOM 后被调用,通常用于访问 DOM 元素、执行一次性操作或与第三方库集成。

示例:可以在这个阶段执行初始化第三方插件、添加事件监听器或进行 DOM 操作。

5、beforeUpdate:

适用场景:在数据更新之前被调用,不会触发首次渲染。

示例:可以在这个阶段进行数据比较,以决定是否触发更新操作。

6、updated:

适用场景:在数据更新后被调用,不会触发首次渲染。

示例:可以在这个阶段执行一些与数据更新相关的操作,例如重新计算或重新渲染。

7、beforeDestroy:

适用场景:在组件销毁之前被调用,通常用于清理工作、取消事件监听器、销毁定时器等。

示例:可以在这个阶段进行资源释放、取消订阅或清理定时器,以避免内存泄漏。

8、destroyed:

适用场景:在组件销毁完成后被调用,事件监听器和子组件已被销毁。

示例:可以在这个阶段执行一些最后的清理工作,如释放资源、断开连接等。

最后修改:2023 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏