vue.js实战,最后一个项目(电商系统),总结
一、项目目录:二、基础配置:(1)、项目全局引入jq和bootstrap<1>、npm/cnpm install jQuery 安装jq<2>、修改webpack.base.conf.js文件:在头部加上var webpack = require('webpack');在node属性的plugins属性里加上plugins: [new webpac...
一、项目目录:
二、基础配置:
(1)、项目全局引入jq和bootstrap
<1>、npm/cnpm install jQuery 安装jq
<2>、修改webpack.base.conf.js文件:
在头部加上var webpack = require('webpack');
在node属性的plugins属性里加上
plugins: [
new webpack.optimize.CommonsChunkPlugin('common.js'),
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery",
"windows.jQuery": "jquery"
})
]
<3>、main.js里面导入:
import $ from ‘jquery’
import ‘…/static/bootstrap-3.3.7-dist/css/bootstrap.css’;
import ‘…/static/bootstrap-3.3.7-dist/js/bootstrap.min’;
(2)、配置路由:
<1>、router/index.js文件:
1、先引入Vue和VueRouter
2、再使用VueRouter:Vue.use(VueRouter);
3、然后配置路由列表:
path是路径,meta是路由的一些信息(用于路由跳转前或者后的时候钩子函数做一些处理)
component:(resolve)=>require(['./views/shopList.vue'],resolve):
代表懒加载,路由跳转后再加之目标文件
const RoutersPath=[
{
path:'/shopList',
meta:{
title:'商品列表'
},
component:(resolve)=>require(['./views/shopList.vue'],resolve)
},{
path:'/carList',
name:'carList',
meta:{
title:'购物车'
},
component:(resolve)=>require(['./views/carList.vue'],resolve)
},{
path:'/productInfo',
name:'productInfo',
meta:{
title:'详细信息'
},
component:(resolve)=>require(['./views/productInfo.vue'],resolve)
}
]
4、配置路由属性:
routes:是路由列表属性
mode:路由路径方式,mode:'history’代表路由路径将以"/"的形式展现,根贴近习惯
base:根路径:base:__dirname代表路由根路径是当前文件所在的目录,
const routerConfig={
routes:RoutersPath,
mode:'history',
base:__dirname
}
5、实例化一个router对象
const router=new VueRouter(routerConfig);
6、声明钩子函数
路由前将路由模板的meta信息赋值给title
路由后滚动条跳转到初始化状态
router.beforeEach((to,from,next)=>{
window.document.title=to.meta.title;
next();
});
router.afterEach((to,from,next)=>{
window.scroll(0,0);
});
7、export default router;
导出路由(其他js文件可以导入)
8、main.js里面:
(1)、import router from ‘./router’:
引入路由配置文件
(2)、给vue实例配置路由属性:router
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
9、项目中的路由跳转方式:
this.$router.push({
name:'productInfo',
params:{
id:id
}
})
(3)、VUEX配置:
1、按模块创建vuex仓库
Vuex/shopStore/index文件:
声明数据(state)\数据赋值操作(mutations)\数据逻辑操作(actions)\数据视图(getters)
import productData from '../../../static/product.js';
//声明取出重复方法
function getFilterArray(array) {
const res= [];
const json = {};
array.forEach((item,index)=>{
const _self=item;
if (!json[_self]) {
res.push(_self);
json[_self]=1
}
})
return res;
};
//声明数据
const state={
productList:[],
carList:[]
}
//声明赋值方法
const mutations={
setProduct(state,data){
state.productList=data;
},
setCar(state,id){
const nowCar=state.carList.find((item)=>item.id===id);
if (nowCar) {
nowCar.count++;
}else {
state.carList.push({
id:id,
count:1
})
}
},
editCar(state,obj){
let car=state.carList.find((item)=>item.id==obj.id);
car.count+=obj.count;
},
deleteItems(state,obj){
let index=state.carList.findIndex((item)=>item.id==obj.id);
state.carList.splice(index,1);
}
}
//有关于逻辑的方法
const actions={
getProductList(concent){
setTimeout(()=>{
concent.commit('setProduct',productData)
},300)
}
}
const getters={
colors:(state)=>{
const colors=state.productList.map((item)=>{
return item.color;
});
return getFilterArray(colors);
},
brands:(state)=>{
const brands=state.productList.map(item=>item.brand);
return getFilterArray(brands);
}
}
export default {
state,
mutations,
actions,
getters
}
2、按模块创建vuex仓库
Vuex/moudle.js文件:vuex的总配置文件
用于集中所有分模块的vuex仓库
import Vue from 'vue';
import Vuex from 'Vuex';
Vue.use(Vuex);
import shopStore from './shopStore/index.js';
export default new Vuex.Store({
modules:{
shopStore:shopStore
}
});
3、main.js文件
引入vuex总配置:import store from ‘./Vuex/moudles.js’;
设置vue实例的store属性:
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
4、其他文件里面调用vuex数据和方法
<1>数据:
this.$store.state.shopStore.productList
<2>数据赋值方法:
this.$store.commit('setCar',id);
<3>含逻辑的方法:
this.$store.dispatch('getProductList');
<4>数据视图:
this.$store.getters.colors;
三、其他文件:
(1)、组件:components/product.vue
<template>
<div class="product-main" @click="routerClick(info.id)">
<div class="product-item" :style="{backgroundColor:colors[info.color]}">
<div class="product-img"><img :src="require('@/assets/img/'+info.image)"></div>
<div class="product-name">{{info.name}}</div>
<div class="product-cost">{{info.cost}}</div>
<div class="product-cost">¥{{info.sales}}</div>
<div class="product-add" @click.stop="addClick(info.id)">加入购物车</div>
</div>
</div>
</template>
<script>
export default {
name: "product",
data(){
return{
colors:{
'粉色':'#FFB6C1',
'红色':'#DC143C',
'金色':'#FFD700',
'绿色':'#90EE90',
'青色':'#00CED1',
}
}
},
props:{
info:{
type:Object
}
},
methods:{
addClick(id){
this.$store.commit('setCar',id);
},
routerClick(id){
this.$router.push({
name:'productInfo',
params:{
id:id
}
})
}
}
}
</script>
<style scoped>
.product-main{
display: flex;
flex-flow:row wrap;
justify-content: center;
align-items:center;
cursor: pointer;
}
.product-add{
background-color: #9acfea;
position:absolute;
top: 1px;
right: 2px;
display: none;
}
.product-main :hover .product-add{
display: inline-block;
}
.product-item{
width: 150px;
height: 230px;
margin: 2px 2px;
display: flex;
flex-flow:column wrap;
justify-content: center;
align-items:center;
position:relative;
}
.product-img{
width: 100px;
height: 100px;
}
.product-img img{
width: 100px;
height: 100px;
}
.product-name{
text-align: center;
height: 50px;
margin-top: 10px;
}
.product-cost{
text-align: center;
height: 10px;
margin-top: 5px;
}
</style>
(2)、路由视图:router/shopList.vue
<template>
<div id="shopList-main">
<div class="shopList-contorle">
<div class="shopList-contorle-item">
<div class="shopList-contorle-itemSpan-lable">排序:</div>
<div @click="handleClick('cost')" class="shopList-contorle-itemSpan" :class="{on:nowSort.indexOf('cost')>-1}">按销量
<template v-if="nowSort=='costAsc'">↑</template>
<template v-if="nowSort=='costDesc'">↓</template>
</div>
<div @click="handleClick('sales')" class="shopList-contorle-itemSpan" :class="{on:nowSort.indexOf('sales')>-1}">按价格
<template v-if="nowSort=='salesAsc'">↑</template>
<template v-if="nowSort=='salesDesc'">↓</template>
</div>
</div>
<div class="shopList-contorle-item">
<div class="shopList-contorle-itemSpan-lable">按颜色筛选:</div>
<div @click="handleSeahColor(item)" :class="{on:nowColor.indexOf(item)>-1}" class="shopList-contorle-itemSpan" v-for="item in colors"> {{item}}
</div>
</div>
<div class="shopList-contorle-item">
<div class="shopList-contorle-itemSpan-lable">按品牌筛选:</div>
<div @click="handleSeahBrand(item)" :class="{on:nowBrand==item}" class="shopList-contorle-itemSpan" v-for="item in brands"> {{item}}
</div>
</div>
</div>
<div class="shopList-product">
<product class="shopList-Item" v-for="item in filterList" :info="item" :key="item.id"></product>
</div>
</div>
</template>
<script>
import product from '../../components/product.vue';
export default {
name: "shopList",
components:{product},
data(){
return{
nowSort:'',
nowColor:[],
nowBrand:''
}
},
methods:{
handleClick(type){
//按销量
if (type=='cost'){
if (this.nowSort=='costAsc') {
this.nowSort='costDesc'
}else {
this.nowSort='costAsc'
}
};
//按价格
if (type=='sales'){
if (this.nowSort=='salesAsc') {
this.nowSort='salesDesc'
}else {
this.nowSort='salesAsc'
}
};
},
//按颜色筛选
handleSeahColor(item){
var index=this.nowColor.findIndex((value)=>value==item);
if (index>-1){
this.nowColor.splice(index,1);
}else {
this.nowColor.push(item);
}
// this.nowColor=this.nowColor==''?item:(this.nowColor==item?'':item);
},
//按品牌筛选
handleSeahBrand(item){
this.nowBrand=this.nowBrand==''?item:(this.nowBrand==item?'':item);
}
},
computed:{
list(){
return this.$store.state.shopStore.productList
},
colors(){
return this.$store.getters.colors;
},
brands(){
return this.$store.getters.brands;
},
filterList(){
//复制list
let list=[...this.list];
//排序
if (this.nowSort!=''){
//按销量排序
if (this.nowSort.indexOf('cost')>-1){
list= list.sort((a,b) =>{
return this.nowSort.indexOf('Asc')>-1?a.cost-b.cost:b.cost-a.cost
});
}
//按价格排序
if (this.nowSort.indexOf('sales')>-1){
list= list.sort((a,b) =>{
return this.nowSort.indexOf('Asc')>-1?a.sales-b.sales:b.sales-a.sales
});
}
};
//按颜色筛选
if (this.nowColor.length!=0){
list=list.filter((item)=>{
var obj=this.nowColor.find((value)=>value==item.color);
if (obj!=undefined) return item;
});
}
//按品牌筛选
if (this.nowBrand!=''){
list=list.filter((item,index)=>item.brand===this.nowBrand)
}
return list;
}
},
mounted(){
this.$store.dispatch('getProductList');
}
}
</script>
<style scoped>
.shopList-main{
display: flex;
flex-flow:row wrap;
justify-content: center;
align-items:center;
}
.shopList-product{
display: flex;
flex-flow:row wrap;
justify-content: flex-start;
align-items:center;
}
.shopList-contorle{
display: flex;
flex-flow:column wrap;
justify-content: flex-start;
align-items:flex-start;
}
.shopList-contorle-item{
margin-top: 5px;
}
.shopList-contorle-itemSpan-lable{
margin-left: 10px;
float: left;
}
.shopList-contorle-itemSpan{
background-color: #c4e3f3;
color: #e38d13;
border: 1px solid #c4e3f3;
cursor: pointer;
margin-left: 10px;
float: left;
width: 50px;
height: 20px;
}
.on{
background-color:#31b0d5;
}
</style>
(3)、路由视图:router/productInfo.vue
<template>
<div class="info-main">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-4">
<div class="info-item">
<img :src="require('@/assets/img/'+productInfo.image)">
</div>
</div>
<div class="col-md-4">
<div class="info-text">
<div>{{productInfo.name}}</div>
<div>{{productInfo.cost}}</div>
<div class="product-add" @click.stop="addClick(productInfo.id)">加入购物车</div>
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
</template>
<script>
export default {
name: "productInfo",
data() {
return {
id:this.$route.params.id,
productInfo:null
};
},
methods:{
getProductInfo(){
let list =this.$store.state.shopStore.productList
this.productInfo=list.find((item)=>item.id===this.id);
},addClick(id){
this.$store.commit('setCar',id);
this.$router.push({
name:'carList'
})
},
},
mounted() {
this.getProductInfo();
}
}
</script>
<style scoped>
.info-main{
height: 520px;
}
.info-item{
height: 210px;
margin: 155px auto;
text-align: center;
}
.info-text{
height: 100px;
margin: 220px auto;
text-align: center;
display: flex;
flex-flow:column wrap;
justify-content: center;
align-items:center;
}
.product-add{
background-color: #9acfea;
display: inline-block;
cursor: pointer;
}
</style>
(4)、路由视图:router/carList.vue
<template>
<div class="car-main">
<div class="car-table row">
<div class="col-md-1"></div>
<div class="col-md-8">
<table class="table table-striped">
<thead>
<th>商品信息</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
<th>删除</th>
</thead>
<tbody>
<tr v-for="(item,index) in carList">
<td>
<img :src="require('@/assets/img/'+productDic[item.id].image)">
<span>{{productDic[item.id].name}}</span>
</td>
<td>{{productDic[item.id].sales}}</td>
<td>
<button @click="handleAdd(1,item.id)">+</button>
{{item.count}}
<button @click="handleAdd(-1,item.id)">-</button>
</td>
<td>{{productDic[item.id].sales*item.count}}</td>
<td><span style="cursor: pointer" @click="deleteItems(item.id)">x</span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-1"></div>
</div>
<div class="car-cosp row">
<div class="col-md-4" style="text-align: left">
<span>优惠码:</span>
<input type="text" v-model="youhuima" placeholder="输入优惠码">
<button class="btn btn-default btn-sm" @click="youhuiclick">使用优惠码</button>
</div>
<div class="col-md-3"></div>
<div class="col-md-2">
<span style="display: block">总数量:{{costAll}}</span>
<span style="display: block">原价:{{salesAll}}</span>
<span style="display: block">优惠价:{{yousalesAll}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: "carList",
data(){
return{
productData:this.$store.state.shopStore.productList,
youhuima:'',
youhuisales:0
}
},
computed:{
carList(){
return this.$store.state.shopStore.carList
},
productDic(){
const dict={};
this.productData.forEach(item=>{
dict[item.id]=item;
});
return dict;
},
costAll(){
var allCount=0;
this.carList.forEach((item)=>{
allCount+=item.count;
});
return allCount;
},
salesAll(){
var allSales=0;
this.carList.forEach((item)=>{
allSales+=this.productDic[item.id].sales*item.count;
});
return allSales;
},
yousalesAll(){
return this.salesAll-this.youhuisales;
}
},
methods:{
handleAdd(count,id){
let carItem=this.carList.find((item)=>item.id==id);
if (count<1 && carItem.count==1){return false};
this.$store.commit('editCar',{
count:count,
id:id
});
},
deleteItems(id){
this.$store.commit('deleteItems',{
id:id
});
},
youhuiclick(){
if (this.youhuima!='vue') {
alert('优惠码错误!');
return false;
}
if (this.youhuisales==500){
alert('已经优惠过了!');
return false;
}
this.youhuisales=500;
}
}
}
</script>
<style scoped>
.car-table,th,td{
text-align: center;
vertical-align: middle;
}
img{
height: 50px;
}
.table tbody tr td{
vertical-align: middle;
}
</style>
(5)、主组件:App.vue
<template>
<div id="app">
<div class="app-main">
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8" >
<div class="app-controller">
<div class="app-router">
<ul class="nav nav-pills">
<li :class="{active:nowId=='listId'}" role="presentation" @click="handleClick"><router-link id="listId" to="/shopList">商品列表</router-link></li>
<li :class="{active:nowId=='carId'}" role="presentation" @click="handleClick"><router-link id="carId" to="/carList">购物车({{shopCarNum}})</router-link></li>
</ul>
</div>
</div>
</div>
<div class="col-md-2"></div>
</div>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-8">
<div class="app-view">
<router-view/>
</div>
</div>
<div class="col-md-2"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data(){
return{
nowId:''
}
},
computed:{
shopCarNum(){
var count=0;
this.$store.state.shopStore.carList.forEach((item)=>{
count+=item.count
})
return count;
}
},
methods:{
handleClick(event){
this.nowId=event.target.id
}
}
}
</script>
<style scoped>
.app-main{
margin: 0 auto;
}
.app-controller{
margin: 0 auto;
display: flex;
flex-flow:row wrap;
justify-content: center;
align-items:center;
}
.app-router{
margin: 0 auto;
text-align: center;
}
.app-view{
margin: 0 auto;
text-align: center;
}
</style>
(6)、main.js程序入口
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import $ from 'jquery'
import '../static/bootstrap-3.3.7-dist/css/bootstrap.css';
import '../static/bootstrap-3.3.7-dist/js/bootstrap.min';
import store from './Vuex/moudles.js';
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
7、图片放在src\assets\img目录下,所有请求图片需要:
<img :src="require('@/assets/img/'+info.image)">
8、bootstrap和product.js等静态资源放在static文件下
9、product.js文件(静态数据)
export default [
{
id: 1,
name: 'AirPods',
brand: 'Apple',
image: '1.png',
sales: 10000,
cost: 1288,
color: '粉色'
},
{
id: 2,
name: 'BeatsX 入耳式耳机',
brand: 'Beats',
image: '2.png',
sales: 11000,
cost: 1188,
color: '粉色'
},
{
id: 3,
name: 'Beats Solo3 Wireless 头戴式式耳机',
brand: 'Beats',
image: '3.png',
sales: 5000,
cost: 2288,
color: '粉色'
},
{
id: 4,
name: 'Beats Pill+ 便携式扬声器',
brand: 'Beats',
image: '4.png',
sales: 3000,
cost: 1888,
color: '红色'
},
{
id: 5,
name: 'Sonos PLAY:1 无线扬声器',
brand: 'Sonos',
image: '5.png',
sales: 8000,
cost: 1578,
color: '红色'
},
{
id: 6,
name: 'Powerbeats3 by Dr. Dre Wireless 入耳式耳机',
brand: 'Beats',
image: '6.png',
sales: 12000,
cost: 1488,
color: '红色'
},
{
id: 7,
name: 'Beats EP 头戴式耳机',
brand: 'Beats',
image: '7.png',
sales: 25000,
cost: 788,
color: '金色'
},
{
id: 8,
name: 'B&O PLAY BeoPlay A1 便携式蓝牙扬声器',
brand: 'B&O',
image: '8.png',
sales: 15000,
cost: 1898,
color: '金色'
},
{
id: 9,
name: 'Bose® QuietComfort® 35 无线耳机',
brand: 'Bose',
image: '9.png',
sales: 14000,
cost: 2878,
color: '青色'
},
{
id: 10,
name: 'B&O PLAY Beoplay H4 无线头戴式耳机',
brand: 'B&O',
image: '10.png',
sales: 9000,
cost: 2298,
color: '绿色'
}
]
更多推荐
所有评论(0)