@astralweb/nova-recently-viewed 0.0.1 → 1.0.1
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/LICENSE +17 -16
- package/README.md +96 -233
- package/dist/module.d.mts +5 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +26 -0
- package/dist/{api → runtime/api}/extendMiddleware.d.ts +4 -5
- package/dist/runtime/api/extendMiddleware.js +64 -0
- package/dist/runtime/api/index.d.ts +1 -0
- package/dist/runtime/api/index.js +1 -0
- package/dist/runtime/composables/index.d.ts +3 -0
- package/dist/runtime/composables/index.js +3 -0
- package/dist/runtime/composables/useRecentlyViewedApi.d.ts +5 -0
- package/dist/runtime/composables/useRecentlyViewedApi.js +21 -0
- package/dist/runtime/composables/useRecentlyViewedList.d.ts +2 -0
- package/dist/runtime/composables/useRecentlyViewedList.js +67 -0
- package/dist/runtime/composables/useRecentlyViewedStorage.d.ts +3 -0
- package/dist/runtime/composables/useRecentlyViewedStorage.js +133 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/{types → runtime/types}/api.d.ts +0 -1
- package/dist/runtime/types/api.js +0 -0
- package/dist/{types → runtime/types}/composables.d.ts +11 -3
- package/dist/runtime/types/composables.js +0 -0
- package/dist/runtime/types/index.d.ts +2 -0
- package/dist/runtime/types/index.js +2 -0
- package/dist/types.d.mts +7 -0
- package/package.json +51 -61
- package/dist/api/extendMiddleware.d.ts.map +0 -1
- package/dist/api/index.d.ts +0 -4
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/queryFields.d.ts +0 -3
- package/dist/api/queryFields.d.ts.map +0 -1
- package/dist/api/web.d.ts +0 -27
- package/dist/api/web.d.ts.map +0 -1
- package/dist/api.cjs +0 -46
- package/dist/api.js +0 -2201
- package/dist/chunks/useRecentlyViewed-BMqCM4G9.js +0 -1
- package/dist/chunks/useRecentlyViewed-DmIwibiG.js +0 -96
- package/dist/chunks/web-DMlcd6E0.js +0 -1
- package/dist/chunks/web-WoqqfD6L.js +0 -26
- package/dist/composables/index.d.ts +0 -2
- package/dist/composables/index.d.ts.map +0 -1
- package/dist/composables/useRecentlyViewed.d.ts +0 -7
- package/dist/composables/useRecentlyViewed.d.ts.map +0 -1
- package/dist/composables.cjs +0 -1
- package/dist/composables.js +0 -4
- package/dist/index.cjs +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -14
- package/dist/types/api.d.ts.map +0 -1
- package/dist/types/composables.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types.cjs +0 -1
- package/dist/types.js +0 -1
package/LICENSE
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
PROPRIETARY LICENSE
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Astral Web
|
|
4
4
|
|
|
5
5
|
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
-
to Astral Web and are intended for internal use
|
|
6
|
+
to Astral Web and are intended solely for internal use by Astral Web and its
|
|
7
|
+
authorized personnel.
|
|
7
8
|
|
|
8
9
|
RESTRICTIONS:
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
- Reverse engineering, decompilation, or disassembly of this Software is prohibited.
|
|
10
|
+
- The Software may not be copied, modified, distributed, or used by any party
|
|
11
|
+
other than Astral Web without express written permission.
|
|
12
|
+
- No part of the Software may be reverse engineered, decompiled, or disassembled.
|
|
13
|
+
- The Software may not be sublicensed, sold, or transferred to any third party.
|
|
14
|
+
- Use of the Software is restricted to internal business operations of Astral Web.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
DISCLAIMER:
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
20
|
+
ASTRAL WEB BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
21
|
+
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
22
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
23
|
|
|
23
|
-
For
|
|
24
|
+
For licensing inquiries, contact: Astral Web
|
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Recently Viewed
|
|
1
|
+
# Recently Viewed 模組使用指南
|
|
2
2
|
|
|
3
3
|
> ⚠️ **內部使用套件** - 此套件僅供 Astral Web 內部使用,未經授權不得用於其他用途。
|
|
4
4
|
|
|
5
5
|
## 概覽
|
|
6
6
|
|
|
7
|
-
Recently Viewed
|
|
7
|
+
Recently Viewed 是一個 Nuxt 3 模組,提供完整的最近瀏覽商品追蹤與管理功能。模組採用 Nuxt Module 架構設計,支援本地儲存與伺服器同步、訪客與會員資料自動合併,並提供靈活的配置選項。
|
|
8
8
|
|
|
9
9
|
## 安裝與設定
|
|
10
10
|
|
|
@@ -16,296 +16,159 @@ Recently Viewed 套件採用 Composable 模式設計,提供完整的最近瀏
|
|
|
16
16
|
yarn add @astralweb/nova-recently-viewed
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
### 2.
|
|
19
|
+
### 2. Middleware 擴充
|
|
20
|
+
|
|
20
21
|
```typescript
|
|
21
22
|
// apps/server/extendApiMethods/index.ts
|
|
22
23
|
|
|
23
24
|
export {
|
|
24
|
-
recentlyViewedProducts,
|
|
25
|
+
recentlyViewedProducts,
|
|
25
26
|
addRecentlyViewedProducts,
|
|
26
27
|
} from '@astralweb/nova-recently-viewed/api';
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
### 3.
|
|
30
|
-
```typescript
|
|
31
|
-
// apps/web/plugins/sdk.client.ts
|
|
32
|
-
import { setSdk } from '@astralweb/nova-recently-viewed/api';
|
|
33
|
-
|
|
34
|
-
export default defineNuxtPlugin(() => {
|
|
35
|
-
// 設定 SDK 實例
|
|
36
|
-
setSdk(sdkInstance);
|
|
37
|
-
});
|
|
38
|
-
```
|
|
30
|
+
### 3. 註冊模組
|
|
39
31
|
|
|
40
|
-
### 4. 新增 nuxt.config.ts 設定
|
|
41
32
|
```typescript
|
|
33
|
+
// apps/web/nuxt.config.ts
|
|
34
|
+
|
|
42
35
|
export default defineNuxtConfig({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
},
|
|
36
|
+
modules: [
|
|
37
|
+
'@astralweb/nova-recently-viewed',
|
|
38
|
+
],
|
|
48
39
|
})
|
|
49
40
|
```
|
|
50
41
|
|
|
51
|
-
##
|
|
42
|
+
## 架構設計
|
|
43
|
+
|
|
44
|
+
### 三層架構
|
|
45
|
+
|
|
46
|
+
1. **API Layer** (`useRecentlyViewedApi`)
|
|
47
|
+
- 處理與 GraphQL 後端的通信
|
|
48
|
+
- 提供 `syncToServer` 和 `fetchFromServer` 方法
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
2. **Storage Layer** (`useRecentlyViewedStorage`)
|
|
51
|
+
- 管理本地儲存與核心業務邏輯
|
|
52
|
+
- 處理訪客/會員資料合併
|
|
53
|
+
- 提供完整的 CRUD 操作
|
|
54
|
+
|
|
55
|
+
3. **List Layer** (`useRecentlyViewedList`)
|
|
56
|
+
- 高階封裝,適用於產品詳情頁
|
|
57
|
+
- 自動處理初始化、登入狀態變化、SKU 更新
|
|
58
|
+
- 提供開箱即用的體驗
|
|
59
|
+
|
|
60
|
+
## 使用方式
|
|
61
|
+
|
|
62
|
+
適合產品詳情頁,自動處理所有邏輯。
|
|
54
63
|
|
|
55
64
|
```vue
|
|
56
65
|
<template>
|
|
57
|
-
<
|
|
58
|
-
<!-- 商品內容 -->
|
|
59
|
-
<ProductDetails :product="product" />
|
|
60
|
-
|
|
61
|
-
<!-- 最近瀏覽商品區塊 -->
|
|
62
|
-
<section v-if="recentlyViewedSkus && recentlyViewedSkus.length > 0" class="mb-10">
|
|
63
|
-
<ProductSlider
|
|
64
|
-
:title="t('product.section.recentlyViewed')"
|
|
65
|
-
:products-sku="recentlyViewedSkus"
|
|
66
|
-
maintain-order
|
|
67
|
-
/>
|
|
68
|
-
</section>
|
|
69
|
-
</div>
|
|
66
|
+
<ProductSlider :products-sku="recentlyViewedSkus" />
|
|
70
67
|
</template>
|
|
71
68
|
|
|
72
69
|
<script setup lang="ts">
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// 獲取當前商品資料
|
|
79
|
-
const { data: product } = await useAsyncData('product', () =>
|
|
80
|
-
fetchProduct(route.params.slug)
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
// 使用最近瀏覽功能
|
|
84
|
-
const {
|
|
85
|
-
addRecentlyViewed,
|
|
86
|
-
getRecentlyViewedSkus,
|
|
87
|
-
mergeGuestToCustomer,
|
|
88
|
-
isLoggedIn
|
|
89
|
-
} = useRecentlyViewed();
|
|
90
|
-
|
|
91
|
-
const recentlyViewedSkus = ref<string[]>([]);
|
|
92
|
-
|
|
93
|
-
// 初始化最近瀏覽商品
|
|
94
|
-
const initializeRecentlyViewed = async (currentSku: string) => {
|
|
95
|
-
if (!currentSku) return;
|
|
96
|
-
|
|
97
|
-
if (isLoggedIn.value) {
|
|
98
|
-
// 登入用戶:先合併資料 > 再添加當前商品 > 最後獲取列表
|
|
99
|
-
await mergeGuestToCustomer();
|
|
100
|
-
await addRecentlyViewed(currentSku);
|
|
101
|
-
recentlyViewedSkus.value = await getRecentlyViewedSkus(currentSku);
|
|
102
|
-
} else {
|
|
103
|
-
// 訪客用戶:先顯示現有資料 > 再添加當前商品
|
|
104
|
-
recentlyViewedSkus.value = await getRecentlyViewedSkus(currentSku);
|
|
105
|
-
await addRecentlyViewed(currentSku);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// 在商品頁面掛載時調用
|
|
110
|
-
onMounted(() => {
|
|
111
|
-
if (product.value?.sku) {
|
|
112
|
-
initializeRecentlyViewed(product.value.sku);
|
|
113
|
-
}
|
|
70
|
+
const { recentlyViewedSkus } = useRecentlyViewedList({
|
|
71
|
+
currentSku: computed(() => product.value?.sku ?? ''),
|
|
72
|
+
isLoggedIn: computed(() => !!user.value),
|
|
73
|
+
enableServerSync: true,
|
|
114
74
|
});
|
|
115
75
|
</script>
|
|
116
76
|
```
|
|
117
77
|
|
|
118
|
-
|
|
78
|
+
**自動處理:**
|
|
79
|
+
- ✅ 組件掛載時初始化
|
|
80
|
+
- ✅ SKU 變化時更新列表
|
|
81
|
+
- ✅ 登入狀態變化時合併資料
|
|
82
|
+
- ✅ 本地緩存優化
|
|
119
83
|
|
|
120
|
-
```typescript
|
|
121
|
-
<script setup lang="ts">
|
|
122
|
-
import { useRecentlyViewed } from '@astralweb/nova-recently-viewed';
|
|
123
84
|
|
|
124
|
-
|
|
85
|
+
## API 參考
|
|
86
|
+
|
|
87
|
+
### useRecentlyViewedList
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
const { recentlyViewedSkus } = useRecentlyViewedList({
|
|
91
|
+
currentSku: Ref<string> | ComputedRef<string>,
|
|
92
|
+
isLoggedIn: ComputedRef<boolean>,
|
|
93
|
+
maxItems?: number, // 預設:20
|
|
94
|
+
guestStorageKey?: string, // 預設:'recently-viewed-guest'
|
|
95
|
+
customerStorageKey?: string, // 預設:'recently-viewed-customer'
|
|
96
|
+
enableServerSync?: boolean, // 預設:true
|
|
97
|
+
autoInit?: boolean, // 預設:true
|
|
132
98
|
});
|
|
133
|
-
</script>
|
|
134
99
|
```
|
|
135
100
|
|
|
136
|
-
|
|
101
|
+
**返回值:**
|
|
102
|
+
- `recentlyViewedSkus`: `Ref<string[]>` - 最近瀏覽商品的 SKU 列表
|
|
137
103
|
|
|
138
|
-
###
|
|
104
|
+
### useRecentlyViewedStorage
|
|
139
105
|
|
|
140
|
-
|
|
106
|
+
核心 composable,提供完整的儲存管理功能。
|
|
141
107
|
|
|
142
108
|
```typescript
|
|
143
109
|
const {
|
|
144
110
|
addRecentlyViewed,
|
|
145
111
|
getRecentlyViewedSkus,
|
|
112
|
+
getSkusFromLocalStorage,
|
|
146
113
|
clearAllRecentlyViewed,
|
|
147
114
|
mergeGuestToCustomer,
|
|
148
|
-
isLoggedIn
|
|
149
|
-
} =
|
|
150
|
-
config?: RecentlyViewedConfig,
|
|
151
|
-
isLoggedIn?: ComputedRef<boolean>
|
|
152
|
-
): UseRecentlyViewedReturn;
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
##### 參數
|
|
156
|
-
|
|
157
|
-
| 參數 | 類型 | 預設值 | 說明 |
|
|
158
|
-
|------|------|--------|------|
|
|
159
|
-
| `config` | `RecentlyViewedConfig` | `{}` | 可選的配置選項 |
|
|
160
|
-
| `isLoggedIn` | `ComputedRef<boolean>` | `computed(() => false)` | 可選的登入狀態 |
|
|
161
|
-
|
|
162
|
-
##### 配置選項 (RecentlyViewedConfig)
|
|
163
|
-
|
|
164
|
-
| 屬性 | 類型 | 預設值 | 說明 |
|
|
165
|
-
|------|------|--------|------|
|
|
166
|
-
| `maxItems` | `number` | `20` | 最大儲存商品數量 |
|
|
167
|
-
| `guestStorageKey` | `string` | `'recently_viewed_products_guest'` | 訪客本地儲存鍵 |
|
|
168
|
-
| `customerStorageKey` | `string` | `'recently_viewed_products_customer'` | 會員本地儲存鍵 |
|
|
169
|
-
| `enableServerSync` | `boolean` | `true` | 是否啟用後端同步 |
|
|
170
|
-
|
|
171
|
-
##### 返回值 (UseRecentlyViewedReturn)
|
|
172
|
-
|
|
173
|
-
| 方法 | 類型 | 說明 |
|
|
174
|
-
|------|------|------|
|
|
175
|
-
| `addRecentlyViewed` | `(sku: string) => Promise<void>` | 添加商品到最近瀏覽 |
|
|
176
|
-
| `getRecentlyViewedSkus` | `(excludeSku?: string) => Promise<string[]>` | 獲取最近瀏覽商品 SKU 列表 |
|
|
177
|
-
| `clearAllRecentlyViewed` | `() => Promise<void>` | 清除所有最近瀏覽記錄 |
|
|
178
|
-
| `mergeGuestToCustomer` | `() => Promise<void>` | 合併訪客資料到會員帳戶 |
|
|
179
|
-
| `isLoggedIn` | `ComputedRef<boolean>` | 登入狀態響應式引用 |
|
|
180
|
-
|
|
181
|
-
### API Functions
|
|
182
|
-
|
|
183
|
-
#### Web 客戶端 API
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
import {
|
|
187
|
-
fetchRecentlyViewedWeb,
|
|
188
|
-
addRecentlyViewedWeb,
|
|
189
|
-
setSdk,
|
|
190
|
-
getSdk
|
|
191
|
-
} from '@astralweb/nova-recently-viewed/api';
|
|
192
|
-
|
|
193
|
-
// 設定 SDK
|
|
194
|
-
setSdk(sdkInstance);
|
|
195
|
-
|
|
196
|
-
// 獲取最近瀏覽商品
|
|
197
|
-
const products = await fetchRecentlyViewedWeb();
|
|
198
|
-
|
|
199
|
-
// 添加最近瀏覽商品
|
|
200
|
-
const result = await addRecentlyViewedWeb([
|
|
201
|
-
{ product_sku: 'SKU001', viewed_at: 1234567890 }
|
|
202
|
-
]);
|
|
115
|
+
isLoggedIn,
|
|
116
|
+
} = useRecentlyViewedStorage(config, isLoggedIn);
|
|
203
117
|
```
|
|
204
118
|
|
|
205
|
-
|
|
206
|
-
|
|
119
|
+
**配置選項:**
|
|
207
120
|
```typescript
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// 在 middleware 中使用
|
|
214
|
-
export const customRecentlyViewedProducts = async (context, variables) => {
|
|
215
|
-
return await recentlyViewedProducts(context, variables);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
export const customAddRecentlyViewedProducts = async (context, variables) => {
|
|
219
|
-
return await addRecentlyViewedProducts(context, variables);
|
|
220
|
-
};
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### 類型定義
|
|
224
|
-
|
|
225
|
-
#### 核心類型
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
// 最近瀏覽商品項目
|
|
229
|
-
interface RecentlyViewedItem {
|
|
230
|
-
sku: string;
|
|
231
|
-
viewedAt: number; // JavaScript timestamp (毫秒)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// API 輸入類型
|
|
235
|
-
interface AddRecentlyViewedInputItem {
|
|
236
|
-
product_sku: string;
|
|
237
|
-
viewed_at: number; // Unix timestamp (秒)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// API 回應類型
|
|
241
|
-
interface AddRecentlyViewedResponse {
|
|
242
|
-
success: boolean;
|
|
243
|
-
message?: string;
|
|
121
|
+
interface RecentlyViewedConfig {
|
|
122
|
+
maxItems?: number; // 最多儲存數量(預設:20)
|
|
123
|
+
guestStorageKey?: string; // 訪客 localStorage key
|
|
124
|
+
customerStorageKey?: string; // 會員 localStorage key
|
|
125
|
+
enableServerSync?: boolean; // 啟用伺服器同步(預設:true)
|
|
244
126
|
}
|
|
245
127
|
```
|
|
246
128
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
129
|
+
**方法:**
|
|
130
|
+
- `addRecentlyViewed(sku: string): Promise<void>` - 添加商品到最近瀏覽
|
|
131
|
+
- `getRecentlyViewedSkus(excludeSku?: string): Promise<string[]>` - 獲取 SKU 列表(伺服器優先)
|
|
132
|
+
- `getSkusFromLocalStorage(excludeSku?: string): string[]` - 從本地儲存獲取 SKU 列表
|
|
133
|
+
- `clearAllRecentlyViewed(): void` - 清除所有記錄
|
|
134
|
+
- `mergeGuestToCustomer(): Promise<void>` - 合併訪客資料到會員帳戶
|
|
250
135
|
|
|
251
|
-
|
|
136
|
+
### useRecentlyViewedApi
|
|
252
137
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
**解決方案**:
|
|
256
|
-
- 檢查是否正確調用了 `addRecentlyViewed()`
|
|
257
|
-
- 確認 localStorage 是否可用
|
|
258
|
-
- 檢查是否有 JavaScript 錯誤
|
|
138
|
+
API 層 composable,處理與後端的通信。
|
|
259
139
|
|
|
260
140
|
```typescript
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const skus = await getRecentlyViewedSkus();
|
|
267
|
-
console.log('Recently viewed SKUs:', skus);
|
|
268
|
-
};
|
|
141
|
+
const {
|
|
142
|
+
syncToServer,
|
|
143
|
+
fetchFromServer,
|
|
144
|
+
} = useRecentlyViewedApi();
|
|
269
145
|
```
|
|
270
146
|
|
|
271
|
-
|
|
147
|
+
**方法:**
|
|
148
|
+
- `syncToServer(input: AddRecentlyViewedInputItem[]): Promise<void>` - 同步資料到伺服器
|
|
149
|
+
- `fetchFromServer(): Promise<AddRecentlyViewedInputItem[]>` - 從伺服器獲取資料
|
|
272
150
|
|
|
273
|
-
|
|
151
|
+
## 工作原理
|
|
274
152
|
|
|
275
|
-
|
|
276
|
-
- 檢查 SDK 是否正確設定
|
|
277
|
-
- 確認 middleware 是否正確導出
|
|
278
|
-
- 檢查網路連接和 API 權限
|
|
153
|
+
### 訪客模式
|
|
279
154
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const sdk = getSdk();
|
|
284
|
-
console.log('SDK configured:', !!sdk);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error('SDK not configured:', error);
|
|
287
|
-
}
|
|
288
|
-
```
|
|
155
|
+
- 資料保存在 `localStorage`(key: `recently-viewed-guest`)
|
|
156
|
+
- 不進行伺服器同步
|
|
157
|
+
- 本地去重與數量限制
|
|
289
158
|
|
|
290
|
-
|
|
159
|
+
### 登入模式
|
|
291
160
|
|
|
292
|
-
|
|
161
|
+
- 主要資料來源:伺服器
|
|
162
|
+
- 本地緩存:加速 UI 更新
|
|
163
|
+
- 新增時:同步到伺服器 → 更新本地緩存
|
|
293
164
|
|
|
294
|
-
|
|
295
|
-
- 確認 `mergeGuestToCustomer()` 在適當時機被調用
|
|
296
|
-
- 檢查登入狀態監聽是否正確設定
|
|
165
|
+
### 登入時資料合併
|
|
297
166
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
await loginUser();
|
|
303
|
-
|
|
304
|
-
// 登入成功後合併資料
|
|
305
|
-
await mergeGuestToCustomer();
|
|
306
|
-
};
|
|
307
|
-
```
|
|
167
|
+
1. 讀取訪客 `localStorage` 資料
|
|
168
|
+
2. 同步到伺服器(合併到會員帳戶)
|
|
169
|
+
3. 清除訪客資料
|
|
170
|
+
4. 從伺服器獲取最新資料
|
|
308
171
|
|
|
309
|
-
##
|
|
172
|
+
## 授權
|
|
310
173
|
|
|
311
174
|
此套件僅供 Astral Web 內部使用,未經授權不得用於其他用途。
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addImports } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
const module = defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: "recently-viewed",
|
|
6
|
+
configKey: "recentlyViewed",
|
|
7
|
+
compatibility: {
|
|
8
|
+
nuxt: ">=3.0.0"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
// Default configuration options of the Nuxt module
|
|
12
|
+
defaults: {},
|
|
13
|
+
setup(_options, _nuxt) {
|
|
14
|
+
const resolver = createResolver(import.meta.url);
|
|
15
|
+
addImports({
|
|
16
|
+
name: "useRecentlyViewedStorage",
|
|
17
|
+
from: resolver.resolve("./runtime/composables/useRecentlyViewedStorage")
|
|
18
|
+
});
|
|
19
|
+
addImports({
|
|
20
|
+
name: "useRecentlyViewedList",
|
|
21
|
+
from: resolver.resolve("./runtime/composables/useRecentlyViewedList")
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export { module as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ApolloQueryResult } from '@apollo/client/core';
|
|
2
|
-
import { Context, TObject } from '@vue-storefront/middleware';
|
|
3
|
-
import { AddRecentlyViewedInputItem, AddRecentlyViewedResponse } from '../types';
|
|
1
|
+
import type { ApolloQueryResult, FetchResult } from '@apollo/client/core';
|
|
2
|
+
import type { Context, TObject } from '@vue-storefront/middleware';
|
|
3
|
+
import type { AddRecentlyViewedInputItem, AddRecentlyViewedResponse } from '../types/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* 查詢最近瀏覽商品
|
|
6
6
|
*/
|
|
@@ -10,7 +10,6 @@ export declare const recentlyViewedProducts: (context: Context, variables: TObje
|
|
|
10
10
|
/**
|
|
11
11
|
* 添加商品到最近瀏覽列表
|
|
12
12
|
*/
|
|
13
|
-
export declare const addRecentlyViewedProducts: (context: Context, variables: TObject) => Promise<
|
|
13
|
+
export declare const addRecentlyViewedProducts: (context: Context, variables: TObject) => Promise<FetchResult<{
|
|
14
14
|
addRecentlyViewedProducts: AddRecentlyViewedResponse;
|
|
15
15
|
}>>;
|
|
16
|
-
//# sourceMappingURL=extendMiddleware.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { consola } from "consola";
|
|
2
|
+
import { gql } from "graphql-tag";
|
|
3
|
+
const getHeaders = (context, customHeaders = {}) => {
|
|
4
|
+
const { getCustomerToken, getStore, getCurrency } = context.config.state;
|
|
5
|
+
return {
|
|
6
|
+
...getCustomerToken() && { Authorization: `Bearer ${getCustomerToken()}` },
|
|
7
|
+
...getStore() && { store: getStore() },
|
|
8
|
+
...getCurrency() && { "Content-Currency": getCurrency() },
|
|
9
|
+
...customHeaders
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export const recentlyViewedProducts = async (context, variables) => {
|
|
13
|
+
const { client } = context;
|
|
14
|
+
try {
|
|
15
|
+
return await client.query({
|
|
16
|
+
query: gql`
|
|
17
|
+
query recentlyViewedProducts {
|
|
18
|
+
recentlyViewedProducts {
|
|
19
|
+
product_sku
|
|
20
|
+
viewed_at
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`,
|
|
24
|
+
variables,
|
|
25
|
+
context: { headers: getHeaders(context) }
|
|
26
|
+
});
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const typedError = error;
|
|
29
|
+
throw typedError;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export const addRecentlyViewedProducts = async (context, variables) => {
|
|
33
|
+
const { client } = context;
|
|
34
|
+
try {
|
|
35
|
+
return await client.mutate({
|
|
36
|
+
mutation: gql`
|
|
37
|
+
mutation addRecentlyViewedProducts($input: [RecentlyViewedProductInput!]!) {
|
|
38
|
+
addRecentlyViewedProducts(input: $input) {
|
|
39
|
+
success
|
|
40
|
+
message
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
variables,
|
|
45
|
+
context: { headers: getHeaders(context) }
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const typedError = error;
|
|
49
|
+
if (typedError.graphQLErrors) {
|
|
50
|
+
consola.debug(typedError);
|
|
51
|
+
return {
|
|
52
|
+
errors: typedError.graphQLErrors,
|
|
53
|
+
data: null
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const networkError = typedError.networkError;
|
|
57
|
+
if (networkError && "result" in networkError) {
|
|
58
|
+
consola.error(networkError.result);
|
|
59
|
+
throw networkError.result;
|
|
60
|
+
}
|
|
61
|
+
consola.error(typedError);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './extendMiddleware.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./extendMiddleware.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useNuxtApp } from "#app";
|
|
2
|
+
export function useRecentlyViewedApi() {
|
|
3
|
+
const { sdk } = useNuxtApp().$alokai;
|
|
4
|
+
const syncToServer = async (input) => {
|
|
5
|
+
const { errors } = await sdk.magento.addRecentlyViewedProducts({ input });
|
|
6
|
+
if (errors && errors.length > 0) {
|
|
7
|
+
throw new Error(errors.map((e) => e.message).join(", "));
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const fetchFromServer = async () => {
|
|
11
|
+
const { data, errors } = await sdk.magento.recentlyViewedProducts({});
|
|
12
|
+
if (errors && errors.length > 0) {
|
|
13
|
+
throw new Error(errors.map((e) => e.message).join(", "));
|
|
14
|
+
}
|
|
15
|
+
return data?.recentlyViewedProducts ?? [];
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
syncToServer,
|
|
19
|
+
fetchFromServer
|
|
20
|
+
};
|
|
21
|
+
}
|