Dcat-Admin中的商品SKU

FLaravel發表於2020-12-30

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 協議》,轉載必須註明作者和本文連結

相關文章