@lancom/shared 0.0.452 → 0.0.454
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/helpers.js +1 -1
- package/assets/js/api/index.js +5 -4
- package/assets/js/utils/cache.js +33 -0
- package/assets/js/utils/fabric/selection-style.js +6 -5
- package/assets/js/utils/fabric-helper.js +17 -22
- package/components/common/tabs.vue +1 -0
- package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +9 -8
- package/components/product/clearance_products/clearance-products.scss +35 -0
- package/components/product/clearance_products/clearance-products.vue +85 -0
- package/components/product/clearance_products/clearance_product/clearance-product.scss +82 -0
- package/components/product/clearance_products/clearance_product/clearance-product.vue +56 -0
- package/components/product/other_products/other-products.vue +38 -11
- package/components/product/other_products/other_product/other-product.vue +9 -21
- package/components/product/related_products/related-products.vue +6 -2
- package/mixins/product-preview.js +63 -0
- package/mixins/product-view.js +3 -2
- package/package.json +2 -1
package/assets/js/api/helpers.js
CHANGED
|
@@ -18,7 +18,7 @@ if (process.client) {
|
|
|
18
18
|
axiosApiInstance.interceptors.response.use(
|
|
19
19
|
response => response,
|
|
20
20
|
error => {
|
|
21
|
-
if (error.response
|
|
21
|
+
if (error.response?.status === 401) {
|
|
22
22
|
removeAuthToken();
|
|
23
23
|
const currentPath = window.location.pathname;
|
|
24
24
|
if (currentPath && currentPath !== '/') {
|
package/assets/js/api/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { _get, _post, _put, _delete, _patch } from './helpers';
|
|
|
3
3
|
import adminApi from './admin';
|
|
4
4
|
import { unminifySimpleProducts } from './utils/simple-products';
|
|
5
5
|
import { unminifyProduct } from './utils/product';
|
|
6
|
+
import { createCachedFunction } from './../utils/cache';
|
|
6
7
|
|
|
7
8
|
const api = {
|
|
8
9
|
fetchClientSettings(shop, params) {
|
|
@@ -93,12 +94,12 @@ const api = {
|
|
|
93
94
|
const url = shop ? `shop/${shop}/products/${alias}/simple-products` : `products/${alias}/simple-products`;
|
|
94
95
|
return unminifySimpleProducts(await _get(url, params));
|
|
95
96
|
},
|
|
96
|
-
fetchRelatedProducts(shop, alias, params) {
|
|
97
|
+
fetchRelatedProducts: createCachedFunction((shop, alias, params) => {
|
|
97
98
|
return _get(`shop/${shop}/products/${alias}/related-products`, params);
|
|
98
|
-
},
|
|
99
|
-
fetchOtherProducts(shop, alias, params) {
|
|
99
|
+
}, 5000),
|
|
100
|
+
fetchOtherProducts: createCachedFunction((shop, alias, params) => {
|
|
100
101
|
return _get(`shop/${shop}/products/${alias}/other-products`, params);
|
|
101
|
-
},
|
|
102
|
+
}, 5000),
|
|
102
103
|
fetchHelpMessages(shop, group) {
|
|
103
104
|
return _get(`shop/${shop}/help-messages/${group}`);
|
|
104
105
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const createCachedFunction = (fn, ttl = 30000) => {
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
const inFlight = new Map();
|
|
4
|
+
|
|
5
|
+
return (...args) => {
|
|
6
|
+
const cacheKey = JSON.stringify(args);
|
|
7
|
+
const cached = cache.get(cacheKey);
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
|
|
10
|
+
if (cached && now - cached.timestamp < ttl) {
|
|
11
|
+
return Promise.resolve(cached.data);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const existing = inFlight.get(cacheKey);
|
|
15
|
+
if (existing) {
|
|
16
|
+
return existing;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const promise = fn(...args).then(data => {
|
|
20
|
+
cache.set(cacheKey, { data, timestamp: now });
|
|
21
|
+
inFlight.delete(cacheKey);
|
|
22
|
+
return data;
|
|
23
|
+
}).catch(error => {
|
|
24
|
+
inFlight.delete(cacheKey);
|
|
25
|
+
throw error;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
inFlight.set(cacheKey, promise);
|
|
29
|
+
return promise;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { createCachedFunction };
|
|
@@ -49,20 +49,21 @@ export function setupCustomControls(fabricHelper) {
|
|
|
49
49
|
['tl', 'bl', 'br'].forEach(key => {
|
|
50
50
|
fabric.Object.prototype.controls[key].sizeX = 7;
|
|
51
51
|
fabric.Object.prototype.controls[key].sizeY = 7;
|
|
52
|
+
fabric.Object.prototype.controls[key].actionName = 'scaling';
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
fabric.Object.prototype.controls.tr = new fabric.Control({
|
|
55
56
|
x: 0.5,
|
|
56
57
|
y: -0.5,
|
|
57
58
|
cursorStyle: 'pointer',
|
|
58
|
-
mouseUpHandler
|
|
59
|
+
mouseUpHandler(eventData, transform) {
|
|
59
60
|
const target = transform.target;
|
|
60
61
|
fabricHelper.dispatch('removeLayer', target.layer);
|
|
61
62
|
fabricHelper.editor.remove(target);
|
|
62
63
|
fabricHelper.editor.renderAll();
|
|
63
64
|
return true;
|
|
64
65
|
},
|
|
65
|
-
render
|
|
66
|
+
render(ctx, left, top, styleOverride, fabricObject) {
|
|
66
67
|
const size = 10;
|
|
67
68
|
ctx.save();
|
|
68
69
|
ctx.translate(left, top);
|
|
@@ -91,7 +92,7 @@ export function setupCustomControls(fabricHelper) {
|
|
|
91
92
|
cursorStyle: 'pointer',
|
|
92
93
|
actionHandler: fabric.controlsUtils.rotationWithSnapping,
|
|
93
94
|
actionName: 'rotate',
|
|
94
|
-
render
|
|
95
|
+
render(ctx, left, top, styleOverride, fabricObject) {
|
|
95
96
|
if (rotateImg) {
|
|
96
97
|
renderIcon(ctx, left, top, styleOverride, fabricObject, rotateImg);
|
|
97
98
|
}
|
|
@@ -106,7 +107,7 @@ export function setupCustomControls(fabricHelper) {
|
|
|
106
107
|
offsetY: -30,
|
|
107
108
|
offsetX: 30,
|
|
108
109
|
cursorStyle: 'pointer',
|
|
109
|
-
mouseUpHandler
|
|
110
|
+
mouseUpHandler(eventData, transform) {
|
|
110
111
|
const target = transform.target;
|
|
111
112
|
const helper = fabricHelper;
|
|
112
113
|
|
|
@@ -134,7 +135,7 @@ export function setupCustomControls(fabricHelper) {
|
|
|
134
135
|
|
|
135
136
|
return true;
|
|
136
137
|
},
|
|
137
|
-
render
|
|
138
|
+
render(ctx, left, top, styleOverride, fabricObject) {
|
|
138
139
|
if (centerImg) {
|
|
139
140
|
renderIcon(ctx, left, top, styleOverride, fabricObject, centerImg);
|
|
140
141
|
} else {
|
|
@@ -137,17 +137,19 @@ export default class FabricHelper {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
setPrintArea(printArea, size, product) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
this.background
|
|
140
|
+
if (printArea) {
|
|
141
|
+
this.printAreaRect = getPrintAreaByName({
|
|
142
|
+
printArea: printArea?.parentPrintArea || printArea?._id,
|
|
143
|
+
printSize: printArea?.printSize,
|
|
144
|
+
printAreaOffsets: printArea?.printAreaOffsets,
|
|
145
|
+
editorWidth: size.width,
|
|
146
|
+
editorHeight: size.height
|
|
147
|
+
}, product, true);
|
|
148
|
+
if (this.background) {
|
|
149
|
+
this.background.setBoundingRect(this.printAreaRect);
|
|
150
|
+
}
|
|
151
|
+
this.addBoundingArea();
|
|
149
152
|
}
|
|
150
|
-
this.addBoundingArea();
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
addBoundingArea() {
|
|
@@ -275,13 +277,7 @@ export default class FabricHelper {
|
|
|
275
277
|
object.on('rotating', () => {
|
|
276
278
|
this.dispatch('setField', { field: 'angle', value: parseInt(object.angle) });
|
|
277
279
|
});
|
|
278
|
-
object.on('moved', () => {
|
|
279
|
-
this.dispatch('setField', { field: 'top', value: Math.round(object.top) });
|
|
280
|
-
this.dispatch('setField', { field: 'left', value: Math.round(object.left) });
|
|
281
|
-
});
|
|
282
|
-
object.on('moving', () => this.checkBoundingIntersection(object));
|
|
283
280
|
object.on('scaling', () => {
|
|
284
|
-
this.checkBoundingIntersection(object);
|
|
285
281
|
if (object.type === 'art') {
|
|
286
282
|
const measure = this.getEditorDPI();
|
|
287
283
|
const originalRatio = object.originalSize.width / object.width;
|
|
@@ -290,17 +286,16 @@ export default class FabricHelper {
|
|
|
290
286
|
}
|
|
291
287
|
});
|
|
292
288
|
|
|
293
|
-
object.on('
|
|
294
|
-
this.dispatch('setField', { field: '
|
|
295
|
-
|
|
296
|
-
object.on('moved', () => {
|
|
289
|
+
object.on('modified', () => {
|
|
290
|
+
this.dispatch('setField', { field: 'top', value: Math.round(object.top) });
|
|
291
|
+
this.dispatch('setField', { field: 'left', value: Math.round(object.left) });
|
|
297
292
|
this.dispatch('setField', { field: 'boundingRect', value: object.getBoundingRect() });
|
|
298
293
|
});
|
|
299
294
|
/*
|
|
300
295
|
** TEXT OBJECT EVENTS
|
|
301
296
|
*/
|
|
302
297
|
if (type === 'text') {
|
|
303
|
-
object.on('
|
|
298
|
+
object.on('modified', target => {
|
|
304
299
|
const fontSize = Math.round(object.fontSize * object.scaleX);
|
|
305
300
|
// const strokeWidth = Math.round(object.strokeWidth * object.scaleX);
|
|
306
301
|
this.dispatch('setField', { field: 'fontSize', value: fontSize });
|
|
@@ -331,7 +326,7 @@ export default class FabricHelper {
|
|
|
331
326
|
** ART OBJECT EVENTS
|
|
332
327
|
*/
|
|
333
328
|
if (type === 'art') {
|
|
334
|
-
object.on('
|
|
329
|
+
object.on('modified', () => {
|
|
335
330
|
this.dispatch('setField', { field: 'scaleX', value: object.scaleX });
|
|
336
331
|
this.dispatch('setField', { field: 'scaleY', value: object.scaleY });
|
|
337
332
|
});
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
45
|
<div
|
|
46
|
-
v-if="fabricHelper && !editModeSelectedLayer"
|
|
46
|
+
v-if="fabricHelper && fabricHelper.printAreaRect && !editModeSelectedLayer"
|
|
47
47
|
class="EditorWorkspaceSide__placeholder"
|
|
48
48
|
:class="{
|
|
49
49
|
tighten: !isZoomed && printAreaIsSmall,
|
|
@@ -233,7 +233,7 @@ export default {
|
|
|
233
233
|
return layers;
|
|
234
234
|
},
|
|
235
235
|
positionPlaceholder() {
|
|
236
|
-
const { center, left, top, width, height } = this.fabricHelper.printAreaRect;
|
|
236
|
+
const { center, left, top, width, height } = this.fabricHelper.printAreaRect || {};
|
|
237
237
|
const ratio = this.calcWorkspaceSize() / this.editorSize.width;
|
|
238
238
|
if (this.printAreaLayers.length > 0) {
|
|
239
239
|
return {
|
|
@@ -386,11 +386,12 @@ export default {
|
|
|
386
386
|
});
|
|
387
387
|
this.fabricHelper.on('removeLayer', (layer) => {
|
|
388
388
|
setTimeout(() => {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
389
|
+
this.removeTemplateLayer(layer);
|
|
390
|
+
// if (!this.editModeSelectedLayer) {
|
|
391
|
+
// this.removeTemplateLayer(layer);
|
|
392
|
+
// } else if (!layer.copy) {
|
|
393
|
+
// this.fabricHelper.dispatch('setField', { field: 'isEditMode', value: false });;
|
|
394
|
+
// }
|
|
394
395
|
}, 100);
|
|
395
396
|
});
|
|
396
397
|
this.fabricHelper.on('outOfPrintArea', this.setOffsetWarningVisibility);
|
|
@@ -462,7 +463,7 @@ export default {
|
|
|
462
463
|
async createTextLayer() {
|
|
463
464
|
this.addedFromCanvas = true;
|
|
464
465
|
window.scrollTo(0, 0);
|
|
465
|
-
const layer = await this.createLayer({ type: 'text', isEditMode: this.isEditMode });
|
|
466
|
+
const layer = this.printAreaLayers?.length > 0 ? this.printAreaLayers[0] : (await this.createLayer({ type: 'text', isEditMode: this.isEditMode }));
|
|
466
467
|
this.visibleWireframe = true;
|
|
467
468
|
if (!this.isEditMode) {
|
|
468
469
|
setTimeout(() => {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.ClearanceProducts {
|
|
2
|
+
&__head {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
margin: 4px 0 16px;
|
|
7
|
+
gap: 16px;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
&__title {
|
|
11
|
+
color: #0A0A0A;
|
|
12
|
+
font-size: 14px;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
line-height: 20px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&__all {
|
|
19
|
+
color: #194BB3;
|
|
20
|
+
text-align: center;
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
font-style: normal;
|
|
23
|
+
font-weight: 600;
|
|
24
|
+
line-height: 16px;
|
|
25
|
+
text-decoration: underline;
|
|
26
|
+
&:hover {
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
&__list {
|
|
31
|
+
display: grid;
|
|
32
|
+
gap: 14px;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="ClearanceProducts__wrapper">
|
|
4
|
+
<div class="ClearanceProducts__head">
|
|
5
|
+
<h2 class="ClearanceProducts__title">
|
|
6
|
+
Clearance Product
|
|
7
|
+
</h2>
|
|
8
|
+
<a
|
|
9
|
+
href="/c/clearance"
|
|
10
|
+
class="ClearanceProducts__all">
|
|
11
|
+
View all clearance products
|
|
12
|
+
</a>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="ClearanceProducts__list">
|
|
15
|
+
<clearance-product
|
|
16
|
+
v-for="item in products"
|
|
17
|
+
:key="item._id"
|
|
18
|
+
:product="item"
|
|
19
|
+
:to-editor="toEditor"
|
|
20
|
+
class="ClearanceProducts__product" />
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import { mapGetters } from 'vuex';
|
|
27
|
+
import api from '@lancom/shared/assets/js/api';
|
|
28
|
+
import ClearanceProduct from './clearance_product/clearance-product';
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
name: 'ClearanceProducts',
|
|
32
|
+
components: {
|
|
33
|
+
ClearanceProduct
|
|
34
|
+
},
|
|
35
|
+
props: {
|
|
36
|
+
product: {
|
|
37
|
+
type: Object,
|
|
38
|
+
required: true
|
|
39
|
+
},
|
|
40
|
+
toEditor: {
|
|
41
|
+
type: Boolean,
|
|
42
|
+
default: false
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
data() {
|
|
46
|
+
return {
|
|
47
|
+
products: [],
|
|
48
|
+
loading: false
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
computed: {
|
|
52
|
+
...mapGetters(['shop', 'country', 'currency'])
|
|
53
|
+
},
|
|
54
|
+
mounted() {
|
|
55
|
+
this.loadProducts();
|
|
56
|
+
},
|
|
57
|
+
methods: {
|
|
58
|
+
isClearanceColor(color) {
|
|
59
|
+
return color?.pricing?.some(c => c.clearance);
|
|
60
|
+
},
|
|
61
|
+
hasClearanceColors(product) {
|
|
62
|
+
return (product.colors || []).some(color => this.isClearanceColor(color));
|
|
63
|
+
},
|
|
64
|
+
async loadProducts() {
|
|
65
|
+
try {
|
|
66
|
+
this.loading = true;
|
|
67
|
+
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
|
|
68
|
+
needShuffle: false,
|
|
69
|
+
country: this.country?._id,
|
|
70
|
+
currency: this.currency?._id
|
|
71
|
+
});
|
|
72
|
+
const clearanceProducts = products.filter(p => p.isClearance || this.hasClearanceColors(p));
|
|
73
|
+
this.products = clearanceProducts.slice(0, 1);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
} finally {
|
|
76
|
+
this.loading = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<style lang="scss" scoped>
|
|
84
|
+
@import 'clearance-products';
|
|
85
|
+
</style>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
.ClearanceProduct {
|
|
2
|
+
&__wrapper {
|
|
3
|
+
display: flex;
|
|
4
|
+
gap: 12px;
|
|
5
|
+
padding: 12px;
|
|
6
|
+
border-radius: 8px;
|
|
7
|
+
border: 1px solid #E5E5E5;
|
|
8
|
+
background: #FFF;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
&__img {
|
|
12
|
+
width: 64px;
|
|
13
|
+
height: 64px;
|
|
14
|
+
object-fit: contain;
|
|
15
|
+
border-radius: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&__body {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 4px;
|
|
22
|
+
flex: 1;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__row {
|
|
26
|
+
display: flex;
|
|
27
|
+
gap: 8px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&__title {
|
|
31
|
+
color: #0A0A0A;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
font-style: normal;
|
|
34
|
+
font-weight: 600;
|
|
35
|
+
line-height: 20px;
|
|
36
|
+
text-decoration: none;
|
|
37
|
+
&:hover {
|
|
38
|
+
text-decoration: underline;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&__sku {
|
|
43
|
+
color: #666;
|
|
44
|
+
font-size: 12px;
|
|
45
|
+
font-style: normal;
|
|
46
|
+
font-weight: 400;
|
|
47
|
+
line-height: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&__clearance-label {
|
|
51
|
+
color: #D97706;
|
|
52
|
+
font-size: 12px;
|
|
53
|
+
font-style: normal;
|
|
54
|
+
font-weight: 600;
|
|
55
|
+
line-height: 16px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&__price {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
margin-top: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&__price-current {
|
|
66
|
+
color: #0A0A0A;
|
|
67
|
+
font-size: 14px;
|
|
68
|
+
font-style: normal;
|
|
69
|
+
font-weight: 600;
|
|
70
|
+
line-height: 20px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&__price-old {
|
|
74
|
+
color: #999;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
font-style: normal;
|
|
77
|
+
font-weight: 400;
|
|
78
|
+
line-height: 16px;
|
|
79
|
+
text-decoration: line-through;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ClearanceProduct__wrapper">
|
|
3
|
+
<a
|
|
4
|
+
v-if="productСover"
|
|
5
|
+
:href="productLink">
|
|
6
|
+
<img
|
|
7
|
+
:src="productСover"
|
|
8
|
+
:alt="product.name"
|
|
9
|
+
class="ClearanceProduct__img" />
|
|
10
|
+
</a>
|
|
11
|
+
<div class="ClearanceProduct__body">
|
|
12
|
+
<div class="ClearanceProduct__row">
|
|
13
|
+
<a
|
|
14
|
+
:href="productLink"
|
|
15
|
+
class="ClearanceProduct__title">
|
|
16
|
+
{{ product.name }}
|
|
17
|
+
</a>
|
|
18
|
+
</div>
|
|
19
|
+
<div
|
|
20
|
+
v-if="product.SKU"
|
|
21
|
+
class="ClearanceProduct__sku">
|
|
22
|
+
sku: {{ product.SKU }}
|
|
23
|
+
</div>
|
|
24
|
+
<div class="ClearanceProduct__price">
|
|
25
|
+
<span class="ClearanceProduct__price-current">
|
|
26
|
+
<price
|
|
27
|
+
:price="currentColorMaxPrice"
|
|
28
|
+
:with-gst="priceIncludeGST" />
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script>
|
|
36
|
+
import productPreview from '@lancom/shared/mixins/product-preview';
|
|
37
|
+
import Price from '@lancom/shared/components/common/price';
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
name: 'ClearanceProduct',
|
|
41
|
+
components: {
|
|
42
|
+
Price
|
|
43
|
+
},
|
|
44
|
+
mixins: [productPreview],
|
|
45
|
+
props: {
|
|
46
|
+
toEditor: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style lang="scss" scoped>
|
|
55
|
+
@import 'clearance-product';
|
|
56
|
+
</style>
|
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
<h2 class="OtherProducts__title">
|
|
5
5
|
Other Products
|
|
6
6
|
</h2>
|
|
7
|
-
<a
|
|
7
|
+
<a
|
|
8
|
+
v-if="categoryLink"
|
|
9
|
+
:href="categoryLink"
|
|
10
|
+
class="OtherProducts__all">
|
|
8
11
|
View all products in this category
|
|
9
12
|
</a>
|
|
10
13
|
</div>
|
|
11
14
|
<div class="OtherProducts__list">
|
|
12
15
|
<other-product
|
|
13
|
-
v-for="
|
|
14
|
-
:key="
|
|
15
|
-
:product="
|
|
16
|
+
v-for="item in products"
|
|
17
|
+
:key="item._id"
|
|
18
|
+
:product="item"
|
|
19
|
+
:to-editor="toEditor"
|
|
16
20
|
class="OtherProducts__product" />
|
|
17
21
|
</div>
|
|
18
22
|
</section>
|
|
@@ -35,7 +39,7 @@ export default {
|
|
|
35
39
|
},
|
|
36
40
|
toEditor: {
|
|
37
41
|
type: Boolean,
|
|
38
|
-
default:
|
|
42
|
+
default: false
|
|
39
43
|
},
|
|
40
44
|
limit: {
|
|
41
45
|
type: Number,
|
|
@@ -49,21 +53,44 @@ export default {
|
|
|
49
53
|
};
|
|
50
54
|
},
|
|
51
55
|
computed: {
|
|
52
|
-
...mapGetters(['shop', 'country', 'currency'])
|
|
56
|
+
...mapGetters(['shop', 'country', 'currency']),
|
|
57
|
+
categoryLink() {
|
|
58
|
+
const category = this.product.category;
|
|
59
|
+
if (!category) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const categories = [];
|
|
63
|
+
let current = category;
|
|
64
|
+
while (current) {
|
|
65
|
+
categories.unshift(current.alias);
|
|
66
|
+
current = current.parent;
|
|
67
|
+
}
|
|
68
|
+
return categories.length ? `/c/${categories.join('/')}` : null;
|
|
69
|
+
}
|
|
53
70
|
},
|
|
54
71
|
mounted() {
|
|
55
72
|
this.loadProducts();
|
|
56
73
|
},
|
|
57
74
|
methods: {
|
|
75
|
+
isClearanceColor(color) {
|
|
76
|
+
return color?.pricing?.some(c => c.clearance);
|
|
77
|
+
},
|
|
78
|
+
hasClearanceColors(product) {
|
|
79
|
+
return (product.colors || []).some(color => this.isClearanceColor(color));
|
|
80
|
+
},
|
|
58
81
|
async loadProducts() {
|
|
59
82
|
try {
|
|
60
83
|
this.loading = true;
|
|
61
|
-
const
|
|
84
|
+
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
|
|
85
|
+
needShuffle: false,
|
|
62
86
|
country: this.country?._id,
|
|
63
|
-
currency: this.currency?._id
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
87
|
+
currency: this.currency?._id
|
|
88
|
+
});
|
|
89
|
+
this.products = products.sort((a, b) => {
|
|
90
|
+
const aIsClearance = a.isClearance || this.hasClearanceColors(a);
|
|
91
|
+
const bIsClearance = b.isClearance || this.hasClearanceColors(b);
|
|
92
|
+
return aIsClearance === bIsClearance ? 0 : aIsClearance ? -1 : 1;
|
|
93
|
+
});
|
|
67
94
|
} catch (e) {
|
|
68
95
|
} finally {
|
|
69
96
|
this.loading = false;
|
|
@@ -21,28 +21,10 @@
|
|
|
21
21
|
class="OtherProduct__sku">
|
|
22
22
|
sku: {{ product.SKU }}
|
|
23
23
|
</div>
|
|
24
|
-
<div
|
|
25
|
-
v-if="product.isClearance"
|
|
26
|
-
class="OtherProduct__price">
|
|
27
|
-
<span
|
|
28
|
-
v-if="product.oldPrice"
|
|
29
|
-
class="OtherProduct__price-old">
|
|
30
|
-
<price
|
|
31
|
-
:price="minPrice"
|
|
32
|
-
:with-gst="priceIncludeGST" />
|
|
33
|
-
</span>
|
|
34
|
-
<span class="OtherProduct__price-current">
|
|
35
|
-
<price
|
|
36
|
-
:price="product.minPrice"
|
|
37
|
-
:with-gst="priceIncludeGST" />
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div
|
|
41
|
-
v-else
|
|
42
|
-
class="OtherProduct__price">
|
|
24
|
+
<div class="OtherProduct__price">
|
|
43
25
|
<span class="OtherProduct__price-current">
|
|
44
26
|
<price
|
|
45
|
-
:price="
|
|
27
|
+
:price="currentColorMaxPrice"
|
|
46
28
|
:with-gst="priceIncludeGST" />
|
|
47
29
|
</span>
|
|
48
30
|
</div>
|
|
@@ -59,7 +41,13 @@ export default {
|
|
|
59
41
|
components: {
|
|
60
42
|
Price
|
|
61
43
|
},
|
|
62
|
-
mixins: [productPreview]
|
|
44
|
+
mixins: [productPreview],
|
|
45
|
+
props: {
|
|
46
|
+
toEditor: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
63
51
|
};
|
|
64
52
|
</script>
|
|
65
53
|
|
|
@@ -37,11 +37,15 @@ export default {
|
|
|
37
37
|
};
|
|
38
38
|
},
|
|
39
39
|
async fetch() {
|
|
40
|
-
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
|
|
40
|
+
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
|
|
41
|
+
needShuffle: false,
|
|
42
|
+
country: this.country?._id,
|
|
43
|
+
currency: this.currency?._id
|
|
44
|
+
});
|
|
41
45
|
this.products = products.splice(0, 6);
|
|
42
46
|
},
|
|
43
47
|
computed: {
|
|
44
|
-
...mapGetters(['shop']),
|
|
48
|
+
...mapGetters(['shop', 'country', 'currency']),
|
|
45
49
|
hasProducts() {
|
|
46
50
|
return this.products.length > 0;
|
|
47
51
|
}
|
|
@@ -2,6 +2,7 @@ import { mapGetters } from 'vuex';
|
|
|
2
2
|
import { getColorBackgroundStyle, getProductMediumCover, getBgStyle, getProductHoverCover } from '@lancom/shared/assets/js/utils/colors';
|
|
3
3
|
import { staticLink } from '@lancom/shared/assets/js/utils/filters';
|
|
4
4
|
import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
|
|
5
|
+
import { sortSizes } from '@lancom/shared/assets/js/utils/sizes';
|
|
5
6
|
|
|
6
7
|
const loadHolder = {
|
|
7
8
|
canLoadImages: true,
|
|
@@ -129,6 +130,68 @@ const productPreview = {
|
|
|
129
130
|
case 'child':
|
|
130
131
|
return 'icon-baby';
|
|
131
132
|
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
colors() {
|
|
136
|
+
const colors = this.product.colors?.filter(c => !!c) || [];
|
|
137
|
+
const sortedColors = colors.sort((a, b) => {
|
|
138
|
+
const aIsClearance = this.isClearanceColor(a);
|
|
139
|
+
const bIsClearance = this.isClearanceColor(b);
|
|
140
|
+
return aIsClearance === bIsClearance ? 0 : aIsClearance ? -1 : 1;
|
|
141
|
+
});
|
|
142
|
+
return this.full ? sortedColors : sortedColors.slice(0, this.maxVisibleColors);
|
|
143
|
+
},
|
|
144
|
+
hasSizes() {
|
|
145
|
+
return this.sizes.length > 0;
|
|
146
|
+
},
|
|
147
|
+
sizes() {
|
|
148
|
+
const sizes = sortSizes(this.product.sizes || []);
|
|
149
|
+
return this.full ? sizes : sizes.slice(0, this.maxVisibleSizes);
|
|
150
|
+
},
|
|
151
|
+
hiddenSizesCount() {
|
|
152
|
+
const sizes = this.product.sizes || [];
|
|
153
|
+
return sizes.length - this.sizes.length;
|
|
154
|
+
},
|
|
155
|
+
isVisibleShowMore() {
|
|
156
|
+
const colors = this.product.colors || [];
|
|
157
|
+
return (colors.length > this.maxVisibleColors) && !this.full;
|
|
158
|
+
},
|
|
159
|
+
mainColor() {
|
|
160
|
+
return this.currentColor || this.colorWithMaxPrice;
|
|
161
|
+
},
|
|
162
|
+
colorWithMaxPrice() {
|
|
163
|
+
const colorWithMaxPrice = this.product.colors?.reduce((max, current) => {
|
|
164
|
+
const currentMaxPrice = current?.maxPrice || 0;
|
|
165
|
+
const maxPrice = max?.maxPrice || 0;
|
|
166
|
+
return (!max || (current && currentMaxPrice > maxPrice)) ? current : max;
|
|
167
|
+
}, null);
|
|
168
|
+
return colorWithMaxPrice;
|
|
169
|
+
},
|
|
170
|
+
visibleTiers() {
|
|
171
|
+
const pricing = (this.full ? this.mainColor?.pricing : this.mainColor?.pricing?.slice(0, this.maxVisibleTiers)) || [];
|
|
172
|
+
const printsPrice = this.product.minPrintsPrice || 0;
|
|
173
|
+
return pricing.map(p => ({ ...p, price: p.price + printsPrice }));
|
|
174
|
+
},
|
|
175
|
+
currentColorMaxPrice() {
|
|
176
|
+
const printsPrice = this.product.maxPrintsPrice || 0;
|
|
177
|
+
const productPrice = this.mainColor?.maxPrice || 0;
|
|
178
|
+
return productPrice + printsPrice;
|
|
179
|
+
},
|
|
180
|
+
currentColorMaxPriceWithoutClearance() {
|
|
181
|
+
const printsPrice = this.product.maxPrintsPrice || 0;
|
|
182
|
+
const productPrice = this.mainColor?.maxPriceWithoutClearance || 0;
|
|
183
|
+
return productPrice + printsPrice;
|
|
184
|
+
},
|
|
185
|
+
minClearanceColorPrice() {
|
|
186
|
+
const clearanceColors = this.product.colors?.filter(c => this.isClearanceColor(c)) || [];
|
|
187
|
+
const minClearanceColor = clearanceColors.reduce((min, current) => {
|
|
188
|
+
const currentMinPrice = current?.minPrice || 0;
|
|
189
|
+
const minPrice = min?.minPrice || 0;
|
|
190
|
+
return (!min || (current && currentMinPrice < minPrice)) ? current : min;
|
|
191
|
+
}, null);
|
|
192
|
+
const printsPrice = this.product.minPrintsPrice || 0;
|
|
193
|
+
const productPrice = minClearanceColor?.minPrice || 0;
|
|
194
|
+
return minClearanceColor ? productPrice + printsPrice : 0;
|
|
132
195
|
}
|
|
133
196
|
},
|
|
134
197
|
mounted() {
|
package/mixins/product-view.js
CHANGED
|
@@ -125,7 +125,8 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
125
125
|
return (thumbProductImages.length > 0 ? thumbProductImages : this.images).slice(0, 6);
|
|
126
126
|
},
|
|
127
127
|
mainProductImageSrc() {
|
|
128
|
-
|
|
128
|
+
const image = (this.isEditor && this.mainProductImage?.extralarge) || this.mainProductImage?.large;
|
|
129
|
+
return image && staticLink(image);
|
|
129
130
|
},
|
|
130
131
|
mainProductImageStyles() {
|
|
131
132
|
return this.mainProductImageSrc ? { 'background-image': `url(${this.mainProductImageSrc});` } : {};
|
|
@@ -342,7 +343,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
|
|
|
342
343
|
if (brand) {
|
|
343
344
|
productSchema.brand = {
|
|
344
345
|
'@type': 'Brand',
|
|
345
|
-
logo: staticLink(brand.logo),
|
|
346
|
+
logo: staticLink(brand.smallLogo || brand.logo),
|
|
346
347
|
name: brand.name
|
|
347
348
|
};
|
|
348
349
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lancom/shared",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.454",
|
|
4
4
|
"description": "lancom common scripts",
|
|
5
5
|
"author": "e.tokovenko <e.tokovenko@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"homepage": "https://bitbucket.org/simpletee/lancom-shared#readme",
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"axios": "0.21.4",
|
|
13
14
|
"basic-auth": "^2.0.1",
|
|
14
15
|
"lodash.get": "^4.4.2",
|
|
15
16
|
"nuxt-client-init-module": "^0.3.0",
|