vue-sku-form
组件
https://hooray.github.io/vue-sku-form/guide/
另外一个比较简单 的
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Vue 实现商品属性的计算显示</title>
    <style>
        body {
            font-size: 12px;
        }
        dt {
            width: 100px;
            text-align: right;
        }
        dl {
            clear: both;
            overflow: hidden;
        }
        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;
        }
        .active {
            color: red;
            background-color:#8EC6FF;
        }
    </style>
</head>
<body>
<div id="app">
<template>
{{ selectedTypeDesc }}
<dl v-if="JSON.stringify(selectedResult)!='{}'">
当前属性:{{'编号:'+selectedResult.skuId+'-库存:'+selectedResult.remain+'-价格:'+selectedResult.price}}
</dl>
<dl v-for="(item, key) in specTypes">
<dt> {{key}} :</dt>
<dd>
<button
class="item"
v-for="value in item"
@click="handleActive(key, value)"
:disabled="value.disabled"
v-bind:class="{active: value.active}"
> {{ value.name }}
</button>
</dd>
</dl>
</template>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const tempData = {};
    let types = [
        {typeTitle: 'A', typeValues: ['大','中', '小']},
        {typeTitle: 'B', typeValues: ['黄', '绿','紫','白','黑','红']},
        {typeTitle: 'C', typeValues: ['重', '轻']}];
    let skuList = {
        '大-黄-重': {skuId: 1, remain: 30, price: 99},
        '小-红-轻': {skuId: 2, remain: 10, price: 56},
        '大-红-重': {skuId: 3, remain: 2, price: 198}
    };
    let vue = new Vue({
        el: "#app",
        data() {
            return {
                specTypes: {skuId: 1, remain: 30, price: 99},//规则属性
                specValues: [],//规则属性节点
                skuList: {},//sku对象,后台格式化好
                selectedResult: {},//选中的最终规则-展现用
                selectedTypeDesc: "请选择",//选中后展示信息
                selectedType: {},//选中的规则
            };
        },
        methods: {
            /**
             * 点击按钮节点触发事件
             */
            handleActive: function (key, node) {
                node.active = !node.active;
                if (node.active) {
                    this.selectedType[key] = node.name;
                    //更新同组其他按钮状态
                    this.specTypes[key].forEach(x => {
                        if (x.name != node.name) {
                            x.active = false;
                        }
                    });
                } else {
                    this.selectedType[key] = '';
                }
                this.refreshSpecTypeNodes()
                // console.log('this.selectedType', this.selectedType)
                // console.log('this.specTypes', this.specTypes)
                let selectedSku = this.getSelectedSku();
                this.selectedTypeDesc = selectedSku.desc;
                this.selectedResult = selectedSku.sku;
            },
            /**
             * 获取当前选择的规则信息
             */
            getSelectedSku: function () {
                let m = "请选择", selected = [], sku = {}, keys = [], values = [];
                keys = Object.keys(this.selectedType);
                keys.forEach(x => {
                    if (!Object.is(this.selectedType[x], null) && !Object.is(this.selectedType[x], '')) {
                        selected.push(x + ":" + this.selectedType[x]);
                    }
                })
                values = Object.values(this.selectedType);
                if (values.length === keys.length) {
                    let key = values.join('-');
                    if (this.skuList.hasOwnProperty(key)) {
                        sku = this.skuList[key];
                    }
                }
                if (selected.length > 0) {
                    m = "已选择:";
                    m += selected.join(";");
                }
                return {
                    desc: m,
                    sku: sku
                };
            },
            /***
             * 刷新规则属性状态
             */
            refreshSpecTypeNodes() {
                let types = this.specTypes,
                    currentSelected = this.selectedType;
                Object.keys(types).forEach(x => {
                    let nodes = types[x];
                    for (let i = 0; i < nodes.length; i++) {
                        let node = nodes[i], tempSelected = {};
                        tempSelected[x] = node.name;
                        let selected = Object.values(Object.assign({}, currentSelected, tempSelected));
                        let s = this.getRemainByKey(selected.filter(x => x));
                        if (s > 0) {
                            node.disabled = false;
                        } else {
                            node.disabled = true;
                            node.active = false;
                        }
                    }
                })
            },
            /**
             * 初始化
             */
            init: function () {
                let result = this.recombineSpecTypes(types);
                this.specTypes = result;
                this.skuList = skuList;
                this.selectedType = this.initSelectedType(result);
                this.selectedTypeDesc = this.getSelectedSku().desc;
                this.refreshSpecTypeNodes()
                console.log('this.specTypes', this.specTypes)
            },
            /**
             * 初始化已选规则
             */
            initSelectedType: function (types) {
                let selectedType = {};
                let keys = Object.keys(types);
                keys.forEach(x => {
                    selectedType[x] = '';
                })
                return selectedType;
            },
            /**
             * 重构规则结构
             *
             */
            recombineSpecTypes: function (types) {
                let result = {};
                let specValues = [];
                for (let i = 0; i < types.length; i++) {
                    let item = types[i];
                    specValues.push(item.typeValues);
                    let values = [];
                    for (let j = 0; j < item.typeValues.length; j++) {
                        let value = item.typeValues[j];
                        values.push({
                            'active': false,
                            'name': value,
                            'disabled': false
                        });
                    }
                    result[item.typeTitle] = values
                }
                this.specValues = specValues;
                return result;
            },
            /**
             * 获取所选值对应的库存
             * @param selected 数组['大','黄']
             * @returns {number|*}
             */
            getRemainByKey: function (selected) {
                const key = selected.join('-')
                if (typeof tempData[key] !== 'undefined') {
                    return tempData[key]
                }
                if (selected.length === this.specValues.length) {
                    return this.skuList[key] ? tempData[key] = this.skuList[key].remain : tempData[key] = 0
                }
                let remain = 0
                let tempSelected = []
                for (let i = 0; i < this.specValues.length; i++) {
                    const exist = this.specValues[i].find(_item => _item === selected[0])
                    if (exist && selected.length > 0) {
                        tempSelected.push(selected.shift())
                    } else {
                        this.specValues[i].forEach(_item => {
                            remain += this.getRemainByKey(tempSelected.concat(_item, selected))
                        })
                        break
                    }
                }
                return tempData[key] = remain
            },
        },
        created() {
            this.init();
        }
    })
</script>
</html>