@lancom/shared 0.0.453 → 0.0.456
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/index.js +5 -4
- package/assets/js/utils/breakpoints.js +3 -2
- package/assets/js/utils/cache.js +33 -0
- package/assets/js/utils/fabric-helper.js +12 -10
- package/components/common/tabs.vue +1 -0
- package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +3 -3
- package/components/editor/mobile_editor_product_details/mobile-editor-product-details.scss +8 -1
- package/components/editor/mobile_editor_product_details/mobile-editor-product-details.vue +9 -3
- 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/product_pricing_tiers/product-pricing-tiers.scss +24 -0
- package/components/product/related_products/related-products.vue +6 -2
- package/components/the_aside/the-aside.scss +7 -0
- package/mixins/product-preview.js +63 -0
- package/mixins/product-view.js +2 -2
- package/package.json +1 -1
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
|
},
|
|
@@ -3,7 +3,8 @@ import debounce from 'lodash.debounce';
|
|
|
3
3
|
|
|
4
4
|
class Breakpoints {
|
|
5
5
|
screens = {
|
|
6
|
-
mini:
|
|
6
|
+
mini: 449,
|
|
7
|
+
xs: 450,
|
|
7
8
|
sm: 768,
|
|
8
9
|
md: 1024,
|
|
9
10
|
lg: 1280,
|
|
@@ -48,7 +49,7 @@ class Breakpoints {
|
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
isMini(val) {
|
|
51
|
-
return val
|
|
52
|
+
return val <= this.screens.mini;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
isXs(val) {
|
|
@@ -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 };
|
|
@@ -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() {
|
|
@@ -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 {
|
|
@@ -451,7 +451,7 @@ export default {
|
|
|
451
451
|
this.removeTemplateLayer(this.selectedLayer);
|
|
452
452
|
},
|
|
453
453
|
onOutsideClick() {
|
|
454
|
-
if (this.addedFromCanvas && this.selectedLayer?.type === 'text' &&
|
|
454
|
+
if (this.addedFromCanvas && this.selectedLayer?.type === 'text' && !this.selectedLayer.copy) {
|
|
455
455
|
this.removeTemplateLayer(this.selectedLayer);
|
|
456
456
|
}
|
|
457
457
|
this.addedFromCanvas = false;
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
background: white;
|
|
9
9
|
left: 0;
|
|
10
10
|
right: 0;
|
|
11
|
+
// min-height: 64px;
|
|
12
|
+
box-shadow: none !important;
|
|
13
|
+
border-radius: 0;
|
|
14
|
+
border: none;
|
|
15
|
+
.EditorPricing__clearance-messages {
|
|
16
|
+
display: none;
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
&__menu {
|
|
13
20
|
position: fixed;
|
|
@@ -117,4 +124,4 @@
|
|
|
117
124
|
overflow-y: auto;
|
|
118
125
|
overflow-x: hidden;
|
|
119
126
|
}
|
|
120
|
-
}
|
|
127
|
+
}
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
style="margin-top: 40px;" />
|
|
80
80
|
<editor-layers
|
|
81
81
|
v-show="currentTab === 'layers'"
|
|
82
|
-
:
|
|
82
|
+
:has-pricing="false" />
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
85
85
|
</div>
|
|
@@ -101,6 +101,12 @@ const { mapMutations, mapGetters } = createNamespacedHelpers('product');
|
|
|
101
101
|
export default {
|
|
102
102
|
name: 'MobileEditorProductDetails',
|
|
103
103
|
mixins: [addToCartMixin],
|
|
104
|
+
props: {
|
|
105
|
+
isEditMode: {
|
|
106
|
+
type: Boolean,
|
|
107
|
+
default: true
|
|
108
|
+
}
|
|
109
|
+
},
|
|
104
110
|
components: {
|
|
105
111
|
EditorWorkspace,
|
|
106
112
|
EditorLayers,
|
|
@@ -156,14 +162,14 @@ export default {
|
|
|
156
162
|
|
|
157
163
|
const prevTab = this.currentTab;
|
|
158
164
|
|
|
159
|
-
if (this.isOpen) {
|
|
165
|
+
if (this.isOpen && this.isEditMode) {
|
|
160
166
|
this.hide(true);
|
|
161
167
|
}
|
|
162
168
|
|
|
163
169
|
this.$nextTick(() => {
|
|
164
170
|
this.isOpen = true;
|
|
165
171
|
|
|
166
|
-
if(tab === 'layers') {
|
|
172
|
+
if (!this.isEditMode && tab === 'layers') {
|
|
167
173
|
this.setEditModeSelectedLayer(false);
|
|
168
174
|
}
|
|
169
175
|
|
|
@@ -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
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import "@/assets/scss/variables";
|
|
2
|
+
|
|
1
3
|
.ProductPricingTiers {
|
|
2
4
|
&__title {
|
|
3
5
|
color: #0A0A0A;
|
|
@@ -21,6 +23,9 @@
|
|
|
21
23
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.10), 0 2px 4px -2px rgba(0, 0, 0, 0.10);
|
|
22
24
|
cursor: pointer;
|
|
23
25
|
transition: all 0.3s ease;
|
|
26
|
+
@media (max-width: $bp-extra-small-max) {
|
|
27
|
+
width: 132px;
|
|
28
|
+
}
|
|
24
29
|
&:hover {
|
|
25
30
|
transform: translateY(-3px);
|
|
26
31
|
box-shadow: 0 4px 8px -1px rgba(0, 0, 0, 0.30), 0 2px 4px -2px rgba(0, 0, 0, 0.30);
|
|
@@ -35,11 +40,20 @@
|
|
|
35
40
|
font-weight: 600;
|
|
36
41
|
line-height: 28px;
|
|
37
42
|
text-align: center;
|
|
43
|
+
@media (max-width: $bp-extra-small-max) {
|
|
44
|
+
padding: 8px 0;
|
|
45
|
+
font-size: 12px;
|
|
46
|
+
line-height: 20px;
|
|
47
|
+
}
|
|
38
48
|
span {
|
|
39
49
|
color: #194BB3;
|
|
40
50
|
font-size: 18px;
|
|
41
51
|
font-weight: 700;
|
|
42
52
|
line-height: 28px;
|
|
53
|
+
@media (max-width: $bp-extra-small-max) {
|
|
54
|
+
font-size: 14px;
|
|
55
|
+
line-height: 20px;
|
|
56
|
+
}
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
&-price {
|
|
@@ -50,11 +64,21 @@
|
|
|
50
64
|
margin-top: 6px;
|
|
51
65
|
padding: 4px;
|
|
52
66
|
text-align: center;
|
|
67
|
+
@media (max-width: $bp-extra-small-max) {
|
|
68
|
+
font-size: 10px;
|
|
69
|
+
line-height: 16px;
|
|
70
|
+
margin-top: 4px;
|
|
71
|
+
padding: 2px;
|
|
72
|
+
}
|
|
53
73
|
span {
|
|
54
74
|
color: #194BB3;
|
|
55
75
|
font-size: 12px;
|
|
56
76
|
font-weight: 700;
|
|
57
77
|
line-height: 20px;
|
|
78
|
+
@media (max-width: $bp-extra-small-max) {
|
|
79
|
+
font-size: 10px;
|
|
80
|
+
line-height: 16px;
|
|
81
|
+
}
|
|
58
82
|
}
|
|
59
83
|
}
|
|
60
84
|
}
|
|
@@ -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,8 +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
|
-
const image = (this.isEditor && this.mainProductImage
|
|
129
|
-
return
|
|
128
|
+
const image = (this.isEditor && this.mainProductImage?.extralarge) || this.mainProductImage?.large;
|
|
129
|
+
return image && staticLink(image);
|
|
130
130
|
},
|
|
131
131
|
mainProductImageStyles() {
|
|
132
132
|
return this.mainProductImageSrc ? { 'background-image': `url(${this.mainProductImageSrc});` } : {};
|