@quantabit/reach-sdk 1.0.0

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.
@@ -0,0 +1,1125 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@quantabit/sdk-config'), require('react')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', '@quantabit/sdk-config', 'react'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.QbitDIDAuth = {}, global.sdkConfig, global.React));
5
+ })(this, (function (exports, sdkConfig, React) { 'use strict';
6
+
7
+ /**
8
+ * Reach SDK - API 客户端
9
+ * 用户触达系统后端接口封装
10
+ *
11
+ * 使用 BaseApiClient 基类简化代码
12
+ */
13
+
14
+
15
+ /**
16
+ * 用户触达 API 客户端
17
+ */
18
+ class ReachApiClient extends sdkConfig.BaseApiClient {
19
+ constructor(config = {}) {
20
+ super('/reach', config);
21
+ }
22
+
23
+ // ============ 触达任务 ============
24
+
25
+ /**
26
+ * 获取触达任务列表
27
+ * @param {Object} params - 查询参数
28
+ */
29
+ async getTasks(params = {}) {
30
+ return this.get('/tasks', params);
31
+ }
32
+
33
+ /**
34
+ * 获取触达任务详情
35
+ * @param {string} taskId - 任务 ID
36
+ */
37
+ async getTask(taskId) {
38
+ return this.get(`/tasks/${taskId}`);
39
+ }
40
+
41
+ /**
42
+ * 创建触达任务(管理员)
43
+ * @param {Object} data - 任务数据
44
+ */
45
+ async createTask(data) {
46
+ return this.post('/tasks', data);
47
+ }
48
+
49
+ /**
50
+ * 更新触达任务(管理员)
51
+ * @param {string} taskId - 任务 ID
52
+ * @param {Object} updates - 更新数据
53
+ */
54
+ async updateTask(taskId, updates) {
55
+ return this.put(`/tasks/${taskId}`, updates);
56
+ }
57
+
58
+ /**
59
+ * 删除触达任务(管理员)
60
+ * @param {string} taskId - 任务 ID
61
+ */
62
+ async deleteTask(taskId) {
63
+ return this.delete(`/tasks/${taskId}`);
64
+ }
65
+
66
+ // ============ 任务控制 ============
67
+
68
+ /**
69
+ * 立即执行(管理员)
70
+ * @param {string} taskId - 任务 ID
71
+ */
72
+ async executeNow(taskId) {
73
+ return this.post(`/tasks/${taskId}/execute`);
74
+ }
75
+
76
+ /**
77
+ * 暂停任务(管理员)
78
+ * @param {string} taskId - 任务 ID
79
+ */
80
+ async pauseTask(taskId) {
81
+ return this.post(`/tasks/${taskId}/pause`);
82
+ }
83
+
84
+ /**
85
+ * 恢复任务(管理员)
86
+ * @param {string} taskId - 任务 ID
87
+ */
88
+ async resumeTask(taskId) {
89
+ return this.post(`/tasks/${taskId}/resume`);
90
+ }
91
+
92
+ /**
93
+ * 取消任务(管理员)
94
+ * @param {string} taskId - 任务 ID
95
+ */
96
+ async cancelTask(taskId) {
97
+ return this.post(`/tasks/${taskId}/cancel`);
98
+ }
99
+
100
+ // ============ 渠道管理 ============
101
+
102
+ /**
103
+ * 获取触达渠道
104
+ */
105
+ async getChannels() {
106
+ return this.get('/channels');
107
+ }
108
+
109
+ /**
110
+ * 获取渠道配置
111
+ * @param {string} channelType - 渠道类型
112
+ */
113
+ async getChannelConfig(channelType) {
114
+ return this.get(`/channels/${channelType}/config`);
115
+ }
116
+
117
+ /**
118
+ * 更新渠道配置(管理员)
119
+ * @param {string} channelType - 渠道类型
120
+ * @param {Object} config - 配置数据
121
+ */
122
+ async updateChannelConfig(channelType, config) {
123
+ return this.put(`/channels/${channelType}/config`, config);
124
+ }
125
+
126
+ /**
127
+ * 测试渠道发送
128
+ * @param {string} channelType - 渠道类型
129
+ * @param {Object} testData - 测试数据
130
+ */
131
+ async testChannel(channelType, testData) {
132
+ return this.post(`/channels/${channelType}/test`, testData);
133
+ }
134
+
135
+ // ============ 模板管理 ============
136
+
137
+ /**
138
+ * 获取消息模板
139
+ * @param {Object} params - 查询参数
140
+ */
141
+ async getTemplates(params = {}) {
142
+ return this.get('/templates', params);
143
+ }
144
+
145
+ /**
146
+ * 获取模板详情
147
+ * @param {string} templateId - 模板 ID
148
+ */
149
+ async getTemplate(templateId) {
150
+ return this.get(`/templates/${templateId}`);
151
+ }
152
+
153
+ /**
154
+ * 创建模板(管理员)
155
+ * @param {Object} data - 模板数据
156
+ */
157
+ async createTemplate(data) {
158
+ return this.post('/templates', data);
159
+ }
160
+
161
+ /**
162
+ * 更新模板(管理员)
163
+ * @param {string} templateId - 模板 ID
164
+ * @param {Object} updates - 更新数据
165
+ */
166
+ async updateTemplate(templateId, updates) {
167
+ return this.put(`/templates/${templateId}`, updates);
168
+ }
169
+
170
+ /**
171
+ * 预览模板
172
+ * @param {string} templateId - 模板 ID
173
+ * @param {Object} variables - 变量数据
174
+ */
175
+ async previewTemplate(templateId, variables = {}) {
176
+ return this.post(`/templates/${templateId}/preview`, {
177
+ variables
178
+ });
179
+ }
180
+
181
+ // ============ 受众管理 ============
182
+
183
+ /**
184
+ * 获取受众列表
185
+ * @param {Object} params - 查询参数
186
+ */
187
+ async getAudiences(params = {}) {
188
+ return this.get('/audiences', params);
189
+ }
190
+
191
+ /**
192
+ * 创建受众(管理员)
193
+ * @param {Object} data - 受众数据
194
+ */
195
+ async createAudience(data) {
196
+ return this.post('/audiences', data);
197
+ }
198
+
199
+ /**
200
+ * 预估受众规模
201
+ * @param {Object} conditions - 筛选条件
202
+ */
203
+ async estimateAudienceSize(conditions) {
204
+ return this.post('/audiences/estimate', conditions);
205
+ }
206
+
207
+ // ============ 统计分析 ============
208
+
209
+ /**
210
+ * 获取任务统计
211
+ * @param {string} taskId - 任务 ID
212
+ */
213
+ async getTaskStats(taskId) {
214
+ return this.get(`/tasks/${taskId}/stats`);
215
+ }
216
+
217
+ /**
218
+ * 获取整体统计
219
+ * @param {Object} params - 统计参数
220
+ */
221
+ async getOverallStats(params = {}) {
222
+ return this.get('/stats', params);
223
+ }
224
+
225
+ /**
226
+ * 获取发送记录
227
+ * @param {string} taskId - 任务 ID
228
+ * @param {Object} params - 查询参数
229
+ */
230
+ async getSendLogs(taskId, params = {}) {
231
+ return this.get(`/tasks/${taskId}/logs`, params);
232
+ }
233
+
234
+ // ============ 兼容性方法 (Campaigns, Push, Popups, Banners, Events) ============
235
+
236
+ /**
237
+ * 获取活动列表
238
+ * @param {Object} params - 查询参数
239
+ */
240
+ async getCampaigns(params = {}) {
241
+ return this.get('/campaigns', params);
242
+ }
243
+
244
+ /**
245
+ * 获取活跃的活动列表
246
+ * @param {Object} params - 查询参数
247
+ */
248
+ async getActiveCampaigns(params = {}) {
249
+ return this.get('/campaigns/active', params);
250
+ }
251
+
252
+ /**
253
+ * 获取活动详情
254
+ * @param {string} campaignId - 活动 ID
255
+ */
256
+ async getCampaign(campaignId) {
257
+ return this.get(`/campaigns/${campaignId}`);
258
+ }
259
+
260
+ /**
261
+ * 创建活动
262
+ * @param {Object} data - 活动数据
263
+ */
264
+ async createCampaign(data) {
265
+ return this.post('/campaigns', data);
266
+ }
267
+
268
+ /**
269
+ * 更新活动
270
+ * @param {string} campaignId - 活动 ID
271
+ * @param {Object} data - 更新数据
272
+ */
273
+ async updateCampaign(campaignId, data) {
274
+ return this.put(`/campaigns/${campaignId}`, data);
275
+ }
276
+
277
+ /**
278
+ * 参与活动
279
+ * @param {string} campaignId - 活动 ID
280
+ * @param {Object} data - 参与数据
281
+ */
282
+ async joinCampaign(campaignId, data = {}) {
283
+ return this.post(`/campaigns/${campaignId}/join`, data);
284
+ }
285
+
286
+ /**
287
+ * 排期/调度活动
288
+ * @param {string} campaignId - 活动 ID
289
+ * @param {string} scheduledTime - 定时发送时间
290
+ */
291
+ async scheduleCampaign(campaignId, scheduledTime) {
292
+ return this.post(`/campaigns/${campaignId}/schedule`, {
293
+ scheduledTime
294
+ });
295
+ }
296
+
297
+ /**
298
+ * 发送活动
299
+ * @param {string} campaignId - 活动 ID
300
+ */
301
+ async sendCampaign(campaignId) {
302
+ return this.post(`/campaigns/${campaignId}/send`);
303
+ }
304
+
305
+ /**
306
+ * 暂停活动
307
+ * @param {string} campaignId - 活动 ID
308
+ */
309
+ async pauseCampaign(campaignId) {
310
+ return this.post(`/campaigns/${campaignId}/pause`);
311
+ }
312
+
313
+ /**
314
+ * 取消活动
315
+ * @param {string} campaignId - 活动 ID
316
+ */
317
+ async cancelCampaign(campaignId) {
318
+ return this.post(`/campaigns/${campaignId}/cancel`);
319
+ }
320
+
321
+ /**
322
+ * 获取弹窗配置列表
323
+ * @param {Object} params - 查询参数
324
+ */
325
+ async getPopups(params = {}) {
326
+ return this.get('/popups', params);
327
+ }
328
+
329
+ /**
330
+ * 获取横幅广告列表
331
+ * @param {Object} params - 查询参数
332
+ */
333
+ async getBanners(params = {}) {
334
+ return this.get('/banners', params);
335
+ }
336
+
337
+ /**
338
+ * 追踪事件
339
+ * @param {string} event - 事件名
340
+ * @param {Object} data - 事件数据
341
+ */
342
+ async trackEvent(event, data = {}) {
343
+ return this.post('/events/track', {
344
+ event,
345
+ data
346
+ });
347
+ }
348
+
349
+ /**
350
+ * 获取推送首选项
351
+ * @param {Object} params - 查询参数
352
+ */
353
+ async getPushPreferences(params = {}) {
354
+ return this.get('/push/preferences', params);
355
+ }
356
+
357
+ /**
358
+ * 更新推送首选项
359
+ * @param {Object} data - 首选项数据
360
+ */
361
+ async updatePushPreferences(data = {}) {
362
+ return this.put('/push/preferences', data);
363
+ }
364
+
365
+ /**
366
+ * 订阅推送
367
+ * @param {Object} params - 订阅参数
368
+ */
369
+ async subscribe(params = {}) {
370
+ return this.post('/push/subscribe', params);
371
+ }
372
+
373
+ /**
374
+ * 取消订阅推送
375
+ * @param {Object} params - 取消参数
376
+ */
377
+ async unsubscribe(params = {}) {
378
+ return this.post('/push/unsubscribe', params);
379
+ }
380
+
381
+ /**
382
+ * 获取分群列表
383
+ * @param {Object} params - 查询参数
384
+ */
385
+ async getSegments(params = {}) {
386
+ return this.get('/segments', params);
387
+ }
388
+
389
+ /**
390
+ * 预估受众规模
391
+ * @param {Object} criteria - 预估条件
392
+ */
393
+ async estimateAudience(criteria = {}) {
394
+ return this.post('/audiences/estimate', criteria);
395
+ }
396
+ }
397
+
398
+ // 创建默认实例
399
+ const reachApi = new ReachApiClient();
400
+
401
+ const ChannelType = {};
402
+ const AudienceType = {};
403
+ const TemplateType = {};
404
+ const CampaignStatus = {};
405
+
406
+ /**
407
+ * Reach SDK - 国际化
408
+ */
409
+
410
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
411
+ const messages = {
412
+ zh: {
413
+ // 触达
414
+ reach: '批量触达',
415
+ campaign: '活动',
416
+ campaigns: '触达活动',
417
+ createCampaign: '创建活动',
418
+ // 渠道
419
+ channel: '渠道',
420
+ channels: '渠道',
421
+ email: '邮件',
422
+ sms: '短信',
423
+ push: '推送',
424
+ inApp: '站内信',
425
+ wechat: '微信',
426
+ // 状态
427
+ draft: '草稿',
428
+ scheduled: '待发送',
429
+ sending: '发送中',
430
+ sent: '已发送',
431
+ paused: '已暂停',
432
+ cancelled: '已取消',
433
+ // 受众
434
+ audience: '受众',
435
+ selectAudience: '选择受众',
436
+ allUsers: '全部用户',
437
+ segment: '用户分群',
438
+ uploadList: '上传名单',
439
+ // 内容
440
+ content: '内容',
441
+ template: '模板',
442
+ subject: '主题',
443
+ preview: '预览',
444
+ // 发送
445
+ send: '发送',
446
+ sendNow: '立即发送',
447
+ schedule: '定时发送',
448
+ scheduledTime: '发送时间',
449
+ // 统计
450
+ delivered: '已送达',
451
+ opened: '已打开',
452
+ clicked: '已点击',
453
+ converted: '已转化',
454
+ failed: '发送失败',
455
+ bounced: '退回',
456
+ unsubscribed: '退订',
457
+ // 速率
458
+ sendRate: '发送速率',
459
+ openRate: '打开率',
460
+ clickRate: '点击率',
461
+ conversionRate: '转化率'
462
+ },
463
+ ja: {
464
+ // 触达
465
+ reach: '一括アプローチ',
466
+ campaign: 'アクティビティ',
467
+ campaigns: 'アプローチキャンペーン',
468
+ createCampaign: 'キャンペーン作成',
469
+ // 渠道
470
+ channel: 'チャネル',
471
+ channels: 'チャネル',
472
+ email: 'メール',
473
+ sms: 'SMS',
474
+ push: 'プッシュ通知',
475
+ inApp: 'メッセージ',
476
+ wechat: 'WeChat',
477
+ // 状态
478
+ draft: '下書き',
479
+ scheduled: '送信待ち',
480
+ sending: '送信中',
481
+ sent: '送信済み',
482
+ paused: '一時停止中',
483
+ cancelled: 'キャンセル済み',
484
+ // 受众
485
+ audience: 'ターゲット',
486
+ selectAudience: 'ターゲットを選択',
487
+ allUsers: 'すべてのユーザー',
488
+ segment: 'ユーザーセグメント',
489
+ uploadList: 'リストをアップロード',
490
+ // 内容
491
+ content: 'コンテンツ',
492
+ template: 'テンプレート',
493
+ subject: '件名',
494
+ preview: 'プレビュー',
495
+ // 发送
496
+ send: '送信',
497
+ sendNow: '今すぐ送信',
498
+ schedule: '予約送信',
499
+ scheduledTime: '送信時間',
500
+ // 统计
501
+ delivered: '配達済み',
502
+ opened: '開封済み',
503
+ clicked: 'クリック済み',
504
+ converted: 'コンバージョン済み',
505
+ failed: '送信失敗',
506
+ bounced: 'バウンス',
507
+ unsubscribed: '配信停止',
508
+ // 速率
509
+ sendRate: '送信速度',
510
+ openRate: '開封率',
511
+ clickRate: 'クリック率',
512
+ conversionRate: 'コンバージョン率'
513
+ },
514
+ ko: {
515
+ // 触达
516
+ reach: '일괄 도달',
517
+ campaign: '이벤트',
518
+ campaigns: '도달 캠페인',
519
+ createCampaign: '캠페인 생성',
520
+ // 渠道
521
+ channel: '채널',
522
+ channels: '채널',
523
+ email: '이메일',
524
+ sms: 'SMS',
525
+ push: '푸시 알림',
526
+ inApp: '사이트 메시지',
527
+ wechat: '위챗',
528
+ // 状态
529
+ draft: '초안',
530
+ scheduled: '발송 대기',
531
+ sending: '발송 중',
532
+ sent: '전송됨',
533
+ paused: '일시 중지됨',
534
+ cancelled: '취소됨',
535
+ // 受众
536
+ audience: '대상',
537
+ selectAudience: '대상 선택',
538
+ allUsers: '모든 사용자',
539
+ segment: '사용자 그룹',
540
+ uploadList: '목록 업로드',
541
+ // 内容
542
+ content: '내용',
543
+ template: '템플릿',
544
+ subject: '주제',
545
+ preview: '미리보기',
546
+ // 发送
547
+ send: '보내기',
548
+ sendNow: '즉시 발송',
549
+ schedule: '예약 발송',
550
+ scheduledTime: '발송 시간',
551
+ // 统计
552
+ delivered: '도달됨',
553
+ opened: '열어봄',
554
+ clicked: '클릭됨',
555
+ converted: '전환됨',
556
+ failed: '전송 실패',
557
+ bounced: '반송됨',
558
+ unsubscribed: '구독 취소됨',
559
+ // 速率
560
+ sendRate: '전송 속도',
561
+ openRate: '오픈율',
562
+ clickRate: '클릭률',
563
+ conversionRate: '전환율'
564
+ },
565
+ en: {
566
+ reach: 'Reach',
567
+ campaign: 'Campaign',
568
+ campaigns: 'Campaigns',
569
+ createCampaign: 'Create Campaign',
570
+ channel: 'Channel',
571
+ channels: 'Channels',
572
+ email: 'Email',
573
+ sms: 'SMS',
574
+ push: 'Push',
575
+ inApp: 'In-App',
576
+ wechat: 'WeChat',
577
+ draft: 'Draft',
578
+ scheduled: 'Scheduled',
579
+ sending: 'Sending',
580
+ sent: 'Sent',
581
+ paused: 'Paused',
582
+ cancelled: 'Cancelled',
583
+ audience: 'Audience',
584
+ selectAudience: 'Select Audience',
585
+ allUsers: 'All Users',
586
+ segment: 'Segment',
587
+ uploadList: 'Upload List',
588
+ content: 'Content',
589
+ template: 'Template',
590
+ subject: 'Subject',
591
+ preview: 'Preview',
592
+ send: 'Send',
593
+ sendNow: 'Send Now',
594
+ schedule: 'Schedule',
595
+ scheduledTime: 'Scheduled Time',
596
+ delivered: 'Delivered',
597
+ opened: 'Opened',
598
+ clicked: 'Clicked',
599
+ converted: 'Converted',
600
+ failed: 'Failed',
601
+ bounced: 'Bounced',
602
+ unsubscribed: 'Unsubscribed',
603
+ sendRate: 'Send Rate',
604
+ openRate: 'Open Rate',
605
+ clickRate: 'Click Rate',
606
+ conversionRate: 'Conversion Rate'
607
+ }
608
+ };
609
+ let currentLanguage = 'zh';
610
+ function setLanguage(lang) {
611
+ if (SUPPORTED_LANGUAGES.includes(lang)) currentLanguage = lang;
612
+ }
613
+ function getLanguage() {
614
+ return currentLanguage;
615
+ }
616
+ function t(key) {
617
+ return (messages[currentLanguage] || messages.en)[key] || key;
618
+ }
619
+
620
+ /**
621
+ * Reach SDK - React Hooks
622
+ */
623
+
624
+
625
+ /**
626
+ * useCampaigns - 触达活动列表Hook
627
+ */
628
+ function useCampaigns(options = {}) {
629
+ const [campaigns, setCampaigns] = React.useState([]);
630
+ const [loading, setLoading] = React.useState(false);
631
+ const [total, setTotal] = React.useState(0);
632
+ const loadCampaigns = React.useCallback(async () => {
633
+ setLoading(true);
634
+ try {
635
+ const result = await reachApi.getCampaigns(options);
636
+ setCampaigns(result.campaigns || []);
637
+ setTotal(result.total || 0);
638
+ } finally {
639
+ setLoading(false);
640
+ }
641
+ }, [options.status, options.channel]);
642
+ React.useEffect(() => {
643
+ loadCampaigns();
644
+ }, [options.status, options.channel]);
645
+ return {
646
+ campaigns,
647
+ loading,
648
+ total,
649
+ refresh: loadCampaigns
650
+ };
651
+ }
652
+
653
+ /**
654
+ * useCampaign - 单个触达活动Hook
655
+ */
656
+ function useCampaign(campaignId) {
657
+ const [campaign, setCampaign] = React.useState(null);
658
+ const [stats, setStats] = React.useState(null);
659
+ const [loading, setLoading] = React.useState(false);
660
+ const [saving, setSaving] = React.useState(false);
661
+ const loadCampaign = React.useCallback(async () => {
662
+ if (!campaignId) return;
663
+ setLoading(true);
664
+ try {
665
+ const [campaignData, statsData] = await Promise.all([reachApi.getCampaign(campaignId), reachApi.getCampaignStats(campaignId)]);
666
+ setCampaign(campaignData);
667
+ setStats(statsData);
668
+ } finally {
669
+ setLoading(false);
670
+ }
671
+ }, [campaignId]);
672
+
673
+ // 保存活动
674
+ const saveCampaign = React.useCallback(async data => {
675
+ setSaving(true);
676
+ try {
677
+ const result = campaignId ? await reachApi.updateCampaign(campaignId, data) : await reachApi.createCampaign(data);
678
+ setCampaign(result);
679
+ return result;
680
+ } finally {
681
+ setSaving(false);
682
+ }
683
+ }, [campaignId]);
684
+
685
+ // 发送活动
686
+ const sendCampaign = React.useCallback(async scheduledTime => {
687
+ if (!campaignId) return false;
688
+ try {
689
+ if (scheduledTime) {
690
+ await reachApi.scheduleCampaign(campaignId, scheduledTime);
691
+ } else {
692
+ await reachApi.sendCampaign(campaignId);
693
+ }
694
+ loadCampaign();
695
+ return true;
696
+ } catch (e) {
697
+ return false;
698
+ }
699
+ }, [campaignId, loadCampaign]);
700
+
701
+ // 暂停发送
702
+ const pauseCampaign = React.useCallback(async () => {
703
+ if (!campaignId) return false;
704
+ try {
705
+ await reachApi.pauseCampaign(campaignId);
706
+ loadCampaign();
707
+ return true;
708
+ } catch (e) {
709
+ return false;
710
+ }
711
+ }, [campaignId, loadCampaign]);
712
+
713
+ // 取消活动
714
+ const cancelCampaign = React.useCallback(async () => {
715
+ if (!campaignId) return false;
716
+ try {
717
+ await reachApi.cancelCampaign(campaignId);
718
+ loadCampaign();
719
+ return true;
720
+ } catch (e) {
721
+ return false;
722
+ }
723
+ }, [campaignId, loadCampaign]);
724
+ React.useEffect(() => {
725
+ loadCampaign();
726
+ }, [campaignId]);
727
+ return {
728
+ campaign,
729
+ stats,
730
+ loading,
731
+ saving,
732
+ saveCampaign,
733
+ sendCampaign,
734
+ pauseCampaign,
735
+ cancelCampaign,
736
+ refresh: loadCampaign
737
+ };
738
+ }
739
+
740
+ /**
741
+ * useTemplates - 模板Hook
742
+ */
743
+ function useTemplates(channel) {
744
+ const [templates, setTemplates] = React.useState([]);
745
+ const [loading, setLoading] = React.useState(false);
746
+ const loadTemplates = React.useCallback(async () => {
747
+ setLoading(true);
748
+ try {
749
+ const result = await reachApi.getTemplates(channel);
750
+ setTemplates(result.templates || []);
751
+ } finally {
752
+ setLoading(false);
753
+ }
754
+ }, [channel]);
755
+ React.useEffect(() => {
756
+ loadTemplates();
757
+ }, [channel]);
758
+ return {
759
+ templates,
760
+ loading,
761
+ refresh: loadTemplates
762
+ };
763
+ }
764
+
765
+ /**
766
+ * useAudience - 受众Hook
767
+ */
768
+ function useAudience() {
769
+ const [segments, setSegments] = React.useState([]);
770
+ const [loading, setLoading] = React.useState(false);
771
+ const loadSegments = React.useCallback(async () => {
772
+ setLoading(true);
773
+ try {
774
+ const result = await reachApi.getSegments();
775
+ setSegments(result.segments || []);
776
+ } finally {
777
+ setLoading(false);
778
+ }
779
+ }, []);
780
+
781
+ // 获取受众人数预估
782
+ const estimateAudience = React.useCallback(async criteria => {
783
+ try {
784
+ const result = await reachApi.estimateAudience(criteria);
785
+ return result.count || 0;
786
+ } catch (e) {
787
+ return 0;
788
+ }
789
+ }, []);
790
+ React.useEffect(() => {
791
+ loadSegments();
792
+ }, []);
793
+ return {
794
+ segments,
795
+ loading,
796
+ estimateAudience,
797
+ refresh: loadSegments
798
+ };
799
+ }
800
+
801
+ /**
802
+ * Reach SDK - 触达组件
803
+ */
804
+
805
+
806
+ /**
807
+ * 状态颜色
808
+ */
809
+ const statusColors = {
810
+ [CampaignStatus.DRAFT]: '#6B7280',
811
+ [CampaignStatus.SCHEDULED]: '#3B82F6',
812
+ [CampaignStatus.SENDING]: '#F59E0B',
813
+ [CampaignStatus.SENT]: '#10B981',
814
+ [CampaignStatus.PAUSED]: '#EF4444',
815
+ [CampaignStatus.CANCELLED]: '#9CA3AF'
816
+ };
817
+
818
+ /**
819
+ * 渠道图标
820
+ */
821
+ const channelIcons = {
822
+ [ChannelType.EMAIL]: '📧',
823
+ [ChannelType.SMS]: '📱',
824
+ [ChannelType.PUSH]: '🔔',
825
+ [ChannelType.IN_APP]: '💬',
826
+ [ChannelType.WECHAT]: '💚'
827
+ };
828
+
829
+ /**
830
+ * CampaignCard 组件
831
+ */
832
+ function CampaignCard({
833
+ campaign,
834
+ onClick,
835
+ onSend,
836
+ onPause,
837
+ onCancel
838
+ }) {
839
+ const {
840
+ id,
841
+ name,
842
+ channel,
843
+ status,
844
+ stats,
845
+ scheduled_time
846
+ } = campaign;
847
+ const statusColor = statusColors[status] || '#6B7280';
848
+ const channelIcon = channelIcons[channel] || '📤';
849
+ return /*#__PURE__*/React.createElement("div", {
850
+ className: "eco-reach-card",
851
+ onClick: () => onClick?.(campaign)
852
+ }, /*#__PURE__*/React.createElement("div", {
853
+ className: "eco-reach-card-header"
854
+ }, /*#__PURE__*/React.createElement("span", {
855
+ className: "eco-reach-channel"
856
+ }, channelIcon), /*#__PURE__*/React.createElement("h4", {
857
+ className: "eco-reach-card-title"
858
+ }, name), /*#__PURE__*/React.createElement("span", {
859
+ className: "eco-reach-status",
860
+ style: {
861
+ backgroundColor: `${statusColor}20`,
862
+ color: statusColor
863
+ }
864
+ }, t(status))), stats && /*#__PURE__*/React.createElement("div", {
865
+ className: "eco-reach-stats"
866
+ }, /*#__PURE__*/React.createElement("div", {
867
+ className: "eco-reach-stat"
868
+ }, /*#__PURE__*/React.createElement("span", {
869
+ className: "eco-reach-stat-value"
870
+ }, stats.sent || 0), /*#__PURE__*/React.createElement("span", {
871
+ className: "eco-reach-stat-label"
872
+ }, t('delivered'))), /*#__PURE__*/React.createElement("div", {
873
+ className: "eco-reach-stat"
874
+ }, /*#__PURE__*/React.createElement("span", {
875
+ className: "eco-reach-stat-value"
876
+ }, ((stats.open_rate || 0) * 100).toFixed(1), "%"), /*#__PURE__*/React.createElement("span", {
877
+ className: "eco-reach-stat-label"
878
+ }, t('openRate'))), /*#__PURE__*/React.createElement("div", {
879
+ className: "eco-reach-stat"
880
+ }, /*#__PURE__*/React.createElement("span", {
881
+ className: "eco-reach-stat-value"
882
+ }, ((stats.click_rate || 0) * 100).toFixed(1), "%"), /*#__PURE__*/React.createElement("span", {
883
+ className: "eco-reach-stat-label"
884
+ }, t('clickRate'))), /*#__PURE__*/React.createElement("div", {
885
+ className: "eco-reach-stat"
886
+ }, /*#__PURE__*/React.createElement("span", {
887
+ className: "eco-reach-stat-value"
888
+ }, ((stats.conversion_rate || 0) * 100).toFixed(1), "%"), /*#__PURE__*/React.createElement("span", {
889
+ className: "eco-reach-stat-label"
890
+ }, t('conversionRate')))), scheduled_time && status === CampaignStatus.SCHEDULED && /*#__PURE__*/React.createElement("div", {
891
+ className: "eco-reach-scheduled"
892
+ }, "\uD83D\uDCC5 ", t('scheduledTime'), ": ", new Date(scheduled_time).toLocaleString()), /*#__PURE__*/React.createElement("div", {
893
+ className: "eco-reach-actions",
894
+ onClick: e => e.stopPropagation()
895
+ }, status === CampaignStatus.DRAFT && /*#__PURE__*/React.createElement("button", {
896
+ className: "eco-reach-btn send",
897
+ onClick: () => onSend?.(id)
898
+ }, t('sendNow')), status === CampaignStatus.SENDING && /*#__PURE__*/React.createElement("button", {
899
+ className: "eco-reach-btn pause",
900
+ onClick: () => onPause?.(id)
901
+ }, t('paused')), status === CampaignStatus.SCHEDULED && /*#__PURE__*/React.createElement("button", {
902
+ className: "eco-reach-btn cancel",
903
+ onClick: () => onCancel?.(id)
904
+ }, t('cancelled'))));
905
+ }
906
+
907
+ /**
908
+ * ChannelSelector 组件
909
+ */
910
+ function ChannelSelector({
911
+ value,
912
+ onChange
913
+ }) {
914
+ const channels = [{
915
+ type: ChannelType.EMAIL,
916
+ icon: '📧',
917
+ label: t('email')
918
+ }, {
919
+ type: ChannelType.SMS,
920
+ icon: '📱',
921
+ label: t('sms')
922
+ }, {
923
+ type: ChannelType.PUSH,
924
+ icon: '🔔',
925
+ label: t('push')
926
+ }, {
927
+ type: ChannelType.IN_APP,
928
+ icon: '💬',
929
+ label: t('inApp')
930
+ }, {
931
+ type: ChannelType.WECHAT,
932
+ icon: '💚',
933
+ label: t('wechat')
934
+ }];
935
+ return /*#__PURE__*/React.createElement("div", {
936
+ className: "eco-reach-channel-selector"
937
+ }, channels.map(channel => /*#__PURE__*/React.createElement("div", {
938
+ key: channel.type,
939
+ className: `eco-reach-channel-option ${value === channel.type ? 'active' : ''}`,
940
+ onClick: () => onChange?.(channel.type)
941
+ }, /*#__PURE__*/React.createElement("span", {
942
+ className: "eco-reach-channel-icon"
943
+ }, channel.icon), /*#__PURE__*/React.createElement("span", {
944
+ className: "eco-reach-channel-label"
945
+ }, channel.label))));
946
+ }
947
+
948
+ /**
949
+ * AudienceSelector 组件
950
+ */
951
+ function AudienceSelector({
952
+ value,
953
+ segments = [],
954
+ estimatedCount,
955
+ onChange
956
+ }) {
957
+ return /*#__PURE__*/React.createElement("div", {
958
+ className: "eco-reach-audience"
959
+ }, /*#__PURE__*/React.createElement("h4", null, t('selectAudience')), /*#__PURE__*/React.createElement("div", {
960
+ className: "eco-reach-audience-options"
961
+ }, /*#__PURE__*/React.createElement("div", {
962
+ className: `eco-reach-audience-option ${value?.type === 'all' ? 'active' : ''}`,
963
+ onClick: () => onChange?.({
964
+ type: 'all'
965
+ })
966
+ }, /*#__PURE__*/React.createElement("span", {
967
+ className: "eco-reach-audience-icon"
968
+ }, "\uD83D\uDC65"), /*#__PURE__*/React.createElement("span", null, t('allUsers'))), /*#__PURE__*/React.createElement("div", {
969
+ className: `eco-reach-audience-option ${value?.type === 'segment' ? 'active' : ''}`,
970
+ onClick: () => onChange?.({
971
+ type: 'segment'
972
+ })
973
+ }, /*#__PURE__*/React.createElement("span", {
974
+ className: "eco-reach-audience-icon"
975
+ }, "\uD83C\uDFAF"), /*#__PURE__*/React.createElement("span", null, t('segment'))), /*#__PURE__*/React.createElement("div", {
976
+ className: `eco-reach-audience-option ${value?.type === 'upload' ? 'active' : ''}`,
977
+ onClick: () => onChange?.({
978
+ type: 'upload'
979
+ })
980
+ }, /*#__PURE__*/React.createElement("span", {
981
+ className: "eco-reach-audience-icon"
982
+ }, "\uD83D\uDCE4"), /*#__PURE__*/React.createElement("span", null, t('uploadList')))), value?.type === 'segment' && segments.length > 0 && /*#__PURE__*/React.createElement("div", {
983
+ className: "eco-reach-segment-list"
984
+ }, segments.map(segment => /*#__PURE__*/React.createElement("div", {
985
+ key: segment.id,
986
+ className: `eco-reach-segment-item ${value?.segmentId === segment.id ? 'active' : ''}`,
987
+ onClick: () => onChange?.({
988
+ type: 'segment',
989
+ segmentId: segment.id
990
+ })
991
+ }, /*#__PURE__*/React.createElement("span", null, segment.name), /*#__PURE__*/React.createElement("span", {
992
+ className: "eco-reach-segment-count"
993
+ }, segment.user_count, " \u4EBA")))), estimatedCount !== undefined && /*#__PURE__*/React.createElement("div", {
994
+ className: "eco-reach-estimated"
995
+ }, "\u9884\u4F30\u89E6\u8FBE\u4EBA\u6570: ", /*#__PURE__*/React.createElement("strong", null, estimatedCount.toLocaleString()), " \u4EBA"));
996
+ }
997
+
998
+ /**
999
+ * CampaignStats 组件 - 活动统计
1000
+ */
1001
+ function CampaignStats({
1002
+ stats
1003
+ }) {
1004
+ if (!stats) return null;
1005
+ const metrics = [{
1006
+ key: 'sent',
1007
+ label: t('delivered'),
1008
+ value: stats.sent || 0
1009
+ }, {
1010
+ key: 'opened',
1011
+ label: t('opened'),
1012
+ value: stats.opened || 0
1013
+ }, {
1014
+ key: 'clicked',
1015
+ label: t('clicked'),
1016
+ value: stats.clicked || 0
1017
+ }, {
1018
+ key: 'converted',
1019
+ label: t('converted'),
1020
+ value: stats.converted || 0
1021
+ }, {
1022
+ key: 'failed',
1023
+ label: t('failed'),
1024
+ value: stats.failed || 0
1025
+ }, {
1026
+ key: 'bounced',
1027
+ label: t('bounced'),
1028
+ value: stats.bounced || 0
1029
+ }];
1030
+ return /*#__PURE__*/React.createElement("div", {
1031
+ className: "eco-reach-stats-detail"
1032
+ }, metrics.map(metric => /*#__PURE__*/React.createElement("div", {
1033
+ key: metric.key,
1034
+ className: "eco-reach-stats-item"
1035
+ }, /*#__PURE__*/React.createElement("div", {
1036
+ className: "eco-reach-stats-value"
1037
+ }, metric.value.toLocaleString()), /*#__PURE__*/React.createElement("div", {
1038
+ className: "eco-reach-stats-label"
1039
+ }, metric.label), stats.total > 0 && /*#__PURE__*/React.createElement("div", {
1040
+ className: "eco-reach-stats-rate"
1041
+ }, (metric.value / stats.total * 100).toFixed(1), "%"))));
1042
+ }
1043
+
1044
+ /**
1045
+ * CampaignList 组件
1046
+ */
1047
+ function CampaignList({
1048
+ campaigns = [],
1049
+ loading = false,
1050
+ onCampaignClick,
1051
+ onSend,
1052
+ onPause,
1053
+ onCancel
1054
+ }) {
1055
+ if (loading && campaigns.length === 0) {
1056
+ return /*#__PURE__*/React.createElement("div", {
1057
+ className: "eco-reach-loading"
1058
+ }, "\u52A0\u8F7D\u4E2D...");
1059
+ }
1060
+ if (campaigns.length === 0) {
1061
+ return /*#__PURE__*/React.createElement("div", {
1062
+ className: "eco-reach-empty"
1063
+ }, "\u6682\u65E0\u89E6\u8FBE\u6D3B\u52A8");
1064
+ }
1065
+ return /*#__PURE__*/React.createElement("div", {
1066
+ className: "eco-reach-list"
1067
+ }, campaigns.map(campaign => /*#__PURE__*/React.createElement(CampaignCard, {
1068
+ key: campaign.id,
1069
+ campaign: campaign,
1070
+ onClick: onCampaignClick,
1071
+ onSend: onSend,
1072
+ onPause: onPause,
1073
+ onCancel: onCancel
1074
+ })));
1075
+ }
1076
+
1077
+ /**
1078
+ * @quantabit/reach-sdk
1079
+ * Campaign SDK - Full Version
1080
+ */
1081
+
1082
+ const getActiveCampaigns = params => reachApi.getActiveCampaigns(params);
1083
+ const getCampaign = campaignId => reachApi.getCampaign(campaignId);
1084
+ const joinCampaign = (campaignId, data) => reachApi.joinCampaign(campaignId, data);
1085
+ const getPopups = params => reachApi.getPopups(params);
1086
+ const getBanners = params => reachApi.getBanners(params);
1087
+ const trackEvent = (event, data) => reachApi.trackEvent(event, data);
1088
+ const getPushPreferences = params => reachApi.getPushPreferences(params);
1089
+ const updatePushPreferences = data => reachApi.updatePushPreferences(data);
1090
+ const subscribe = params => reachApi.subscribe(params);
1091
+ const unsubscribe = params => reachApi.unsubscribe(params);
1092
+
1093
+ exports.AudienceSelector = AudienceSelector;
1094
+ exports.AudienceType = AudienceType;
1095
+ exports.CampaignCard = CampaignCard;
1096
+ exports.CampaignList = CampaignList;
1097
+ exports.CampaignStats = CampaignStats;
1098
+ exports.CampaignStatus = CampaignStatus;
1099
+ exports.ChannelSelector = ChannelSelector;
1100
+ exports.ChannelType = ChannelType;
1101
+ exports.ReachApiClient = ReachApiClient;
1102
+ exports.SUPPORTED_LANGUAGES = SUPPORTED_LANGUAGES;
1103
+ exports.TemplateType = TemplateType;
1104
+ exports.getActiveCampaigns = getActiveCampaigns;
1105
+ exports.getBanners = getBanners;
1106
+ exports.getCampaign = getCampaign;
1107
+ exports.getLanguage = getLanguage;
1108
+ exports.getPopups = getPopups;
1109
+ exports.getPushPreferences = getPushPreferences;
1110
+ exports.joinCampaign = joinCampaign;
1111
+ exports.messages = messages;
1112
+ exports.reachApi = reachApi;
1113
+ exports.setLanguage = setLanguage;
1114
+ exports.subscribe = subscribe;
1115
+ exports.t = t;
1116
+ exports.trackEvent = trackEvent;
1117
+ exports.unsubscribe = unsubscribe;
1118
+ exports.updatePushPreferences = updatePushPreferences;
1119
+ exports.useAudience = useAudience;
1120
+ exports.useCampaign = useCampaign;
1121
+ exports.useCampaigns = useCampaigns;
1122
+ exports.useTemplates = useTemplates;
1123
+
1124
+ }));
1125
+ //# sourceMappingURL=index.umd.js.map