商品Address

This commit is contained in:
2025-01-16 11:34:04 +08:00
parent 98a5c4f73f
commit c59d865fd8
33 changed files with 3179 additions and 3 deletions

View File

@ -0,0 +1,349 @@
{extend name="main"}
{block name="button"}
<button data-target-submit class='layui-btn layui-btn-sm'>保存数据</button>
<button data-target-backup class="layui-btn layui-btn-sm layui-btn-danger" data-confirm="确认要取消编辑吗?">取消编辑</button>
{/block}
{block name="content"}
{include file='goods/form_style'}
<form action="{:sysuri()}" method="post" data-auto="true" class="layui-form layui-card">
<div class="layui-card-body">
<!--{notempty name='marks'}-->
<div class="layui-form-item">
<span class="help-label label-required-prev"><b>商品标签</b>Goods Mark</span>
<div class="layui-textarea help-checks">
{foreach $marks as $mark}
<label class="think-checkbox">
{if isset($vo.marks) && is_array($vo.marks) && in_array($mark, $vo.marks)}
<input name="marks[]" type="checkbox" value="{$mark}" lay-ignore checked> {$mark}
{else}
<input name="marks[]" type="checkbox" value="{$mark}" lay-ignore> {$mark}
{/if}
</label>
{/foreach}
</div>
</div>
<!--{/notempty}-->
<!--{notempty name='cates'}-->
<div class="layui-form-item block relative">
<span class="help-label label-required-prev"><b>所属分类</b>Category Name</span>
<select class="layui-select" lay-search name="cates">
{foreach $cates as $cate}{if in_array($cate.id, $vo.cates)}
<option selected value="{:arr2str($cate.ids)}">{:join(' > ', $cate.names)}</option>
{else}
<option value="{:arr2str($cate.ids)}">{:join(' > ', $cate.names)}</option>
{/if}{/foreach}
</select>
</div>
<!--{/notempty}-->
<label class="layui-form-item block relative">
<span class="help-label"><b>商品名称</b>Product Name</span>
<input class="layui-input" name="name" placeholder="请输入商品名称" vali-name="商品名称" required value="{$vo.name|default=''}">
</label>
<div class="layui-form-item">
<span class="help-label label-required-prev"><b>商品封面及轮播图片</b>Cover and Pictures</span>
<table class="layui-table">
<thead>
<tr>
<th class="text-center">商品封面</th>
<th class="full-width">轮播图片 <span class="color-desc font-w1">( 轮播图片推荐的宽高比为 5:3 )</span></th>
</tr>
<tr>
<td class="text-center text-top ta-p-0">
<div class="help-images">
<input name="cover" data-max-width="500" data-max-height="500" type="hidden" value="{$vo.cover|default=''}">
<script>$('[name="cover"]').uploadOneImage();</script>
</div>
</td>
<td class="text-left ta-p-0">
<div class="help-images">
<input name="slider" data-max-width="2048" data-max-height="1024" type="hidden" value="{$vo.slider|default=''}">
<script>$('[name="slider"]').uploadMultipleImage();</script>
</div>
</td>
</tr>
</thead>
</table>
</div>
<div class="layui-form-item" id="GoodsSpecsEditor">
<span class="help-label label-required-prev">
<b>商品规格</b><span class="color-red font-s12">( 规格填写后不允许再次增加规格分组,规格图片推荐的宽高比为 5:3 )</span>
</span>
<div class="ta-mb-10" v-for="(x,$index) in specs" :key="$index">
<div class="goods-spec-box ta-pr-10" style="background:#ddd">
<span class="text-center goods-spec-name">分组</span>
<label class="label-required-null inline-block">
<input v-model.trim="x.name" @change="trimSpace(x,'name')" vali-name="分组" placeholder="请输入分组名称" required>
</label>
<div class="pull-right flex">
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="addSpecVal(x.list)">增加</a>
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="upSpecRow(specs,$index)" :class="{false:'layui-btn-disabled'}[$index>0]">上移</a>
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="dnSpecRow(specs,$index)" :class="{false:'layui-btn-disabled'}[$index<specs.length-1]">下移</a>
<div style="display:none" :class="{true:'layui-show'}[mode==='add' && specs.length>0]">
<a class="layui-btn layui-btn-sm layui-btn-primary goods-spec-btn" @click="delSpecRow(specs,$index)" v-if="specs.length>1">删除</a>
</div>
</div>
</div>
<div class="goods-spec-box" v-if="x.list && x.list.length>0">
<label class="label-required-null nowrap" v-for="xx in x.list">
<input lay-ignore @click="xx.check=checkListChecked(x.list,$event.target.checked)" v-model.trim="xx.check" type="checkbox">
<input v-model.trim="xx.name" @change="trimSpace(xx,'name')" vali-name="规格" placeholder="请输入规格" required type="text">
<a class="layui-icon layui-icon-close goods-spec-close" @click="delSpecVal(x.list,$index)" v-if="x.list.length>1"></a>
</label>
</div>
</div>
<div v-if="mode==='add'">
<a class="layui-btn layui-btn-sm layui-btn-primary" @click="addSpecRow(specs)" v-if="specs.length<3">增加规则分组</a>
<p class="ta-mt-10"><span class="color-red">请完成属性修改后再编辑下面的规格信息,否则规格数据会丢失!</span></p>
</div>
<table class="layui-table goods-spec-table">
<thead>
<tr>
<th class="layui-bg-gray" :colspan="specs.length">规格</th>
<th colspan="3">商品价格</th>
<th colspan="5">其他属性</th>
</tr>
<tr>
<th class="nowrap layui-bg-gray" v-for="x in specs"><b>{{x.name}}</b></th>
<th class="nowrap" @click="batchSet('cost',2,'请输入商品成本价格')"><b>成本价</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('market',2,'请输入商品市场价格')"><b>市场价</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('selling',2,'请输入商品销售积分格')"><b>销售积分</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('virtual',0,'请输入虚拟销量数值')"><b>虚拟销量</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" @click="batchSet('express',0,'请输入快递计费系数')"><b>快递系数</b><i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" width="9%" @click="batchSet('gsku',null,'请输入商品SKU代码')"><b>商品SKU</b> <i class="layui-icon">&#xe63c;</i></th>
<th class="nowrap" width="6%">规格图片</th>
<th class="nowrap" width="6%"><b>销售状态</b></th>
</tr>
</thead>
<tbody>
<tr v-for="(item,hash) in items" :key="hash">
<td class="layui-bg-gray nowrap text-center" v-if="s.check" v-for="s in item.s">{{s.name}}</td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.cost"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.market"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.selling"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.virtual"></label></td>
<td><label><input class="layui-input" @blur="syncSet(hash)" v-model.trim="item.v.express"></label></td>
<td><label><input class="layui-input font-code" v-model.trim="item.v.gsku"></label></td>
<td class="upload-image-xs ta-p-0 text-center">
<upload-image v-model.trim="item.v.image" :showinput="false"></upload-image>
</td>
<td class="layui-bg-gray"><label class="think-checkbox"><input lay-ignore v-model.trim="item.v.status" type="checkbox"></label></td>
</tr>
</tbody>
</table>
<p class="color-desc">请注意商品的 sku 在系统中仅作为显示之用,系统会根据规格生成哈希值作为商品唯一区别码!</p>
<label class="layui-hide">
<textarea class="layui-textarea" name="specs">{{JSON.stringify(specs)}}</textarea>
<textarea class="layui-textarea" name="items">{{JSON.stringify(attrs)}}</textarea>
</label>
</div>
<label class="layui-form-item block">
<span class="help-label"><b>商品简介描述</b></span>
<textarea class="layui-textarea" name="remark" placeholder="请输入商品简介描述">{$vo.remark|default=''|raw}</textarea>
</label>
<div class="layui-form-item block">
<span class="help-label label-required-prev"><b>商品富文本详情</b></span>
<textarea class="layui-hide" name="content">{$vo.content|default=''|raw}</textarea>
</div>
<div class="hr-line-dashed ta-mt-40"></div>
<input name="code" type="hidden" value="{$vo.code}">
<div class="layui-form-item text-center">
<button class="layui-btn layui-btn-danger" type="button" data-target-backup>取消编辑</button>
<button class="layui-btn" type="submit">保存商品</button>
</div>
</div>
</form>
{/block}
{block name='script'}
<label class="layui-hide">
<textarea id="GoodsSpecs">{$vo.specs|raw|default=''}</textarea>
<textarea id="GoodsItems">{$vo.items|raw|default=''}</textarea>
</label>
<script>
/*! 加载扩展插件 */
require(['md5', 'vue', 'ckeditor'], function (md5, Vue) {
// 创建富文本
window.createEditor('[name=content]', {height: 500});
// 图片上传组件
Vue.component('UploadImage', {
data: () => ({eid: Math.random().toString().replace('0.', 'up')}),
model: {prop: 'mvalue', event: 'change'}, props: {mvalue: {type: String, default: ''}},
template: '<input class="layui-hide" v-model="mvalue" :id="eid" readonly>',
mounted: function () {
this.$nextTick(() => $('#' + this.eid).uploadOneImage().on('change', e => this.$emit('change', e.target.value)));
}
});
// 字段格式规则
let rules = {
image: '_',
cost: '(parseFloat(_)||0).toFixed(2)',
market: '(parseFloat(_)||0).toFixed(2)',
selling: '(parseFloat(_)||0).toFixed(2)',
balance: '(parseFloat(_)||0).toFixed(2)',
integral: '(parseFloat(_)||0).toFixed(0)',
express: '(parseFloat(_)||0).toFixed(0)',
virtual: '(parseFloat(_)||0).toFixed(0)',
allow_balance: '(parseFloat(_)||0).toFixed(2)',
allow_integral: '(parseFloat(_)||0).toFixed(0)',
};
// 历史及缓存数据
let cache = {};
layui.each(JSON.parse($('#GoodsItems').val() || '{}') || {}, function (k, v) {
for (let i in v) setValue(k, i, v[i]);
});
// 创建 Vue2 实例
let app = new Vue({
el: '#GoodsSpecsEditor', data: () => ({
mode: '{$mode|default="add"}', items: {}, attrs: {},
specs: JSON.parse(JSON.parse($('#GoodsSpecs').val() || '[]')) || []
}),
created: function () {
this.specs.length < 1 && addSpecRow(this.specs);
},
watch: {
specs: {
deep: true, immediate: true, handler: function (values) {
// 笛卡尔积生成商品SKU多规格算法
let items = {}, attrs = [], list = values.reduce((a, b) => {
let res = [];
a.map(x => b.list.map(y => y.check && res.push(x.concat(Object.assign({group: b.name}, y)))));
return res;
}, [[]]);
// 初始化商品规格同步变量值
list.map(function (cols) {
let keys = [], specs = [], unids = [];
cols.map(v => keys.push(v.group + '::' + v.name) && specs.push(v) && unids.push(v.unid));
let hash = md5.hash("{$vo.code}#" + unids.sort().join(';')), values = {
hash: hash,
spec: keys.join(';;'),
gsku: getValue(hash, 'gsku', withRandString(14, 'S')),
image: getValue(hash, 'image', ''),
status: !!getValue(hash, 'status', 1),
cost: getValue(hash, 'cost', '0.00'),
market: getValue(hash, 'market', '0.00'),
selling: getValue(hash, 'selling', '0.00'),
balance: getValue(hash, 'balance', '0.00'),
integral: getValue(hash, 'integral', '0'),
express: getValue(hash, 'express', '1'),
virtual: getValue(hash, 'virtual', '0'),
allow_balance: getValue(hash, 'allow_balance', '0.00'),
allow_integral: getValue(hash, 'allow_integral', '0'),
};
items[hash] = {s: specs, v: values};
attrs.push(values)
})
this.attrs = attrs;
this.items = items;
}
}
},
methods: {
/*! 同步格式化值 */
syncSet: function (hash) {
let v = this.items[hash].v;
for (let k in v) v[k] = setValue(hash, k, v[k])
},
/*! 批量设置数值 */
batchSet: function (name, fixed, title) {
let min = (0 / Math.pow(10, parseInt(fixed))).toFixed(fixed), max = (999999).toFixed(fixed);
layer.prompt({
title: title || (fixed === null ? '请输入内容' : '请输入数量【 取值范围:1 - 999999 】'),
formType: 0, value: fixed === null ? '' : min, success: function ($el) {
$el.find('.layui-layer-input').attr({'data-value-min': min, 'data-value-max': max, 'data-blur-number': fixed});
}
}, function (value, index) {
if (fixed !== null) value = (parseFloat(value) || 0).toFixed(fixed);
for (let i in app.items) app.items[i].v[name] = value;
layer.close(index) || app.$forceUpdate()
});
},
/*! 去除空白字符 */
trimSpace: function (x, i) {
return x[i] = (x[i] || '').replace(/[\s,;'"]+/ig, '');
},
/*! 判断规则选择 */
checkListChecked: function (data, check) {
for (let i in data) if (data[i].check) return check;
return true;
},
/*! 下移整行规格分组 */
dnSpecRow: function (items, index) {
index + 1 < items.length && (item => items.splice(index + 1, 1) && items.splice(index, 0, item))(items[index + 1]);
},
/*! 上移整行规格分组 */
upSpecRow: function (items, index) {
index > 0 && (item => items.splice(index - 1, 1) && items.splice(index, 0, item))(items[index - 1]);
},
/*! 增加整行规格分组 */
addSpecRow: function (data) {
addSpecRow(data);
},
/*! 移除整行规格分组 */
delSpecRow: function (items, index) {
items.splice(index, 1);
},
/*! 增加分组的属性 */
addSpecVal: function (data) {
addSpecVal(data);
},
/*! 移除分组的属性 */
delSpecVal: function (data, $index) {
data.splice($index, 1);
},
}
})
/*! 动态设置内容 */
function setValue(hash, name, value, filter) {
filter = filter || rules[name] || null;
if (typeof filter === 'string' && filter.indexOf('_') > -1) {
value = eval(filter.replace('_', "'" + value + "'"));
}
cache[hash] = cache[hash] || {};
return cache[hash][name] = value;
}
/*!动态读取内容 */
function getValue(hash, name, value) {
let _cache = cache[hash] || {};
if (typeof _cache[name] === 'undefined') {
setValue(hash, name, value, '_')
_cache = cache[hash] || {};
}
return _cache[name];
}
// 创建分组
function addSpecRow(data) {
data.push({name: '规格分组' + (data.length || ''), list: addSpecVal([])})
}
// 创建规格
function addSpecVal(data) {
data.push({name: '规格属性' + (data.length || ''), unid: withRandString(16), check: true});
return data;
}
// 随机字符串
function withRandString(length, prefix) {
return (function (time, code) {
code += parseInt(time.substring(0, 1)) + parseInt(time.substring(1, 2)) + time.substring(2);
while (code.length < length) code += (Math.random()).toString().replace('.', '');
return code.substring(0, length);
})(Date.now().toString(), prefix || '')
}
});
</script>
{/block}