前端如何展示商品属性:SKU多维属性状态判断算法的应用-Vue 实现

由于公司开发了一个电商项目,涉及到前台商品属性的展示,所以百度上找了一下!找到了 周琪力写的一个算法例子,因为作者只有jQuery 实现demo, 自己仿照 demo 实现了一个 vue 的!

周琪力原文: http://mp.weixin.qq.com/s?__biz=MzIwNjQwMzUwMQ==&mid=2247484853&idx=1&sn=bed59c1d83c3aeb4bf7881be8dbdd917&chksm=97236777a054ee61fc3cef07eb4b164fa28e26917ce0a409d876964ad3c2ee3f90a000e29beb#rd

原文的demo实现:http://codepen.io/keelii/pen/RoOzgb

实现demo

效果图:
这里写图片描述

附上代码:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Vue 实现商品属性的计算显示</title>
	<style>
		body {
            font-size: 12px;
        }
        dt {
            width: 100px;
            text-align: right;
        }
        dl {
            clear: both;
            overflow:hidden;
        }
        dl.hl {
            background:#ddd;
        }
        dt, dd {
            float:left;
            height: 40px;
            line-height: 40px;
            margin-left: 10px;
        }
        button {
            font-size: 14px;
            font-weight: bold;
            width: 100px;
            height: 30px;
            margin: 0 10px;
        }

        .disabled {
            color:#999;
            border: 1px dashed #666;
        }
        .active {
            color: red;
        }

        .top-but {
        	margin: 10px;
        }

        #skuId {
        	height: 24px;
        	font-size: 14px;
        	line-height: 24px;
        }
	</style>
</head>
<body>
	<textarea id="values" style="width:600px;height:100px">
		[
		   { "颜色": "红", "尺码": "大", "型号": "A", "skuId": "3158055" },
		   { "颜色": "白", "尺码": "大", "型号": "A", "skuId": "3158054" },
		   { "颜色": "白", "尺码": "中", "型号": "B", "skuId": "3133859" },
		   { "颜色": "蓝", "尺码": "小", "型号": "C", "skuId": "3516833" }
		]
	</textarea>
	<div id="app">
		<label> 
			默认选中 :  
			<input type="text" name="skuId" id="skuId" v-bind:value="skuId">
		</label>
		<button @click="getTextareaData" class="top-but"> 重新加载数据 </button> 当前属性ID:{{ skuId }}
		<dl v-for="item, key in list.result" class="content" v-bind:class="{hl: highKeys[key]}">
			<dt> {{key}} : </dt> 
			<dd>
				<button
					class="item"
					v-for="value in item"
					@click="handleActive(key, value)"
					v-bind:class="{active: value.active, disabled: !value.active && value.disabled}" 
					> {{ value.name }} </button> 
			</dd>
		</dl>
		已经选择:{{ message }}
	</div>
</body>
<script src="https://unpkg.com/vue "></script>
<script>
	let vue = new Vue({
		el: "#app",
		data(){
			return {
				data: [],
				skuId: "",
				skuName: "skuId",
				// 属性名称信息
				keys: [],
				// 数据集合{list.result list.items}
				list: {},
				// 分隔符
				spliter: '\u2299',
				result: {},
				message: "",
				highKeys: {},
			};
		},
		methods: {
			powerset(arr) {
			    let ps = [[]];
			    for (let i = 0; i < arr.length; i++) {
			        for (let j = 0, len = ps.length; j < len; j++) {
			            ps.push(ps[j].concat(arr[i]));
			        }
			    }

			    return ps;
			},

			/**
			 * 初始化数据
			 * @return 
			 */
			initData() {
				this.result = {};
				this.keys = this.getAllKeys();
				for (let i = 0; i < this.keys.length; i ++) {
					this.highKeys[this.keys[i]] = false;
				}

				this.list = this.combineAttr(this.data, this.keys);
				this.initSeleted(this.skuId);
				this.buildResult(this.list.items)
				this.updateStatus(this.getSelectedItem());
				this.showResult();
			},

			/**
			 * 获取输入表单中的数据进行初始化
			 * @return
			 */
			getTextareaData() {
				let data = document.getElementById('values').value;
				let skuId = document.getElementById("skuId").value;
				
				try {
					this.data = JSON.parse(data);
					let isHas = false;
					for (let i = 0; i < this.data.length; i ++) {
						if (skuId == this.data[i][this.skuName]) {
							isHas = true;
							break
						}
					}

					this.skuId = isHas ? skuId : this.data[0][this.skuName];
					this.initData();
				} catch (e) {
					this.data = [];
				}
			},

			/**
			 * 正常属性点击
			 */
			handleNormalClick(key, value) {
				for (let i in this.list.result[key]) {
					if (i != value.name) {
						this.list.result[key][i].active = false;
					} else {
						this.list.result[key][i].active = true;
					}
				}
			},

			/**
			 * 无效属性点击
			 */
			handleDisableClick(key, value) {
				this.list.result[key][value.name]["disabled"] = false;
			    // 清空高亮行的已选属性状态(因为更新的时候默认会跳过已选状态)
			    for (let i in this.list.result) {
			    	if (i != key) {
			    		for (let x in this.list.result[i]) {
			    			this.list.result[i][x].active = false;
			    		}
			    	}
			    }
	
			    this.updateStatus(this.getSelectedItem());
			},

			/**
			 * 高亮行
			 */
			highAttributes: function() {
				for (let key in this.list.result) {
					this.highKeys[key] = true;
					for (let attr in this.list.result[key]) {
						if (this.list.result[key][attr].active === true) {
							this.highKeys[key] = false;
							break;
						}
					}
				}
			},

			/**
			 * 点击事件处理
			 * @param  key   点击的行
			 * @param  value 点击的按钮的数据
			 */
			handleActive: function(key, value) {
				if (value.active == true) {
					return false;
				}

	            this.handleNormalClick(key, value);
	            if (value.disabled === true) {
	                this.handleDisableClick(key, value);
	            }

	            this.updateStatus(this.getSelectedItem());
	            this.highAttributes();
	            this.showResult();
			},

			/**
			 * 计算属性
			 * @param  {[type]} data [description]
			 * @param  {[type]} keys [description]
			 * @return {[type]}      [description]
			 */
			combineAttr(data, keys) {
			    let allKeys = []
			    let result = {}

			    for (let i = 0; i < data.length; i++) {
			        let item = data[i]
			        let values = []

			        for (let j = 0; j < keys.length; j++) {
			            let key = keys[j]
			            if (!result[key]) {
			            	result[key] = {};
			            }

			            if (!result[key][item[key]]) {
			            	result[key][item[key]] = {"name": item[key], "active": false, "disabled": true};
			            }

			            values.push(item[key]);
			        }

			        allKeys.push({
			            path: values.join(this.spliter),
			            sku: item['skuId']
			        });
			    }

			    return {
			        result: result,
			        items: allKeys
			    }
			},

			/**
			 * 获取所有属性
			 * @return {[type]} [description]
			 */
			getAllKeys() {
				let arrKeys = [];
				for (let attribute in this.data[0]) {
					if (!this.data[0].hasOwnProperty(attribute)) {
						continue;
					} 

					if (attribute !== this.skuName) {
						arrKeys.push(attribute);
					}
				}

				return arrKeys;
			},

			getAttruites(arr) {
				let result = []
			    for (let i = 0; i < arr.length; i++) { 
			    	result.push(arr[i].path) 
			    }

			    return result
			},

			/**
			 * 生成所有子集是否可选、库存状态 map
			 */
			buildResult(items) {
			    let allKeys = this.getAttruites(items)

			    for (let i = 0; i < allKeys.length; i++) {
			        let curr = allKeys[i];
			        let sku = items[i].sku;
			        let values = curr.split(this.spliter);
			        let allSets = this.powerset(values);

			        // 每个组合的子集
			        for (let j = 0; j < allSets.length; j++) {
			            let set = allSets[j]
			            let key = set.join(this.spliter)

			            if (this.result[key]) {
			                this.result[key].skus.push(sku)
			            } else {
			                this.result[key] = {
			                    skus: [sku]
			                }
			            }
			        }
			    }
			},

			/**
			 * 获取选中的信息
			 * @return Array 
			 */
			getSelectedItem() {
				let result = [];
				for (let attr in this.list.result) {
					let attributeName = '';
					for (let attribute in this.list.result[attr]) {
						if (this.list.result[attr][attribute].active === true) {
							attributeName = attribute;
						}
					}

					result.push(attributeName);
				}

			    return result
			},

			/**
			 * 更新所有属性状态
			 */
			updateStatus(selected) {
			    for (let i = 0; i < this.keys.length; i++) {
			        let key = this.keys[i],
			         	data = this.list.result[key],
			        	hasActive = !!selected[i],
			        	copy = selected.slice();

			        for (let j in data) {
			            let item = data[j]["name"];
			            if (selected[i] == item) {
			            	continue
			            }

			            copy[i] = item
			            let curr = this.trimSpliter(copy.join(this.spliter), this.spliter);
			            this.list.result[key][j]["disabled"]  = this.result[curr] ? false : true;
			        }
			    }
			},

			trimSpliter(str, spliter) {
			    // ⊙abc⊙ => abc
			    // ⊙a⊙⊙b⊙c⊙ => a⊙b⊙c
			    let reLeft    = new RegExp('^' + spliter + '+', 'g');
			    let reRight   = new RegExp(spliter + '+$', 'g');
			    let reSpliter = new RegExp(spliter + '+', 'g');
			    return str.replace(reLeft, '')
			        .replace(reRight, '')
			        .replace(reSpliter, spliter)
			},

			/**
			 * 初始化选中
			 * @param  mixed|Int|String skuId 需要选中的skuId
			 * @return {[type]}       [description]
			 */
			initSeleted(skuId) {
			    for (let i in this.data) {
			    	if (this.data[i][this.skuName] == skuId) {
			    		for (let x in this.data[i]) {
			    			if (x !== this.skuName) {
			    				this.list.result[x][this.data[i][x]].active = true;
			    			}
			    		}
			    		break;
			    	}
			    }
			},

			/**
			 * 显示选中的信息
			 * @return 
			 */
			showResult() {
			    let result = this.getSelectedItem()
			    let s = []
			    for (let i = 0; i < result.length; i++) {
			        let item = result[i];
			        if (!!item) {
			            s.push(item)
			        }
			    }

			    if (s.length == this.keys.length) {
			        let curr = this.result[s.join(this.spliter)]
			        if (curr) {
			            s = s.concat(curr.skus)
			            this.skuId = curr.skus[0];
			        }
			        
			        this.message = s.join('\u3000-\u3000');
			    }
			}
		},

		created() {
			this.getTextareaData();
		}
	})
</script>
</html>

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值