大前端-番外篇-01 vue 工程利用 pubsub-JS 实现兄弟组件之间的通信
一、前言
Vue父子组件之间如何传递参数呢?今天就来看一下pubsub.js这个包的用法。
二、步骤流程
vue项目搭建完成之后的文件图如下:
1、导入包
项目中需要用到axios这个包,所以我们先安装相应的包,项目终端输入:npm install axios -S
。后面兄弟组件之间通信我们采用的是“订阅消息/发布消息”的方法,这个也是一个包,所以也先安装下依赖包 npm i pubsub-js -S
。安装完成后可以在项目package.json文件中看到对应依赖包。
npm install axios -S
npm i pubsub-js -S
2、创建父子组件
src文件夹下components下创建 main.vue
和 header.vue
两个组件,分别代表项目中主体区域和头部区域,等下需要挂载到根组件App下。
header.vue文件中相关代码:
<template>
<div id="header">
<div class="container">
<div class="row center">
<h1>Search Github Users</h1>
<input type="text" v-model="searchName">
<input type="button" value="Search" class="btn btn-primary" placeholder="请输入github用户名" @click="search">
</div>
</div>
</div>
</template>
<script>
import PubSub from 'pubsub-js'
export default {
name: 'Header',
data () {
return {
searchName: ''
}
},
methods: {
search ()
{
// 发布消息
const searchName = this.searchName.trim()
if (searchName) {
PubSub.publish('search', searchName)
}
}
}
}
</script>
<style>
#header{
height: 200px;
}
.container .center{
text-align: center;
}
</style>
main.vue文件中相关代码:
<template>
<div id="main">
<h1 v-if="firstview">请输入搜索用户的名称</h1>
<h1 v-if="loading">loading</h1>
<h1 v-if="errormsg">{{errormsg}}</h1>
<div class="row">
<div class="col-xs-6 col-md-3" v-for="(user,index) in users" :key="index">
<a :href="user.url">
<img :src="user.avatar_url">
</a>
<span>{{user.name}}</span>
</div>
</div>
</div>
</template>
<script>
import PubSub from 'pubsub-js'
import axios from 'axios'
// https://api.github.com/search/users?q=
export default {
name: 'Main',
data () {
return {
firstview: true,
loading: false,
errormsg: '',
users: null // [{url:' ',name:'',avatar_url:""}]
}
},
mounted () {
// 订阅消息
PubSub.subscribe('search', (msg, searchName) => {
const url = 'https://api.github.com/search/users?q='+searchName
// 更新状态
this.firstview = false
this.loading = true
this.users=null
this.errormsg=''
axios.get(url).then((response) => {
const result = response.data
const users = result.items.map(item => ({
url: item.html_url,
avatar_url: item.avatar_url,
name: item.login
}))
// 更新请求成功的状态
this.loading=false
this.users=users
}).catch(error => {
this.loading=false
this.errormsg='搜索失败'
})
})
}
}
</script>
<style>
#main > .row ,#main h1{
position: absolute;
left: 50%;
transform: translateX(-50%);
}
#main > .row > div{
float: left;
width: 100px;
padding: 0;
text-align: center;
color: red;
font-size: 18px;
margin: 0 10px;
}
.row a img{
width: 100px;
height: 100px;
}
</style>
这里主要是详细解释下main.vue中相关代码。在项目的主体区域中,我们需要通过后台返回的数据进行页面的渲染。标签模板中主要是通过v-for="(item,index) in items" :key="index"
指令遍历得到的数组,进行页面渲染,利用v-for我们可以轻松得到一组类似的结构,而不必多次写一些重复的html标签。在组件模板对象中我们定义了一组数据信息:
这里的users主要是保存由后台返回回来的一组数据,可以供页面进行渲染加载。另外三种皆为不同的状态标志属性,当我们进行不同的操作,或者发送Ajax请求(成功?失败)都可以改变相应的状态值从而使页面呈现不同状态。
3、pub-sub库的使用
项目中我们头部header需要向后台发送关键字,后台根据得到的关键字进行相应的操作,返回项目需要的数据。main主体区域中需要利用后台返回的数据,进行页面的渲染,main区域中必定会利用header中提供的关键字发送ajax请求,所以这就牵扯到组件之间的通信问题,pubsub-js就是用来实现组件之间通信的。兄弟组件之间通信如果利用props属性,需要借助父组件来实现,pubsub跨越组件之间的关系阶层进行通信。pubsub-js也就是我们所说的订阅消息和发布消息,订阅消息可以理解为事件的监听,发布消息可以理解为触发事件。我们在header中点击搜索会通知main区域向后台发送Ajax请求,所以我们在header中发布消息,main中订阅消息。
利用pubsub,首先需要导入这个包。search按钮点击的时候,我们可以在按钮点击的回调函数里去发布消息。发布消息是PubSub.publish()方法,这里需要提供两个参数,"发布的消息名","提供给订阅者的参数",这里的参数是输入框的关键字。
main区域在组件的生命周期函数mounted(页面加载完成)中订阅消息。订阅消息是 PubSub.subscribe()方法,这里接受两个参数,"发布的消息名","事件的监听函数",这里事件的监听函数需要两个参数:一个是msg(发布的消息名,无用),一个是searchName(发布者传来的参数)。我们在事件的监听里面去发送Ajax请求,更新页面。
4、项目完成后的效果图
三、项目实战
上图这个项目,分类和品牌都是组件,然后引入到这个Vue表单页面,需要实现的功能是选择分类的具体某个选项时,选择品牌
可以根据选择的分类拿到具体的这个分类下的品牌,然后显示出来,等选完品牌之后,然后将这个属性传到父页面表单属性里。
1、在 src/main.js
引入pub-sub.js
import Vue from 'vue'
import App from '@/App'
import router from '@/router' // api: https://github.com/vuejs/vue-router
import store from '@/store' // api: https://github.com/vuejs/vuex
import VueCookie from 'vue-cookie' // api: https://github.com/alfhen/vue-cookie
import '@/element-ui' // api: https://github.com/ElemeFE/element
import '@/icons' // api: http://www.iconfont.cn/
import '@/element-ui-theme'
import '@/assets/scss/index.scss'
import httpRequest from '@/utils/httpRequest' // api: https://github.com/axios/axios
import {
isAuth
} from '@/utils'
import cloneDeep from 'lodash/cloneDeep'
// vue工程利用pubsub - js实现兄弟组件之间的通信
// doc: https://www.cnblogs.com/dreamcc/archive/2019/03/21/10570273.html
import PubSub from 'pubsub-js'
Vue.use(VueCookie)
Vue.config.productionTip = false
// 非生产环境, 适配mockjs模拟数据 // api: https://github.com/nuysoft/Mock
if (process.env.NODE_ENV !== 'production') {
require('@/mock')
}
// 挂载全局
Vue.prototype.$http = httpRequest // ajax请求方法
Vue.prototype.isAuth = isAuth // 权限方法
Vue.prototype.PubSub = PubSub
// 保存整站vuex本地储存初始状态
window.SITE_CONFIG['storeState'] = cloneDeep(store.state)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: {
App
}
})
2、品牌选择组件:
<template>
<div>
<el-select placeholder="请选择" v-model="brandId" filterable clearable>
<el-option
v-for="item in brands"
:key="item.brandId"
:label="item.brandName"
:value="item.brandId"
></el-option>
</el-select>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
data() {
//这里存放数据
return {
catId: 0,
brands: [
{
label: "a",
value: 1
}
],
brandId: "",
subscribe: null
};
},
//计算属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {
brandId(val) {
this.PubSub.publish("brandId", val);
}
},
//方法集合
methods: {
getCatBrands() {
this.$http({
url: this.$http.adornUrl("/product/categorybrandrelation/brands/list"),
method: "get",
params: this.$http.adornParams({
catId: this.catId
})
}).then(({ data }) => {
this.brands = data.data;
});
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
//监听三级分类消息的变化
this.subscribe = PubSub.subscribe("catPath", (msg, val) => {
this.catId = val[val.length - 1];
this.getCatBrands();
});
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {
PubSub.unsubscribe(this.subscribe); //销毁订阅
}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>
表单页面:src/views/modules/product/spuadd.vue
<template>
<div>
<el-row>
<el-col :span="24">
<el-steps :active="step" finish-status="success">
<el-step title="基本信息"></el-step>
<el-step title="规格参数"></el-step>
<el-step title="销售属性"></el-step>
<el-step title="SKU信息"></el-step>
<el-step title="保存完成"></el-step>
</el-steps>
</el-col>
<el-col :span="24" v-show="step==0">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-form ref="spuBaseForm" :model="spu" label-width="120px" :rules="spuBaseInfoRules">
<el-form-item label="商品名称" prop="spuName">
<el-input v-model="spu.spuName"></el-input>
</el-form-item>
<el-form-item label="商品描述" prop="spuDescription">
<el-input v-model="spu.spuDescription"></el-input>
</el-form-item>
<el-form-item label="选择分类" prop="catalogId">
<category-cascader></category-cascader>
</el-form-item>
<el-form-item label="选择品牌" prop="brandId">
<brand-select></brand-select>
</el-form-item>
<el-form-item label="商品重量(Kg)" prop="weight">
<el-input-number v-model.number="spu.weight" :min="0" :precision="3" :step="0.1"></el-input-number>
</el-form-item>
<el-form-item label="设置积分" prop="bounds">
<label>金币</label>
<el-input-number
style="width:130px"
placeholder="金币"
v-model="spu.bounds.buyBounds"
:min="0"
controls-position="right"
></el-input-number>
<label style="margin-left:15px">成长值</label>
<el-input-number
style="width:130px"
placeholder="成长值"
v-model="spu.bounds.growBounds"
:min="0"
controls-position="right"
>
<template slot="prepend">成长值</template>
</el-input-number>
</el-form-item>
<el-form-item label="商品介绍" prop="decript">
<multi-upload v-model="spu.decript"></multi-upload>
</el-form-item>
<el-form-item label="商品图集" prop="images">
<multi-upload v-model="spu.images"></multi-upload>
</el-form-item>
<el-form-item>
<el-button type="success" @click="collectSpuBaseInfo">下一步:设置基本参数</el-button>
</el-form-item>
</el-form>
</el-card>
</el-col>
<el-col :span="24" v-show="step==1">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-tabs tab-position="left" style="width:98%">
<el-tab-pane
:label="group.attrGroupName"
v-for="(group,gidx) in dataResp.attrGroups"
:key="group.attrGroupId"
>
<!-- 遍历属性,每个tab-pane对应一个表单,每个属性是一个表单项 spu.baseAttrs[0] = [{attrId:xx,val:}]-->
<el-form ref="form" :model="spu">
<el-form-item
:label="attr.attrName"
v-for="(attr,aidx) in group.attrs"
:key="attr.attrId"
>
<el-input
v-model="dataResp.baseAttrs[gidx][aidx].attrId"
type="hidden"
v-show="false"
></el-input>
<el-select
v-model="dataResp.baseAttrs[gidx][aidx].attrValues"
:multiple="attr.valueType == 1"
filterable
allow-create
default-first-option
placeholder="请选择或输入值"
>
<el-option
v-for="(val,vidx) in attr.valueSelect.split(';')"
:key="vidx"
:label="val"
:value="val"
></el-option>
</el-select>
<el-checkbox
v-model="dataResp.baseAttrs[gidx][aidx].showDesc"
:true-label="1"
:false-label="0"
>快速展示</el-checkbox>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
<div style="margin:auto">
<el-button type="primary" @click="step = 0">上一步</el-button>
<el-button type="success" @click="generateSaleAttrs">下一步:设置销售属性</el-button>
</div>
</el-card>
</el-col>
<el-col :span="24" v-show="step==2">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>选择销售属性</span>
<el-form ref="saleform" :model="spu">
<el-form-item
:label="attr.attrName"
v-for="(attr,aidx) in dataResp.saleAttrs"
:key="attr.attrId"
>
<el-input
v-model="dataResp.tempSaleAttrs[aidx].attrId"
type="hidden"
v-show="false"
></el-input>
<el-checkbox-group v-model="dataResp.tempSaleAttrs[aidx].attrValues">
<el-checkbox
v-if="dataResp.saleAttrs[aidx].valueSelect != ''"
:label="val"
v-for="val in dataResp.saleAttrs[aidx].valueSelect.split(';')"
:key="val"
></el-checkbox>
<div style="margin-left:20px;display:inline">
<el-button
v-show="!inputVisible[aidx].view"
class="button-new-tag"
size="mini"
@click="showInput(aidx)"
>+自定义</el-button>
<el-input
v-show="inputVisible[aidx].view"
v-model="inputValue[aidx].val"
:ref="'saveTagInput'+aidx"
size="mini"
style="width:150px"
@keyup.enter.native="handleInputConfirm(aidx)"
@blur="handleInputConfirm(aidx)"
></el-input>
</div>
</el-checkbox-group>
</el-form-item>
</el-form>
</div>
<el-button type="primary" @click="step = 1">上一步</el-button>
<el-button type="success" @click="generateSkus">下一步:设置SKU信息</el-button>
</el-card>
</el-card>
</el-col>
<el-col :span="24" v-show="step==3">
<el-card class="box-card" style="width:80%;margin:20px auto">
<el-table :data="spu.skus" style="width: 100%">
<el-table-column label="属性组合">
<el-table-column
:label="item.attrName"
v-for="(item,index) in dataResp.tableAttrColumn"
:key="item.attrId"
>
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.attr[index].attrValue }}</span>
</template>
</el-table-column>
</el-table-column>
<el-table-column label="商品名称" prop="skuName">
<template slot-scope="scope">
<el-input v-model="scope.row.skuName"></el-input>
</template>
</el-table-column>
<el-table-column label="标题" prop="skuTitle">
<template slot-scope="scope">
<el-input v-model="scope.row.skuTitle"></el-input>
</template>
</el-table-column>
<el-table-column label="副标题" prop="skuSubtitle">
<template slot-scope="scope">
<el-input v-model="scope.row.skuSubtitle"></el-input>
</template>
</el-table-column>
<el-table-column label="价格" prop="price">
<template slot-scope="scope">
<el-input v-model="scope.row.price"></el-input>
</template>
</el-table-column>
<el-table-column type="expand">
<template slot-scope="scope">
<el-row>
<el-col :span="24">
<label style="display:block;float:left">选择图集 或</label>
<multi-upload
style="float:left;margin-left:10px;"
:showFile="false"
:listType="'text'"
v-model="uploadImages"
></multi-upload>
</el-col>
<el-col :span="24">
<el-divider></el-divider>
</el-col>
<el-col :span="24">
<el-card
style="width:170px;float:left;margin-left:15px;margin-top:15px;"
:body-style="{ padding: '0px' }"
v-for="(img,index) in spu.images"
:key="index"
>
<img :src="img" style="width:160px;height:120px" />
<div style="padding: 14px;">
<el-row>
<el-col :span="12">
<el-checkbox
v-model="scope.row.images[index].imgUrl"
:true-label="img"
false-label
></el-checkbox>
</el-col>
<el-col :span="12">
<el-tag v-if="scope.row.images[index].defaultImg == 1">
<input
type="radio"
checked
:name="scope.row.skuName"
@change="checkDefaultImg(scope.row,index,img)"
/>设为默认
</el-tag>
<el-tag v-else>
<input
type="radio"
:name="scope.row.skuName"
@change="checkDefaultImg(scope.row,index,img)"
/>设为默认
</el-tag>
</el-col>
</el-row>
</div>
</el-card>
</el-col>
</el-row>
<!-- 折扣,满减,会员价 -->
<el-form :model="scope.row">
<el-row>
<el-col :span="24">
<el-form-item label="设置折扣">
<label>满</label>
<el-input-number
style="width:160px"
:min="0"
controls-position="right"
v-model="scope.row.fullCount"
></el-input-number>
<label>件</label>
<label style="margin-left:15px;">打</label>
<el-input-number
style="width:160px"
v-model="scope.row.discount"
:precision="2"
:max="1"
:min="0"
:step="0.01"
controls-position="right"
></el-input-number>
<label>折</label>
<el-checkbox
v-model="scope.row.countStatus"
:true-label="1"
:false-label="0"
>可叠加优惠</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="设置满减">
<label>满</label>
<el-input-number
style="width:160px"
v-model="scope.row.fullPrice"
:step="100"
:min="0"
controls-position="right"
></el-input-number>
<label>元</label>
<label style="margin-left:15px;">减</label>
<el-input-number
style="width:160px"
v-model="scope.row.reducePrice"
:step="10"
:min="0"
controls-position="right"
></el-input-number>
<label>元</label>
<el-checkbox
v-model="scope.row.priceStatus"
:true-label="1"
:false-label="0"
>可叠加优惠</el-checkbox>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="设置会员价" v-if="scope.row.memberPrice.length>0">
<br />
<!-- @change="handlePriceChange(scope,mpidx,$event)" -->
<el-form-item v-for="(mp,mpidx) in scope.row.memberPrice" :key="mp.id">
{{mp.name}}
<el-input-number
style="width:160px"
v-model="scope.row.memberPrice[mpidx].price"
:precision="2"
:min="0"
controls-position="right"
></el-input-number>
</el-form-item>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</el-table-column>
</el-table>
<el-button type="primary" @click="step = 2">上一步</el-button>
<el-button type="success" @click="submitSkus">下一步:保存商品信息</el-button>
</el-card>
</el-col>
<el-col :span="24" v-show="step==4">
<el-card class="box-card" style="width:80%;margin:20px auto">
<h1>保存成功</h1>
<el-button type="primary" @click="addAgian">继续添加</el-button>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import CategoryCascader from "../common/category-cascader";
import BrandSelect from "../common/brand-select";
import MultiUpload from "@/components/upload/multiUpload";
export default {
//import引入的组件需要注入到对象中才能使用
components: { CategoryCascader, BrandSelect, MultiUpload },
props: {},
data() {
return {
catPathSub: null,
brandIdSub: null,
uploadDialogVisible: false,
uploadImages: [],
step: 0,
//spu_name spu_description catalog_id brand_id weight publish_status
spu: {
//要提交的数据
spuName: "",
spuDescription: "",
catalogId: 0,
brandId: "",
weight: "",
publishStatus: 0,
decript: [], //商品详情
images: [], //商品图集,最后sku也可以新增
bounds: {
//积分
buyBounds: 0,
growBounds: 0
},
baseAttrs: [], //基本属性
skus: [] //所有sku信息
},
spuBaseInfoRules: {
spuName: [
{ required: true, message: "请输入商品名字", trigger: "blur" }
],
spuDescription: [
{ required: true, message: "请编写一个简单描述", trigger: "blur" }
],
catalogId: [
{ required: true, message: "请选择一个分类", trigger: "blur" }
],
brandId: [
{ required: true, message: "请选择一个品牌", trigger: "blur" }
],
decript: [
{ required: true, message: "请上传商品详情图集", trigger: "blur" }
],
images: [
{ required: true, message: "请上传商品图片集", trigger: "blur" }
],
weight: [
{
type: "number",
required: true,
message: "请填写正确的重量值",
trigger: "blur"
}
]
},
dataResp: {
//后台返回的所有数据
attrGroups: [],
baseAttrs: [],
saleAttrs: [],
tempSaleAttrs: [],
tableAttrColumn: [],
memberLevels: [],
steped: [false, false, false, false, false]
},
inputVisible: [],
inputValue: []
};
},
//计算属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {
uploadImages(val) {
//扩展每个skus里面的imgs选项
let imgArr = Array.from(new Set(this.spu.images.concat(val)));
//{imgUrl:"",defaultImg:0} 由于concat每次迭代上次,有很多重复。所以我们必须得到上次+这次的总长
this.spu.skus.forEach((item, index) => {
let len = imgArr.length - this.spu.skus[index].images.length; //还差这么多
if (len > 0) {
let imgs = new Array(len);
imgs = imgs.fill({ imgUrl: "", defaultImg: 0 });
this.spu.skus[index].images = item.images.concat(imgs);
}
});
this.spu.images = imgArr; //去重
console.log("this.spu.skus", this.spu.skus);
}
},
//方法集合
methods: {
addAgian() {
this.step = 0;
this.resetSpuData();
},
resetSpuData() {
this.spu = {
spuName: "",
spuDescription: "",
catalogId: 0,
brandId: "",
weight: "",
publishStatus: 0,
decript: [],
images: [],
bounds: {
buyBounds: 0,
growBounds: 0
},
baseAttrs: [],
skus: []
};
},
handlePriceChange(scope, mpidx, e) {
this.spu.skus[scope.$index].memberPrice[mpidx].price = e;
},
getMemberLevels() {
this.$http({
url: this.$http.adornUrl("/member/memberlevel/list"),
method: "get",
params: this.$http.adornParams({
page: 1,
limit: 500
})
})
.then(({ data }) => {
this.dataResp.memberLevels = data.page.list;
})
.catch(e => {
console.log(e);
});
},
showInput(idx) {
console.log("``````", this.view);
this.inputVisible[idx].view = true;
// this.$refs['saveTagInput'+idx].$refs.input.focus();
},
checkDefaultImg(row, index, img) {
console.log("默认图片", row, index);
//这个图片被选中了,
row.images[index].imgUrl = img; //默认选中
row.images[index].defaultImg = 1; //修改标志位;
//修改其他人的标志位
row.images.forEach((item, idx) => {
if (idx != index) {
row.images[idx].defaultImg = 0;
}
});
},
handleInputConfirm(idx) {
let inputValue = this.inputValue[idx].val;
if (inputValue) {
// this.dynamicTags.push(inputValue);
if (this.dataResp.saleAttrs[idx].valueSelect == "") {
this.dataResp.saleAttrs[idx].valueSelect = inputValue;
} else {
this.dataResp.saleAttrs[idx].valueSelect += ";" + inputValue;
}
}
this.inputVisible[idx].view = false;
this.inputValue[idx].val = "";
},
collectSpuBaseInfo() {
//spuBaseForm
this.$refs.spuBaseForm.validate(valid => {
if (valid) {
this.step = 1;
this.showBaseAttrs();
} else {
return false;
}
});
},
generateSaleAttrs() {
//把页面绑定的所有attr处理到spu里面,这一步都要做
this.spu.baseAttrs = [];
this.dataResp.baseAttrs.forEach(item => {
item.forEach(attr => {
let { attrId, attrValues, showDesc } = attr;
//跳过没有录入值的属性
if (attrValues != "") {
if (attrValues instanceof Array) {
//多个值用;隔开
attrValues = attrValues.join(";");
}
this.spu.baseAttrs.push({ attrId, attrValues, showDesc });
}
});
});
console.log("baseAttrs", this.spu.baseAttrs);
this.step = 2;
this.getShowSaleAttr();
},
generateSkus() {
this.step = 3;
//根据笛卡尔积运算进行生成sku
let selectValues = [];
this.dataResp.tableAttrColumn = [];
this.dataResp.tempSaleAttrs.forEach(item => {
if (item.attrValues.length > 0) {
selectValues.push(item.attrValues);
this.dataResp.tableAttrColumn.push(item);
}
});
let descartes = this.descartes(selectValues);
//[["黑色","6GB","移动"],["黑色","6GB","联通"],["黑色","8GB","移动"],["黑色","8GB","联通"],
//["白色","6GB","移动"],["白色","6GB","联通"],["白色","8GB","移动"],["白色","8GB","联通"],
//["蓝色","6GB","移动"],["蓝色","6GB","联通"],["蓝色","8GB","移动"],["蓝色","8GB","联通"]]
console.log("生成的组合", JSON.stringify(descartes));
//有多少descartes就有多少sku
let skus = [];
descartes.forEach((descar, descaridx) => {
let attrArray = []; //sku属性组
descar.forEach((de, index) => {
//构造saleAttr信息
let saleAttrItem = {
attrId: this.dataResp.tableAttrColumn[index].attrId,
attrName: this.dataResp.tableAttrColumn[index].attrName,
attrValue: de
};
attrArray.push(saleAttrItem);
});
//先初始化几个images,后面的上传还要加
let imgs = [];
this.spu.images.forEach((img, idx) => {
imgs.push({ imgUrl: "", defaultImg: 0 });
});
//会员价,也必须在循环里面生成,否则会导致数据绑定问题
let memberPrices = [];
if (this.dataResp.memberLevels.length > 0) {
for (let i = 0; i < this.dataResp.memberLevels.length; i++) {
if (this.dataResp.memberLevels[i].priviledgeMemberPrice == 1) {
memberPrices.push({
id: this.dataResp.memberLevels[i].id,
name: this.dataResp.memberLevels[i].name,
price: 0
});
}
}
}
//;descaridx,判断如果之前有就用之前的值;
let res = this.hasAndReturnSku(this.spu.skus, descar);
if (res === null) {
skus.push({
attr: attrArray,
skuName: this.spu.spuName + " " + descar.join(" "),
price: 0,
skuTitle: this.spu.spuName + " " + descar.join(" "),
skuSubtitle: "",
images: imgs,
descar: descar,
fullCount: 0,
discount: 0,
countStatus: 0,
fullPrice: 0.0,
reducePrice: 0.0,
priceStatus: 0,
memberPrice: new Array().concat(memberPrices)
});
} else {
skus.push(res);
}
});
this.spu.skus = skus;
console.log("结果!!!", this.spu.skus, this.dataResp.tableAttrColumn);
},
//判断如果包含之前的sku的descar组合,就返回这个sku的详细信息;
hasAndReturnSku(skus, descar) {
let res = null;
if (skus.length > 0) {
for (let i = 0; i < skus.length; i++) {
if (skus[i].descar.join(" ") == descar.join(" ")) {
res = skus[i];
}
}
}
return res;
},
getShowSaleAttr() {
//获取当前分类可以使用的销售属性
if (!this.dataResp.steped[1]) {
this.$http({
url: this.$http.adornUrl(
`/product/attr/sale/list/${this.spu.catalogId}`
),
method: "get",
params: this.$http.adornParams({
page: 1,
limit: 500
})
}).then(({ data }) => {
this.dataResp.saleAttrs = data.page.list;
data.page.list.forEach(item => {
this.dataResp.tempSaleAttrs.push({
attrId: item.attrId,
attrValues: [],
attrName: item.attrName
});
this.inputVisible.push({ view: false });
this.inputValue.push({ val: "" });
});
this.dataResp.steped[1] = true;
});
}
},
showBaseAttrs() {
if (!this.dataResp.steped[0]) {
this.$http({
url: this.$http.adornUrl(
`/product/attrgroup/${this.spu.catalogId}/withattr`
),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
//先对表单的baseAttrs进行初始化
data.data.forEach(item => {
let attrArray = [];
item.attrs.forEach(attr => {
attrArray.push({
attrId: attr.attrId,
attrValues: "",
showDesc: attr.showDesc
});
});
this.dataResp.baseAttrs.push(attrArray);
});
this.dataResp.steped[0] = 0;
this.dataResp.attrGroups = data.data;
});
}
},
submitSkus() {
console.log("~~~~~", JSON.stringify(this.spu));
this.$confirm("将要提交商品数据,需要一小段时间,是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$http({
url: this.$http.adornUrl("/product/spuinfo/save"),
method: "post",
data: this.$http.adornData(this.spu, false)
}).then(({ data }) => {
if (data.code == 0) {
this.$message({
type: "success",
message: "新增商品成功!"
});
this.step = 4;
} else {
this.$message({
type: "error",
message: "保存失败,原因【" + data.msg + "】"
});
}
});
})
.catch(e => {
console.log(e);
this.$message({
type: "info",
message: "已取消"
});
});
},
//笛卡尔积运算
descartes(list) {
//parent上一级索引;count指针计数
var point = {};
var result = [];
var pIndex = null;
var tempCount = 0;
var temp = [];
//根据参数列生成指针对象
for (var index in list) {
if (typeof list[index] == "object") {
point[index] = { parent: pIndex, count: 0 };
pIndex = index;
}
}
//单维度数据结构直接返回
if (pIndex == null) {
return list;
}
//动态生成笛卡尔积
while (true) {
for (var index in list) {
tempCount = point[index]["count"];
temp.push(list[index][tempCount]);
}
//压入结果数组
result.push(temp);
temp = [];
//检查指针最大值问题
while (true) {
if (point[index]["count"] + 1 >= list[index].length) {
point[index]["count"] = 0;
pIndex = point[index]["parent"];
if (pIndex == null) {
return result;
}
//赋值parent进行再次检查
index = pIndex;
} else {
point[index]["count"]++;
break;
}
}
}
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
this.catPathSub = PubSub.subscribe("catPath", (msg, val) => {
this.spu.catalogId = val[val.length - 1];
});
this.brandIdSub = PubSub.subscribe("brandId", (msg, val) => {
this.spu.brandId = val;
});
this.getMemberLevels();
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {
PubSub.unsubscribe(this.catPathSub);
PubSub.unsubscribe(this.brandIdSub);
}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>
选择分类组件:src/views/modules/common/category-cascader.vue
<template>
<!--
使用说明:
1)、引入category-cascader.vue
2)、语法:<category-cascader :catelogPath.sync="catelogPath"></category-cascader>
解释:
catelogPath:指定的值是cascader初始化需要显示的值,应该和父组件的catelogPath绑定;
由于有sync修饰符,所以cascader路径变化以后自动会修改父的catelogPath,这是结合子组件this.$emit("update:catelogPath",v);做的
-->
<div>
<el-cascader
filterable
clearable
placeholder="试试搜索:手机"
v-model="paths"
:options="categorys"
:props="setting"
></el-cascader>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
//import引入的组件需要注入到对象中才能使用
components: {},
//接受父组件传来的值
props: {
catelogPath: {
type: Array,
default(){
return [];
}
}
},
data() {
//这里存放数据
return {
setting: {
value: "catId",
label: "name",
children: "children"
},
categorys: [],
paths: this.catelogPath
};
},
watch:{
catelogPath(v){
this.paths = this.catelogPath;
},
paths(v){
this.$emit("update:catelogPath",v);
//还可以使用pubsub-js进行传值
this.PubSub.publish("catPath",v);
}
},
//方法集合
methods: {
getCategorys() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
this.categorys = data.data;
});
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getCategorys();
}
};
</script>
<style scoped>
</style>
相关文章:
vue工程利用pubsub-js实现兄弟组件之间的通信
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)