├── .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 | 
30 |
31 |
32 |
33 | * 在表单界面规格名位置选择下拉框中的相关属性或者手动输入,生成SKU相关信息。
34 |
35 |
36 | 
37 |
38 |
39 |
40 | * 插件配置页面可以配置图片上传、删除的URL。
41 |
42 |
43 | 
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 |
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 |
--------------------------------------------------------------------------------