菜单是单独定义的一个组件,本项目的菜单只有一级,如果需要定义多级菜单,可参照 《5.3.1》小节的实现。在 components 目录下新建 Menus.vue 。如下:
Menus.vue
这个组件比较简单,都是静态代码。由于本项目只是用于演示基于 Vue 前端开发涉及的各个功能的实现,所以暂时只提供了首页和新书菜单的实现,其它 3 个(特价书、教材、视听教程)功能的实现是类似的,只需要服务端提供相应的接口即可。
首页和新书菜单组件渲染的位置(即 router-view)在 App.vue 中指定。App.vue 的代码如下:
App.vue
本项目没有用到嵌套路由,所有页面级路由组件的渲染都是在这里。换句话说,即所有渲染的页面都有头部和菜单。
图书分类组件用于显示商品的分类,每个分类都是一个链接,单击链接将跳转到展示该分类下所有商品的页面。
在 components 目录下新建 HomeCategory.vue。如下:
HomeCategory.vue
图书分类
{{ category.name }}
{{ child.name}}
在 created 生命周期钩子中向服务端请求所有分类数据。服务端提供的该数据的内容如下:
http://111.229.37.167/api/category
[{"id": 1,"name": "Java EE","root": true,"parentId": null,"children": [{"id": 3,"name": "Servlet/JSP","root": false,"parentId": 1,"children": []}, {"id": 4,"name": "应用服务器","root": false,"parentId": 1,"children": []}, {"id": 5,"name": "MVC框架","root": false,"parentId": 1,"children": []}]
}, {"id": 2,"name": "程序设计","root": true,"parentId": null,"children": [{"id": 6,"name": "C/C++","root": false,"parentId": 2,"children": [{"id": 9,"name": "C11","root": false,"parentId": 6,"children": []}]}, {"id": 7,"name": "Java","root": false,"parentId": 2,"children": []}, {"id": 8,"name": "C#","root": false,"parentId": 2,"children": []}]
}]
子分类是放到 children 数组属性中的,本项目中未用到 root 和 parentId 属性,前者可用于列出某个根分类下的所有商品,后者可以用于查找某个分类的父分类,甚至反向查找所有上级分类。
清楚了数据接口返回的数据结构,那么 HomeCategory 组件的代码也就清楚了。
广告图片轮播功能在电商网站属于标配的功能,其实是通过 JavaScript 代码控制图片的轮播,并处理一些控制图片显示的单击事件。
由于 Vue 3.0 推出的时间还不是特别长,之前 Vue 2.x 下的很多好用的图片轮播插件还没有移植到 Vue 3.0 下,如果自己编写一个成熟的图片轮播组件,又会增加本项目的复杂度,因此这里暂时先用一张静止的图片代替图片轮播。如果对图片轮播功能的实现有兴趣,可以在网上找到很多案例,将其封装为组件使用即可。(不过,此时网上应该已经有支持 Vue 3.0 的图片轮播插件了)。
在 components 目录下新建 HomeScrollPic.vue 。如下:
HomeScrollPic.vue
图片是保存在 public 目录下的,该目录下的资源直接通过根路径“/”引用即可。
热门推荐组件用于显示热门商品,用户如果对某一热门商品感兴趣,可以单击该商品链接,进入商品详情页面。
在 components 目录下新建 HomeBooksHot.vue 。如下:
HomeBooksHot.vue
热门推荐
{{ book.title }}{{ currency(factPrice(book.price, book.discount))}}
在 created 声明周期钩子中向服务端请求热门商品数据。服务端提供的该数据接口如下:
http://111.229.37.167/api/book/hot
返回的数据结构如下:
[{"id":1,"title":" VC++深入详解(第3版)","author":"孙鑫","price":168,"discount":0.95,"bookConcern":null,"imgUrl":"/api/img/vc++.jpg","bigImgUrl":"/api/img/vc++big.jpg","publishDate":null,"brief":null,"inventory":1000},{"id":2,"title":"Java编程思想","author":"Bruce Eckel","price":108,"discount":0.5,"bookConcern":null,"imgUrl":"/api/img/javathink.jpg","bigImgUrl":"/api/img/javathinkbig.jpg","publishDate":null,"brief":null,"inventory":500},{"id":3,"title":"C Primer Plus 第6版","author":"Stephen Prata","price":89,"discount":0.5,"bookConcern":null,"imgUrl":"/api/img/c++primer.jpg","bigImgUrl":"/api/img/c++primerbig.jpg","publishDate":null,"brief":null,"inventory":400},{"id":4,"title":"Servlet/JSP深入详解","author":"孙鑫","price":139,"discount":0.9,"bookConcern":null,"imgUrl":"/api/img/jsp.jpg","bigImgUrl":"/api/img/jspbig.jpg","publishDate":null,"brief":null,"inventory":1000}
]
实际上,热门推荐组件用不到全部信息,只是服务端的数据接口返回的数据就是如此,那么从这些数据中选择游泳的数据使用即可。
一般电商网站的商品有定价和实际销售价格,在前端展示商品的时候需要同时显示这两种价格。从这里返回的数据来看,服务端只提供了商品的定价和折扣,并没有实际销售价格,那么实际销售价格就需要我们自己来处理。这在实际开发中也很常见,不能期望服务端的开发人员专门为你(当然老板除外)的需求提供一个接口,也许还有其他前端也要用到该接口。
实际价格是定价与折扣相乘得到的,由于实际价格在多处要用到,因此编写一个单独的函数来计算价格。此外,还要考虑价格显示问题,价格只是显示到分就可以了,而在计算过程中,由于是浮点数,可能会出现小数点后两位之后的数据,所以要进行处理。除此之外,价格一般还会加上货币符号,如国内会加上人民币符号¥。为此,再编写一个函数,专门负责价格的格式化问题。
将这两个函数放到单独的 JS 文件中,在 src 目录下新建 utils 文件夹,在该文件夹下新建 util.js。如下:
util.js
const digitsRE = /(\d{3})(?=\d)/gexport function factPrice(value, discount) {value = parseFloat(discount);if (!discount) {return value}return value * discount;
}export function currency(value, currency, decimals) {value = parseFloat(value);if (!isFinite(value) || (!value && value !== 0)) {return ''}currency = currency != null ? currency : '¥';decimals = decimals != null ? decimals : 2;var stringified = Math.abs(value).toFixed(decimals);var _int = decimals ? stringified.slice(0, -1 - decimals) : stringified;var i = _int.length % 3;var head = i > 0 ? (_int.slice(0, i) + (_int.length > 3 ? ',' : '')) : '';var _float = decimals ? stringified.slice(-1 - decimals) : '';var sign = value < 0 ? '-' : '';return sign + currency + head + _int.slice(i).replace(digitsRE, '$1,') + _float
}
为了方便在各个组件内使用这两个函数,在 App 组件内通过 provide 选项向所有后代组件提供这两个函数。编辑 App.vue ,修改后的代码如下:
App.vue
之后记得在组件内使用 inject 选项注入这两个函数,如下:
inject:['factPrice','currency']
新书上市组件用于显示刚上市的商品,用户如果对某一商品感兴趣,可以单击该商品链接,进入商品详情页面。
在 components 目录下新建 BooksNew.vue,由于该组件会被复用,所以这里没有使用主页的前缀 Home。
BooksNew.vue
新书上市
![]()
{{ book.title }} {{ currency(factPrice(book.price, book.discount)) }}{{ currency(book.price) }}
在 created 生命周期钩子中向服务端请求新书的数据。服务端提供的该数据接口如下:
http://111.229.37.167/api/book/new
返回的数据形式同 /book/hot。
首页的各个组成部分编写完成后,就可以开始集成这几个部分了。首页作为页面级组件,放到 views 目录下。在 views 目录下新建 Home.vue 。如下:
src/views/Home.vue
Home 组件比较简单,只是用于拼接各个子组件
其它,后面启动的时候可能会出现以下命名规则错误

修改 .eslintrc.js,如下:
//在rules中添加自定义规则
//关闭组件命名规则
"vue/multi-word-component-names": "off",
