@rocketsearch/vue-uappi 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -27,4 +27,7 @@ optimizeDeps: { exclude: ['@rocketsearch/vue-uappi'] }
27
27
  Use os componentes em PascalCase (`<RocketSearch>`, `<RocketShowcase position="top">`): o tema pode ter
28
28
  `isCustomElement` para tags com hífen.
29
29
 
30
+ Landing page ("Personalizado" no painel): `<RocketLanding>` mostra todas as vitrines da landing, na ordem do
31
+ painel. O identificador vem do nome da rota (`/black-friday` → `black-friday`) ou de `custom-key="..."`.
32
+
30
33
  Documentação completa: https://api.rocketsearch.com.br/sdk/uappi/docs.html
package/index.js CHANGED
@@ -109,6 +109,7 @@ export default {
109
109
  app.component('RocketBanner', defineAsyncComponent(() => import('./runtime/components/RocketBanner.vue')));
110
110
  app.component('RocketSearch', defineAsyncComponent(() => import('./runtime/components/RocketSearch.vue')));
111
111
  app.component('RocketShowcase', defineAsyncComponent(() => import('./runtime/components/RocketShowcase.vue')));
112
+ app.component('RocketLanding', defineAsyncComponent(() => import('./runtime/components/RocketLanding.vue')));
112
113
 
113
114
  // Conexão fria + preflight CORS era o grosso da latência do storefront, não a busca.
114
115
  const preconnect = document.createElement('link');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocketsearch/vue-uappi",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Rocket Search para storefronts Uappi em Vue 3 + Vite + Pinia: busca, autocomplete, vitrines, banners e eventos",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <RocketShowcase
3
+ v-for="showcase in showcases"
4
+ :key="showcase.id"
5
+ page-type="custom"
6
+ :custom-key="key"
7
+ :showcase-id="showcase.id"
8
+ :position="(showcase.placement && showcase.placement.position) || 'middle'"
9
+ :media-size="mediaSize"
10
+ >
11
+ <template v-if="$slots.default" #default="scope">
12
+ <slot v-bind="scope" />
13
+ </template>
14
+ <template v-if="$slots.product" #product="scope">
15
+ <slot name="product" v-bind="scope" />
16
+ </template>
17
+ </RocketShowcase>
18
+ </template>
19
+
20
+ <script>
21
+ import RocketShowcase from './RocketShowcase.vue';
22
+
23
+ // Landing page ("Personalizado" no painel): todas as vitrines da landing, empilhadas pela ordem
24
+ // do painel (a API já responde por display_order). Sem elemento próprio: as vitrines entram
25
+ // direto no container do tema e herdam o espaçamento dele.
26
+ export default {
27
+ name: 'RocketLanding',
28
+ components: { RocketShowcase },
29
+ inheritAttrs: false,
30
+ props: {
31
+ // Identificador da landing no painel; sem ele, o nome da rota (/black-friday -> black-friday).
32
+ customKey: { type: String, default: '' },
33
+ mediaSize: { type: String, default: 'medium' }
34
+ },
35
+ data () {
36
+ return { showcases: [] };
37
+ },
38
+ computed: {
39
+ key () {
40
+ if (this.customKey) return this.customKey;
41
+ const path = (this.$route && this.$route.path) || location.pathname;
42
+ return path.split('/').filter(Boolean).pop() || '';
43
+ }
44
+ },
45
+ watch: {
46
+ key: 'load'
47
+ },
48
+ mounted () {
49
+ this.load();
50
+ },
51
+ methods: {
52
+ async load () {
53
+ this.showcases = this.key ? await this.$rocket.getPageShowcases('custom', this.key) : [];
54
+ }
55
+ }
56
+ };
57
+ </script>
@@ -69,6 +69,10 @@ export default {
69
69
  position: { type: String, required: true },
70
70
  // Força o pageType; sem ele vem da página atual (currentPageData do tema).
71
71
  pageType: { type: String, default: '' },
72
+ // Landing (page-type="custom"): identificador cadastrado no painel em "Personalizado".
73
+ customKey: { type: String, default: '' },
74
+ // Vitrine específica (a landing tem várias na mesma posição); sem ele, a primeira da posição.
75
+ showcaseId: { type: [String, Number], default: null },
72
76
  mediaSize: { type: String, default: 'medium' }
73
77
  },
74
78
  data () {
@@ -117,8 +121,10 @@ export default {
117
121
  if (this.started) return;
118
122
  this.started = true;
119
123
  try {
120
- const showcases = await this.$rocket.getPageShowcases(this.pageType || undefined);
121
- const showcase = showcases.find(s => ((s.placement && s.placement.position) || s.position) === this.position);
124
+ const showcases = await this.$rocket.getPageShowcases(this.pageType || undefined, this.customKey || undefined);
125
+ const showcase = showcases.find(s => this.showcaseId != null
126
+ ? String(s.id) === String(this.showcaseId)
127
+ : ((s.placement && s.placement.position) || s.position) === this.position);
122
128
  if (!showcase) return;
123
129
  // Banner autônomo: render direto, sem produtos, skeleton nem impressão de vitrine
124
130
  // (o RocketBannerItem mede o criativo).
@@ -112,7 +112,7 @@ export function toUappiListing (response, products, rawQuery, options) {
112
112
  if (facet.type !== 'terms' || facet.field === 'hasStock' || !(facet.values || []).length) return;
113
113
  const values = facetValues(facet, query, path);
114
114
  if (facet.field === 'brand.name') marcas.push(...values);
115
- else atributos.push({ id: index + 1, nome: facet.label, tipo: 'texto', valores: values });
115
+ else atributos.push({ id: index + 1, nome: facet.label, tipo: 'texto', campo: facet.field, valores: values });
116
116
  });
117
117
 
118
118
  const ordenacao = ((response && response.sortOptions) || []).map(option => ({
@@ -194,9 +194,13 @@ async function withUappiProducts ($rocket, response, query, opts) {
194
194
  export function mergeIntoUappiPage (base, listing) {
195
195
  const conteudo = (base && base.conteudo) || {};
196
196
  const rocket = listing.conteudo.detalhes;
197
+ // A faceta de categoria da Rocket duplicaria a navegação de (sub)categorias da Uappi, que a página
198
+ // mantém. Sem essa navegação (coleção, por exemplo), ela fica: é o único filtro de categoria ali.
199
+ const nav = (conteudo.detalhes && conteudo.detalhes.categorias) || {};
200
+ const hasCategoryNav = [nav.filhas, nav.niveisAnteriores].some(list => Array.isArray(list) && list.length > 0);
197
201
  const detalhes = Object.assign({}, conteudo.detalhes, {
198
202
  marcas: rocket.marcas,
199
- atributos: rocket.atributos,
203
+ atributos: hasCategoryNav ? rocket.atributos.filter(a => a.campo !== 'category.name') : rocket.atributos,
200
204
  filtrosPreco: rocket.filtrosPreco,
201
205
  ordenacao: rocket.ordenacao,
202
206
  filtros: [],
package/runtime/rocket.js CHANGED
@@ -122,7 +122,7 @@ export function createRocket (config) {
122
122
 
123
123
  rocket.setPage = function (next) {
124
124
  page = Object.assign({ section: null, productId: null, categoryId: null, product: null }, next);
125
- pageShowcases = null;
125
+ pageShowcases = {};
126
126
  };
127
127
  rocket.getPage = () => page;
128
128
 
@@ -294,13 +294,15 @@ export function createRocket (config) {
294
294
  return res.json();
295
295
  };
296
296
 
297
- rocket.getShowcasesByContext = async function (type) {
297
+ // customKey: identificador da landing (pageType=custom); sem ele a API responde 400.
298
+ rocket.getShowcasesByContext = async function (type, customKey) {
298
299
  const pageType = resolvePageType(type);
299
300
  if (!pageType) return [];
300
301
  const sessionId = await getSessionIdWithTimeout();
301
302
  const params = { storeId: config.storeId, pageType, page: pageType, sessionId };
302
303
  if (pageType === 'product' && page.productId) params.productId = page.productId;
303
304
  if (pageType === 'category' && page.categoryId) params.categoryId = page.categoryId;
305
+ if (pageType === 'custom') params.customKey = customKey;
304
306
 
305
307
  const res = await fetch(config.apiUrl + '/showcases/context?' + query(params), { headers: headers() });
306
308
  const showcases = await res.json();
@@ -317,11 +319,13 @@ export function createRocket (config) {
317
319
  };
318
320
 
319
321
  // Uma chamada por página, repartida entre os slots (<RocketShowcase position>) — na Linx
320
- // era o fetchShowCases único distribuindo por data-rocket-showcase-position. Zera no setPage.
321
- let pageShowcases = null;
322
- rocket.getPageShowcases = function (type) {
323
- if (!pageShowcases) pageShowcases = rocket.getShowcasesByContext(type).catch(() => []);
324
- return pageShowcases;
322
+ // era o fetchShowCases único distribuindo por data-rocket-showcase-position. Por pageType +
323
+ // customKey: a landing e o contexto da página são chamadas distintas. Zera no setPage.
324
+ let pageShowcases = {};
325
+ rocket.getPageShowcases = function (type, customKey) {
326
+ const key = (type || '') + '|' + (customKey || '');
327
+ if (!pageShowcases[key]) pageShowcases[key] = rocket.getShowcasesByContext(type, customKey).catch(() => []);
328
+ return pageShowcases[key];
325
329
  };
326
330
 
327
331
  // Só banners (DTO banner-native). Gated pela flag `banners`: loja sem ela recebe [].