';
html += '';
html += '';
html += '' + (row.goods_sn || '-') + '';
html += '';
if (row.brand && row.brand.name) {
html += '';
html += '';
html += '' + row.brand.name + '';
html += '';
}
html += '
';
if (row.subtitle) {
html += '
' + row.subtitle + '
';
}
if (row.spec_type == 1 && row.default_sku && row.default_sku.sku_attr) {
html += '
';
html += '';
html += '默认规格: ' + Controller.formatSkuAttr(row.default_sku.sku_attr);
html += '
';
}
if (row.activity_name && row.activity_name.trim() !== '') {
html += '
';
html += '';
html += '限时折扣活动';
html += '' + row.activity_name + '';
html += '
';
}
html += '
';
return html;
},
formatGoodsType: function(value, row, index) {
var goodsTypeList = Controller.api.parseConfigJson('goodsTypeList', {});
var text = (goodsTypeList && goodsTypeList[value]) ? goodsTypeList[value] : '未知';
var typeStyles = {
1: {class: 'label-primary', icon: 'fa-cube'},
2: {class: 'label-info', icon: 'fa-key'},
3: {class: 'label-warning', icon: 'fa-cloud'},
4: {class: 'label-success', icon: 'fa-ticket'}
};
var style = typeStyles[parseInt(value)] || {class: 'label-primary', icon: 'fa-cube'};
return ' ' + text + '';
},
formatSpecType: function(value, row, index) {
var specTypeList = Controller.api.parseConfigJson('specTypeList', {});
var label = (specTypeList && specTypeList[value]) ? specTypeList[value] : (value == 1 ? '多规格' : '单规格');
var className = value == 1 ? 'label-info' : 'label-success';
return ' ' + label + '';
},
formatGoodsStatus: function(value, row, index) {
var statusList = Controller.api.parseConfigJson('statusList', {});
var statusStyles = {
0: {class: 'label-warning', icon: 'fa-warehouse'},
1: {class: 'label-success', icon: 'fa-check-circle'},
2: {class: 'label-danger', icon: 'fa-times-circle'},
3: {class: 'label-default', icon: 'fa-eye-slash'}
};
var text = statusList && statusList[value] ? statusList[value] : '未知';
var style = statusStyles[value] || {class: 'label-default', icon: 'fa-question'};
return ' ' + text + '';
},
select: function () {
var specTypeList = Controller.api.parseConfigJson('specTypeList', {0: "单规格", 1: "多规格"});
var urlParams = new URLSearchParams(window.location.search);
var selectMode = urlParams.get('selectMode') || 'multiple';
var maxSelect = parseInt(urlParams.get('maxSelect')) || 0;
Table.api.init({
extend: {
index_url: 'shop/goods/index' + location.search,
table: 'shop_goods',
}
});
Table.config.dragsortfield = '';
var table = $("#table");
var columns = [
{field: 'id', title: 'ID', width: 60},
{field: 'image', title: '图片', width: 120, operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
{
field: 'title',
title: '商品信息',
operate: 'LIKE',
formatter: function (value, row, index) {
var html = '
';
html += '
' + value + '
';
html += '
';
html += '编码: ' + (row.goods_sn || '-');
if (row.category && row.category.name) {
html += ' | 分类: ' + row.category.name;
}
html += '
';
html += '
';
return html;
}
},
{field: 'spec_type', title: '规格', width: 80, searchList: specTypeList, formatter: Table.api.formatter.label},
{
field: 'price',
title: '价格',
width: 100,
operate: 'BETWEEN',
formatter: function (value, row, index) {
return '¥' + (value || '0.00') + '';
}
},
{field: 'stocks', title: '库存', width: 80},
{
field: 'operate',
title: selectMode === 'single' ? '选择' : '操作',
width: selectMode === 'single' ? 80 : 100,
operate: false,
formatter: function (value, row, index) {
var html = '';
if (selectMode === 'single') {
html = '';
} else {
html = '';
}
return html;
}
},
{field: 'category_id', title: '分类', visible: false, searchList: $.getJSON("shop/category/getList")},
{field: 'goods_sn', title: '商品编码', visible: false, operate: 'LIKE'},
];
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
clickToSelect: false,
columns: [columns]
});
var selectedGoods = [];
$(document).on('click', '.btn-select-single', function () {
var $btn = $(this);
var index = $btn.data('index');
var rowData = table.bootstrapTable('getData')[index];
Fast.api.close([rowData]);
});
$(document).on('click', '.btn-select-multi', function () {
var $btn = $(this);
var id = $btn.data('id');
var index = $btn.data('index');
var isSelected = $btn.data('selected');
var rowData = table.bootstrapTable('getData')[index];
if (!isSelected) {
if (maxSelect > 0 && selectedGoods.length >= maxSelect) {
Toastr.warning('最多只能选择 ' + maxSelect + ' 个商品');
return;
}
selectedGoods.push(rowData);
$btn.removeClass('btn-success').addClass('btn-danger')
.html(' 取消选择')
.attr('title', '点击从选择列表中移除')
.data('selected', true);
} else {
selectedGoods = selectedGoods.filter(function(item) {
return item.id != id;
});
$btn.removeClass('btn-danger').addClass('btn-success')
.html(' 选择')
.attr('title', '点击添加到选择列表')
.data('selected', false);
}
updateSelectedCount();
});
function updateSelectedCount() {
var count = selectedGoods.length;
var countText = count > 0 ? '已选择 ' + count + ' 个商品' : '请选择商品';
$('.btn-goods-select').text(countText === '请选择商品' ? '确认选择' : '确认选择 (' + count + ')');
if (maxSelect > 0 && count >= maxSelect) {
$('.btn-select-multi[data-selected="false"]').prop('disabled', true).addClass('disabled');
} else {
$('.btn-select-multi[data-selected="false"]').prop('disabled', false).removeClass('disabled');
}
}
$(document).on('click', '.btn-goods-select', function () {
if (selectedGoods.length === 0) {
Layer.alert('请选择商品');
return;
}
Fast.api.close(selectedGoods);
});
Table.api.bindevent(table);
},
recyclebin: function () {
Table.api.init({
extend: {
'dragsort_url': ''
}
});
var table = $("#table");
table.bootstrapTable({
url: 'shop/goods/recyclebin' + location.search,
pk: 'id',
sortName: 'id',
columns: [
[{
checkbox: true
},
{
field: 'id',
title: __('Id')
},
{
field: 'title',
title: __('Title'),
align: 'left'
},
{
field: 'deletetime',
title: __('Deletetime'),
operate: 'RANGE',
addclass: 'datetimerange',
formatter: Table.api.formatter.datetime
},
{
field: 'operate',
width: '130px',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
buttons: [{
name: 'Restore',
text: __('Restore'),
classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
icon: 'fa fa-rotate-left',
url: 'shop/goods/restore',
refresh: true
},
{
name: 'Destroy',
text: __('Destroy'),
classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
icon: 'fa fa-times',
url: 'shop/goods/destroy',
refresh: true
}
],
formatter: Table.api.formatter.operate
}
]
]
});
Table.api.bindevent(table);
},
getAttribute: function (category_id, attribute_ids = []) {
$.get('shop/attribute/attrs?category_id=' + category_id, function (res) {
const {code, data, msg} = res;
if (code) {
$('#attributes').html(data.length ? Template('attributetpl', {row: data, attribute_ids}) : ' ');
var instance = $("form[data-toggle='validator']").data("validator");
$('#attributes input').each(function () {
instance._parse(this);
});
} else {
Toastr.error(msg);
}
})
},
add: function () {
var that = this;
$(document).ready(function() {
that.initializeForm();
Controller.api.bindevent();
Controller.api.add_sku();
});
},
edit: function () {
var that = this;
$(document).ready(function() {
that.initializeForm(true);
that.initEditTypeState();
setTimeout(function() {
that.initSingleSpecData();
}, 1000);
Controller.api.bindevent();
Controller.api.add_sku();
});
},
initializeForm: function(isEdit) {
var that = this;
that.initGoodsTypeCards();
that.initOnlineOfflineControl();
that.initContentPreview();
TabValidator.init();
that.watchSingleSpecFormVisibility();
if (isEdit) {
$('#c-category_id, #c-category_ids').on('change', function () {
var category_id = $(this).val();
});
}
},
watchSingleSpecFormVisibility: function() {
var that = this;
if (typeof MutationObserver !== 'undefined') {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var $singleSpecForm = $('#single-spec-form');
if ($singleSpecForm.length > 0 && $singleSpecForm.is(':visible')) {
var $priceField = $('#c-price');
var $stocksField = $('#c-stocks');
if ($priceField.length > 0 && $stocksField.length > 0) {
var currentPrice = $priceField.val();
var currentStocks = $stocksField.val();
if ((!currentPrice || currentPrice === '') &&
(!currentStocks || currentStocks === '') &&
Config.goods && Config.goods.spec_type == '0' &&
Config.goods_skus && Config.goods_skus.length > 0) {
setTimeout(function() {
that.initSingleSpecData();
}, 100);
}
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['style', 'class']
});
this._singleSpecObserver = observer;
}
$('a[href="#skus"]').on('shown.bs.tab', function() {
setTimeout(function() {
var specType = $('input[name="row[spec_type]"]:checked').val();
if (specType == '0') {
var $priceField = $('#c-price');
var $stocksField = $('#c-stocks');
if ($priceField.length > 0 && $stocksField.length > 0) {
var currentPrice = $priceField.val();
var currentStocks = $stocksField.val();
if ((!currentPrice || currentPrice === '') &&
(!currentStocks || currentStocks === '')) {
that.initSingleSpecData();
}
}
}
}, 200);
});
},
isMultiSelectEmpty: function(value) {
if (Array.isArray(value)) {
return value.length === 0;
} else if (typeof value === 'string') {
var trimmed = value.trim();
return trimmed === '' || trimmed === '0' || trimmed === 'null' || trimmed === 'undefined';
} else {
return !value || value === null || value === undefined;
}
},
initGoodsTypeCards: function() {
if ($('.goods-type-card').length === 0) {
return;
}
function updateTypeCardState() {
var $cards = $('.goods-type-card');
if ($cards.length > 0) {
$cards.removeClass('selected');
$('.goods-type-card input[type="radio"]:checked').closest('.goods-type-card').addClass('selected');
}
}
var $radioInputs = $('.goods-type-card input[type="radio"]');
if ($radioInputs && $radioInputs.length > 0) {
$radioInputs.off('change.typecard').on('change.typecard', function() {
updateTypeCardState();
});
}
updateTypeCardState();
},
initEditTypeState: function() {
if (Config.goods && Config.goods.type) {
var $typeRadio = $('input[name="row[type]"][value="' + Config.goods.type + '"]');
if ($typeRadio.length > 0) {
$typeRadio.prop('checked', true);
$typeRadio.trigger('change');
}
}
if (Config.goods && Config.goods.online_type !== undefined) {
var $onlineTypeRadio = $('input[name="row[online_type]"][value="' + Config.goods.online_type + '"]');
if ($onlineTypeRadio.length > 0) {
$onlineTypeRadio.prop('checked', true);
$onlineTypeRadio.trigger('change');
}
}
if (Config.goods) {
if (Config.goods.scheduled_online_time) {
$('#c-scheduled_online_time').val(Config.goods.scheduled_online_time);
}
if (Config.goods.is_auto_offline) {
$('#c-is_auto_offline').prop('checked', true);
if (Config.goods.scheduled_offline_time) {
$('#c-scheduled_offline_time').val(Config.goods.scheduled_offline_time);
}
}
}
if (Config.goods && Config.goods.spec_type !== undefined) {
var $specTypeRadio = $('input[name="row[spec_type]"][value="' + Config.goods.spec_type + '"]');
if ($specTypeRadio.length > 0) {
$specTypeRadio.prop('checked', true);
$specTypeRadio.trigger('change');
setTimeout(function() {
$(document).trigger('fa.event.favisible', $specTypeRadio);
}, 100);
}
}
this.initSingleSpecData();
},
initSingleSpecData: function() {
var that = this;
if (Config.goods && Config.goods.spec_type == '0' && Config.goods_skus && Config.goods_skus.length > 0) {
var sku = Config.goods_skus[0];
var fillAttempts = 0;
var maxAttempts = 5;
function attemptFill() {
fillAttempts++;
var $priceField = $('#c-price');
var $stocksField = $('#c-stocks');
if ($priceField.length === 0 || $stocksField.length === 0) {
if (fillAttempts < maxAttempts) {
setTimeout(attemptFill, 200 * fillAttempts);
}
return;
}
try {
$priceField.val(sku.price || '0.01');
$('#c-lineation_price').val(sku.lineation_price || '0.00');
$('#c-cost_price').val(sku.cost_price || '0.00');
$stocksField.val(sku.stocks || '1');
$('#c-weight').val(sku.weight || '0.00');
$('#c-volume').val(sku.volume || '0.00');
$('#c-sku-sn').val(sku.sku_sn || '');
$('#c-single-image').val(sku.image || '');
if (sku.image) {
var $preview = $('#p-single-image');
if ($preview.length > 0) {
var img = '
';
$preview.html(img);
}
}
$priceField.trigger('change');
$stocksField.trigger('change');
setTimeout(function() {
var currentPrice = $priceField.val();
var currentStocks = $stocksField.val();
if (!currentPrice || currentPrice === '' || !currentStocks || currentStocks === '') {
if (fillAttempts < maxAttempts) {
setTimeout(attemptFill, 300);
}
}
}, 100);
} catch (error) {
if (fillAttempts < maxAttempts) {
setTimeout(attemptFill, 200 * fillAttempts);
}
}
}
setTimeout(attemptFill, 300);
}
},
validateField: function(fieldName) {
var $field = $('[name="' + fieldName + '"]');
var result = {
isValid: true,
label: fieldName,
element: null,
message: ''
};
if ($field.length > 0) {
result.element = $field[0];
var $formGroup = $field.closest('.form-group');
var $label = $formGroup.find('.control-label');
if ($label.length > 0) {
result.label = $label.text().replace(/[::*]/g, '').trim();
}
// 使用 FastAdmin 的 Nice-validator 进行验证
var $form = $field.closest('form');
var validator = $form.data('validator');
if (validator && typeof validator.isValid === 'function') {
// 触发单个字段验证
var fieldResult = validator.isValid($field[0]);
if (!fieldResult) {
result.isValid = false;
// 获取错误消息
var $msgBox = $field.closest('.form-group').find('.msg-box');
if ($msgBox.length > 0) {
result.message = $msgBox.text() || result.label + '验证失败';
} else {
result.message = result.label + '验证失败';
}
}
} else {
// FastAdmin 的 Nice-validator 可能尚未初始化,使用基础检查
var value = $field.val();
var rules = $field.attr('data-rule');
if (rules && rules.indexOf('required') !== -1) {
if (!value || value.trim() === '') {
result.isValid = false;
result.message = result.label + '不能为空';
}
}
}
}
return result;
},
validatePriceStock: function() {
var result = {
isValid: true,
errors: [],
errorFields: []
};
var specType = $('input[name="row[spec_type]"]:checked').val();
if (specType == '0') {
var price = $('#c-price').val();
var stocks = $('#c-stocks').val();
if (!price || parseFloat(price) <= 0) {
result.isValid = false;
result.errors.push('销售价格');
result.errorFields.push(document.getElementById('c-price'));
}
if (!stocks || parseInt(stocks) <= 0) {
result.isValid = false;
result.errors.push('库存数量');
result.errorFields.push(document.getElementById('c-stocks'));
}
} else if (specType == '1') {
if (typeof vm !== 'undefined' && vm && vm.tableData && vm.tableData.length > 0) {
var validSkus = vm.tableData.filter(function(sku) {
return sku.status == 1 && sku.price > 0 && sku.stocks > 0;
});
if (validSkus.length === 0) {
result.isValid = false;
result.errors.push('至少需要一个有效的SKU规格(价格>0,库存>0,且状态为显示)');
}
} else {
result.isValid = false;
result.errors.push('请先设置商品规格');
}
}
return result;
},
initOnlineOfflineControl: function() {
$('input[name="row[online_type]"]').change(function() {
var value = $(this).val();
if (value == '3') {
$('#scheduled-online-time').show();
} else {
$('#scheduled-online-time').hide();
}
if (value == '1' || value == '3') {
$('#offline-time-setting').show();
} else {
$('#offline-time-setting').hide();
}
});
$('input[name="row[online_type]"]:checked').trigger('change');
$('#c-is_auto_offline').change(function() {
if ($(this).is(':checked')) {
$('#scheduled-offline-time').show();
} else {
$('#scheduled-offline-time').hide();
}
});
if ($('#c-is_auto_offline').is(':checked')) {
$('#scheduled-offline-time').show();
} else {
$('#scheduled-offline-time').hide();
}
},
initContentPreview: function() {
var previewTimer = null;
var lastContent = "";
$('a[href="#detail"]').on('shown.bs.tab', function (e) {
updatePreview();
if (previewTimer === null) {
previewTimer = setInterval(function() {
checkContentChange();
}, 1000);
}
});
$('a').not('a[href="#detail"]').on('shown.bs.tab', function (e) {
if (previewTimer) {
clearInterval(previewTimer);
previewTimer = null;
}
});
function checkContentChange() {
var editor = $("#c-content").data("nkeditor");
if (!editor) return;
var content = editor.html();
if (content !== lastContent) {
lastContent = content;
updatePreview();
}
}
function updatePreview() {
var editor = $("#c-content").data("nkeditor");
if (!editor) return;
var content = editor.html();
$('#content-preview').html(content || '
暂无内容
');
adjustPreviewHeight();
}
function adjustPreviewHeight() {
var $editorContainer = $('.ke-container');
if ($editorContainer.length > 0) {
var editorHeight = $editorContainer.height();
$('#content-preview').css('height', editorHeight - 40 + 'px');
}
}
setTimeout(function() {
adjustPreviewHeight();
updatePreview();
}, 1000);
},
// 解析sku_attr数据的通用方法
parseSkuAttr: function(skuAttr) {
if (!skuAttr) {
return [];
}
try {
if (skuAttr.charAt(0) === '[' || skuAttr.charAt(0) === '{') {
// JSON格式
return JSON.parse(skuAttr);
} else {
// 字符串格式:规格名:规格值,规格名:规格值
var attrs = skuAttr.split(',');
var result = [];
attrs.forEach(function(attr) {
var parts = attr.split(':');
if (parts.length >= 2) {
result.push({
name: parts[0].trim(),
key: parts[0].trim(),
value: parts[1].trim(),
type: 'basic'
});
}
});
return result;
}
} catch (e) {
console.warn('解析sku_attr失败:', skuAttr, e);
return [];
}
},
// 生成sku_attr数据
generateSkuAttr: function(specValues) {
if (!specValues || !Array.isArray(specValues)) {
return '';
}
var attrs = [];
specValues.forEach(function(value) {
if (value.name && value.value) {
attrs.push(value.name + ':' + value.value);
}
});
return attrs.join(',');
},
formatSkuAttr: function(skuAttr) {
if (!skuAttr) {
return '';
}
var attrs = this.parseSkuAttr(skuAttr);
if (attrs.length > 0) {
var formatted = [];
attrs.forEach(function(attr) {
if (attr.name && attr.value) {
formatted.push(attr.name + ': ' + attr.value);
} else if (attr.key && attr.value) {
formatted.push(attr.key + ': ' + attr.value);
}
});
return formatted.join(' | ');
}
return skuAttr;
},
api: {
parseConfigJson: function(configKey, defaultValue) {
var configValue = Config[configKey] || defaultValue || {};
if (typeof configValue === 'string') {
try {
return JSON.parse(configValue);
} catch (e) {
return defaultValue || {};
}
}
return configValue;
},
initSpecTypeControl: function() {
$('input[name="row[spec_type]"]').off('change.spectype').on('change.spectype', function() {
var specType = $(this).val();
$(document).trigger('fa.event.favisible');
if (specType == '0') {
if (typeof vm !== 'undefined' && vm) {
// vm.specList = [];
// vm.tableData = [];
}
setTimeout(function() {
Controller.initSingleSpecData();
}, 200);
}
if (specType == '1' && typeof vm !== 'undefined' && vm) {
var $multiSpecForm = $('#multi-spec-form');
if ($multiSpecForm.length > 0 && !$multiSpecForm.is(':visible')) {
setTimeout(function() {
$(document).trigger('fa.event.favisible');
}, 100);
}
}
});
$('input[name="row[spec_type]"]:checked').trigger('change.spectype');
},
bindevent: function () {
$('[data-toggle="popover"]').popover({
trigger: 'hover',
html: true,
container: 'body'
});
$(document).on("click", ".btn-legal", function (a) {
Fast.api.ajax({
url: "shop/ajax/check_content_islegal",
data: {content: $("#c-content").val()}
}, function (data, ret) {
}, function (data, ret) {
if ($.isArray(data)) {
Layer.alert(__('Banned words') + ":" + data.join(","));
}
});
});
$(document).on("click", ".btn-keywords", function (a) {
Fast.api.ajax({
url: "shop/ajax/get_content_keywords",
data: {title: $("#c-title").val(), content: $("#c-content").val()}
}, function (data, ret) {
$("#c-keywords").val(data.keywords);
$("#c-description").val(data.description);
});
});
var $form = $("form[role=form]");
Form.api.bindevent($form, function (data, ret) {
TabValidator.clearTabErrors();
}, function (data, ret) {
var errorMessage = ret.msg || '提交失败';
var fieldName = null;
var extraData = null;
if (ret.data && typeof ret.data === 'object') {
if (ret.data.errors) {
extraData = { errors: ret.data.errors };
} else if (ret.data.field) {
fieldName = ret.data.field;
}
}
if (!fieldName && errorMessage) {
var fieldPatterns = [
/(\w+)\s*(字段|不能|必须|不可)/,
/row\[(\w+)\]/,
/'(\w+)'\s*(字段|必须|不能)/,
/(\w+)\s*is\s*(required|invalid)/,
/请输入\s*(\w+)/,
/(\w+)\s*格式不正确/
];
for (var i = 0; i < fieldPatterns.length; i++) {
var match = errorMessage.match(fieldPatterns[i]);
if (match) {
fieldName = match[1];
break;
}
}
}
if (fieldName) {
var targetTab = TabValidator.getTabByFieldName(fieldName);
if (targetTab) {
var steps = [
{ id: '#basics' }, { id: '#skus' }, { id: '#delivery' },
{ id: '#detail' }, { id: '#params' }, { id: '#sales' }
];
var stepIndex = steps.findIndex(function(step) {
return step.id === targetTab;
});
if (stepIndex !== -1) {
TabValidator.goToStep(stepIndex);
setTimeout(function() {
TabValidator.highlightErrorFields(targetTab);
}, 300);
}
}
}
TabValidator.handleValidationError(errorMessage, fieldName, extraData);
Toastr.error(errorMessage);
}, function (success, error) {
TabValidator.clearTabErrors();
if (typeof TabValidator.validateAllSteps === 'function') {
var allValidationPassed = TabValidator.validateAllSteps();
if (!allValidationPassed.isValid) {
if (allValidationPassed.firstErrorStep !== -1) {
TabValidator.goToStep(allValidationPassed.firstErrorStep);
var errorMessages = [];
for (var stepIndex in allValidationPassed.allErrors) {
var stepError = allValidationPassed.allErrors[stepIndex];
errorMessages.push(stepError.stepName + ': ' + stepError.errors.join('、'));
}
if (errorMessages.length > 0) {
Toastr.error('请完善以下内容:\n' + errorMessages.join('\n'));
}
}
return false;
}
}
let skus = '[]', spec = '[]';
var specType = $('input[name="row[spec_type]"]:checked').val();
if (specType == '1') {
if (typeof vm !== 'undefined' && vm && vm.tableData && vm.tableData.length > 0) {
try {
// 为每个SKU生成sku_attr字段
var processedSkus = vm.tableData.map(function(sku, index) {
var skuData = Object.assign({}, sku);
// 生成sku_attr字段
if (sku.skus && Array.isArray(sku.skus) && vm.specList) {
var skuAttrs = [];
sku.skus.forEach(function(specValue, specIndex) {
if (vm.specList[specIndex] && specValue) {
skuAttrs.push(vm.specList[specIndex].name + ':' + specValue);
}
});
skuData.sku_attr = skuAttrs.join(',');
} else {
skuData.sku_attr = '';
}
return skuData;
});
skus = JSON.stringify(processedSkus);
spec = JSON.stringify(vm.specList || []);
} catch (e) {
console.error('多规格数据处理失败:', e);
Toastr.error('多规格数据处理失败');
return false;
}
} else {
Toastr.error('请设置完整的多规格数据');
return false;
}
} else {
var singleSku = {
sku_sn: $('#c-sku-sn').val() || $('#c-goods_sn').val() || '',
price: parseFloat($('#c-price').val()) || 0,
lineation_price: parseFloat($('#c-lineation_price').val()) || 0,
cost_price: parseFloat($('#c-cost_price').val()) || 0,
stocks: parseInt($('#c-stocks').val()) || 0,
weight: parseFloat($('#c-weight').val()) || 0,
volume: parseFloat($('#c-volume').val()) || 0,
image: $('#c-single-image').val() || '',
sales: 0,
status: 1,
is_default: 1,
sku_attr: '', // 单规格没有sku_attr
skus: []
};
skus = JSON.stringify([singleSku]);
spec = JSON.stringify([]);
}
let html = `
`;
this.find('#goods-sku').html(html);
Form.api.submit(this, success, error);
return false;
});
require(['backend/shop/card'], function (Card) {
Card.api.bindcardevent();
});
},
bindUpload: function () {
if ($('.goods-sku-table table td.td-img button.faupload:not([initialized])').length === 0) {
return;
}
clearTimeout(si);
si = setTimeout(function () {
let doms = $('.goods-sku-table table td.td-img').toArray();
function uploadButtonTask(deadline) {
while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && doms.length > 0) {
bindEvent();
}
if (doms.length > 0)
requestIdleCallback(uploadButtonTask);
}
function bindEvent() {
var dom = doms.shift();
if (dom) {
var $elements = $(".faselect,.fachoose", dom);
if ($elements && $elements.length > 0) {
$elements.off('click');
}
Form.events.plupload(dom);
Form.events.faselect(dom);
}
}
requestIdleCallback(uploadButtonTask, {timeout: 1000});
}, 250);
},
add_sku: function () {
$('.td-img').find('.faupload').removeAttr('initialized');
require(['vue'], function (Vue) {
vm = new Vue({
el: '#vue-app',
computed: {
specValueText() {
return (skus, k) => {
return !skus || typeof skus[k] == 'undefined' ? '' : skus[k];
}
},
contentHtml() {
return field => {
return `
确定
`;
}
}
},
watch: {
specList: {
handler: function (val) {
this.renderTableData(val);
var that = this;
that.$nextTick(function() {
Controller.api.bindSpecValueUpload();
Controller.api.rebindMultipleEdit();
setTimeout(function() {
Controller.api.bindUploadButtons();
that.updateSpecImagePreviews();
}, 300);
});
},
deep: true
}
},
data() {
return {
spec_name: '',
specList: [],
tableData: [],
result: [],
skus: [],
defaultSpecIndex: 0
}
},
mounted() {
let that = this;
that.init();
setTimeout(function() {
Controller.api.initSpecTypeControl();
Controller.api.bindSpecValueUpload();
if (Config.goods && Config.goods.spec_type == '1') {
var $multiSpecForm = $('#multi-spec-form');
if ($multiSpecForm.length > 0) {
$multiSpecForm.show();
}
}
}, 100);
this.$nextTick(function () {
$('body').on('click', function (e) {
if (!$(e.target).hasClass('multiple-edit') &&
$(e.target).parents('.multiple-edit').length === 0 &&
$(e.target).parents('.popover.in').length === 0) {
$('.multiple-edit').popover('hide');
}
});
$(".multiple-edit").popover({
sanitize: false,
container: "body",
html: true,
placement: "top",
trigger: 'manual'
}).on('click', function (e) {
$(".popover").hide();
$(this).popover('show');
});
$(document).on('click', '.sku-confirm', function () {
let value = $(this).parent().prev().val().trim();
let field = $(this).data('field');
if (field != 'sku_sn' && Number.isNaN(parseFloat(value))) {
Toastr.error('请输入数字');
return;
}
for (let [index] of that.tableData.entries()) {
that.$set(that.tableData[index], field, value);
}
});
require(['selectpage'], function () {
$('.selectpage', $('.spec-template')).selectPage({
eAjaxSuccess: function (data) {
data.totalRow = data.total;
return data;
},
eSelect: function (row) {
let spec_names = row.spec_names.split(';');
let spec_values = row.spec_values.split(';');
let list = [];
for (let [i, v] of spec_names.entries()) {
let valueList = spec_values[i].split(',').map(val => ({
name: val,
image: '',
description: ''
}));
list.push({
name: v,
type: 'basic',
value: valueList
});
}
that.tableData = [];
that.skus = [];
setTimeout(function () {
that.specList = list;
}, 100);
}
});
});
})
},
methods: {
init() {
let skus = [];
if (Config.goods_skus && Config.goods_skus.length) {
if (Config.goods && Config.goods.spec_type == '1') {
let specList = [];
if (Config.spec_data && Config.spec_data.length > 0) {
specList = Config.spec_data.map(spec => {
return {
name: spec.name,
type: spec.type || 'basic',
value: spec.value || []
};
});
for (let item of Config.goods_skus) {
if (item.sku_attr) {
let attr = [];
let skuAttrs = [];
// 使用统一的sku_attr解析方法
skuAttrs = Controller.parseSkuAttr(item.sku_attr);
for (let attrObj of skuAttrs) {
let specName_key = attrObj.key || attrObj.name;
let specValue = attrObj.value;
if (specName_key && specValue) {
attr.push(specValue);
}
}
let attrKey = attr.join(',');
skus[attrKey] = item;
}
}
} else {
let specName = {};
let specTypeMap = {};
for (let item of Config.goods_skus) {
if (item.sku_attr) {
let attr = [];
let skuAttrs = [];
// 使用统一的sku_attr解析方法
skuAttrs = Controller.parseSkuAttr(item.sku_attr);
for (let attrObj of skuAttrs) {
let specName_key = attrObj.key || attrObj.name;
let specValue = attrObj.value;
let specType = attrObj.type || 'basic';
if (specName_key && specValue) {
attr.push(specValue);
if (!specName[specName_key]) {
specName[specName_key] = [];
}
if (!specName[specName_key].includes(specValue)) {
specName[specName_key].push(specValue);
}
specTypeMap[specName_key] = specType;
}
}
let attrKey = attr.join(',');
skus[attrKey] = item;
}
}
let specValueMap = {};
if (Config.spec_values && Config.spec_values.length > 0) {
Config.spec_values.forEach(function(item) {
if (!specValueMap[item.spec_name]) {
specValueMap[item.spec_name] = {};
}
specValueMap[item.spec_name][item.value] = {
name: item.value,
image: item.image || '',
description: item.description || ''
};
});
}
for (let i in specName) {
let valueList = specName[i].map(val => {
if (typeof val === 'string') {
if (specValueMap[i] && specValueMap[i][val]) {
return {
name: specValueMap[i][val].name,
image: specValueMap[i][val].image || '',
description: specValueMap[i][val].description || ''
};
} else {
return {
name: val,
image: '',
description: ''
};
}
}
return {
name: val.name || val,
image: val.image || '',
description: val.description || ''
};
});
specList.push({
name: i,
type: specTypeMap[i] || 'basic',
value: valueList
});
}
}
this.skus = skus;
this.specList = specList;
this.$nextTick(() => {
if (this.specList.length > 0) {
this.renderTableData(this.specList);
setTimeout(() => {
this.updateSpecImagePreviews();
}, 500);
}
});
}
}
},
addSpec() {
if (!this.spec_name.trim()) {
Toastr.error('请输入规格名称');
return;
}
if (this.specList.some(item => item.name == this.spec_name)) {
Toastr.error('已存在规格名称');
return;
}
this.specList.push({
name: this.spec_name,
type: 'basic',
value: [{
name: '',
image: '',
description: ''
}]
});
this.spec_name = '';
},
showAddSpecForm() {
this.specList.push({
name: '',
type: 'basic',
value: [{
name: '',
image: '',
description: ''
}]
});
},
addSpecValue(key) {
this.specList[key].value.push({
name: '',
image: '',
description: ''
});
this.$nextTick(function() {
setTimeout(function() {
Controller.api.bindUploadButtons();
}, 100);
});
},
removeSpecValue(key, index) {
this.specList[key].value.splice(index, 1);
},
renderTableData(list) {
const isEditMode = Config.goods && Config.goods.id;
const defaultValues = {
goods_sn: $('#c-goods_sn').val() || '',
price: '0.01',
lineation_price: '0.00',
cost_price: '0.00',
weight: '0.00',
volume: '0.00',
stocks: '1'
};
let columns = [];
this.result = [];
this.resetSpec(list, 0);
this.result.forEach((item, index) => {
let su = this.skus[item];
let row;
if (isEditMode && su) {
row = {
skus: item ? item.split(',') : [],
sku_sn: su.sku_sn || '',
image: su.image || '',
price: parseFloat(su.price) || 0,
lineation_price: parseFloat(su.lineation_price) || 0,
cost_price: parseFloat(su.cost_price) || 0,
weight: parseFloat(su.weight) || 0,
volume: parseFloat(su.volume) || 0,
stocks: parseInt(su.stocks) || 0,
sales: parseInt(su.sales) || 0,
status: su.status !== undefined ? parseInt(su.status) : 1,
is_default: su.is_default !== undefined ? parseInt(su.is_default) : 0
};
} else {
row = {
skus: item ? item.split(',') : [],
sku_sn: defaultValues.goods_sn,
image: '',
price: defaultValues.price,
lineation_price: defaultValues.lineation_price,
cost_price: defaultValues.cost_price,
weight: defaultValues.weight,
volume: defaultValues.volume,
stocks: defaultValues.stocks,
sales: 0,
status: 1,
is_default: index === 0 ? 1 : 0
};
}
if (!isEditMode) {
let old = this.tableData[index];
if (old) {
for (let i in row) {
if ((row[i] === '' || row[i] === 0 || row[i] === '0.00') && old[i]) {
row[i] = old[i];
}
}
}
}
columns.push(row);
});
this.tableData = columns;
if (isEditMode) {
this.defaultSpecIndex = this.tableData.findIndex(item => item.is_default === 1);
}
this.$nextTick(function () {
Controller.api.bindUpload();
Controller.api.bindSpecValueUpload();
Controller.api.rebindMultipleEdit();
});
},
removeSpec(key) {
this.specList.splice(key, 1);
},
updateSpecImagePreviews() {
this.specList.forEach((spec, specKey) => {
if (spec.value && spec.value.length > 0) {
spec.value.forEach((value, valueIndex) => {
if (value.image) {
const previewId = 'p-spec-image-' + specKey + '-' + valueIndex;
const $preview = $('#' + previewId);
if ($preview.length > 0) {
const img = '';
$preview.html(img);
}
}
});
}
});
},
resetSpec(list, index) {
if (list[index] != undefined) {
let value = list[index].value.map(item => typeof item === 'string' ? item : item.name);
if (!index) {
this.result = value;
} else {
let res = [];
for (let i of this.result) {
for (let j of value) {
res.push(i + ',' + j);
}
}
if (res.length) {
this.result = res;
}
}
this.resetSpec(list, ++index);
}
},
}
});
});
$(document).on('click', '.btn-del-sku', function () {
vm.specList = [];
vm.tableData = [];
});
$(document).on('change', '.sku-images', function () {
let index = $(this).data('index');
let value = $(this).val();
vm.tableData[index].image = value;
});
},
rebindMultipleEdit: function() {
var $multipleEdit = $(".multiple-edit");
if (!$multipleEdit || $multipleEdit.length === 0) {
return;
}
$multipleEdit.popover('destroy');
$multipleEdit.popover({
sanitize: false,
container: "body",
html: true,
placement: "top",
trigger: 'manual'
});
if ($multipleEdit && $multipleEdit.length > 0) {
$multipleEdit.off('click.multiple').on('click.multiple', function (e) {
$(".popover").hide();
$(this).popover('show');
});
}
},
bindSpecValueUpload: function() {
if ($('.spec-image-input').length === 0) {
return;
}
$(document).off('change.specvalue', '.spec-image-input');
$(document).on('change.specvalue', '.spec-image-input', function () {
var specKey = parseInt($(this).data('spec-key'));
var valueIndex = parseInt($(this).data('value-index'));
var imageUrl = $(this).val();
if (vm && vm.specList && vm.specList[specKey] && vm.specList[specKey].value[valueIndex]) {
vm.$set(vm.specList[specKey].value[valueIndex], 'image', imageUrl);
}
});
setTimeout(function() {
Controller.api.bindUploadButtons();
}, 200);
},
bindUploadButtons: function() {
if ($('.spec-upload-btn').length === 0) {
return;
}
setTimeout(function() {
require(['upload'], function (Upload) {
$('.spec-upload-btn:not([initialized])').each(function() {
var $btn = $(this);
var specKey = $btn.attr('data-spec-key');
var valueIndex = $btn.attr('data-value-index');
if (specKey !== undefined && valueIndex !== undefined) {
$btn.attr('initialized', true);
if ($btn && $btn.length > 0) {
$btn.off('click.specUpload').on('click.specUpload', function() {
var inputId = 'c-spec-image-' + specKey + '-' + valueIndex;
var previewId = 'p-spec-image-' + specKey + '-' + valueIndex;
var tempBtn = $('');
tempBtn.attr('data-input-id', inputId);
tempBtn.attr('data-preview-id', previewId);
tempBtn.attr('data-mimetype', $btn.attr('data-mimetype'));
tempBtn.attr('data-multiple', $btn.attr('data-multiple'));
$('body').append(tempBtn);
Upload.api.upload(tempBtn, function(data, ret) {
var url = data.url || '';
if (vm && vm.specList && vm.specList[specKey] && vm.specList[specKey].value[valueIndex]) {
vm.$set(vm.specList[specKey].value[valueIndex], 'image', url);
}
$('#' + inputId).val(url);
var $preview = $('#' + previewId);
if (url) {
var img = '';
$preview.html(img);
} else {
$preview.empty();
}
tempBtn.remove();
}, function(data, ret) {
Toastr.error(ret.msg || '上传失败');
tempBtn.remove();
});
tempBtn.click();
});
}
}
});
$('.spec-choose-btn:not([initialized])').each(function() {
var $btn = $(this);
if (!$btn || $btn.length === 0) {
return;
}
var specKey = $btn.attr('data-spec-key');
var valueIndex = $btn.attr('data-value-index');
if (specKey !== undefined && valueIndex !== undefined) {
$btn.attr('initialized', true);
if ($btn && $btn.length > 0) {
$btn.off('click.specChoose').on('click.specChoose', function() {
var inputId = 'c-spec-image-' + specKey + '-' + valueIndex;
var previewId = 'p-spec-image-' + specKey + '-' + valueIndex;
parent.Fast.api.open("general/attachment/select?element_id=" + inputId + "&multiple=false&mimetype=image/*", __('Choose'), {
callback: function(data) {
var url = data.url || '';
if (vm && vm.specList && vm.specList[specKey] && vm.specList[specKey].value[valueIndex]) {
vm.$set(vm.specList[specKey].value[valueIndex], 'image', url);
}
$('#' + inputId).val(url);
var $preview = $('#' + previewId);
if (url) {
var img = '';
$preview.html(img);
} else {
$preview.empty();
}
}
});
});
}
}
});
});
}, 100);
},
content: function (value, row, index) {
var width = this.width != undefined ? (this.width.match(/^\d+$/) ? this.width + "px" : this.width) : "350px";
return "