基于Springboot+Vue实现前后端分离商城管理系统

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 基于Springboot+Vue实现前后端分离商城管理系统

项目编号:BS-SC-030

一,项目简介

  新新商城,一款基于 Springboot+Vue 的电商项目,前后端分离项目。完整的实现了一个商城系统应有的基本功能,包括但不限于以下主要功能模块:

前端商城用户

  1. 用户注册登陆
  2. 商品信息分类和品牌浏览
  3. 全文搜索
  4. 添加购物车管理
  5. 在线购买商品:使用支付宝沙箱在线支付
  6. 个人信息管理
  7. 个人订单管理
  8. 在线退换货功能
  9. 退款功能

后台用户管理功能

  1. 商品分类管理
  2. 商品品牌管理
  3. 商品规格管理
  4. 商品采购管理
  5. 供应商管理
  6. 订单管理
  7. 退货退款管理
  8. 轮播图设置管理
  9. 用户管理
  10. 权限角色管理
  11. 个人信息管理

     项目后台基于Springboot+MybatisPlus开发实现,前端使用VUE+Element开发实现,前后端分离开发,前端通过调用后台接口来进行相应的交互处理。

     亮点技术:短信发送验证码、阿里云OSS云存储商品图片、邮箱自动发邮件验证操作权限,Shiro权限管理,数据加密处理,支付宝沙箱技术应用,Redis数据缓存处理。

   项目功能完整,界面优雅大方,人机交互流畅,是一个难得的毕业设计作品。

二,环境介绍

语言环境:Java:  jdk1.8

数据库:Mysql: mysql5.7  Redis:5.0.10

应用服务器:Tomcat:  tomcat8.5.31

开发工具:IDEA或eclipse

技术应用:

后端技术

技术

说明

官网

SpringBoot

容器+MVC框架

https://spring.io/projects/spring-boot

Shiro

认证和授权框架

Apache Shiro Simple. Java. Security.

MyBatis

ORM框架

http://www.mybatis.org/mybatis-3/zh/index.html

MySQL

数据库

https://www.mysql.com/

Redis

分布式缓存

https://redis.io/

Druid

数据库连接池

https://github.com/alibaba/druid

前端技术

技术

说明

官网

Vue

前端框架

https://vuejs.org/

Vue-router

路由框架

https://router.vuejs.org/

Vuex

全局状态管理框架

https://vuex.vuejs.org/

Element

前端UI框架

https://element.eleme.io

Axios

前端HTTP框架

https://github.com/axios/axios

vue-clipboard2

将内容复制到剪贴板

https://github.com/Inndy/vue-clipboard2

vuex-persistedstate

vuex持久化

https://www.npmjs.com/package/vuex-persistedstate

nprogress

进度条控件

https://github.com/rstacruz/nprogress

开发环境

第三方技术

工具

官网

支付宝沙箱技术

沙箱环境 | 开放平台

OSS 存储

阿里云-为了无法计算的价值

阿里云短信服务

阿里云-为了无法计算的价值

QQ邮箱服务

登录QQ邮箱

三,系统展示

下面展示一下项目的主要核心功能:

前端用户操作

用户注册:会对邮箱发验证码验证,要求必须真实有效邮箱

用户登陆

首页

商品购买

购物车

结算付款

我的订单:可以申请退款、收货后可以申请退货等操作

后台管理用户功能

后台首页

商品管理:可以实现商品添加、编辑、删除、下架、查询等,另可以对商品分类、品牌、规则、采购信息进行管理,以及对供应商进行管理,不再一一展示。菜单上的功能都是齐全的。

订单管理:可以进行发货、退货、退款等相关操作

营销管理

主要对前端展示的广告轮播图进行管理

用户和权限管理:可以对管理员、顾客、角色进行管理操作

四,核心代码展示

package com.qiu.controller;
import com.qiu.entity.Banner;
import com.qiu.service.BannerService;
import com.qiu.util.general.CommonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @author ZNZ
 * @email 469603589@qq.com
 * @date 2022/12/31 16:23
 * @description 商品类别
 */
@CrossOrigin
@RestController
public class BannerController {
    @Autowired
    private BannerService bannerService;
    @RequestMapping(value = "/banner/add")
    public CommonResult addBanner(Banner banner) {
        if (bannerService.insertData(banner)) {
            return CommonResult.success("商品轮播图添加成功", banner);
        }
        return CommonResult.error("商品轮播图添加失败");
    }
    @RequestMapping(value = "/banner/update")
    public CommonResult updateType(Banner banner) {
        if (bannerService.updateById(banner)) {
            return CommonResult.success("商品轮播图修改成功", banner);
        }
        return CommonResult.error("商品轮播图修改失败");
    }
    @RequestMapping(value = "/banner/deleteById")
    public CommonResult deleteTypeById(Integer bannerId) {
        if (bannerService.deleteById(bannerId)) {
            return CommonResult.success("商品轮播图删除成功", "bannerId: " + bannerId);
        }
        return CommonResult.error("商品轮播图删除失败");
    }
    @RequestMapping(value = "/banner/findAll")
    public CommonResult findAllType() {
        List<Banner> banners = bannerService.selectAll();
        if (banners != null) {
            return CommonResult.success("商品轮播图查询成功", banners);
        }
        return CommonResult.error("商品轮播图查询失败");
    }
    @RequestMapping(value = "/banner/findById")
    public CommonResult findById(Integer bannerId) {
        Banner banner = bannerService.selectById(bannerId);
        if (banner != null) {
            return CommonResult.success("商品轮播图查询成功", banner);
        }
        return CommonResult.error("商品轮播图查询失败");
    }
}
package com.qiu.controller;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import com.qiu.constant.UserStatusEnum;
import com.qiu.entity.Role;
import com.qiu.entity.User;
import com.qiu.service.RoleService;
import com.qiu.service.UserService;
import com.qiu.util.general.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * @author ZNZ
 * @email 469603589@qq.com
 * @date 2022/12/6 17:10
 * @description 登录、退出、修改个人信息等业务操作
 */
@Slf4j
@CrossOrigin
@RestController
public class OperateController {
    private static final String EMAIL_FLAG = "@";
    @Autowired
    private RoleService roleService;
    @Autowired
    private UserService userService;
    /**
     * 登录操作
     *
     * @param username 用户登录的账号
     * @param password 用户登录的密码
     */
    @RequestMapping(value = "/login", produces = {"application/json;charset=UTF-8"})
    public CommonResult doLogin(String username, String password) {
        User user;
        if (username.contains(EMAIL_FLAG)) {
            //包含@符号,代表用户通过邮箱账号登录
            user = userService.selectByKey(username);
        } else {
            //不包含@符号,代表用户通过手机号登录
            user = userService.selectByPhone(username);
        }
        if (user == null) {
            return CommonResult.error("账号不存在");
        }
        String encodePassword = SaSecureUtil.md5BySalt(password, user.getAccountNumber());
        if (!encodePassword.equals(user.getPassword())) {
            return CommonResult.error("用户名或密码错误");
        }
        // 账号被锁定
        if (!user.getUserState()) {
            StpUtil.disable(user.getUserId(), -1);
        }
        StpUtil.login(user.getUserId());
        //更新最后登录时间
        user.setLoginTime(new Date());
        userService.updateById(user);
        //存放用户信息
        Map<String, Object> info = new HashMap<>(4);
        info.put("user", user);
        //存放sessionId, 即 token
        info.put("sessionId", StpUtil.getTokenInfo().getTokenValue());
        List<Role> roles = userService.getRoleList(user.getUserId());
        Set<String> roseNames = roles.stream().map(Role::getRoleName).collect(Collectors.toSet());
        Set<String> roseDescribes = roles.stream().map(Role::getRoleDescribe).collect(Collectors.toSet());
        info.put("role", roseNames);
        info.put("roleInfo", roseDescribes);
        return CommonResult.success("登录成功", info);
    }
    /**
     * 注销登录
     */
    @RequestMapping(value = "/logout")
    public CommonResult logout() {
        StpUtil.logout();
        return CommonResult.success("注销成功");
    }
    /**
     * 判断key是否存在   目前用于判断邮箱是否被注册过
     *
     * @param email 邮箱号(账号)
     */
    @RequestMapping(value = "/allow/existUser")
    public CommonResult existUser(String email) {
        Boolean isExist = userService.existsWithPrimaryKey(email);
        if (isExist != null) {
            return CommonResult.success("查询成功", isExist);
        }
        return CommonResult.error("查询失败");
    }
    /**
     * 判断手机号phone是否存在  目前被用于绑定手机号时,确认手机号已被绑定
     *
     * @param telephone 手机号
     */
    @RequestMapping(value = "/allow/existPhone")
    public CommonResult existPhone(String telephone) {
        Boolean isExist = userService.existsWithPrimaryPhone(telephone);
        if (isExist != null) {
            return CommonResult.success("手机号查询成功", isExist);
        }
        return CommonResult.error("手机号查询失败");
    }
    /**
     * 重置密码、找回密码
     *
     * @param account  账号
     * @param password 密码
     */
    @RequestMapping(value = "/allow/resetpwd")
    public CommonResult resetPwd(String account, String password) {
        if (account != null && password != null) {
            String encodePassword = SaSecureUtil.md5BySalt(password, account);
            Integer id = userService.selectIdByKey(account);
            User user = new User();
            user.setUserId(id);
            user.setPassword(encodePassword);
            if (userService.updateById(user)) {
                return CommonResult.success("重置密码成功", user);
            }
            return CommonResult.error("重置密码失败");
        }
        return CommonResult.error("用户数据不存在");
    }
    /**
     * 注册新用户
     *
     * @param user 用户信息
     */
    @RequestMapping(value = "/allow/add")
    public CommonResult add(User user) {
        if (user.getPassword() != null && user.getUserName() != null) {
            String encodePassword = SaSecureUtil.md5BySalt(user.getPassword(), user.getAccountNumber());
            user.setPassword(encodePassword);
            user.setUserState(true);
            user.setStatus(UserStatusEnum.CUSTOMER);
            if (userService.insertData(user)) {
                log.info("用户添加成功,用户信息:{}", user);
                return CommonResult.success("注册成功", user);
            } else {
                return CommonResult.error("注册失败");
            }
        }
        return CommonResult.error("用户数据不存在");
    }
    /**
     * 更新用户信息
     *
     * @param user 用户信息
     */
    @RequestMapping(value = "/allow/update")
    public CommonResult update(User user) {
        if (user.getUserState() != null && user.getUserState()) {
            StpUtil.untieDisable(user.getUserId());
        }
        if (userService.updateById(user)) {
            return CommonResult.success("信息保存成功", user);
        }
        return CommonResult.error("信息保存失败");
    }
}
package com.qiu.controller;
import com.alibaba.fastjson.JSON;
import com.qiu.entity.Logistics;
import com.qiu.entity.Order;
import com.qiu.entity.Product;
import com.qiu.service.LogisticsService;
import com.qiu.service.OrderService;
import com.qiu.service.ProductService;
import com.qiu.util.general.CommonResult;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * @author ZNZ
 * @email 469603589@qq.com
 * @date 2022/12/28 18:11
 * @description 订单相关业务
 */
@RestController
@CrossOrigin
public class OrderController {
    private static final String VIP = "Vip";
    private static final String COLLECT_GOODS_STATE = "已收货";
    @Autowired
    private OrderService orderService;
    @Autowired
    private ProductService productService;
    @Autowired
    private LogisticsService logisticsService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @RequestMapping(value = "/order/findById")
    public CommonResult findOrderById(Integer orderId) {
        Order order = orderService.selectById(orderId);
        if (orderId != null) {
            return CommonResult.success("订单信息查询成功", order);
        }
        return CommonResult.error("订单信息查询失败");
    }
    @RequestMapping(value = "/order/findOrderInfo")
    public CommonResult findOrderInfo(String userAccount) {
        List<Map<String, Object>> orderMap = orderService.selectAllOrder(userAccount);
        if (orderMap != null) {
            return CommonResult.success("订单信息查询成功", orderMap);
        }
        return CommonResult.error("订单信息查询失败");
    }
    @RequestMapping(value = "/order/findAll")
    public CommonResult findAllOrder() {
        List<Order> orders = orderService.selectAll();
        if (orders != null) {
            return CommonResult.success("订单信息查询成功", orders);
        }
        return CommonResult.error("订单信息查询失败");
    }
    @RequestMapping(value = "/order/findCount")
    public CommonResult findCount() {
        Integer count = orderService.selectCount();
        if (count != null) {
            return CommonResult.success("订单数量查询成功", count);
        }
        return CommonResult.error("订单数量查询失败");
    }
    @RequestMapping(value = "/order/add")
    public CommonResult addOrder(Order order) {
        if (order != null) {
            if (order.getProductNo().contains(VIP)) {
                return handleMemberOrders(order);
            }
            return handleMerchandiseOrders(order);
        } else {
            return CommonResult.error("订单数据不完整");
        }
    }
    @RequestMapping(value = "/order/cartOrder")
    public CommonResult cartOrder(String orderNo, String ordersInfo, String cartIds) {
        List<String> cartIdList = JSON.parseArray(cartIds, String.class);
        List<Order> orders = JSON.parseArray(ordersInfo, Order.class);
        if (orders != null) {
            ArrayList<String> orderInfo = new ArrayList<>();
            ArrayList<String> productInfo = new ArrayList<>();
            for (Order order : orders) {
                Product product = productService.selectByKey(order.getProductNo());
                Integer productStock = product.getProductStock();
                Integer payAmount = order.getPayAmount();
                if (productStock >= payAmount) {
                    Product newProduct = new Product();
                    newProduct.setProductId(product.getProductId());
                    int newStock = productStock - payAmount;
                    newProduct.setProductStock(newStock);
                    newProduct.setIsStockOut(newStock < product.getLowestStock());
                    // 如果库存小于等于0,自动下架
                    newProduct.setIsSale(newStock > 0);
                    if (productService.updateById(newProduct) && orderService.insertData(order)) {
                        orderInfo.add(order.getOrderNo());
                        productInfo.add(order.getProductNo());
                    }
                }
            }
            if (!orderInfo.isEmpty()) {
                String cartIdsInfo = StringUtils.join(cartIdList.toArray(), ",");
                String orderNoInfo = StringUtils.join(orderInfo, ",");
                String productNoInfo = StringUtils.join(productInfo, ",");
                redisTemplate.opsForValue().set(orderNo, orderNoInfo, 24, TimeUnit.HOURS);
                redisTemplate.opsForValue().set("cartId" + orderNo, cartIdsInfo, 24, TimeUnit.HOURS);
                return CommonResult.success("创建订单成功", productNoInfo);
            }
            return CommonResult.error("创建订单失败,请查看商品库存是否满足购买数量");
        } else {
            return CommonResult.error("订单数据不完整");
        }
    }
    @RequestMapping(value = "/order/update")
    public CommonResult updateOrder(Order order) {
        if (orderService.updateById(order)) {
            return CommonResult.success("修改订单成功", order);
        }
        return CommonResult.error("修改订单失败");
    }
    @RequestMapping(value = "/order/delete")
    public CommonResult deleteOrder(Integer orderId) {
        if (orderService.deleteById(orderId)) {
            return CommonResult.success("删除订单成功", "订单id:" + orderId);
        }
        return CommonResult.error("删除订单失败");
    }
    @RequestMapping(value = "/order/receipt")
    public CommonResult updateOrder(Integer orderId) {
        Order order = new Order();
        order.setOrderId(orderId);
        order.setOrderState(COLLECT_GOODS_STATE);
        if (orderService.updateById(order)) {
            return CommonResult.success("商品收货成功", order);
        }
        return CommonResult.error("商品收货失败");
    }
    @RequestMapping(value = "/orderDetail/orderInfo")
    public CommonResult orderInfo(String orderNo) {
        ArrayList<Object> resultList = new ArrayList<>();
        Order order = orderService.selectByKey(orderNo);
        Logistics logistics = logisticsService.selectOrderNo(orderNo);
        if (order != null) {
            resultList.add(order);
        }
        if (logistics != null) {
            resultList.add(logistics);
        }
        return CommonResult.success("订单详情查询成功", resultList);
    }
    /**
     * 处理会员订单
     *
     * @param order 订单信息
     */
    private CommonResult handleMemberOrders(Order order) {
        if (orderService.insertData(order)) {
            return CommonResult.success("创建订单成功", order);
        } else {
            return CommonResult.error("创建订单失败");
        }
    }
    /**
     * 处理商品订单
     *
     * @param order 订单信息
     */
    private CommonResult handleMerchandiseOrders(Order order) {
        Product product = productService.selectByKey(order.getProductNo());
        Integer productStock = product.getProductStock();
        Integer payAmount = order.getPayAmount();
        boolean isOk = productStock >= payAmount;
        if (isOk) {
            Product newProduct = new Product();
            newProduct.setProductId(product.getProductId());
            int newStock = productStock - payAmount;
            newProduct.setProductStock(newStock);
            newProduct.setIsStockOut(newStock < product.getLowestStock());
            // 如果库存小于等于0,自动下架
            newProduct.setIsSale(newStock > 0);
            if (productService.updateById(newProduct)) {
                if (orderService.insertData(order)) {
                    redisTemplate.opsForValue().set(order.getOrderNo(), order.getOrderNo(), 24, TimeUnit.HOURS);
                    return CommonResult.success("创建订单成功", order);
                } else {
                    return CommonResult.error("创建订单失败");
                }
            } else {
                return CommonResult.error("创建订单失败");
            }
        } else {
            return CommonResult.error("商品库存不足");
        }
    }
}

五,项目总结

 项目后台基于Springboot+MybatisPlus开发实现,前端使用VUE+Element开发实现,前后端分离开发,前端通过调用后台接口来进行相应的交互处理。

     亮点技术:短信发送验证码、阿里云OSS云存储商品图片、邮箱自动发邮件验证操作权限,Shiro权限管理,数据加密处理,支付宝沙箱技术应用,Redis数据缓存处理。

   项目功能完整,界面优雅大方,人机交互流畅,是一个难得的毕业设计作品。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
32 13
|
11天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
XML Java 数据库连接
SpringBoot集成Flowable:打造强大的工作流管理系统
在企业级应用开发中,工作流管理是一个核心组件,它能够帮助我们定义、执行和管理业务流程。Flowable是一个开源的工作流和业务流程管理(BPM)平台,它提供了强大的工作流引擎和建模工具。结合SpringBoot,我们可以快速构建一个高效、灵活的工作流管理系统。本文将探讨如何将Flowable集成到SpringBoot应用中,并展示其强大的功能。
139 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
69 2
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
40 0
|
21天前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
23天前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
|
25天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
28 1
vue学习第一章
|
25天前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
24 1
vue学习第三章
|
25天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
35 1
vue学习第四章