运行环境
Dcat版本: 2.0.14
Laravel版本: 8.0
PHP版本: 7.4
MySQL版本: 8.0
详细内容
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','商品规格');
转载自:https://learnku.com/articles/53112