@rocketsearch/vue-uappi 0.1.1 → 0.1.3

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.1",
3
+ "version": "0.1.3",
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,60 @@
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
+ const key = this.key;
54
+ const showcases = key ? await this.$rocket.getPageShowcases('custom', key, true) : [];
55
+ // Troca rápida de rota: a resposta da landing anterior não sobrescreve a atual.
56
+ if (key === this.key) this.showcases = showcases;
57
+ }
58
+ }
59
+ };
60
+ </script>
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <div
3
- v-if="showcase || loading || debug"
4
- :class="['rocket-search', { 'rocket-search--empty': !showcase && !loading }]"
5
- :data-rocket-showcase-position="position"
3
+ v-if="showcase || loading || debug || restIds.length"
4
+ :class="['rocket-search', { 'rocket-search--empty': !showcase && !loading && !restIds.length, 'rocket-search--stacked': stacked }]"
5
+ :data-rocket-showcase-position="stacked ? null : position"
6
6
  >
7
7
  <div v-if="loading" class="rocket-search__showcase rocket-search__showcase--skeleton" :style="columnsStyle">
8
8
  <div class="rocket-search__showcase__title rocket-search__product-placeholder__item" />
@@ -47,6 +47,26 @@
47
47
  <button type="button" class="rocket-search__swiper-button-next" aria-label="Próximos" @click="slide(1)" />
48
48
  </slot>
49
49
  </section>
50
+
51
+ <!-- Demais vitrines da mesma posição, na ordem da API: cada uma medida por conta própria. -->
52
+ <RocketShowcase
53
+ v-for="id in restIds"
54
+ :key="id"
55
+ :position="position"
56
+ :page-type="pageType"
57
+ :custom-key="customKey"
58
+ :showcase-id="id"
59
+ :media-size="mediaSize"
60
+ stacked
61
+ @products-loaded="$emit('products-loaded', $event)"
62
+ >
63
+ <template v-if="$slots.default" #default="scope">
64
+ <slot v-bind="scope" />
65
+ </template>
66
+ <template v-if="$slots.product" #product="scope">
67
+ <slot name="product" v-bind="scope" />
68
+ </template>
69
+ </RocketShowcase>
50
70
  </div>
51
71
  </template>
52
72
 
@@ -69,10 +89,17 @@ export default {
69
89
  position: { type: String, required: true },
70
90
  // Força o pageType; sem ele vem da página atual (currentPageData do tema).
71
91
  pageType: { type: String, default: '' },
72
- mediaSize: { type: String, default: 'medium' }
92
+ // Landing (page-type="custom"): identificador cadastrado no painel em "Personalizado".
93
+ customKey: { type: String, default: '' },
94
+ // Vitrine específica (a landing tem várias na mesma posição); sem ele, todas as da posição.
95
+ showcaseId: { type: [String, Number], default: null },
96
+ mediaSize: { type: String, default: 'medium' },
97
+ // Uso interno: vitrine empilhada abaixo da primeira da posição. Sem o atributo de posição,
98
+ // o debug contorna a posição uma vez só.
99
+ stacked: { type: Boolean, default: false }
73
100
  },
74
101
  data () {
75
- return { showcase: null, products: [], loading: false, debug: false };
102
+ return { showcase: null, products: [], restIds: [], loading: false, debug: false };
76
103
  },
77
104
  computed: {
78
105
  columns () {
@@ -117,9 +144,14 @@ export default {
117
144
  if (this.started) return;
118
145
  this.started = true;
119
146
  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);
147
+ const showcases = await this.$rocket.getPageShowcases(this.pageType || undefined, this.customKey || undefined);
148
+ const inPosition = showcases.filter(s => ((s.placement && s.placement.position) || s.position) === this.position);
149
+ const showcase = this.showcaseId != null
150
+ ? showcases.find(s => String(s.id) === String(this.showcaseId))
151
+ : inPosition[0];
122
152
  if (!showcase) return;
153
+ // Posição sem showcaseId: este slot mostra a primeira e empilha as demais logo abaixo.
154
+ if (this.showcaseId == null) this.restIds = inPosition.slice(1).map(s => s.id);
123
155
  // Banner autônomo: render direto, sem produtos, skeleton nem impressão de vitrine
124
156
  // (o RocketBannerItem mede o criativo).
125
157
  if (isBannerItem(showcase)) {
@@ -6,6 +6,11 @@
6
6
  margin-bottom: 60px;
7
7
  }
8
8
 
9
+ /* Segunda vitrine em diante na mesma posição: o mesmo respiro entre elas. */
10
+ .rocket-search--stacked {
11
+ margin-top: 60px;
12
+ }
13
+
9
14
  .rocket-search__showcase {
10
15
  --showcase-gap: 16px;
11
16
  position: relative;
package/runtime/rocket.js CHANGED
@@ -122,7 +122,10 @@ 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
+ // A landing (custom) não depende do contexto da página e fica: senão, um setPage entre a busca
126
+ // do RocketLanding e a montagem das vitrines dele dispararia uma segunda chamada. Ela se
127
+ // renova quando o RocketLanding monta (refresh).
128
+ Object.keys(pageShowcases).forEach((k) => { if (k.indexOf('custom|') !== 0) delete pageShowcases[k]; });
126
129
  };
127
130
  rocket.getPage = () => page;
128
131
 
@@ -294,13 +297,15 @@ export function createRocket (config) {
294
297
  return res.json();
295
298
  };
296
299
 
297
- rocket.getShowcasesByContext = async function (type) {
300
+ // customKey: identificador da landing (pageType=custom); sem ele a API responde 400.
301
+ rocket.getShowcasesByContext = async function (type, customKey) {
298
302
  const pageType = resolvePageType(type);
299
303
  if (!pageType) return [];
300
304
  const sessionId = await getSessionIdWithTimeout();
301
305
  const params = { storeId: config.storeId, pageType, page: pageType, sessionId };
302
306
  if (pageType === 'product' && page.productId) params.productId = page.productId;
303
307
  if (pageType === 'category' && page.categoryId) params.categoryId = page.categoryId;
308
+ if (pageType === 'custom') params.customKey = customKey;
304
309
 
305
310
  const res = await fetch(config.apiUrl + '/showcases/context?' + query(params), { headers: headers() });
306
311
  const showcases = await res.json();
@@ -317,11 +322,13 @@ export function createRocket (config) {
317
322
  };
318
323
 
319
324
  // 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;
325
+ // era o fetchShowCases único distribuindo por data-rocket-showcase-position. Por pageType +
326
+ // customKey: a landing e o contexto da página são chamadas distintas. O setPage zera só o contexto.
327
+ let pageShowcases = {};
328
+ rocket.getPageShowcases = function (type, customKey, refresh) {
329
+ const key = (type || '') + '|' + (customKey || '');
330
+ if (refresh || !pageShowcases[key]) pageShowcases[key] = rocket.getShowcasesByContext(type, customKey).catch(() => []);
331
+ return pageShowcases[key];
325
332
  };
326
333
 
327
334
  // Só banners (DTO banner-native). Gated pela flag `banners`: loja sem ela recebe [].