@lancom/shared 0.0.326 → 0.0.328
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/js/api/admin.js +16 -1
- package/assets/js/utils/product.js +56 -11
- package/components/checkout/order/address-form/address-form.vue +41 -31
- package/components/checkout/order/order-billing-information/order-billing-information.vue +1 -0
- package/components/checkout/order/order-success/order-success.scss +1 -1
- package/components/common/postcode_select/postcode-select.vue +9 -1
- package/components/products/products_brands/products-brands.vue +1 -1
- package/components/products/products_link/products-link.vue +4 -0
- package/components/products/products_types/products-types.vue +8 -6
- package/layouts/products.vue +38 -8
- package/mixins/meta-info.js +1 -1
- package/mixins/product-view.js +30 -4
- package/package.json +1 -1
- package/routes/index.js +75 -0
- package/store/products.js +13 -1
package/assets/js/api/admin.js
CHANGED
|
@@ -200,6 +200,18 @@ export default {
|
|
|
200
200
|
removePage(id) {
|
|
201
201
|
return _delete(`admin/pages/${id}`);
|
|
202
202
|
},
|
|
203
|
+
fetchCategories(params) {
|
|
204
|
+
return _get('admin/categories', params);
|
|
205
|
+
},
|
|
206
|
+
fetchCategoryById(id) {
|
|
207
|
+
return _get(`admin/categories/${id}`);
|
|
208
|
+
},
|
|
209
|
+
saveCategory(category) {
|
|
210
|
+
return category._id ? _put(`admin/categories/${category._id}`, category) : _post('admin/categories', category);
|
|
211
|
+
},
|
|
212
|
+
removeCategory(id) {
|
|
213
|
+
return _delete(`admin/categories/${id}`);
|
|
214
|
+
},
|
|
203
215
|
fetchPackages() {
|
|
204
216
|
return _get('admin/packages');
|
|
205
217
|
},
|
|
@@ -731,5 +743,8 @@ export default {
|
|
|
731
743
|
},
|
|
732
744
|
findResources(query) {
|
|
733
745
|
return _get(`admin/order/resources?search=${query || ''}`);
|
|
734
|
-
}
|
|
746
|
+
},
|
|
747
|
+
getReOrderReport(params) {
|
|
748
|
+
return _get(`admin/reports/re-order`, params);
|
|
749
|
+
},
|
|
735
750
|
};
|
|
@@ -98,12 +98,24 @@ export function filterBigSize(simpleProducts) {
|
|
|
98
98
|
// };
|
|
99
99
|
// };
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
function getCategoryPathFromRoute(routeParams) {
|
|
102
|
+
const categories = [];
|
|
103
|
+
for (let i = 5; i >= 0; i--) {
|
|
104
|
+
const category = routeParams?.[`category${!i ? '' : i}`];
|
|
105
|
+
if (category) {
|
|
106
|
+
categories.push(category);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return categories.join('/');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function generateProductsLink($route, data, skipParams = false) {
|
|
113
|
+
let { type, category, brand, text, sort } = data;
|
|
114
|
+
category = (category || category === null) ? generateCategoryPath(category) : getCategoryPathFromRoute($route?.params);
|
|
115
|
+
type = (type || type === null) ? type : ($route?.params?.type || $route?.query?.type);
|
|
116
|
+
brand = (brand || brand === null) ? brand : ($route?.params?.brand || $route?.query?.brand);
|
|
117
|
+
text = (text || text === '') ? text : $route?.query?.text;
|
|
118
|
+
sort = (sort || sort === '') ? sort : $route?.query?.sort;
|
|
107
119
|
|
|
108
120
|
const params = [];
|
|
109
121
|
|
|
@@ -120,7 +132,8 @@ export function generateProductsLink($route, data) {
|
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
Object.keys({ ...(($route && $route.query) || {}), ...data }).forEach(key => {
|
|
123
|
-
|
|
135
|
+
const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand'];
|
|
136
|
+
if (!rootKeys.includes(key)) {
|
|
124
137
|
let items = ($route && $route.query[key]) ? $route.query[key].split(',') : [];
|
|
125
138
|
items = (data[key] ? (items.includes(data[key]) ? items.filter(c => c !== data[key]) : [...items, data[key]]) : items).join(',');
|
|
126
139
|
if (items) {
|
|
@@ -128,16 +141,48 @@ export function generateProductsLink($route, data) {
|
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
});
|
|
144
|
+
if (category) {
|
|
145
|
+
if (brand) {
|
|
146
|
+
params.push(`brand=${brand}`);
|
|
147
|
+
}
|
|
148
|
+
if (type) {
|
|
149
|
+
params.push(`type=${type}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
131
152
|
|
|
132
|
-
const
|
|
133
|
-
const
|
|
153
|
+
const categoryRoute = category ? `/c/${category}` : '';
|
|
154
|
+
const typeRoute = (!category && type) ? `/${type}` : '';
|
|
155
|
+
const brandRoute = (!category && brand) ? `/brand/${brand}` : '';
|
|
134
156
|
|
|
135
|
-
|
|
157
|
+
const baseRoute = categoryRoute || `/products${typeRoute}${brandRoute}`;
|
|
158
|
+
return `${baseRoute}${(params.length && !skipParams) ? `?${params.join('&')}` : ''}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function generateCategoryPath(cat) {
|
|
162
|
+
let category = cat;
|
|
163
|
+
const categories = [];
|
|
164
|
+
while (category) {
|
|
165
|
+
categories.unshift(category.alias);
|
|
166
|
+
category = category.parent;
|
|
167
|
+
}
|
|
168
|
+
let baseLink = '';
|
|
169
|
+
if (categories.length) {
|
|
170
|
+
baseLink = `${categories.join('/')}`;
|
|
171
|
+
}
|
|
172
|
+
return baseLink;
|
|
136
173
|
}
|
|
137
174
|
|
|
138
175
|
export function generateProductLink(product, color, toEditor = false) {
|
|
139
176
|
const baseLink = `/${product.brand?.alias}/${product.productType?.alias}/${product.alias}`;
|
|
140
|
-
|
|
177
|
+
let link = toEditor ? `${baseLink}/editor` : baseLink;
|
|
178
|
+
if (product.useTaxonomyUrl) {
|
|
179
|
+
const categoryPath = generateCategoryPath(product.category);
|
|
180
|
+
if (categoryPath) {
|
|
181
|
+
const categoryBaseLink = `/c/${categoryPath}/${product.alias}/${product.SKU.toLowerCase()}`;
|
|
182
|
+
link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}/info.html`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return `${link}${color ? `?color=${color.alias || color}` : ''}`;
|
|
141
186
|
}
|
|
142
187
|
|
|
143
188
|
export function getProductColorImages(product, color, skipDefault = false) {
|
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
<validation-provider
|
|
6
6
|
v-slot="{ errors }"
|
|
7
7
|
tag="div"
|
|
8
|
-
name="Full Name"
|
|
8
|
+
:name="namePrefix + 'Full Name'"
|
|
9
9
|
rules="required"
|
|
10
10
|
class="form-col col-half">
|
|
11
11
|
<label
|
|
12
|
-
for="fullName"
|
|
12
|
+
:for="'fullName'+uniqueKey"
|
|
13
13
|
class="form-label">
|
|
14
14
|
Full Name
|
|
15
15
|
</label>
|
|
16
16
|
<input
|
|
17
|
-
id="fullName"
|
|
17
|
+
:id="'fullName'+uniqueKey"
|
|
18
18
|
ref="fullName"
|
|
19
19
|
v-model="address.fullName"
|
|
20
20
|
placeholder="Full Name"
|
|
21
|
-
name="fullName"
|
|
21
|
+
:name="'fullName'+uniqueKey"
|
|
22
22
|
type="text"
|
|
23
23
|
class="form-field labelless"
|
|
24
24
|
:class="{
|
|
@@ -35,19 +35,19 @@
|
|
|
35
35
|
<validation-provider
|
|
36
36
|
v-slot="{ errors }"
|
|
37
37
|
tag="div"
|
|
38
|
-
name="
|
|
38
|
+
:name="namePrefix + 'Company'"
|
|
39
39
|
class="form-col col-half">
|
|
40
40
|
<label
|
|
41
|
-
for="company"
|
|
41
|
+
:for="'company'+uniqueKey"
|
|
42
42
|
class="form-label">
|
|
43
43
|
Company Name
|
|
44
44
|
</label>
|
|
45
45
|
<input
|
|
46
|
-
id="company"
|
|
46
|
+
:id="'company'+uniqueKey"
|
|
47
47
|
ref="company"
|
|
48
48
|
v-model="address.company"
|
|
49
49
|
placeholder="Company Name"
|
|
50
|
-
name="company"
|
|
50
|
+
:name="'company'+uniqueKey"
|
|
51
51
|
type="text"
|
|
52
52
|
class="form-field labelless"
|
|
53
53
|
:class="{
|
|
@@ -66,20 +66,20 @@
|
|
|
66
66
|
<validation-provider
|
|
67
67
|
v-slot="{ errors }"
|
|
68
68
|
tag="div"
|
|
69
|
-
name="
|
|
69
|
+
:name="namePrefix + 'Email'"
|
|
70
70
|
rules="required|email"
|
|
71
71
|
class="form-col col-half">
|
|
72
72
|
<label
|
|
73
|
-
for="email"
|
|
73
|
+
:for="'email'+uniqueKey"
|
|
74
74
|
class="form-label">
|
|
75
75
|
Email
|
|
76
76
|
</label>
|
|
77
77
|
<input
|
|
78
|
-
id="email"
|
|
78
|
+
:id="'email'+uniqueKey"
|
|
79
79
|
ref="email"
|
|
80
80
|
v-model="address.email"
|
|
81
81
|
placeholder="Email"
|
|
82
|
-
name="email"
|
|
82
|
+
:name="'email'+uniqueKey"
|
|
83
83
|
type="email"
|
|
84
84
|
class="form-field labelless"
|
|
85
85
|
:class="{
|
|
@@ -96,20 +96,20 @@
|
|
|
96
96
|
<validation-provider
|
|
97
97
|
v-slot="{ errors }"
|
|
98
98
|
tag="div"
|
|
99
|
-
name="Phone"
|
|
99
|
+
:name="namePrefix + 'Phone'"
|
|
100
100
|
rules="required|phone"
|
|
101
101
|
class="form-col col-half">
|
|
102
102
|
<label
|
|
103
|
-
for="phone"
|
|
103
|
+
:for="'phone'+uniqueKey"
|
|
104
104
|
class="form-label">
|
|
105
105
|
Phone number
|
|
106
106
|
</label>
|
|
107
107
|
<input
|
|
108
|
-
id="phone"
|
|
108
|
+
:id="'phone'+uniqueKey"
|
|
109
109
|
ref="phone"
|
|
110
110
|
v-model="address.phone"
|
|
111
111
|
placeholder="Phone"
|
|
112
|
-
name="phone"
|
|
112
|
+
:name="'phone'+uniqueKey"
|
|
113
113
|
type="text"
|
|
114
114
|
class="form-field labelless"
|
|
115
115
|
:class="{
|
|
@@ -127,19 +127,19 @@
|
|
|
127
127
|
<validation-provider
|
|
128
128
|
v-slot="{ errors }"
|
|
129
129
|
tag="div"
|
|
130
|
-
name="Address line 1"
|
|
130
|
+
:name="namePrefix + 'Address line 1'"
|
|
131
131
|
rules="required"
|
|
132
132
|
class="form-row">
|
|
133
133
|
<label
|
|
134
|
-
for="addressLine1"
|
|
134
|
+
:for="'addressLine1'+uniqueKey"
|
|
135
135
|
class="form-label">
|
|
136
136
|
Address line 1
|
|
137
137
|
</label>
|
|
138
138
|
<input
|
|
139
|
-
id="addressLine1"
|
|
139
|
+
:id="'addressLine1'+uniqueKey"
|
|
140
140
|
ref="addressLine1"
|
|
141
141
|
v-model="address.addressLine1"
|
|
142
|
-
name="addressLine1"
|
|
142
|
+
:name="'addressLine1'+uniqueKey"
|
|
143
143
|
type="text"
|
|
144
144
|
class="form-field labelless"
|
|
145
145
|
placeholder="Address line 1"
|
|
@@ -158,18 +158,18 @@
|
|
|
158
158
|
<validation-provider
|
|
159
159
|
v-slot="{ errors }"
|
|
160
160
|
tag="div"
|
|
161
|
-
name="Address line 2"
|
|
161
|
+
:name="namePrefix + 'Address line 2'"
|
|
162
162
|
class="form-row">
|
|
163
163
|
<label
|
|
164
|
-
for="addressLine2"
|
|
164
|
+
:for="'addressLine2'+uniqueKey"
|
|
165
165
|
class="form-label">
|
|
166
166
|
Address line 2
|
|
167
167
|
</label>
|
|
168
168
|
<input
|
|
169
|
-
id="addressLine2"
|
|
169
|
+
:id="'addressLine2'+uniqueKey"
|
|
170
170
|
ref="addressLine2"
|
|
171
171
|
v-model="address.addressLine2"
|
|
172
|
-
name="addressLine2"
|
|
172
|
+
:name="'addressLine2'+uniqueKey"
|
|
173
173
|
type="text"
|
|
174
174
|
class="form-field labelless"
|
|
175
175
|
placeholder="Address line 2"
|
|
@@ -191,20 +191,21 @@
|
|
|
191
191
|
:suburb="address.suburb"
|
|
192
192
|
:labelless="true"
|
|
193
193
|
:required="true"
|
|
194
|
+
:name-prefix="namePrefix"
|
|
194
195
|
placeholder="Suburb"
|
|
195
196
|
@select="handleSuburbChange">
|
|
196
197
|
</postcode-select>
|
|
197
198
|
<div class="form-col col-half">
|
|
198
199
|
<label
|
|
199
|
-
for="country"
|
|
200
|
+
:for="'country'+uniqueKey"
|
|
200
201
|
class="form-label">
|
|
201
202
|
Country
|
|
202
203
|
</label>
|
|
203
204
|
<input
|
|
204
|
-
id="country"
|
|
205
|
+
:id="'country'+uniqueKey"
|
|
205
206
|
ref="country"
|
|
206
207
|
:value="address.country === 'Australia' ? 'Only Australia' : address.country"
|
|
207
|
-
name="country"
|
|
208
|
+
:name="'country'+uniqueKey"
|
|
208
209
|
type="country"
|
|
209
210
|
class="form-field filled labelless"
|
|
210
211
|
disabled />
|
|
@@ -214,18 +215,18 @@
|
|
|
214
215
|
v-if="!withoutAdditionalInfo"
|
|
215
216
|
v-slot="{ errors }"
|
|
216
217
|
tag="div"
|
|
217
|
-
name="
|
|
218
|
+
:name="namePrefix + 'Additional Info'"
|
|
218
219
|
class="form-row">
|
|
219
220
|
<label
|
|
220
|
-
for="additionalInfo"
|
|
221
|
+
:for="'additionalInfo'+uniqueKey"
|
|
221
222
|
class="form-label">
|
|
222
223
|
Additional Info
|
|
223
224
|
</label>
|
|
224
225
|
<input
|
|
225
|
-
id="additionalInfo"
|
|
226
|
+
:id="'additionalInfo'+uniqueKey"
|
|
226
227
|
ref="additionalInfo"
|
|
227
228
|
v-model="address.additionalInfo"
|
|
228
|
-
name="additionalInfo"
|
|
229
|
+
:name="'additionalInfo'+uniqueKey"
|
|
229
230
|
placeholder="Additional Info"
|
|
230
231
|
type="text"
|
|
231
232
|
class="form-field labelless"
|
|
@@ -253,6 +254,11 @@ export default {
|
|
|
253
254
|
components: {
|
|
254
255
|
PostcodeSelect
|
|
255
256
|
},
|
|
257
|
+
data() {
|
|
258
|
+
return {
|
|
259
|
+
uniqueKey: Math.random()
|
|
260
|
+
};
|
|
261
|
+
},
|
|
256
262
|
props: {
|
|
257
263
|
address: {
|
|
258
264
|
type: Object,
|
|
@@ -264,6 +270,10 @@ export default {
|
|
|
264
270
|
withoutAdditionalInfo: {
|
|
265
271
|
type: Boolean,
|
|
266
272
|
default: false
|
|
273
|
+
},
|
|
274
|
+
namePrefix: {
|
|
275
|
+
type: String,
|
|
276
|
+
default: ''
|
|
267
277
|
}
|
|
268
278
|
},
|
|
269
279
|
computed: {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
v-slot="{ errors }"
|
|
5
5
|
tag="div"
|
|
6
6
|
:rules="required ? 'required' : ''"
|
|
7
|
-
name="Postcode"
|
|
7
|
+
:name="namePrefix + 'Postcode'"
|
|
8
8
|
class="form-group">
|
|
9
9
|
<label
|
|
10
10
|
v-if="labelless"
|
|
@@ -55,6 +55,9 @@
|
|
|
55
55
|
<input
|
|
56
56
|
type="text"
|
|
57
57
|
:value="value"
|
|
58
|
+
:id="'postcode'+uniqueKey"
|
|
59
|
+
:name="'postcode'+uniqueKey"
|
|
60
|
+
:required="required"
|
|
58
61
|
:class="{ 'filled': value || selected }"
|
|
59
62
|
class="form-hidden-validator form-field"
|
|
60
63
|
style="display: none" />
|
|
@@ -120,10 +123,15 @@ export default {
|
|
|
120
123
|
},
|
|
121
124
|
suburb: {
|
|
122
125
|
type: Object
|
|
126
|
+
},
|
|
127
|
+
namePrefix: {
|
|
128
|
+
type: String,
|
|
129
|
+
default: ''
|
|
123
130
|
}
|
|
124
131
|
},
|
|
125
132
|
data() {
|
|
126
133
|
return {
|
|
134
|
+
uniqueKey: Math.random(),
|
|
127
135
|
selected: null,
|
|
128
136
|
suburbs: [],
|
|
129
137
|
options: [],
|
|
@@ -12,6 +12,9 @@ import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
|
|
|
12
12
|
export default {
|
|
13
13
|
name: 'ProductsLink',
|
|
14
14
|
props: {
|
|
15
|
+
category: {
|
|
16
|
+
type: String
|
|
17
|
+
},
|
|
15
18
|
type: {
|
|
16
19
|
type: String
|
|
17
20
|
},
|
|
@@ -37,6 +40,7 @@ export default {
|
|
|
37
40
|
computed: {
|
|
38
41
|
link() {
|
|
39
42
|
const params = {
|
|
43
|
+
category: this.category,
|
|
40
44
|
type: this.type,
|
|
41
45
|
brand: this.brand,
|
|
42
46
|
tags: this.tags,
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
:class="{
|
|
9
9
|
'ProductsTypes__item--active': type.alias === currentType
|
|
10
10
|
}">
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
<products-link
|
|
12
|
+
class="ProductsTypes__more"
|
|
13
|
+
:type="type.alias === currentType ? null : type.alias">
|
|
14
14
|
<checked-icon
|
|
15
15
|
v-if="hasSelectedIcon"
|
|
16
16
|
:checked="type.alias === currentType"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
v-else-if="hasThumbs"
|
|
26
26
|
:class="`icon-${type.alias}`"></i>
|
|
27
27
|
{{ type.name }}
|
|
28
|
-
</
|
|
28
|
+
</products-link>
|
|
29
29
|
</div>
|
|
30
30
|
</toggle-content>
|
|
31
31
|
</div>
|
|
@@ -35,12 +35,14 @@
|
|
|
35
35
|
import { mapGetters } from 'vuex';
|
|
36
36
|
import ToggleContent from '@lancom/shared/components/common/toggle-content';
|
|
37
37
|
import CheckedIcon from '@lancom/shared/components/common/checked-icon';
|
|
38
|
+
import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
|
|
38
39
|
|
|
39
40
|
export default {
|
|
40
41
|
name: 'ProductsTypes',
|
|
41
42
|
components: {
|
|
42
43
|
ToggleContent,
|
|
43
|
-
CheckedIcon
|
|
44
|
+
CheckedIcon,
|
|
45
|
+
ProductsLink
|
|
44
46
|
},
|
|
45
47
|
props: {
|
|
46
48
|
hasSelectedIcon: {
|
|
@@ -59,7 +61,7 @@ export default {
|
|
|
59
61
|
computed: {
|
|
60
62
|
...mapGetters('products', ['types']),
|
|
61
63
|
currentType() {
|
|
62
|
-
return this.$route.params.type;
|
|
64
|
+
return this.$route.params.type || this.$route.query.type;
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
};
|
package/layouts/products.vue
CHANGED
|
@@ -98,27 +98,51 @@
|
|
|
98
98
|
computed: {
|
|
99
99
|
...mapGetters('page', ['routeInfo']),
|
|
100
100
|
...mapGetters(['notificationBar', 'shop', 'country', 'currency']),
|
|
101
|
-
...mapGetters('products', ['brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
|
|
101
|
+
...mapGetters('products', ['category', 'categories', 'brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
|
|
102
|
+
pageItemCanonical() {
|
|
103
|
+
return this.breadcrumbs[this.breadcrumbs.length - 1]?.to;
|
|
104
|
+
},
|
|
102
105
|
currentBrand() {
|
|
103
106
|
return this.findByRouteParam(this.brands, 'brand');
|
|
104
107
|
},
|
|
105
108
|
currentTag() {
|
|
106
|
-
return this.findByRouteParam(this.tags, '
|
|
109
|
+
return this.findByRouteParam(this.tags, 'tag');
|
|
107
110
|
},
|
|
108
111
|
currentProductType() {
|
|
109
112
|
return this.findByRouteParam(this.types, 'type');
|
|
110
113
|
},
|
|
114
|
+
currentCategory() {
|
|
115
|
+
return this.category;
|
|
116
|
+
},
|
|
111
117
|
breadcrumbs() {
|
|
112
118
|
const breadcrumbs = [{
|
|
113
119
|
to: '/products',
|
|
114
120
|
text: 'Products'
|
|
115
121
|
}];
|
|
116
|
-
|
|
122
|
+
if (this.currentCategory) {
|
|
123
|
+
const categoriesBreadcrumbs = [];
|
|
124
|
+
let category = this.currentCategory;
|
|
125
|
+
console.log('category: ', category);
|
|
126
|
+
while (category) {
|
|
127
|
+
categoriesBreadcrumbs.unshift({
|
|
128
|
+
to: generateProductsLink(this.$route, {
|
|
129
|
+
type: null,
|
|
130
|
+
brand: null,
|
|
131
|
+
tag: null,
|
|
132
|
+
category
|
|
133
|
+
}, true),
|
|
134
|
+
text: category.name
|
|
135
|
+
});
|
|
136
|
+
category = category.parent;
|
|
137
|
+
}
|
|
138
|
+
breadcrumbs.push(...categoriesBreadcrumbs);
|
|
139
|
+
}
|
|
117
140
|
if (this.currentProductType) {
|
|
118
141
|
breadcrumbs.push({
|
|
119
142
|
to: generateProductsLink(this.$route, {
|
|
120
143
|
type: this.currentProductType.alias,
|
|
121
144
|
brand: null,
|
|
145
|
+
tag: null,
|
|
122
146
|
category: null
|
|
123
147
|
}),
|
|
124
148
|
text: this.currentProductType.name
|
|
@@ -129,8 +153,9 @@
|
|
|
129
153
|
breadcrumbs.push({
|
|
130
154
|
to: generateProductsLink(this.$route, {
|
|
131
155
|
type: this.currentProductType?.alias,
|
|
132
|
-
|
|
133
|
-
brand: null
|
|
156
|
+
tag: this.currentTag?.alias,
|
|
157
|
+
brand: null,
|
|
158
|
+
category: null
|
|
134
159
|
}),
|
|
135
160
|
text: this.currentTag.name
|
|
136
161
|
});
|
|
@@ -140,8 +165,9 @@
|
|
|
140
165
|
breadcrumbs.push({
|
|
141
166
|
to: generateProductsLink(this.$route, {
|
|
142
167
|
type: this.currentProductType?.alias,
|
|
143
|
-
|
|
144
|
-
brand: this.currentBrand?.alias
|
|
168
|
+
tag: this.currentTag?.alias,
|
|
169
|
+
brand: this.currentBrand?.alias,
|
|
170
|
+
category: this.currentCategory?.alias
|
|
145
171
|
}),
|
|
146
172
|
text: this.currentBrand.name
|
|
147
173
|
});
|
|
@@ -155,6 +181,9 @@
|
|
|
155
181
|
}
|
|
156
182
|
|
|
157
183
|
const items = ['Products'];
|
|
184
|
+
if (this.currentCategory) {
|
|
185
|
+
items.push(this.currentCategory.name);
|
|
186
|
+
}
|
|
158
187
|
if (this.currentProductType) {
|
|
159
188
|
items.push(this.currentProductType.name);
|
|
160
189
|
}
|
|
@@ -304,7 +333,8 @@
|
|
|
304
333
|
fillWithItemData(text = '') {
|
|
305
334
|
text = this.currentBrand ? text.replace(/{{brandName}}/g, this.currentBrand.name) : text;
|
|
306
335
|
text = this.currentProductType ? text.replace(/{{typeName}}/g, this.currentProductType.name) : text;
|
|
307
|
-
text = this.currentTag ? text.replace(/{{
|
|
336
|
+
text = this.currentTag ? text.replace(/{{tagName}}/g, this.currentTag.name) : text;
|
|
337
|
+
text = this.currentCategory ? text.replace(/{{categoryName}}/g, this.currentCategory.name) : text;
|
|
308
338
|
return text;
|
|
309
339
|
},
|
|
310
340
|
async openAside() {
|
package/mixins/meta-info.js
CHANGED
|
@@ -19,7 +19,7 @@ const metaInfo = {
|
|
|
19
19
|
},
|
|
20
20
|
methods: {
|
|
21
21
|
getCanonical() {
|
|
22
|
-
return this.$route.path === '/' ? '' : this.$route.path;
|
|
22
|
+
return this.$route.path === '/' ? '' : (this.pageItemCanonical || this.$route.path);
|
|
23
23
|
},
|
|
24
24
|
generateTitleFromRoute() {
|
|
25
25
|
return this.$route.path
|
package/mixins/product-view.js
CHANGED
|
@@ -7,17 +7,17 @@ import { tax, staticLink, inRange } from '@lancom/shared/assets/js/utils/filters
|
|
|
7
7
|
import { getProductLargeCover } from '@lancom/shared/assets/js/utils/colors';
|
|
8
8
|
import metaInfo from '@lancom/shared/mixins/meta-info';
|
|
9
9
|
import { STORE_CODES } from '@/constants/store';
|
|
10
|
-
|
|
10
|
+
import { generateProductLink, generateProductsLink } from '@lancom/shared/assets/js/utils/product';
|
|
11
11
|
const { mapActions, mapMutations } = createNamespacedHelpers('product');
|
|
12
12
|
|
|
13
|
-
export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
|
|
13
|
+
export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
14
14
|
mixins: [metaInfo],
|
|
15
15
|
transition(to, from) {
|
|
16
16
|
if (from && from.name === 'brand-tees-slug') { return 'slide-left-to-right'; };
|
|
17
17
|
if (to && to.name === 'brand-tees-slug') { return 'slide-right-to-left'; };
|
|
18
18
|
return 'fade';
|
|
19
19
|
},
|
|
20
|
-
async asyncData({ store, params, error, query }) {
|
|
20
|
+
async asyncData({ store, params, error, query, redirect }) {
|
|
21
21
|
try {
|
|
22
22
|
const { print, color, colour } = query;
|
|
23
23
|
const { shop, country, currency } = store.getters;
|
|
@@ -29,10 +29,20 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
|
|
|
29
29
|
print,
|
|
30
30
|
defaultColor: color || colour
|
|
31
31
|
};
|
|
32
|
+
|
|
32
33
|
await store.dispatch('product/fetchProduct', data);
|
|
33
34
|
await store.dispatch('product/fetchProductDetails', data);
|
|
34
35
|
|
|
35
36
|
const product = store.getters['product/product'];
|
|
37
|
+
|
|
38
|
+
if ((product.useTaxonomyUrl && !params.sku) || (!product.useTaxonomyUrl && params.sku)) {
|
|
39
|
+
const redirectUrl = generateProductLink(product, data.defaultColor, isEditor);
|
|
40
|
+
return redirect(301, redirectUrl);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (params.sku && params.sku !== product?.SKU?.toLowerCase()) {
|
|
44
|
+
return error({ statusCode: 404, message: 'Product not found' });
|
|
45
|
+
}
|
|
36
46
|
if (IS_PRODUCT_PRESET_PRINT_PRICING) {
|
|
37
47
|
const printType = product.printTypes[0];
|
|
38
48
|
store.commit('product/setSelectedPrintType', printType);
|
|
@@ -69,6 +79,9 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
|
|
|
69
79
|
pageItemImage() {
|
|
70
80
|
return this.mainProductImageSrc;
|
|
71
81
|
},
|
|
82
|
+
pageItemCanonical() {
|
|
83
|
+
return generateProductLink(this.product);
|
|
84
|
+
},
|
|
72
85
|
hasImages() {
|
|
73
86
|
return this.images.length > 0;
|
|
74
87
|
},
|
|
@@ -181,7 +194,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
|
|
|
181
194
|
}
|
|
182
195
|
},
|
|
183
196
|
fillBreadcrumbs() {
|
|
184
|
-
|
|
197
|
+
const breadcrumbs = [{
|
|
185
198
|
to: '/products',
|
|
186
199
|
text: 'Products'
|
|
187
200
|
}, {
|
|
@@ -190,6 +203,19 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
|
|
|
190
203
|
}, {
|
|
191
204
|
text: this.product.name
|
|
192
205
|
}];
|
|
206
|
+
if (this.product.useTaxonomyUrl && this.product.category) {
|
|
207
|
+
const categoriesBreadcrumbs = [];
|
|
208
|
+
let category = this.product.category;
|
|
209
|
+
while (category) {
|
|
210
|
+
categoriesBreadcrumbs.unshift({
|
|
211
|
+
to: generateProductsLink(null, { category }, true),
|
|
212
|
+
text: category.name
|
|
213
|
+
});
|
|
214
|
+
category = category.parent;
|
|
215
|
+
}
|
|
216
|
+
breadcrumbs.splice(1, 1, ...categoriesBreadcrumbs);
|
|
217
|
+
}
|
|
218
|
+
this.breadcrumbs = breadcrumbs;
|
|
193
219
|
}
|
|
194
220
|
},
|
|
195
221
|
getRoute() {
|
package/package.json
CHANGED
package/routes/index.js
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
module.exports = function(routes, resolve) {
|
|
2
2
|
return [{
|
|
3
|
+
name: 'product-taxonomy-view-level1',
|
|
4
|
+
path: '/c/:category/:slug/:sku/info.html',
|
|
5
|
+
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
6
|
+
chunkName: 'pages/product/view/level1'
|
|
7
|
+
}, {
|
|
8
|
+
name: 'product-taxonomy-view-level2',
|
|
9
|
+
path: '/c/:category1/:category/:slug/:sku/info.html',
|
|
10
|
+
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
11
|
+
chunkName: 'pages/product/view/level2'
|
|
12
|
+
}, {
|
|
13
|
+
name: 'product-taxonomy-view-level3',
|
|
14
|
+
path: '/c/:category2/:category1/:category/:slug/:sku/info.html',
|
|
15
|
+
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
16
|
+
chunkName: 'pages/product/view/level3'
|
|
17
|
+
}, {
|
|
18
|
+
name: 'product-taxonomy-view-level4',
|
|
19
|
+
path: '/c/:category3/:category2/:category1/:category/:slug/:sku/info.html',
|
|
20
|
+
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
21
|
+
chunkName: 'pages/product/view/level4'
|
|
22
|
+
}, {
|
|
23
|
+
name: 'product-taxonomy-view-level5',
|
|
24
|
+
path: '/c/:category4/:category3/:category2/:category1/:category/:slug/:sku/info.html',
|
|
25
|
+
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
26
|
+
chunkName: 'pages/product/view/level5'
|
|
27
|
+
}, {
|
|
28
|
+
name: 'product-taxonomy-editor-level1',
|
|
29
|
+
path: '/c/:category/:slug/:sku.html',
|
|
30
|
+
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
31
|
+
chunkName: 'pages/product/editor/level1'
|
|
32
|
+
}, {
|
|
33
|
+
name: 'product-taxonomy-editor-level2',
|
|
34
|
+
path: '/c/:category1/:category/:slug/:sku.html',
|
|
35
|
+
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
36
|
+
chunkName: 'pages/product/editor/level2'
|
|
37
|
+
}, {
|
|
38
|
+
name: 'product-taxonomy-editor-level3',
|
|
39
|
+
path: '/c/:category2/:category1/:category/:slug/:sku.html',
|
|
40
|
+
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
41
|
+
chunkName: 'pages/product/editor/level3'
|
|
42
|
+
}, {
|
|
43
|
+
name: 'product-taxonomy-editor-level4',
|
|
44
|
+
path: '/c/:category3/:category2/:category1/:category/:slug/:sku.html',
|
|
45
|
+
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
46
|
+
chunkName: 'pages/product/editor/level4'
|
|
47
|
+
}, {
|
|
48
|
+
name: 'product-taxonomy-editor-level5',
|
|
49
|
+
path: '/c/:category4/:category3/:category2/:category1/:category/:slug/:sku.html',
|
|
50
|
+
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
51
|
+
chunkName: 'pages/product/editor/level5'
|
|
52
|
+
}, {
|
|
53
|
+
name: 'products-taxonomy-catalog-level1',
|
|
54
|
+
path: '/c/:category',
|
|
55
|
+
component: resolve('@/pages/products/index.vue'),
|
|
56
|
+
chunkName: 'pages/products/catalog/level1'
|
|
57
|
+
}, {
|
|
58
|
+
name: 'products-taxonomy-catalog-level2',
|
|
59
|
+
path: '/c/:category1/:category',
|
|
60
|
+
component: resolve('@/pages/products/index.vue'),
|
|
61
|
+
chunkName: 'pages/products/catalog/level2'
|
|
62
|
+
}, {
|
|
63
|
+
name: 'products-taxonomy-catalog-level3',
|
|
64
|
+
path: '/c/:category2/:category1/:category',
|
|
65
|
+
component: resolve('@/pages/products/index.vue'),
|
|
66
|
+
chunkName: 'pages/products/catalog/level3'
|
|
67
|
+
}, {
|
|
68
|
+
name: 'products-taxonomy-catalog-level4',
|
|
69
|
+
path: '/c/:category3/:category2/:category1/:category',
|
|
70
|
+
component: resolve('@/pages/products/index.vue'),
|
|
71
|
+
chunkName: 'pages/products/catalog/level4'
|
|
72
|
+
}, {
|
|
73
|
+
name: 'products-taxonomy-catalog-level5',
|
|
74
|
+
path: '/c/:category4/:category3/:category2/:category1/:category',
|
|
75
|
+
component: resolve('@/pages/products/index.vue'),
|
|
76
|
+
chunkName: 'pages/products/catalog/level5'
|
|
77
|
+
}, {
|
|
3
78
|
name: 'order-view',
|
|
4
79
|
path: '/order/:token',
|
|
5
80
|
component: resolve('@lancom/shared/pages/order/_token/index.vue'),
|
package/store/products.js
CHANGED
|
@@ -10,6 +10,8 @@ export const state = () => ({
|
|
|
10
10
|
types: [],
|
|
11
11
|
colors: [],
|
|
12
12
|
brands: [],
|
|
13
|
+
category: null,
|
|
14
|
+
categories: [],
|
|
13
15
|
tags: [],
|
|
14
16
|
attributes: [],
|
|
15
17
|
loadError: null,
|
|
@@ -21,6 +23,8 @@ export const getters = {
|
|
|
21
23
|
types: ({ types }) => types || [],
|
|
22
24
|
colors: ({ colors }) => colors || [],
|
|
23
25
|
brands: ({ brands }) => brands || [],
|
|
26
|
+
categories: ({ categories }) => categories || [],
|
|
27
|
+
category: ({ category }) => category,
|
|
24
28
|
tags: ({ tags }) => tags || [],
|
|
25
29
|
attributes: ({ attributes }) => attributes || [],
|
|
26
30
|
page: ({ page }) => page,
|
|
@@ -46,7 +50,7 @@ export const actions = {
|
|
|
46
50
|
commit('setLoadError', null);
|
|
47
51
|
const {
|
|
48
52
|
products, count, page, perPage, productTypes, colors, brands, tags, attributes,
|
|
49
|
-
minPrice, maxPrice
|
|
53
|
+
categories, minPrice, maxPrice, category
|
|
50
54
|
} = await api.fetchProducts(shop, params);
|
|
51
55
|
commit('setProducts', products);
|
|
52
56
|
commit('setPage', page);
|
|
@@ -55,6 +59,8 @@ export const actions = {
|
|
|
55
59
|
commit('setTypes', productTypes);
|
|
56
60
|
commit('setColors', colors);
|
|
57
61
|
commit('setBrands', brands);
|
|
62
|
+
commit('setCategories', categories);
|
|
63
|
+
commit('setCategory', category);
|
|
58
64
|
commit('setTags', tags);
|
|
59
65
|
commit('setAttributes', attributes);
|
|
60
66
|
commit('setMinPrice', minPrice);
|
|
@@ -104,6 +110,12 @@ export const mutations = {
|
|
|
104
110
|
setBrands(state, brands) {
|
|
105
111
|
state.brands = brands;
|
|
106
112
|
},
|
|
113
|
+
setCategories(state, categories) {
|
|
114
|
+
state.categories = categories;
|
|
115
|
+
},
|
|
116
|
+
setCategory(state, category) {
|
|
117
|
+
state.category = category;
|
|
118
|
+
},
|
|
107
119
|
setTags(state, tags) {
|
|
108
120
|
state.tags = tags;
|
|
109
121
|
},
|