Vue-admin-template模板使用

vue-admin-template模板

谷粒学苑项目框架

image-20230324182411190

框架入口

index.htmlsrc/main.js

框架作用

对 Vue 和 Element-ui 的封装

配置目录

index.js 文件

'use strict'
const path = require('path')
module.exports = {
    dev: {
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        proxyTable: {},
        host: 'localhost', 
        port: 9528, 
        autoOpenBrowser: true,
        errorOverlay: true,
        notifyOnErrors: false,
        poll: false, 
        // 这里修改为false, 不需要使用这么严格的Eslint检查
        useEslint: false,
        showEslintErrorsInOverlay: false,
        devtool: 'cheap-source-map',
        cssSourceMap: false
    },

    build: {
        index: path.resolve(__dirname, '../dist/index.html'),
        assetsRoot: path.resolve(__dirname, '../dist'),
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        productionSourceMap: false,
        devtool: 'source-map',
        productionGzip: false,
        productionGzipExtensions: ['js', 'css'],
        bundleAnalyzerReport: process.env.npm_config_report || false,
        generateAnalyzerReport: process.env.npm_config_generate_report || false
    }
}

dev.env.js 开发环境配置

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
    NODE_ENV: '"development"',
    // BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
    // 修改BASE_API, 使得可以登录成功
    BASE_API: '"http://localhost:8001"',
})

工具目录

request.js 文件中通过将 axios 封装成

import axios from 'axios'
import {Message, MessageBox} from 'element-ui'
import store from '../store'
import {getToken} from '@/utils/auth'

// 创建axios实例
const service = axios.create({
    baseURL: process.env.BASE_API, // api 的 base_url
    timeout: 5000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(
    config => {
        if (store.getters.token) {
            config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
        }
        return config
    },
    error => {
        // Do something with request error
        console.log(error) // for debug
        Promise.reject(error)
    }
)

// response 拦截器
service.interceptors.response.use(
    response => {
        /**
         * code为非20000是抛错 可结合自己业务进行修改
         */
        const res = response.data
        if (res.code !== 20000) {
            Message({
                message: res.message,
                type: 'error',
                duration: 5 * 1000
            })

            // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
            if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
                MessageBox.confirm(
                    '你已被登出,可以取消继续留在该页面,或者重新登录',
                    '确定登出',
                    {
                        confirmButtonText: '重新登录',
                        cancelButtonText: '取消',
                        type: 'warning'
                    }
                ).then(() => {
                    store.dispatch('FedLogOut').then(() => {
                        location.reload() // 为了重新实例化vue-router对象 避免bug
                    })
                })
            }
            return Promise.reject('error')
        } else {
            return response.data
        }
    },
    error => {
        console.log('err' + error) // for debug
        Message({
            message: error.message,
            type: 'error',
            duration: 5 * 1000
        })
        return Promise.reject(error)
    }
)

export default service

vue.config.js

配置文件, 修改访问端口地址

  • src目录

    • api目录

      自定义方法

    • asset目录

      静态资源

    • components目录

      存放组件

    • icons目录

      存放图标

    • router目录

      路由

    • style目录

      样式文件

    • utils目录

      request文件

    • views目录

      项目中的具体页面

@RestController表示交给Spring管理, 并返回数据

操作流程

  1. 修改vue.config.js中的端口号为8001

添加路由

  1. 在router/index.js中添加
  2. title属性决定页面的标题
  3. component表示点击路由跳转到指定页面

讲师列表的前端实现

添加路由

  1. router/index.js中模仿样例改造一级路由讲师管理和二级路由

  2. 创建路由对应的页面并修改router/index.js中对应的路由映射路径

    1. 讲师列表views/edu/teacher/list.vue
    2. 添加讲师views/edu/teacher/save.vue
  3. vue页面的基本写法

     
  4. api文件夹中创建teacher.js, 在其中定义接口的访问地址

     // 引入utils/request, request中封装了axios
     import request from '@/utils/request'
    
     export function getList(params) {
       return request({
         url: '/vue-admin-template/table/list',
         method: 'get',
         params
       })
     }

vue-admin-template 项目改造

后端请求路径改造

vue-admin-template 默认使用 https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin/user/login 地址进行请求,需要修改 dev.env.js 中的 BASE_API 属性

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
    NODE_ENV: '"development"',
    BASE_API: '"http://localhost:8001"',
})

页面改造

image-20230323003906516

讲师管理模块页面改造

路由设置

    // src/router/index.js

    // 侧边栏, 主体框架
    {
        path: '/teacher',
        component: Layout,
        // 重定向到子路由的路径上, 相当于这一个分支的默认展示页面
        redirect: '/teacher/table',
        name: '讲师管理',

        meta: {
            // title 的修改会反映到对应页面上
            title: '讲师管理',
            // icon 是 src/icons/svg 目录下存储的矢量图片
            icon: 'user'
        },
        children: [
            {
                // 子路由的路径: /teacher/table
                path: 'table',
                name: '讲师列表',
                // '@/views/edu/teacher/table' 这些页面从现有的模板中进行拷贝, 或者自己写页面
                component: () => import('@/views/edu/teacher/table'),
                meta: {
                    title: '讲师列表',
                    icon: 'table'
                }
            },

            {
                // 子路由的路径: /teacher/tree
                path: 'tree',
                name: '讲师层次',
                component: () => import('@/views/edu/teacher/tree'),
                meta: {
                    title: '讲师层次',
                    icon: 'tree'
                }
            },

            {
                path: '/add',
                name: '添加讲师',
                component: () => import('@/views/edu/teacher/add'),
                meta: {
                    title: '添加讲师',
                    icon: 'form'
                }
            },
        ]
    },

页面设置

image-20230323005923169

效果展示

image-20230323005958836

后台的前端项目开发

login 登录业务

前端接口修改(src/api/login.js)

import request from '@/utils/request'

export function login(username, password) {
    return request({
        url: '/edu/user/login',
        method: 'post',
        data: {
            username,
            password
        }
    })
}

export function getInfo(token) {
    return request({
        url: '/edu/user/info',
        method: 'get',
        params: {token}
    })
}

export function logout() {
    return request({
        url: '/edu/user/logout',
        method: 'post'
    })
}

后端接口修改

package com.xiong.controller;

import com.xiong.Result;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;


/**
 * RestController注解: 表示该类交给Spring管理并返回数据
 * <p>
 * CrossOrigin注解: 用来解决跨域问题
 * <p>
 * 这里的逻辑纯粹是为了迎合前端vue-admin-template模板中的要求
 */
@RestController
@RequestMapping("/edu/user")
@Api(description = "登录管理")
@CrossOrigin
public class EduLoginController {

    @PostMapping("login")
    public Result login() {
//        "token"是由于vue-admin-template模板登录功能store/user中使用的token
        return Result.ok()
                .setData("token", "admin");
    }

    @GetMapping("info")
    public Result info() {
//        这里的name和avatar应该和前端关键字相同吧
        return Result.ok()
                .setData("roles", "[admin]")
                .setData("name", "admin")
                .setData("avatar", "https://gw.alicdn.com/i4/710600684/O1CN01bNcLnV1GvJd3wK1k2_!!710600684.jpg_Q75.jpg_.webp");
    }
}

分页查询业务

后端接口

    @ApiOperation(value = "带条件的分页查询")
    @PostMapping("pageTeacherCondition/{current}/{limit}")
    public Result pageTeacherCondition(@ApiParam(name = "current", value = "当前页", required = true) @PathVariable long current,
                                       @ApiParam(name = "limit", value = "每页最多个数", required = true) @PathVariable long limit,
                                       @RequestBody(required = false) TeacherQuery teacherQuery) {
        QueryWrapper<Teacher> wrapper = null;

        if (teacherQuery != null) {
//            构建查询条件
            wrapper = new QueryWrapper<Teacher>();
//            多条件组合查询, 相当于mybatis中的动态sql
            String name = teacherQuery.getName();
            Integer level = teacherQuery.getLevel();
            String begin = teacherQuery.getBegin();
            String end = teacherQuery.getEnd();
//            拼接上下面的查询条件
            if (name != null) {
                wrapper.like("name", name);
            }
            if (level != null) {
                wrapper.eq("level", level);
            }
            if (begin != null) {
                wrapper.ge("gmt_create", begin);
            }
            if (end != null) {
                wrapper.le("gmt_modified", end);
            }
//        对数据进行排序, 按照创建时间倒序排序
            wrapper.orderByDesc("gmt_create");
        }
        //current和limit的参数类型是根据代码内部逻辑确定的, 而不是固定的String类型
        Page<Teacher> teacherPage = new Page<>(current, limit);


        //将数据封装到teacherPage对象中
        teacherService.page(teacherPage, wrapper);

//        获取返回数据的总条数
        long total = teacherPage.getTotal();
//        获取每一条返回数据
        List<Teacher> records = teacherPage.getRecords();

        return Result.ok().setData("total", total).setData("rows", records);
    }

前端接口

import request from '@/utils/request'

// 导出为一个对象(例如为teacher), 可以通过teacher.pageQuery来调用方法, 不需要import {pageQuery, add, logout} from '@/api/edu/teacher'
export default {
    pageQuery(current, pageSize, queryObject) {
        return request({
            // 通过路径进行传递值
            url: `/edu/teacher/pageTeacherCondition/${current}/${pageSize}`,
            method: 'post',
            data: {
                // data 的作用是将对象转为json
                // 通过 post 请求体进行传递值, 对应后端的 @RequestBody (将json数据变为对象)
                queryObject
            }
        })
    },


    /**
     * 添加一位教师
     * @param teacherObject 待添加的teacher对象
     */
    add(teacherObject) {
        return request({
            url: '/edu/teacher/add',
            method: 'post',
            data: {
                teacherObject
            }
        })
    },

    logout() {
        return request({
            url: '/edu/user/logout',
            method: 'post'
        })
    }
}

前端页面调用接口

前端页面内容

表单组件

分页组件

    

条件查询组件

删除业务

存在的问题

删除无法自动刷新,执行删除操作后需要手动进行一次刷新或者执行两次删除操作,才能看到删除效果

修改业务

上传图片业务

图片上传到阿里云OSS进行存储

如果想保存到自己本地的服务器该如何操作呢?这里只是图片上传功能,那通用的文件上传功能是如何实现的呢?

课程分类业务(难点)

分类任务建模

数据库中通过id和parentId进行实现

一级分类的parentId为默认的0

二级分类的parentId为一级分类的id

三级分类的parentId为二级分类的id

依此类推

课程分类

id parentId
10(后端) 0(技术)
11(前端) 0(技术)
100(Java) 10
150(JavaScript) 11

EsayExcel 实现写操作

EsayExcel 实现读操作

文件上传功能

多级别的分类

多级选择框的联动问题:例如省市县

可以返回一个map,其中key对应id(准确说是包含id,可能在前端还需要显示该对象的其他属性,往往显示的不是id属性),而value是一个子类别的列表。

这样在前端进行一级分类指定id后,可以获取该一级分类下的所有二级分类;同样在指定二级分类id后,可以获取三级分类列表,以此类推

对于省市县这种固定的分类和不固定的分类是否需要分别考虑,固定的分类直接写死避免查询数据库吗?还是有什么其他的优化?存疑

课程管理业务

课程添加过程需求分析

image-20230324182715975

image-20230324191352674

Vo 和 Po 问题

  1. 课程添加页面中包含各种信息,而这些信息分布在多张表中(edu_course 和 edu_course_description),因此需要创建一个 XXXVo 类来接收前端传入的对象,而 XXXPo 类则是和数据库表对应。(XXXPo 和 XXXVo 在简单的场景下可能是相同的,此时就没有必要额外创建一个 XXXVo 类)
  2. 同样由于 XXXVo 类和 XXXPo 类不完全相同,因此接收到的 XXXVo 需要将信息拆分添加到多张表中
  3. 所属讲师所属分类 在前端限制成下拉列表的形式,限制域的取值范围。所属分类是多级分类,需要实现多级联动效果(省市县)。

添加课程业务中存在的问题

添加课程在前端是三个阶段的过程:编辑基础信息、添加课程大纲、最终发布。但是目前在编辑基础信息的时候就将课程数据保存到数据库中,如果用户在执行到第二个阶段或第三个阶段希望取消,那该如何撤销之前保留的数据?

三个阶段之间的数据不能够回显,如何做到数据回显的效果?

解决思路:第一个阶段执行完成后,保存到数据库,会自动生成课程id,此时需要获取该课程的id,传递给第二、三阶段进行使用。数据回显也可以基于课程id来实现。

XXXVo 对象中不保存id,因此需要一个额外的属性来记录id值,该id值在第一次添加时由数据库生成并返回给前端进行记录,在进行修改时,前端需要通过该值来对数据库进行访问。类似cookie的作用。

视频点播能力(阿里云)

上传、自动化转码(普通视频转高清,怎么实现的呢?)、媒体资源管理、分发加速

这些功能如果需要存储在本地该如何实现呢?即老板愿意买磁盘,但是不愿意买阿里云的服务来存储视频。难道必须存储在阿里云,还是应该学点别的。

上传视频

数据库中不存储视频的地址,因为对于加密视频而言,不能通过视频地址直接播放,所以数据库中存储视频id。通过视频id来获取视频播放地址和凭证,再通过凭证来判断是否有播放(访问)权限。

删除视频

存放在阿里云的这种视频,如何保证事务一致性呢?会不会出现本地数据库中视频id被删除,但是实际阿里云存储的视频没有删除成功(先删除id,后删除视频会产生这个问题)。但先删除视频,后删除视频id,如果阿里云那边成功删除视频,但是本地删除数据库中的视频id时发生错误。这样导致本地数据库回滚,而阿里云中无法回滚,那该怎么办呢?

批量删除

在Controller中通过List来接收多个视频id,在阿里云存储视频的方式中,需要将List中的值拼接成一个以逗号分隔的字符串(因此用别人的接口,按照别人的要求)。这个需求可以使用Spring提供的工具类 StringUtils 中的 join() 方法

删除多个视频的时候,阿里云是如何保证事务的呢?会出现成功删除一部分,另外一部分删除失败吗?或者某个视频删除了一半?感觉删除还是先逻辑删除,确保业务操作简单,之后再在某个空闲的时候统一删除会更好。

播放视频

SpringCloud 微服务框架

模块和模块之间是相互独立的,一个模块引入另一个模块不属于微服务架构。因此微服务架构需要考虑微服务和微服务之间的相互调用问题(RPC)

SpringCloud 在接口调用上,会经过几个组件的配合:

  1. Feign 接口化请求调用

    将restTemplate直接硬编码的请求地址给转化成接口的形式,Feign 会根据指定的服务名去服务注册中心中查找服务地址,然后向那个微服务发送调用请求

    @Component
    @FeignClient("${nacos-service.vod}")
    public interface VodClient {
    
        @GetMapping("/edu/vod/test/{id}")
        String getVodServiceTest(@PathVariable("id") String id);
    }
  2. Hystrix 服务熔断

    通过在当前微服务模块中简单实现一个 VodClient 接口的实现类,作为VodClient 接口远程调用失败时的服务降级方案。对上面的接口进行一些简单修改,在@FeignClient注解中添加上 fallback = VodClientImpl.class 即可

    @Component
    @FeignClient(value = "${nacos-service.vod}", fallback = VodClientImpl.class)
    public interface VodClient {
    
        @GetMapping("/edu/vod/test/{id}")
        String getVodServiceTest(@PathVariable("id") String id);
    }
    @Component
    @ResponseBody
    public class VodClientImpl implements VodClient {
    
        // Hystrix 服务降级
        // todo: 为什么这里会经过视图解析器, 不是添加了@ResponseBody吗? 这是IDEA的小bug
        @Override
        public String getVodServiceTest(String id) {
            return "服务熔断";
        }
    }
  1. Ribbon 负载均衡

    如果被调用的微服务有多个,那么会将请求通过负载均衡策略分配到该微服务的实例

  2. Http Client / OkHttp 进行http请求调用

前台的前端项目开发

NUXT框架

AJAX 请求:缺点是不利于SEO,不利于爬虫(百度、谷歌)的排名

服务端渲染技术

注意需要通过 nvm use 14 来切换成 node 14.x 版本,再通过 npm run dev 来启动nuxt项目

目录介绍

  • assets:一般存放静态资源,例如css、js、img等
  • component:存放项目相关组件,例如上传功能组件
  • layouts

整合前台系统页面

首页显示banner数据(轮播图或幻灯片)

首页显示热门课程和名师

首页数据使用 Redis 进行缓存

Maven 加载机制

mapper.xml 文件应该存放在resource目录下的mapper包中(这些名称都是默认指定的,不要修改为别的名称,否则容易产生mapper文件找不到的问题)


   转载规则


《Vue-admin-template模板使用》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Redis高级特性 Redis高级特性
Redis 高级特性IO 多路复用分布式锁单 Redis 实例做分布式锁(setnx)这里的单 Redis 实例并不是指单机版的 Redis,这里可以是 Redis 集群,也可以是 Redis 主从哨兵。之所以称之为单Redis实例分布式锁
2023-03-26
下一篇 
IDEA配置使用 IDEA配置使用
IDEA 配置编码配置 自动导包配置 提示忽略大小写配置 Java 编译级别设置 取消单行显示标签页 启用注解处理器 IDEA 使用配置远程连接Linux服务器 查看类继承图 使用右键可以添加属性字段等信息 使用F4可以直接跳转进入到图
2023-03-22
  目录