视频地址:vue 2.0项目-快餐店pos系统
vue+Element-ui快餐店pos系统
1.项目搭建
1.新建项目
新建一个空文件夹AwesomePos
并在VScode中打开。
首先安装一下vue-cli
:
npm install vue-cli -g
要验证是否安装成功的话输入:vue -V
,如果出现版本号说明安装成功。
然后是用脚手架初始化项目(因为项目文件夹已经建好了所以webpack后面不用再跟参数了):
vue init webpack
然后是进行一些初始化的项目配置:项目名(要小写),项目作者等等。
Install vue-router? Yes
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
// 别的一些配置全部回车即可
接着执行一下npm install
下载一些依赖包。
最后可以执行npm run dev
来看一下项目初始化完成了没,如果能在浏览器中正常打开页面,说明安装正确。
2.修改部分文件
修改一下index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>AwesomePOS快餐店收银系统</title> // 重命名一下网页标题
<style>
html,body,#app{height:100%;padding: 0;margin:0;} //重新调整一下css样式
</style>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
还可以把App.vue
中的vue的logo图给删掉:
<template>
<div id="app">
<!-- <img src="./assets/logo.png"> --> //vuelogo图
<router-view/>
</div>
</template>
...
3.新建组件Pos.vue
在components
文件夹下新建一个Pos.vue
作为入口文件然后写出vue模板的基本架构即可:
<template>
<div class="pos">
Hello AwesomePOS Demo.
</div>
</template>
<script>
export default {
name: 'pos'
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>
4.修改路由文件
修改一下路由配置文件,让入口变成Pos组件:
import Vue from 'vue'
import Router from 'vue-router'
import Pos from '@/components/Pos'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Pos',
component: Pos
}
]
})
至此为止,项目的基本配置运行环境全部完成。
2.项目图标
图标采用的是阿里巴巴矢量图标库里的天猫图标库,步骤如下:
- 在图标库中选择你想要的图标,添加至购物车。然后添加到项目,如果没有就自己新建一个。
- 如果选择在线使用的话,就生成在线链接,然后将链接在你要使用的页面中通过Link引入:
<title>AwesomePOS快餐店收银系统</title>
<link rel="stylesheet" href="http://at.alicdn.com/t/font_2351615_fprn7byotcn.css">
接着就可以在App.vue
中使用i标签来引入图标了:
<i class="icon iconfont icon-dianpu"></i>
// icon iconfont 这两个类名都是固定的,最后一个根据图标的不同而不同
注意如果后面又加入了几个图标,那么需要重新将新增的图标添加至项目,然后更新在线链接,并重新引入链接才能使用新图标。
3.侧边栏导航组件
首先在components
文件夹下新建两个文件夹common
和page
:
common
文件夹用来放共用组件,下面写的leftNav.vue
组件就放到这里。page
文件夹用来放页面模板组件,页面的模板文件放到这里。将Hello.vue
和Pos.vue
放在这里面。
然后编写leftNav.vue
:
<template>
<div class="left-nav">
<ul>
<li>
<i class="icon iconfont icon-goumai"></i>
<div>收银</div>
</li>
<li>
<i class="icon iconfont icon-dianpu"></i>
<div>店铺</div>
</li>
<li>
<i class="icon iconfont icon-hanbao"></i>
<div>商品</div>
</li>
<li>
<i class="icon iconfont icon-huiyuanqia"></i>
<div>会员</div>
</li>
<li>
<i class="icon iconfont icon-gongnengjianyi"></i>
<div>统计</div>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'leftNav',
data () {
return {
}
}
}
</script>
<style>
.left-nav{
color:#fff;
font-size:10px;
height:100%;
background-color: #1D8ce0;
float:left;
width:5%;
}
.iconfont{
font-size:24px;
}
.left-nav ul{
padding:0px;
margin: 0px;
}
.left-nav li{
list-style: none;
text-align: center;
border-bottom:1px solid #20a0ff;
padding:10px;
}
</style>
然后就是将leftNav.vue
引入到App.vue
中去并写到页面上:
<template>
<div id="app">
<!-- <img src="./assets/logo.png"> -->
<!--左侧导航-->
<leftNav></leftNav>
<!--操作区域-->
<div class="main">
<router-view></router-view>
</div>
</div>
</template>
<script>
import leftNav from '@/components/common/leftNav'
export default {
name: 'App',
components:{
leftNav
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
height: 100%;
/* margin-top: 60px; */
}
.main {
float: left;
width: 95%;
background-color: #3FF2F7;
height:100%;
overflow: auto;
}
</style>
至此为止,侧边导航栏已经基本完成。
4.Element组件库
Element是一套基于 Vue 2.0 的桌面端组件库。
首先安装一下element-ui
,使用下面的命令:
npm install element-ui --save
接着导入:
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// 如果出现报错,检查路径是否正确,视频中写的是theme-default,但是现在好像已经更新为了theme-chalk
element-ui
使用的是栅格化布局,能适应多端页面显示。可以在Pos.vue中尝试一下布局:
<div>
<el-row >
<el-col :span='7' class="pos-order">
我是订单栏
</el-col>
<!--商品展示-->
<el-col :span="17">
我是产品栏
</el-col>
</el-row>
</div>
然后调整一下css样式:
.pos-order {
background-color: #F9FAFC;
border-right: 1px solid #C0CCDA;
}
但是实际显示的效果的高度并不是100%。在css样式中也调整不了height的值。
这个时候要用到js
以及钩子函数mounted
来调整高度100%:
<template>
<div class="pos">
<el-row>
<el-col :span="7" class="pos-order" id="order-list">
我是订单栏
</el-col>
<el-col :span="17">
我是产品栏
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: 'pos',
mounted:function(){ //等全部的虚拟DOM(ElementUI)加载完成之后调整高度
var orderHeight = document.body.clientHeight;
// console.log(orderHeight);
document.getElementById('order-list').style.height=orderHeight+'px';
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.pos-order {
background-color: #F9FAFC;
border-right: 1px solid #C0CCDA;
}
</style>
最终实现效果如下图:
最左侧的是导航栏,中间的是订单栏,右侧的是产品栏。
5.利用Element快速布局-1
1.el-tabs标签页组件
<el-col :span="7" class="pos-order" id="order-list">
<el-tabs type="card"> //如果对标签页的样式不满意可以自己调整,例如type="card"
<el-tab-pane label="点餐"> //label属性的值就是标签页的名字
点餐 //这里就是"点餐"标签页中的内容
</el-tab-pane>
<el-tab-pane label="挂单">
挂单
</el-tab-pane>
<el-tab-pane label="外卖">
外卖
</el-tab-pane>
</el-tabs>
</el-col>
2.el-table表格组件
<el-table :data="tableData" border style="width:100%" stripe :cell-style="rowClass" :header-cell-style="headClass">
<el-table-column prop="goodsName" label="名称" width="150"></el-table-column>
<el-table-column prop="goodsCount" label="数量"></el-table-column>
<el-table-column prop="goodsPrice" label="金额"></el-table-column>
<el-table-column label="操作" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small">删除</el-button>
<el-button type="text" size="small">增加</el-button>
</template>
</el-table-column>
</el-table>
<script>
export default {
name: 'pos',
data(){
return {
tableData: [{
goodsName: '可口可乐',
goodsPrice: 8,
goodsCount:1
}, {
goodsName: '香辣鸡腿堡',
goodsPrice: 15,
goodsCount:1
}, {
goodsName: '爱心薯条',
goodsPrice: 8,
goodsCount:1
}, {
goodsName: '甜筒',
goodsPrice: 8,
goodsCount:1
}]
}
},
mounted:function(){ //等全部的虚拟DOM(ElementUI)加载完成之后调整高度
var orderHeight = document.body.clientHeight;
// console.log(orderHeight);
document.getElementById('order-list').style.height=orderHeight+'px';
},
methods: {
// 表头样式设置
headClass () {
return 'text-align: center;' //文字居中
},
// 表格样式设置
rowClass () {
return 'text-align: center;' //文字居中
}
}
}
</script>
在上面的代码中,:data="tableData"
用来绑定表格中的数据源(这里是先在js中写死);:cell-style="rowClass" :header-cell-style="headClass"
以及在js中的methods中的两个方法headClass,rowClass
用来让表格的表头以及内容文字居中。fixed="right"
的作用是将最后一列操作列固定在右侧,使得表格内容在滑动时,右侧的操作栏始终不动。
注意,在写
<template slot-scope="scope">
的时候,如果slot-scope="scope"
(scope="scope"已经不适配最新版Vue)报错,进入VScode设置,把Vetur>Validation:Template的勾给去掉。如下图:
3.el-button 按钮组件
接下去就是在表格的下方增加三个功能按钮:
<div class="div-btn">
<el-button type="warning">挂单</el-button>
<el-button type="danger">删除</el-button>
<el-button type="success">结账</el-button>
</div>
...
<style scoped>
.div-btn {
margin-top: 10px;
}
</style>
6.利用Element快速布局-2
接下去就是进行产品栏的布局。
1.常用商品
<div class="often-goods">
<div class="title">常用商品</div>
<div class="often-goods-list">
<ul>
<li>
<span>香辣鸡腿堡</span>
<span class="o-price">¥15元</span>
</li>
</ul>
</div>
</div>
...
<style scoped>
.pos-order {
background-color: #F9FAFC;
border-right: 1px solid #C0CCDA;
}
.div-btn {
margin-top: 10px;
}
.title{
height: 20px;
border-bottom:1px solid #D3DCE6;
background-color: #F9FAFC;
padding:10px;
text-align: center;
}
.often-goods-list ul li{
list-style: none;
float:left;
border:1px solid #E5E9F2;
padding:10px;
margin:5px;
background-color:#fff;
}
.o-price{
color:#58B7FF;
}
</style>
然后可以在js中申明多个数据列表并渲染到页面中去:
<li v-for="oftenGood in oftenGoods">
<span>{{oftenGood.goodsName}}</span>
<span class="o-price">💰{{oftenGood.price}}</span>
</li>
...
oftenGoods:[
{
goodsId:1,
goodsName:'香辣鸡腿堡',
price:18
}, {
goodsId:2,
goodsName:'田园鸡腿堡',
price:15
}, {
goodsId:3,
goodsName:'和风汉堡',
price:15
}, {
goodsId:4,
goodsName:'快乐全家桶',
price:80
}, {
goodsId:5,
goodsName:'脆皮炸鸡腿',
price:10
}, {
goodsId:6,
goodsName:'魔法鸡块',
price:20
}, {
goodsId:7,
goodsName:'可乐大杯',
price:10
}, {
goodsId:8,
goodsName:'雪顶咖啡',
price:18
}, {
goodsId:9,
goodsName:'大块鸡米花',
price:15
}, {
goodsId:20,
goodsName:'香脆鸡柳',
price:17
}
]
2.分类商品
<div class="goods-type">
<el-tabs type="border-card">
<el-tab-pane label="汉堡">
<div>
<ul class='cookList'>
<li v-for="goods in type0Goods">
<span class="foodImg"><img :src="goods.goodsImg" width="100%"></span>
<span class="foodName">{{goods.goodsName}}</span>
<span class="foodPrice">¥{{goods.price}}元</span>
</li>
</ul>
</div>
</el-tab-pane>
<el-tab-pane label="小食">
小食
</el-tab-pane>
<el-tab-pane label="饮料">
饮料
</el-tab-pane>
<el-tab-pane label="套餐">
套餐
</el-tab-pane>
</el-tabs>
</div>
...
type0Goods:[
{
goodsId:1,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'香辣鸡腿堡',
price:18
}, {
goodsId:2,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'田园鸡腿堡',
price:15
}, {
goodsId:3,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'和风汉堡',
price:15
}, {
goodsId:4,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'快乐全家桶',
price:80
}, {
goodsId:5,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'脆皮炸鸡腿',
price:10
}, {
goodsId:6,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'魔法鸡块',
price:20
}, {
goodsId:7,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'可乐大杯',
price:10
}, {
goodsId:8,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'雪顶咖啡',
price:18
}, {
goodsId:9,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'大块鸡米花',
price:15
}, {
goodsId:20,
goodsImg:"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2506175111,2572218063&fm=26&gp=0.jpg",
goodsName:'香脆鸡柳',
price:17
}
]
...
.goods-type {
clear: both;
}
.cookList li{
list-style: none;
width:23%;
border:1px solid #E5E9F2;
height: auot;
overflow: hidden;
background-color:#fff;
padding: 2px;
float:left;
margin: 2px;
}
.cookList li span{
display: block;
float:left;
}
.foodImg{
width: 40%;
}
.foodName{
font-size: 18px;
padding-left: 10px;
color:brown;
}
.foodPrice{
font-size: 16px;
padding-left: 10px;
padding-top:10px;
}
常用商品和分类商品编写逻辑和格式其实差不多,至此为止页面布局已经全部完成(页面内的数据目前仍然是写死的)。接下去就是动态获取到数据渲染到页面中。
7.Axios从远程获取数据
首先安装一下Axios,使用命令:
npm n install axios --save
或者
npm install --save axios
接着在Pos.vue
中引入:
import axios from 'axios'
然后用钩子函数通过服务端获取数据,方法如下:
created(){
axios.get('后端接口地址')
.then(response=>{
console.log(response);
this.oftenGoods=response.data;
})
.catch(error=>{
console.log(error);
alert('网络错误,不能访问');
})
},
如果自己没有后端接口地址,那么可以用vue-mock来自己建立一个简单的后端数据接口来模拟请求。
8.逻辑功能的实现
1.添加商品到订单页面
添加商品的功能具体描述就是:点击常用商品或者分类商品中的某个商品,会把它添加到左侧点餐栏处,如果相同商品点击第二次那么该商品的数量会+1。
addOrderList(goods) {
let isHave = false; //默认不存在
// 判断商品是否已经存在于点餐栏中
for(let i = 0;i<this.tableData.length;i++) {
if(this.tableData[i].goodsId==goods.goodsId) {
isHave=true;
}
}
if(isHave) { //如果有,商品对应的数量+1
let arr = this.tableData.filter(o=>o.goodsId == goods.goodsId)
arr[0].goodsCount ++;
} else { //如果没有,商品添加到点餐栏中
let newGoods = {goodsId:goods.goodsId,goodsName:goods.goodsName,goodsPrice:goods.goodsPrice,goodsCount:1} //新商品
this.tableData.push(newGoods);
}
}
在对应的商品元素上绑定好方法(包括常用商品和分类商品中的所有):
<ul>
<li v-for="oftenGood in oftenGoods" @click="addOrderList(oftenGood)">
<span>{{oftenGood.goodsName}}</span>
<span class="o-price">💰{{oftenGood.goodsPrice}}</span>
</li>
</ul>
2.操作栏中的增加功能
在操作栏中的增加功能只需要调用同样的方法就可以了:
<el-button type="text" size="small" @click="addOrderList(scope.row)">增加</el-button>
scope.row
是elementUI中规定的语法,相当于就是把这一行的对象数据作为参数传递过去。
3.操作栏中的删除功能
因为删除完成后,商品的总数量和总金额都得重新计算,所以把计算总数量和总金额封装在一个方法中:
getAllMoney(){ //汇总数量和金额
this.totalCount=0;
this.totalMoney=0;
if(this.tableData){
this.tableData.forEach((element) => {
this.totalCount+=element.goodsCount;
this.totalMoney=this.totalMoney+(element.goodsPrice*element.goodsCount);
});
}
}
然后写删除商品的逻辑:
<el-button type="text" size="small" @click="delOrderList(scope.row)">增加</el-button>
...
delOrderList(goods) { //删除商品
console.log(goods);
this.tableData=this.tableData.filter(o => o.goodsId !=goods.goodsId);
this.getAllMoney();
},
4.全部删除功能
<el-button type="danger" @click="delAllGoods">删除</el-button>
...
//删除所有商品
delAllGoods() {
this.tableData = [];
this.getAllMoney();
},
5.结账功能
<el-button type="success" @click="checkout">结账</el-button>
...
checkout() {
if (this.totalCount!=0) {
this.tableData = [];
this.totalCount = 0;
this.totalMoney = 0;
this.$message({
message: '结账成功,感谢你又为店里出了一份力!',
type: 'success'
});
}else{
this.$message.error('不能空结。老板了解你急切的心情!');
}
}
9.项目打包和上线
关于打包路径的一些设置在config
文件夹下的index.js
文件中的build
属性中:
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
/**
* Source Maps
*/
productionSourceMap: true,
// https://webpack.js.org/configuration/devtool/#production
devtool: '#source-map',
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
}
把里面的assetsPublicPath: '/',
改成:
assetsPublicPath: './',
这样才能保证我们打包出去的项目可以正常预览。
打包使用到的命令为:
npm run build
打包成功后的提示为:
完成之后会在项目目录下生成一个dist
文件夹,双击打开里面的index.html
文件就是刚完成的页面,如果能成功打开并且测试也成功,说明打包成功了。
最终实现效果如下图: