VUE-SKU组件源码及使用教程

这是一个基于 Vue & ElementUI 的电商 SKU 表单配置组件
组件名SkuForm.vue

<template>
    <div class="sku-container">
        <div v-if="!disabled" class="sku-check">
            <div v-if="theme == 1" class="theme-1">
                <el-card v-for="(item, index) in myAttribute" :key="index" class="item" shadow="never">
                    <div slot="header">{{ item.name }}</div>
                    <el-checkbox v-for="(item2, index2) in item.item" :key="index2" v-model="item2.checked" :label="item2.name" size="small" />
                    <el-input v-if="item.canAddAttribute" v-model="item.addAttribute" size="small" placeholder="新增一个规格" class="add-attr" @keyup.enter.native="onAddAttribute(index)">
                        <el-button slot="append" size="small" icon="el-icon-plus" @click="onAddAttribute(index)">添加</el-button>
                    </el-input>
                </el-card>
            </div>
            <el-table v-else :data="myAttribute" :show-header="false" class="theme-2">
                <el-table-column prop="name" width="120" :resizable="false" />
                <el-table-column>
                    <template slot-scope="scope">
                        <el-checkbox v-for="(item2, index2) in scope.row.item" :key="index2" v-model="item2.checked" :label="item2.name" size="small" />
                    </template>
                </el-table-column>
                <el-table-column width="250">
                    <template slot-scope="scope">
                        <el-input v-model="scope.row.addAttribute" size="small" placeholder="新增一个规格" class="add-attr" @keyup.enter.native="onAddAttribute(scope.$index)">
                            <el-button slot="append" size="small" icon="el-icon-plus" @click="onAddAttribute(scope.$index)">添加</el-button>
                        </el-input>
                    </template>
                </el-table-column>
            </el-table>
        </div>
        <div class="sku-list">
            <el-form ref="form" :model="form" status-icon inline-message>
                <el-table :data="form.skuData" stripe border highlight-current-row>
                    <!-- 考虑到异步加载的情况,如果 attribute 数据先加载完成,则表头会立马展示,效果不理想,故使用emitAttribute 数据,该数据为计算属性,通过 myAttribute 生成,结构与 attribute 一致 -->
                    <el-table-column v-if="emitAttribute.length > 0" type="index" width="50" align="center" :resizable="false" />
                    <el-table-column v-for="(attr, index) in emitAttribute" :key="`attribute-${index}`" :label="attr.name" :prop="attr.name" width="120" align="center" :resizable="false" sortable />
                    <el-table-column v-for="(item, index) in structure" :key="`structure-${index}`" :label="item.label" :prop="item.name" align="center" :resizable="false" min-width="120px">
                        <!-- 自定义表头 -->
                        <template slot="header">
                            <span :class="{'required_title': item.required}">
                                {{ item.label }}
                            </span>
                            <el-tooltip v-if="item.tip" effect="dark" :content="item.tip" placement="top">
                                <i class="el-icon-info" />
                            </el-tooltip>
                        </template>
                        <!-- 自定义表格内部展示 -->
                        <template slot-scope="scope">
                            <!-- 增加是 key 是为了保证异步验证不会出现 skuData 数据变化后无法验证的 bug -->
                            <el-form-item v-if="item.type == 'input'" :key="`structure-input-${index}-${scope.row.sku}`" :prop="'skuData.' + scope.$index + '.' + item.name" :rules="rules[item.name]">
                                <el-input v-model="scope.row[item.name]" :placeholder="`请输入${item.label}`" size="small" />
                            </el-form-item>
                            <el-form-item v-else-if="item.type == 'slot'" :key="`structure-input-${index}-${scope.row.sku}`" :prop="'skuData.' + scope.$index + '.' + item.name" :rules="rules[item.name]">
                                <slot :name="item.name" :$index="scope.$index" :row="scope.row" :column="scope.column" />
                            </el-form-item>
                        </template>
                    </el-table-column>
                    <!-- 批量设置,当 sku 数超过 2 个时出现 -->
                    <template v-if="isBatch && form.skuData.length > 2" slot="append">
                        <el-table :data="[{}]" :show-header="false">
                            <el-table-column :width="attribute.length * 120 + 50" align="center" :resizable="false">批量设置</el-table-column>
                            <el-table-column v-for="(item, index) in structure" :key="`batch-structure-${index}`" align="center" :resizable="false" min-width="120px">
                                <el-input v-if="item.type == 'input' && item.batch != false" v-model="batch[item.name]" :placeholder="`填写一个${item.label}`" size="small" @keyup.enter.native="onBatchSet(item.name)" />
                            </el-table-column>
                        </el-table>
                    </template>
                </el-table>
            </el-form>
        </div>
    </div>
</template>

<script>
export default {
    name: 'SkuForm',
    props: {
        /**
         * 原始规格数据
         * sourceAttribute: [
         *   { name: '颜色', item: ['黑', '金', '白'] },
         *   { name: '内存', item: ['16G', '32G'] },
         *   { name: '运营商', item: ['电信', '移动', '联通'] }
         * ]
         */
        sourceAttribute: {
            type: Array,
            default: () => []
        },
        /**
         * 已使用的规格数据,用于复原数据,支持.sync修饰符
         * attribute: [
         *   { name: '颜色', item: ['黑'] },
         *   { name: '运营商', item: ['电信', '移动', '联通'] }
         * ]
         */
        attribute: {
            type: Array,
            default: () => []
        },
        /**
         * 用于复原sku数据,支持.sync修饰符
         * sku: [
         *   { sku: '黑;电信', price: 1, stock: 1 },
         *   { sku: '黑;移动', price: 2, stock: 2 },
         *   { sku: '黑;联通', price: 3, stock: 3 }
         * ]
         */
        sku: {
            type: Array,
            default: () => []
        },
        /**
         * 表格结构,注意name字段,用于输出sku数据
         */
        structure: {
            type: Array,
            default: () => [
                { name: 'price', type: 'input', label: '价格' },
                { name: 'stock', type: 'input', label: '库存' }
            ]
        },
        // sku 字段分隔符
        separator: {
            type: String,
            default: ';'
        },
        // 无规格的 sku
        emptySku: {
            type: String,
            default: ''
        },
        // 是否显示 sku 选择栏
        disabled: {
            type: Boolean,
            default: false
        },
        // 主题风格
        theme: {
            type: Number,
            default: 1
        },
        // 是否开启异步加载
        async: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            isInit: false,
            myAttribute: [],
            form: {
                skuData: []
            },
            batch: {}
        }
    },
    computed: {
        rules() {
            // 重新生成验证规则
            let rules = {}
            this.structure.forEach(v => {
                if (v.type == 'input') {
                    rules[v.name] = []
                    if (v.required) {
                        rules[v.name].push({ required: true, message: `${v.label}不能为空`, trigger: 'blur' })
                    }
                    if (v.validate) {
                        rules[v.name].push({ validator: this.customizeValidate, trigger: 'blur' })
                    }
                } else if (v.type == 'slot') {
                    rules[v.name] = []
                    if (v.required) {
                        rules[v.name].push({ required: true, message: `${v.label}不能为空`, trigger: ['change', 'blur'] })
                    }
                    if (v.validate) {
                        rules[v.name].push({ validator: this.customizeValidate, trigger: ['change', 'blur'] })
                    }
                }
            })
            return rules
        },
        isBatch() {
            return this.structure.some(item => {
                return item.type == 'input' && item.batch != false
            })
        },
        // 将 myAttribute 数据还原会 attribute 数据的结构,用于更新 attribute
        emitAttribute() {
            let attribute = []
            this.myAttribute.forEach(v1 => {
                const obj = {
                    name: v1.name,
                    item: []
                }
                v1.item.forEach(v2 => {
                    if (v2.checked) {
                        obj.item.push(v2.name)
                    }
                })
                if (obj.item.length !== 0) {
                    attribute.push(obj)
                }
            })
            return attribute
        }
    },
    watch: {
        myAttribute: {
            handler() {
                if (!this.isInit) {
                    // 更新父组件
                    this.$emit('update:attribute', this.emitAttribute)
                }
                // 解决通过 $emit 更新后无法拿到 attribute 最新数据的问题
                this.$nextTick(() => {
                    if (this.attribute.length !== 0) {
                        this.combinationAttribute()
                    } else {
                        this.form.skuData = []
                        const obj = {
                            sku: this.emptySku
                        }
                        this.structure.forEach(v => {
                            if (!(v.type == 'slot' && v.skuProperty == false)) {
                                obj[v.name] = typeof v.defaultValue != 'undefined' ? v.defaultValue : ''
                            }
                        })
                        this.form.skuData.push(obj)
                    }
                    this.clearValidate()
                })
            },
            deep: true
        },
        'form.skuData': {
            handler(newValue, oldValue) {
                if (!this.isInit || (newValue.length == 1 && newValue[0].sku == this.emptySku)) {
                    // 如果有老数据,或者 sku 数据为空,则更新父级 sku 数据
                    if (oldValue.length || !this.sku.length) {
                        // 更新父组件
                        const arr = []
                        newValue.forEach(v1 => {
                            const obj = {
                                sku: v1.sku
                            }
                            this.structure.forEach(v2 => {
                                if (!(v2.type == 'slot' && v2.skuProperty == false)) {
                                    obj[v2.name] = v1[v2.name] || (typeof v2.defaultValue != 'undefined' ? v2.defaultValue : '')
                                }
                            })
                            arr.push(obj)
                        })
                        this.$emit('update:sku', arr)
                    }
                }
            },
            deep: true
        }
    },
    mounted() {
        !this.async && this.init()
    },
    methods: {
        init() {
            this.$nextTick(() => {
                this.isInit = true
                // 初始化 myAttribute
                let myAttribute = []
                // 根据 sourceAttribute 复原 myAttribute 的结构
                this.sourceAttribute.forEach(v => {
                    const temp = {
                        name: v.name,
                        canAddAttribute: typeof v.canAddAttribute != 'undefined' ?  v.canAddAttribute : true,
                        addAttribute: '',
                        item: []
                    }
                    v.item.forEach(itemName => {
                        temp.item.push({
                            name: itemName,
                            checked: false
                        })
                    })
                    myAttribute.push(temp)
                })
                // 根据 attribute 更新 myAttribute
                this.attribute.forEach(attrVal => {
                    myAttribute.forEach(myAttrVal => {
                        if (attrVal.name === myAttrVal.name) {
                            attrVal.item.forEach(attrName => {
                                if (
                                    !myAttrVal.item.some(myAttrItem => {
                                        if (attrName === myAttrItem.name) {
                                            myAttrItem.checked = true
                                        }
                                        return attrName === myAttrItem.name
                                    })
                                ) {
                                    myAttrVal.item.push({
                                        name: attrName,
                                        checked: true
                                    })
                                }
                            })
                        }
                    })
                })
                this.myAttribute = myAttribute
                // 通过 sku 更新 skuData,但因为 skuData 是实时监听 myAttribute 变化并自动生成,而 watch 是在 methods 后执行,所以增加 setTimeout 方法,确保 skuData 生成后在执行下面的代码
                setTimeout(() => {
                    this.sku.forEach(skuItem => {
                        this.form.skuData.forEach(skuDataItem => {
                            if (skuItem.sku === skuDataItem.sku) {
                                this.structure.forEach(structureItem => {
                                    skuDataItem[structureItem.name] = skuItem[structureItem.name]
                                })
                            }
                        })
                    })
                    this.isInit = false
                }, 0)
            })
        },
        // 根据 attribute 进行排列组合,生成 skuData 数据
        combinationAttribute(index = 0, dataTemp = []) {
            if (index === 0) {
                for (let i = 0; i < this.attribute[0].item.length; i++) {
                    const obj = {
                        sku: this.attribute[0].item[i],
                        [this.attribute[0].name]: this.attribute[0].item[i]
                    }
                    this.structure.forEach(v => {
                        if (!(v.type == 'slot' && v.skuProperty == false)) {
                            obj[v.name] = typeof v.defaultValue != 'undefined' ? v.defaultValue : ''
                        }
                    })
                    dataTemp.push(obj)
                }
            } else {
                const temp = []
                for (let i = 0; i < dataTemp.length; i++) {
                    for (let j = 0; j < this.attribute[index].item.length; j++) {
                        temp.push(JSON.parse(JSON.stringify(dataTemp[i])))
                        temp[temp.length - 1][this.attribute[index].name] = this.attribute[index].item[j]
                        temp[temp.length - 1]['sku'] = [temp[temp.length - 1]['sku'], this.attribute[index].item[j]].join(this.separator)
                    }
                }
                dataTemp = temp
            }
            if (index !== this.attribute.length - 1) {
                this.combinationAttribute(index + 1, dataTemp)
            } else {
                if (!this.isInit || this.async) {
                    // 将原有的 sku 数据和新的 sku 数据比较,相同的 sku 则把原有的 sku 数据覆盖到新的 sku 数据里
                    for (let i = 0; i < this.form.skuData.length; i++) {
                        for (let j = 0; j < dataTemp.length; j++) {
                            if (this.form.skuData[i].sku === dataTemp[j].sku) {
                                dataTemp[j] = this.form.skuData[i]
                            }
                        }
                    }
                }
                this.form.skuData = dataTemp
            }
        },
        // 新增一个规格
        onAddAttribute(index) {
            this.myAttribute[index].addAttribute = this.myAttribute[index].addAttribute.trim()
            if (this.myAttribute[index].addAttribute !== '') {
                if (!this.myAttribute[index].addAttribute.includes(this.separator)) {
                    const flag = this.myAttribute[index].item.some(item => {
                        return item.name === this.myAttribute[index].addAttribute
                    })
                    if (!flag) {
                        this.myAttribute[index].item.push({
                            name: this.myAttribute[index].addAttribute,
                            checked: true
                        })
                        this.myAttribute[index].addAttribute = ''
                    } else {
                        this.$message({
                            type: 'warning',
                            message: '请勿添加相同规格'
                        })
                    }
                } else {
                    this.$message({
                        type: 'warning',
                        message: `规格里不允许出现「 ${this.separator} 」字符,请检查后重新添加`
                    })
                }
            }
        },
        onBatchSet(type) {
            if (this.batch[type] != '') {
                this.form.skuData.forEach(v => {
                    v[type] = this.batch[type]
                })
                this.batch[type] = ''
                // 批量设置完成后,触发一次当前列的验证
                this.validateFieldByColumns([type], () => {})
            }
        },
        // 自定义输入框验证,通过调用 structure 里的 validate 方法实现,重点是 callback 要带过去
        customizeValidate(rule, value, callback) {
            let [model, index, name] = rule.field.split('.')
            this.structure.forEach(v => {
                if (v.name == name) {
                    v.validate(this.form[model], index, callback)
                }
            })
        },
        // sku 表单验证
        validate(callback) {
            this.$refs['form'].validate(valid => {
                callback(valid)
            })
        },
        validateFieldByColumns(colums, callback) {
            let props = []
            this.form.skuData.forEach((v, i) => {
                colums.forEach(v => {
                    props.push(`skuData.${i}.${v}`)
                })
            })
            this.$refs['form'].validateField(props, valid => {
                callback(valid)
            })
        },
        validateFieldByRows(index, prop, callback) {
            this.$refs['form'].validateField([`skuData.${index}.${prop}`], valid => {
                callback(valid)
            })
        },
        clearValidate() {
            this.$refs['form'].clearValidate()
        }
    }
}
</script>

<style lang="scss" scoped>
.sku-container {
    ::v-deep .el-card {
        margin: 10px 0;
        .el-card__header {
            line-height: initial;
            padding: 10px 20px;
        }
        .el-card__body {
            padding: 10px 20px 20px;
        }
    }
    .sku-check {
        .theme-1 {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            margin-bottom: 10px;
            .item {
                width: 32%;
                &:last-child:nth-child(3n - 1) {
                    margin-right: calc(100% - 32% * 2 - 4% / 2) !important;
                }
                .add-attr {
                    width: 100%;
                    margin-top: 10px;
                }
            }
        }
        .theme-2 {
            border: 1px solid #ebeef5;
            border-bottom: 0;
            margin-bottom: 20px;
        }
    }
    .sku-name {
        text-align: right;
    }
    .batch-set {
        width: 100%;
        margin-top: 5px;
    }
    .sku-list {
        line-height: initial;
        ::v-deep .el-input__inner {
            text-align: center;
        }
        ::v-deep .el-table__append-wrapper {
            overflow: initial;
            .el-table {
                overflow: initial;
                .el-table__body-wrapper {
                    overflow: initial;
                }
            }
        }
        ::v-deep .el-form-item {
            margin-bottom: 0;
            .el-form-item__content {
                line-height: initial;
                .el-form-item__error {
                    margin-left: 0;
                }
            }
        }
        .required_title::before {
            content: '*';
            color: #f56c6c;
        }
    }
}
</style>

组件参数:

Props:

参数说明类型默认值
source-attributeSKU可选属性array[]
structure表单结构array[{ name: 'price', type: 'input', label: '价格' }, { name: 'stock', type: 'input', label: '库存' }]
attributeSKU已选属性,支持 .sync修饰符array[]
skuSKU数据,支持 .sync 修饰符array[]
separatorSKU字段分隔符string;
emptySku无属性SKU名称string
disabled是否显示SKU选择栏booleanfalse
themeSKU选择栏主题风格,可选1或2int1
async是否开启异步加载booleanfalse

#source-attribute

名称说明类型默认值
name属性名称string
item属性可选项array
canAddAttribute是否可以添加属性booleantrue
// 例子
[
    { name: '颜色', item: ['黑', '金', '白'] },
    { name: '内存', item: ['16G', '32G'], canAddAttribute: false },
    { name: '运营商', item: ['电信', '移动', '联通'] }
]

#structure

名称说明类型默认值可选值
nameSKU数据里的属性string
type表单展示形式,默认为输入框,当设置为 slot 时,为自定义插槽stringinputinput, slot
skuProperty当 type 设置为 slot 时,可选择是否插槽数据是否记录到 sku 数据里booleantrue
defaultValue默认值any
label表头名称string
tip鼠标悬停提示string
batch是否开启批量设置booleantrue
required是否必填booleanfalse
validate自定义校验回调方法functionfalse
// 例子
[
    {
        name: 'code',
        type: 'input',
        label: '商品唯一编码',
        required: true,
        batch: false
    },
    {
        name: 'originalprice',
        type: 'input',
        label: '成本价'
    },
    {
        name: 'price',
        type: 'input',
        label: '销售价'
    },
    {
        name: 'stock',
        type: 'input',
        defaultValue: 10,
        label: '库存',
        tip: '库存数不能少于10',
        validate: (data, index, callback) => {
            if (parseInt(data[index].stock) < 10) {
                callback(new Error('库存不能小于10'))
            }
            callback()
        }
    }
]

#attribute

名称说明类型可选值
name属性名称string
item属性已选项array
// 例子
[
    { name: '颜色', item: ['黑', '红'] },
    { name: '运营商', item: ['电信'] }
]
// 需要注意 attribute 的属性名称需要 source-attribute 里存在,但 attribute 的属性已选项则没有限制
// 例如下面的例子则是错误示范
[
    { name: '尺码', item: ['L', 'M'] },
    { name: '运营商', item: ['电信'] }
]

Methods

方法名说明
init初始化方法,如果数据是异步载入,需要在获取到数据后手动进行SKU表单的初始化
validateSKU表单验证方法,参数为一个回调函数
validateFieldSKU表单验证指定列方法,第一个参数为列名(structure 里的属性名),第二个参数为一个回调函数
clearValidate清除SKU表单验证结果

使用示例

基本示例

base.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            attribute: [],
            sku: []
        }
    }
}
</script>

自定义SKU(连接符)

separator.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :attribute.sync="attribute"
            :sku.sync="sku"
            separator="__"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑']
                },
                {
                    name: '内存',
                    item: ['16G']
                }
            ],
            sku: [
                {
                    sku: '黑__16G',
                    price: 80,
                    stock: 100
                }
            ]
        }
    }
}
</script>

关闭SKU可选属性配置

canAddAttribute.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白'],
                    canAddAttribute: false
                },
                {
                    name: '内存',
                    item: ['16G', '32G'],
                    canAddAttribute: false
                }
            ],
            attribute: [],
            sku: []
        }
    }
}
</script>

禁用SKU可选属性配置

disabled.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :attribute.sync="attribute"
            :sku.sync="sku"
            :disabled="true"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            sku: [
                {
                    sku: '黑;16G',
                    price: 80,
                    stock: 100
                },
                {
                    sku: '黑;32G',
                    price: 80,
                    stock: 100
                }
            ]
        }
    }
}
</script>

主题风格

t1.png
t2.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :attribute.sync="attribute"
            :sku.sync="sku"
            :theme="theme"
        />
        <el-button type="primary" style="margin-top: 10px;" @click="switchTheme">切换主题</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            attribute: [],
            sku: [],
            theme: 1
        }
    },
    methods: {
        switchTheme() {
            this.theme = this.theme == 1 ? 2 : 1
        }
    }
}
</script>

自定义表格

table.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'originalprice',
                    type: 'input',
                    label: '原价'
                },
                {
                    name: 'price',
                    type: 'input',
                    label: '现价'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                }
            ],
            attribute: [],
            sku: []
        }
    }
}
</script>

自定义表格(插槽-文本)

table-1.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        >
            <template #price="slotProps">
                {{ slotProps.row.price }}
            </template>
            <template #totalPrice="slotProps">
                {{ total(slotProps.row) }}
            </template>
        </SkuForm>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'slot',
                    label: '现价'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                },
                {
                    name: 'totalPrice',
                    type: 'slot',
                    // 如果该字段无需记录到 sku 数据里,则设置为 false
                    skuProperty: false,
                    label: '总价',
                    tip: '总价 = 价格 * 库存,如果价格或库存为空时,则不计算'
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G']
                }
            ],
            sku: [
                {
                    sku: '黑;16G',
                    price: 85,
                    stock: 100
                },
                {
                    sku: '金;16G',
                    price: 85,
                    stock: 50
                },
                {
                    sku: '白;16G',
                    price: 85,
                    stock: 50
                }
            ]
        }
    },
    methods: {
        total(data) {
            let totalPrice = ''
            if (data.price && data.stock) {
                totalPrice = (parseFloat(data.price) * parseFloat(data.stock)).toFixed(2)
                totalPrice += ' 元'
            }
            return totalPrice
        }
    }
}
</script>

自定义表格(插槽-组件)

table-2.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        >
            <template #score="slotProps">
                <div>
                    <el-rate v-model="slotProps.row.score" />
                </div>
            </template>
            <template #image="slotProps">
                <div class="image-upload-container">
                    <el-image v-if="slotProps.row.image" :src="slotProps.row.image" :preview-src-list="[slotProps.row.image]" fit="cover" title="点击预览" />
                    <el-upload :show-file-list="false" action="http://scrm.1daas.com/api/upload/upload" :data="{token: 'TKD917339526087186'}" name="image" :before-upload="beforeUpload" :on-success="res => imageUpload(res, slotProps)" class="images-upload">
                        <el-button size="small" icon="el-icon-upload2">{{ slotProps.row.image ? '重新上传' : '上传图片' }}</el-button>
                    </el-upload>
                    <el-button v-if="slotProps.row.image" size="small" icon="el-icon-delete" @click="imageRemove(slotProps)" />
                </div>
            </template>
        </SkuForm>
        <el-button type="primary" style="margin-top: 10px;" @click="submit">提交</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'input',
                    label: '现价'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                },
                {
                    name: 'score',
                    type: 'slot',
                    defaultValue: 0,
                    label: '评分'
                },
                {
                    name: 'image',
                    type: 'slot',
                    label: '图片',
                    required: true
                }
            ],
            attribute: [],
            sku: []
        }
    },
    methods: {
        beforeUpload(file) {
            const ext = ['jpg', 'png', 'gif', 'bmp']
            const size = 2
            const fileName = file.name.split('.')
            const fileExt = fileName[fileName.length - 1]
            const isTypeOk = ext.indexOf(fileExt) >= 0
            const isSizeOk = file.size / 1024 / 1024 < size
            if (!isTypeOk) {
                this.$message.error(`上传图片只支持 ${ ext.join(' / ') } 格式!`)
            }
            if (!isSizeOk) {
                this.$message.error(`上传图片大小不能超过 ${size}MB!`)
            }
            return isTypeOk && isSizeOk
        },
        // 图片上传
        imageUpload(res, data) {
            // 这里会返回上传结果,提取出图片地址url
            console.log(res)
            // 模拟返回数据
            let imagePath = 'http://images.lookbi.com/uploads/apply/166/e2e1b23647d67df2655d5e6bed76670c.jpg'
            data.row.image = imagePath
            this.$message.success('图片上传成功')
            this.$refs.skuForm.validateFieldByRows(data.$index, 'image', () => {})
        },
        imageRemove(data) {
            data.row.image = ''
        },
        submit() {
            this.$refs.skuForm.validate(valid => {
                if (valid) {
                    this.$message.success('验证通过')
                } else {
                    this.$message.warning('验证失败')
                }
            })
        }
    }
}
</script>

<style lang="scss" scoped>
/deep/ .el-upload-dragger {
    width: initial;
    height: initial;
    border: 0;
    border-radius: 0;
    background-color: initial;
    overflow: auto;
}
/deep/ .image-upload-container {
    .el-image {
        vertical-align: middle;
        margin: 0 5px;
        width: 30px;
        height: 30px;
    }
    .images-upload,
    > .el-button {
        display: inline-block;
        margin: 0 5px;
        vertical-align: middle;
    }
}
</style>

数据还原(初始数据)

init.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'originalprice',
                    type: 'input',
                    label: '原价'
                },
                {
                    name: 'price',
                    type: 'input',
                    label: '现价'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金']
                },
                {
                    name: '内存',
                    item: ['16G']
                }
            ],
            sku: [
                {
                    sku: '黑;16G',
                    originalprice: 100,
                    price: 80,
                    stock: 100
                },
                {
                    sku: '金;16G',
                    originalprice: 100,
                    price: 85,
                    stock: 50
                }
            ]
        }
    }
}
</script>

批量设置

当 SKU 数据超过 2 条时,自动开启批量设置,可在 structure 里设置 batch: false 进行关闭
batch.png
<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'input',
                    label: '价格'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存',
                    batch: false
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G']
                }
            ],
            sku: [
                {
                    sku: '黑;16G',
                    price: 85,
                    stock: 100
                },
                {
                    sku: '金;16G',
                    price: 85,
                    stock: 50
                },
                {
                    sku: '白;16G',
                    price: 85,
                    stock: 50
                }
            ]
        }
    }
}
</script>

必填验证

required.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-button type="primary" @click="submit" style="margin-top: 10px;">提交</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'input',
                    label: '价格',
                    required: true
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存',
                    required: true
                }
            ],
            attribute: [],
            sku: []
        }
    },
    methods: {
        submit() {
            this.$refs.skuForm.validate(valid => {
                if (valid) {
                    this.$message.success('验证通过')
                } else {
                    this.$message.warning('验证失败')
                }
            })
        }
    }
}
</script>

自定义验证

validate.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-button type="primary" @click="submit" style="margin-top: 10px;">提交</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'input',
                    label: '价格'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存',
                    tip: '库存数不能少于10',
                    required: true,
                    // data: 完整 sku 数据,index: 当前 sku 在 data 中的下标,callback: 验证结果回调函数
                    validate: (data, index, callback) => {
                        if (data[index].stock && parseInt(data[index].stock) < 10) {
                            callback(new Error('库存不能小于10'))
                        }
                        callback()
                    }
                }
            ],
            attribute: [],
            sku: []
        }
    },
    methods: {
        submit() {
            this.$refs.skuForm.validate(valid => {
                if (valid) {
                    this.$message.success('验证通过')
                } else {
                    this.$message.warning('验证失败')
                }
            })
        }
    }
}
</script>

异步验证

async-validate.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-button type="primary" @click="submit" style="margin-top: 10px;">提交</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'code',
                    type: 'input',
                    label: '商品唯一编码',
                    tip: '模拟异步操作,如果输入123则会提示编码已存在',
                    required: true,
                    validate: (data, index, callback) => {
                        setTimeout(() => {
                            if (data[index].code == '123') {
                                callback(new Error('商品唯一编码已存在'))
                            }
                            callback()
                        }, 1000)
                    }
                },
                {
                    name: 'price',
                    type: 'input',
                    label: '价格'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                }
            ],
            attribute: [],
            sku: []
        }
    },
    methods: {
        submit() {
            this.$refs.skuForm.validate(valid => {
                if (valid) {
                    this.$message.success('验证通过')
                } else {
                    this.$message.warning('验证失败')
                }
            })
        }
    }
}
</script>

指定列验证

validate-col.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
        />
        <el-button type="primary" style="margin-top: 10px;" @click="clear">清除验证结果</el-button>
        <el-button type="primary" style="margin-top: 10px;" @click="check('price')">验证价格</el-button>
        <el-button type="primary" style="margin-top: 10px;" @click="check('stock')">验证库存</el-button>
        <el-button type="primary" style="margin-top: 10px;" @click="checkAll">验证多列(价格和库存)</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'input',
                    label: '价格',
                    required: true
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存',
                    tip: '库存数不能少于10',
                    required: true,
                    // data: 完整 sku 数据,index: 当前 sku 在 data 中的下标,callback: 验证结果回调函数
                    validate: (data, index, callback) => {
                        if (data[index].stock && parseInt(data[index].stock) < 10) {
                            callback(new Error('库存不能小于10'))
                        } else {
                            callback()
                        }
                    }
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                }
            ],
            sku: []
        }
    },
    methods: {
        clear() {
            this.$refs.skuForm.clearValidate()
        },
        check(name) {
            let isError = false
            this.$refs.skuForm.validateFieldByColumns([name], errorMessage => {
                if (errorMessage) {
                    isError = true
                }
            })
            if (isError) {
                // 此处可以写验证失败后的业务代码
            }
        },
        checkAll() {
            let isError = false
            this.$refs.skuForm.validateFieldByColumns(['price', 'stock'], errorMessage => {
                if (errorMessage) {
                    isError = true
                }
            })
            if (isError) {
                // 此处可以写验证失败后的业务代码
            }
        }
    }
}
</script>

异步加载

async-recovery.png

<template>
    <div>
        <SkuForm
            ref="skuForm"
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
            async
        />
        <el-button type="primary" :loading="loading" style="margin-top: 10px;" @click="load(1)">模拟加载数据1</el-button>
        <el-button type="primary" :loading="loading" style="margin-top: 10px;" @click="load(2)">模拟加载数据2</el-button>
        <el-button type="primary" :loading="loading" style="margin-top: 10px;" @click="load(3)">模拟加载数据3</el-button>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            loading: false,
            sourceAttribute: [],
            structure: [
                {
                    name: 'originalprice',
                    type: 'input',
                    label: '成本价'
                },
                {
                    name: 'price',
                    type: 'input',
                    label: '销售价'
                },
                {
                    name: 'stock',
                    type: 'input',
                    label: '库存'
                }
            ],
            attribute: [],
            sku: []
        }
    },
    mounted() {},
    methods: {
        load(type) {
            this.loading = true
            if (type == 1) {
                setTimeout(() => {
                    this.sourceAttribute = [
                        {
                            name: '颜色',
                            item: ['黑', '金', '白']
                        },
                        {
                            name: '内存',
                            item: ['16G', '32G']
                        }
                    ]
                    setTimeout(() => {
                        this.attribute = [
                            {
                                name: '颜色',
                                item: ['黑', '金']
                            },
                            {
                                name: '内存',
                                item: ['64G']
                            }
                        ]
                        setTimeout(() => {
                            this.sku = [
                                {
                                    sku: '黑;64G',
                                    originalprice: '100',
                                    price: '80',
                                    stock: '100'
                                },
                                {
                                    sku: '金;64G',
                                    originalprice: '100',
                                    price: '85',
                                    stock: '50'
                                }
                            ]
                            // 切记,必须在 attribute、sku 数据都加载后再执行 init() 方法
                            this.$refs.skuForm.init()
                            this.loading = false
                        }, 300)
                    }, 300)
                }, 300)
            } else if (type == 2) {
                setTimeout(() => {
                    this.sourceAttribute = [
                        {
                            name: '内存',
                            item: ['16G', '32G']
                        },
                        {
                            name: '颜色',
                            item: ['黑', '金', '白']
                        }
                    ]
                    setTimeout(() => {
                        this.attribute = [
                            {
                                name: '内存',
                                item: ['64G']
                            },
                            {
                                name: '颜色',
                                item: ['红', '白']
                            }
                        ]
                        setTimeout(() => {
                            this.sku = [
                                {
                                    sku: '64G;红',
                                    originalprice: 10,
                                    price: 8,
                                    stock: 10
                                },
                                {
                                    sku: '64G;白',
                                    originalprice: 10,
                                    price: 8,
                                    stock: 5
                                }
                            ]
                            // 切记,必须在 attribute、sku 数据都加载后再执行 init() 方法
                            this.$refs.skuForm.init()
                            this.loading = false
                        }, 300)
                    }, 300)
                }, 300)
            } else {
                setTimeout(() => {
                    this.sourceAttribute = [
                        {
                            name: '颜色',
                            item: ['黑', '金', '白']
                        }
                    ]
                    this.attribute = []
                    this.sku = []
                    this.$refs.skuForm.init()
                    this.loading = false
                }, 300)
            }
        }
    }
}
</script>

前台应用实现

假设我们有一组已经配置好的 SKU 数据,如下:
list.png

<template>
    <div>
        <SkuForm
            :source-attribute="sourceAttribute"
            :structure="structure"
            :attribute.sync="attribute"
            :sku.sync="sku"
            :disabled="true"
        >
            <template #price="slotProps">
                {{ slotProps.row.price }}
            </template>
            <template #stock="slotProps">
                {{ slotProps.row.stock }}
            </template>
        </SkuForm>
        <el-row type="flex" :gutter="20">
            <el-col>
                <el-divider content-position="left">attribute 数据</el-divider>
                <pre><code>{{ attribute }}</code></pre>
            </el-col>
            <el-col>
                <el-divider content-position="left">sku 数据</el-divider>
                <pre><code>{{ sku }}</code></pre>
            </el-col>
        </el-row>
    </div>
</template>

<script>
export default {
    data() {
        return {
            sourceAttribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                },
                {
                    name: '运营商',
                    item: ['电信', '移动', '联通']
                }
            ],
            structure: [
                {
                    name: 'price',
                    type: 'slot',
                    label: '价格'
                },
                {
                    name: 'stock',
                    type: 'slot',
                    label: '库存'
                }
            ],
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                },
                {
                    name: '运营商',
                    item: ['电信', '移动', '联通']
                }
            ],
            sku: [
                { sku: '黑;16G;电信', price: 100, stock: 10 },
                { sku: '黑;16G;移动', price: 101, stock: 11 },
                { sku: '黑;16G;联通', price: 102, stock: 0 },
                { sku: '黑;32G;电信', price: 103, stock: 13},
                { sku: '黑;32G;移动', price: 104, stock: 14},
                { sku: '黑;32G;联通', price: 105, stock: 0},
                { sku: '金;16G;电信', price: 106, stock: 16},
                { sku: '金;16G;移动', price: 107, stock: 17},
                { sku: '金;16G;联通', price: 108, stock: 18},
                { sku: '金;32G;电信', price: 109, stock: 0},
                { sku: '金;32G;移动', price: 110, stock: 20},
                { sku: '金;32G;联通', price: 111, stock: 21},
                { sku: '白;16G;电信', price: 112, stock: 0},
                { sku: '白;16G;移动', price: 113, stock: 23},
                { sku: '白;16G;联通', price: 114, stock: 24},
                { sku: '白;32G;电信', price: 115, stock: 0},
                { sku: '白;32G;移动', price: 116, stock: 26},
                { sku: '白;32G;联通', price: 117, stock: 27}
            ]
        }
    }
}
</script>

对于前台来说,只需要用到 attribute 和 sku 数据,当然需要对这两组数组进行加工处理下:
select.png

<template>
    <el-row type="flex" class="sku-info">
        <el-col v-for="(attr, index) in process_attribute" :key="index">
            <h3>{{ attr.name }}</h3>
            <el-button-group>
                <el-button v-for="(item, index2) in attr.item" :key="index2" :type="item.actived ? 'primary' : ''" :disabled="item.disabled" @click="skuClick(index, index2)">{{ item.name }}</el-button>
            </el-button-group>
        </el-col>
    </el-row>
    <div style="margin-top: 20px;">
        库存:
        <span>{{ stock }}</span>
    </div>
    <div style="margin-bottom: 20px;">
        价格:
        <span v-if="minprice == maxprice">{{ minprice }}</span>
        <span v-else>{{ minprice }} - {{ maxprice }}</span>
    </div>
    <el-button type="primary" @click="submit">购买</el-button>
    <el-row type="flex" :gutter="20">
        <el-col>
            <el-divider content-position="left">加工处理过的 attribute 数据</el-divider>
            <pre><code>{{ process_attribute }}</code></pre>
        </el-col>
        <el-col>
            <el-divider content-position="left">加工处理过的 sku 数据</el-divider>
            <pre><code>{{ process_sku }}</code></pre>
        </el-col>
    </el-row>
</template>

<script>
export default {
    data() {
        return {
            // sku字段分隔符
            separator: ';',
            attribute: [
                {
                    name: '颜色',
                    item: ['黑', '金', '白']
                },
                {
                    name: '内存',
                    item: ['16G', '32G']
                },
                {
                    name: '运营商',
                    item: ['电信', '移动', '联通']
                }
            ],
            sku: [
                { sku: '黑;16G;电信', price: 100, stock: 10 },
                { sku: '黑;16G;移动', price: 101, stock: 11 },
                { sku: '黑;16G;联通', price: 102, stock: 0 },
                { sku: '黑;32G;电信', price: 103, stock: 13},
                { sku: '黑;32G;移动', price: 104, stock: 14},
                { sku: '黑;32G;联通', price: 105, stock: 0},
                { sku: '金;16G;电信', price: 106, stock: 16},
                { sku: '金;16G;移动', price: 107, stock: 17},
                { sku: '金;16G;联通', price: 108, stock: 18},
                { sku: '金;32G;电信', price: 109, stock: 0},
                { sku: '金;32G;移动', price: 110, stock: 20},
                { sku: '金;32G;联通', price: 111, stock: 21},
                { sku: '白;16G;电信', price: 112, stock: 0},
                { sku: '白;16G;移动', price: 113, stock: 23},
                { sku: '白;16G;联通', price: 114, stock: 24},
                { sku: '白;32G;电信', price: 115, stock: 0},
                { sku: '白;32G;移动', price: 116, stock: 26},
                { sku: '白;32G;联通', price: 117, stock: 27}
            ],
            process_attribute: [],
            process_sku: [],
            // 当前选中 sku 的库存及价格区间
            stock: '',
            minprice: '',
            maxprice: ''
        }
    },
    mounted() {
        this.init()
    },
    methods: {
        init() {
            // 对 attribute 数据进行加工,并存入 process_attribute 中
            this.attribute.map(attr => {
                let temp = {
                    name: attr.name
                }
                temp.item = attr.item.map(item => {
                    return {
                        name: item,
                        actived: false,
                        disabled: false
                    }
                })
                this.process_attribute.push(temp)
            })
            // 对 sku 数据进行加工,并存入 process_sku 中
            this.sku.map(v => {
                var combArr = this.arrayCombine(v.sku.split(this.separator))
                for (var j = 0; j < combArr.length; j++) {
                    var key = combArr[j].join(this.separator)
                    if (this.process_sku[key]) {
                        // 库存累加,价格添加进数组
                        this.process_sku[key].stock += v.stock
                        this.process_sku[key].prices.push(v.price)
                    } else {
                        this.process_sku[key] = {
                            stock: v.stock,
                            prices: [v.price]
                        }
                    }
                }
            })
            // 更新数据视图
            this.process_sku = Object.assign({}, this.process_sku)
            this.skuCheck()
        },
        arrayCombine(targetArr) {
            var resultArr = []
            for (var n = 0; n <= targetArr.length; n++) {
                var flagArrs = this.getFlagArrs(targetArr.length, n)
                while (flagArrs.length) {
                    var flagArr = flagArrs.shift()
                    var combArr = Array(targetArr.length)
                    for (var i = 0; i < targetArr.length; i++) {
                        if (flagArr[i]) {
                            combArr[i] = targetArr[i]
                        }
                    }
                    resultArr.push(combArr)
                }
            }
            return resultArr
        },
        getFlagArrs(m, n) {
            var flagArrs = [],
                flagArr = [],
                isEnd = false
            for (var i = 0; i < m; i++) {
                flagArr[i] = i < n ? 1 : 0
            }
            flagArrs.push(flagArr.concat())
            // 当n不等于0并且m大于n的时候进入
            if (n && m > n) {
                while (!isEnd) {
                    var leftCnt = 0
                    for (var i = 0; i < m - 1; i++) {
                        if (flagArr[i] == 1 && flagArr[i + 1] == 0) {
                            for (var j = 0; j < i; j++) {
                                flagArr[j] = j < leftCnt ? 1 : 0
                            }
                            flagArr[i] = 0
                            flagArr[i + 1] = 1
                            var aTmp = flagArr.concat()
                            flagArrs.push(aTmp)
                            if (aTmp.slice(-n).join('').indexOf('0') == -1) {
                                isEnd = true
                            }
                            break
                        }
                        flagArr[i] == 1 && leftCnt++
                    }
                }
            }
            return flagArrs
        },
        skuClick(key1, key2) {
            if (!this.process_attribute[key1].item[key2].disabled) {
                this.process_attribute[key1].item.map((item, index) => {
                    item.actived = index == key2 ? !item.actived : false
                })
                this.skuCheck()
                this.getStockPrice()
            }
        },
        skuCheck() {
            let sku = []
            this.process_attribute.map(attr => {
                let name = ''
                attr.item.map(item => {
                    if (item.actived) {
                        name = item.name
                    }
                })
                sku.push(name)
            })
            this.stock = this.process_sku[sku.join(this.separator)].stock
            this.minprice = Math.min.apply(Math, this.process_sku[sku.join(this.separator)].prices)
            this.maxprice = Math.max.apply(Math, this.process_sku[sku.join(this.separator)].prices)
        },
        getStockPrice() {
            this.process_attribute.map(attr => {
                attr.item.map(item => {
                    item.disabled = false
                })
            })
            let count = 0
            let i = 0
            this.process_attribute.map((attr, index) => {
                let flag = false
                attr.item.map(item => {
                    if (item.actived) {
                        flag = true
                    }
                })
                if (!flag) {
                    count += 1
                    i = index
                }
            })
            // 当只有一组规格没选时
            if (count == 1) {
                this.process_attribute[i].item.map(item => {
                    let sku = []
                    let text = item.name
                    this.process_attribute.map((attr, index) => {
                        if (index != i) {
                            attr.item.map(item2 => {
                                if (item2.actived) {
                                    sku.push(item2.name)
                                }
                            })
                        } else {
                            sku.push(text)
                        }
                    })
                    if (this.process_sku[sku.join(this.separator)].stock == 0) {
                        item.disabled = true
                    }
                })
            }
            // 当所有规格都有选时
            if (count == 0) {
                this.process_attribute.map((attr, index) => {
                    let i = index
                    this.process_attribute[index].item.map(item => {
                        if (!item.actived) {
                            let sku = []
                            let text = item.name
                            this.process_attribute.map((list, index) => {
                                if (index != i) {
                                    list.item.map(item2 => {
                                        if (item2.actived) {
                                            sku.push(item2.name)
                                        }
                                    })
                                } else {
                                    sku.push(text)
                                }
                            })
                            if (this.process_sku[sku.join(this.separator)].stock == 0) {
                                item.disabled = true
                            }
                        }
                    })
                })
            }
        },
        submit() {
            let sku = []
            let isSelectSKU = this.process_attribute.every(attr => {
                let filter = attr.item.filter(v => v.actived)
                if (filter.length != 0) {
                    sku.push(filter[0].name)
                }
                return filter.length != 0
            })
            if (isSelectSKU) {
                this.$message.success(`当前SKU为:${sku.join(this.separator)}`)
            } else {
                this.$message.warning('请选择完整商品属性')
            }
        }
    }
}
</script>

源码地址:https://github.com/hooray/vue-sku-form

标签: VUE

相关文章

在JavaScript或Vue中屏蔽所有报错信息

在 JavaScript 或 Vue 中,如果你想屏蔽所有 JavaScript 报错,可以通过捕获全局的错误事件来实现。需要注意的是,尽量避免屏蔽所有错误,因为这可能会掩盖一些实际问题,影响调...

JAVA+VUE的多国语言跨境电商外贸商城源码

多语言跨境电商外贸商城TikTok内嵌商城,商家入驻、一键铺货、一键提货 全开源完美运营海外版抖音TikTok商城系统源码,TikToK内嵌商城,跨境商城系统源码接在tiktok里面的商城。ti...

VUE倒计时组件

常用于发送短信、邮件后1分钟倒计时,倒计时结束后又可以再次点击vue组件封装:<template> <div class="timer-btn">...

图片Base64编码

CSR生成

图片无损放大

图片占位符

Excel拆分文件