@lancom/shared 0.0.325 → 0.0.327

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.
@@ -146,6 +146,9 @@ export default {
146
146
  markShipmentAsDispatched(order, shipment) {
147
147
  return _post(`admin/shop/${order.shop?._id || order.shop}/order/${order._id}/shipment/${shipment._id || shipment.guid}/dispatched`, shipment);
148
148
  },
149
+ markShipmentAsHasException(order, shipment) {
150
+ return _post(`admin/shop/${order.shop?._id || order.shop}/order/${order._id}/shipment/${shipment._id || shipment.guid}/has-exception`, shipment);
151
+ },
149
152
  markSubOrderAsDispatched(order, subOrder) {
150
153
  return _post(`admin/shop/${order.shop?._id || order.shop}/order/${order._id}/sub-order/${subOrder._id}/dispatched`, subOrder);
151
154
  },
@@ -197,6 +200,18 @@ export default {
197
200
  removePage(id) {
198
201
  return _delete(`admin/pages/${id}`);
199
202
  },
203
+ fetchCategories(params) {
204
+ return _get('admin/categories', params);
205
+ },
206
+ fetchCategoryById(id) {
207
+ return _get(`admin/categories/${id}`);
208
+ },
209
+ saveCategory(category) {
210
+ return category._id ? _put(`admin/categories/${category._id}`, category) : _post('admin/categories', category);
211
+ },
212
+ removeCategory(id) {
213
+ return _delete(`admin/categories/${id}`);
214
+ },
200
215
  fetchPackages() {
201
216
  return _get('admin/packages');
202
217
  },
@@ -728,5 +743,8 @@ export default {
728
743
  },
729
744
  findResources(query) {
730
745
  return _get(`admin/order/resources?search=${query || ''}`);
731
- }
746
+ },
747
+ getReOrderReport(params) {
748
+ return _get(`admin/reports/re-order`, params);
749
+ },
732
750
  };
@@ -98,12 +98,24 @@ export function filterBigSize(simpleProducts) {
98
98
  // };
99
99
  // };
100
100
 
101
- export function generateProductsLink($route, data) {
102
- let { type, brand, text, sort } = data;
103
- type = (type || type === null) ? type : ($route && $route.params.type);
104
- brand = (brand || brand === null) ? brand : ($route && $route.params.brand);
105
- text = (text || text === '') ? text : ($route && $route.query.text);
106
- sort = (sort || sort === '') ? sort : ($route && $route.query.sort);
101
+ function getCategoryPathFromRoute(routeParams) {
102
+ const categories = [];
103
+ for (let i = 5; i >= 0; i--) {
104
+ const category = routeParams?.[`category${!i ? '' : i}`];
105
+ if (category) {
106
+ categories.push(category);
107
+ }
108
+ }
109
+ return categories.join('/');
110
+ }
111
+
112
+ export function generateProductsLink($route, data, skipParams = false) {
113
+ let { type, category, brand, text, sort } = data;
114
+ category = (category || category === null) ? generateCategoryPath(category) : getCategoryPathFromRoute($route?.params);
115
+ type = (type || type === null) ? type : ($route?.params?.type || $route?.query?.type);
116
+ brand = (brand || brand === null) ? brand : ($route?.params?.brand || $route?.query?.brand);
117
+ text = (text || text === '') ? text : $route?.query?.text;
118
+ sort = (sort || sort === '') ? sort : $route?.query?.sort;
107
119
 
108
120
  const params = [];
109
121
 
@@ -120,7 +132,8 @@ export function generateProductsLink($route, data) {
120
132
  }
121
133
 
122
134
  Object.keys({ ...(($route && $route.query) || {}), ...data }).forEach(key => {
123
- if (!['text', 'sort', 'page', 'type', 'brand'].includes(key)) {
135
+ const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand'];
136
+ if (!rootKeys.includes(key)) {
124
137
  let items = ($route && $route.query[key]) ? $route.query[key].split(',') : [];
125
138
  items = (data[key] ? (items.includes(data[key]) ? items.filter(c => c !== data[key]) : [...items, data[key]]) : items).join(',');
126
139
  if (items) {
@@ -128,16 +141,48 @@ export function generateProductsLink($route, data) {
128
141
  }
129
142
  }
130
143
  });
144
+ if (category) {
145
+ if (brand) {
146
+ params.push(`brand=${brand}`);
147
+ }
148
+ if (type) {
149
+ params.push(`type=${type}`);
150
+ }
151
+ }
131
152
 
132
- const typeRoute = type ? `/${type}` : '';
133
- const brandRoute = brand ? `/brand/${brand}` : '';
153
+ const categoryRoute = category ? `/c/${category}` : '';
154
+ const typeRoute = (!category && type) ? `/${type}` : '';
155
+ const brandRoute = (!category && brand) ? `/brand/${brand}` : '';
134
156
 
135
- return `/products${typeRoute}${brandRoute}${params.length ? `?${params.join('&')}` : ''}`;
157
+ const baseRoute = categoryRoute || `/products${typeRoute}${brandRoute}`;
158
+ return `${baseRoute}${(params.length && !skipParams) ? `?${params.join('&')}` : ''}`;
159
+ }
160
+
161
+ export function generateCategoryPath(cat) {
162
+ let category = cat;
163
+ const categories = [];
164
+ while (category) {
165
+ categories.unshift(category.alias);
166
+ category = category.parent;
167
+ }
168
+ let baseLink = '';
169
+ if (categories.length) {
170
+ baseLink = `${categories.join('/')}`;
171
+ }
172
+ return baseLink;
136
173
  }
137
174
 
138
175
  export function generateProductLink(product, color, toEditor = false) {
139
176
  const baseLink = `/${product.brand?.alias}/${product.productType?.alias}/${product.alias}`;
140
- return `${toEditor ? `${baseLink}/editor` : baseLink}${color ? `?color=${color.alias}` : ''}`;
177
+ let link = toEditor ? `${baseLink}/editor` : baseLink;
178
+ if (product.useTaxonomyUrl) {
179
+ const categoryPath = generateCategoryPath(product.category);
180
+ if (categoryPath) {
181
+ const categoryBaseLink = `/c/${categoryPath}/${product.alias}/${product.SKU.toLowerCase()}`;
182
+ link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}/info.html`;
183
+ }
184
+ }
185
+ return `${link}${color ? `?color=${color.alias || color}` : ''}`;
141
186
  }
142
187
 
143
188
  export function getProductColorImages(product, color, skipDefault = false) {
@@ -54,7 +54,7 @@ export default {
54
54
  return this.brands;
55
55
  },
56
56
  currentBrand() {
57
- return this.$route.params.brand;
57
+ return this.$route.params.brand || this.$route.query.brand;
58
58
  }
59
59
  }
60
60
  };
@@ -12,6 +12,9 @@ import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
12
12
  export default {
13
13
  name: 'ProductsLink',
14
14
  props: {
15
+ category: {
16
+ type: String
17
+ },
15
18
  type: {
16
19
  type: String
17
20
  },
@@ -37,6 +40,7 @@ export default {
37
40
  computed: {
38
41
  link() {
39
42
  const params = {
43
+ category: this.category,
40
44
  type: this.type,
41
45
  brand: this.brand,
42
46
  tags: this.tags,
@@ -8,9 +8,9 @@
8
8
  :class="{
9
9
  'ProductsTypes__item--active': type.alias === currentType
10
10
  }">
11
- <a
12
- :href="`/products${type.alias === currentType ? '' : `/${type.alias}`}`"
13
- class="ProductsTypes__more">
11
+ <products-link
12
+ class="ProductsTypes__more"
13
+ :type="type.alias === currentType ? null : type.alias">
14
14
  <checked-icon
15
15
  v-if="hasSelectedIcon"
16
16
  :checked="type.alias === currentType"
@@ -25,7 +25,7 @@
25
25
  v-else-if="hasThumbs"
26
26
  :class="`icon-${type.alias}`"></i>
27
27
  {{ type.name }}
28
- </a>
28
+ </products-link>
29
29
  </div>
30
30
  </toggle-content>
31
31
  </div>
@@ -35,12 +35,14 @@
35
35
  import { mapGetters } from 'vuex';
36
36
  import ToggleContent from '@lancom/shared/components/common/toggle-content';
37
37
  import CheckedIcon from '@lancom/shared/components/common/checked-icon';
38
+ import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
38
39
 
39
40
  export default {
40
41
  name: 'ProductsTypes',
41
42
  components: {
42
43
  ToggleContent,
43
- CheckedIcon
44
+ CheckedIcon,
45
+ ProductsLink
44
46
  },
45
47
  props: {
46
48
  hasSelectedIcon: {
@@ -59,7 +61,7 @@ export default {
59
61
  computed: {
60
62
  ...mapGetters('products', ['types']),
61
63
  currentType() {
62
- return this.$route.params.type;
64
+ return this.$route.params.type || this.$route.query.type;
63
65
  }
64
66
  }
65
67
  };
@@ -98,27 +98,51 @@
98
98
  computed: {
99
99
  ...mapGetters('page', ['routeInfo']),
100
100
  ...mapGetters(['notificationBar', 'shop', 'country', 'currency']),
101
- ...mapGetters('products', ['brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
101
+ ...mapGetters('products', ['category', 'categories', 'brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
102
+ pageItemCanonical() {
103
+ return this.breadcrumbs[this.breadcrumbs.length - 1]?.to;
104
+ },
102
105
  currentBrand() {
103
106
  return this.findByRouteParam(this.brands, 'brand');
104
107
  },
105
108
  currentTag() {
106
- return this.findByRouteParam(this.tags, 'category');
109
+ return this.findByRouteParam(this.tags, 'tag');
107
110
  },
108
111
  currentProductType() {
109
112
  return this.findByRouteParam(this.types, 'type');
110
113
  },
114
+ currentCategory() {
115
+ return this.category;
116
+ },
111
117
  breadcrumbs() {
112
118
  const breadcrumbs = [{
113
119
  to: '/products',
114
120
  text: 'Products'
115
121
  }];
116
-
122
+ if (this.currentCategory) {
123
+ const categoriesBreadcrumbs = [];
124
+ let category = this.currentCategory;
125
+ console.log('category: ', category);
126
+ while (category) {
127
+ categoriesBreadcrumbs.unshift({
128
+ to: generateProductsLink(this.$route, {
129
+ type: null,
130
+ brand: null,
131
+ tag: null,
132
+ category
133
+ }, true),
134
+ text: category.name
135
+ });
136
+ category = category.parent;
137
+ }
138
+ breadcrumbs.push(...categoriesBreadcrumbs);
139
+ }
117
140
  if (this.currentProductType) {
118
141
  breadcrumbs.push({
119
142
  to: generateProductsLink(this.$route, {
120
143
  type: this.currentProductType.alias,
121
144
  brand: null,
145
+ tag: null,
122
146
  category: null
123
147
  }),
124
148
  text: this.currentProductType.name
@@ -129,8 +153,9 @@
129
153
  breadcrumbs.push({
130
154
  to: generateProductsLink(this.$route, {
131
155
  type: this.currentProductType?.alias,
132
- category: this.currentTag?.alias,
133
- brand: null
156
+ tag: this.currentTag?.alias,
157
+ brand: null,
158
+ category: null
134
159
  }),
135
160
  text: this.currentTag.name
136
161
  });
@@ -140,8 +165,9 @@
140
165
  breadcrumbs.push({
141
166
  to: generateProductsLink(this.$route, {
142
167
  type: this.currentProductType?.alias,
143
- category: this.currentTag?.alias,
144
- brand: this.currentBrand?.alias
168
+ tag: this.currentTag?.alias,
169
+ brand: this.currentBrand?.alias,
170
+ category: this.currentCategory?.alias
145
171
  }),
146
172
  text: this.currentBrand.name
147
173
  });
@@ -155,6 +181,9 @@
155
181
  }
156
182
 
157
183
  const items = ['Products'];
184
+ if (this.currentCategory) {
185
+ items.push(this.currentCategory.name);
186
+ }
158
187
  if (this.currentProductType) {
159
188
  items.push(this.currentProductType.name);
160
189
  }
@@ -304,7 +333,8 @@
304
333
  fillWithItemData(text = '') {
305
334
  text = this.currentBrand ? text.replace(/{{brandName}}/g, this.currentBrand.name) : text;
306
335
  text = this.currentProductType ? text.replace(/{{typeName}}/g, this.currentProductType.name) : text;
307
- text = this.currentTag ? text.replace(/{{categoryName}}/g, this.currentTag.name) : text;
336
+ text = this.currentTag ? text.replace(/{{tagName}}/g, this.currentTag.name) : text;
337
+ text = this.currentCategory ? text.replace(/{{categoryName}}/g, this.currentCategory.name) : text;
308
338
  return text;
309
339
  },
310
340
  async openAside() {
@@ -19,7 +19,7 @@ const metaInfo = {
19
19
  },
20
20
  methods: {
21
21
  getCanonical() {
22
- return this.$route.path === '/' ? '' : this.$route.path;
22
+ return this.$route.path === '/' ? '' : (this.pageItemCanonical || this.$route.path);
23
23
  },
24
24
  generateTitleFromRoute() {
25
25
  return this.$route.path
@@ -7,17 +7,17 @@ import { tax, staticLink, inRange } from '@lancom/shared/assets/js/utils/filters
7
7
  import { getProductLargeCover } from '@lancom/shared/assets/js/utils/colors';
8
8
  import metaInfo from '@lancom/shared/mixins/meta-info';
9
9
  import { STORE_CODES } from '@/constants/store';
10
-
10
+ import { generateProductLink, generateProductsLink } from '@lancom/shared/assets/js/utils/product';
11
11
  const { mapActions, mapMutations } = createNamespacedHelpers('product');
12
12
 
13
- export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
13
+ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
14
14
  mixins: [metaInfo],
15
15
  transition(to, from) {
16
16
  if (from && from.name === 'brand-tees-slug') { return 'slide-left-to-right'; };
17
17
  if (to && to.name === 'brand-tees-slug') { return 'slide-right-to-left'; };
18
18
  return 'fade';
19
19
  },
20
- async asyncData({ store, params, error, query }) {
20
+ async asyncData({ store, params, error, query, redirect }) {
21
21
  try {
22
22
  const { print, color, colour } = query;
23
23
  const { shop, country, currency } = store.getters;
@@ -29,10 +29,20 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
29
29
  print,
30
30
  defaultColor: color || colour
31
31
  };
32
+
32
33
  await store.dispatch('product/fetchProduct', data);
33
34
  await store.dispatch('product/fetchProductDetails', data);
34
35
 
35
36
  const product = store.getters['product/product'];
37
+
38
+ if ((product.useTaxonomyUrl && !params.sku) || (!product.useTaxonomyUrl && params.sku)) {
39
+ const redirectUrl = generateProductLink(product, data.defaultColor, isEditor);
40
+ return redirect(301, redirectUrl);
41
+ }
42
+
43
+ if (params.sku && params.sku !== product?.SKU?.toLowerCase()) {
44
+ return error({ statusCode: 404, message: 'Product not found' });
45
+ }
36
46
  if (IS_PRODUCT_PRESET_PRINT_PRICING) {
37
47
  const printType = product.printTypes[0];
38
48
  store.commit('product/setSelectedPrintType', printType);
@@ -69,6 +79,9 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
69
79
  pageItemImage() {
70
80
  return this.mainProductImageSrc;
71
81
  },
82
+ pageItemCanonical() {
83
+ return generateProductLink(this.product);
84
+ },
72
85
  hasImages() {
73
86
  return this.images.length > 0;
74
87
  },
@@ -181,7 +194,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
181
194
  }
182
195
  },
183
196
  fillBreadcrumbs() {
184
- this.breadcrumbs = [{
197
+ const breadcrumbs = [{
185
198
  to: '/products',
186
199
  text: 'Products'
187
200
  }, {
@@ -190,6 +203,19 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING) => ({
190
203
  }, {
191
204
  text: this.product.name
192
205
  }];
206
+ if (this.product.useTaxonomyUrl && this.product.category) {
207
+ const categoriesBreadcrumbs = [];
208
+ let category = this.product.category;
209
+ while (category) {
210
+ categoriesBreadcrumbs.unshift({
211
+ to: generateProductsLink(null, { category }, true),
212
+ text: category.name
213
+ });
214
+ category = category.parent;
215
+ }
216
+ breadcrumbs.splice(1, 1, ...categoriesBreadcrumbs);
217
+ }
218
+ this.breadcrumbs = breadcrumbs;
193
219
  }
194
220
  },
195
221
  getRoute() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.325",
3
+ "version": "0.0.327",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
package/routes/index.js CHANGED
@@ -1,5 +1,80 @@
1
1
  module.exports = function(routes, resolve) {
2
2
  return [{
3
+ name: 'product-taxonomy-view-level1',
4
+ path: '/c/:category/:slug/:sku/info.html',
5
+ component: resolve('@/pages/_brand/_type/_slug/index.vue'),
6
+ chunkName: 'pages/product/view/level1'
7
+ }, {
8
+ name: 'product-taxonomy-view-level2',
9
+ path: '/c/:category1/:category/:slug/:sku/info.html',
10
+ component: resolve('@/pages/_brand/_type/_slug/index.vue'),
11
+ chunkName: 'pages/product/view/level2'
12
+ }, {
13
+ name: 'product-taxonomy-view-level3',
14
+ path: '/c/:category2/:category1/:category/:slug/:sku/info.html',
15
+ component: resolve('@/pages/_brand/_type/_slug/index.vue'),
16
+ chunkName: 'pages/product/view/level3'
17
+ }, {
18
+ name: 'product-taxonomy-view-level4',
19
+ path: '/c/:category3/:category2/:category1/:category/:slug/:sku/info.html',
20
+ component: resolve('@/pages/_brand/_type/_slug/index.vue'),
21
+ chunkName: 'pages/product/view/level4'
22
+ }, {
23
+ name: 'product-taxonomy-view-level5',
24
+ path: '/c/:category4/:category3/:category2/:category1/:category/:slug/:sku/info.html',
25
+ component: resolve('@/pages/_brand/_type/_slug/index.vue'),
26
+ chunkName: 'pages/product/view/level5'
27
+ }, {
28
+ name: 'product-taxonomy-editor-level1',
29
+ path: '/c/:category/:slug/:sku.html',
30
+ component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
31
+ chunkName: 'pages/product/editor/level1'
32
+ }, {
33
+ name: 'product-taxonomy-editor-level2',
34
+ path: '/c/:category1/:category/:slug/:sku.html',
35
+ component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
36
+ chunkName: 'pages/product/editor/level2'
37
+ }, {
38
+ name: 'product-taxonomy-editor-level3',
39
+ path: '/c/:category2/:category1/:category/:slug/:sku.html',
40
+ component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
41
+ chunkName: 'pages/product/editor/level3'
42
+ }, {
43
+ name: 'product-taxonomy-editor-level4',
44
+ path: '/c/:category3/:category2/:category1/:category/:slug/:sku.html',
45
+ component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
46
+ chunkName: 'pages/product/editor/level4'
47
+ }, {
48
+ name: 'product-taxonomy-editor-level5',
49
+ path: '/c/:category4/:category3/:category2/:category1/:category/:slug/:sku.html',
50
+ component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
51
+ chunkName: 'pages/product/editor/level5'
52
+ }, {
53
+ name: 'products-taxonomy-catalog-level1',
54
+ path: '/c/:category',
55
+ component: resolve('@/pages/products/index.vue'),
56
+ chunkName: 'pages/products/catalog/level1'
57
+ }, {
58
+ name: 'products-taxonomy-catalog-level2',
59
+ path: '/c/:category1/:category',
60
+ component: resolve('@/pages/products/index.vue'),
61
+ chunkName: 'pages/products/catalog/level2'
62
+ }, {
63
+ name: 'products-taxonomy-catalog-level3',
64
+ path: '/c/:category2/:category1/:category',
65
+ component: resolve('@/pages/products/index.vue'),
66
+ chunkName: 'pages/products/catalog/level3'
67
+ }, {
68
+ name: 'products-taxonomy-catalog-level4',
69
+ path: '/c/:category3/:category2/:category1/:category',
70
+ component: resolve('@/pages/products/index.vue'),
71
+ chunkName: 'pages/products/catalog/level4'
72
+ }, {
73
+ name: 'products-taxonomy-catalog-level5',
74
+ path: '/c/:category4/:category3/:category2/:category1/:category',
75
+ component: resolve('@/pages/products/index.vue'),
76
+ chunkName: 'pages/products/catalog/level5'
77
+ }, {
3
78
  name: 'order-view',
4
79
  path: '/order/:token',
5
80
  component: resolve('@lancom/shared/pages/order/_token/index.vue'),
package/store/products.js CHANGED
@@ -10,6 +10,8 @@ export const state = () => ({
10
10
  types: [],
11
11
  colors: [],
12
12
  brands: [],
13
+ category: null,
14
+ categories: [],
13
15
  tags: [],
14
16
  attributes: [],
15
17
  loadError: null,
@@ -21,6 +23,8 @@ export const getters = {
21
23
  types: ({ types }) => types || [],
22
24
  colors: ({ colors }) => colors || [],
23
25
  brands: ({ brands }) => brands || [],
26
+ categories: ({ categories }) => categories || [],
27
+ category: ({ category }) => category,
24
28
  tags: ({ tags }) => tags || [],
25
29
  attributes: ({ attributes }) => attributes || [],
26
30
  page: ({ page }) => page,
@@ -46,7 +50,7 @@ export const actions = {
46
50
  commit('setLoadError', null);
47
51
  const {
48
52
  products, count, page, perPage, productTypes, colors, brands, tags, attributes,
49
- minPrice, maxPrice
53
+ categories, minPrice, maxPrice, category
50
54
  } = await api.fetchProducts(shop, params);
51
55
  commit('setProducts', products);
52
56
  commit('setPage', page);
@@ -55,6 +59,8 @@ export const actions = {
55
59
  commit('setTypes', productTypes);
56
60
  commit('setColors', colors);
57
61
  commit('setBrands', brands);
62
+ commit('setCategories', categories);
63
+ commit('setCategory', category);
58
64
  commit('setTags', tags);
59
65
  commit('setAttributes', attributes);
60
66
  commit('setMinPrice', minPrice);
@@ -104,6 +110,12 @@ export const mutations = {
104
110
  setBrands(state, brands) {
105
111
  state.brands = brands;
106
112
  },
113
+ setCategories(state, categories) {
114
+ state.categories = categories;
115
+ },
116
+ setCategory(state, category) {
117
+ state.category = category;
118
+ },
107
119
  setTags(state, tags) {
108
120
  state.tags = tags;
109
121
  },