@lancom/shared 0.0.351 → 0.0.353
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/utils/colors.js +1 -1
- package/assets/js/utils/fonts-helper.js +2 -1
- package/assets/js/utils/product.js +10 -2
- package/components/editor/editor_layers/editor_layer_forms/editor_layer_form_text/editor-layer-form-text.vue +1 -1
- package/components/editor/editor_product_details/editor-product-details.scss +13 -0
- package/components/editor/editor_product_details/editor-product-details.vue +28 -1
- package/components/editor/editor_workspace/editor-workspace.scss +5 -1
- package/components/editor/editor_workspace/editor-workspace.vue +25 -14
- package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.scss +27 -4
- package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +68 -19
- package/components/product/gallery/gallery.scss +6 -0
- package/components/product/gallery/gallery.vue +29 -5
- package/components/product/layouts/product_main_info/product-main-info.vue +2 -2
- package/components/product/product.vue +17 -2
- package/components/product/product_color_image/product-color-image.vue +7 -2
- package/components/products/products_aside/products-aside.vue +15 -1
- package/components/products/products_link/products-link.vue +8 -0
- package/components/products/products_minimum_qty/products-minimum-qty.scss +39 -0
- package/components/products/products_minimum_qty/products-minimum-qty.vue +88 -0
- package/components/products/products_production_time/products-production-time.scss +39 -0
- package/components/products/products_production_time/products-production-time.vue +85 -0
- package/feeds/google-shopping.js +7 -1
- package/layouts/products.vue +5 -3
- package/middleware/page-info.js +16 -3
- package/mixins/product-view.js +10 -4
- package/package.json +1 -1
- package/plugins/cache-headers.js +6 -2
- package/store/product.js +10 -0
|
@@ -11,7 +11,7 @@ export const getColorImage = (product = {}, size = 'small', type, color, allowAn
|
|
|
11
11
|
const validImages = (product.images || []).filter(i => !excludeTypes.some(type => isValidImageType(i, type)));
|
|
12
12
|
const colorImage = color && validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (color?._id === i.color || color?._id === i.color?._id));
|
|
13
13
|
const image = colorImage || validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (allowAnyColor || !i.color));
|
|
14
|
-
return image && staticLink(image[size]);
|
|
14
|
+
return image && image[size] && staticLink(image[size]);
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export const getProductCover = (product = {}, size = 'small', type = COLORS_IMAGES_TYPES.FRONT, color, allowAnyColor = false) => {
|
|
@@ -65,7 +65,8 @@ export const getFormattedFont = alias => {
|
|
|
65
65
|
export const getFontPath = alias => {
|
|
66
66
|
const { PROD_CDN_URL, DEV_CDN_URL, CDN_DOMAIN } = Vue.$env;
|
|
67
67
|
const { path } = getFont(alias) || {};
|
|
68
|
-
|
|
68
|
+
const domain = process.env.IS_LOCAL ? '' : (CDN_DOMAIN || PROD_CDN_URL || DEV_CDN_URL || '');
|
|
69
|
+
return path && staticLink(`${domain}${path}`);
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
export const loadFont = alias => new Promise((resolve, reject) => {
|
|
@@ -110,10 +110,12 @@ function getCategoryPathFromRoute(routeParams) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export function generateProductsLink($route, data, skipParams = false) {
|
|
113
|
-
let { type, category, brand, text, sort } = data;
|
|
113
|
+
let { type, category, brand, text, sort, minimumQty, productionTime } = data;
|
|
114
114
|
category = (category || category === null) ? generateCategoryPath(category) : getCategoryPathFromRoute($route?.params);
|
|
115
115
|
type = (type || type === null) ? type : ($route?.params?.type || $route?.query?.type);
|
|
116
116
|
brand = (brand || brand === null) ? brand : ($route?.params?.brand || $route?.query?.brand);
|
|
117
|
+
minimumQty = (minimumQty || minimumQty === null) ? minimumQty : ($route?.params?.minimumQty || $route?.query?.minimumQty);
|
|
118
|
+
productionTime = (productionTime || productionTime === null) ? productionTime : ($route?.params?.productionTime || $route?.query?.productionTime);
|
|
117
119
|
text = (text || text === '') ? text : $route?.query?.text;
|
|
118
120
|
sort = (sort || sort === '') ? sort : $route?.query?.sort;
|
|
119
121
|
|
|
@@ -125,6 +127,12 @@ export function generateProductsLink($route, data, skipParams = false) {
|
|
|
125
127
|
if (sort) {
|
|
126
128
|
params.push(`sort=${sort}`);
|
|
127
129
|
}
|
|
130
|
+
if (minimumQty) {
|
|
131
|
+
params.push(`minimumQty=${minimumQty}`);
|
|
132
|
+
}
|
|
133
|
+
if (productionTime) {
|
|
134
|
+
params.push(`productionTime=${productionTime}`);
|
|
135
|
+
}
|
|
128
136
|
|
|
129
137
|
const { page = 1 } = data;
|
|
130
138
|
if (page > 1) {
|
|
@@ -132,7 +140,7 @@ export function generateProductsLink($route, data, skipParams = false) {
|
|
|
132
140
|
}
|
|
133
141
|
|
|
134
142
|
Object.keys({ ...(($route && $route.query) || {}), ...data }).forEach(key => {
|
|
135
|
-
const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand'];
|
|
143
|
+
const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand', 'minimumQty', 'productionTime'];
|
|
136
144
|
if (!rootKeys.includes(key)) {
|
|
137
145
|
let items = ($route && $route.query[key]) ? $route.query[key].split(',') : [];
|
|
138
146
|
items = (data[key] ? (items.includes(data[key]) ? items.filter(c => c !== data[key]) : [...items, data[key]]) : items).join(',');
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
margin-top: -10px;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
+
&__colors {
|
|
17
|
+
position: relative;
|
|
18
|
+
}
|
|
19
|
+
&__loader {
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
bottom: 0;
|
|
24
|
+
left: 0;
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
}
|
|
16
29
|
&__description {
|
|
17
30
|
margin-top: 15px;
|
|
18
31
|
color: rgb(118, 118, 118);
|
|
@@ -125,9 +125,19 @@
|
|
|
125
125
|
<i class="icon-rotate-tee"></i>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
128
|
+
<div class="EditorProductDetails__section">
|
|
129
|
+
<client-only>
|
|
130
|
+
<gallery
|
|
131
|
+
v-if="hasImages"
|
|
132
|
+
:show-big-image="false"
|
|
133
|
+
:image-types="['model']"
|
|
134
|
+
:show-filters="false" />
|
|
135
|
+
</client-only>
|
|
136
|
+
</div>
|
|
128
137
|
<div
|
|
129
138
|
v-if="productDetailsLoaded"
|
|
130
|
-
id="EditorProductDetails"
|
|
139
|
+
id="EditorProductDetails"
|
|
140
|
+
class="EditorProductDetails__colors">
|
|
131
141
|
<div class="EditorProductDetails__section">
|
|
132
142
|
<product-colors-selector
|
|
133
143
|
:has-another-print-btn="false"
|
|
@@ -136,6 +146,13 @@
|
|
|
136
146
|
<div class="EditorProductDetails__section">
|
|
137
147
|
<editor-pricing :has-cart-btn="false" />
|
|
138
148
|
</div>
|
|
149
|
+
<div
|
|
150
|
+
v-if="loadingProductDetails"
|
|
151
|
+
class="EditorProductDetails__loader">
|
|
152
|
+
<div>
|
|
153
|
+
<spinner />
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
139
156
|
</div>
|
|
140
157
|
</div>
|
|
141
158
|
</template>
|
|
@@ -153,10 +170,13 @@ import Price from '@lancom/shared/components/common/price';
|
|
|
153
170
|
import ProductSideWithPrint from '@lancom/shared/components/common/product_side_with_print/product-side-with-print';
|
|
154
171
|
import RichText from '@lancom/shared/components/common/rich-text';
|
|
155
172
|
import QuoteRequestModal from '@lancom/shared/components/quotes/quote_request_modal/quote-request-modal';
|
|
173
|
+
import Gallery from '@lancom/shared/components/product/gallery/gallery';
|
|
174
|
+
import { isValidImageType } from '@lancom/shared/assets/js/utils/colors';
|
|
156
175
|
|
|
157
176
|
export default {
|
|
158
177
|
name: 'EditorProductDetails',
|
|
159
178
|
components: {
|
|
179
|
+
Gallery,
|
|
160
180
|
ProductColorsSelector,
|
|
161
181
|
PricingDiscountsTable,
|
|
162
182
|
EditorPricing,
|
|
@@ -179,6 +199,7 @@ export default {
|
|
|
179
199
|
computed: {
|
|
180
200
|
...mapGetters(['taxName']),
|
|
181
201
|
...mapGetters('product', [
|
|
202
|
+
'loadingProductDetails',
|
|
182
203
|
'product',
|
|
183
204
|
'editableColor',
|
|
184
205
|
'images',
|
|
@@ -197,6 +218,12 @@ export default {
|
|
|
197
218
|
'pricingSettings',
|
|
198
219
|
'country'
|
|
199
220
|
]),
|
|
221
|
+
hasImages() {
|
|
222
|
+
return this.modelImages.length > 0;
|
|
223
|
+
},
|
|
224
|
+
modelImages() {
|
|
225
|
+
return this.images.filter(i => isValidImageType(i, 'model'));
|
|
226
|
+
},
|
|
200
227
|
previewPrintProduct() {
|
|
201
228
|
return {
|
|
202
229
|
...this.product,
|
|
@@ -84,9 +84,14 @@
|
|
|
84
84
|
</div>
|
|
85
85
|
<div
|
|
86
86
|
v-if="isZoomIn"
|
|
87
|
-
class="EditorWorkspace__zoom-out"
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
class="EditorWorkspace__zoom-out">
|
|
88
|
+
<btn
|
|
89
|
+
btn-class="green"
|
|
90
|
+
btn-label="Save"
|
|
91
|
+
@onclick="zoomOut()" />
|
|
92
|
+
<btn
|
|
93
|
+
btn-label="Cancel"
|
|
94
|
+
@onclick="zoomOut(true)" />
|
|
90
95
|
</div>
|
|
91
96
|
</div>
|
|
92
97
|
</client-only>
|
|
@@ -136,14 +141,7 @@ export default {
|
|
|
136
141
|
size: null,
|
|
137
142
|
fabricHelper: null,
|
|
138
143
|
preloading: true,
|
|
139
|
-
isRotating: false
|
|
140
|
-
productSides: [
|
|
141
|
-
{ label: 'Front', value: 'front' },
|
|
142
|
-
{ label: 'Back', value: 'back' }
|
|
143
|
-
// { label: 'Left sleeve', value: 'left_sleeve' },
|
|
144
|
-
// { label: 'Right sleeve', value: 'right_sleeve' },
|
|
145
|
-
// { label: 'Outside label', value: 'outside_label' }
|
|
146
|
-
]
|
|
144
|
+
isRotating: false
|
|
147
145
|
};
|
|
148
146
|
},
|
|
149
147
|
props: {
|
|
@@ -156,6 +154,7 @@ export default {
|
|
|
156
154
|
...mapGetters([
|
|
157
155
|
'product',
|
|
158
156
|
'selectedLayer',
|
|
157
|
+
'selectedLayerCopy',
|
|
159
158
|
'productDetailsLoaded',
|
|
160
159
|
'selectedPrintArea',
|
|
161
160
|
'editablePrintArea',
|
|
@@ -164,6 +163,14 @@ export default {
|
|
|
164
163
|
'editableLayers',
|
|
165
164
|
'editorSize'
|
|
166
165
|
]),
|
|
166
|
+
productSides() {
|
|
167
|
+
const sides = [
|
|
168
|
+
{ label: 'Front', value: 'front' },
|
|
169
|
+
{ label: 'Back', value: 'back' }
|
|
170
|
+
]
|
|
171
|
+
const validSides = sides.filter(s => this.product.printAreas?.some(pa => pa.side === s.value));
|
|
172
|
+
return validSides[0] ? validSides : [sides[0]];
|
|
173
|
+
},
|
|
167
174
|
sideZoomSize() {
|
|
168
175
|
return this.printAreaZoomSize?.width;
|
|
169
176
|
},
|
|
@@ -216,10 +223,14 @@ export default {
|
|
|
216
223
|
'setEditableSide',
|
|
217
224
|
'setSelectedPrintArea',
|
|
218
225
|
'setEditablePrintArea',
|
|
219
|
-
'setSelectedLayer'
|
|
226
|
+
'setSelectedLayer',
|
|
227
|
+
'updateTemplateLayer'
|
|
220
228
|
]),
|
|
221
|
-
zoomOut() {
|
|
222
|
-
|
|
229
|
+
zoomOut(isCancel) {
|
|
230
|
+
if (isCancel) {
|
|
231
|
+
this.updateTemplateLayer(this.selectedLayerCopy);
|
|
232
|
+
}
|
|
233
|
+
setTimeout(() => this.setSelectedLayer(null), 100);
|
|
223
234
|
},
|
|
224
235
|
onWorkspaceChange({ size, fabricHelper }) {
|
|
225
236
|
this.fabricHelper = fabricHelper;
|
|
@@ -6,14 +6,20 @@
|
|
|
6
6
|
transition: background-color 0.5s ease;
|
|
7
7
|
background-color: white;
|
|
8
8
|
}
|
|
9
|
-
&__toggle-
|
|
9
|
+
&__toggle-bg {
|
|
10
10
|
position: absolute;
|
|
11
|
+
top: 5px;
|
|
12
|
+
display: flex;
|
|
13
|
+
left: 0;
|
|
14
|
+
right: 0;
|
|
15
|
+
}
|
|
16
|
+
&__toggle-wireframe {
|
|
11
17
|
z-index: 1001;
|
|
12
18
|
background: gainsboro;
|
|
13
19
|
padding: 6px;
|
|
14
|
-
top: 5px;
|
|
15
20
|
font-size: 14px;
|
|
16
21
|
cursor: pointer;
|
|
22
|
+
margin-right: 5px;
|
|
17
23
|
&--active {
|
|
18
24
|
background: rgb(196, 226, 236);
|
|
19
25
|
}
|
|
@@ -88,6 +94,9 @@
|
|
|
88
94
|
display: inline;
|
|
89
95
|
}
|
|
90
96
|
}
|
|
97
|
+
&:last-child {
|
|
98
|
+
display: none;
|
|
99
|
+
}
|
|
91
100
|
}
|
|
92
101
|
&-divider {
|
|
93
102
|
color: $grey_1;
|
|
@@ -110,10 +119,17 @@
|
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
&.side {
|
|
113
|
-
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: row;
|
|
114
124
|
.EditorWorkspaceSide__placeholder-option span {
|
|
115
125
|
&:last-child {
|
|
116
|
-
display: inline;
|
|
126
|
+
display: inline-block;
|
|
127
|
+
background-color: $purple;
|
|
128
|
+
color: $white;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
padding: 4px;
|
|
131
|
+
margin: 1px;
|
|
132
|
+
font-size: 15px;
|
|
117
133
|
}
|
|
118
134
|
&:first-child {
|
|
119
135
|
display: none;
|
|
@@ -122,6 +138,13 @@
|
|
|
122
138
|
.EditorWorkspaceSide__placeholder-divider {
|
|
123
139
|
display: none;
|
|
124
140
|
}
|
|
141
|
+
.EditorWorkspaceSide__placeholder-option--edit-btn {
|
|
142
|
+
display: flex;
|
|
143
|
+
span {
|
|
144
|
+
margin-top: -0.5px !important;
|
|
145
|
+
display: inline-block !important;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
150
|
&__alert {
|
|
@@ -7,15 +7,29 @@
|
|
|
7
7
|
}"
|
|
8
8
|
v-click-outside="onOutsideClick"
|
|
9
9
|
@mouseover="toogleBoundBox(true)"
|
|
10
|
-
@mouseleave="toogleBoundBox(false)"
|
|
10
|
+
@mouseleave="toogleBoundBox(false)"
|
|
11
|
+
@mouseup="toggleOnpressImage(false)">
|
|
11
12
|
<div
|
|
12
|
-
v-if="hasWireframeImage"
|
|
13
|
-
class="EditorWorkspaceSide__toggle-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
v-if="hasOnpressImage || hasWireframeImage"
|
|
14
|
+
class="EditorWorkspaceSide__toggle-bg">
|
|
15
|
+
<div
|
|
16
|
+
v-if="hasWireframeImage"
|
|
17
|
+
class="EditorWorkspaceSide__toggle-wireframe"
|
|
18
|
+
:class="{
|
|
19
|
+
'EditorWorkspaceSide__toggle-wireframe--active': visibleWireframe
|
|
20
|
+
}"
|
|
21
|
+
@click.stop.prevent="toggleWireframeImage">
|
|
22
|
+
{{ visibleWireframe ? 'Hide Wireframe' : 'Show Wireframe' }}
|
|
23
|
+
</div>
|
|
24
|
+
<div
|
|
25
|
+
v-if="hasOnpressImage"
|
|
26
|
+
class="EditorWorkspaceSide__toggle-wireframe"
|
|
27
|
+
:class="{
|
|
28
|
+
'EditorWorkspaceSide__toggle-wireframe--active': visibleOnpress
|
|
29
|
+
}"
|
|
30
|
+
@mousedown.stop.prevent="toggleOnpressImage(true)">
|
|
31
|
+
{{ visibleOnpress ? 'Hide On Press' : 'Show On Press' }}
|
|
32
|
+
</div>
|
|
19
33
|
</div>
|
|
20
34
|
<div
|
|
21
35
|
v-if="deleteButtonPos"
|
|
@@ -33,7 +47,7 @@
|
|
|
33
47
|
</div>
|
|
34
48
|
</div>
|
|
35
49
|
<div
|
|
36
|
-
v-if="fabricHelper"
|
|
50
|
+
v-if="fabricHelper && !editModeSelectedLayer"
|
|
37
51
|
class="EditorWorkspaceSide__placeholder"
|
|
38
52
|
:class="{
|
|
39
53
|
tighten: !isZoomed && printAreaIsSmall,
|
|
@@ -45,7 +59,7 @@
|
|
|
45
59
|
@click="createTextLayer"
|
|
46
60
|
class="EditorWorkspaceSide__placeholder-option">
|
|
47
61
|
<span>Type here</span>
|
|
48
|
-
<span>
|
|
62
|
+
<span style="margin-top: -0.5px;">Text</span>
|
|
49
63
|
</div>
|
|
50
64
|
<div class="EditorWorkspaceSide__placeholder-divider">
|
|
51
65
|
or
|
|
@@ -61,7 +75,7 @@
|
|
|
61
75
|
<i class="icon-picture"></i> Add art
|
|
62
76
|
</span>
|
|
63
77
|
<span>
|
|
64
|
-
|
|
78
|
+
Art
|
|
65
79
|
</span>
|
|
66
80
|
</div>
|
|
67
81
|
</template>
|
|
@@ -77,6 +91,12 @@
|
|
|
77
91
|
</template>
|
|
78
92
|
</file-uploader>
|
|
79
93
|
</div>
|
|
94
|
+
<!-- <div
|
|
95
|
+
v-if="editablePrintAreaLayers"
|
|
96
|
+
@click="editFirstLayer"
|
|
97
|
+
class="EditorWorkspaceSide__placeholder-option EditorWorkspaceSide__placeholder-option--edit-btn">
|
|
98
|
+
<span>Edit</span>
|
|
99
|
+
</div> -->
|
|
80
100
|
</div>
|
|
81
101
|
<div
|
|
82
102
|
v-show="isVisibleOverlay"
|
|
@@ -143,6 +163,7 @@ export default {
|
|
|
143
163
|
return {
|
|
144
164
|
addedFromCanvas: false,
|
|
145
165
|
visibleWireframe: false,
|
|
166
|
+
visibleOnpress: false,
|
|
146
167
|
fabricHelper: null,
|
|
147
168
|
redrawWithThrottle: throttle(this.redraw, 100, { trailing: false }),
|
|
148
169
|
saveLayersAsImageWithDebounce: debounce(this.saveLayersAsImage, 400),
|
|
@@ -165,6 +186,7 @@ export default {
|
|
|
165
186
|
'editableColor',
|
|
166
187
|
'editableLayers',
|
|
167
188
|
'selectedLayer',
|
|
189
|
+
'selectedLayerCopy',
|
|
168
190
|
'editorSize',
|
|
169
191
|
'editModeSelectedLayer'
|
|
170
192
|
]),
|
|
@@ -174,12 +196,22 @@ export default {
|
|
|
174
196
|
hasWireframeImage() {
|
|
175
197
|
return !!this.wireframeImage;
|
|
176
198
|
},
|
|
199
|
+
hasOnpressImage() {
|
|
200
|
+
return !!this.onpressImage;
|
|
201
|
+
},
|
|
177
202
|
wireframeImage() {
|
|
178
203
|
return this.images.find(i => i.types?.includes('wireframe') && i.types?.includes(this.side));
|
|
179
204
|
},
|
|
205
|
+
onpressImage() {
|
|
206
|
+
console.log('this.images: ', this.images);
|
|
207
|
+
return this.product.images?.find(i => i.types?.includes('on_press'));
|
|
208
|
+
},
|
|
180
209
|
sideEditableLayers() {
|
|
181
210
|
return this.editableLayers.filter(l => l.sideId === this.side);
|
|
182
211
|
},
|
|
212
|
+
editablePrintAreaLayers() {
|
|
213
|
+
return this.printAreaLayers.some(l => l.editableDetails);
|
|
214
|
+
},
|
|
183
215
|
printAreaLayers() {
|
|
184
216
|
const paId = this.printArea?.parentPrintArea || this.printArea?._id;
|
|
185
217
|
const layers = this.editableLayers.filter(l => !paId || (l.printArea === paId));
|
|
@@ -196,8 +228,8 @@ export default {
|
|
|
196
228
|
// console.log('this.fabricHelper.printAreaRect: ', this.fabricHelper.printAreaRect);
|
|
197
229
|
const ratio = this.calcWorkspaceSize() / this.editorSize.width;
|
|
198
230
|
return this.printAreaLayers.length > 0 ? {
|
|
199
|
-
top: `${top * ratio}px`,
|
|
200
|
-
left: `${
|
|
231
|
+
top: `${(top + height) * ratio}px`,
|
|
232
|
+
left: `${left * ratio}px`
|
|
201
233
|
} : {
|
|
202
234
|
top: `${top * ratio}px`,
|
|
203
235
|
left: `${left * ratio}px`,
|
|
@@ -241,6 +273,9 @@ export default {
|
|
|
241
273
|
visibleWireframe() {
|
|
242
274
|
this.updateBackgroundImage();
|
|
243
275
|
},
|
|
276
|
+
visibleOnpress() {
|
|
277
|
+
this.updateBackgroundImage();
|
|
278
|
+
},
|
|
244
279
|
layers() {
|
|
245
280
|
this.redrawWithThrottle();
|
|
246
281
|
},
|
|
@@ -275,18 +310,29 @@ export default {
|
|
|
275
310
|
...mapActions('product', ['createLayer']),
|
|
276
311
|
...mapMutations('product', [
|
|
277
312
|
'setSelectedLayerField',
|
|
313
|
+
'setEditModeSelectedLayer',
|
|
278
314
|
'setSelectedLayer',
|
|
279
315
|
'removeTemplateLayer'
|
|
280
316
|
]),
|
|
281
317
|
...mapMutations('layers', [
|
|
282
318
|
'setLayersThumbnail'
|
|
283
319
|
]),
|
|
320
|
+
editFirstLayer() {
|
|
321
|
+
const layer = this.printAreaLayers.find(l => l.editableDetails);
|
|
322
|
+
if (layer?.editableDetails) {
|
|
323
|
+
this.setSelectedLayer(layer);
|
|
324
|
+
this.setEditModeSelectedLayer(true);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
284
327
|
checkVisibleWireframe() {
|
|
285
328
|
this.visibleWireframe = this.sideEditableLayers.length > 0;
|
|
286
329
|
},
|
|
287
330
|
toggleWireframeImage() {
|
|
288
331
|
this.visibleWireframe = !this.visibleWireframe;
|
|
289
332
|
},
|
|
333
|
+
toggleOnpressImage(state) {
|
|
334
|
+
this.visibleOnpress = state;
|
|
335
|
+
},
|
|
290
336
|
adjustSelectedArtDPI() {
|
|
291
337
|
const measure = this.fabricHelper.getEditorDPI();
|
|
292
338
|
const scale = measure / 75;
|
|
@@ -385,19 +431,22 @@ export default {
|
|
|
385
431
|
const isMedium = this.calcWorkspaceSize(true) <= 400;
|
|
386
432
|
const type = this.side === 'front' ? COLORS_IMAGES_TYPES.FRONT : COLORS_IMAGES_TYPES.BACK;
|
|
387
433
|
const wireframeImageUrl = this.visibleWireframe && (isMedium ? this.wireframeImage?.medium : this.wireframeImage?.large);
|
|
434
|
+
const onpressImageUrl = this.visibleOnpress && (isMedium ? this.onpressImage?.medium : this.onpressImage?.large);
|
|
388
435
|
const getter = isMedium ? getMediumColorImage : getLargeColorImage;
|
|
389
|
-
const imageUrl = wireframeImageUrl || getter(this.product, type, this.editableColor, false, ['wireframe']);
|
|
436
|
+
const imageUrl = wireframeImageUrl || onpressImageUrl || getter(this.product, type, this.editableColor, false, ['wireframe']);
|
|
390
437
|
if (this.backgroundImage && this.backgroundImageUrl === imageUrl) {
|
|
391
438
|
return this.fabricHelper.setBackgroundImage(this.backgroundImage);
|
|
392
439
|
}
|
|
393
440
|
|
|
394
441
|
this.backgroundImageLoaded = false;
|
|
442
|
+
this.backgroundImageUrl = imageUrl;
|
|
395
443
|
fabric.Image.fromURL(imageUrl, image => {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
444
|
+
if (this.backgroundImageUrl === imageUrl) {
|
|
445
|
+
image.scale(this.editorSize.width / image.width);
|
|
446
|
+
this.backgroundImage = image;
|
|
447
|
+
this.backgroundImageLoaded = true;
|
|
448
|
+
this.fabricHelper.setBackgroundImage(this.backgroundImage);
|
|
449
|
+
}
|
|
401
450
|
});
|
|
402
451
|
},
|
|
403
452
|
removeSelected() {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="Gallery__wrapper">
|
|
3
|
-
<div
|
|
3
|
+
<div
|
|
4
|
+
v-if="showBigImage"
|
|
5
|
+
class="Gallery__big">
|
|
4
6
|
<div class="Gallery__big-image">
|
|
5
7
|
<img
|
|
6
8
|
:src="staticLink(currentImage.large)"
|
|
@@ -40,7 +42,10 @@
|
|
|
40
42
|
</div>
|
|
41
43
|
<div
|
|
42
44
|
v-if="isZooming"
|
|
43
|
-
class="Gallery__zoom-image"
|
|
45
|
+
class="Gallery__zoom-image"
|
|
46
|
+
:class="{
|
|
47
|
+
preview: !showBigImage
|
|
48
|
+
}">
|
|
44
49
|
<img
|
|
45
50
|
:src="staticLink(currentImage.large)"
|
|
46
51
|
:style="zoomImageStyles"
|
|
@@ -54,7 +59,9 @@
|
|
|
54
59
|
:class="{
|
|
55
60
|
'Gallery__small-image--active': (visibleImagesFrom + index) === activeIndex
|
|
56
61
|
}"
|
|
57
|
-
@click="goToSlideAndChangeColor(visibleImagesFrom + index)"
|
|
62
|
+
@click="goToSlideAndChangeColor(visibleImagesFrom + index)"
|
|
63
|
+
@mouseenter="startPreview(visibleImagesFrom + index)"
|
|
64
|
+
@mouseleave="stopZoom">
|
|
58
65
|
<img
|
|
59
66
|
:src="staticLink(image.small)"
|
|
60
67
|
:alt="product.name"
|
|
@@ -81,7 +88,7 @@
|
|
|
81
88
|
</button>
|
|
82
89
|
</div>
|
|
83
90
|
<div
|
|
84
|
-
v-if="hasValidFilters"
|
|
91
|
+
v-if="showFilters && hasValidFilters"
|
|
85
92
|
class="Gallery__filters">
|
|
86
93
|
<div
|
|
87
94
|
v-for="filter in validFilters"
|
|
@@ -116,6 +123,17 @@ export default {
|
|
|
116
123
|
slidesPerRow: {
|
|
117
124
|
type: Number,
|
|
118
125
|
default: 6
|
|
126
|
+
},
|
|
127
|
+
showBigImage: {
|
|
128
|
+
type: Boolean,
|
|
129
|
+
default: true
|
|
130
|
+
},
|
|
131
|
+
showFilters: {
|
|
132
|
+
type: Boolean,
|
|
133
|
+
default: true
|
|
134
|
+
},
|
|
135
|
+
imageTypes: {
|
|
136
|
+
type: Array
|
|
119
137
|
}
|
|
120
138
|
},
|
|
121
139
|
data() {
|
|
@@ -149,7 +167,7 @@ export default {
|
|
|
149
167
|
return this.validFilters.length > 1;
|
|
150
168
|
},
|
|
151
169
|
galleryImages() {
|
|
152
|
-
return this.images.filter(i => !(i.types || []).includes('designer'));
|
|
170
|
+
return this.imageTypes ? this.images.filter(i => isValidImageTypes(i, this.imageTypes)) : this.images.filter(i => !(i.types || []).includes('designer'));
|
|
153
171
|
},
|
|
154
172
|
validFilters() {
|
|
155
173
|
return this.filters.filter(filter => (this.galleryImages || []).some(i => this.isValidImageByFilter(filter, i)));
|
|
@@ -199,6 +217,12 @@ export default {
|
|
|
199
217
|
methods: {
|
|
200
218
|
...mapActions('product', ['selectColor']),
|
|
201
219
|
staticLink,
|
|
220
|
+
startPreview(index) {
|
|
221
|
+
if (!this.isZoomin) {
|
|
222
|
+
this.startZoom();
|
|
223
|
+
this.goToSlideAndChangeColor(index);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
202
226
|
startZoom() {
|
|
203
227
|
this.isZooming = true;
|
|
204
228
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
class="lc_regular16 ProductMainInfo__description-body"
|
|
30
30
|
v-html="product.description || '—'">
|
|
31
31
|
</p>
|
|
32
|
-
<div class="ProductMainInfo__additional-info">
|
|
32
|
+
<!-- <div class="ProductMainInfo__additional-info">
|
|
33
33
|
<span class="lc_medium16">
|
|
34
34
|
•
|
|
35
35
|
Shoulder to shoulder tape
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</template>
|
|
54
54
|
</v-popover>
|
|
55
|
-
</div>
|
|
55
|
+
</div> -->
|
|
56
56
|
</div>
|
|
57
57
|
</div>
|
|
58
58
|
</template>
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
<section class="Product__section">
|
|
19
19
|
<product-main-info :product="product" />
|
|
20
20
|
</section>
|
|
21
|
+
<section class="Product__section">
|
|
22
|
+
<btn
|
|
23
|
+
:btn-block="true"
|
|
24
|
+
:to="goToEditorLink"
|
|
25
|
+
btn-class="purple"
|
|
26
|
+
btn-label="DESIGN AND ORDER">
|
|
27
|
+
<i
|
|
28
|
+
slot="icon-after"
|
|
29
|
+
class="icon-arrow-right"></i>
|
|
30
|
+
</btn>
|
|
31
|
+
</section>
|
|
21
32
|
<section class="Product__section">
|
|
22
33
|
<wizard-editor />
|
|
23
34
|
</section>
|
|
@@ -44,6 +55,9 @@
|
|
|
44
55
|
|
|
45
56
|
<script>
|
|
46
57
|
import { mapGetters } from 'vuex';
|
|
58
|
+
import RelatedProducts from '@lancom/shared/components/product/related_products/related-products';
|
|
59
|
+
import Gallery from '@lancom/shared/components/product/gallery/gallery';
|
|
60
|
+
import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
|
|
47
61
|
import ProductMainInfo from './layouts/product_main_info/product-main-info';
|
|
48
62
|
import ProductColorsSelector from './product_colors_selector/product-colors-selector';
|
|
49
63
|
import ProductTierPrices from './layouts/product_tier_prices/product-tier-prices';
|
|
@@ -51,8 +65,6 @@ import ProductFabricAndSizeInfo from './layouts/product_fabric_and_size_info/pro
|
|
|
51
65
|
import ProductPackaging from './layouts/product_packaging/product-packaging';
|
|
52
66
|
import ProductModelMeasurements from './layouts/product_model_measurements/product-model-measurements';
|
|
53
67
|
import ProductSizeTable from './layouts/product_size_table/product-size-table';
|
|
54
|
-
import RelatedProducts from '@lancom/shared/components/product/related_products/related-products';
|
|
55
|
-
import Gallery from '@lancom/shared/components/product/gallery/gallery';
|
|
56
68
|
import WizardEditor from './wizard-editor/wizard-editor.vue';
|
|
57
69
|
|
|
58
70
|
|
|
@@ -80,6 +92,9 @@ export default {
|
|
|
80
92
|
'productDetails',
|
|
81
93
|
'editableColor'
|
|
82
94
|
]),
|
|
95
|
+
goToEditorLink() {
|
|
96
|
+
return generateProductLink(this.product, null, true);
|
|
97
|
+
},
|
|
83
98
|
hasImages() {
|
|
84
99
|
return this.images.length > 0;
|
|
85
100
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<div
|
|
7
7
|
v-if="hasAdditionalColors"
|
|
8
8
|
:style="{
|
|
9
|
-
'top': additionalColorHeight,
|
|
9
|
+
'top': hasMainColor ? additionalColorHeight : 0,
|
|
10
10
|
width: '100%',
|
|
11
11
|
height: '100%',
|
|
12
12
|
position: 'relative'
|
|
@@ -41,6 +41,9 @@ export default {
|
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
computed: {
|
|
44
|
+
hasMainColor() {
|
|
45
|
+
return (!!this.color?.pattern || !!this.color?.rgb);
|
|
46
|
+
},
|
|
44
47
|
pattern() {
|
|
45
48
|
return this.color.pattern && staticLink(this.color.pattern);
|
|
46
49
|
},
|
|
@@ -54,7 +57,9 @@ export default {
|
|
|
54
57
|
return (this.color.additionalColors || []);
|
|
55
58
|
},
|
|
56
59
|
additionalColorHeight() {
|
|
57
|
-
|
|
60
|
+
const color = this.hasMainColor ? 1 : 0;
|
|
61
|
+
const height = 100 / (this.additionalColors.length + color);
|
|
62
|
+
return `${height}%`;
|
|
58
63
|
},
|
|
59
64
|
additionalColorsThumbs() {
|
|
60
65
|
return (this.color.additionalColors || [])
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
:has-selected-icon="hasSelectedIcon"
|
|
24
24
|
:selected-icon-circle="selectedIconCircle" />
|
|
25
25
|
</div>
|
|
26
|
+
<div class="ProductsAside__item">
|
|
27
|
+
<products-production-time
|
|
28
|
+
:has-selected-icon="hasSelectedIcon"
|
|
29
|
+
:selected-icon-circle="selectedIconCircle" />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="ProductsAside__item">
|
|
32
|
+
<products-minimum-qty
|
|
33
|
+
:has-selected-icon="hasSelectedIcon"
|
|
34
|
+
:selected-icon-circle="selectedIconCircle" />
|
|
35
|
+
</div>
|
|
26
36
|
<div class="ProductsAside__item">
|
|
27
37
|
<products-colors />
|
|
28
38
|
</div>
|
|
@@ -44,6 +54,8 @@ import ProductsBrands from '@lancom/shared/components/products/products_brands/p
|
|
|
44
54
|
import ProductsTags from '@lancom/shared/components/products/products_tags/products-tags';
|
|
45
55
|
import ProductsAttributes from '@lancom/shared/components/products/products_attributes/products-attributes';
|
|
46
56
|
import ProductsColors from '@lancom/shared/components/products/products_colors/products-colors';
|
|
57
|
+
import ProductsProductionTime from '@lancom/shared/components/products/products_production_time/products-production-time';
|
|
58
|
+
import ProductsMinimumQty from '@lancom/shared/components/products/products_minimum_qty/products-minimum-qty';
|
|
47
59
|
import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
|
|
48
60
|
|
|
49
61
|
export default {
|
|
@@ -53,7 +65,9 @@ export default {
|
|
|
53
65
|
ProductsBrands,
|
|
54
66
|
ProductsTags,
|
|
55
67
|
ProductsAttributes,
|
|
56
|
-
ProductsColors
|
|
68
|
+
ProductsColors,
|
|
69
|
+
ProductsProductionTime,
|
|
70
|
+
ProductsMinimumQty
|
|
57
71
|
},
|
|
58
72
|
props: {
|
|
59
73
|
hasSelectedIcon: {
|
|
@@ -27,6 +27,12 @@ export default {
|
|
|
27
27
|
color: {
|
|
28
28
|
type: String
|
|
29
29
|
},
|
|
30
|
+
productionTime: {
|
|
31
|
+
type: String
|
|
32
|
+
},
|
|
33
|
+
minimumQty: {
|
|
34
|
+
type: String
|
|
35
|
+
},
|
|
30
36
|
attributes: {
|
|
31
37
|
type: Object
|
|
32
38
|
},
|
|
@@ -45,6 +51,8 @@ export default {
|
|
|
45
51
|
brand: this.brand,
|
|
46
52
|
tags: this.tags,
|
|
47
53
|
colors: this.color,
|
|
54
|
+
productionTime: this.productionTime,
|
|
55
|
+
minimumQty: this.minimumQty,
|
|
48
56
|
option: this.option,
|
|
49
57
|
text: this.text,
|
|
50
58
|
sort: this.sort,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@import "@/assets/scss/variables";
|
|
2
|
+
|
|
3
|
+
.ProductsMinimumQty {
|
|
4
|
+
&__header {
|
|
5
|
+
font-size: 22px;
|
|
6
|
+
line-height: 20px;
|
|
7
|
+
font-weight: 600;
|
|
8
|
+
margin-top: 3px;
|
|
9
|
+
margin-bottom: 24px;
|
|
10
|
+
color: $black;
|
|
11
|
+
}
|
|
12
|
+
&__item {
|
|
13
|
+
margin: 13px 0;
|
|
14
|
+
font-size: 15px;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
i {
|
|
17
|
+
font-size: 20px;
|
|
18
|
+
margin-right: 10px;
|
|
19
|
+
}
|
|
20
|
+
a {
|
|
21
|
+
color: $grey_1;
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
}
|
|
27
|
+
&--active a {
|
|
28
|
+
color: $green;
|
|
29
|
+
}
|
|
30
|
+
&-icon {
|
|
31
|
+
margin-right: 10px;
|
|
32
|
+
background-position: center;
|
|
33
|
+
background-repeat: no-repeat;
|
|
34
|
+
background-size: contain;
|
|
35
|
+
width: 20px;
|
|
36
|
+
height: 20px;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ProductsMinimumQty__wrapper">
|
|
3
|
+
<toggle-content label="Minimum Qty">
|
|
4
|
+
<div
|
|
5
|
+
v-for="minimumQtyRange in minimumQtyRanges"
|
|
6
|
+
:key="minimumQtyRange.alias"
|
|
7
|
+
class="ProductsMinimumQty__item"
|
|
8
|
+
:class="{
|
|
9
|
+
'ProductsMinimumQty__item--active': isSelectedMinimumQty(minimumQtyRange)
|
|
10
|
+
}">
|
|
11
|
+
<products-link
|
|
12
|
+
class="ProductsMinimumQty__more"
|
|
13
|
+
:minimum-qty="isSelectedMinimumQty(minimumQtyRange) ? null : minimumQtyRange.alias">
|
|
14
|
+
<span>
|
|
15
|
+
{{ minimumQtyRange.name }}
|
|
16
|
+
</span>
|
|
17
|
+
<checked-icon
|
|
18
|
+
v-if="hasSelectedIcon"
|
|
19
|
+
:checked="isSelectedMinimumQty(minimumQtyRange)"
|
|
20
|
+
:circle="selectedIconCircle" />
|
|
21
|
+
</products-link>
|
|
22
|
+
</div>
|
|
23
|
+
</toggle-content>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import ToggleContent from '@lancom/shared/components/common/toggle-content';
|
|
29
|
+
import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
|
|
30
|
+
import CheckedIcon from '@lancom/shared/components/common/checked-icon';
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
name: 'ProductsMinimumQty',
|
|
34
|
+
components: {
|
|
35
|
+
ProductsLink,
|
|
36
|
+
ToggleContent,
|
|
37
|
+
CheckedIcon
|
|
38
|
+
},
|
|
39
|
+
props: {
|
|
40
|
+
hasSelectedIcon: {
|
|
41
|
+
type: Boolean,
|
|
42
|
+
default: false
|
|
43
|
+
},
|
|
44
|
+
selectedIconCircle: {
|
|
45
|
+
type: Boolean,
|
|
46
|
+
default: false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
data() {
|
|
50
|
+
return {
|
|
51
|
+
minimumQtyRanges: [{
|
|
52
|
+
alias: 'no-minimums',
|
|
53
|
+
name: 'No minimums'
|
|
54
|
+
}, {
|
|
55
|
+
alias: '6-or-less',
|
|
56
|
+
name: '6 or less'
|
|
57
|
+
}, {
|
|
58
|
+
alias: '12-or-less',
|
|
59
|
+
name: '12 or less'
|
|
60
|
+
}, {
|
|
61
|
+
alias: '25-or-less',
|
|
62
|
+
name: '25 or less'
|
|
63
|
+
}, {
|
|
64
|
+
alias: '50-or-less',
|
|
65
|
+
name: '50 or less'
|
|
66
|
+
}, {
|
|
67
|
+
alias: '100-or-less',
|
|
68
|
+
name: '100 or less'
|
|
69
|
+
}]
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
computed: {
|
|
73
|
+
selectedMinimumQties() {
|
|
74
|
+
const minimumQty = (this.$route.query.minimumQty || '').split(',');
|
|
75
|
+
return this.minimumQtyRanges.filter(minimumQtyRange => minimumQty.includes(minimumQtyRange.alias));
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
methods: {
|
|
79
|
+
isSelectedMinimumQty(minimumQty) {
|
|
80
|
+
return this.selectedMinimumQties.some(mq => mq.alias === minimumQty.alias);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<style lang="scss" scoped>
|
|
87
|
+
@import 'products-minimum-qty';
|
|
88
|
+
</style>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
@import "@/assets/scss/variables";
|
|
2
|
+
|
|
3
|
+
.ProductsProductionTime {
|
|
4
|
+
&__header {
|
|
5
|
+
font-size: 22px;
|
|
6
|
+
line-height: 20px;
|
|
7
|
+
font-weight: 600;
|
|
8
|
+
margin-top: 3px;
|
|
9
|
+
margin-bottom: 24px;
|
|
10
|
+
color: $black;
|
|
11
|
+
}
|
|
12
|
+
&__item {
|
|
13
|
+
margin: 13px 0;
|
|
14
|
+
font-size: 15px;
|
|
15
|
+
font-weight: 400;
|
|
16
|
+
i {
|
|
17
|
+
font-size: 20px;
|
|
18
|
+
margin-right: 10px;
|
|
19
|
+
}
|
|
20
|
+
a {
|
|
21
|
+
color: $grey_1;
|
|
22
|
+
text-decoration: none;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: space-between;
|
|
26
|
+
}
|
|
27
|
+
&--active a {
|
|
28
|
+
color: $green;
|
|
29
|
+
}
|
|
30
|
+
&-icon {
|
|
31
|
+
margin-right: 10px;
|
|
32
|
+
background-position: center;
|
|
33
|
+
background-repeat: no-repeat;
|
|
34
|
+
background-size: contain;
|
|
35
|
+
width: 20px;
|
|
36
|
+
height: 20px;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ProductsProductionTime__wrapper">
|
|
3
|
+
<toggle-content label="Production Time">
|
|
4
|
+
<div
|
|
5
|
+
v-for="produnctionRange in produnctionTimesRanges"
|
|
6
|
+
:key="produnctionRange.alias"
|
|
7
|
+
class="ProductsProductionTime__item"
|
|
8
|
+
:class="{
|
|
9
|
+
'ProductsProductionTime__item--active': isSelectedProdunctionTime(produnctionRange)
|
|
10
|
+
}">
|
|
11
|
+
<products-link
|
|
12
|
+
class="ProductsProductionTime__more"
|
|
13
|
+
:production-time="isSelectedProdunctionTime(produnctionRange) ? null : produnctionRange.alias">
|
|
14
|
+
<span>
|
|
15
|
+
{{ produnctionRange.name }}
|
|
16
|
+
</span>
|
|
17
|
+
<checked-icon
|
|
18
|
+
v-if="hasSelectedIcon"
|
|
19
|
+
:checked="isSelectedProdunctionTime(produnctionRange)"
|
|
20
|
+
:circle="selectedIconCircle" />
|
|
21
|
+
</products-link>
|
|
22
|
+
</div>
|
|
23
|
+
</toggle-content>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script>
|
|
28
|
+
import ToggleContent from '@lancom/shared/components/common/toggle-content';
|
|
29
|
+
import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
|
|
30
|
+
import CheckedIcon from '@lancom/shared/components/common/checked-icon';
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
name: 'ProductsProductionTime',
|
|
34
|
+
components: {
|
|
35
|
+
ProductsLink,
|
|
36
|
+
ToggleContent,
|
|
37
|
+
CheckedIcon
|
|
38
|
+
},
|
|
39
|
+
props: {
|
|
40
|
+
hasSelectedIcon: {
|
|
41
|
+
type: Boolean,
|
|
42
|
+
default: false
|
|
43
|
+
},
|
|
44
|
+
selectedIconCircle: {
|
|
45
|
+
type: Boolean,
|
|
46
|
+
default: false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
data() {
|
|
50
|
+
return {
|
|
51
|
+
produnctionTimesRanges: [{
|
|
52
|
+
alias: 'same-day',
|
|
53
|
+
name: 'Same day'
|
|
54
|
+
}, {
|
|
55
|
+
alias: '3-days',
|
|
56
|
+
name: 'Rush 3 day'
|
|
57
|
+
}, {
|
|
58
|
+
alias: '10-days',
|
|
59
|
+
name: '10 days'
|
|
60
|
+
}, {
|
|
61
|
+
alias: '2-weeks',
|
|
62
|
+
name: '2 weeks'
|
|
63
|
+
}, {
|
|
64
|
+
alias: '4-weeks',
|
|
65
|
+
name: '4 or more weeks'
|
|
66
|
+
}]
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
computed: {
|
|
70
|
+
selectedProdunctionTimes() {
|
|
71
|
+
const productionTime = (this.$route.query.productionTime || '').split(',');
|
|
72
|
+
return this.produnctionTimesRanges.filter(produnctionTimesRange => productionTime.includes(produnctionTimesRange.alias));
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
methods: {
|
|
76
|
+
isSelectedProdunctionTime(produnctionTime) {
|
|
77
|
+
return this.selectedProdunctionTimes.some(pt => pt.alias === produnctionTime.alias);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<style lang="scss" scoped>
|
|
84
|
+
@import 'products-production-time';
|
|
85
|
+
</style>
|
package/feeds/google-shopping.js
CHANGED
|
@@ -59,8 +59,9 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
|
|
|
59
59
|
.replace(/·/, '·');
|
|
60
60
|
|
|
61
61
|
let link = `https://${config.HOST_NAME}${generateProductLink(product, sp.color, isEditor)}`;
|
|
62
|
+
link = link.includes('?') ? `${link}&price=IT` : `${link}?price=IT`
|
|
62
63
|
if (sp.multipackQty) {
|
|
63
|
-
link = `${link}&multipack=${sp.SKU}`
|
|
64
|
+
link = link.includes('?') ? `${link}&multipack=${sp.SKU}` : `${link}?multipack=${sp.SKU}`;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const productWeight = +((product.weight || 0) * (sp.multipackQty || 1)).toFixed(3);
|
|
@@ -72,6 +73,7 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
|
|
|
72
73
|
'g:item_group_id': { _text: product.SKU },
|
|
73
74
|
'g:size': { _text: sp.size.name },
|
|
74
75
|
'g:size_system': COUNTRIES_SIZE_SYSTEMS[country] || country || 'AU',
|
|
76
|
+
'g:ships_from_country': country || 'AU',
|
|
75
77
|
'g:size_type': 'regular',
|
|
76
78
|
'g:gender': { _text: product.gender },
|
|
77
79
|
'g:material': { _text: product.fabricInfoShort },
|
|
@@ -119,6 +121,10 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
|
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
if (product.country) {
|
|
125
|
+
info['g:shipping_label'] = product.country;
|
|
126
|
+
}
|
|
127
|
+
|
|
122
128
|
const productHighlight = `${product.feedProductHighlight || ''}`
|
|
123
129
|
.trim()
|
|
124
130
|
.split(/\n+/g)
|
package/layouts/products.vue
CHANGED
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
import gtm from '@lancom/shared/assets/js/utils/gtm';
|
|
62
62
|
import metaInfo from '@lancom/shared/mixins/meta-info';
|
|
63
63
|
import { generateProductsLink, generateProductLink } from '@lancom/shared/assets/js/utils/product';
|
|
64
|
-
import { getProductLargeCover } from '@lancom/shared/assets/js/utils/colors';
|
|
64
|
+
import { getProductLargeCover, getProductMediumCover } from '@lancom/shared/assets/js/utils/colors';
|
|
65
65
|
import LazyHydrate from 'vue-lazy-hydration';
|
|
66
66
|
import TheNavbar from '@/components/the_navbar/the-navbar';
|
|
67
67
|
import TheChangesSavedIndicator from '@lancom/shared/components/the_changes_saved_indicator/the-changes-saved-indicator';
|
|
@@ -93,6 +93,9 @@
|
|
|
93
93
|
middleware: ['page-info'],
|
|
94
94
|
async fetch() {
|
|
95
95
|
await this.loadProducts();
|
|
96
|
+
if (process.server) {
|
|
97
|
+
this.$root.context?.res?.setHeader('Cache-Control', `max-age=${86400}`);;
|
|
98
|
+
}
|
|
96
99
|
},
|
|
97
100
|
computed: {
|
|
98
101
|
...mapGetters('page', ['routeInfo']),
|
|
@@ -253,7 +256,7 @@
|
|
|
253
256
|
};
|
|
254
257
|
}
|
|
255
258
|
|
|
256
|
-
const image = getProductLargeCover(product, 'front');
|
|
259
|
+
const image = getProductLargeCover(product, 'front') || getProductMediumCover(product, 'front');
|
|
257
260
|
if (image) {
|
|
258
261
|
schema.image = image;
|
|
259
262
|
}
|
|
@@ -324,7 +327,6 @@
|
|
|
324
327
|
setTimeout(() => this.logGtm());
|
|
325
328
|
} catch ({ response }) {
|
|
326
329
|
if (process.server) {
|
|
327
|
-
// console.log('status code: ', this.loadError, process.server, this._self.context, Object.keys(this));
|
|
328
330
|
this.$root.context.res.statusCode = this.loadError?.statusCode || 500;
|
|
329
331
|
}
|
|
330
332
|
}
|
package/middleware/page-info.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
|
-
export default async function ({ store, route, redirect }) {
|
|
1
|
+
export default async function ({ store, route, redirect, error }) {
|
|
2
2
|
const matchedRoute = route.matched[route.matched.length - 1];
|
|
3
3
|
if (matchedRoute) {
|
|
4
4
|
const path = matchedRoute.path.replace(/\?/g, '');
|
|
5
5
|
const data = {
|
|
6
6
|
shop: store.getters.shop._id,
|
|
7
7
|
route: path || '/',
|
|
8
|
-
fullPath: route.
|
|
8
|
+
fullPath: route.path
|
|
9
9
|
};
|
|
10
10
|
const routeInfo = await store.dispatch('page/fetchRouteInfo', data);
|
|
11
11
|
if (routeInfo?.type === 'redirect' && routeInfo.redirectTo) {
|
|
12
|
-
redirect(301, routeInfo.redirectTo);
|
|
12
|
+
redirect(301, route.fullPath.replace(route.path, routeInfo.redirectTo));
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
const data = {
|
|
16
|
+
shop: store.getters.shop._id,
|
|
17
|
+
route: route.path || '/',
|
|
18
|
+
fullPath: route.path
|
|
19
|
+
};
|
|
20
|
+
const routeInfo = await store.dispatch('page/fetchRouteInfo', data);
|
|
21
|
+
if (routeInfo?.type === 'deleted') {
|
|
22
|
+
error({
|
|
23
|
+
statusCode: 410,
|
|
24
|
+
message: 'Page has been deleted'
|
|
25
|
+
});
|
|
13
26
|
}
|
|
14
27
|
}
|
|
15
28
|
}
|
package/mixins/product-view.js
CHANGED
|
@@ -17,7 +17,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
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, redirect }) {
|
|
20
|
+
async asyncData({ store, params, error, query, redirect, res }) {
|
|
21
21
|
try {
|
|
22
22
|
const { print, color, colour } = query;
|
|
23
23
|
const { shop, country, stockCountry, currency } = store.getters;
|
|
@@ -53,6 +53,9 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
53
53
|
if (query.store) {
|
|
54
54
|
await store.dispatch('product/updatePriceIncludeGST', true);
|
|
55
55
|
}
|
|
56
|
+
if (query.price) {
|
|
57
|
+
store.commit('product/setPriceIncludeGST', query.price === 'IT');
|
|
58
|
+
}
|
|
56
59
|
} catch (e) {
|
|
57
60
|
console.log(e);
|
|
58
61
|
}
|
|
@@ -63,6 +66,8 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
63
66
|
error(loadError);
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
res?.setHeader('Cache-Control', `max-age=${3600}`);
|
|
70
|
+
|
|
66
71
|
return {
|
|
67
72
|
isEditor,
|
|
68
73
|
pageItem,
|
|
@@ -122,9 +127,10 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
122
127
|
};
|
|
123
128
|
if (!this.product) {
|
|
124
129
|
await this.fetchProduct(data);
|
|
125
|
-
await this.fetchProductDetails(data);
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
await this.fetchProductDetails(data);
|
|
133
|
+
|
|
128
134
|
try {
|
|
129
135
|
if (this.preSetPrints?.length) {
|
|
130
136
|
for (const preSetPrint of this.preSetPrints) {
|
|
@@ -187,7 +193,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
187
193
|
},
|
|
188
194
|
methods: {
|
|
189
195
|
...mapActions(['fetchProduct', 'fetchProductDetails', 'selectColor']),
|
|
190
|
-
...mapMutations(['clearProduct', 'clearTemplate', 'addTemplateLayer', 'setSimpleProductAmount']),
|
|
196
|
+
...mapMutations(['clearProduct', 'clearTemplate', 'addTemplateLayer', 'setSimpleProductAmount', 'setPriceIncludeGST']),
|
|
191
197
|
staticLink,
|
|
192
198
|
goToInfoTab(tab) {
|
|
193
199
|
this.$refs.infoTabs.selectedTab = tab;
|
|
@@ -235,7 +241,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
235
241
|
const productSchema = {
|
|
236
242
|
'@context': 'https://schema.org',
|
|
237
243
|
'@type': 'Product',
|
|
238
|
-
description: this.product.gsFeedDescription || this.product.description,
|
|
244
|
+
description: (this.product.gsFeedDescription || this.product.description || '').replace(/<[^>]*>/g, ''),
|
|
239
245
|
name,
|
|
240
246
|
offers: this.productDetails?.simpleProducts.map(sp => {
|
|
241
247
|
const spMaxPrice = (sp.pricing || []).reduce((price, pricing) => Math.max(price, pricing.price), 0);
|
package/package.json
CHANGED
package/plugins/cache-headers.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
module.exports = function (req, res, next) {
|
|
2
|
+
if (res.getHeader('Cache-Control')) {
|
|
3
|
+
return next();
|
|
4
|
+
}
|
|
5
|
+
|
|
2
6
|
if (!/auth\=/.test(req.headers?.cookie || '')) {
|
|
3
7
|
const routes = [
|
|
4
8
|
/^\/quotes\//,
|
|
5
9
|
/\.xml$/
|
|
6
10
|
];
|
|
7
11
|
const value = routes.some(route => route.test(`${req.url}`)) ? 'no-cache' : `max-age=${31536000}`;
|
|
8
|
-
res
|
|
12
|
+
res?.setHeader('Cache-Control', value);
|
|
9
13
|
} else {
|
|
10
|
-
res
|
|
14
|
+
res?.setHeader('Cache-Control', 'no-cache');
|
|
11
15
|
}
|
|
12
16
|
next();
|
|
13
17
|
};
|
package/store/product.js
CHANGED
|
@@ -12,6 +12,7 @@ import { sortByName } from '../assets/js/utils/filters';
|
|
|
12
12
|
export const state = () => ({
|
|
13
13
|
multipack: null,
|
|
14
14
|
calculatingPrice: false,
|
|
15
|
+
loadingProductDetails: false,
|
|
15
16
|
images: [],
|
|
16
17
|
priceIncludeGST: false,
|
|
17
18
|
product: null,
|
|
@@ -34,6 +35,7 @@ export const state = () => ({
|
|
|
34
35
|
isPrintPricing: false,
|
|
35
36
|
editableColor: null,
|
|
36
37
|
selectedLayer: null,
|
|
38
|
+
selectedLayerCopy: null,
|
|
37
39
|
editablePrintArea: null,
|
|
38
40
|
editablePrintAreas: [],
|
|
39
41
|
selectedPrintType: null,
|
|
@@ -49,6 +51,7 @@ export const state = () => ({
|
|
|
49
51
|
});
|
|
50
52
|
|
|
51
53
|
export const getters = {
|
|
54
|
+
loadingProductDetails: ({ loadingProductDetails }) => loadingProductDetails,
|
|
52
55
|
multipack: ({ multipack }) => multipack,
|
|
53
56
|
calculatingPrice: ({ calculatingPrice }) => calculatingPrice,
|
|
54
57
|
product: ({ product }) => product,
|
|
@@ -78,6 +81,7 @@ export const getters = {
|
|
|
78
81
|
editableColor: ({ editableColor }) => editableColor,
|
|
79
82
|
editableLayers: ({ template, editableColor, editablePrintArea }) => template.layers,
|
|
80
83
|
selectedLayer: ({ selectedLayer }) => selectedLayer,
|
|
84
|
+
selectedLayerCopy: ({ selectedLayerCopy }) => selectedLayerCopy,
|
|
81
85
|
editModeSelectedLayer: ({ editModeSelectedLayer }) => editModeSelectedLayer,
|
|
82
86
|
selectedPrintAreas: ({ selectedPrintAreas }) => selectedPrintAreas,
|
|
83
87
|
selectedPrintArea: ({ selectedPrintAreas, editablePrintArea }) =>
|
|
@@ -177,8 +181,10 @@ export const actions = {
|
|
|
177
181
|
},
|
|
178
182
|
async fetchProductDetails({ commit, state }, { shop, slug, country, stockCountry, currency, defaultColor = 'white' }) {
|
|
179
183
|
const params = { country, currency, stockCountry };
|
|
184
|
+
commit('setLoadingProductDetails', true);
|
|
180
185
|
const response = await api.fetchProductDetails(shop, slug, params);
|
|
181
186
|
commit('setProductDetails', response);
|
|
187
|
+
commit('setLoadingProductDetails', false);
|
|
182
188
|
|
|
183
189
|
const catalogFrontColorId = getColorOfDefaultCatalogFrontImage(state.product);
|
|
184
190
|
const editableColor = state.availableColors.find(c => c.alias === defaultColor) ||
|
|
@@ -273,6 +279,9 @@ export const actions = {
|
|
|
273
279
|
};
|
|
274
280
|
|
|
275
281
|
export const mutations = {
|
|
282
|
+
setLoadingProductDetails(state, loadingProductDetails) {
|
|
283
|
+
state.loadingProductDetails = loadingProductDetails;
|
|
284
|
+
},
|
|
276
285
|
setMultipack(state, multipack) {
|
|
277
286
|
state.multipack = multipack;
|
|
278
287
|
},
|
|
@@ -351,6 +360,7 @@ export const mutations = {
|
|
|
351
360
|
*/
|
|
352
361
|
setSelectedLayer(state, layer) {
|
|
353
362
|
state.selectedLayer = layer;
|
|
363
|
+
state.selectedLayerCopy = JSON.parse(JSON.stringify(layer || null));
|
|
354
364
|
},
|
|
355
365
|
setEditModeSelectedLayer(state, editMode) {
|
|
356
366
|
state.editModeSelectedLayer = editMode;
|