码桶
发现社区成员的开源项目
版本历史
5 次提交提交详情
0481b07提交编号
0481b071c133c0a2069c5f7551ba97bc72fa2326提交作者DMFish<codebucket@localhost>
提交时间2026-07-31 11:51:40 +0800
更新说明更新项目文件
文件变更 12 个文件
文件新增删除
.env.example+8-0DEPLOY.md+77-29admin.html+1-0api/data.js+104-0contact.html+5-1css/style.css+257-1index.html+3-0js/admin.js+50-31js/data.js+7-3js/main.js+4-1js/sync.js+65-0package.json+5-2文件差异 逐行查看本次提交的修改
.env.example+8-0
@@ -4,3 +4,11 @@44
# 管理後台密碼(必填)55
# 設置後,密碼只在服務端驗證,前端代碼裡看不到66
ADMIN_PASSWORD=your-secure-password-here7
+8
+# ===== Upstash Redis =====9
+# Redis 的環境變量會在 Vercel 安裝 Upstash 後自動注入,無需手動設置:10
+# KV_REST_API_URL=https://xxx.upstash.io11
+# KV_REST_API_TOKEN=xxx12
+# (部分集成也可能注入 UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN,代碼兩者都兼容)13
+#14
+# 安裝步驟:Vercel 項目 → Storage → Create Database → Upstash RedisDEPLOY.md+77-29
@@ -1,20 +1,22 @@1
-# 域名米表 - Vercel 部署指南1
+# 域名米表 - Vercel 部署指南(Upstash Redis 版)22
## 項目結構33
```44
domain-market/55
├── api/6
-│ └── auth.js ← Vercel Serverless Function(密碼驗證)7
-├── index.html ← 前台域名展示頁8
-├── contact.html ← 聯絡我們頁面9
-├── admin.html ← 後台管理頁10
-├── css/style.css ← 共享樣式6
+│ ├── auth.js ← 密碼驗證(環境變量)7
+│ └── data.js ← Redis 數據讀寫(GET 公開 / POST 認證)8
+├── index.html ← 前台域名展示頁9
+├── contact.html ← 聯絡我們頁面10
+├── admin.html ← 後台管理頁11
+├── css/style.css ← 共享樣式1112
├── js/12
-│ ├── data.js ← 數據層(localStorage + SiteConfig)13
-│ ├── main.js ← 前台邏輯14
-│ └── admin.js ← 後台邏輯(API 登入 + 本地兜底)15
-├── package.json13
+│ ├── sync.js ← 數據同步層(init 拉取 + save 推送)14
+│ ├── data.js ← 數據層(localStorage + Redis 雙寫)15
+│ ├── main.js ← 前台邏輯16
+│ └── admin.js ← 後台邏輯17
+├── package.json ← 含 @upstash/redis 依賴1618
├── vercel.json1719
└── .env.example1820
```@@ -32,39 +34,85 @@ domain-market/3234
5. Build Command 留空(純靜態,不需要構建)3335
6. 點 Deploy34
-### 3. 設置環境變量(關鍵步驟)35
-部署完成後:36
+### 3. 安裝 Upstash Redis(關鍵步驟)37
+38
+> ⚠️ Vercel KV 已於 2024 年底廢棄,現在用 Upstash Redis 替代。39
+40
+1. 進入 Vercel 項目 → 點頂部 **Storage** 標籤頁41
+2. 點 **Create Database** → 選 **Upstash Redis**(如果看不到,直接訪問 [Vercel Marketplace](https://vercel.com/marketplace?category=storage) 搜索 "redis")42
+3. 取個名字(如 `domain-market-db`),選擇區域(建議選離用戶近的)43
+4. 點 **Connect to Project** / **Create & Connect**44
+5. 安裝完成後,Vercel 會自動把以下環境變量注入到項目:45
+ - `UPSTASH_REDIS_REST_URL`46
+ - `UPSTASH_REDIS_REST_TOKEN`47
+48
+### 4. 設置密碼環境變量3649
1. 進入項目 Settings → Environment Variables3750
2. 添加變量:3851
- Name: `ADMIN_PASSWORD`3952
- Value: `你的管理密碼`(例如 `MySecurePass2024!`)4053
3. 點 Save41
-4. 回到 Deployments,點最新一條旁邊的 `...` → Redeploy42
-### 4. 完成驗證54
+### 5. 重新部署55
+回到 Deployments,點最新一條旁邊的 `...` → **Redeploy**56
+57
+### 6. 完成驗證4358
- 打開 `https://你的項目.vercel.app/admin.html`4459
- 輸入你設置的 `ADMIN_PASSWORD` 密碼45
-- 成功登入 = 部署完成60
+- 成功登入後,添加幾個域名61
+- 打開 `https://你的自定義域名/` → 應該能看到剛添加的域名62
+63
+## 數據存儲架構64
+65
+```66
+管理員添加域名67
+ ↓68
+DomainDB.save() → localStorage(即時) + POST /api/data → Upstash Redis(持久化)69
+ ↓70
+訪客打開任意域名 → DataSync.init() → GET /api/data → Redis 讀取 → 覆蓋 localStorage → 顯示71
+```46
-## 密碼安全說明72
+| 特性 | localStorage | Upstash Redis |73
+|------|-------------|-----------|74
+| 跨域名共享 | 否 | ✅ 是 |75
+| 跨瀏覽器共享 | 否 | ✅ 是 |76
+| 換設備保留 | 否 | ✅ 是 |77
+| 清緩存丟失 | 是 | 否,Redis 裡還有 |78
+| 免費額度 | 無限 | 10,000 命令/天 |79
+80
+## 安全說明4781
| 模式 | 密碼位置 | 前端可見? |4882
|------|----------|-----------|49
-| Vercel 部署 | 環境變量 `ADMIN_PASSWORD` | 否,密碼只在服務端驗證 |50
-| 純靜態部署(VPS/CDN) | localStorage | 是,默認密碼 `admin888` 寫在 JS 裡 |83
+| Vercel 部署 | 環境變量 `ADMIN_PASSWORD` | 否 |84
+| 純靜態部署(VPS/CDN) | localStorage | 是,默認 `admin888` |85
+86
+### API 權限87
+- `GET /api/data` — **公開**,任何人可讀取域名列表和網站設置88
+- `POST /api/data` — **需認證**,只有登入後的管理員才能寫入51
-部署在 Vercel 上時,登入邏輯:52
-1. 前端發送密碼到 `/api/auth`53
-2. Serverless Function 用環境變量比對54
-3. 返回成功/失敗,密碼本身不會返回前端55
-4. 登入狀態存在 sessionStorage(關閉瀏覽器後失效)89
+## 修改密碼56
-## 數據說明90
+到 Vercel 項目 Settings → Environment Variables 修改 `ADMIN_PASSWORD`,然後 Redeploy。57
-域名數據和網站設置仍存在瀏覽器的 localStorage 中(按瀏覽器隔離)。58
-換電腦前請用後台的「導出 JSON」備份,在新電腦「匯入 JSON」恢復。91
+## 從舊版升級(已有數據遷移)59
-## 修改密碼92
+如果之前已在 vercel.app 域名上添加過域名(存在瀏覽器 localStorage):60
-- **Vercel 模式**:到 Vercel 項目 Settings → Environment Variables 修改 `ADMIN_PASSWORD`,然後 Redeploy61
-- **靜態模式**:後台 → 修改密碼(僅影響當前瀏覽器)93
+1. 打開 `https://舊域名.vercel.app/admin.html`94
+2. 點「導出 JSON」下載備份95
+3. 確認 Upstash Redis 已安裝並連接到項目96
+4. 重新部署(Redeploy)使新版代碼生效97
+5. 打開 `https://你的域名/admin.html`,登入98
+6. 點「匯入 JSON」上傳剛下載的備份99
+7. 數據自動寫入 Redis,之後所有域名都能看到100
+101
+## 本地開發102
+103
+不裝 Redis 也能跑。DataSync 會檢測到 Redis 不可用,自動回退到純 localStorage 模式:104
+105
+```bash106
+cd domain-market107
+python -m http.server 8765108
+# 打開 http://localhost:8765/109
+```admin.html+1-0
@@ -272,6 +272,7 @@272272
</div>273273
</div>274
+<script src="js/sync.js"></script>274275
<script src="js/data.js"></script>275276
<script src="js/admin.js"></script>276277
</body>contact.html+5-1
@@ -8,6 +8,8 @@88
</head>99
<body>10
+<button class="nav-toggle" onclick="var s=document.querySelector('.sidebar');s.classList.toggle('sidebar-open');this.querySelector('.hamburger').textContent=s.classList.contains('sidebar-open')?'✕':'☰';"><span class="hamburger">☰</span> 菜單</button>11
+1012
<table class="main-table">1113
<tr>1214
<!-- 側邊導航 -->@@ -39,9 +41,11 @@3941
</tr>4042
</table>43
+<script src="js/sync.js"></script>4144
<script src="js/data.js"></script>4245
<script>43
-(function() {46
+(async function() {47
+ await DataSync.init();4448
const cfg = SiteConfig.get();4549
// 標題css/style.css+257-1
@@ -516,7 +516,16 @@ body {516516
to { transform: translateX(0); opacity: 1; }517517
}518
-/* 響應式 */518
+/* ============================================519
+ 響應式 - 手機端適配520
+ ============================================ */521
+522
+/* ---- 導航開關按鈕(僅手機端顯示) ---- */523
+.nav-toggle {524
+ display: none;525
+}526
+527
+/* ---- 平板及以下 ---- */519528
@media (max-width: 920px) {520529
.main-table {521530
width: 100%;@@ -541,3 +550,250 @@ body {541550
padding: 15px;542551
}543552
}553
+554
+/* ---- 手機端 ---- */555
+@media (max-width: 768px) {556
+557
+ /* ===== 通用 ===== */558
+ body {559
+ font-size: 14px;560
+ }561
+562
+ /* ===== 導航開關按鈕 ===== */563
+ .nav-toggle {564
+ display: block;565
+ width: 100%;566
+ background: #cc0066;567
+ color: #FFF;568
+ padding: 10px 16px;569
+ border: none;570
+ font-size: 15px;571
+ font-family: inherit;572
+ font-weight: bold;573
+ cursor: pointer;574
+ text-align: left;575
+ position: sticky;576
+ top: 0;577
+ z-index: 100;578
+ }579
+ .nav-toggle .hamburger {580
+ display: inline-block;581
+ margin-right: 8px;582
+ font-size: 18px;583
+ }584
+585
+ /* ===== 主佈局 → block 模式 ===== */586
+ .main-table,587
+ .main-table > tbody,588
+ .main-table > tbody > tr,589
+ .main-table > tbody > tr > td {590
+ display: block;591
+ width: 100%;592
+ }593
+ .main-table {594
+ border: none;595
+ }596
+ .main-table > tbody > tr > td.content {597
+ padding: 8px;598
+ }599
+600
+ /* ===== 側邊導航 → 預設收起,展開時下拉 ===== */601
+ .main-table > tbody > tr > td.sidebar {602
+ display: none;603
+ width: 100%;604
+ border-right: none;605
+ border-bottom: 2px solid #FF00FF;606
+ padding: 8px 0;607
+ background: #FF99FF;608
+ }609
+ .main-table > tbody > tr > td.sidebar.sidebar-open {610
+ display: block;611
+ }612
+613
+ .nav-btn {614
+ display: inline-block;615
+ margin: 4px;616
+ font-size: 16px;617
+ padding: 8px 14px;618
+ border-width: 3px;619
+ }620
+621
+ /* ===== 標題區 ===== */622
+ .title-en { font-size: 18px; }623
+ .title-zh { font-size: 18px; }624
+ .intro-text { font-size: 13px; margin: 10px 0; }625
+ .feature-list { font-size: 13px; margin: 10px 0 10px 20px; }626
+627
+ /* ===== 搜尋列 ===== */628
+ .search-bar {629
+ flex-wrap: wrap;630
+ gap: 6px;631
+ padding: 0 4px;632
+ }633
+ .search-bar select {634
+ flex: 1;635
+ min-width: 80px;636
+ font-size: 13px;637
+ }638
+ .search-bar input[type="text"] {639
+ flex: 2;640
+ min-width: 120px;641
+ font-size: 13px;642
+ }643
+ .search-bar button {644
+ font-size: 13px;645
+ padding: 6px 14px;646
+ }647
+648
+ /* ===== 域名表格 ===== */649
+ .domain-table {650
+ font-size: 12px;651
+ }652
+ .domain-table th {653
+ padding: 6px 4px;654
+ font-size: 12px;655
+ }656
+ .domain-table td {657
+ padding: 5px 6px;658
+ font-size: 12px;659
+ }660
+ .domain-table td.cat {661
+ width: 60px;662
+ font-size: 12px;663
+ }664
+ .result-count {665
+ font-size: 12px;666
+ padding: 0 4px;667
+ }668
+669
+ /* ===== 聯絡頁 ===== */670
+ .contact-info {671
+ font-size: 13px;672
+ padding: 0 8px;673
+ }674
+675
+ /* ===== 後台管理 ===== */676
+ .admin-header {677
+ padding: 10px 12px;678
+ font-size: 16px;679
+ }680
+ .admin-header a {681
+ font-size: 12px;682
+ }683
+ .admin-container {684
+ margin: 0;685
+ padding: 12px;686
+ border-radius: 0;687
+ }688
+689
+ /* 統計卡片 → 2列 */690
+ .stats-bar {691
+ gap: 8px;692
+ }693
+ .stat-card {694
+ min-width: calc(50% - 4px);695
+ flex: 1 1 calc(50% - 4px);696
+ padding: 10px;697
+ }698
+ .stat-card .num { font-size: 22px; }699
+ .stat-card .label { font-size: 12px; }700
+701
+ /* 表單 → 標籤和輸入框上下排列 */702
+ .form-row {703
+ flex-direction: column;704
+ align-items: stretch;705
+ gap: 4px;706
+ }707
+ .form-row label {708
+ width: auto;709
+ text-align: left;710
+ font-size: 13px;711
+ }712
+ .form-row input,713
+ .form-row select {714
+ width: 100% !important;715
+ min-width: 0 !important;716
+ font-size: 14px;717
+ padding: 8px 10px;718
+ }719
+ .form-row input[type="text"] { min-width: 0 !important; }720
+ .form-row label[style] { margin-left: 0 !important; }721
+722
+ /* 工具列 */723
+ .toolbar {724
+ gap: 6px;725
+ }726
+ .toolbar input[type="text"],727
+ .toolbar select {728
+ width: 100% !important;729
+ font-size: 13px;730
+ }731
+ .toolbar .spacer { display: none; }732
+ .toolbar .btn { flex: 1; min-width: 70px; }733
+ .btn-sm { padding: 6px 8px; font-size: 12px; }734
+735
+ /* 管理表格 → 橫向滾動 */736
+ .admin-table {737
+ display: block;738
+ overflow-x: auto;739
+ white-space: nowrap;740
+ -webkit-overflow-scrolling: touch;741
+ }742
+ .admin-table th,743
+ .admin-table td {744
+ font-size: 12px;745
+ padding: 6px 8px;746
+ }747
+748
+ /* 登入頁 */749
+ .login-box {750
+ width: 90%;751
+ max-width: 320px;752
+ padding: 20px;753
+ }754
+ .login-box h2 { font-size: 18px; }755
+ .login-box input { font-size: 15px; }756
+ .login-box button { font-size: 15px; }757
+758
+ /* 批量匯入 */759
+ #batchInput {760
+ font-size: 12px !important;761
+ }762
+763
+ /* 設置區表單 */764
+ #settingsArea { padding: 12px !important; }765
+ #settingsArea .form-row input,766
+ #settingsArea .form-row select {767
+ width: 100% !important;768
+ }769
+770
+ /* Toast 提示 */771
+ .toast {772
+ top: 10px;773
+ right: 10px;774
+ left: 10px;775
+ font-size: 13px;776
+ text-align: center;777
+ }778
+}779
+780
+/* ---- 小手機 ---- */781
+@media (max-width: 480px) {782
+ .nav-btn {783
+ font-size: 14px;784
+ padding: 6px 10px;785
+ margin: 3px;786
+ border-width: 2px;787
+ }788
+ .domain-table {789
+ font-size: 11px;790
+ }791
+ .domain-table td { padding: 4px 3px; }792
+ .domain-table td.cat { width: 50px; }793
+ .stats-bar { gap: 6px; }794
+ .stat-card { padding: 8px; }795
+ .stat-card .num { font-size: 18px; }796
+ .stat-card .label { font-size: 11px; }797
+ .admin-header { font-size: 14px; padding: 8px 10px; }798
+ .admin-header a { font-size: 11px; }799
+}index.html+3-0
@@ -10,6 +10,8 @@1010
</head>1111
<body>12
+<button class="nav-toggle" onclick="var s=document.querySelector('.sidebar');s.classList.toggle('sidebar-open');this.querySelector('.hamburger').textContent=s.classList.contains('sidebar-open')?'✕':'☰';"><span class="hamburger">☰</span> 菜單</button>13
+1214
<table class="main-table">1315
<tr>1416
<!-- 側邊導航 -->@@ -74,6 +76,7 @@7476
</tr>7577
</table>78
+<script src="js/sync.js"></script>7679
<script src="js/data.js"></script>7780
<script src="js/main.js"></script>7881
</body>js/admin.js+50-31
@@ -27,13 +27,14 @@ function hideLogin() {2727
}2828
// 檢查會話(刷新頁面時自動登入)29
-function checkSession() {29
+async function checkSession() {3030
const token = sessionStorage.getItem('admin_token');3131
if (!token) return false;3232
isApiMode = sessionStorage.getItem('admin_api_mode') === '1';3333
isLoggedIn = true;3434
hideLogin();3535
document.getElementById('mainContent').style.display = '';36
+ await DataSync.init();3637
loadStats();3738
loadDomainList();3839
initCategoryOptions();@@ -66,7 +67,7 @@ async function doLogin() {6667
isApiMode = true;6768
sessionStorage.setItem('admin_token', data.token || '1');6869
sessionStorage.setItem('admin_api_mode', '1');69
- onLoginSuccess();70
+ await onLoginSuccess();7071
return;7172
}@@ -78,7 +79,7 @@ async function doLogin() {7879
isApiMode = false;7980
sessionStorage.setItem('admin_token', 'local_' + Date.now());8081
sessionStorage.setItem('admin_api_mode', '0');81
- onLoginSuccess();82
+ await onLoginSuccess();8283
return;8384
}8485
showToast('密碼錯誤!', 'error');@@ -89,11 +90,12 @@ async function doLogin() {8990
}9091
}91
-function onLoginSuccess() {92
+async function onLoginSuccess() {9293
isLoggedIn = true;9394
hideLogin();9495
document.getElementById('mainContent').style.display = '';9596
showToast('登入成功!', 'success');97
+ await DataSync.init();9698
loadStats();9799
loadDomainList();98100
initCategoryOptions();@@ -120,13 +122,22 @@ function updatePasswordSection() {120122
if (!pwdArea) return;121123
if (isApiMode) {122
- pwdArea.innerHTML =123
- '<div style="padding:15px;background:#FFF;border:1px solid #FF99FF;border-radius:5px;">' +124
- '<p style="color:#990000;font-weight:bold;margin-bottom:8px;">密碼由 Vercel 環境變量管理</p>' +125
- '<p style="font-size:13px;color:#666;">當前為 Vercel 部署模式,管理密碼由環境變量 ' +126
- '<code style="background:#FFF0FF;padding:2px 6px;border-radius:3px;">ADMIN_PASSWORD</code> 設定。<br>' +127
- '如需修改密碼,請到 Vercel 項目設置 → Environment Variables 中修改。</p>' +128
- '</div>';124
+ // 隱藏密碼表單(不銷毀,避免影響事件綁定)125
+ const form = pwdArea.querySelector('#pwdForm');126
+ if (form) form.style.display = 'none';127
+128
+ // 插入提示信息(只插一次)129
+ if (!pwdArea.querySelector('.api-mode-msg')) {130
+ const msg = document.createElement('div');131
+ msg.className = 'api-mode-msg';132
+ msg.style.cssText = 'padding:15px;background:#FFF;border:1px solid #FF99FF;border-radius:5px;';133
+ msg.innerHTML =134
+ '<p style="color:#990000;font-weight:bold;margin-bottom:8px;">密碼由 Vercel 環境變量管理</p>' +135
+ '<p style="font-size:13px;color:#666;">當前為 Vercel 部署模式,管理密碼由環境變量 ' +136
+ '<code style="background:#FFF0FF;padding:2px 6px;border-radius:3px;">ADMIN_PASSWORD</code> 設定。<br>' +137
+ '如需修改密碼,請到 Vercel 項目設置 → Environment Variables 中修改。</p>';138
+ pwdArea.insertBefore(msg, form || null);139
+ }129140
}130141
}@@ -489,54 +500,62 @@ function resetSettings() {489500
}490501
// 頁面初始化491
-document.addEventListener('DOMContentLoaded', function() {502
+document.addEventListener('DOMContentLoaded', async function() {503
+ // 安全綁定事件(元素不存在時跳過,不會中斷後續綁定)504
+ function bind(id, event, handler) {505
+ const el = document.getElementById(id);506
+ if (el) el.addEventListener(event, handler);507
+ else console.warn('Element not found:', id);508
+ }509
+492510
// 先檢查會話,已登入則跳過登入頁493
- if (!checkSession()) {511
+ const hadSession = await checkSession();512
+ if (!hadSession) {494513
showLogin();495514
}496
- document.getElementById('loginBtn').addEventListener('click', doLogin);497
- document.getElementById('loginPwd').addEventListener('keypress', function(e) {515
+ bind('loginBtn', 'click', doLogin);516
+ bind('loginPwd', 'keypress', function(e) {498517
if (e.key === 'Enter') doLogin();499518
});500
- document.getElementById('domainForm').addEventListener('submit', submitDomain);501
- document.getElementById('cancelBtn').addEventListener('click', cancelEdit);519
+ bind('domainForm', 'submit', submitDomain);520
+ bind('cancelBtn', 'click', cancelEdit);502
- document.getElementById('adminSearch').addEventListener('input', loadDomainList);503
- document.getElementById('adminStatusFilter').addEventListener('change', loadDomainList);521
+ bind('adminSearch', 'input', loadDomainList);522
+ bind('adminStatusFilter', 'change', loadDomainList);504
- document.getElementById('batchBtn').addEventListener('click', function() {523
+ bind('batchBtn', 'click', function() {505524
const area = document.getElementById('batchArea');506525
area.style.display = area.style.display === 'none' ? '' : 'none';507526
});508
- document.getElementById('batchSubmit').addEventListener('click', batchImport);527
+ bind('batchSubmit', 'click', batchImport);509
- document.getElementById('pwdBtn').addEventListener('click', function() {528
+ bind('pwdBtn', 'click', function() {510529
const area = document.getElementById('pwdArea');511530
area.style.display = area.style.display === 'none' ? '' : 'none';512531
});513
- document.getElementById('pwdForm').addEventListener('submit', function(e) {532
+ bind('pwdForm', 'submit', function(e) {514533
e.preventDefault();515534
changePassword();516535
});517
- document.getElementById('exportBtn').addEventListener('click', exportData);518
- document.getElementById('importFile').addEventListener('change', importData);519
- document.getElementById('clearBtn').addEventListener('click', clearAllData);520
- document.getElementById('logoutBtn').addEventListener('click', doLogout);521
- document.getElementById('viewSiteBtn').addEventListener('click', function() {536
+ bind('exportBtn', 'click', exportData);537
+ bind('importFile', 'change', importData);538
+ bind('clearBtn', 'click', clearAllData);539
+ bind('logoutBtn', 'click', doLogout);540
+ bind('viewSiteBtn', 'click', function() {522541
window.open('index.html', '_blank');523542
});524543
// 網站設置525
- document.getElementById('settingsBtn').addEventListener('click', function() {544
+ bind('settingsBtn', 'click', function() {526545
const area = document.getElementById('settingsArea');527546
area.style.display = area.style.display === 'none' ? '' : 'none';528547
if (area.style.display !== 'none') {529548
loadSettings();530549
}531550
});532
- document.getElementById('settingsForm').addEventListener('submit', saveSettings);533
- document.getElementById('resetSettingsBtn').addEventListener('click', resetSettings);551
+ bind('settingsForm', 'submit', saveSettings);552
+ bind('resetSettingsBtn', 'click', resetSettings);534553
});js/data.js+7-3
@@ -25,9 +25,10 @@ const DomainDB = {2525
}2626
},27
- // 保存所有域名27
+ // 保存所有域名(同步到 localStorage + KV)2828
save(domains) {2929
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(domains));30
+ if (typeof DataSync !== 'undefined') DataSync.save('domains', domains);3031
},3132
// 新增域名@@ -151,6 +152,7 @@ const DomainDB = {151152
// 清空所有數據152153
clearAll() {153154
localStorage.removeItem(this.STORAGE_KEY);155
+ if (typeof DataSync !== 'undefined') DataSync.save('domains', []);154156
},155157
// 密碼管理@@ -241,6 +243,7 @@ const SiteConfig = {241243
save(config) {242244
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(config));245
+ if (typeof DataSync !== 'undefined') DataSync.save('config', config);243246
},244247
update(partial) {@@ -251,7 +254,8 @@ const SiteConfig = {251254
},252255
reset() {253
- localStorage.removeItem(this.STORAGE_KEY);254
- return this.getDefaults();256
+ const defaults = this.getDefaults();257
+ this.save(defaults);258
+ return defaults;255259
}256260
};js/main.js+4-1
@@ -149,7 +149,10 @@ function initContactInfo() {149149
}150150
// 頁面初始化151
-document.addEventListener('DOMContentLoaded', function() {151
+document.addEventListener('DOMContentLoaded', async function() {152
+ // 先從 KV 同步最新數據到 localStorage153
+ await DataSync.init();154
+152155
renderHeader();153156
initCategoryFilter();154157
initContactInfo();package.json+5-2
@@ -1,6 +1,9 @@11
{22
"name": "domain-market",3
- "version": "1.0.0",3
+ "version": "2.0.0",44
"private": true,5
- "description": "域名展示米表 - 66.hk 風格復刻版"5
+ "description": "域名展示米表 - 66.hk 風格復刻版(Upstash Redis 版)",6
+ "dependencies": {7
+ "@upstash/redis": "^1.34.0"8
+ }69
}