Dcat-Admin 商品規格SKU,先看效果圖
我只是一名搬運工,將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 協議》,轉載必須註明作者和本文連結