iphone-xudale 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/.eslintrc.json +3 -0
  2. package/app/(pages)/options/page.tsx +720 -0
  3. package/app/(pages)/popup/page.tsx +166 -0
  4. package/app/(pages)/tips/page.tsx +40 -0
  5. package/app/components/DropListBox.tsx +157 -0
  6. package/app/components/SVGPlay.tsx +14 -0
  7. package/app/favicon.ico +0 -0
  8. package/app/globals.css +27 -0
  9. package/app/layout.tsx +15 -0
  10. package/app/page.tsx +3 -0
  11. package/app/scripts/content/checkoutSteps.ts +341 -0
  12. package/app/scripts/content/doFroApplePages.ts +206 -0
  13. package/app/scripts/content/getPageInitInfo.ts +49 -0
  14. package/app/scripts/content/getStoreCanPickInfo.ts +251 -0
  15. package/app/scripts/content/goOrderSteps.ts +179 -0
  16. package/app/scripts/content/index.ts +56 -0
  17. package/app/scripts/content/playSystemNotifacation.ts +39 -0
  18. package/app/scripts/content/sendSelfNotificatioin.ts +27 -0
  19. package/app/scripts/inject/index.ts +18 -0
  20. package/app/shared/constants.ts +236 -0
  21. package/app/shared/interface.ts +25 -0
  22. package/app/shared/location/city.json +1774 -0
  23. package/app/shared/location/county.json +17115 -0
  24. package/app/shared/location/province.json +94 -0
  25. package/app/shared/util.ts +93 -0
  26. package/buildAfter.js +86 -0
  27. package/bunBuild.ts +54 -0
  28. package/extension/content-script.js +7 -0
  29. package/extension/favicon.ico +0 -0
  30. package/extension/icons/icon128.png +0 -0
  31. package/extension/icons/icon16.png +0 -0
  32. package/extension/icons/icon19-disable.png +0 -0
  33. package/extension/icons/icon19.png +0 -0
  34. package/extension/icons/icon32.png +0 -0
  35. package/extension/icons/icon38.png +0 -0
  36. package/extension/icons/icon48.png +0 -0
  37. package/extension/inject-script.js +1 -0
  38. package/extension/manifest.json +38 -0
  39. package/extension/service-worker.js +62 -0
  40. package/extension.next.config.js +25 -0
  41. package/icon_generator.py +30 -0
  42. package/middleware.ts +21 -0
  43. package/next.config.js +15 -0
  44. package/package.json +67 -0
  45. package/postcss.config.js +6 -0
  46. package/public/assets/images/SCR-20230916-nbkz.png +0 -0
  47. package/public/assets/images/SCR-20230916-nbyv.png +0 -0
  48. package/public/assets/images/SCR-20230916-ncte.png +0 -0
  49. package/public/assets/images/SCR-20230916-ndgw.png +0 -0
  50. package/public/assets/images/SCR-20230916-ndks.png +0 -0
  51. package/public/assets/images/SCR-20230916-neaa.png +0 -0
  52. package/public/assets/images/SCR-20230916-neeq.jpeg +0 -0
  53. package/public/assets/images/SCR-20230916-nfkt.png +0 -0
  54. package/public/assets/images/SCR-20230919-ulfn.png +0 -0
  55. package/public/assets/images/SCR-20230919-ulzd.png +0 -0
  56. package/public/assets/images/SCR-20230919-uocr.png +0 -0
  57. package/public/icon_original.png +0 -0
  58. package/public/next.svg +1 -0
  59. package/public/vercel.svg +1 -0
  60. package/tailwind.config.ts +20 -0
  61. package/tsconfig.json +27 -0
  62. package/types/global.d.ts +19 -0
@@ -0,0 +1,720 @@
1
+ 'use client'
2
+ import { restoreFromStorage, saveToStorage, sleep } from '@/app/shared/util'
3
+ import {
4
+ storeKeys,
5
+ billItemList,
6
+ defaultiPhoneOrderConfig,
7
+ billTypeKeys,
8
+ defaultPayinstallmentTotal,
9
+ } from '@/app/shared/constants'
10
+ import DropListBox from '@/app/components/DropListBox'
11
+ import { IPHONEORDER_CONFIG } from '@/app/shared/interface'
12
+ import { useCallback, useEffect, useState, useRef, useMemo } from 'react'
13
+ import { filter as _filter, map as _map, find as _find, findIndex as _findIndex, sortBy as _sortBy } from 'lodash'
14
+ import city from '@/app/shared/location/city.json'
15
+ import province from '@/app/shared/location/province.json'
16
+ import county from '@/app/shared/location/county.json'
17
+
18
+ const defaultItem = { id: '', name: '' }
19
+
20
+ type VoiceType = { lang?: string; voiceName?: string; id: string; name: string }
21
+
22
+ export default function Options() {
23
+ const [config, setConfig] = useState<IPHONEORDER_CONFIG>(defaultiPhoneOrderConfig)
24
+ const [payinstallmentList, setpayinstallmentList] = useState([defaultItem])
25
+ const [provinceList, setProvinceList] = useState(province)
26
+ const [selectedProvinceIndex, setSelectedProvinceIndex] = useState(0)
27
+ const [cityList, setCityList] = useState([defaultItem])
28
+ const [selectedCityIndex, setSelectedCityIndex] = useState(0)
29
+ const [districtList, setDistrictList] = useState([defaultItem])
30
+ const [selectedDistrictIndex, setSelectedDistrictIndex] = useState(0)
31
+ const [voiceList, setVoiceList] = useState<VoiceType[]>([defaultItem])
32
+ const firstNameRef = useRef<HTMLInputElement>(null)
33
+ const lastNameRef = useRef<HTMLInputElement>(null)
34
+ const last4codeRef = useRef<HTMLInputElement>(null)
35
+ const mobileRef = useRef<HTMLInputElement>(null)
36
+ const appleidRef = useRef<HTMLInputElement>(null)
37
+ const passwordRef = useRef<HTMLInputElement>(null)
38
+ const stepWaitRef = useRef<HTMLInputElement>(null)
39
+ const beforeReloadCountRef = useRef<HTMLInputElement>(null)
40
+ const voiceTimesRef = useRef<HTMLInputElement>(null)
41
+ const voiceTextRef = useRef<HTMLInputElement>(null)
42
+ const notiAPIRef = useRef<HTMLInputElement>(null)
43
+
44
+ useEffect(() => {
45
+ restoreFromStorage(storeKeys.orderConfig).then(data => {
46
+ if (data) {
47
+ console.log(`restoreFromStorage`, data)
48
+ setConfig(config => {
49
+ return {
50
+ ...config,
51
+ ...(data as IPHONEORDER_CONFIG),
52
+ }
53
+ })
54
+ }
55
+ })
56
+
57
+ const syncFun = async () => {
58
+ const voiceList = await getVoices()
59
+ setVoiceList(voiceList)
60
+ }
61
+ syncFun()
62
+ }, [])
63
+
64
+ const voiceSelected = useMemo(() => {
65
+ let _index = -1
66
+ const { lang, voiceName } = config?.voiceInfo || {}
67
+ if (lang && voiceName && voiceList.length) {
68
+ _index = _findIndex(voiceList, v => {
69
+ return v.lang == lang && v.voiceName == voiceName
70
+ })
71
+ }
72
+ if (_index < 0) _index = 0
73
+ return _index
74
+ }, [voiceList, config.voiceInfo])
75
+ // ************ 更新选中支付方式 ************
76
+ const billSelected = useMemo(() => {
77
+ return (
78
+ (config?.payBill &&
79
+ _findIndex(billItemList, _b => {
80
+ return _b.id == config.payBill
81
+ })) ||
82
+ 0
83
+ )
84
+ }, [config.payBill])
85
+
86
+ // ************ 更新选中分期笔数 ************
87
+ const payinstallmentSelected = useMemo(() => {
88
+ return (
89
+ (config?.payInstallment &&
90
+ _findIndex(payinstallmentList, _b => {
91
+ return _b.id == String(config.payInstallment)
92
+ })) ||
93
+ 0
94
+ )
95
+ }, [config.payInstallment, payinstallmentList])
96
+
97
+ // ************ 👇下拉菜单联动👇 ************
98
+ useEffect(() => {
99
+ const newPayinstallmentList = _map(
100
+ _filter(defaultPayinstallmentTotal, item => {
101
+ if (item.id == 0) return true
102
+ if (item?.includes && Number(item?.includes?.length) > 0) {
103
+ return item.includes.includes(config.payBill)
104
+ }
105
+ return false
106
+ }),
107
+ (_item: any) => {
108
+ return {
109
+ id: _item.id,
110
+ name: _item.name,
111
+ }
112
+ }
113
+ )
114
+
115
+ setpayinstallmentList(newPayinstallmentList)
116
+ // 当原来的分期笔数不存在时
117
+ if (
118
+ !_find(newPayinstallmentList, _t => {
119
+ return _t.id == config.payInstallment
120
+ })
121
+ ) {
122
+ setConfig({
123
+ ...config,
124
+ payInstallment: Number(newPayinstallmentList[0].id) || 0,
125
+ })
126
+ }
127
+ }, [config.payBill])
128
+
129
+ useEffect(() => {
130
+ if (config.provinceName) {
131
+ let provinceIndex: number = _findIndex(province, item => {
132
+ return item.name == config.provinceName
133
+ })
134
+ provinceIndex = provinceIndex > -1 ? provinceIndex : 0
135
+
136
+ setSelectedProvinceIndex(provinceIndex)
137
+ const provinceId = province[provinceIndex].id
138
+ // @ts-ignore
139
+ const newCityList = city[provinceId]
140
+ setCityList(newCityList)
141
+ if (
142
+ !_find(newCityList, _t => {
143
+ return _t.name == config.cityName
144
+ })
145
+ ) {
146
+ setConfig({
147
+ ...config,
148
+ cityName: newCityList[0].name,
149
+ })
150
+ }
151
+ // if (provinceList.length < 1) {
152
+ // setProvinceList(province)
153
+ // }
154
+ }
155
+ }, [config.provinceName])
156
+
157
+ useEffect(() => {
158
+ if (config.cityName) {
159
+ let cityIndex: number = _findIndex(cityList, item => {
160
+ return item.name == config.cityName
161
+ })
162
+ cityIndex = cityIndex > -1 ? cityIndex : 0
163
+ setSelectedCityIndex(cityIndex)
164
+ const cityId: string = cityList[cityIndex].id
165
+ // @ts-ignore
166
+ const newDistrictList = county[cityId]
167
+ // @ts-ignore
168
+ console.log(`cityIndex`, cityIndex, cityList[cityIndex], county[cityId])
169
+ if (newDistrictList) {
170
+ setDistrictList(newDistrictList)
171
+ if (
172
+ !_find(newDistrictList, _t => {
173
+ return _t.name == config.districtName
174
+ })
175
+ ) {
176
+ setConfig({
177
+ ...config,
178
+ districtName: newDistrictList[0].name,
179
+ })
180
+ }
181
+ }
182
+ }
183
+ }, [config.cityName, cityList])
184
+
185
+ useEffect(() => {
186
+ // setSelectedDistrictIndex
187
+ let districtIndex: number = _findIndex(districtList, item => {
188
+ return item.name == config.districtName
189
+ })
190
+ districtIndex = districtIndex > -1 ? districtIndex : 0
191
+ setSelectedDistrictIndex(districtIndex)
192
+ }, [config.districtName, districtList])
193
+ // ************ 👆下拉菜单联动👆 ************
194
+
195
+ // ************ 支付方式 ************
196
+ const handleSelectPayType = (payItem: Record<string, any>) => {
197
+ setConfig(prev => {
198
+ return {
199
+ ...prev,
200
+ payBill: payItem.id,
201
+ }
202
+ })
203
+ }
204
+
205
+ // ************ 分期笔数 ************
206
+ const handleSelectPayinstallment = (payinstallmentItem: Record<string, any>) => {
207
+ setConfig(prev => {
208
+ return {
209
+ ...prev,
210
+ payInstallment: payinstallmentItem.id,
211
+ }
212
+ })
213
+ }
214
+
215
+ // ************ 选中省份 ************
216
+ const handleSelectProvince = (provinceItem: Record<string, any>) => {
217
+ setConfig(prev => {
218
+ return {
219
+ ...prev,
220
+ provinceName: provinceItem.name,
221
+ }
222
+ })
223
+ }
224
+ // ************ 选中城市 ************
225
+ const handleSelectCity = (cityItem: Record<string, any>) => {
226
+ setConfig(prev => {
227
+ return {
228
+ ...prev,
229
+ cityName: cityItem.name,
230
+ }
231
+ })
232
+ }
233
+
234
+ // ************ 选中区域 ************
235
+ const handleSelectDistrict = (districtItem: Record<string, any>) => {
236
+ setConfig(prev => {
237
+ return {
238
+ ...prev,
239
+ districtName: districtItem.name,
240
+ }
241
+ })
242
+ }
243
+
244
+ const handleSelectVoice = (voiceItem: Record<string, any>) => {
245
+ setConfig(prev => {
246
+ return {
247
+ ...prev,
248
+ voiceInfo: {
249
+ ...prev.voiceInfo,
250
+ lang: voiceItem.lang,
251
+ voiceName: voiceItem.voiceName,
252
+ },
253
+ }
254
+ })
255
+ }
256
+ const handleSave = useCallback(() => {
257
+ const saveConfig: IPHONEORDER_CONFIG = {
258
+ ...config,
259
+ firstName: firstNameRef.current?.value,
260
+ lastName: lastNameRef.current?.value,
261
+ last4code: last4codeRef.current?.value,
262
+ mobile: mobileRef.current?.value,
263
+ appleId: appleidRef.current?.value,
264
+ password: passwordRef.current?.value,
265
+ stepWait: Number(stepWaitRef.current?.value) || config.stepWait || 10,
266
+ afterCountThenReload: Number(beforeReloadCountRef.current?.value) || config.afterCountThenReload || 50,
267
+ selfNotiAPI: notiAPIRef.current?.value || '',
268
+ voiceInfo: {
269
+ ...config.voiceInfo,
270
+ text: voiceTextRef.current?.value || defaultiPhoneOrderConfig.voiceInfo.text,
271
+ voiceName: config.voiceInfo.voiceName || '',
272
+ lang: config.voiceInfo.lang || '',
273
+ times: Number(voiceTimesRef.current?.value) || defaultiPhoneOrderConfig.voiceInfo.times,
274
+ },
275
+ }
276
+ console.log(saveConfig)
277
+ saveAsync(saveConfig)
278
+ }, [config])
279
+
280
+ const handleCancel = useCallback(() => {
281
+ window.close()
282
+ }, [])
283
+
284
+ const handlePlaySound = () => {
285
+ const _text = voiceTextRef.current?.value || defaultiPhoneOrderConfig.voiceInfo.text,
286
+ _voiceName = config.voiceInfo.voiceName || '',
287
+ _lang = config.voiceInfo.lang || ''
288
+ let voiceOptions = {}
289
+ if (config.voiceInfo.voiceName && config.voiceInfo.lang) {
290
+ voiceOptions = {
291
+ lang: config.voiceInfo.lang,
292
+ voiceName: config.voiceInfo.voiceName,
293
+ }
294
+ }
295
+ if (typeof chrome !== 'undefined' && chrome?.tts) {
296
+ chrome.tts.speak(_text, voiceOptions)
297
+ }
298
+ }
299
+
300
+ const inputClass = `px-3 block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6`
301
+ const labelClass = `block text-sm font-medium leading-6 text-gray-900`
302
+ return (
303
+ <main className="flex min-h-screen flex-col items-center justify-between p-24">
304
+ <div className="space-y-12">
305
+ <div className="border-b border-gray-900/10 pb-12">
306
+ <h2 className="text-base font-semibold leading-7 text-gray-900">配置信息</h2>
307
+ <p className="mt-1 text-sm leading-6 text-gray-600">以下信息用于抢购iPhone时自动填入</p>
308
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
309
+ <div className="sm:col-span-3">
310
+ <label htmlFor="last-name" className={labelClass}>
311
+ 姓氏
312
+ </label>
313
+ <div className="mt-2">
314
+ <input
315
+ ref={lastNameRef}
316
+ type="text"
317
+ name="last-name"
318
+ id="last-name"
319
+ autoComplete="family-name"
320
+ className={inputClass}
321
+ defaultValue={config.lastName}
322
+ />
323
+ </div>
324
+ </div>
325
+
326
+ <div className="sm:col-span-3">
327
+ <label htmlFor="first-name" className={labelClass}>
328
+ 名字
329
+ </label>
330
+ <div className="mt-2">
331
+ <input
332
+ ref={firstNameRef}
333
+ type="text"
334
+ name="first-name"
335
+ id="first-name"
336
+ autoComplete="given-name"
337
+ className={inputClass}
338
+ defaultValue={config.firstName}
339
+ />
340
+ </div>
341
+ </div>
342
+
343
+ <div className="sm:col-span-3">
344
+ <label htmlFor="mobile-number" className={labelClass}>
345
+ 手机号
346
+ </label>
347
+ <div className="mt-2">
348
+ <input
349
+ type="tel"
350
+ ref={mobileRef}
351
+ name="mobile-number"
352
+ id="mobile-number"
353
+ className={inputClass}
354
+ defaultValue={config.mobile}
355
+ />
356
+ </div>
357
+ </div>
358
+
359
+ <div className="sm:col-span-3">
360
+ <label htmlFor="last-code" className={labelClass}>
361
+ 身份证后四位
362
+ </label>
363
+ <div className="mt-2">
364
+ <input
365
+ type="text"
366
+ ref={last4codeRef}
367
+ name="last-code"
368
+ id="last-code"
369
+ className={inputClass}
370
+ defaultValue={config.last4code}
371
+ />
372
+ </div>
373
+ </div>
374
+
375
+ <div className="sm:col-span-3">
376
+ <label htmlFor="email" className={labelClass}>
377
+ 邮箱/Apple ID
378
+ </label>
379
+ <div className="mt-2">
380
+ <input
381
+ ref={appleidRef}
382
+ id="email"
383
+ name="email"
384
+ type="email"
385
+ autoComplete="email"
386
+ className={inputClass}
387
+ defaultValue={config.appleId}
388
+ />
389
+ </div>
390
+ </div>
391
+
392
+ <div className="sm:col-span-3">
393
+ <label htmlFor="password" className={labelClass}>
394
+ 登录密码(可选,不填则以访客模式下单)
395
+ </label>
396
+ <div className="mt-2">
397
+ <input
398
+ ref={passwordRef}
399
+ id="password"
400
+ name="password"
401
+ type="password"
402
+ autoComplete="password"
403
+ className={inputClass}
404
+ defaultValue={config.password}
405
+ />
406
+ </div>
407
+ </div>
408
+
409
+ <div className="sm:col-span-3">
410
+ <label htmlFor="pay-type" className={labelClass}>
411
+ 支付方式
412
+ </label>
413
+ <div className="mt-2">
414
+ <DropListBox
415
+ itemList={billItemList}
416
+ domID={'pay-type'}
417
+ selectedIndex={billSelected}
418
+ callback={handleSelectPayType}
419
+ />
420
+ </div>
421
+ </div>
422
+
423
+ <div className="sm:col-span-3">
424
+ <label htmlFor="pay-installment" className={labelClass}>
425
+ 分期笔数
426
+ </label>
427
+ <div className="mt-2">
428
+ <DropListBox
429
+ itemList={payinstallmentList}
430
+ domID={'pay-installment'}
431
+ selectedIndex={payinstallmentSelected}
432
+ callback={handleSelectPayinstallment}
433
+ />
434
+ </div>
435
+ </div>
436
+
437
+ <div className="sm:col-span-2 sm:col-start-1">
438
+ <label htmlFor="province-list" className={labelClass}>
439
+ 省份
440
+ </label>
441
+ <div className="mt-2">
442
+ <DropListBox
443
+ itemList={provinceList}
444
+ selectedIndex={selectedProvinceIndex}
445
+ domID={'province-list'}
446
+ callback={handleSelectProvince}
447
+ />
448
+ </div>
449
+ </div>
450
+
451
+ <div className="sm:col-span-2">
452
+ <label htmlFor="city-list" className={labelClass}>
453
+ 城市
454
+ </label>
455
+ <div className="mt-2">
456
+ <DropListBox
457
+ itemList={cityList}
458
+ domID={'city-list'}
459
+ callback={handleSelectCity}
460
+ selectedIndex={selectedCityIndex}
461
+ />
462
+ </div>
463
+ </div>
464
+
465
+ <div className="sm:col-span-2">
466
+ <label htmlFor="district-list" className={labelClass}>
467
+ 区名
468
+ </label>
469
+ <div className="mt-2">
470
+ <DropListBox
471
+ itemList={districtList}
472
+ selectedIndex={selectedDistrictIndex}
473
+ domID={'district-list'}
474
+ callback={handleSelectDistrict}
475
+ />
476
+ </div>
477
+ </div>
478
+ </div>
479
+ </div>
480
+
481
+ <div className="border-b border-gray-900/10 pb-12">
482
+ <h2 className="text-base font-semibold leading-7 text-gray-900">系统设置</h2>
483
+ <h4 className="text-sm font-medium leading-7 text-gray-900">
484
+ 如果你不了解以下配置应该如何设置,保持默认值即可。
485
+ </h4>
486
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
487
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
488
+ <div className="sm:col-span-3">
489
+ <label htmlFor="step-wait" className={labelClass}>
490
+ StepWait (步频等待秒数。默认10秒,不建议设置过短,会封IP)
491
+ </label>
492
+ <div className="mt-2">
493
+ <input
494
+ ref={stepWaitRef}
495
+ id={'step-wait'}
496
+ min={1}
497
+ max={20}
498
+ step={0.5}
499
+ type="number"
500
+ defaultValue={config.stepWait}
501
+ className={inputClass}
502
+ />
503
+ </div>
504
+ </div>
505
+ <div className="sm:col-span-3">
506
+ <label htmlFor="beforereload-count" className={labelClass}>
507
+ 每周期重试次数 (默认50,后台请求达到该次数才会刷新页面,防止签名过期)
508
+ </label>
509
+ <div className="mt-2">
510
+ <input
511
+ ref={beforeReloadCountRef}
512
+ id={'beforereload-count'}
513
+ min={1}
514
+ max={100}
515
+ step={1}
516
+ type="number"
517
+ defaultValue={config.afterCountThenReload}
518
+ className={inputClass}
519
+ />
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
525
+ <div className="sm:col-span-3">
526
+ <label htmlFor="voice-list" className={labelClass}>
527
+ 提示音声音
528
+ </label>
529
+ <div className="mt-2">
530
+ <DropListBox
531
+ itemList={voiceList}
532
+ selectedIndex={voiceSelected}
533
+ domID={'voice-list'}
534
+ callback={handleSelectVoice}
535
+ />
536
+ </div>
537
+ </div>
538
+ <div className="sm:col-span-3">
539
+ <label htmlFor="voice-times" className={labelClass}>
540
+ 播放次数
541
+ </label>
542
+ <div className="mt-2">
543
+ <input
544
+ ref={voiceTimesRef}
545
+ id="voice-times"
546
+ name="voice-times"
547
+ type="number"
548
+ min={1}
549
+ max={15}
550
+ step={1}
551
+ className={inputClass}
552
+ defaultValue={config.voiceInfo.times}
553
+ />
554
+ </div>
555
+ </div>
556
+
557
+ <div className="sm:col-span-5">
558
+ <label htmlFor="voice-text" className={labelClass}>
559
+ 提示音文本
560
+ </label>
561
+ <div className="mt-2">
562
+ <input
563
+ type="text"
564
+ ref={voiceTextRef}
565
+ name="voice-text"
566
+ id="voice-text"
567
+ className={inputClass}
568
+ defaultValue={config.voiceInfo.text || ''}
569
+ />
570
+ </div>
571
+ </div>
572
+ <div className="sm:col-span-1 align-bottom flex items-end justify-end">
573
+ <div className="flex w-1/2 items-end justify-end">
574
+ <button
575
+ type="submit"
576
+ className="rounded-md w-full bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
577
+ onClick={handlePlaySound}
578
+ >
579
+ 播放
580
+ </button>
581
+ </div>
582
+ </div>
583
+ </div>
584
+ </div>
585
+
586
+ <div className="border-b border-gray-900/10 pb-12">
587
+ <h2 className="text-base font-semibold leading-7 text-gray-900">实验性功能</h2>
588
+ <h4 className="text-sm font-medium leading-7 text-gray-900">
589
+ 如果你不了解以下配置的功能,可以忽略它们。
590
+ </h4>
591
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
592
+ <div className="mt-10 space-y-10">
593
+ <div className="sm:col-span-6">
594
+ <label htmlFor="noti-api" className={labelClass}>
595
+ 自定义消息API (由于跨域限制,仅支持GET请求,将以图片 src 方式引入并调用)
596
+ </label>
597
+ <div className="mt-2">
598
+ <input
599
+ type="text"
600
+ ref={notiAPIRef}
601
+ name="noti-api"
602
+ id="noti-api"
603
+ className={inputClass}
604
+ defaultValue={config.selfNotiAPI || ''}
605
+ />
606
+ </div>
607
+ </div>
608
+ </div>
609
+ </div>
610
+
611
+ {/* <div className="border-b border-gray-900/10 pb-12">
612
+ <h2 className="text-base font-semibold leading-7 text-gray-900">Notifications</h2>
613
+ <p className="mt-1 text-sm leading-6 text-gray-600"></p>
614
+
615
+ <div className="mt-10 space-y-10">
616
+ <fieldset>
617
+ <legend className="text-sm font-semibold leading-6 text-gray-900">By Email</legend>
618
+ <div className="mt-6 space-y-6">
619
+ <div className="relative flex gap-x-3">
620
+ <div className="flex h-6 items-center">
621
+ <input
622
+ id="comments"
623
+ name="comments"
624
+ type="checkbox"
625
+ className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
626
+ />
627
+ </div>
628
+ <div className="text-sm leading-6">
629
+ <label htmlFor="comments" className="font-medium text-gray-900">
630
+ Comments
631
+ </label>
632
+ <p className="text-gray-500">
633
+ Get notified when someones posts a comment on a posting.
634
+ </p>
635
+ </div>
636
+ </div>
637
+
638
+ </div>
639
+ </fieldset>
640
+ <fieldset>
641
+ <legend className="text-sm font-semibold leading-6 text-gray-900">
642
+ Push Notifications
643
+ </legend>
644
+ <p className="mt-1 text-sm leading-6 text-gray-600">
645
+ These are delivered via SMS to your mobile phone.
646
+ </p>
647
+ <div className="mt-6 space-y-6">
648
+ <div className="flex items-center gap-x-3">
649
+ <input
650
+ id="push-everything"
651
+ name="push-notifications"
652
+ type="radio"
653
+ className="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600"
654
+ />
655
+ <label htmlFor="push-everything" className={labelClass}>
656
+ Everything
657
+ </label>
658
+ </div>
659
+
660
+ </div>
661
+ </fieldset>
662
+ </div>
663
+ </div> */}
664
+ </div>
665
+
666
+ <div className="mt-6 flex items-center justify-end gap-x-6">
667
+ <button type="button" className="text-sm font-semibold leading-6 text-gray-900" onClick={handleCancel}>
668
+ 取消
669
+ </button>
670
+ <button
671
+ type="submit"
672
+ className="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
673
+ onClick={handleSave}
674
+ >
675
+ 保存
676
+ </button>
677
+ </div>
678
+ </main>
679
+ )
680
+ }
681
+
682
+ const saveAsync = async (config: IPHONEORDER_CONFIG) => {
683
+ await saveToStorage(config, storeKeys.orderConfig)
684
+ await sleep(1)
685
+ window.close()
686
+ }
687
+
688
+ const validateVoices = ['zh-cn', 'zh-tw', 'zh-hk', 'en-us', 'en-gb']
689
+ const getVoices = async (): Promise<VoiceType[]> => {
690
+ let voiceList: VoiceType[] = [defaultItem]
691
+ if (typeof chrome !== 'undefined' && chrome?.tts) {
692
+ return new Promise((resolve, reject) => {
693
+ chrome.tts.getVoices(
694
+ // @ts-ignore
695
+ function (voices) {
696
+ voiceList = []
697
+ _map(voices, v => {
698
+ const { lang, voiceName } = v || {}
699
+ const prefixlang = lang && lang.toLowerCase()
700
+ if (validateVoices.includes(prefixlang)) {
701
+ voiceList.push({
702
+ lang: lang,
703
+ voiceName: voiceName,
704
+ name: `${lang} - ${voiceName}`,
705
+ id: `${lang} - ${voiceName}`,
706
+ })
707
+ }
708
+ })
709
+ voiceList = _sortBy(voiceList, function (o) {
710
+ const x = !o?.lang ? 9999 : validateVoices.indexOf(o.lang.toLowerCase())
711
+ return x
712
+ })
713
+ resolve(voiceList)
714
+ }
715
+ )
716
+ })
717
+ }
718
+
719
+ return voiceList
720
+ }