Dcat-Admin中的商品SKU

Dcat-Admin 商品规格SKU,先看效果图

Laravel
Laravel
Laravel

我只是一名搬运工,将SKU组件 github.com/rossroma/vue-sku/tree/r... 搬到了Dcat-Admin中,分享给需要的人

下面是代码:

<div class="{{$viewClass['form-group']}}">

    <label class="{{$viewClass['label']}} control-label">{{$label}}</label>

    <div class="{{$viewClass['field']}}">

        @include('admin::form.error')

        <div {!! $attributes !!} style="width: 100%; height: 100%;">
            <div class="card">
                <div class="card-body">
                    <div v-for="(item, index) in specification" :key="index">
                        <span v-if="!cacheSpecification[index].status">@{{ item.name }}</span>
                        <span style="float:right;cursor: pointer" v-on:click="delSpec(index)"><i class="feather icon-x"></i></span>
                        <div class="input-group mb-2" v-if="cacheSpecification[index].status" style="width: 25%">
                            <input type="text" class="form-control" v-model="cacheSpecification[index].name" placeholder="输入产品规格" @keyup.native.enter="saveSpec(index)">
                            <div class="input-group-prepend" v-on:click="saveSpec(index)">
                                <span class="input-group-text"><i class="feather icon-check"></i></span>
                            </div>
                        </div>
                        <div class="row" style="padding-top: 10px">
                            <div class="col-sm-3 input-group mb-2"
                                 v-for="(tag, j) in item.value" :key="j"
                            >
                                <input type="text"
                                       class="form-control"
                                       :value="tag"
                                >
                                <div class="input-group-append" v-on:click="delSpecTag(index, j)">
                                    <span style="cursor: pointer" class="input-group-text"><i class="feather icon-x"></i></span>
                                </div>
                            </div>
                        </div>
                        <div class="input-group mb-3" style="width: 25%;cursor: pointer">
                            <input type="text" class="form-control" v-model="addValues[index]" placeholder="多个产品属性以空格隔开">
                            <div class="input-group-append" v-on:click="addSpecTag(index)">
                                <span class="input-group-text" ><i class="feather icon-check"></i></span>
                            </div>
                        </div>
                        <hr>
                    </div>
                </div>
                <div class="card-footer">
                    <button type="button" :disabled="specification.length >= 5" class="btn btn-primary btn-sm" v-on:click="addSpec">添加规格值</button>
                </div>
            </div>
            <div class="card">
                <div class="card-header">
                    规格表格
                </div>
                <div class="card-body">
                    <table class="table" cellspacing="0" cellpadding="0">
                        <thead>
                        <tr>
                            <th
                                v-for="(item, index) in specification"
                                :key="index">
                                @{{item.name}}
                            </th>
{{--                            <th>规格编码</th>--}}
                            <th>成本价(元)</th>
                            <th>销售价(元)</th>
                            <th>库存</th>
                            <th>是否启用</th>
                        </tr>
                        </thead>
                        <tbody v-if="specification[0] && specification[0].value.length">
                        <tr
                            :key="index"
                            v-for="(item, index) in countSum(0)">
                            <template v-for="(n, specIndex) in specification.length">
                                <td
                                    style="vertical-align: middle!important;text-align:right"
                                    v-if="showTd(specIndex, index)"
                                    :key="n"
                                    :rowspan="countSum(n)">
                                    @{{getSpecAttr(specIndex, index)}}
                                </td>
                            </template>
{{--                            <td>--}}
{{--                                <input--}}
{{--                                    type="text"--}}
{{--                                    class="form-control"--}}
{{--                                    :disabled="!childProductArray[index].isUse"--}}
{{--                                    v-model="childProductArray[index].childProductNo"--}}
{{--                                    v-on:blur="handleNo(index)"--}}
{{--                                    placeholder="输入商品规格编号">--}}
{{--                            </td>--}}
                            <td>
                                <input
                                    type="text"
                                    class="form-control"
                                    v-model.number="childProductArray[index].childProductCost"
                                    placeholder="输入成本价"
                                    :disabled="!childProductArray[index].isUse">
                            </td>
                            <td>
                                <input
                                    type="text"
                                    class="form-control"
                                    v-model.number="childProductArray[index].childProductPrice"
                                    placeholder="输入销售价"
                                    :disabled="!childProductArray[index].isUse">
                            </td>
                            <td>
                                <input
                                    type="text"
                                    class="form-control"
                                    v-model.number="childProductArray[index].childProductStock"
                                    placeholder="输入库存"
                                    :disabled="!childProductArray[index].isUse">
                            </td>
                            <td>
                                <input type="checkbox" name="on_sale" v-on:change="(val) => {handleUserChange(index, val)}" class="field_on_sale" data-size="small" data-color="#586cb1" v-model="childProductArray[index].isUse" data-plugin="form-W8oh1vxmswitchery">
                            </td>
                        </tr>
                        <tr>
                            <td colspan="8" class="wh-foot">
                                <span class="label">批量设置:</span>
                                <template v-if="isSetListShow">
                                    <button type="button" class="btn btn-primary" v-on:click="openBatch('childProductCost')">成本价</button>
                                    <button type="button" class="btn btn-primary" v-on:click="openBatch('childProductStock')">库存</button>
                                    <button type="button" class="btn btn-primary" v-on:click="openBatch('childProductPrice')">销售价</button>
                                </template>
                                <template v-else>
                                    <form class="form-inline">
                                        <input type="number" class="form-control" style="width:200px;" v-model.number="batchValue" placeholder="输入要设置的数量"></input>
                                        <button type="button"  v-on:click="setBatch" class="btn btn-primary"><i class="feather icon-check"></i></button>
                                        <button type="button" v-on:click="cancelBatch" class="btn btn-danger"><i class="feather icon-x"></i></button>
                                    </form>
                                </template>
                            </td>
                        </tr>
                        </tbody>
                    </table>
                </div>
            </div>
            <input type="hidden" name="{{$name}}" :value="getSku" />
        </div>
        @include('admin::form.help-block')
    </div>
</div>

<script init="{!! $selector !!}">
// 为Object添加一个原型方法,判断两个对象是否相等
function objEquals (object1, object2) {
    // For the first loop, we only check for types
    for (let propName in object1) {
        // Check for inherited methods and properties - like .equals itself
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
        // Return false if the return value is different
        if (object1.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
            return false
            // Check instance type
        } else if (typeof object1[propName] !== typeof object2[propName]) {
            // Different types => not equal
            return false
        }
    }
    // Now a deeper check using other objects property names
    for (let propName in object2) {
        // We must check instances anyway, there may be a property that only exists in object2
        // I wonder, if remembering the checked values from the first loop would be faster or not
        if (object1.hasOwnProperty(propName) !== object2.hasOwnProperty(propName)) {
            return false
        } else if (typeof object1[propName] !== typeof object2[propName]) {
            return false
        }
        // If the property is inherited, do not check any more (it must be equa if both objects inherit it)
        if (!object1.hasOwnProperty(propName)) {
            continue
        }
        // Now the detail check and recursion
        // This returns the script back to the array comparing
        /** REQUIRES Array.equals**/
        if (object1[propName] instanceof Array && object2[propName] instanceof Array) {
            // recurse into the nested arrays
            if (objEquals(!object1[propName], object2[propName])) {
                return false
            }
        } else if (object1[propName] instanceof Object && object2[propName] instanceof Object) {
            // recurse into another objects
            // console.log("Recursing to compare ", this[propName],"with",object2[propName], " both named \""+propName+"\"");
            if (objEquals(!object1[propName], object2[propName])) {
                return false
            }
            // Normal value comparison for strings and numbers
        } else if (object1[propName] !== object2[propName]) {
            return false
        }
    }
    // If everything passed, let's say YES
    return true
}
var vm = new Vue({
    el: '#' + id,
    data() {
        return {
            specificationStatus: false, // 显示规格列表
            // 规格
            specification: [],
            // 子规格
            childProductArray: [],
            // 用来存储要添加的规格属性
            addValues: [],
            // 默认商品编号
            defaultProductNo: 'PRODUCTNO_',
            // 批量设置相关
            isSetListShow: true,
            batchValue: '', // 批量设置所绑定的值
            currentType: '', // 要批量设置的类型
            cacheSpecification: [] // 缓存规格名称
        }
    },
    computed: {
        // 所有sku的id
        stockSpecArr () {
            return this.childProductArray.map(item => item.childProductSpec)
        },
        getSku() {
            return JSON.stringify(this.childProductArray)
        }
    },
    created() {
        this.createData()
    },
    methods: {
        // 创建模拟数据
        createData() {
            const arr = ['颜色', '尺寸']
            const arr2 = ['黑色 白色 蓝色 红色', 's m xl']
            for (let i = 0; i < 2; i++) {
                // 添加数据
                this.addSpec()
                // 数据
                this.specification[i].name = arr[i]
                this.addValues[i] = arr2[i]
                // 缓存按钮键值
                this.cacheSpecification[i].status = false
                this.cacheSpecification[i].name = arr[i]
                // 构建
                this.addSpecTag(i)
            }
        },
        // 添加规格项目
        addSpec () {
            if (this.specification.length < 5) {
                this.cacheSpecification.push({
                    status: true,
                    name: ''
                })
                this.specification.push({
                    name: '',
                    value: []
                })
            }
        },
        // 添加规格属性
        addSpecTag (index) {
            let str = this.addValues[index] || ''
            if (!str.trim() || !this.cacheSpecification[index].name.trim()) {
                Dcat.error('名称不能为空,请注意修改')
                return
            } // 判空
            str = str.trim()
            let arr = str.split(/\s+/) // 使用空格分割成数组
            let newArr = this.specification[index].value.concat(arr)
            newArr = Array.from(new Set(newArr)) // 去重
            this.$set(this.specification[index], 'value', newArr)
            this.clearAddValues(index)
            this.handleSpecChange('add')
            this.specification[index].name = this.cacheSpecification[index].name
            this.cacheSpecification[index].status = false
        },

        // 清空 addValues
        clearAddValues (index) {
            this.$set(this.addValues, index, '')
        },

        /**
         * [handleSpecChange 监听标签的变化,当添加标签时;求出每一行的hash id,再动态变更库存信息;当删除标签时,先清空已有库存信息,然后将原有库存信息暂存到stockCopy中,方便后面调用]
         * @param  {[string]} option ['add'|'del' 操作类型,添加或删除]
         * @return {[type]}        [description]
         */
        handleSpecChange (option) {
            const stockCopy = JSON.parse(JSON.stringify(this.childProductArray))
            if (option === 'del') {
                this.childProductArray = []
            }
            for (let i = 0; i < this.countSum(0); i++) {
                this.changeStock(option, i, stockCopy)
            }
        },

        /*
            计算属性的乘积
            @params
         specIndex 规格项目在 advancedSpecification 中的序号
        */
        countSum (specIndex) {
            let num = 1
            this.specification.forEach((item, index) => {
                if (index >= specIndex && item.value.length) {
                    num *= item.value.length
                }
            })
            return num
        },

        /**
         * [根据规格,动态改变库存相关信息]
         * @param  {[string]} option    ['add'|'del' 操作类型,添加或删除]
         * @param  {[array]} stockCopy [库存信息的拷贝]
         * @return {[type]}           [description]
         */
        changeStock (option, index, stockCopy) {
            let childProduct = {
                childProductId: 0,
                childProductSpec: this.getChildProductSpec(index),
                // childProductNo: this.defaultProductNo + index,
                childProductStock: 0,
                childProductPrice: 0,
                childProductCost: 0,
                isUse: true
            }
            const spec = childProduct.childProductSpec
            if (option === 'add') {
                // 如果此id不存在,说明为新增属性,则向 childProductArray 中添加一条数据
                if (this.stockSpecArr.findIndex((item) => objEquals(spec, item)) === -1) {
                    this.$set(this.childProductArray, index, childProduct)
                }
            } else if (option === 'del') {
                // 因为是删除操作,理论上所有数据都能从stockCopy中获取到
                let origin = ''
                stockCopy.forEach(item => {
                    if (objEquals(spec, item.childProductSpec)) {
                        origin = item
                        return false
                    }
                })
                this.childProductArray.push(origin || childProduct)
            }
        },
        // 获取childProductArray的childProductSpec属性
        getChildProductSpec (index) {
            let obj = {}
            this.specification.forEach((item, specIndex) => {
                obj[item.name] = this.getSpecAttr(specIndex, index)
            })
            return obj
        },
        /*
        根据传入的属性值,拿到相应规格的属性
        @params
         specIndex 规格项目在 advancedSpecification 中的序号
         index 所有属性在遍历时的序号
        */
        getSpecAttr (specIndex, index) {
            // 获取当前规格项目下的属性值
            const currentValues = this.specification[specIndex].value
            let indexCopy
            // 判断是否是最后一个规格项目
            if (this.specification[specIndex + 1] && this.specification[specIndex + 1].value.length) {
                indexCopy = index / this.countSum(specIndex + 1)
            } else {
                indexCopy = index
            }
            const i = Math.floor(indexCopy % currentValues.length)
            if (i.toString() !== 'NaN') {
                return currentValues[i]
            } else {
                return ''
            }
        },
        // 删除规格属性
        delSpecTag (index, num) {
            this.specification[index].value.splice(num, 1)
            this.handleSpecChange('del')
        },
        // 保存规格名
        saveSpec(index) {
            if (!this.cacheSpecification[index].name.trim()) {
                this.$message.error('名称不能为空,请注意修改')
                return
            }
            // 保存需要验证名称是否重复
            if (this.specification[index].name === this.cacheSpecification[index].name) {
                this.cacheSpecification[index].status = false
            } else {
                if (this.verifyRepeat(index)) {
                    // 如果有重复的,禁止修改
                    this.$message.error('名称重复,请注意修改')
                } else {
                    this.specification[index].name = this.cacheSpecification[index].name
                    this.cacheSpecification[index].status = false
                }
            }
        },
        // 删除规格项目
        delSpec (index) {
            this.specification.splice(index, 1)
            this.handleSpecChange('del')
        },
        verifyRepeat(index) {
            let flag = false
            this.specification.forEach((value, j) => {
                // 检查除了当前选项以外的值是否和新的值想等,如果相等,则禁止修改
                if (index !== j) {
                    if (this.specification[j].name === this.cacheSpecification[index].name) {
                        flag = true
                    }
                }
            })
            return flag
        },
        // 根据传入的条件,来判断是否显示该td
        showTd (specIndex, index) {
            // 如果当前项目下没有属性,则不显示
            if (!this.specification[specIndex]) {
                return false
                // 自己悟一下吧
            } else if (index % this.countSum(specIndex + 1) === 0) {
                return true
            } else {
                return false
            }
        },
        // 监听规格启用操作
        handleUserChange (index, value) {
            // 启用规格时,生成不重复的商品编号;关闭规格时,清空商品编号
            if (value) {
                let No = this.makeProductNoNotRepet(index)
                this.$set(this.childProductArray[index], 'childProductNo', No)
            } else {
                this.$set(this.childProductArray[index], 'childProductNo', '')
            }
        },
        // 监听商品编号的blur事件
        handleNo (index) {
            // 1.当用户输入完商品编号时,判断是否重复,如有重复,则提示客户并自动修改为不重复的商品编号
            const value = this.childProductArray[index].childProductNo
            let isRepet
            this.childProductArray.forEach((item, i) => {
                if (item.childProductNo === value && i !== index) {
                    isRepet = true
                }
            })
            if (isRepet) {
                this.$message({
                    type: 'warning',
                    message: '不允许输入重复的商品编号'
                })
                this.$set(this.childProductArray[index], 'childProductNo', this.makeProductNoNotRepet(index))
            }
        },
        // 生成不重复的商品编号
        makeProductNoNotRepet (index) {
            let No
            let i = index
            let isRepet = true
            while (isRepet) {
                No = this.defaultProductNo + i
                isRepet = this.isProductNoRepet(No)
                i++
            }
            return No
        },
        // 商品编号判重
        isProductNoRepet (No) {
            const result = this.childProductArray.findIndex((item) => {
                return item.childProductNo === No
            })
            return result > -1
        },
        // 打开设置框
        openBatch (type) {
            this.currentType = type
            this.isSetListShow = false
        },
        // 批量设置
        setBatch () {
            if (typeof this.batchValue === 'string') {
                this.$message({
                    type: 'warning',
                    message: '请输入正确的值'
                })
                return
            }
            this.childProductArray.forEach(item => {
                if (item.isUse) {
                    item[this.currentType] = this.batchValue
                }
            })
            this.cancelBatch()
        },
        // 取消批量设置
        cancelBatch () {
            this.batchValue = ''
            this.currentType = ''
            this.isSetListShow = true
        }
    }
})
</script>
<?php

namespace App\Admin\Extensions;

use Dcat\Admin\Form\Field;

class Sku extends Field {

    protected $view = 'admin.sku';

    protected static $js = [
        'https://cdn.jsdelivr.net/npm/vue/dist/vue.js'    // 这里可以将vue.js下载下来引入
    ];
}

然后在bootstrap.php 中 添加扩展组件与使用

Form::extend('sku', Sku::class);

$form->sku('skus','商品规格');
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 23

封装个扩展!!!

3年前 评论
FLaravel (楼主) 3年前

封装个扩展!!!

3年前 评论

laravel-admin有嘛。。。

3年前 评论
FLaravel (楼主) 3年前
shwfz01a

是不是缺什么东西。。

file

file

3年前 评论
FLaravel (楼主) 3年前
shwfz01a (作者) 3年前
FLaravel (楼主) 3年前
shwfz01a (作者) 3年前
yangweijie

laravel 是以jquery为主的 建议改成jquery 看你用的vue

3年前 评论
FLaravel (楼主) 3年前
porygonCN 3年前
shwfz01a

file 而且为什么js没有加载呢。。。页面都是死的。。。

3年前 评论
FLaravel (楼主) 3年前
FLaravel (楼主) 3年前
shwfz01a (作者) 3年前

file这个是哪里缺少文件吗?

3年前 评论
赵昊 2年前

很棒!!!铁汁

3年前 评论

这个要咋继承到dcat里呀

2年前 评论

@FLaravel 请问这个怎么处理数据回显 $form->sku('skus','商品规格');可以传入id之类的参数吗

2年前 评论

喜欢vue,后端写起来方便

4个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!