├── .gitattributes ├── .gitignore ├── README.md ├── composer.json ├── logo.png ├── resources ├── assets │ ├── css │ │ └── index.css │ └── js │ │ └── index.js └── views │ └── index.blade.php ├── screenshots ├── attributes.png ├── form.png └── setting.png ├── src ├── DcatSkuPlusServiceProvider.php ├── Http │ ├── Controllers │ │ ├── SkuAttributeController.php │ │ └── UploadController.php │ ├── Repositories │ │ └── SkuAttribute.php │ └── routes.php ├── Models │ └── SkuAttribute.php ├── Setting.php └── SkuField.php ├── updates └── create_sku_attribute_table.php └── version.php /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=php -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | phpunit.phar 3 | /vendor 4 | composer.phar 5 | composer.lock 6 | *.project 7 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dcat Admin SKU扩展增强版 2 | 3 | > 首先感谢前人的肩膀:https://github.com/lty5240/dcat-easy-sku 4 | > 5 | > 以及前人的前人的肩膀:https://github.com/jade-kun/sku 6 | > 7 | > 因改动太大,故没有往前面两位大佬的仓库中PR。 8 | 9 | ### **如果感觉好用,还请给个`Star`鼓励一下,谢谢 :beers: 。** 10 | 11 | 该插件是 [Dcat Admin](https://learnku.com/docs/dcat-admin/) 的插件,安装方式遵循Dcat Admin官方文档。 12 | 13 | 默认规格带有`图片`、`库存`、`价格`三个属性,可自行添加属性,自行处理。 14 | 15 | ## 改进的地方 16 | 17 | - 增加了独立设置相关属性的模块; 18 | - 增加了多图上传功能; 19 | - 增加删除图片时操作确认; 20 | - 增加删除图片时同时删除后端图片; 21 | - 增加图片上传、删除的URL配置项,不用再去修改js文件中的相关变量; 22 | 23 | ## 界面展示 24 | 25 | * 可以独立设置规格属性,默认`输入框`,支持`单选框`和`复选框`。 26 | 27 |
28 | 29 | ![image](screenshots/attributes.png) 30 | 31 |
32 | 33 | * 在表单界面规格名位置选择下拉框中的相关属性或者手动输入,生成SKU相关信息。 34 |
35 | 36 | ![image](screenshots/form.png) 37 | 38 |
39 | 40 | * 插件配置页面可以配置图片上传、删除的URL。 41 |
42 | 43 | ![image](screenshots/setting.png) 44 | 45 |
46 | 47 | ## 安装 48 | 49 | #### composer安装 50 | 51 | ```shell 52 | composer require abbotton/dcat-sku-plus 53 | ``` 54 | 55 | #### 应用商店安装 56 | 57 | ``` 58 | 等待Dcat Admin 上商店 59 | ``` 60 | 61 | ## 使用 62 | 63 | ```php 64 | // app/Admin/Controllers/ProductController.php 65 | 66 | protected function form() 67 | { 68 | return Form::make(new Product(), function (Form $form) { 69 | $skuParams = [ 70 | // 扩展第一列 71 | [ 72 | 'name' => '会员价', // table 第一行 title 73 | 'field' => 'member_price', // input 的 field_name 名称 74 | 'default' => 5, // 默认值 75 | ], 76 | // 扩展第二列 77 | ]; 78 | // 新增时 79 | $form->sku('sku', '生成SKU')->addColumn($skuParams); 80 | 81 | // 编辑时 82 | $skuData = []; // 数据结构见附录 83 | $skuString = json_encode($skuData); 84 | $form->sku('sku', '生成SKU')->addColumn($skuParams)->value($skuString); 85 | 86 | // 获取提交的数据. 87 | $form->saving(function (Form $form) { 88 | // 拿到SKU数据,按照业务逻辑做响应处理即可。 89 | dd($form->input('sku')); 90 | }); 91 | }); 92 | } 93 | ``` 94 | 95 | ## 附录 96 | 97 | #### 最终生成的SKU数据结构,仅供参考 98 | 99 | ```json 100 | { 101 | "attrs": { 102 | "测试": [ 103 | "测试1", 104 | "测试2" 105 | ], 106 | "颜色": [ 107 | "绿", 108 | "黄" 109 | ], 110 | "含电池": [ 111 | "含电池" 112 | ], 113 | "内存": [ 114 | "8G" 115 | ] 116 | }, 117 | "sku": [ 118 | { 119 | "测试": "测试1", 120 | "颜色": "绿", 121 | "含电池": "含电池", 122 | "内存": "8G", 123 | "pic": [ 124 | { 125 | "short_url": "sku/HjdrG0RpfIwI3kkgpNNFfmxPasgaOLg6bBPOCDxd.jpg", 126 | "full_url": "http://127.0.0.1:8000/storage/sku/HjdrG0RpfIwI3kkgpNNFfmxPasgaOLg6bBPOCDxd.jpg" 127 | }, 128 | { 129 | "short_url": "sku/bkucABLjzRQ5pEHYX0gwdS1VxJrS6ObiQCIWVvIl.png", 130 | "full_url": "http://127.0.0.1:8000/storage/sku/bkucABLjzRQ5pEHYX0gwdS1VxJrS6ObiQCIWVvIl.png" 131 | } 132 | ], 133 | "stock": "", 134 | "price": "", 135 | "member_price": 5 136 | } 137 | ] 138 | } 139 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "abbotton/dcat-sku-plus", 3 | "description": "基于Dcat Admin的SKU扩展增强版", 4 | "alias": "Dcat Admin SKU扩展增强版", 5 | "type": "library", 6 | "keywords": ["dcat-admin", "extension"], 7 | "homepage": "https://github.com/abbotton/dcat-sku-plus", 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Abbotton", 12 | "email": "uctoo@foxmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.1.0", 17 | "dcat/laravel-admin": "~2.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Dcat\\Admin\\Extension\\DcatSkuPlus\\": "src/" 22 | } 23 | }, 24 | "extra": { 25 | "dcat-admin": "Dcat\\Admin\\Extension\\DcatSkuPlus\\DcatSkuPlusServiceProvider", 26 | "laravel": { 27 | "providers": [ 28 | "Dcat\\Admin\\Extension\\DcatSkuPlus\\DcatSkuPlusServiceProvider" 29 | ] 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abbotton/dcat-sku-plus/b2b5dce2f8f47b984a7f2b0b0fe6e8b9fa153325/logo.png -------------------------------------------------------------------------------- /resources/assets/css/index.css: -------------------------------------------------------------------------------- 1 | .sku_wrap .sku_attr_key_val { 2 | margin-top: 10px; 3 | } 4 | 5 | .sku_wrap .sku_attr_val_wrap { 6 | clear: both; 7 | } 8 | 9 | .sku_wrap .sku_attr_val_wrap:after { 10 | content: ' '; 11 | display: block; 12 | clear: both; 13 | } 14 | 15 | .sku_wrap .sku_attr_val_item { 16 | display: inline-block; 17 | float: left; 18 | } 19 | 20 | .sku_wrap .sku_attr_val_item .sku_attr_val_input { 21 | margin-left: 10px; 22 | margin-bottom: 5px; 23 | width: 100px; 24 | float: left; 25 | } 26 | 27 | .sku_wrap .sku_attr_val_item .btn { 28 | float: left; 29 | border-radius: 0 !important; 30 | } 31 | 32 | .sku_wrap .sku_edit_wrap .Js_sku_upload { 33 | display: inline-block; 34 | height: 40px; 35 | width: 40px; 36 | text-align: center; 37 | box-sizing: border-box; 38 | cursor: pointer; 39 | user-select: none; 40 | background-size: 100%; 41 | background-repeat: no-repeat; 42 | padding-top: 13px; 43 | margin-top: 1px; 44 | } 45 | 46 | .sku_wrap .sku_edit_wrap .Js_sku_del_pic { 47 | display: inline-block; 48 | margin-left: 5px; 49 | text-decoration: underline; 50 | cursor: pointer; 51 | } 52 | 53 | .sku_wrap .sku_edit_wrap tr th, .sku_wrap .sku_edit_wrap .attr-name { 54 | line-height: 40px; 55 | } 56 | 57 | .sku_wrap .sku_edit_wrap tr th, .sku_wrap .sku_edit_wrap tr td { 58 | text-align: center; 59 | padding-left: .65rem; 60 | } 61 | 62 | .sku_wrap .sku_edit_wrap tr td input { 63 | text-align: center; 64 | margin-top: 5px; 65 | } 66 | 67 | .sku_wrap .sku_edit_wrap tr td .icon-upload-cloud, .sku_wrap .sku_edit_wrap tr td .icon-x { 68 | font-size: 18px; 69 | line-height: 18px; 70 | } 71 | 72 | .sku_wrap .sku_edit_wrap tr th input { 73 | text-align: center; 74 | } 75 | 76 | .sku_wrap .input_attr_name { 77 | margin-top: 5px; 78 | } 79 | 80 | .sku_wrap .sku_img { 81 | display: flex; 82 | height: 100%; 83 | justify-content: center; 84 | width: 100%; 85 | flex-wrap: wrap; 86 | flex-direction: row; 87 | align-content: flex-start; 88 | align-items: flex-start; 89 | } 90 | 91 | .sku_wrap .sku_img div { 92 | position: relative; 93 | width: 40px; 94 | height: 40px; 95 | margin: 1px; 96 | border: 1px solid #d9d9d9; 97 | } 98 | 99 | .sku_wrap .sku_img div i { 100 | display: none; 101 | position: absolute; 102 | top: 50%; 103 | left: 50%; 104 | margin-left: -25%; 105 | margin-top: -25%; 106 | cursor: pointer; 107 | } 108 | 109 | .sku_wrap .sku_img img { 110 | width: 100%; 111 | } 112 | -------------------------------------------------------------------------------- /resources/assets/js/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function SKU(wrap) { 3 | this.wrap = $(wrap); 4 | this.attrs = {}; 5 | this.skuAttributes = JSON.parse($('.sku_attributes').val()); 6 | this.uploadUrl = $('.upload_url').val(); 7 | this.deleteUrl = $('.delete_url').val(); 8 | this.attrIndex = 0; 9 | this.currentAttributeValue = []; 10 | this.currentSkuId = ''; 11 | this.skuAttr = {}; 12 | this.init(); 13 | } 14 | 15 | SKU.prototype.init = function () { 16 | let _this = this; 17 | // 绑定属性值添加事件 18 | _this.wrap.find('.sku_attr_key_val').on('click', '.Js_add_attr_val', function () { 19 | let html = '
' + 20 | '
' + 21 | '' + 22 | '
' + 23 | '' + 24 | '
'; 25 | $(this).before(html); 26 | }); 27 | 28 | // 绑定属性值移除事件 29 | _this.wrap.find('.sku_attr_key_val').on('click', '.Js_remove_attr_val', function () { 30 | $(this).parent('.sku_attr_val_item').remove(); 31 | _this.getSkuAttr(); 32 | }); 33 | 34 | // 选择属性触发 35 | _this.wrap.find('.sku_attr_key_val').on('change', '.attribute_selector', function () { 36 | if ($(this).val() != 'input') { 37 | let skuAttributesArray = _this.skuAttributes; 38 | _this.attrIndex = $(this).find("option:selected").data('idx'); 39 | _this.skuAttr = skuAttributesArray[_this.attrIndex]; 40 | } 41 | _this.changeAttrValueHtml($(this), $(this).val()); 42 | }); 43 | 44 | // 绑定添加属性名事件 45 | _this.wrap.find('.Js_add_attr_name').click(function () { 46 | _this.wrap.find('.sku_attr_key_val tbody').append(_this.getHtml()) 47 | }); 48 | 49 | // 绑定移除属性名事件 50 | _this.wrap.find('.sku_attr_key_val').on('click', '.Js_remove_attr_name', function () { 51 | $(this).closest('tr').remove(); 52 | _this.getSkuAttr() 53 | }); 54 | 55 | // 绑定input变化事件 56 | _this.wrap.find('.sku_attr_key_val tbody').on('change', 'input', _this.getSkuAttr.bind(_this)); 57 | _this.wrap.find('.sku_edit_wrap tbody').on('keyup', 'input', _this.processSku.bind(_this)); 58 | 59 | // SKU图片上传 60 | _this.wrap.find('.sku_edit_wrap tbody').on('click', '.Js_sku_upload', function () { 61 | _this.upload($(this)); 62 | }); 63 | 64 | // 删除图片 65 | _this.wrap.find('.sku_edit_wrap tbody').on('click', '.sku_img .icon-x', function () { 66 | let that = $(this); 67 | Dcat.confirm('确认要删除图片吗?', null, function () { 68 | $.ajax({ 69 | type: "POST", 70 | url: _this.deleteUrl, 71 | data: {path: that.data('path')}, 72 | headers: { 73 | Accept: "application/json" 74 | }, 75 | success: function (res) { 76 | let method = res.code == 200 ? 'success' : 'error'; 77 | Dcat[method](res.message); 78 | if (res.code == 200) { 79 | that.parent('div').remove(); 80 | _this.processSku(); 81 | } 82 | } 83 | }) 84 | }); 85 | 86 | return false; 87 | }); 88 | 89 | _this.wrap.find('.sku_edit_wrap tbody').on('mouseenter', '.sku_img img', function () { 90 | $(this).next('.icon-x').show(); 91 | }); 92 | 93 | _this.wrap.find('.sku_edit_wrap tbody').on('mouseleave', '.sku_img img', function () { 94 | $(this).next('.icon-x').hide(); 95 | }); 96 | 97 | let old_val = _this.wrap.find('.Js_sku_input').val(); 98 | let params = _this.wrap.find('.Js_sku_params_input').val(); 99 | if (old_val) { 100 | // 根据值生成DOM 101 | old_val = JSON.parse(old_val); 102 | // 处理规格名 103 | let attr_names = old_val.attrs; 104 | let tbody = _this.wrap.find('.sku_attr_key_val tbody'); 105 | let attr_keys = Object.keys(attr_names); 106 | let attr_keys_len = attr_keys.length; 107 | 108 | attr_keys.forEach(function (attr_key, index) { 109 | // 规格名 110 | let tr = tbody.find('tr').eq(index); 111 | let scopeAttrType = ''; 112 | let sku = _this.skuAttributes.filter((res) => { 113 | return res.attr_name == attr_key; 114 | }); 115 | if (sku.length > 0) { 116 | scopeAttrType = sku[0].attr_type; 117 | } 118 | switch (scopeAttrType) { 119 | case 'checkbox': 120 | case 'radio': 121 | _this.currentAttributeValue = attr_names[attr_key]; 122 | _this.currentSkuId = sku[0].id; 123 | let attributeHtml = _this.getAttributeHtml(scopeAttrType, sku[0]); 124 | let html = _this.getHtml(attributeHtml); 125 | _this.wrap.find('.sku_attr_key_val tbody').append(html); 126 | break; 127 | default: 128 | tr.find('td:eq(0) input').val(attr_key); 129 | // 规格值 130 | let attr_val_td = tr.find('td:eq(1)'); 131 | let attr_vals = attr_names[attr_key]; 132 | let attr_vals_len = attr_vals.length; 133 | attr_vals.forEach(function (attr_val, index_2) { 134 | attr_val_td.find('input').eq(index_2).val(attr_val); 135 | if (index_2 < attr_vals_len - 1) { 136 | attr_val_td.find('.Js_add_attr_val').trigger('click'); 137 | } 138 | }); 139 | break; 140 | } 141 | }); 142 | 143 | // 生成具体的SKU配置表单 144 | _this.attrs = old_val.attrs; 145 | _this.SKUForm(old_val.sku, JSON.parse(params)); 146 | } else { 147 | _this.processSku(); 148 | } 149 | }; 150 | 151 | // 获取SKU属性 152 | SKU.prototype.getSkuAttr = function () { 153 | let attr = {}; // 所有属性 154 | let _this = this; 155 | let trs = _this.wrap.find('.sku_attr_key_val tbody tr'); 156 | trs.each(function () { 157 | let tr = $(this); 158 | let attr_val = []; // 属性值 159 | let scopeAttrType = tr.find('td:eq(0) select:eq(0)').val(); 160 | let scopeAttrName = ''; 161 | switch (scopeAttrType) { 162 | case 'checkbox': 163 | case 'radio': 164 | scopeAttrName = tr.find('td:eq(0) select:eq(0)').find("option:selected").text(); 165 | tr.find('td:eq(1) input[type="' + scopeAttrType + '"]:checked').each(function (i, v) { 166 | attr_val.push($(v).val()); 167 | }); 168 | break; 169 | default: 170 | scopeAttrName = tr.find('td:eq(0) input').val(); 171 | tr.find('td:eq(1) input').each(function () { 172 | let ipt_val = $(this).val(); 173 | if (ipt_val) { 174 | attr_val.push(ipt_val) 175 | } 176 | }); 177 | break; 178 | } 179 | if (attr_val.length) { 180 | attr[scopeAttrName] = attr_val; 181 | } 182 | }); 183 | if (JSON.stringify(_this.attrs) !== JSON.stringify(attr)) { 184 | _this.attrs = attr; 185 | let params = _this.wrap.find('.Js_sku_params_input').val(); 186 | _this.SKUForm(null, JSON.parse(params)) 187 | } 188 | }; 189 | 190 | // 生成具体的SKU配置表单 191 | SKU.prototype.SKUForm = function (default_sku, params) { 192 | let _this = this; 193 | let attr_names = Object.keys(_this.attrs); 194 | if (attr_names.length === 0) { 195 | _this.wrap.find('.sku_edit_wrap tbody').html(' '); 196 | _this.wrap.find('.sku_edit_wrap thead').html(' '); 197 | } else { 198 | // 渲染表头 199 | let thead_html = ''; 200 | attr_names.forEach(function (attr_name) { 201 | thead_html += '' + attr_name + '' 202 | }); 203 | thead_html += '图片 '; 204 | thead_html += '库存 '; 205 | thead_html += '价格 '; 206 | 207 | params.forEach((v) => { 208 | thead_html += '' + v['name'] + '' 209 | }) 210 | 211 | thead_html += ''; 212 | _this.wrap.find('.sku_edit_wrap thead').html(thead_html); 213 | 214 | // 求笛卡尔积 215 | let cartesianProductOf = (function () { 216 | return Array.prototype.reduce.call(arguments, function (a, b) { 217 | const ret = []; 218 | a.forEach(function (a) { 219 | b.forEach(function (b) { 220 | ret.push(a.concat([b])); 221 | }); 222 | }); 223 | return ret; 224 | }, [[]]); 225 | })(...Object.values(_this.attrs)); 226 | 227 | // 根据计算的笛卡尔积渲染tbody 228 | let tbody_html = ''; 229 | cartesianProductOf.forEach(function (sku_item) { 230 | tbody_html += ''; 231 | sku_item.forEach(function (attr_val, index) { 232 | let attr_name = attr_names[index]; 233 | tbody_html += '' + attr_val + ''; 234 | }); 235 | tbody_html += '
'; 236 | tbody_html += ''; 237 | tbody_html += ''; 238 | 239 | params.forEach((v) => { 240 | tbody_html += ''; 241 | }) 242 | tbody_html += '' 243 | }); 244 | _this.wrap.find('.sku_edit_wrap tbody').html(tbody_html); 245 | if (default_sku) { 246 | // 填充数据 247 | default_sku.forEach(function (item_sku, index) { 248 | let tr = _this.wrap.find('.sku_edit_wrap tbody tr').eq(index); 249 | Object.keys(item_sku).forEach(function (field) { 250 | if (field == 'pic' && item_sku[field].length > 0) { 251 | let html = ''; 252 | item_sku[field].forEach(function (v) { 253 | html += '
'; 254 | }); 255 | tr.find('.Js_sku_upload').before(html); 256 | } else { 257 | let input = tr.find('td[data-field="' + field + '"] input'); 258 | if (input.length) { 259 | input.val(item_sku[field]); 260 | } 261 | } 262 | }) 263 | }); 264 | } 265 | } 266 | _this.processSku() 267 | _this.setListener() 268 | }; 269 | 270 | 271 | SKU.prototype.setListener = function () { 272 | let _this = this 273 | let ths = _this.wrap.find('.sku_edit_wrap thead th') 274 | let tr = _this.wrap.find('.sku_edit_wrap tbody tr') 275 | let tds = tr.find('td[data-field]') 276 | 277 | ths.each(function () { 278 | let th = $(this) 279 | let input = th.find('input') 280 | input.change(function () { 281 | let value = input.val() 282 | let field = th.attr('data-field') 283 | // 找出所有带这个attr的下级 284 | tds.each(function () { 285 | let td = $(this) 286 | let tdField = td.attr('data-field') 287 | let tdInput = td.find('input') 288 | if (tdInput && field === tdField) { 289 | tdInput.val(value); 290 | } 291 | }) 292 | _this.processSku() 293 | }) 294 | }); 295 | }; 296 | 297 | // 处理最终SKU数据,并写入input 298 | SKU.prototype.processSku = function () { 299 | let _this = this; 300 | let sku_json = {}; 301 | sku_json.type = _this.wrap.find('.sku_attr_select .btn.btn-success').attr('data-type'); 302 | // 多规格 303 | sku_json.attrs = _this.attrs; 304 | let sku = []; 305 | _this.wrap.find('.sku_edit_wrap tbody tr').each(function () { 306 | let tr = $(this); 307 | let item_sku = {}; 308 | tr.find('td[data-field]').each(function () { 309 | let pic = []; 310 | let td = $(this); 311 | let field = td.attr('data-field'); 312 | if (field == 'pic') { 313 | let skuImg = td.find('.img'); 314 | if (skuImg.length > 0) { 315 | skuImg.each(function (i, v) { 316 | pic.push({ 317 | short_url: $(v).find('.icon-x').data('path'), 318 | full_url: $(v).find('img').attr('src') 319 | }); 320 | }); 321 | item_sku['pic'] = pic; 322 | } 323 | } else { 324 | item_sku[field] = td.find('input').val() || td.text(); 325 | } 326 | }); 327 | sku.push(item_sku); 328 | }); 329 | sku_json.sku = sku; 330 | _this.wrap.find('.Js_sku_input').val(JSON.stringify(sku_json)); 331 | }; 332 | 333 | // 图片上传 334 | SKU.prototype.upload = function (obj) { 335 | let _this = this; 336 | // 创建input[type="file"]元素 337 | let file_input = document.createElement('input'); 338 | file_input.setAttribute('type', 'file'); 339 | file_input.setAttribute('accept', 'image/x-png,image/jpeg'); 340 | 341 | // 模拟点击 选择文件 342 | file_input.click(); 343 | 344 | file_input.onchange = function () { 345 | let file = file_input.files[0]; //获取上传的文件名 346 | let formData = new FormData(); 347 | formData.append('file', file); 348 | // 使用ajax上传文件 349 | $.ajax({ 350 | type: "POST", 351 | url: _this.uploadUrl, 352 | data: formData, 353 | contentType: false, //告诉jQuery不要去设置Content-Type请求头 354 | headers: { 355 | Accept: "application/json" 356 | }, 357 | processData: false, //告诉jQuery不要去处理发送的数据 358 | success: function (res) { 359 | obj.before('
'); 360 | _this.processSku(); 361 | } 362 | }) 363 | } 364 | }; 365 | 366 | SKU.prototype.changeAttrValueHtml = function (e, attrType) { 367 | let html = this.getAttributeHtml(attrType, this.skuAttr); 368 | attrType != 'input' ? e.next('input').hide() : e.next('input').show(); 369 | e.parent().next('td').find('.sku_attr_val_wrap').html(html); 370 | }; 371 | 372 | SKU.prototype.getAttributeHtml = function (attrType, attribute) { 373 | let _this = this; 374 | let html = ''; 375 | switch (attrType) { 376 | case 'checkbox': 377 | if (attribute.attr_value.length > 0) { 378 | html += '
'; 379 | attribute.attr_value.forEach(function (v) { 380 | html += '
= 0) { 382 | html += ' checked="checked"'; 383 | } 384 | html += '>' + 385 | '' + 386 | '' + 387 | '' + 388 | '' + 389 | '' + v + '' + 390 | '
' 391 | }); 392 | html += '
'; 393 | } 394 | break; 395 | case 'radio': 396 | if (attribute.attr_value.length > 0) { 397 | html += '
'; 398 | attribute.attr_value.forEach(function (v) { 399 | html += '
' + 400 | '= 0) { 402 | html += ' checked="checked"'; 403 | } 404 | html += '>' + 405 | '' + 406 | '' + 407 | '' + 408 | '' + v + '' + 409 | '
'; 410 | }); 411 | html += '
'; 412 | } 413 | break; 414 | default: 415 | html = '
' + 416 | '
' + 417 | '' + 418 | '
' + 419 | '' + 420 | '
' + 421 | '
' + 422 | '' + 423 | '
'; 424 | break; 425 | } 426 | 427 | return html; 428 | }; 429 | 430 | SKU.prototype.getHtml = function (innerHtml = '') { 431 | let _this = this; 432 | let skuAttributesArray = this.skuAttributes; 433 | let html = '
' + 446 | (innerHtml.length > 0 ? innerHtml : this.getAttributeHtml('input', this.skuAttr)) + 447 | '
移除'; 448 | 449 | return html; 450 | }; 451 | 452 | window.JadeKunSKU = SKU; 453 | })(); 454 | -------------------------------------------------------------------------------- /resources/views/index.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 46 | 49 | 50 | 51 |
规格名规格值操作
25 | 31 | 32 | 34 |
35 |
36 |
37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
47 | 添加 48 |
52 |
53 | 54 | 55 |
56 | 57 | 58 | 59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 | 85 | -------------------------------------------------------------------------------- /screenshots/attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abbotton/dcat-sku-plus/b2b5dce2f8f47b984a7f2b0b0fe6e8b9fa153325/screenshots/attributes.png -------------------------------------------------------------------------------- /screenshots/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abbotton/dcat-sku-plus/b2b5dce2f8f47b984a7f2b0b0fe6e8b9fa153325/screenshots/form.png -------------------------------------------------------------------------------- /screenshots/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Abbotton/dcat-sku-plus/b2b5dce2f8f47b984a7f2b0b0fe6e8b9fa153325/screenshots/setting.png -------------------------------------------------------------------------------- /src/DcatSkuPlusServiceProvider.php: -------------------------------------------------------------------------------- 1 | '属性管理', 21 | 'uri' => 'sku-attribute' 22 | ] 23 | ]; 24 | 25 | public function init() 26 | { 27 | parent::init(); 28 | 29 | if ($views = $this->getViewPath()) { 30 | $this->loadViewsFrom($views, 'dcat-sku-plus'); 31 | } 32 | 33 | Admin::booting(function () { 34 | Form::extend('sku', SkuField::class); 35 | }); 36 | } 37 | 38 | public function settingForm(): Setting 39 | { 40 | return new Setting($this); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Http/Controllers/SkuAttributeController.php: -------------------------------------------------------------------------------- 1 | '复选框', 15 | 'radio' => '单选框', 16 | ]; 17 | 18 | /** 19 | * Index interface. 20 | * 21 | * @param Content $content 22 | * 23 | * @return Content 24 | */ 25 | public function index(Content $content) 26 | { 27 | return $content 28 | ->title('属性列表') 29 | ->body($this->grid()); 30 | } 31 | 32 | /** 33 | * Make a grid builder. 34 | * 35 | * @return Grid 36 | */ 37 | protected function grid() 38 | { 39 | return Grid::make(new SkuAttribute(), function (Grid $grid) { 40 | $grid->model()->orderByDesc('id'); 41 | $grid->id->sortable(); 42 | $grid->column('attr_name', '属性名称'); 43 | $grid->column('attr_type', '属性类型') 44 | ->using($this->attrType) 45 | ->label([ 46 | 'checkbox' => 'info', 47 | 'radio' => 'primary' 48 | ]); 49 | $grid->column('sort', '排序')->help('排序越大越靠前'); 50 | $grid->column('attr_value', '属性值')->explode()->label(); 51 | 52 | $grid->created_at; 53 | $grid->updated_at->sortable(); 54 | 55 | $grid->disableViewButton(); 56 | 57 | $grid->filter(function (Grid\Filter $filter) { 58 | $filter->equal('id'); 59 | $filter->like('attr_name', '属性名称'); 60 | $filter->equal('attr_type', '属性类型')->select($this->attrType); 61 | }); 62 | }); 63 | } 64 | 65 | /** 66 | * Edit interface. 67 | * 68 | * @param mixed $id 69 | * @param Content $content 70 | * 71 | * @return Content 72 | */ 73 | public function edit($id, Content $content) 74 | { 75 | return $content 76 | ->title('编辑属性') 77 | ->body($this->form()->edit($id)); 78 | } 79 | 80 | /** 81 | * Make a form builder. 82 | * 83 | * @return Form 84 | */ 85 | protected function form() 86 | { 87 | return Form::make(new SkuAttribute(), function (Form $form) { 88 | $form->display('id'); 89 | $form->text('attr_name', '属性名称')->required(); 90 | $form->radio('attr_type', '属性类型')->options($this->attrType)->required(); 91 | $form->list('attr_value', '属性值'); 92 | $form->number('sort', '排序')->default(0)->min(0)->max(100); 93 | 94 | $form->display('created_at'); 95 | $form->display('updated_at'); 96 | 97 | $form->disableViewButton(); 98 | $form->disableViewCheck(); 99 | }); 100 | } 101 | 102 | /** 103 | * Create interface. 104 | * 105 | * @param Content $content 106 | * 107 | * @return Content 108 | */ 109 | public function create(Content $content) 110 | { 111 | return $content 112 | ->title('添加属性') 113 | ->body($this->form()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Http/Controllers/UploadController.php: -------------------------------------------------------------------------------- 1 | hasFile('file')) { 19 | $file = request()->file('file'); 20 | $disk = config('admin.upload.disk'); 21 | $path = Storage::disk($disk)->put('sku', $file); 22 | $response = ['full_url' => Storage::disk($disk)->url($path), 'short_url' => $path]; 23 | 24 | return response()->json($response); 25 | } 26 | } 27 | 28 | /** 29 | * 删除图片. 30 | * 31 | * @return JsonResponse 32 | */ 33 | public function delete(): JsonResponse 34 | { 35 | $disk = config('admin.upload.disk'); 36 | $path = request()->input('path'); 37 | if (!Storage::disk($disk)->exists($path)) { 38 | return response()->json(['code' => 404, 'message' => '未找到相关图片']); 39 | } 40 | 41 | try { 42 | Storage::disk($disk)->delete($path); 43 | 44 | return response()->json(['code' => 200, 'message' => '删除成功']); 45 | } catch (\Exception $exception) { 46 | return response()->json(['code' => $exception->getCode(), 'message' => $exception->getMessage()]); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Http/Repositories/SkuAttribute.php: -------------------------------------------------------------------------------- 1 | except('show'); 8 | // 图片上传 9 | Route::post('sku-image-upload', [Controllers\UploadController::class, 'store']); 10 | Route::post('sku-image-remove', [Controllers\UploadController::class, 'delete']); 11 | -------------------------------------------------------------------------------- /src/Models/SkuAttribute.php: -------------------------------------------------------------------------------- 1 | 'json' 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /src/Setting.php: -------------------------------------------------------------------------------- 1 | text('sku_plus_img_upload_url', '图片上传地址') 17 | ->default('/admin/sku-image-upload') 18 | ->help('必须以【/】开头') 19 | ->required(); 20 | 21 | $this->text('sku_plus_img_remove_url', '图片删除地址') 22 | ->default('/admin/sku-image-remove') 23 | ->help('必须以【/】开头') 24 | ->required(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SkuField.php: -------------------------------------------------------------------------------- 1 | get(); 21 | 22 | $this->script = <<< EOF 23 | window.DemoSku = new JadeKunSKU('{$this->getElementClassSelector()}'); 24 | EOF; 25 | $this->addVariables(compact('skuAttributes', 'uploadUrl', 'deleteUrl')); 26 | 27 | return parent::render(); 28 | } 29 | 30 | /** 31 | * 添加扩展列. 32 | * 33 | * @param array $column 34 | * @return $this 35 | */ 36 | public function addColumn(array $column = []): self 37 | { 38 | $this->addVariables(['extra_column' => json_encode($column)]); 39 | 40 | return $this; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /updates/create_sku_attribute_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('attr_name', 128)->comment('规格名称'); 19 | $table->enum('attr_type', ['checkbox', 'radio'])->comment('规格类型'); 20 | $table->json('attr_value')->nullable()->comment('规格值'); 21 | $table->tinyInteger('sort')->default(0)->comment('排序'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('sku_attribute'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /version.php: -------------------------------------------------------------------------------- 1 | [ 5 | '第一版完成', 6 | 'create_sku_attribute_table.php' 7 | ], 8 | ]; 9 | --------------------------------------------------------------------------------