feat: currentTrends 添加到导入导出配置

This commit is contained in:
hzm
2026-02-23 20:54:25 +08:00
parent 9d20960580
commit 8c7465b9c8
2 changed files with 31 additions and 17 deletions

View File

@@ -45,8 +45,8 @@
cp env.example .env.local cp env.example .env.local
``` ```
按照 `env.example` 填入以下值: 按照 `env.example` 填入以下值:
- `NEXT_PUBLIC_SUPABASE_URL`Supabase 项目 URL - `NEXT_PUBLIC_Supabase_URL`Supabase 项目 URL
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`Supabase 匿名公钥 - `NEXT_PUBLIC_Supabase_ANON_KEY`Supabase 匿名公钥
- `NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY`Web3Forms Access Key - `NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY`Web3Forms Access Key
- `NEXT_PUBLIC_GA_ID`Google Analytics Measurement ID形如 `G-xxxx` - `NEXT_PUBLIC_GA_ID`Google Analytics Measurement ID形如 `G-xxxx`
@@ -58,35 +58,35 @@
``` ```
访问 [http://localhost:3000](http://localhost:3000) 查看效果。 访问 [http://localhost:3000](http://localhost:3000) 查看效果。
### supabase 配置说明 ### Supabase 配置说明
1. NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_ANON_KEY 获取 1. NEXT_PUBLIC_Supabase_URL 和 NEXT_PUBLIC_Supabase_ANON_KEY 获取
NEXT_PUBLIC_SUPABASE_URLsupabase控制台 → Project Settings → General → Project ID NEXT_PUBLIC_Supabase_URLSupabase控制台 → Project Settings → General → Project ID
NEXT_PUBLIC_SUPABASE_ANON_KEY supabase控制台 → Project Settings → API Keys → Publishable key NEXT_PUBLIC_Supabase_ANON_KEY Supabase控制台 → Project Settings → API Keys → Publishable key
2. 邮件数量修改 2. 邮件数量修改
supabase 免费项目自带每小时2条邮件服务。如果觉得额度不够可以改成自己的邮箱SMTP。修改路径在 supabase控制台 → Authentication → Email → SMTP Settings。 Supabase 免费项目自带每小时2条邮件服务。如果觉得额度不够可以改成自己的邮箱SMTP。修改路径在 Supabase控制台 → Authentication → Email → SMTP Settings。
之后可在 Rate Limits ,自由修改每小时邮件数量。 之后可在 Rate Limits ,自由修改每小时邮件数量。
3. 修改接收到的邮件为验证码 3. 修改接收到的邮件为验证码
supabase控制台 → Authentication → Email → Confirm sign up选择 `{{.token}}`。 Supabase控制台 → Authentication → Email → Confirm sign up选择 `{{.token}}`。
4. 修改验证码位数 4. 修改验证码位数
官方验证码位数默认为8位可自行修改。常见一般为6位。 官方验证码位数默认为8位可自行修改。常见一般为6位。
supabase控制台 → Authentication → Sign In / Providers → Auth Providers → email → Minimum password length。 Supabase控制台 → Authentication → Sign In / Providers → Auth Providers → email → Minimum password length 和 Email OTP Length 都改为6位
5. 目前项目用到的 sql 语句,查看项目 supabase.sql 文件。 5. 目前项目用到的 sql 语句,查看项目 supabase.sql 文件。
更多 supabase 相关内容查阅官方文档。 更多 Supabase 相关内容查阅官方文档。
### 构建与部署 ### 构建与部署
本项目已配置 GitHub Actions。每次推送到 `main` 分支时,会自动执行构建并部署到 GitHub Pages。 本项目已配置 GitHub Actions。每次推送到 `main` 分支时,会自动执行构建并部署到 GitHub Pages。
如需使用 GitHub Actions 部署,请在 GitHub 项目 Settings → Secrets and variables → Actions 中创建对应的 Repository secrets字段名称与 `.env.local` 保持一致)。 如需使用 GitHub Actions 部署,请在 GitHub 项目 Settings → Secrets and variables → Actions 中创建对应的 Repository secrets字段名称与 `.env.local` 保持一致)。
包括:`NEXT_PUBLIC_SUPABASE_URL`、`NEXT_PUBLIC_SUPABASE_ANON_KEY`、`NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY`、`NEXT_PUBLIC_GA_ID`。 包括:`NEXT_PUBLIC_Supabase_URL`、`NEXT_PUBLIC_Supabase_ANON_KEY`、`NEXT_PUBLIC_WEB3FORMS_ACCESS_KEY`、`NEXT_PUBLIC_GA_ID`。
若要手动构建: 若要手动构建:
```bash ```bash
@@ -102,7 +102,7 @@ npm run build
```bash ```bash
docker build -t real-time-fund . docker build -t real-time-fund .
# 或通过 --build-arg 传入,例如: # 或通过 --build-arg 传入,例如:
# docker build -t real-time-fund --build-arg NEXT_PUBLIC_SUPABASE_URL=xxx --build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx --build-arg NEXT_PUBLIC_GA_ID=G-xxxx . # docker build -t real-time-fund --build-arg NEXT_PUBLIC_Supabase_URL=xxx --build-arg NEXT_PUBLIC_Supabase_ANON_KEY=xxx --build-arg NEXT_PUBLIC_GA_ID=G-xxxx .
``` ```
2. 启动容器 2. 启动容器

View File

@@ -1443,7 +1443,8 @@ export default function HomePage() {
setLoginSuccess(''); setLoginSuccess('');
setLoginError(''); setLoginError('');
} }
fetchCloudConfig(session.user.id); // 仅在明确的登录动作SIGNED_IN时检查冲突INITIAL_SESSION刷新页面等不检查直接以云端为准
fetchCloudConfig(session.user.id, event === 'SIGNED_IN');
}; };
supabase.auth.getSession().then(async ({ data, error }) => { supabase.auth.getSession().then(async ({ data, error }) => {
@@ -2206,7 +2207,7 @@ export default function HomePage() {
} }
}; };
const fetchCloudConfig = async (userId) => { const fetchCloudConfig = async (userId, checkConflict = false) => {
if (!userId) return; if (!userId) return;
try { try {
const { data, error } = await supabase const { data, error } = await supabase
@@ -2229,9 +2230,14 @@ export default function HomePage() {
const cloudComparable = getComparablePayload(data.data); const cloudComparable = getComparablePayload(data.data);
if (localComparable !== cloudComparable) { if (localComparable !== cloudComparable) {
// 如果数据不一致,无论时间戳如何,都提示用户 // 如果数据不一致
// 用户可以选择使用本地数据覆盖云端,或者使用云端数据覆盖本地 if (checkConflict) {
setCloudConfigModal({ open: true, userId, type: 'conflict', cloudData: data.data }); // 只有明确要求检查冲突时才提示(例如刚登录时)
setCloudConfigModal({ open: true, userId, type: 'conflict', cloudData: data.data });
return;
}
// 否则直接覆盖本地(例如已登录状态下的刷新)
await applyCloudConfig(data.data, data.updated_at);
return; return;
} }
@@ -2333,6 +2339,7 @@ export default function HomePage() {
favorites: JSON.parse(localStorage.getItem('favorites') || '[]'), favorites: JSON.parse(localStorage.getItem('favorites') || '[]'),
groups: JSON.parse(localStorage.getItem('groups') || '[]'), groups: JSON.parse(localStorage.getItem('groups') || '[]'),
collapsedCodes: JSON.parse(localStorage.getItem('collapsedCodes') || '[]'), collapsedCodes: JSON.parse(localStorage.getItem('collapsedCodes') || '[]'),
collapsedTrends: JSON.parse(localStorage.getItem('collapsedTrends') || '[]'),
refreshMs: parseInt(localStorage.getItem('refreshMs') || '30000', 10), refreshMs: parseInt(localStorage.getItem('refreshMs') || '30000', 10),
viewMode: localStorage.getItem('viewMode') === 'list' ? 'list' : 'card', viewMode: localStorage.getItem('viewMode') === 'list' ? 'list' : 'card',
holdings: JSON.parse(localStorage.getItem('holdings') || '{}'), holdings: JSON.parse(localStorage.getItem('holdings') || '{}'),
@@ -2389,6 +2396,7 @@ export default function HomePage() {
const currentFavorites = JSON.parse(localStorage.getItem('favorites') || '[]'); const currentFavorites = JSON.parse(localStorage.getItem('favorites') || '[]');
const currentGroups = JSON.parse(localStorage.getItem('groups') || '[]'); const currentGroups = JSON.parse(localStorage.getItem('groups') || '[]');
const currentCollapsed = JSON.parse(localStorage.getItem('collapsedCodes') || '[]'); const currentCollapsed = JSON.parse(localStorage.getItem('collapsedCodes') || '[]');
const currentTrends = JSON.parse(localStorage.getItem('collapsedTrends') || '[]');
const currentPendingTrades = JSON.parse(localStorage.getItem('pendingTrades') || '[]'); const currentPendingTrades = JSON.parse(localStorage.getItem('pendingTrades') || '[]');
let mergedFunds = currentFunds; let mergedFunds = currentFunds;
@@ -2434,6 +2442,12 @@ export default function HomePage() {
storageHelper.setItem('collapsedCodes', JSON.stringify(mergedCollapsed)); storageHelper.setItem('collapsedCodes', JSON.stringify(mergedCollapsed));
} }
if (Array.isArray(data.collapsedTrends)) {
const mergedTrends = Array.from(new Set([...currentTrends, ...data.collapsedTrends]));
setCollapsedTrends(new Set(mergedTrends));
storageHelper.setItem('collapsedTrends', JSON.stringify(mergedTrends));
}
if (typeof data.refreshMs === 'number' && data.refreshMs >= 5000) { if (typeof data.refreshMs === 'number' && data.refreshMs >= 5000) {
setRefreshMs(data.refreshMs); setRefreshMs(data.refreshMs);
setTempSeconds(Math.round(data.refreshMs / 1000)); setTempSeconds(Math.round(data.refreshMs / 1000));