define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { Fast.config.openArea = ['80%', '80%']; var vm, si; // 选项卡验证器模块 var TabValidator = { steps: [ { id: '#basics', name: '基础信息', required: ['row[type]', 'row[goods_sn]', 'row[title]', 'row[category_ids]', 'row[image]', 'row[images]'], conditionalRequired: { 'row[supplier_id]': function() { return true; }, 'row[inspection_type_id]': function() { return true; } } }, { id: '#skus', name: '价格库存', required: [], customValidation: 'validatePriceStock' }, { id: '#delivery', name: '配送设置', required: ['row[delivery_type]'], conditionalRequired: { 'row[express_freight]': function() { return $('input[name="row[delivery_type]"]:checked').val() === 'EXPRESS' && $('input[name="row[express_type]"]:checked').val() === '2'; }, 'row[express_template_id]': function() { return $('input[name="row[delivery_type]"]:checked').val() === 'EXPRESS' && $('input[name="row[express_type]"]:checked').val() === '3'; } } }, { id: '#detail', name: '商品详情', required: [], optional: true }, { id: '#params', name: '商品参数', required: [], optional: true }, { id: '#sales', name: '销售设置', required: ['row[stock_show_type]', 'row[sales_show_type]'] } ], currentStep: 0, completedSteps: [], stepValidationResults: {}, init: function() { this.addValidationStyles(); this.bindNiceValidatorEvents(); this.bindTabEvents(); this.initStepNavigation(); }, bindNiceValidatorEvents: function() { var self = this; // 监听 Nice-validator 的验证事件 $(document).on('valid.form', 'form[role="form"]', function(e, obj) { // 表单验证通过时清除所有选项卡错误状态 self.clearTabErrors(); }); $(document).on('invalid.form', 'form[role="form"]', function(e, obj) { // 表单验证失败时处理错误 if (obj && obj.errors && obj.errors.length > 0) { self.handleNiceValidatorErrors(obj.errors); } else if (obj && obj.element) { // 单个字段错误 var fieldName = obj.element.name || obj.element.id; var errorMessage = obj.msg || obj.message || '字段验证失败'; self.handleFieldError(fieldName, errorMessage); } }); $(document).on('invalid.field', 'form[role="form"]', function(e, obj) { // 单个字段验证失败时处理 if (obj && obj.element) { var fieldName = obj.element.name || obj.element.id; var errorMessage = obj.msg || obj.message || '字段验证失败'; self.handleFieldError(fieldName, errorMessage); } }); // 监听FastAdmin的验证器事件 - 兼容性处理 $(document).on('fa.event.validator', function(e, obj) { if (obj && obj.errors && obj.errors.length > 0) { self.handleNiceValidatorErrors(obj.errors); } }); }, addValidationStyles: function() { if ($('#goods-validation-styles').length > 0) return; var styles = ` `; $('head').append(styles); }, bindTabEvents: function() { var self = this; $('.nav-tabs a[data-toggle="tab"]').on('shown.bs.tab', function(e) { self.clearTabErrors(); var target = $(e.target).attr('href'); for (var i = 0; i < self.steps.length; i++) { if (self.steps[i].id === target) { self.currentStep = i; self.updateButtons(); break; } } }); $(document).on('invalid.field invalid.form', 'form[data-toggle="validator"]', function(e, data) { if (data && data.element) { var fieldName = data.element.name || data.element.id; var errorMessage = data.message || '字段验证失败'; self.handleValidationError(errorMessage, fieldName); } }); }, handleNiceValidatorErrors: function(errors) { var self = this; var errorsByTab = {}; // 按选项卡分组错误 for (var i = 0; i < errors.length; i++) { var error = errors[i]; var fieldName = error.element ? error.element.name : ''; var tabId = self.getTabByFieldName(fieldName); if (tabId && !errorsByTab[tabId]) { errorsByTab[tabId] = []; } if (tabId) { errorsByTab[tabId].push({ field: fieldName, message: error.msg || '验证失败', element: error.element }); } } // 为每个有错误的选项卡显示错误状态 for (var tabId in errorsByTab) { var tabErrors = errorsByTab[tabId]; var errorMessages = tabErrors.map(function(err) { return err.message; }); this.showTabError(tabId, errorMessages.join('、')); } // 跳转到第一个有错误的选项卡 var firstErrorTab = Object.keys(errorsByTab)[0]; if (firstErrorTab) { var stepIndex = this.getStepIndexByTabId(firstErrorTab); if (stepIndex !== -1) { this.goToStep(stepIndex); } } }, handleFieldError: function(fieldName, errorMessage) { var tabId = this.getTabByFieldName(fieldName); if (tabId) { this.showTabError(tabId, errorMessage); var stepIndex = this.getStepIndexByTabId(tabId); if (stepIndex !== -1) { this.goToStep(stepIndex); } } }, getStepIndexByTabId: function(tabId) { for (var i = 0; i < this.steps.length; i++) { if (this.steps[i].id === tabId) { return i; } } return -1; }, initStepNavigation: function() { var self = this; this.updateButtons(); $('.btn-prev').off('click.step').on('click.step', function() { if (self.currentStep > 0) { self.goToStep(self.currentStep - 1); } }); $('.btn-next').off('click.step').on('click.step', function() { // 直接跳转到下一步,无需验证 self.clearTabError(self.steps[self.currentStep].id); if (self.completedSteps.indexOf(self.currentStep) === -1) { self.completedSteps.push(self.currentStep); } if (self.currentStep < self.steps.length - 1) { self.goToStep(self.currentStep + 1); } }); $('.nav-tabs a[data-toggle="tab"]').off('click.step').on('click.step', function(e) { e.preventDefault(); var targetTabId = $(this).attr('href'); var targetStepIndex = self.steps.findIndex(function(step) { return step.id === targetTabId; }); if (targetStepIndex !== -1) { // 允许自由切换选项卡,无需验证前置步骤 self.goToStep(targetStepIndex); } }); }, goToStep: function(stepIndex) { if (stepIndex < 0 || stepIndex >= this.steps.length) return; $('.tab-pane').removeClass('active in'); $(this.steps[stepIndex].id).addClass('active in'); $('.nav-tabs li').removeClass('active'); $('.nav-tabs a[href="' + this.steps[stepIndex].id + '"]').parent().addClass('active'); this.currentStep = stepIndex; this.updateButtons(); $('html, body').animate({scrollTop: 0}, 300); }, updateButtons: function() { var $prevBtn = $('.btn-prev'); var $nextBtn = $('.btn-next'); var $submitBtn = $('.btn-submit'); if (this.currentStep === 0) { $prevBtn.hide(); } else { $prevBtn.show(); } if (this.currentStep === this.steps.length - 1) { $nextBtn.hide(); $submitBtn.show(); } else { $nextBtn.show(); $submitBtn.hide(); } $('.step-current').text(this.currentStep + 1); $('.step-total').text(this.steps.length); $('.step-text').text(this.steps[this.currentStep].name); var progress = ((this.currentStep + 1) / this.steps.length) * 100; $('.step-progress-bar').css('width', progress + '%'); }, validateCurrentStep: function() { var step = this.steps[this.currentStep]; var isValid = true; var errors = []; var errorFields = []; step.required.forEach(function(fieldName) { var fieldResult = Controller.validateField(fieldName); if (!fieldResult.isValid) { isValid = false; errors.push(fieldResult.label); errorFields.push(fieldResult.element); } }); if (step.conditionalRequired) { for (var fieldName in step.conditionalRequired) { var condition = step.conditionalRequired[fieldName]; if (condition && typeof condition === 'function' && condition()) { var fieldResult = Controller.validateField(fieldName); if (!fieldResult.isValid) { isValid = false; errors.push(fieldResult.label); errorFields.push(fieldResult.element); } } } } if (step.customValidation && Controller[step.customValidation]) { var customResult = Controller[step.customValidation](); if (!customResult.isValid) { isValid = false; errors = errors.concat(customResult.errors); if (customResult.errorFields) { errorFields = errorFields.concat(customResult.errorFields); } } } this.stepValidationResults[this.currentStep] = { isValid: isValid, errors: errors, errorFields: errorFields }; if (!isValid) { var message = errors.length > 0 ? '请完善以下必填项:\n' + errors.join('、') : '当前步骤存在验证错误,请检查后重试'; Toastr.error(message); this.highlightErrorFields(step.id, errorFields); this.showTabError(step.id, message); } return isValid; }, validateAllSteps: function() { var result = { isValid: true, firstErrorStep: -1, errorSteps: [], allErrors: {} }; for (var i = 0; i < this.steps.length; i++) { var step = this.steps[i]; var stepValid = true; var stepErrors = []; step.required.forEach(function(fieldName) { var fieldResult = Controller.validateField(fieldName); if (!fieldResult.isValid) { stepValid = false; stepErrors.push(fieldResult.label); } }); if (step.conditionalRequired) { for (var fieldName in step.conditionalRequired) { var condition = step.conditionalRequired[fieldName]; if (condition && typeof condition === 'function' && condition()) { var fieldResult = Controller.validateField(fieldName); if (!fieldResult.isValid) { stepValid = false; stepErrors.push(fieldResult.label); } } } } if (step.customValidation && Controller[step.customValidation]) { var customResult = Controller[step.customValidation](); if (!customResult.isValid) { stepValid = false; stepErrors = stepErrors.concat(customResult.errors); } } if (!stepValid) { result.isValid = false; if (result.firstErrorStep === -1) { result.firstErrorStep = i; } result.errorSteps.push(i); result.allErrors[i] = { stepName: step.name, errors: stepErrors }; this.showTabError(step.id, stepErrors.join('、')); } } return result; }, getTabByFieldName: function(fieldName) { var fieldTabMap = { // 基础信息tab 'type': '#basics', 'goods_sn': '#basics', 'title': '#basics', 'sub_title': '#basics', 'subtitle': '#basics', 'category_id': '#basics', 'category_ids': '#basics', 'brand_id': '#basics', 'image': '#basics', 'images': '#basics', 'label_ids': '#basics', 'guarantee_ids': '#basics', 'unit_id': '#basics', 'supplier_id': '#basics', 'inspection_type_id': '#basics', 'weigh': '#basics', 'online_type': '#basics', 'scheduled_online_time': '#basics', 'is_auto_offline': '#basics', 'scheduled_offline_time': '#basics', // 价格库存tab 'spec_type': '#skus', 'price': '#skus', 'lineation_price': '#skus', 'cost_price': '#skus', 'stocks': '#skus', 'weight': '#skus', 'volume': '#skus', 'sku_sn': '#skus', 'skus': '#skus', 'spec': '#skus', // 配送设置tab 'delivery_type': '#delivery', 'express_type': '#delivery', 'express_freight': '#delivery', 'express_template_id': '#delivery', // 商品详情tab 'content': '#detail', // 商品参数tab 'params': '#params', 'attribute_ids': '#params', // 销售设置tab 'stock_show_type': '#sales', 'sales_show_type': '#sales', 'virtual_sales': '#sales', 'keywords': '#sales', 'description': '#sales', 'is_hot': '#sales' }; // 清理字段名,支持多种格式 var cleanFieldName = fieldName; if (fieldName.indexOf('row[') === 0) { cleanFieldName = fieldName.replace(/^row\[/, '').replace(/\]$/, ''); } else if (fieldName.indexOf('c-') === 0) { cleanFieldName = fieldName.replace(/^c-/, ''); } // 处理特殊情况 if (cleanFieldName.indexOf('single-') === 0) { cleanFieldName = cleanFieldName.replace(/^single-/, ''); } return fieldTabMap[cleanFieldName] || fieldTabMap[fieldName] || null; }, showTabError: function(tabSelector, message) { var $tabLink = $('.nav-tabs a[href="' + tabSelector + '"]'); if ($tabLink.length > 0) { $tabLink.addClass('tab-error'); if ($tabLink.find('.error-icon').length === 0) { $tabLink.append(' '); } if (message) { $tabLink.attr('title', message).tooltip('destroy').tooltip(); } } }, clearTabError: function(tabSelector) { var $tabLink = $('.nav-tabs a[href="' + tabSelector + '"]'); if ($tabLink.length > 0) { $tabLink.removeClass('tab-error'); $tabLink.find('.error-icon').remove(); $tabLink.removeAttr('title').tooltip('destroy'); } }, clearTabErrors: function() { $('.nav-tabs a').removeClass('tab-error'); $('.nav-tabs .error-icon').remove(); $('.nav-tabs a').removeAttr('title').tooltip('destroy'); }, highlightErrorFields: function(tabId, errorFields) { if (!tabId) return; var $tabPane = $(tabId); if ($tabPane.length === 0) return; var $fieldsToHighlight = $(); if (errorFields && errorFields.length > 0) { errorFields.forEach(function(element) { if (element) { $fieldsToHighlight = $fieldsToHighlight.add($(element)); } }); } else { // 使用FastAdmin默认的错误样式查找字段 $fieldsToHighlight = $tabPane.find('.has-error input, .has-error select, .has-error textarea') .add($tabPane.find('.form-group.has-error input, .form-group.has-error select, .form-group.has-error textarea')) .add($tabPane.find('.is-invalid')) .add($tabPane.find('[aria-invalid="true"]')); if ($fieldsToHighlight.length === 0) { $fieldsToHighlight = $tabPane.find('.msg-box:visible').siblings('input, select, textarea'); } } // 自动聚焦到第一个错误字段,不添加自定义样式 if ($fieldsToHighlight.length > 0) { var $firstError = $fieldsToHighlight.first(); setTimeout(function() { $firstError.focus(); $('html, body').animate({ scrollTop: $firstError.offset().top - 100 }, 300); }, 100); } }, handleValidationError: function(errorMessage, fieldName) { var targetTab = this.getTabByFieldName(fieldName); if (targetTab) { var stepIndex = this.steps.findIndex(function(step) { return step.id === targetTab; }); if (stepIndex !== -1) { this.goToStep(stepIndex); setTimeout(function() { TabValidator.highlightErrorFields(targetTab); }, 300); } } } }; var Controller = { index: function () { Table.api.init({ extend: { index_url: 'shop/goods/index' + location.search, add_url: 'shop/goods/add', edit_url: 'shop/goods/edit?dialog=1', del_url: 'shop/goods/del', multi_url: 'shop/goods/multi', import_url: 'shop/goods/import', table: 'shop_goods', } }); Table.config.dragsortfield = ''; var table = $("#table"); var goodsTypeList = Controller.api.parseConfigJson('goodsTypeList', {}); var specTypeList = Controller.api.parseConfigJson('specTypeList', {}); var statusList = Controller.api.parseConfigJson('statusList', {}); table.bootstrapTable({ url: $.fn.bootstrapTable.defaults.extend.index_url, pk: 'id', sortName: 'id', fixedColumns: true, fixedRightNumber: 1, columns: [ [ {checkbox: true}, {field: 'id', title: 'ID', width: 60, sortable: true}, {field: 'image', title: '图片', width: 100, operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image}, { field: 'title', title: '商品信息', width: 300, operate: 'LIKE', formatter: Controller.formatGoodsInfo }, { field: 'type', title: '商品类型', width: 100, searchList: goodsTypeList, formatter: Controller.formatGoodsType }, { field: 'spec_type', title: '规格', width: 80, searchList: specTypeList, formatter: Controller.formatSpecType }, {field: 'price', title: '价格', width: 80, operate: 'BETWEEN', sortable: true}, {field: 'stocks', title: '库存', width: 80, operate: 'BETWEEN', sortable: true}, {field: 'sales', title: '销量', width: 80, operate: 'BETWEEN', sortable: true}, {field: 'views', title: '浏览量', width: 80, operate: 'BETWEEN', sortable: true}, { field: 'status', title: '状态', width: 80, searchList: statusList, formatter: Controller.formatGoodsStatus }, {field: 'createtime', title: '创建时间', width: 120, operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, visible: false}, {field: 'updatetime', title: '更新时间', width: 120, operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, visible: false}, {field: 'brand_id', title: '品牌', visible: false, searchList: $.getJSON("shop/brand/getList")}, {field: 'goods_sn', title: '商品编码', visible: false, operate: 'LIKE'}, {field: 'title', title: '商品标题', visible: false, operate: 'LIKE'}, { field: 'operate', title: '操作', width: 120, table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate, buttons: [ { name: 'edit', title: '编辑', classname: 'btn btn-xs btn-success btn-dialog', icon: 'fa fa-pencil', url: 'shop/goods/edit' } ] } ] ] }); Table.api.bindevent(table); }, formatGoodsInfo: function(value, row, index) { var html = '
'; html += '
' + (value || '') + '
'; html += '
'; 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 "
    " + value + "
    "; }, } }; return Controller; });