diff --git a/.dockerignore b/.dockerignore
index 58ecfac..62aacce 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,12 +5,12 @@
**/.git
.DS_Store
-# Do not send local node_modules / build outputs (Docker runs npm ci + build)
+# Do not send unused frontend source build artifacts
QuantDinger-Vue-src/node_modules
QuantDinger-Vue-src/dist
-# Pre-built copy used for local nginx tests only — image builds from source
-frontend/dist
+# The open-source repo ships prebuilt frontend assets in `frontend/dist`,
+# and the frontend image copies them directly, so keep this directory included.
# Backend & DB not needed for frontend stage
backend_api_python
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..a7f6968
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,21 @@
+#
+# Project-root Docker/Compose overrides
+# Copy to `.env` only when you need custom ports or want to switch image source.
+#
+
+# Optional custom ports
+# FRONTEND_PORT=8888
+# BACKEND_PORT=127.0.0.1:5000
+# DB_PORT=127.0.0.1:5432
+
+# Global image source switch
+# Leave empty for official Docker Hub:
+# IMAGE_PREFIX=
+#
+# Common alternatives (pick one, keep trailing slash):
+# IMAGE_PREFIX=docker.m.daocloud.io/library/
+# IMAGE_PREFIX=docker.xuanyuan.me/library/
+#
+# Important:
+# - This file lives in the project root and is used by docker-compose build/pull.
+# - `backend_api_python/.env` is runtime config for the backend container only.
diff --git a/README.md b/README.md
index 9eeb7b9..e8c2077 100644
--- a/README.md
+++ b/README.md
@@ -70,25 +70,40 @@
git clone https://github.com/brokermr810/QuantDinger.git
cd QuantDinger
-# 2. Configure (edit admin password & AI API key)
+# 2. Copy backend config
cp backend_api_python/env.example backend_api_python/.env
-# 3. IMPORTANT: Generate and set a secure SECRET_KEY
-# Linux/Mac:
-python3 -c "import secrets; print(secrets.token_hex(32))" | xargs -I {} sed -i 's|SECRET_KEY=.*|SECRET_KEY={}|' backend_api_python/.env
-
-# Or manually edit backend_api_python/.env and replace SECRET_KEY value
-# Windows PowerShell:
-# $key = python -c "import secrets; print(secrets.token_hex(32))"
-# (Get-Content backend_api_python\.env) -replace 'SECRET_KEY=.*', "SECRET_KEY=$key" | Set-Content backend_api_python\.env
+# 3. Generate and write a secure SECRET_KEY
+./scripts/generate-secret-key.sh
# 4. Launch!
docker-compose up -d --build
```
+> **Linux/macOS**:
+> - Copy backend config:
+> `cp backend_api_python/env.example backend_api_python/.env`
+> - Need more knobs? Scroll to the lower "Advanced / rarely changed" section inside:
+> `backend_api_python/env.example`
+> - Optional: if Docker Hub is slow/unreachable in your network, copy root Docker config:
+> `cp .env.example .env`
+> - Generate and write `SECRET_KEY`:
+> `./scripts/generate-secret-key.sh`
+> - Launch:
+> `docker-compose up -d --build`
+
> **Windows PowerShell**:
-> - Copy: `Copy-Item backend_api_python\env.example -Destination backend_api_python\.env`
-> - Generate SECRET_KEY: `python -c "import secrets; print(secrets.token_hex(32))"` then edit `.env` manually
+> - Copy backend config:
+> `Copy-Item backend_api_python\env.example -Destination backend_api_python\.env`
+> - Need more knobs? Scroll to the lower "Advanced / rarely changed" section inside:
+> `backend_api_python\env.example`
+> - Optional: if Docker Hub is slow/unreachable in your network, copy root Docker config:
+> `Copy-Item .env.example -Destination .env`
+> - Generate and write `SECRET_KEY`:
+> `$key = py -c "import secrets; print(secrets.token_hex(32))"`
+> `(Get-Content backend_api_python\.env) -replace '^SECRET_KEY=.*$', "SECRET_KEY=$key" | Set-Content backend_api_python\.env -Encoding UTF8`
+> - Launch:
+> `docker-compose up -d --build`
> **⚠️ Security Note**: The container will **NOT start** if `SECRET_KEY` is using the default value. This prevents insecure deployments.
@@ -161,6 +176,42 @@ FRONTEND_PORT=3000 # Default: 8888
BACKEND_PORT=127.0.0.1:5001 # Default: 5000
```
+**Alternative image sources** — Default uses Docker Hub. If pulling base images fails in your region/network, copy `.env.example` to `.env` and change one line:
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+Other common choices:
+```ini
+IMAGE_PREFIX=
+IMAGE_PREFIX=docker.xuanyuan.me/library/
+```
+
+**Docker troubleshooting**
+```text
+1. Image source switching is controlled by the project-root `.env`, not `backend_api_python/.env`.
+2. The open-source repo ships prebuilt frontend files in `frontend/dist`, so frontend deployment does not require Node.js.
+3. On Windows, if backend logs show:
+ exec /usr/local/bin/docker-entrypoint.sh: no such file or directory
+ it is usually caused by shell/line-ending compatibility in the image layer. Rebuild the backend image with:
+ docker-compose build --no-cache backend
+4. If frontend logs show:
+ host not found in upstream "backend"
+ it usually means the backend container failed first. Fix backend, then restart frontend:
+ docker-compose restart frontend
+5. If frontend build fails with:
+ COPY frontend/dist ... not found
+ check `.dockerignore` and make sure `frontend/dist` is NOT excluded from the Docker build context.
+6. If saving settings from the admin panel fails with:
+ Read-only file system: '/app/.env'
+ make sure `backend_api_python/.env` is mounted read-write in `docker-compose.yml`.
+7. For Docker deployments, a local proxy must use `host.docker.internal`, not `127.0.0.1`.
+ Example:
+ PROXY_URL=socks5h://host.docker.internal:10808
+8. If exchange logs say a symbol is "not found" after proxy/network fixes, it may be a market-symbol mapping issue
+ on that exchange (for example, renamed tokens), not a general network failure.
+```
+
---
@@ -370,6 +421,14 @@ flowchart TB
| **Forex** | MetaTrader 5 (MT5), OANDA | ✅ Via MT5 |
| **Futures** | Exchange APIs | ⚡ Data + Notify |
+### Exchange Rebate Links
+
+If you are opening a new exchange account, use a rebate link from the start. Many traders actively look for fee-rebate links anyway, and these links give you a straightforward 20% trading fee discount at no extra cost.
+
+- **OKX**: [Open account with 20% fee rebate](https://www.promooboost.com/join/QUANTDINGER)
+- **Bitget**: [Open account with 20% fee rebate](https://partner.bitget.com/bg/dinger)
+- **Bitget (backup link)**: [Alternate 20% fee rebate signup link](https://partner.hdmune.cn/bg/7r4xz8kd)
+
---
## 🏗️ Architecture & Configuration
@@ -427,7 +486,8 @@ QuantDinger/
⚙️ Configuration Reference (.env)
-Use `backend_api_python/env.example` as template:
+Use `backend_api_python/env.example` as the simplified template.
+The upper part is for first-time deployment, and the lower "Advanced / rarely changed" section contains optional tuning.
| Category | Key Variables |
|----------|-----------|
@@ -438,7 +498,7 @@ Use `backend_api_python/env.example` as template:
| **Security** | `TURNSTILE_SITE_KEY`, `ENABLE_REGISTRATION` |
| **Membership** | `MEMBERSHIP_MONTHLY_PRICE_USD`, `MEMBERSHIP_MONTHLY_CREDITS` |
| **USDT Payment** | `USDT_PAY_ENABLED`, `USDT_TRC20_XPUB`, `TRONGRID_API_KEY` |
-| **Proxy** | `PROXY_PORT` or `PROXY_URL` |
+| **Proxy** | `PROXY_URL` |
| **Workers** | `ENABLE_PENDING_ORDER_WORKER`, `ENABLE_PORTFOLIO_MONITOR` |
@@ -470,6 +530,7 @@ All detailed guides are in the [`docs/`](docs/) folder:
|----------|-------------|
| [Changelog](docs/CHANGELOG.md) | Version history & migration notes |
| [Multi-User Setup](docs/multi-user-setup.md) | PostgreSQL multi-user deployment |
+| [Cloud Deployment](docs/CLOUD_DEPLOYMENT_EN.md) | Cloud server deployment with domain, HTTPS, and reverse proxy |
### Strategy Development
diff --git a/backend_api_python/Dockerfile b/backend_api_python/Dockerfile
index ce5b0cf..814d473 100644
--- a/backend_api_python/Dockerfile
+++ b/backend_api_python/Dockerfile
@@ -1,5 +1,7 @@
# QuantDinger Backend API Dockerfile
-FROM python:3.12-slim-bookworm
+# BASE_IMAGE can be overridden from docker-compose/.env.
+ARG BASE_IMAGE=python:3.12-slim-bookworm
+FROM ${BASE_IMAGE}
# Set working directory
WORKDIR /app
@@ -31,7 +33,8 @@ COPY . .
# Copy and set up entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/
-RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
+ && chmod +x /usr/local/bin/docker-entrypoint.sh
# Create log and data directories
RUN mkdir -p logs data/memory
diff --git a/backend_api_python/README.md b/backend_api_python/README.md
index 73eaac1..dc10fd4 100644
--- a/backend_api_python/README.md
+++ b/backend_api_python/README.md
@@ -208,7 +208,7 @@ gunicorn -c gunicorn_config.py "run:app"
## Troubleshooting
- **Database connection failed**: Check `DATABASE_URL` format and PostgreSQL service status
-- **Outbound requests fail**: Configure `PROXY_PORT` or `PROXY_URL` in `.env`
+- **Outbound requests fail**: Configure `PROXY_URL` in `.env`
- **Disable auto-restore**: Set `DISABLE_RESTORE_RUNNING_STRATEGIES=true`
- **Disable pending-order worker**: Set `ENABLE_PENDING_ORDER_WORKER=false`
diff --git a/backend_api_python/app/config/api_keys.py b/backend_api_python/app/config/api_keys.py
index f4eebfa..865e181 100644
--- a/backend_api_python/app/config/api_keys.py
+++ b/backend_api_python/app/config/api_keys.py
@@ -81,18 +81,6 @@ class MetaAPIKeys(type):
return [k.strip() for k in val.split(',') if k.strip()]
return []
- @property
- def BOCHA_API_KEYS(cls):
- """Bocha Search API keys (comma-separated for rotation)"""
- env_val = os.getenv('BOCHA_API_KEYS', '').strip()
- if env_val:
- return [k.strip() for k in env_val.split(',') if k.strip()]
- from app.utils.config_loader import load_addon_config
- val = load_addon_config().get('bocha', {}).get('api_keys', '')
- if val:
- return [k.strip() for k in val.split(',') if k.strip()]
- return []
-
@property
def SERPAPI_KEYS(cls):
"""SerpAPI keys (comma-separated for rotation)"""
diff --git a/backend_api_python/app/config/data_sources.py b/backend_api_python/app/config/data_sources.py
index 542f348..ff114f9 100644
--- a/backend_api_python/app/config/data_sources.py
+++ b/backend_api_python/app/config/data_sources.py
@@ -130,30 +130,13 @@ class MetaCCXTConfig(type):
@property
def PROXY(cls):
- from app.utils.config_loader import load_addon_config
- val = load_addon_config().get('ccxt', {}).get('proxy')
- if val:
- return val
-
- # 1) Direct CCXT proxy env
- ccxt_proxy = (os.getenv('CCXT_PROXY') or '').strip()
- if ccxt_proxy:
- return ccxt_proxy
-
- # 2) Local proxy helpers from backend_api_python/.env
+ # 1) Local proxy helpers from backend_api_python/.env
# PROXY_URL has the highest priority if provided.
proxy_url = (os.getenv('PROXY_URL') or '').strip()
if proxy_url:
return proxy_url
- # Build from parts: PROXY_SCHEME/PROXY_HOST/PROXY_PORT
- proxy_port = (os.getenv('PROXY_PORT') or '').strip()
- if proxy_port:
- proxy_scheme = (os.getenv('PROXY_SCHEME') or 'socks5h').strip()
- proxy_host = (os.getenv('PROXY_HOST') or '127.0.0.1').strip()
- return f"{proxy_scheme}://{proxy_host}:{proxy_port}"
-
- # 3) Standard proxy envs (fallback)
+ # 2) Standard proxy envs (fallback)
for key in ['HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY']:
v = (os.getenv(key) or '').strip()
if v:
diff --git a/backend_api_python/app/config/settings.py b/backend_api_python/app/config/settings.py
index 63bf82a..aeb0242 100644
--- a/backend_api_python/app/config/settings.py
+++ b/backend_api_python/app/config/settings.py
@@ -65,12 +65,6 @@ class MetaConfig(type):
# ==================== 安全配置 ====================
- @property
- def CORS_ORIGINS(cls):
- from app.utils.config_loader import load_addon_config
- val = load_addon_config().get('app', {}).get('cors_origins')
- return val if val else os.getenv('CORS_ORIGINS', '*')
-
@property
def RATE_LIMIT(cls):
from app.utils.config_loader import load_addon_config
@@ -95,15 +89,6 @@ class MetaConfig(type):
return bool(val)
return os.getenv('ENABLE_REQUEST_LOG', 'True').lower() == 'true'
- @property
- def ENABLE_AI_ANALYSIS(cls):
- from app.utils.config_loader import load_addon_config
- val = load_addon_config().get('app', {}).get('enable_ai_analysis')
- if val is not None:
- return bool(val)
- return os.getenv('ENABLE_AI_ANALYSIS', 'True').lower() == 'true'
-
-
class Config(metaclass=MetaConfig):
"""应用配置类"""
diff --git a/backend_api_python/app/routes/settings.py b/backend_api_python/app/routes/settings.py
index cb1e211..fde6b31 100644
--- a/backend_api_python/app/routes/settings.py
+++ b/backend_api_python/app/routes/settings.py
@@ -65,7 +65,7 @@ CONFIG_SCHEMA = {
# ==================== 2. AI/LLM 配置 ====================
'ai': {
- 'title': 'AI / LLM Configuration',
+ 'title': 'AI / LLM & Search',
'icon': 'robot',
'order': 2,
'items': [
@@ -212,14 +212,6 @@ CONFIG_SCHEMA = {
'default': '0.7',
'description': 'Model creativity (0-1). Lower = more deterministic'
},
- {
- 'key': 'AI_MODELS_JSON',
- 'label': 'Custom Models (JSON)',
- 'type': 'text',
- 'default': '{}',
- 'required': False,
- 'description': 'Custom model list in JSON format for model selector'
- },
{
'key': 'AI_ANALYSIS_CONSENSUS_TIMEFRAMES',
'label': 'Consensus Timeframes',
@@ -228,6 +220,66 @@ CONFIG_SCHEMA = {
'required': False,
'description': 'Multi-timeframe consensus for fast AI analysis. Comma-separated, e.g. "1D,4H"'
},
+ {
+ 'key': 'SEARCH_PROVIDER',
+ 'label': 'Search Provider',
+ 'type': 'select',
+ 'options': ['tavily', 'google', 'bing', 'none'],
+ 'default': 'google',
+ 'description': 'News / web search provider used by AI analysis. Configure both LLM and search to get full AI analysis results'
+ },
+ {
+ 'key': 'SEARCH_MAX_RESULTS',
+ 'label': 'Search Max Results',
+ 'type': 'number',
+ 'default': '10',
+ 'description': 'Maximum number of search/news results returned per AI analysis request'
+ },
+ {
+ 'key': 'TAVILY_API_KEYS',
+ 'label': 'Tavily API Keys',
+ 'type': 'password',
+ 'required': False,
+ 'link': 'https://tavily.com/',
+ 'link_text': 'settings.link.getApiKey',
+ 'description': 'Tavily search API keys (comma-separated). Recommended lightweight search source for AI analysis'
+ },
+ {
+ 'key': 'SEARCH_GOOGLE_API_KEY',
+ 'label': 'Google Search API Key',
+ 'type': 'password',
+ 'required': False,
+ 'link': 'https://console.cloud.google.com/apis/credentials',
+ 'link_text': 'settings.link.getApiKey',
+ 'description': 'Google Custom Search JSON API key'
+ },
+ {
+ 'key': 'SEARCH_GOOGLE_CX',
+ 'label': 'Google Search Engine ID (CX)',
+ 'type': 'text',
+ 'required': False,
+ 'link': 'https://programmablesearchengine.google.com/',
+ 'link_text': 'settings.link.getApiKey',
+ 'description': 'Google Programmable Search Engine ID'
+ },
+ {
+ 'key': 'SEARCH_BING_API_KEY',
+ 'label': 'Bing Search API Key',
+ 'type': 'password',
+ 'required': False,
+ 'link': 'https://portal.azure.com/',
+ 'link_text': 'settings.link.getApiKey',
+ 'description': 'Microsoft Bing Web Search API key'
+ },
+ {
+ 'key': 'SERPAPI_KEYS',
+ 'label': 'SerpAPI Keys',
+ 'type': 'password',
+ 'required': False,
+ 'link': 'https://serpapi.com/',
+ 'link_text': 'settings.link.getApiKey',
+ 'description': 'SerpAPI keys (comma-separated)'
+ },
]
},
@@ -270,13 +322,6 @@ CONFIG_SCHEMA = {
'link_text': 'settings.link.supportedExchanges',
'description': 'Default exchange for crypto data (binance, coinbase, okx, etc.)'
},
- {
- 'key': 'CCXT_PROXY',
- 'label': 'Crypto Data Proxy',
- 'type': 'text',
- 'required': False,
- 'description': 'Proxy URL for crypto data requests (e.g. socks5h://127.0.0.1:1080)'
- },
{
'key': 'FINNHUB_API_KEY',
'label': 'Finnhub API Key',
@@ -422,42 +467,7 @@ CONFIG_SCHEMA = {
'label': 'Proxy URL',
'type': 'text',
'required': False,
- 'description': 'Global proxy URL (e.g. socks5h://127.0.0.1:1080 or http://proxy:8080)'
- },
- ]
- },
-
- # ==================== 9. 搜索配置 ====================
- 'search': {
- 'title': 'Web Search',
- 'icon': 'search',
- 'order': 9,
- 'items': [
- {
- 'key': 'SEARCH_PROVIDER',
- 'label': 'Search Provider',
- 'type': 'select',
- 'options': ['bocha', 'tavily', 'google', 'bing', 'none'],
- 'default': 'bocha',
- 'description': 'Web search provider for AI research features'
- },
- {
- 'key': 'TAVILY_API_KEYS',
- 'label': 'Tavily API Keys',
- 'type': 'password',
- 'required': False,
- 'link': 'https://tavily.com/',
- 'link_text': 'settings.link.getApiKey',
- 'description': 'Tavily Search API keys (comma-separated). Free 1000 req/month'
- },
- {
- 'key': 'BOCHA_API_KEYS',
- 'label': 'Bocha API Keys',
- 'type': 'password',
- 'required': False,
- 'link': 'https://bochaai.com/',
- 'link_text': 'settings.link.getApiKey',
- 'description': 'Bocha Search API keys (comma-separated)'
+ 'description': 'Global outbound proxy URL. Used by requests and by crypto data requests when a proxy is needed.'
},
]
},
@@ -546,13 +556,6 @@ CONFIG_SCHEMA = {
'default': 'False',
'description': 'Enable billing system. Users need credits to use certain features'
},
- {
- 'key': 'BILLING_VIP_BYPASS',
- 'label': 'VIP Bypass (Legacy)',
- 'type': 'boolean',
- 'default': 'False',
- 'description': 'Legacy switch. If enabled, VIP users bypass ALL feature credit costs. Recommended OFF: VIP should only unlock VIP-free indicators.'
- },
# ===== Membership Plans (3 tiers) =====
{
@@ -684,13 +687,6 @@ CONFIG_SCHEMA = {
'default': '8',
'description': 'Credits per portfolio AI monitoring run'
},
- {
- 'key': 'RECHARGE_TELEGRAM_URL',
- 'label': 'Recharge Telegram URL',
- 'type': 'text',
- 'default': 'https://t.me/your_support_bot',
- 'description': 'Telegram URL for recharge inquiries'
- },
{
'key': 'CREDITS_REGISTER_BONUS',
'label': 'Register Bonus',
@@ -708,28 +704,6 @@ CONFIG_SCHEMA = {
]
},
- # ==================== 12. 应用功能 ====================
- 'app': {
- 'title': 'Application',
- 'icon': 'appstore',
- 'order': 12,
- 'items': [
- {
- 'key': 'CORS_ORIGINS',
- 'label': 'CORS Origins',
- 'type': 'text',
- 'default': '*',
- 'description': 'Allowed CORS origins (* for all, or comma-separated URLs)'
- },
- {
- 'key': 'ENABLE_AI_ANALYSIS',
- 'label': 'Enable AI Analysis',
- 'type': 'boolean',
- 'default': 'True',
- 'description': 'Enable AI-powered market analysis features'
- },
- ]
- },
}
diff --git a/backend_api_python/app/services/ai_calibration.py b/backend_api_python/app/services/ai_calibration.py
index 7419f75..d7492d6 100644
--- a/backend_api_python/app/services/ai_calibration.py
+++ b/backend_api_python/app/services/ai_calibration.py
@@ -71,7 +71,6 @@ class AICalibrationService:
sell_threshold DECIMAL(10,4) NOT NULL,
min_consensus_abs_override DECIMAL(10,4) NOT NULL,
quality_hold_threshold DECIMAL(10,4) NOT NULL,
- sample_count INT NOT NULL DEFAULT 0,
validated_at TIMESTAMP DEFAULT NOW(),
created_at TIMESTAMP DEFAULT NOW()
);
@@ -281,9 +280,9 @@ class AICalibrationService:
INSERT INTO qd_ai_calibration
(market, buy_threshold, sell_threshold,
min_consensus_abs_override, quality_hold_threshold,
- sample_count, validated_at, created_at)
+ validated_at, created_at)
VALUES
- (%s, %s, %s, %s, %s, %s, NOW(), NOW())
+ (%s, %s, %s, %s, %s, NOW(), NOW())
""",
(
market,
@@ -291,7 +290,6 @@ class AICalibrationService:
sell_threshold,
min_consensus_abs_override,
quality_hold_threshold,
- sample_count,
),
)
db.commit()
diff --git a/backend_api_python/app/services/analysis_memory.py b/backend_api_python/app/services/analysis_memory.py
index acae9f5..1045b2e 100644
--- a/backend_api_python/app/services/analysis_memory.py
+++ b/backend_api_python/app/services/analysis_memory.py
@@ -58,16 +58,11 @@ class AnalysisMemory:
decision VARCHAR(10) NOT NULL,
confidence INT DEFAULT 50,
price_at_analysis DECIMAL(24, 8),
- entry_price DECIMAL(24, 8),
- stop_loss DECIMAL(24, 8),
- take_profit DECIMAL(24, 8),
summary TEXT,
reasons JSONB,
- risks JSONB,
scores JSONB,
indicators_snapshot JSONB,
raw_result JSONB,
- consensus_decision VARCHAR(10),
consensus_score DECIMAL(24, 8),
consensus_abs DECIMAL(24, 8),
agreement_ratio DECIMAL(10, 6),
@@ -102,14 +97,6 @@ class AnalysisMemory:
ALTER TABLE qd_analysis_memory ADD COLUMN raw_result JSONB;
END IF;
- -- 添加多周期共识列(如果不存在)
- IF NOT EXISTS (
- SELECT 1 FROM information_schema.columns
- WHERE table_name = 'qd_analysis_memory' AND column_name = 'consensus_decision'
- ) THEN
- ALTER TABLE qd_analysis_memory ADD COLUMN consensus_decision VARCHAR(10);
- END IF;
-
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'qd_analysis_memory' AND column_name = 'consensus_score'
@@ -182,18 +169,13 @@ class AnalysisMemory:
decision = analysis_result.get("decision")
confidence = analysis_result.get("confidence")
price = analysis_result.get("market_data", {}).get("current_price")
- entry = analysis_result.get("trading_plan", {}).get("entry_price")
- stop = analysis_result.get("trading_plan", {}).get("stop_loss")
- take = analysis_result.get("trading_plan", {}).get("take_profit")
summary = analysis_result.get("summary")
reasons = json.dumps(analysis_result.get("reasons", []))
- risks = json.dumps(analysis_result.get("risks", []))
scores = json.dumps(analysis_result.get("scores", {}))
indicators = json.dumps(analysis_result.get("indicators", {}))
raw = json.dumps(analysis_result)
consensus = analysis_result.get("consensus") or {}
- consensus_decision = consensus.get("consensus_decision")
consensus_score = consensus.get("consensus_score")
consensus_abs = consensus.get("consensus_abs")
agreement_ratio = consensus.get("agreement_ratio")
@@ -202,15 +184,16 @@ class AnalysisMemory:
cur.execute("""
INSERT INTO qd_analysis_memory (
user_id, market, symbol, decision, confidence,
- price_at_analysis, entry_price, stop_loss, take_profit,
- summary, reasons, risks, scores, indicators_snapshot, raw_result,
- consensus_decision, consensus_score, consensus_abs, agreement_ratio, quality_multiplier
- ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
- %s, %s, %s, %s, %s)
+ price_at_analysis, summary, reasons, scores, indicators_snapshot, raw_result,
+ consensus_score, consensus_abs, agreement_ratio, quality_multiplier
+ ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
+ %s, %s, %s, %s)
RETURNING id
- """, (user_id, market, symbol, decision, confidence, price, entry, stop, take,
- summary, reasons, risks, scores, indicators, raw,
- consensus_decision, consensus_score, consensus_abs, agreement_ratio, quality_multiplier))
+ """, (
+ user_id, market, symbol, decision, confidence,
+ price, summary, reasons, scores, indicators, raw,
+ consensus_score, consensus_abs, agreement_ratio, quality_multiplier,
+ ))
# 使用 lastrowid 属性获取 ID(execute 内部已经处理了 RETURNING)
memory_id = cur.lastrowid
diff --git a/backend_api_python/app/services/billing_service.py b/backend_api_python/app/services/billing_service.py
index 531af56..41b5730 100644
--- a/backend_api_python/app/services/billing_service.py
+++ b/backend_api_python/app/services/billing_service.py
@@ -1,12 +1,13 @@
"""
Billing Service - 统一计费服务
-管理用户积分消费、VIP状态检查、计费配置等功能。
-支持两种计费模式:
-1. 积分消耗模式:每次使用功能扣除相应积分
-2. VIP免费模式:VIP用户在有效期内免费使用
+负责用户积分余额、功能扣费、会员状态与套餐发放。
+当前计费模型为:
+1. 是否扣费由 `BILLING_ENABLED` 与各功能 cost 配置决定
+2. VIP/会员状态用于会员套餐与权益展示
+3. 社区指标的 `vip_free` 逻辑在社区购买流程中单独处理,不在这里做全局旁路
-计费配置存储在 .env 文件中,可通过系统设置界面配置。
+计费配置存储在 `.env` 文件中,可通过系统设置界面配置。
"""
import os
import time
@@ -27,9 +28,7 @@ BILLING_CONFIG_PREFIX = 'BILLING_'
DEFAULT_BILLING_CONFIG = {
# 全局开关
'enabled': False, # 是否启用计费
- # IMPORTANT: VIP 不再默认免扣积分(VIP 仅对“VIP免费指标”生效)
- 'vip_bypass': False, # VIP用户是否免费(功能计费层面的旁路,默认关闭)
-
+
# 各功能积分消耗(0表示免费)
'cost_ai_analysis': 10, # AI分析 每次消耗积分
'cost_strategy_run': 5, # 策略运行 每次消耗积分(启动时)
@@ -96,7 +95,7 @@ class BillingService:
return config.get('enabled', False)
def get_feature_cost(self, feature: str) -> int:
- """获取功能消耗积分"""
+ """获取指定功能的积分消耗,0 表示免费"""
config = self.get_billing_config()
cost_key = f'cost_{feature}'
return config.get(cost_key, 0)
@@ -475,13 +474,10 @@ class BillingService:
# 免费功能
if cost <= 0:
return True, 'free_feature'
-
- # 检查VIP状态
- if config.get('vip_bypass', True):
- is_vip, _ = self.get_user_vip_status(user_id)
- if is_vip:
- return True, 'vip_free'
-
+
+ # 说明:这里不再根据 VIP 做全局免扣积分旁路。
+ # VIP / membership 仅保留为套餐、到期时间和社区 vip_free 指标权益。
+
# 检查积分余额
credits = self.get_user_credits(user_id)
if credits < cost:
@@ -710,7 +706,7 @@ class BillingService:
return {'items': [], 'total': 0, 'page': 1, 'page_size': page_size, 'total_pages': 0}
def get_user_billing_info(self, user_id: int) -> Dict[str, Any]:
- """获取用户计费信息(供前端显示)"""
+ """获取用户计费与会员信息快照(供前端显示)"""
credits = self.get_user_credits(user_id)
is_vip, vip_expires_at = self.get_user_vip_status(user_id)
config = self.get_billing_config()
@@ -720,9 +716,6 @@ class BillingService:
'is_vip': is_vip,
'vip_expires_at': vip_expires_at.isoformat() if vip_expires_at else None,
'billing_enabled': config.get('enabled', False),
- 'vip_bypass': config.get('vip_bypass', False),
- # Public support link for credits recharge / VIP purchase
- 'recharge_telegram_url': os.getenv('RECHARGE_TELEGRAM_URL', '').strip() or 'https://t.me/your_support_bot',
# 功能费用(供前端显示)
'feature_costs': {
'ai_analysis': config.get('cost_ai_analysis', 0),
diff --git a/backend_api_python/app/services/live_trading/bitget.py b/backend_api_python/app/services/live_trading/bitget.py
index b8f6858..b541010 100644
--- a/backend_api_python/app/services/live_trading/bitget.py
+++ b/backend_api_python/app/services/live_trading/bitget.py
@@ -20,6 +20,17 @@ from app.services.live_trading.symbols import to_bitget_um_symbol
class BitgetMixClient(BaseRestClient):
+ _CHANNEL_API_CODE_ORDER_PATHS = {
+ "/api/v2/mix/order/place-order",
+ "/api/v2/mix/order/batch-place-order",
+ "/api/v2/mix/order/modify-order",
+ "/api/v2/mix/order/place-plan-order",
+ "/api/v2/mix/order/place-tpsl-order",
+ "/api/v3/trade/place-order",
+ "/api/v3/trade/place-batch",
+ "/api/v3/trade/modify-order",
+ }
+
def __init__(
self,
*,
@@ -28,11 +39,13 @@ class BitgetMixClient(BaseRestClient):
passphrase: str,
base_url: str = "https://api.bitget.com",
timeout_sec: float = 15.0,
+ channel_api_code: str = "qvz9x",
):
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
self.api_key = (api_key or "").strip()
self.secret_key = (secret_key or "").strip()
self.passphrase = (passphrase or "").strip()
+ self.channel_api_code = (channel_api_code or "").strip()
if not self.api_key or not self.secret_key or not self.passphrase:
raise LiveTradingError("Missing Bitget api_key/secret_key/passphrase")
@@ -172,14 +185,18 @@ class BitgetMixClient(BaseRestClient):
mac = hmac.new(self.secret_key.encode("utf-8"), prehash.encode("utf-8"), hashlib.sha256).digest()
return base64.b64encode(mac).decode("utf-8")
- def _headers(self, ts_ms: str, sign: str) -> Dict[str, str]:
- return {
+ def _headers(self, ts_ms: str, sign: str, request_path: str = "") -> Dict[str, str]:
+ headers = {
"ACCESS-KEY": self.api_key,
"ACCESS-SIGN": sign,
"ACCESS-TIMESTAMP": ts_ms,
"ACCESS-PASSPHRASE": self.passphrase,
"Content-Type": "application/json",
}
+ clean_path = str(request_path or "").split("?", 1)[0]
+ if self.channel_api_code and clean_path in self._CHANNEL_API_CODE_ORDER_PATHS:
+ headers["X-CHANNEL-API-CODE"] = self.channel_api_code
+ return headers
def _signed_request(
self,
@@ -210,7 +227,7 @@ class BitgetMixClient(BaseRestClient):
path,
params=params,
data=body_str if body_str else None,
- headers=self._headers(ts_ms, sign),
+ headers=self._headers(ts_ms, sign, path),
)
if code >= 400:
raise LiveTradingError(f"Bitget HTTP {code}: {text[:500]}")
diff --git a/backend_api_python/app/services/live_trading/bitget_spot.py b/backend_api_python/app/services/live_trading/bitget_spot.py
index 827c20a..144d995 100644
--- a/backend_api_python/app/services/live_trading/bitget_spot.py
+++ b/backend_api_python/app/services/live_trading/bitget_spot.py
@@ -23,6 +23,15 @@ from app.services.live_trading.symbols import to_bitget_um_symbol
class BitgetSpotClient(BaseRestClient):
+ _CHANNEL_API_CODE_ORDER_PATHS = {
+ "/api/v2/spot/trade/place-order",
+ "/api/v2/spot/trade/batch-orders",
+ "/api/v2/spot/trade/place-plan-order",
+ "/api/v3/trade/place-order",
+ "/api/v3/trade/place-batch",
+ "/api/v3/trade/modify-order",
+ }
+
def __init__(
self,
*,
@@ -31,7 +40,7 @@ class BitgetSpotClient(BaseRestClient):
passphrase: str,
base_url: str = "https://api.bitget.com",
timeout_sec: float = 15.0,
- channel_api_code: str = "bntva",
+ channel_api_code: str = "qvz9x",
):
super().__init__(base_url=base_url, timeout_sec=timeout_sec)
self.api_key = (api_key or "").strip()
@@ -150,7 +159,7 @@ class BitgetSpotClient(BaseRestClient):
mac = hmac.new(self.secret_key.encode("utf-8"), prehash.encode("utf-8"), hashlib.sha256).digest()
return base64.b64encode(mac).decode("utf-8")
- def _headers(self, ts_ms: str, sign: str) -> Dict[str, str]:
+ def _headers(self, ts_ms: str, sign: str, request_path: str = "") -> Dict[str, str]:
h = {
"ACCESS-KEY": self.api_key,
"ACCESS-SIGN": sign,
@@ -158,7 +167,8 @@ class BitgetSpotClient(BaseRestClient):
"ACCESS-PASSPHRASE": self.passphrase,
"Content-Type": "application/json",
}
- if self.channel_api_code:
+ clean_path = str(request_path or "").split("?", 1)[0]
+ if self.channel_api_code and clean_path in self._CHANNEL_API_CODE_ORDER_PATHS:
h["X-CHANNEL-API-CODE"] = self.channel_api_code
return h
@@ -188,7 +198,7 @@ class BitgetSpotClient(BaseRestClient):
path,
params=params,
data=body_str if body_str else None,
- headers=self._headers(ts_ms, sign),
+ headers=self._headers(ts_ms, sign, path),
)
if code >= 400:
raise LiveTradingError(f"BitgetSpot HTTP {code}: {text[:500]}")
diff --git a/backend_api_python/app/services/live_trading/bybit.py b/backend_api_python/app/services/live_trading/bybit.py
index 6398ba2..1e5479c 100644
--- a/backend_api_python/app/services/live_trading/bybit.py
+++ b/backend_api_python/app/services/live_trading/bybit.py
@@ -22,6 +22,8 @@ from app.services.live_trading.symbols import to_bybit_symbol
class BybitClient(BaseRestClient):
+ _DEFAULT_BROKER_REFERER = "Ri001020"
+
def __init__(
self,
*,
@@ -36,6 +38,7 @@ class BybitClient(BaseRestClient):
self.api_key = (api_key or "").strip()
self.secret_key = (secret_key or "").strip()
self.category = (category or "linear").strip().lower()
+ self.broker_referer = self._DEFAULT_BROKER_REFERER
if self.category not in ("linear", "spot"):
self.category = "linear"
try:
@@ -155,8 +158,17 @@ class BybitClient(BaseRestClient):
def _sign(self, prehash: str) -> str:
return hmac.new(self.secret_key.encode("utf-8"), prehash.encode("utf-8"), hashlib.sha256).hexdigest()
+ @staticmethod
+ def _resolve_position_idx(pos_side: str) -> Optional[int]:
+ ps = str(pos_side or "").strip().lower()
+ if ps == "long":
+ return 1
+ if ps == "short":
+ return 2
+ return None
+
def _headers(self, ts_ms: str, sign: str) -> Dict[str, str]:
- return {
+ headers = {
"X-BAPI-API-KEY": self.api_key,
"X-BAPI-SIGN": sign,
"X-BAPI-TIMESTAMP": ts_ms,
@@ -164,6 +176,9 @@ class BybitClient(BaseRestClient):
"X-BAPI-SIGN-TYPE": "2",
"Content-Type": "application/json",
}
+ if self.broker_referer:
+ headers["Referer"] = self.broker_referer
+ return headers
def _signed_request(
self,
@@ -277,6 +292,37 @@ class BybitClient(BaseRestClient):
return (Decimal("0"), qty_precision)
return (q, qty_precision)
+ def _normalize_price(self, *, symbol: str, price: float) -> Tuple[Decimal, Optional[int]]:
+ p = self._to_dec(price)
+ if p <= 0:
+ return (Decimal("0"), None)
+ sym = to_bybit_symbol(symbol)
+ try:
+ info = self.get_instrument_info(category=self.category, symbol=sym) or {}
+ except Exception:
+ info = {}
+ pf = (info.get("priceFilter") if isinstance(info, dict) else None) or {}
+ tick = self._to_dec((pf or {}).get("tickSize") or "0")
+ if tick > 0:
+ p = self._floor_to_step(p, tick)
+
+ price_precision = None
+ if tick > 0:
+ try:
+ tick_normalized = tick.normalize()
+ tick_str = str(tick_normalized)
+ if "." in tick_str:
+ price_precision = len(tick_str.split(".")[1])
+ if price_precision < 0:
+ price_precision = 0
+ if price_precision > 18:
+ price_precision = 18
+ else:
+ price_precision = 0
+ except Exception:
+ pass
+ return (p, price_precision)
+
def place_market_order(
self,
*,
@@ -284,6 +330,7 @@ class BybitClient(BaseRestClient):
side: str,
qty: float,
reduce_only: bool = False,
+ pos_side: str = "",
client_order_id: Optional[str] = None,
) -> LiveOrderResult:
sym = to_bybit_symbol(symbol)
@@ -300,8 +347,13 @@ class BybitClient(BaseRestClient):
"side": "Buy" if sd == "buy" else "Sell",
"orderType": "Market",
"qty": self._dec_str(q_dec, strict_precision=qty_precision),
- "timeInForce": "GTC",
+ "timeInForce": "IOC",
}
+ if self.category == "spot":
+ body["marketUnit"] = "baseCoin"
+ pos_idx = self._resolve_position_idx(pos_side) if self.category == "linear" else None
+ if pos_idx is not None:
+ body["positionIdx"] = pos_idx
if reduce_only and self.category == "linear":
body["reduceOnly"] = True
if client_order_id:
@@ -319,6 +371,7 @@ class BybitClient(BaseRestClient):
qty: float,
price: float,
reduce_only: bool = False,
+ pos_side: str = "",
client_order_id: Optional[str] = None,
) -> LiveOrderResult:
sym = to_bybit_symbol(symbol)
@@ -326,21 +379,27 @@ class BybitClient(BaseRestClient):
if sd not in ("buy", "sell"):
raise LiveTradingError(f"Invalid side: {side}")
q_req = float(qty or 0.0)
- px = float(price or 0.0)
- if q_req <= 0 or px <= 0:
+ px_req = float(price or 0.0)
+ if q_req <= 0 or px_req <= 0:
raise LiveTradingError("Invalid qty/price")
q_dec, qty_precision = self._normalize_qty(symbol=symbol, qty=q_req)
+ px_dec, price_precision = self._normalize_price(symbol=symbol, price=px_req)
if float(q_dec or 0) <= 0:
raise LiveTradingError(f"Invalid qty (below step/min): requested={q_req}")
+ if float(px_dec or 0) <= 0:
+ raise LiveTradingError(f"Invalid price (below tick/min): requested={px_req}")
body: Dict[str, Any] = {
"category": self.category,
"symbol": sym,
"side": "Buy" if sd == "buy" else "Sell",
"orderType": "Limit",
"qty": self._dec_str(q_dec, strict_precision=qty_precision),
- "price": str(px),
+ "price": self._dec_str(px_dec, strict_precision=price_precision),
"timeInForce": "GTC",
}
+ pos_idx = self._resolve_position_idx(pos_side) if self.category == "linear" else None
+ if pos_idx is not None:
+ body["positionIdx"] = pos_idx
if reduce_only and self.category == "linear":
body["reduceOnly"] = True
if client_order_id:
@@ -404,13 +463,28 @@ class BybitClient(BaseRestClient):
# Extract fee from cumExecFee (Bybit API field for cumulative execution fee)
fee = 0.0
fee_ccy = ""
- try:
- fee = abs(float(last.get("cumExecFee") or 0.0))
- except Exception:
- fee = 0.0
- # Bybit linear contracts are settled in USDT
- if fee > 0:
- fee_ccy = "USDT"
+ fee_detail = last.get("cumFeeDetail") if isinstance(last, dict) else None
+ if isinstance(fee_detail, dict) and fee_detail:
+ total_fee = 0.0
+ fee_keys = []
+ for k, v in fee_detail.items():
+ try:
+ fv = abs(float(v or 0.0))
+ except Exception:
+ fv = 0.0
+ if fv > 0:
+ total_fee += fv
+ fee_keys.append(str(k))
+ fee = total_fee
+ if len(fee_keys) == 1:
+ fee_ccy = fee_keys[0]
+ if fee <= 0:
+ try:
+ fee = abs(float(last.get("cumExecFee") or 0.0))
+ except Exception:
+ fee = 0.0
+ if fee > 0 and self.category == "linear":
+ fee_ccy = "USDT"
if filled > 0 and avg_price > 0:
return {"filled": filled, "avg_price": avg_price, "fee": fee, "fee_ccy": fee_ccy, "status": status, "order": last}
if status.lower() in ("filled", "cancelled", "canceled", "rejected"):
diff --git a/backend_api_python/app/services/live_trading/execution.py b/backend_api_python/app/services/live_trading/execution.py
index e8acc51..879b654 100644
--- a/backend_api_python/app/services/live_trading/execution.py
+++ b/backend_api_python/app/services/live_trading/execution.py
@@ -184,6 +184,7 @@ def place_order_from_signal(
side=side,
qty=qty,
reduce_only=reduce_only,
+ pos_side=pos_side,
client_order_id=client_order_id,
)
if isinstance(client, CoinbaseExchangeClient):
diff --git a/backend_api_python/app/services/live_trading/factory.py b/backend_api_python/app/services/live_trading/factory.py
index 3b4fe1a..a8849d2 100644
--- a/backend_api_python/app/services/live_trading/factory.py
+++ b/backend_api_python/app/services/live_trading/factory.py
@@ -84,15 +84,22 @@ def create_client(exchange_config: Dict[str, Any], *, market_type: str = "swap")
if exchange_id == "bitget":
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://api.bitget.com"
if mt == "spot":
- channel_api_code = _get(exchange_config, "channel_api_code", "channelApiCode") or "bntva"
+ channel_api_code = _get(exchange_config, "channel_api_code", "channelApiCode") or "qvz9x"
return BitgetSpotClient(api_key=api_key, secret_key=secret_key, passphrase=passphrase, base_url=base_url, channel_api_code=channel_api_code)
- return BitgetMixClient(api_key=api_key, secret_key=secret_key, passphrase=passphrase, base_url=base_url)
+ channel_api_code = _get(exchange_config, "channel_api_code", "channelApiCode") or "qvz9x"
+ return BitgetMixClient(api_key=api_key, secret_key=secret_key, passphrase=passphrase, base_url=base_url, channel_api_code=channel_api_code)
if exchange_id == "bybit":
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://api.bybit.com"
category = "spot" if mt == "spot" else "linear"
recv_window_ms = int(exchange_config.get("recv_window_ms") or exchange_config.get("recvWindow") or 5000)
- return BybitClient(api_key=api_key, secret_key=secret_key, base_url=base_url, category=category, recv_window_ms=recv_window_ms)
+ return BybitClient(
+ api_key=api_key,
+ secret_key=secret_key,
+ base_url=base_url,
+ category=category,
+ recv_window_ms=recv_window_ms,
+ )
if exchange_id in ("coinbaseexchange", "coinbase_exchange"):
base_url = _get(exchange_config, "base_url", "baseUrl") or "https://api.exchange.coinbase.com"
diff --git a/backend_api_python/app/services/market_data_collector.py b/backend_api_python/app/services/market_data_collector.py
index 0d403a7..114e9ef 100644
--- a/backend_api_python/app/services/market_data_collector.py
+++ b/backend_api_python/app/services/market_data_collector.py
@@ -1006,7 +1006,7 @@ class MarketDataCollector:
策略(按优先级):
1. 结构化API (Finnhub) - 美股首选
- 2. 搜索引擎 (Bocha/Tavily) - 补充搜索
+ 2. 搜索引擎 (Tavily/Google/Bing/SerpAPI) - 补充搜索
3. 情绪分析 - Finnhub 社交媒体情绪
"""
news_list = []
@@ -1090,7 +1090,7 @@ class MarketDataCollector:
"""
从搜索引擎获取新闻
- 使用增强的搜索服务 (Bocha/Tavily/SerpAPI)
+ 使用增强的搜索服务 (Tavily/Google/Bing/SerpAPI)
"""
news_list = []
diff --git a/backend_api_python/app/services/pending_order_worker.py b/backend_api_python/app/services/pending_order_worker.py
index 1d9a6a6..9a815ad 100644
--- a/backend_api_python/app/services/pending_order_worker.py
+++ b/backend_api_python/app/services/pending_order_worker.py
@@ -1386,6 +1386,7 @@ class PendingOrderWorker:
qty=remaining,
price=limit_price,
reduce_only=reduce_only,
+ pos_side=pos_side,
client_order_id=limit_client_oid,
)
elif isinstance(client, CoinbaseExchangeClient):
@@ -1718,6 +1719,7 @@ class PendingOrderWorker:
side=side,
qty=remaining,
reduce_only=reduce_only,
+ pos_side=pos_side,
client_order_id=market_client_oid,
)
elif isinstance(client, CoinbaseExchangeClient):
diff --git a/backend_api_python/app/services/search.py b/backend_api_python/app/services/search.py
index 3603f99..c37aeda 100644
--- a/backend_api_python/app/services/search.py
+++ b/backend_api_python/app/services/search.py
@@ -3,12 +3,11 @@ Search service v2.0 - 增强版搜索服务
整合多个搜索引擎,支持 API Key 轮换和故障转移
支持的搜索引擎(按优先级):
-1. Bocha (博查) - 搜索优化
-2. Tavily - 专为AI设计,免费1000次/月
-3. SerpAPI - Google/Bing 结果抓取
-4. Google CSE - 自定义搜索引擎
-5. Bing Search API
-6. DuckDuckGo - 免费兜底
+1. Tavily - 专为AI设计,免费1000次/月
+2. SerpAPI - Google/Bing 结果抓取
+3. Google CSE - 自定义搜索引擎
+4. Bing Search API
+5. DuckDuckGo - 免费兜底
参考:daily_stock_analysis-main/src/search_service.py
"""
@@ -331,130 +330,6 @@ class TavilySearchProvider(BaseSearchProvider):
)
-class BochaSearchProvider(BaseSearchProvider):
- """
- 博查搜索引擎
-
- 特点:
- - 专为AI优化的中文搜索API
- - 结果准确、摘要完整
- - 支持时间范围过滤和AI摘要
-
- 文档:https://bocha-ai.feishu.cn/wiki/RXEOw02rFiwzGSkd9mUcqoeAnNK
- """
-
- def __init__(self, api_keys: List[str]):
- super().__init__(api_keys, "Bocha")
-
- def _do_search(self, query: str, api_key: str, max_results: int, days: int = 7) -> SearchResponse:
- """执行博查搜索"""
- try:
- url = "https://api.bochaai.com/v1/web-search"
-
- headers = {
- 'Authorization': f'Bearer {api_key}',
- 'Content-Type': 'application/json'
- }
-
- # 确定时间范围
- freshness = "oneWeek"
- if days <= 1:
- freshness = "oneDay"
- elif days <= 7:
- freshness = "oneWeek"
- elif days <= 30:
- freshness = "oneMonth"
- else:
- freshness = "oneYear"
-
- payload = {
- "query": query,
- "freshness": freshness,
- "summary": True,
- "count": min(max_results, 50)
- }
-
- response = requests.post(url, headers=headers, json=payload, timeout=15)
-
- if response.status_code != 200:
- error_message = response.text
- try:
- if response.headers.get('content-type', '').startswith('application/json'):
- error_data = response.json()
- error_message = error_data.get('message', response.text)
- except:
- pass
-
- if response.status_code == 403:
- error_msg = f"余额不足: {error_message}"
- elif response.status_code == 401:
- error_msg = f"API KEY无效: {error_message}"
- elif response.status_code == 429:
- error_msg = f"请求频率达到限制: {error_message}"
- else:
- error_msg = f"HTTP {response.status_code}: {error_message}"
-
- return SearchResponse(
- query=query,
- results=[],
- provider=self.name,
- success=False,
- error_message=error_msg
- )
-
- data = response.json()
-
- if data.get('code') != 200:
- return SearchResponse(
- query=query,
- results=[],
- provider=self.name,
- success=False,
- error_message=data.get('msg') or f"API返回错误码: {data.get('code')}"
- )
-
- results = []
- web_pages = data.get('data', {}).get('webPages', {})
- value_list = web_pages.get('value', [])
-
- for item in value_list[:max_results]:
- snippet = item.get('summary') or item.get('snippet', '')
- if snippet:
- snippet = snippet[:500]
-
- results.append(SearchResult(
- title=item.get('name', ''),
- snippet=snippet,
- url=item.get('url', ''),
- source=item.get('siteName') or self._extract_domain(item.get('url', '')),
- published_date=item.get('datePublished'),
- ))
-
- return SearchResponse(
- query=query,
- results=results,
- provider=self.name,
- success=True,
- )
-
- except requests.exceptions.Timeout:
- return SearchResponse(
- query=query,
- results=[],
- provider=self.name,
- success=False,
- error_message="请求超时"
- )
- except Exception as e:
- return SearchResponse(
- query=query,
- results=[],
- provider=self.name,
- success=False,
- error_message=str(e)
- )
-
-
class SerpAPISearchProvider(BaseSearchProvider):
"""
SerpAPI 搜索引擎
@@ -865,38 +740,32 @@ class SearchService:
"""初始化搜索引擎(按优先级排序)"""
from app.config import APIKeys
- # 1. Bocha 优先(国内搜索优化)
- bocha_keys = APIKeys.BOCHA_API_KEYS
- if bocha_keys:
- self._providers.append(BochaSearchProvider(bocha_keys))
- logger.info(f"已配置 Bocha 搜索,共 {len(bocha_keys)} 个 API Key")
-
- # 2. Tavily(AI优化搜索)
+ # 1. Tavily(AI优化搜索)
tavily_keys = APIKeys.TAVILY_API_KEYS
if tavily_keys:
self._providers.append(TavilySearchProvider(tavily_keys))
logger.info(f"已配置 Tavily 搜索,共 {len(tavily_keys)} 个 API Key")
- # 3. SerpAPI
+ # 2. SerpAPI
serpapi_keys = APIKeys.SERPAPI_KEYS
if serpapi_keys:
self._providers.append(SerpAPISearchProvider(serpapi_keys))
logger.info(f"已配置 SerpAPI 搜索,共 {len(serpapi_keys)} 个 API Key")
- # 4. Google CSE
+ # 3. Google CSE
google_api_key = self._config.get('google', {}).get('api_key')
google_cx = self._config.get('google', {}).get('cx')
if google_api_key and google_cx:
self._providers.append(GoogleSearchProvider(google_api_key, google_cx))
logger.info("已配置 Google CSE 搜索")
- # 5. Bing
+ # 4. Bing
bing_api_key = self._config.get('bing', {}).get('api_key')
if bing_api_key:
self._providers.append(BingSearchProvider(bing_api_key))
logger.info("已配置 Bing 搜索")
- # 6. DuckDuckGo(免费兜底)
+ # 5. DuckDuckGo(免费兜底)
self._providers.append(DuckDuckGoSearchProvider())
logger.info("已配置 DuckDuckGo 搜索(免费兜底)")
diff --git a/backend_api_python/app/utils/config_loader.py b/backend_api_python/app/utils/config_loader.py
index acaa9c5..be33aed 100644
--- a/backend_api_python/app/utils/config_loader.py
+++ b/backend_api_python/app/utils/config_loader.py
@@ -69,7 +69,6 @@ def load_addon_config() -> Dict[str, Any]:
('OPENROUTER_MAX_TOKENS', 'openrouter.max_tokens', 'int'),
('OPENROUTER_TIMEOUT', 'openrouter.timeout', 'int'),
('OPENROUTER_CONNECT_TIMEOUT', 'openrouter.connect_timeout', 'int'),
- ('AI_MODELS_JSON', 'ai.models', 'json'),
# OpenAI Direct
('OPENAI_API_KEY', 'openai.api_key', 'string'),
@@ -94,11 +93,9 @@ def load_addon_config() -> Dict[str, Any]:
('LLM_PROVIDER', 'llm.provider', 'string'),
# App
- ('CORS_ORIGINS', 'app.cors_origins', 'string'),
('RATE_LIMIT', 'app.rate_limit', 'int'),
('ENABLE_CACHE', 'app.enable_cache', 'bool'),
('ENABLE_REQUEST_LOG', 'app.enable_request_log', 'bool'),
- ('ENABLE_AI_ANALYSIS', 'app.enable_ai_analysis', 'bool'),
# Data source common
('DATA_SOURCE_TIMEOUT', 'data_source.timeout', 'int'),
@@ -113,7 +110,6 @@ def load_addon_config() -> Dict[str, Any]:
# CCXT
('CCXT_DEFAULT_EXCHANGE', 'ccxt.default_exchange', 'string'),
('CCXT_TIMEOUT', 'ccxt.timeout', 'int'),
- ('CCXT_PROXY', 'ccxt.proxy', 'string'),
# Other sources
('YFINANCE_TIMEOUT', 'yfinance.timeout', 'int'),
@@ -131,9 +127,6 @@ def load_addon_config() -> Dict[str, Any]:
# Tavily (AI-optimized search)
('TAVILY_API_KEYS', 'tavily.api_keys', 'string'),
- # Bocha (Chinese search optimization)
- ('BOCHA_API_KEYS', 'bocha.api_keys', 'string'),
-
# SerpAPI (Google/Bing scraper)
('SERPAPI_KEYS', 'serpapi.api_keys', 'string'),
]
diff --git a/backend_api_python/docker-entrypoint.sh b/backend_api_python/docker-entrypoint.sh
index 08d8c48..0ad4842 100644
--- a/backend_api_python/docker-entrypoint.sh
+++ b/backend_api_python/docker-entrypoint.sh
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
# QuantDinger Docker Entrypoint Script
# Checks and validates SECRET_KEY before starting the application
diff --git a/backend_api_python/env.example b/backend_api_python/env.example
index ff892dc..0739db8 100644
--- a/backend_api_python/env.example
+++ b/backend_api_python/env.example
@@ -1,8 +1,13 @@
# QuantDinger local configuration (copy to `.env` and edit)
# `run.py` will load `backend_api_python/.env` automatically if present.
+#
+# This file is organized as:
+# 1) first-time deployment settings at the top
+# 2) advanced / rarely changed settings at the bottom
+# For Docker image source / ports, use the project-root `.env`.
# =========================
-# Auth (local login)
+# Auth (required)
# =========================
SECRET_KEY=quantdinger-secret-key-change-me
ADMIN_USER=quantdinger
@@ -10,67 +15,43 @@ ADMIN_PASSWORD=123456
ADMIN_EMAIL=
# =========================
-# Demo Mode
+# Core app
# =========================
-# Set to true to enable read-only mode for public demo.
-# Blocks all POST/PUT/DELETE requests except login.
IS_DEMO_MODE=false
-
-# =========================
-# Network / App
-# =========================
-PYTHON_API_HOST=0.0.0.0
-PYTHON_API_PORT=5000
-PYTHON_API_DEBUG=False
-
-# =========================
-# Database Configuration (PostgreSQL)
-# =========================
-# PostgreSQL connection URL (required for multi-user mode)
-# Format: postgresql://user:password@host:port/dbname
-# Docker: uses this default, no changes needed
-# Local: change 'postgres' to 'localhost' and update password
DATABASE_URL=postgresql://quantdinger:quantdinger123@postgres:5432/quantdinger
+FRONTEND_URL=http://localhost:8888
+ENABLE_REGISTRATION=true
# =========================
-# Pending orders worker (optional)
+# AI / LLM (choose one provider)
+# =========================
+LLM_PROVIDER=openrouter
+
+OPENROUTER_API_KEY=
+OPENROUTER_MODEL=openai/gpt-4o
+
+OPENAI_API_KEY=
+OPENAI_MODEL=gpt-4o
+
+GOOGLE_API_KEY=
+GOOGLE_MODEL=gemini-1.5-flash
+
+DEEPSEEK_API_KEY=
+DEEPSEEK_MODEL=deepseek-chat
+
+GROK_API_KEY=
+GROK_MODEL=grok-beta
+
+# =========================
+# Common background jobs
# =========================
-# Paper mode default: disabled. If enabled, it will consume `pending_orders` and dispatch signals via webhook.
-# Poll and dispatch orders from `pending_orders` (live/signal).
-# Local mode default is enabled in code, but you can override here.
ENABLE_PENDING_ORDER_WORKER=true
-
-# =========================
-# Portfolio monitor (optional)
-# =========================
-# Background service that runs scheduled AI analysis on manual positions
-# and sends notifications (email/telegram/browser).
-# Default: enabled. Set to false to disable.
ENABLE_PORTFOLIO_MONITOR=true
-
-# Reclaim orders stuck in status=processing after worker crashes (seconds).
-PENDING_ORDER_STALE_SEC=90
+DISABLE_RESTORE_RUNNING_STRATEGIES=false
# =========================
-# Live trading order execution settings
+# Email / SMTP (optional)
# =========================
-# Order execution mode:
-# - "market": Market order only (immediate execution, recommended for stability)
-# - "maker": Limit order first, then market order for remaining (lower fees, may not fill)
-ORDER_MODE=market
-
-# How long to wait for limit order to fill before switching to market order (seconds)
-MAKER_WAIT_SEC=10
-
-# Price offset for limit orders in basis points (1 bps = 0.01%)
-# Buy orders: price = market_price * (1 - offset)
-# Sell orders: price = market_price * (1 + offset)
-MAKER_OFFSET_BPS=2
-
-# =========================
-# Email / SMTP (公共邮件服务,所有用户共用)
-# =========================
-# 用户在个人中心配置自己的通知邮箱,SMTP 服务器由管理员统一配置
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
@@ -79,283 +60,144 @@ SMTP_FROM=
SMTP_USE_TLS=true
SMTP_USE_SSL=false
-# Phone / SMS (optional; Twilio REST, required if you enable phone channel)
-TWILIO_ACCOUNT_SID=
-TWILIO_AUTH_TOKEN=
-TWILIO_FROM_NUMBER=
-
-# Restore strategies with status='running' on backend startup.
-# Set to true to disable auto-restore.
-DISABLE_RESTORE_RUNNING_STRATEGIES=false
-
# =========================
-# Strategy execution loop (tick interval)
+# Proxy (optional)
# =========================
-# Default tick interval for strategy monitoring loop (seconds).
-# The strategy thread will fetch current price and evaluate triggers once per tick.
-STRATEGY_TICK_INTERVAL_SEC=10
-
-# In-memory price cache TTL (seconds). Normally doesn't matter when tick interval is >= TTL.
-PRICE_CACHE_TTL_SEC=10
-
-# =========================
-# Outbound Proxy (optional, recommended if your network blocks data providers)
-# =========================
-# If you use a local proxy (common ports: 7890/7891/10808), set PROXY_PORT only.
-# Default scheme is socks5h and host is 127.0.0.1.
-# For Docker deployment: set PROXY_HOST=host.docker.internal to access host's proxy
-PROXY_PORT=
-PROXY_HOST=127.0.0.1
-PROXY_SCHEME=socks5h
+# Most users only need PROXY_URL.
+# Example local:
+# PROXY_URL=socks5h://127.0.0.1:10808
+#
+# Example Docker:
+# PROXY_URL=socks5h://host.docker.internal:10808
PROXY_URL=
-# You can also set standard variables directly (advanced):
-# ALL_PROXY=socks5h://127.0.0.1:10808
-# HTTP_PROXY=socks5h://127.0.0.1:10808
-# HTTPS_PROXY=socks5h://127.0.0.1:10808
+# =========================
+# Captcha / OAuth (optional)
+# =========================
+TURNSTILE_SITE_KEY=
+TURNSTILE_SECRET_KEY=
-# Allow frontend dev server
-CORS_ORIGINS=*
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+GOOGLE_REDIRECT_URI=http://localhost:5000/api/auth/oauth/google/callback
-# Request rate limit (per minute)
+GITHUB_CLIENT_ID=
+GITHUB_CLIENT_SECRET=
+GITHUB_REDIRECT_URI=http://localhost:5000/api/auth/oauth/github/callback
+
+# =========================
+# Billing / payments (optional)
+# =========================
+BILLING_ENABLED=False
+USDT_PAY_ENABLED=False
+
+# =========================
+# Advanced / rarely changed
+# =========================
+# The settings below are optional. Most users can leave them unchanged.
+
+# Network / App tuning
+PYTHON_API_HOST=0.0.0.0
+PYTHON_API_PORT=5000
+PYTHON_API_DEBUG=False
RATE_LIMIT=100
-
ENABLE_CACHE=False
ENABLE_REQUEST_LOG=True
-ENABLE_AI_ANALYSIS=True
-# =========================
-# Agent memory & reflection (optional)
-# =========================
-# Toggle agent memory usage in multi-agent analysis
-ENABLE_AGENT_MEMORY=true
+# Strategy / execution tuning
+PENDING_ORDER_STALE_SEC=90
+ORDER_MODE=market
+MAKER_WAIT_SEC=10
+MAKER_OFFSET_BPS=2
+STRATEGY_TICK_INTERVAL_SEC=10
+PRICE_CACHE_TTL_SEC=10
+K_LINE_HISTORY_GET_NUMBER=500
-# Memory retrieval settings
-AGENT_MEMORY_ENABLE_VECTOR=true
-AGENT_MEMORY_EMBEDDING_DIM=256
-AGENT_MEMORY_TOP_K=5
-AGENT_MEMORY_CANDIDATE_LIMIT=500
-AGENT_MEMORY_HALF_LIFE_DAYS=30
-AGENT_MEMORY_W_SIM=0.75
-AGENT_MEMORY_W_RECENCY=0.20
-AGENT_MEMORY_W_RETURNS=0.05
-
-# Automated verification loop (replaces cron)
-ENABLE_REFLECTION_WORKER=false
-REFLECTION_WORKER_INTERVAL_SEC=86400
-
-# =========================
-# LLM Provider Selection
-# =========================
-# Choose your LLM provider: openrouter, openai, google, deepseek, grok
-LLM_PROVIDER=openrouter
-
-# =========================
-# OpenRouter (Multi-model gateway, recommended)
-# =========================
-OPENROUTER_API_KEY=
+# LLM advanced tuning
OPENROUTER_API_URL=https://openrouter.ai/api/v1/chat/completions
-OPENROUTER_MODEL=openai/gpt-4o
OPENROUTER_TEMPERATURE=0.7
OPENROUTER_MAX_TOKENS=4000
OPENROUTER_TIMEOUT=300
OPENROUTER_CONNECT_TIMEOUT=30
-# =========================
-# OpenAI Direct
-# =========================
-OPENAI_API_KEY=
OPENAI_BASE_URL=https://api.openai.com/v1
-OPENAI_MODEL=gpt-4o
-
-# =========================
-# Google Gemini
-# =========================
-GOOGLE_API_KEY=
-GOOGLE_MODEL=gemini-1.5-flash
-
-# =========================
-# DeepSeek
-# =========================
-DEEPSEEK_API_KEY=
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
-DEEPSEEK_MODEL=deepseek-chat
-
-# =========================
-# xAI Grok
-# =========================
-GROK_API_KEY=
GROK_BASE_URL=https://api.x.ai/v1
-GROK_MODEL=grok-beta
-# Optional: override model list shown in UI (JSON object: {"model_id":"Display Name", ...})
-AI_MODELS_JSON={}
-
-# =========================
-# Data sources (Kline / pricing)
-# =========================
+# Data sources
DATA_SOURCE_TIMEOUT=30
DATA_SOURCE_RETRY=3
DATA_SOURCE_RETRY_BACKOFF=0.5
-# Finnhub (US stocks / forex helpers depending on implementation)
FINNHUB_API_KEY=
FINNHUB_TIMEOUT=10
FINNHUB_RATE_LIMIT=60
-# CCXT (crypto via Binance by default)
CCXT_DEFAULT_EXCHANGE=coinbase
CCXT_TIMEOUT=10000
-CCXT_PROXY=
-# Akshare (optional)
AKSHARE_TIMEOUT=30
-
-# YFinance
YFINANCE_TIMEOUT=30
-# Tiingo (optional)
TIINGO_API_KEY=
TIINGO_TIMEOUT=10
-# =========================
-# Web Search & News (optional)
-# =========================
-# Search provider priority: tavily > bocha > google > bing > duckduckgo
+# AI search / news providers
SEARCH_PROVIDER=google
SEARCH_MAX_RESULTS=10
-# Google Custom Search (CSE)
SEARCH_GOOGLE_API_KEY=
SEARCH_GOOGLE_CX=
-
-# Bing Search API
SEARCH_BING_API_KEY=
-# Tavily Search API (专为AI设计,推荐!免费1000次/月)
-# Get your key at: https://tavily.com/
-# 支持多个 key 轮换,用逗号分隔: key1,key2,key3
TAVILY_API_KEYS=
-
-# 博查 Bocha Search API (国内搜索优化)
-# Get your key at: https://bochaai.com/
-# 支持多个 key 轮换,用逗号分隔: key1,key2,key3
-BOCHA_API_KEYS=
-
-# SerpAPI (Google/Bing 结果抓取,免费100次/月)
-# Get your key at: https://serpapi.com/
-# 支持多个 key 轮换,用逗号分隔: key1,key2,key3
SERPAPI_KEYS=
-# Internal API key (optional, if you add internal-service auth later)
-INTERNAL_API_KEY=
+# SMS / phone notifications
+TWILIO_ACCOUNT_SID=
+TWILIO_AUTH_TOKEN=
+TWILIO_FROM_NUMBER=
-# =========================
-# Registration & Security (注册与安全)
-# =========================
-# Enable user registration (允许用户注册)
-ENABLE_REGISTRATION=true
-
-# Cloudflare Turnstile (人机验证)
-# Get your keys at: https://dash.cloudflare.com/?to=/:account/turnstile
-TURNSTILE_SITE_KEY=
-TURNSTILE_SECRET_KEY=
-
-# Frontend URL (for OAuth redirects)
-FRONTEND_URL=http://localhost:8080
-
-# Google OAuth
-# Get your credentials at: https://console.cloud.google.com/apis/credentials
-GOOGLE_CLIENT_ID=
-GOOGLE_CLIENT_SECRET=
-GOOGLE_REDIRECT_URI=http://localhost:5000/api/auth/oauth/google/callback
-
-# GitHub OAuth
-# Get your credentials at: https://github.com/settings/developers
-GITHUB_CLIENT_ID=
-GITHUB_CLIENT_SECRET=
-GITHUB_REDIRECT_URI=http://localhost:5000/api/auth/oauth/github/callback
-
-# Security: IP rate limit (防爆破 - IP维度)
-# Block IP after N failed attempts within M minutes for X minutes
+# Security / verification tuning
SECURITY_IP_MAX_ATTEMPTS=10
SECURITY_IP_WINDOW_MINUTES=5
SECURITY_IP_BLOCK_MINUTES=15
-# Security: Account rate limit (防爆破 - 账户维度)
SECURITY_ACCOUNT_MAX_ATTEMPTS=5
SECURITY_ACCOUNT_WINDOW_MINUTES=60
SECURITY_ACCOUNT_BLOCK_MINUTES=30
-# Verification code settings (验证码设置)
VERIFICATION_CODE_EXPIRE_MINUTES=10
VERIFICATION_CODE_RATE_LIMIT=60
VERIFICATION_CODE_IP_HOURLY_LIMIT=10
VERIFICATION_CODE_MAX_ATTEMPTS=5
VERIFICATION_CODE_LOCK_MINUTES=30
-# =========================
-# Billing & Credits (积分计费系统)
-# =========================
-# Enable billing system (启用计费系统)
-BILLING_ENABLED=False
-
-# Legacy: VIP users bypass ALL paid feature credit costs (NOT recommended)
-# 建议关闭:VIP 仅用于“VIP免费指标”,其它功能仍扣积分
-BILLING_VIP_BYPASS=False
-
-# Credits consumed per feature (各功能消耗积分数)
+# Billing / credits
BILLING_COST_AI_ANALYSIS=10
BILLING_COST_STRATEGY_RUN=5
BILLING_COST_BACKTEST=3
BILLING_COST_PORTFOLIO_MONITOR=8
BILLING_COST_POLYMARKET_DEEP_ANALYSIS=15
-# Telegram customer service URL for recharge (充值跳转的Telegram链接)
-RECHARGE_TELEGRAM_URL=https://t.me/quantdinger
+CREDITS_REGISTER_BONUS=100
+CREDITS_REFERRAL_BONUS=50
-# =========================
-# Membership Plans (会员套餐 - Mock支付配置)
-# =========================
-# Price in USD
+# Membership plans
MEMBERSHIP_MONTHLY_PRICE_USD=19.9
MEMBERSHIP_YEARLY_PRICE_USD=199
MEMBERSHIP_LIFETIME_PRICE_USD=499
-# Credits bonus
MEMBERSHIP_MONTHLY_CREDITS=500
MEMBERSHIP_YEARLY_CREDITS=8000
-
-# Lifetime: monthly credits granted every 30 days
MEMBERSHIP_LIFETIME_MONTHLY_CREDITS=800
-# =========================
-# USDT Pay (Plan B: per-order unique address)
-# =========================
-USDT_PAY_ENABLED=False
+# USDT payment
USDT_PAY_CHAIN=TRC20
-
-# TRC20 (TRON) watch-only xpub (derive deposit addresses from index 0..N)
USDT_TRC20_XPUB=
-
-# USDT TRC20 contract on TRON (default)
USDT_TRC20_CONTRACT=TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj
-
-# TronGrid API
TRONGRID_BASE_URL=https://api.trongrid.io
TRONGRID_API_KEY=
-
-# Order confirmation delay and expiration
USDT_PAY_CONFIRM_SECONDS=30
USDT_PAY_EXPIRE_MINUTES=30
-# Background worker poll interval (seconds) for checking pending USDT orders
USDT_WORKER_POLL_INTERVAL=30
-
-# New user registration bonus credits (新用户注册赠送积分)
-CREDITS_REGISTER_BONUS=100
-
-# Referral bonus credits (邀请用户赠送积分,邀请人获得)
-CREDITS_REFERRAL_BONUS=50
-
-# History K-Line ticket get number (策略中默认获取历史K线数量)
-K_LINE_HISTORY_GET_NUMBER=500
diff --git a/backend_api_python/migrations/init.sql b/backend_api_python/migrations/init.sql
index 49e6874..ddfb2bb 100644
--- a/backend_api_python/migrations/init.sql
+++ b/backend_api_python/migrations/init.sql
@@ -365,31 +365,6 @@ CREATE TABLE IF NOT EXISTS qd_indicator_codes (
CREATE INDEX IF NOT EXISTS idx_indicator_codes_user_id ON qd_indicator_codes USING btree (user_id);
CREATE INDEX IF NOT EXISTS idx_indicator_review_status ON qd_indicator_codes USING btree (review_status);
--- =============================================================================
--- 8. AI Decisions
--- =============================================================================
-
-CREATE TABLE IF NOT EXISTS qd_ai_decisions (
- id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL DEFAULT 1 REFERENCES qd_users(id) ON DELETE CASCADE,
- strategy_id INTEGER REFERENCES qd_strategies_trading(id) ON DELETE CASCADE,
- decision_data TEXT,
- context_data TEXT,
- created_at TIMESTAMP DEFAULT NOW()
-);
-
-CREATE INDEX IF NOT EXISTS idx_ai_decisions_user_id ON qd_ai_decisions(user_id);
-
--- =============================================================================
--- 9. Addon Config
--- =============================================================================
-
-CREATE TABLE IF NOT EXISTS qd_addon_config (
- config_key VARCHAR(100) PRIMARY KEY,
- config_value TEXT,
- type VARCHAR(20) DEFAULT 'string'
-);
-
-- =============================================================================
-- 10. Watchlist
-- =============================================================================
@@ -614,56 +589,6 @@ INSERT INTO qd_market_symbols (market, symbol, name, exchange, currency, is_acti
('Futures', 'NQ', 'NASDAQ 100 E-mini', 'CME', 'USD', 1, 1, 91)
ON CONFLICT (market, symbol) DO NOTHING;
--- =============================================================================
--- 18. Agent Memories (AI Learning System)
--- =============================================================================
--- Stores agent decision experiences for RAG-style retrieval during analysis.
--- Each agent (trader, risk_analyst, etc.) shares this table but is identified by agent_name.
-
-CREATE TABLE IF NOT EXISTS qd_agent_memories (
- id SERIAL PRIMARY KEY,
- agent_name VARCHAR(100) NOT NULL,
- situation TEXT NOT NULL,
- recommendation TEXT NOT NULL,
- result TEXT,
- returns REAL,
- market VARCHAR(50),
- symbol VARCHAR(50),
- timeframe VARCHAR(20),
- features_json TEXT,
- embedding BYTEA,
- created_at TIMESTAMP DEFAULT NOW(),
- updated_at TIMESTAMP DEFAULT NOW()
-);
-
-CREATE INDEX IF NOT EXISTS idx_agent_memories_agent ON qd_agent_memories(agent_name);
-CREATE INDEX IF NOT EXISTS idx_agent_memories_created ON qd_agent_memories(agent_name, created_at DESC);
-CREATE INDEX IF NOT EXISTS idx_agent_memories_market ON qd_agent_memories(agent_name, market, symbol);
-
--- =============================================================================
--- 19. Reflection Records (AI Auto-Verification System)
--- =============================================================================
--- Records analysis predictions for future auto-verification and closed-loop learning.
-
-CREATE TABLE IF NOT EXISTS qd_reflection_records (
- id SERIAL PRIMARY KEY,
- market VARCHAR(50) NOT NULL,
- symbol VARCHAR(50) NOT NULL,
- initial_price REAL,
- decision VARCHAR(20),
- confidence INTEGER,
- reasoning TEXT,
- analysis_date TIMESTAMP DEFAULT NOW(),
- target_check_date TIMESTAMP,
- status VARCHAR(20) DEFAULT 'PENDING',
- final_price REAL,
- actual_return REAL,
- check_result TEXT
-);
-
-CREATE INDEX IF NOT EXISTS idx_reflection_status ON qd_reflection_records(status, target_check_date);
-CREATE INDEX IF NOT EXISTS idx_reflection_market ON qd_reflection_records(market, symbol);
-
-- =============================================================================
-- 19.5. Analysis Memory (Fast AI Analysis Memory System)
-- =============================================================================
@@ -677,15 +602,15 @@ CREATE TABLE IF NOT EXISTS qd_analysis_memory (
decision VARCHAR(10) NOT NULL,
confidence INT DEFAULT 50,
price_at_analysis DECIMAL(24, 8),
- entry_price DECIMAL(24, 8),
- stop_loss DECIMAL(24, 8),
- take_profit DECIMAL(24, 8),
summary TEXT,
reasons JSONB,
- risks JSONB,
scores JSONB,
indicators_snapshot JSONB,
raw_result JSONB, -- Full analysis result for history replay
+ consensus_score DECIMAL(24, 8),
+ consensus_abs DECIMAL(24, 8),
+ agreement_ratio DECIMAL(10, 6),
+ quality_multiplier DECIMAL(10, 6),
created_at TIMESTAMP DEFAULT NOW(),
validated_at TIMESTAMP,
actual_outcome VARCHAR(20),
diff --git a/backend_api_python/run.py b/backend_api_python/run.py
index 4e7c869..067da0c 100644
--- a/backend_api_python/run.py
+++ b/backend_api_python/run.py
@@ -47,14 +47,6 @@ def _apply_proxy_env():
# If user provided explicit proxy URL, honor it.
proxy_url = (os.getenv('PROXY_URL') or '').strip()
- # If user only provided port, build a URL (common local proxy setups).
- if not proxy_url:
- port = (os.getenv('PROXY_PORT') or '').strip()
- if port:
- host = (os.getenv('PROXY_HOST') or '127.0.0.1').strip()
- scheme = (os.getenv('PROXY_SCHEME') or 'socks5h').strip()
- proxy_url = f"{scheme}://{host}:{port}"
-
if not proxy_url:
return
@@ -63,9 +55,6 @@ def _apply_proxy_env():
_set_if_blank('HTTP_PROXY', proxy_url)
_set_if_blank('HTTPS_PROXY', proxy_url)
- # CCXT config uses CCXT_PROXY in our codebase.
- _set_if_blank('CCXT_PROXY', proxy_url)
-
_apply_proxy_env()
# Add project root to Python path
diff --git a/docker-compose.yml b/docker-compose.yml
index 4228415..663777f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,19 +8,24 @@
# 3. docker-compose up -d --build
# 4. Open http://localhost:8888
#
-# Frontend image: multi-stage build from QuantDinger-Vue-src (context = repo root).
-# To refresh static files without Docker: cd QuantDinger-Vue-src && npm run build
-# && rm -rf ../frontend/dist && cp -R dist ../frontend/dist
+# Frontend image: nginx serving prebuilt files from `frontend/dist`.
+# The open-source repo does not require Node.js to build the frontend image.
#
# Note: The container will NOT start if SECRET_KEY is using the default value.
# This is a security measure to prevent insecure deployments.
+#
+# Docker image sources:
+# Default uses official Docker Hub images.
+# To switch source globally, set a single `IMAGE_PREFIX` in project-root `.env`.
+# Examples: empty (official), `docker.m.daocloud.io/library/`,
+# `docker.xuanyuan.me/library/`.
services:
# ========================
# PostgreSQL Database
# ========================
postgres:
- image: postgres:16-alpine
+ image: ${IMAGE_PREFIX:-}postgres:16-alpine
container_name: quantdinger-db
restart: unless-stopped
environment:
@@ -48,6 +53,8 @@ services:
build:
context: ./backend_api_python
dockerfile: Dockerfile
+ args:
+ BASE_IMAGE: ${IMAGE_PREFIX:-}python:3.12-slim-bookworm
container_name: quantdinger-backend
restart: unless-stopped
depends_on:
@@ -58,8 +65,8 @@ services:
volumes:
- backend_logs:/app/logs
- backend_data:/app/data
- # Mount .env for runtime config
- - ./backend_api_python/.env:/app/.env:ro
+ # Mount .env for runtime config and admin-panel updates
+ - ./backend_api_python/.env:/app/.env
environment:
- PYTHON_API_HOST=0.0.0.0
- PYTHON_API_PORT=5000
@@ -75,12 +82,14 @@ services:
retries: 3
# ========================
- # Frontend (Nginx + Vue build from QuantDinger-Vue-src)
+ # Frontend (Nginx serving prebuilt dist)
# ========================
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
+ args:
+ RUNTIME_IMAGE: ${IMAGE_PREFIX:-}nginx:1.25-alpine
container_name: quantdinger-frontend
restart: unless-stopped
ports:
diff --git a/docs/CLOUD_DEPLOYMENT_CN.md b/docs/CLOUD_DEPLOYMENT_CN.md
new file mode 100644
index 0000000..1e0d31f
--- /dev/null
+++ b/docs/CLOUD_DEPLOYMENT_CN.md
@@ -0,0 +1,450 @@
+# QuantDinger 云服务器部署指南
+
+本文面向生产/准生产环境,使用云服务器 + Docker Compose 部署 QuantDinger,并补充域名、HTTPS、反向代理与前后端分离说明。
+
+## 推荐架构
+
+推荐使用“同域名 + Nginx 反向代理”:
+
+- 用户访问:`https://app.example.com`
+- 宿主机 Nginx:监听 `80/443`
+- Docker `frontend`:绑定到 `127.0.0.1:8888`
+- Docker `backend`:绑定到 `127.0.0.1:5000`
+- Docker `postgres`:绑定到 `127.0.0.1:5432`
+
+这样做的好处:
+
+- 外网只暴露 `80/443`
+- 前端和 API 走同域名,最省心
+- 数据库与后端管理端口不直接暴露到公网
+
+## 1. 服务器准备
+
+建议配置:
+
+- Ubuntu 22.04 / Debian 12
+- 2C4G 及以上
+- 已开放安全组端口:`22`、`80`、`443`
+- 已准备域名,例如 `app.example.com`
+
+域名解析:
+
+1. 在 DNS 服务商后台添加一条 `A` 记录
+2. 主机记录填 `app`
+3. 记录值填云服务器公网 IP
+4. 等待解析生效
+
+验证:
+
+```bash
+ping app.example.com
+```
+
+## 2. 安装 Docker 与 Docker Compose
+
+Ubuntu / Debian 示例:
+
+```bash
+curl -fsSL https://get.docker.com | sh
+sudo usermod -aG docker $USER
+newgrp docker
+docker --version
+docker compose version
+```
+
+如果你的网络拉取 Docker Hub 较慢,可稍后通过项目根 `.env` 的 `IMAGE_PREFIX` 切换镜像源。
+
+## 3. 拉取项目
+
+```bash
+git clone https://github.com/brokermr810/QuantDinger.git
+cd QuantDinger
+```
+
+## 4. 配置后端 `.env`
+
+复制模板:
+
+```bash
+cp backend_api_python/env.example backend_api_python/.env
+```
+
+生成并写入 `SECRET_KEY`:
+
+```bash
+./scripts/generate-secret-key.sh
+```
+
+至少检查并修改这些配置:
+
+```ini
+ADMIN_USER=quantdinger
+ADMIN_PASSWORD=your_strong_password
+SECRET_KEY=your_generated_secret
+```
+
+如果你需要 AI 功能,再补充至少一个模型提供商密钥,例如:
+
+```ini
+OPENROUTER_API_KEY=your_key
+```
+
+## 5. 配置项目根 `.env`
+
+项目根 `.env` 用于 Docker Compose 的端口和镜像源控制。
+
+推荐生产配置:
+
+```bash
+cp .env.example .env
+```
+
+编辑为:
+
+```ini
+FRONTEND_PORT=127.0.0.1:8888
+BACKEND_PORT=127.0.0.1:5000
+DB_PORT=127.0.0.1:5432
+IMAGE_PREFIX=
+```
+
+说明:
+
+- `FRONTEND_PORT=127.0.0.1:8888`:只允许宿主机本地访问,由外层 Nginx 转发
+- `BACKEND_PORT=127.0.0.1:5000`:避免后端 API 直接暴露公网
+- `DB_PORT=127.0.0.1:5432`:避免数据库端口暴露公网
+- `IMAGE_PREFIX=`:空值表示官方 Docker Hub
+
+如果拉镜像失败,可改成:
+
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+或:
+
+```ini
+IMAGE_PREFIX=docker.xuanyuan.me/library/
+```
+
+## 6. 启动容器
+
+```bash
+docker-compose up -d --build
+docker-compose ps
+```
+
+查看日志:
+
+```bash
+docker-compose logs -f backend
+docker-compose logs -f frontend
+```
+
+此时服务通常已在本机监听:
+
+- `127.0.0.1:8888`
+- `127.0.0.1:5000`
+- `127.0.0.1:5432`
+
+## 7. 安装并配置 Nginx
+
+安装:
+
+```bash
+sudo apt update
+sudo apt install -y nginx
+```
+
+推荐站点配置 `/etc/nginx/sites-available/quantdinger.conf`:
+
+```nginx
+server {
+ listen 80;
+ server_name app.example.com;
+
+ client_max_body_size 20m;
+
+ location / {
+ proxy_pass http://127.0.0.1:8888;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+启用站点:
+
+```bash
+sudo ln -s /etc/nginx/sites-available/quantdinger.conf /etc/nginx/sites-enabled/quantdinger.conf
+sudo nginx -t
+sudo systemctl reload nginx
+```
+
+如果启用了防火墙:
+
+```bash
+sudo ufw allow OpenSSH
+sudo ufw allow 'Nginx Full'
+sudo ufw enable
+```
+
+## 8. 配置 HTTPS(Let's Encrypt)
+
+安装 Certbot:
+
+```bash
+sudo apt install -y certbot python3-certbot-nginx
+```
+
+申请证书:
+
+```bash
+sudo certbot --nginx -d app.example.com
+```
+
+自动续期测试:
+
+```bash
+sudo certbot renew --dry-run
+```
+
+完成后访问:
+
+```text
+https://app.example.com
+```
+
+## 9. 推荐生产模式:同域名部署
+
+这是开源版最推荐的方式。
+
+原因:
+
+- 当前开源前端为预编译 `frontend/dist`
+- 前端容器内部已把 `/api/*` 代理到 Docker 网络中的 `backend:5000`
+- 用户只需要维护一个域名和一套 HTTPS
+
+拓扑如下:
+
+```text
+Browser
+ -> https://app.example.com
+ -> Host Nginx :443
+ -> 127.0.0.1:8888 (frontend container)
+ -> /api/* 再由 frontend 容器代理到 backend:5000
+```
+
+## 10. 高级方案:前后端分离 / 双域名
+
+如果你希望:
+
+- 前端域名:`app.example.com`
+- API 域名:`api.example.com`
+
+可以使用双域名方案,但请注意:
+
+1. 需要前端能够把 API 指向 `api.example.com`
+2. 后端需要正确处理跨域
+3. 这种方式更适合有前端源码控制权或二次开发的部署场景
+
+宿主机 Nginx 可拆成两个站点:
+
+- `app.example.com` -> `127.0.0.1:8888`
+- `api.example.com` -> `127.0.0.1:5000`
+
+`api.example.com` 示例:
+
+```nginx
+server {
+ listen 80;
+ server_name api.example.com;
+
+ client_max_body_size 20m;
+
+ location / {
+ proxy_pass http://127.0.0.1:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+如果你只是想“前后端逻辑分离”,但不想处理跨域,建议仍使用同域名:
+
+- `https://app.example.com/`
+- `https://app.example.com/api/`
+
+## 11. 常用运维命令
+
+查看状态:
+
+```bash
+docker-compose ps
+```
+
+查看日志:
+
+```bash
+docker-compose logs -f backend
+docker-compose logs -f frontend
+docker-compose logs -f postgres
+```
+
+更新版本:
+
+```bash
+git pull
+docker-compose up -d --build
+```
+
+重启:
+
+```bash
+docker-compose restart backend
+docker-compose restart frontend
+```
+
+停止:
+
+```bash
+docker-compose down
+```
+
+## 12. 常见问题排查
+
+### 1. 拉镜像失败
+
+现象:
+
+- `failed to resolve source metadata`
+- `registry-1.docker.io`
+- `Docker Desktop has no HTTPS proxy`
+
+处理:
+
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+然后重新:
+
+```bash
+docker-compose up -d --build
+```
+
+### 2. 后端日志出现 `exec /usr/local/bin/docker-entrypoint.sh: no such file or directory`
+
+处理:
+
+```bash
+docker-compose build --no-cache backend
+docker-compose up -d backend
+```
+
+### 3. 前端日志出现 `host not found in upstream "backend"`
+
+通常表示后端没先起来。
+
+处理:
+
+```bash
+docker-compose ps
+docker-compose logs backend --tail=100
+docker-compose restart frontend
+```
+
+### 4. 前端构建报错 `COPY frontend/dist ... not found`
+
+原因通常是 `.dockerignore` 把 `frontend/dist` 排除了,而当前开源版前端正是直接复制这个预编译目录。
+
+检查:
+
+```bash
+cat .dockerignore
+ls frontend/dist
+```
+
+确认 `.dockerignore` 中不要包含:
+
+```text
+frontend/dist
+```
+
+### 5. 后台保存配置时报 `Read-only file system: '/app/.env'`
+
+原因是 `backend_api_python/.env` 被只读挂载到容器内。
+
+请检查 `docker-compose.yml`,确保不要使用:
+
+```yaml
+- ./backend_api_python/.env:/app/.env:ro
+```
+
+而应改为可写挂载:
+
+```yaml
+- ./backend_api_python/.env:/app/.env
+```
+
+然后执行:
+
+```bash
+docker-compose up -d backend
+```
+
+### 6. Docker 内代理不生效
+
+如果宿主机代理监听在本机 `127.0.0.1:10808`,容器内不能直接写 `127.0.0.1`,因为那会指向容器自己。
+
+Docker 部署请改为:
+
+```ini
+PROXY_URL=socks5h://host.docker.internal:10808
+```
+
+### 7. 交易所日志出现 `symbol not found`
+
+如果你已经确认代理和外网访问正常,但日志仍出现某些币对不存在,例如:
+
+```text
+Symbol 'MATIC/USDT' not found on okx
+```
+
+这通常是交易所的符号映射/代币更名问题,不一定是网络故障。
+
+### 8. Nginx 502 / 504
+
+检查:
+
+```bash
+docker-compose ps
+curl http://127.0.0.1:8888/health
+curl http://127.0.0.1:5000/api/health
+sudo nginx -t
+```
+
+### 9. 数据库不应暴露公网
+
+生产环境建议:
+
+```ini
+DB_PORT=127.0.0.1:5432
+```
+
+不要开放:
+
+- `5432`
+- `5000`
+
+公网只开放:
+
+- `80`
+- `443`
+
diff --git a/docs/CLOUD_DEPLOYMENT_EN.md b/docs/CLOUD_DEPLOYMENT_EN.md
new file mode 100644
index 0000000..db16852
--- /dev/null
+++ b/docs/CLOUD_DEPLOYMENT_EN.md
@@ -0,0 +1,450 @@
+# QuantDinger Cloud Server Deployment Guide
+
+This guide covers production-style deployment on a cloud server with Docker Compose, domain setup, HTTPS, reverse proxy, and frontend/backend separation options.
+
+## Recommended Architecture
+
+Recommended setup: single domain + host Nginx reverse proxy
+
+- Public URL: `https://app.example.com`
+- Host Nginx: listens on `80/443`
+- Docker `frontend`: binds to `127.0.0.1:8888`
+- Docker `backend`: binds to `127.0.0.1:5000`
+- Docker `postgres`: binds to `127.0.0.1:5432`
+
+Benefits:
+
+- Only `80/443` are exposed publicly
+- Frontend and API stay on the same origin
+- Backend and database are not directly exposed to the internet
+
+## 1. Prepare the Server
+
+Recommended:
+
+- Ubuntu 22.04 / Debian 12
+- 2 vCPU / 4 GB RAM or higher
+- Security group ports open: `22`, `80`, `443`
+- A domain such as `app.example.com`
+
+DNS steps:
+
+1. Create an `A` record
+2. Host: `app`
+3. Value: your server public IP
+4. Wait for DNS propagation
+
+Verify:
+
+```bash
+ping app.example.com
+```
+
+## 2. Install Docker and Docker Compose
+
+Ubuntu / Debian example:
+
+```bash
+curl -fsSL https://get.docker.com | sh
+sudo usermod -aG docker $USER
+newgrp docker
+docker --version
+docker compose version
+```
+
+If Docker Hub is slow or blocked in your network, you can switch image source later with `IMAGE_PREFIX` in the project-root `.env`.
+
+## 3. Clone the Project
+
+```bash
+git clone https://github.com/brokermr810/QuantDinger.git
+cd QuantDinger
+```
+
+## 4. Configure `backend_api_python/.env`
+
+Copy the template:
+
+```bash
+cp backend_api_python/env.example backend_api_python/.env
+```
+
+Generate and write `SECRET_KEY`:
+
+```bash
+./scripts/generate-secret-key.sh
+```
+
+At minimum, review these values:
+
+```ini
+ADMIN_USER=quantdinger
+ADMIN_PASSWORD=your_strong_password
+SECRET_KEY=your_generated_secret
+```
+
+If you want AI features, add at least one provider key, for example:
+
+```ini
+OPENROUTER_API_KEY=your_key
+```
+
+## 5. Configure the Project-Root `.env`
+
+The project-root `.env` is used by Docker Compose for ports and image source selection.
+
+Copy the template:
+
+```bash
+cp .env.example .env
+```
+
+Recommended production values:
+
+```ini
+FRONTEND_PORT=127.0.0.1:8888
+BACKEND_PORT=127.0.0.1:5000
+DB_PORT=127.0.0.1:5432
+IMAGE_PREFIX=
+```
+
+Explanation:
+
+- `FRONTEND_PORT=127.0.0.1:8888`: only accessible locally, exposed through host Nginx
+- `BACKEND_PORT=127.0.0.1:5000`: avoid exposing API directly
+- `DB_PORT=127.0.0.1:5432`: avoid exposing PostgreSQL directly
+- `IMAGE_PREFIX=`: empty means official Docker Hub
+
+If image pulls fail, try:
+
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+or:
+
+```ini
+IMAGE_PREFIX=docker.xuanyuan.me/library/
+```
+
+## 6. Start the Containers
+
+```bash
+docker-compose up -d --build
+docker-compose ps
+```
+
+Logs:
+
+```bash
+docker-compose logs -f backend
+docker-compose logs -f frontend
+```
+
+At this point, services usually listen on:
+
+- `127.0.0.1:8888`
+- `127.0.0.1:5000`
+- `127.0.0.1:5432`
+
+## 7. Install and Configure Nginx
+
+Install:
+
+```bash
+sudo apt update
+sudo apt install -y nginx
+```
+
+Recommended site config `/etc/nginx/sites-available/quantdinger.conf`:
+
+```nginx
+server {
+ listen 80;
+ server_name app.example.com;
+
+ client_max_body_size 20m;
+
+ location / {
+ proxy_pass http://127.0.0.1:8888;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+Enable it:
+
+```bash
+sudo ln -s /etc/nginx/sites-available/quantdinger.conf /etc/nginx/sites-enabled/quantdinger.conf
+sudo nginx -t
+sudo systemctl reload nginx
+```
+
+If using UFW:
+
+```bash
+sudo ufw allow OpenSSH
+sudo ufw allow 'Nginx Full'
+sudo ufw enable
+```
+
+## 8. Enable HTTPS with Let's Encrypt
+
+Install Certbot:
+
+```bash
+sudo apt install -y certbot python3-certbot-nginx
+```
+
+Request the certificate:
+
+```bash
+sudo certbot --nginx -d app.example.com
+```
+
+Test renewal:
+
+```bash
+sudo certbot renew --dry-run
+```
+
+Then open:
+
+```text
+https://app.example.com
+```
+
+## 9. Recommended Production Mode: Single Domain
+
+This is the recommended mode for the open-source edition.
+
+Why:
+
+- The open-source frontend is shipped as prebuilt `frontend/dist`
+- The frontend container already proxies `/api/*` to `backend:5000` inside Docker
+- Only one public domain and one TLS configuration are needed
+
+Topology:
+
+```text
+Browser
+ -> https://app.example.com
+ -> Host Nginx :443
+ -> 127.0.0.1:8888 (frontend container)
+ -> /api/* then proxied by frontend container to backend:5000
+```
+
+## 10. Advanced Option: Frontend / Backend Separation
+
+If you want:
+
+- frontend: `app.example.com`
+- API: `api.example.com`
+
+you can use a dual-domain setup, but note:
+
+1. the frontend must point API requests to `api.example.com`
+2. backend cross-origin handling must be correct
+3. this is better suited for deployments where you control frontend source/customization
+
+Host Nginx can expose:
+
+- `app.example.com` -> `127.0.0.1:8888`
+- `api.example.com` -> `127.0.0.1:5000`
+
+Example `api.example.com` config:
+
+```nginx
+server {
+ listen 80;
+ server_name api.example.com;
+
+ client_max_body_size 20m;
+
+ location / {
+ proxy_pass http://127.0.0.1:5000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+}
+```
+
+If you only want logical separation without cross-origin complexity, keep:
+
+- `https://app.example.com/`
+- `https://app.example.com/api/`
+
+## 11. Common Operations
+
+Check status:
+
+```bash
+docker-compose ps
+```
+
+View logs:
+
+```bash
+docker-compose logs -f backend
+docker-compose logs -f frontend
+docker-compose logs -f postgres
+```
+
+Update:
+
+```bash
+git pull
+docker-compose up -d --build
+```
+
+Restart:
+
+```bash
+docker-compose restart backend
+docker-compose restart frontend
+```
+
+Stop:
+
+```bash
+docker-compose down
+```
+
+## 12. Troubleshooting
+
+### 1. Image pull failures
+
+Symptoms:
+
+- `failed to resolve source metadata`
+- `registry-1.docker.io`
+- `Docker Desktop has no HTTPS proxy`
+
+Fix:
+
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+Then rerun:
+
+```bash
+docker-compose up -d --build
+```
+
+### 2. Backend logs show `exec /usr/local/bin/docker-entrypoint.sh: no such file or directory`
+
+Fix:
+
+```bash
+docker-compose build --no-cache backend
+docker-compose up -d backend
+```
+
+### 3. Frontend logs show `host not found in upstream "backend"`
+
+This usually means backend failed first.
+
+Fix:
+
+```bash
+docker-compose ps
+docker-compose logs backend --tail=100
+docker-compose restart frontend
+```
+
+### 4. Frontend build fails with `COPY frontend/dist ... not found`
+
+This usually means `.dockerignore` excluded `frontend/dist`, while the current open-source frontend image copies that prebuilt directory directly.
+
+Check:
+
+```bash
+cat .dockerignore
+ls frontend/dist
+```
+
+Make sure `.dockerignore` does NOT contain:
+
+```text
+frontend/dist
+```
+
+### 5. Saving settings fails with `Read-only file system: '/app/.env'`
+
+This means `backend_api_python/.env` is mounted read-only into the container.
+
+In `docker-compose.yml`, avoid:
+
+```yaml
+- ./backend_api_python/.env:/app/.env:ro
+```
+
+Use a writable mount instead:
+
+```yaml
+- ./backend_api_python/.env:/app/.env
+```
+
+Then run:
+
+```bash
+docker-compose up -d backend
+```
+
+### 6. Proxy works on host but not inside Docker
+
+If your proxy listens on host `127.0.0.1:10808`, do not use `127.0.0.1` inside the container, because that points to the container itself.
+
+For Docker deployments, use:
+
+```ini
+PROXY_URL=socks5h://host.docker.internal:10808
+```
+
+### 7. Exchange logs show `symbol not found`
+
+If proxy/network access is already working but some symbols still fail, for example:
+
+```text
+Symbol 'MATIC/USDT' not found on okx
+```
+
+this is usually a market-symbol mapping / token-rename issue on the exchange side, not a general network failure.
+
+### 8. Nginx 502 / 504
+
+Check:
+
+```bash
+docker-compose ps
+curl http://127.0.0.1:8888/health
+curl http://127.0.0.1:5000/api/health
+sudo nginx -t
+```
+
+### 9. PostgreSQL should not be public
+
+Recommended:
+
+```ini
+DB_PORT=127.0.0.1:5432
+```
+
+Do not expose:
+
+- `5432`
+- `5000`
+
+Publicly expose only:
+
+- `80`
+- `443`
+
diff --git a/docs/README_CN.md b/docs/README_CN.md
index aa3cd75..368b646 100644
--- a/docs/README_CN.md
+++ b/docs/README_CN.md
@@ -68,25 +68,40 @@
git clone https://github.com/brokermr810/QuantDinger.git
cd QuantDinger
-# 2. 配置(编辑管理员密码和AI API密钥)
+# 2. 复制后端配置
cp backend_api_python/env.example backend_api_python/.env
-# 3. 重要:生成并设置安全的 SECRET_KEY
-# Linux/Mac:
-python3 -c "import secrets; print(secrets.token_hex(32))" | xargs -I {} sed -i 's|SECRET_KEY=.*|SECRET_KEY={}|' backend_api_python/.env
-
-# 或手动编辑 backend_api_python/.env 并替换 SECRET_KEY 值
-# Windows PowerShell:
-# $key = python -c "import secrets; print(secrets.token_hex(32))"
-# (Get-Content backend_api_python\.env) -replace 'SECRET_KEY=.*', "SECRET_KEY=$key" | Set-Content backend_api_python\.env
+# 3. 生成并写入安全的 SECRET_KEY
+./scripts/generate-secret-key.sh
# 4. 启动!
docker-compose up -d --build
```
+> **Linux/macOS**:
+> - 复制后端配置:
+> `cp backend_api_python/env.example backend_api_python/.env`
+> - 如果需要更多高级配置,直接查看这个文件下半部分的 “Advanced / rarely changed”:
+> `backend_api_python/env.example`
+> - 可选:如果你所在网络拉取 Docker Hub 较慢或不可达,再复制项目根镜像配置:
+> `cp .env.example .env`
+> - 生成并写入 `SECRET_KEY`:
+> `./scripts/generate-secret-key.sh`
+> - 启动:
+> `docker-compose up -d --build`
+
> **Windows PowerShell**:
-> - 复制:`Copy-Item backend_api_python\env.example -Destination backend_api_python\.env`
-> - 生成 SECRET_KEY:`python -c "import secrets; print(secrets.token_hex(32))"` 然后手动编辑 `.env`
+> - 复制后端配置:
+> `Copy-Item backend_api_python\env.example -Destination backend_api_python\.env`
+> - 如果需要更多高级配置,直接查看这个文件下半部分的 “Advanced / rarely changed”:
+> `backend_api_python\env.example`
+> - 可选:如果你所在网络拉取 Docker Hub 较慢或不可达,再复制项目根镜像配置:
+> `Copy-Item .env.example -Destination .env`
+> - 生成并写入 `SECRET_KEY`:
+> `$key = py -c "import secrets; print(secrets.token_hex(32))"`
+> `(Get-Content backend_api_python\.env) -replace '^SECRET_KEY=.*$', "SECRET_KEY=$key" | Set-Content backend_api_python\.env -Encoding UTF8`
+> - 启动:
+> `docker-compose up -d --build`
> **⚠️ 安全提示**:如果 `SECRET_KEY` 使用默认值,容器将**不会启动**。这可以防止不安全的部署。
@@ -159,6 +174,42 @@ FRONTEND_PORT=3000 # 默认:8888
BACKEND_PORT=127.0.0.1:5001 # 默认:5000
```
+**可选镜像源** — 默认直连 Docker Hub。如果你所在地区/网络无法拉取基础镜像,可复制 `.env.example` 为 `.env`,只改一行:
+```ini
+IMAGE_PREFIX=docker.m.daocloud.io/library/
+```
+
+其他常见选择:
+```ini
+IMAGE_PREFIX=
+IMAGE_PREFIX=docker.xuanyuan.me/library/
+```
+
+**Docker 常见问题**
+```text
+1. 镜像源切换由项目根目录 `.env` 控制,不是 `backend_api_python/.env`。
+2. 当前开源仓库已内置 `frontend/dist`,前端部署不再依赖 Node.js。
+3. Windows 下如果后端日志出现:
+ exec /usr/local/bin/docker-entrypoint.sh: no such file or directory
+ 通常是镜像层里的 shell/换行符兼容性问题。可执行:
+ docker-compose build --no-cache backend
+4. 如果前端日志出现:
+ host not found in upstream "backend"
+ 一般是后端先启动失败导致的连锁报错。先修复后端,再执行:
+ docker-compose restart frontend
+5. 如果前端构建报错:
+ COPY frontend/dist ... not found
+ 请检查 `.dockerignore`,确认没有把 `frontend/dist` 排除在 Docker 构建上下文之外。
+6. 如果后台配置页点击保存时报错:
+ Read-only file system: '/app/.env'
+ 请检查 `docker-compose.yml`,确认 `backend_api_python/.env` 不是以只读方式挂载。
+7. Docker 部署下如果需要走本机代理,不要使用 `127.0.0.1`,应改为 `host.docker.internal`。
+ 例如:
+ PROXY_URL=socks5h://host.docker.internal:10808
+8. 如果代理修复后仍出现某些交易对 "not found" 日志,通常是交易所符号映射问题
+ (例如代币更名),不一定是网络故障。
+```
+
---
@@ -340,6 +391,14 @@ flowchart TB
| **外汇** | MetaTrader 5 (MT5)、OANDA | ✅ 通过MT5 |
| **期货** | 交易所API | ⚡ 数据 + 通知 |
+### 交易所返佣入口
+
+如果你本来就准备开通交易所账户,建议直接走返佣链接。很多交易用户本来就会主动找“手续费减免/返佣链接”,而下面这些链接都可以直接享受 20% 手续费减免,不增加任何额外成本。
+
+- **OKX**:[开户即享 20% 手续费减免](https://www.promooboost.com/join/QUANTDINGER)
+- **Bitget**:[开户即享 20% 手续费减免](https://partner.bitget.com/bg/dinger)
+- **Bitget(备用链接)**:[备用 20% 手续费减免注册链接](https://partner.hdmune.cn/bg/7r4xz8kd)
+
---
## 🏗️ 架构与配置
@@ -397,7 +456,8 @@ QuantDinger/
⚙️ 配置参考(.env)
-使用 `backend_api_python/env.example` 作为模板:
+使用 `backend_api_python/env.example` 作为精简模板。
+文件上半部分适合首次部署,下面的 “Advanced / rarely changed” 区域用于可选的高级调优。
| 类别 | 关键变量 |
|----------|-----------|
@@ -408,7 +468,7 @@ QuantDinger/
| **安全** | `TURNSTILE_SITE_KEY`、`ENABLE_REGISTRATION` |
| **会员** | `MEMBERSHIP_MONTHLY_PRICE_USD`、`MEMBERSHIP_MONTHLY_CREDITS` |
| **USDT支付** | `USDT_PAY_ENABLED`、`USDT_TRC20_XPUB`、`TRONGRID_API_KEY` |
-| **代理** | `PROXY_PORT` 或 `PROXY_URL` |
+| **代理** | `PROXY_URL` |
| **工作器** | `ENABLE_PENDING_ORDER_WORKER`、`ENABLE_PORTFOLIO_MONITOR` |
@@ -440,6 +500,7 @@ QuantDinger/
|----------|-------------|
| [更新日志](docs/CHANGELOG.md) | 版本历史和迁移说明 |
| [多用户设置](docs/multi-user-setup.md) | PostgreSQL多用户部署 |
+| [云服务器部署](docs/CLOUD_DEPLOYMENT_CN.md) | 云服务器部署、域名、HTTPS 与反向代理 |
### 策略开发
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index 1a91d54..ea90699 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,31 +1,13 @@
-# QuantDinger Frontend — multi-stage build
+# QuantDinger Frontend — static runtime image
# Build context: repository root (see docker-compose.yml)
-#
-# Stage 1: compile Vue app from QuantDinger-Vue-src
-# Stage 2: nginx static + API reverse proxy (nginx.conf)
+# The open-source repo ships prebuilt static assets in `frontend/dist`,
+# so no Node.js build stage is required.
+ARG RUNTIME_IMAGE=nginx:1.25-alpine
+FROM ${RUNTIME_IMAGE}
-FROM node:18-alpine AS builder
-
-WORKDIR /app
-
-# Layer cache: deps only
-COPY QuantDinger-Vue-src/package.json QuantDinger-Vue-src/package-lock.json ./
-RUN npm ci
-
-COPY QuantDinger-Vue-src/ ./
-
-# Match local production build (`npm run build` in QuantDinger-Vue-src)
-ENV NODE_OPTIONS=--max_old_space_size=4096
-RUN npm run build
-
-# --- runtime ---
-
-FROM nginx:1.25-alpine
-
-# curl: docker-compose healthcheck
RUN apk add --no-cache curl
-COPY --from=builder /app/dist /usr/share/nginx/html/
+COPY frontend/dist/ /usr/share/nginx/html/
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
diff --git a/frontend/dist/css/268.37fb7134.css b/frontend/dist/css/125.37fb7134.css
similarity index 100%
rename from frontend/dist/css/268.37fb7134.css
rename to frontend/dist/css/125.37fb7134.css
diff --git a/frontend/dist/css/158.e48d5d81.css b/frontend/dist/css/158.e48d5d81.css
new file mode 100644
index 0000000..fdd2b37
--- /dev/null
+++ b/frontend/dist/css/158.e48d5d81.css
@@ -0,0 +1 @@
+.profile-page[data-v-86a53e9c]{padding:24px;min-height:calc(100vh - 120px);background:-webkit-gradient(linear,left top,left bottom,from(#f8fafc),to(#f1f5f9));background:linear-gradient(180deg,#f8fafc,#f1f5f9)}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:24px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:24px;font-weight:700;margin:0 0 8px 0;color:#1e3a5f;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:12px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:28px;color:#1890ff}.profile-page .page-header .page-desc[data-v-86a53e9c]{color:#64748b;font-size:14px;margin:0}.profile-page .profile-cards-row[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-86a53e9c]{height:100%}.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c] .ant-card-body,.profile-page .profile-cards-row .right-cards-col[data-v-86a53e9c] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row[data-v-86a53e9c]{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-86a53e9c]{height:100%}.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-86a53e9c] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);text-align:center}.profile-page .profile-card .avatar-section[data-v-86a53e9c]{padding:20px 0}.profile-page .profile-card .avatar-section .ant-avatar[data-v-86a53e9c]{-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}.profile-page .profile-card .avatar-section .username[data-v-86a53e9c]{margin:16px 0 8px;font-size:20px;font-weight:600;color:#1e3a5f}.profile-page .profile-card .avatar-section .user-role[data-v-86a53e9c]{margin:0}.profile-page .profile-card .profile-info[data-v-86a53e9c]{text-align:left;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:12px 0;border-bottom:1px solid #f0f0f0}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]:last-child{border-bottom:none}.profile-page .profile-card .profile-info .info-item .anticon[data-v-86a53e9c]{font-size:16px;color:#1890ff;margin-right:12px}.profile-page .profile-card .profile-info .info-item .label[data-v-86a53e9c]{color:#64748b;margin-right:8px}.profile-page .profile-card .profile-info .info-item .value[data-v-86a53e9c]{color:#1e3a5f;font-weight:500}.profile-page .edit-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08)}.profile-page .edit-card .password-form[data-v-86a53e9c],.profile-page .edit-card .profile-form[data-v-86a53e9c]{max-width:500px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input-password,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input-password{border-radius:8px}.profile-page .edit-card .password-form .email-hint[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint[data-v-86a53e9c]{margin-top:8px;font-size:12px;color:rgba(0,0,0,.45)}.profile-page .edit-card .password-form .email-hint.email-warning[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint.email-warning[data-v-86a53e9c]{color:#faad14}.profile-page .edit-card .amount-positive[data-v-86a53e9c]{color:#52c41a;font-weight:600}.profile-page .edit-card .amount-negative[data-v-86a53e9c]{color:#ff4d4f;font-weight:600}.profile-page .edit-card .notification-settings-form .field-hint[data-v-86a53e9c]{margin-top:6px;font-size:12px;color:rgba(0,0,0,.45);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px}.profile-page .edit-card .notification-settings-form .field-hint .anticon[data-v-86a53e9c]{font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group{width:100%}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper{margin-bottom:8px}.profile-page .credits-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.profile-page .credits-card[data-v-86a53e9c] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .credits-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .credits-card .credits-header .credits-title[data-v-86a53e9c]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-86a53e9c]{font-size:18px}.profile-page .credits-card .credits-body[data-v-86a53e9c]{padding:20px 0;text-align:center;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:42px;font-weight:700;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.2)}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:16px;color:hsla(0,0%,100%,.9);margin-left:8px}.profile-page .credits-card .credits-body .vip-status[data-v-86a53e9c]{margin-top:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px;font-size:13px}.profile-page .credits-card .credits-body .vip-status .vip-active[data-v-86a53e9c]{color:gold}.profile-page .credits-card .credits-body .vip-status .vip-expired[data-v-86a53e9c]{color:hsla(0,0%,100%,.6)}.profile-page .credits-card .credits-body .vip-status .no-vip[data-v-86a53e9c]{color:hsla(0,0%,100%,.7)}.profile-page .credits-card .credits-actions[data-v-86a53e9c]{text-align:center;margin-top:auto}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{border-radius:20px;padding:0 24px;height:36px;font-weight:500;background:#fff;color:#667eea;border:none}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]:hover{background:hsla(0,0%,100%,.9);color:#764ba2}.profile-page .credits-card .credits-hint[data-v-86a53e9c]{margin-top:12px;text-align:center;font-size:12px;color:hsla(0,0%,100%,.7);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#11998e,#38ef7d);color:#fff}.profile-page .referral-card[data-v-86a53e9c] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .referral-card .referral-header .referral-title[data-v-86a53e9c]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-86a53e9c]{font-size:18px}.profile-page .referral-card .referral-body[data-v-86a53e9c]{padding:12px 0;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.profile-page .referral-card .referral-body .referral-stats[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .referral-card .referral-body .referral-stats .stat-item[data-v-86a53e9c]{text-align:center}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:28px;font-weight:700;color:#fff;display:block}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-86a53e9c]{font-size:12px;color:hsla(0,0%,100%,.8)}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-86a53e9c]{font-size:12px;color:hsla(0,0%,100%,.9);margin-bottom:6px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input{background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.3);color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-webkit-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-moz-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input:-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .anticon-copy{color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .anticon-copy:hover{color:gold}.profile-page .referral-card .referral-body .referral-hint[data-v-86a53e9c]{margin-top:auto;text-align:center;font-size:12px;color:hsla(0,0%,100%,.85);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card .referral-body .referral-hint .anticon-gift[data-v-86a53e9c]{color:gold}.profile-page .referral-user-cell[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:10px}.profile-page .referral-user-cell .user-info[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-user-cell .user-info .nickname[data-v-86a53e9c]{font-weight:500;color:#333}.profile-page .referral-user-cell .user-info .username[data-v-86a53e9c]{font-size:12px;color:#999}.profile-page .exchange-config-section .credential-hint[data-v-86a53e9c]{font-family:SFMono-Regular,Consolas,monospace;font-size:13px;color:#666}.profile-page .test-result-msg[data-v-86a53e9c]{margin-top:8px;font-size:13px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:6px}.profile-page .test-result-msg.success[data-v-86a53e9c]{color:#52c41a}.profile-page .test-result-msg.error[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark[data-v-86a53e9c]{background:-webkit-gradient(linear,left top,left bottom,from(#0d1117),to(#161b22));background:linear-gradient(180deg,#0d1117,#161b22)}.profile-page.theme-dark .page-header .page-title[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .page-header .page-desc[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-86a53e9c],.profile-page.theme-dark .profile-card[data-v-86a53e9c]{background:#1e222d;-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-card-body,.profile-page.theme-dark .profile-card[data-v-86a53e9c] .ant-card-body{background:#1e222d}.profile-page.theme-dark .profile-card .avatar-section .username[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .profile-card .profile-info .info-item[data-v-86a53e9c]{border-bottom-color:#30363d}.profile-page.theme-dark .profile-card .profile-info .info-item .label[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .profile-card .profile-info .info-item .value[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-bar{border-bottom-color:#30363d}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab:hover{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab-active{color:#1890ff}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-form-item-label label{color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:hover{border-color:#1890ff}.profile-page.theme-dark .credits-card[data-v-86a53e9c]{background:linear-gradient(135deg,#1a1a2e,#16213e 50%,#0f3460);-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .credits-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.1)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{background:hsla(0,0%,100%,.15);color:#fff;border:1px solid hsla(0,0%,100%,.2)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-86a53e9c]:hover{background:hsla(0,0%,100%,.25)}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table{background:#1e222d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-thead>tr>th{background:#2a2e39;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td{background:#1e222d;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr:hover>td{background:#2a2e39}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-placeholder{background:#1e222d;color:#8b949e}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td,.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td :not(.ant-tag):not(.ant-btn),.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td div,.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td span{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .amount-positive{color:#52c41a}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .amount-negative{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-label>label,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-label>label,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-label>label{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-extra,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-extra,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-extra{color:#8b949e}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selection-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selection-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selection-placeholder{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}.profile-page.theme-dark .notification-settings-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .notification-settings-form .field-hint[data-v-86a53e9c],.profile-page.theme-dark .password-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .password-form .field-hint[data-v-86a53e9c],.profile-page.theme-dark .profile-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .profile-form .field-hint[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .notification-settings-form .email-warning[data-v-86a53e9c],.profile-page.theme-dark .password-form .email-warning[data-v-86a53e9c],.profile-page.theme-dark .profile-form .email-warning[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert .ant-alert-message{color:#c9d1d9}.profile-page.theme-dark .exchange-config-section .credential-hint[data-v-86a53e9c],.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert .ant-alert-description{color:#8b949e}.profile-page.theme-dark .test-result-msg.success[data-v-86a53e9c]{color:#52c41a}.profile-page.theme-dark .test-result-msg.error[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark .referral-user-cell .user-info .nickname[data-v-86a53e9c]{color:#c9d1d9}.profile-page.theme-dark .referral-user-cell .user-info .username[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark[data-v-86a53e9c] .ant-tag{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item{background:#1e222d;border-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item a{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item-active a{color:#fff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-next .ant-pagination-item-link,.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-prev .ant-pagination-item-link{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-options .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-btn.ant-btn-default{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}@media screen and (max-width:768px){.profile-page[data-v-86a53e9c]{padding:12px}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:16px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:20px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:22px}.profile-page .page-header .page-desc[data-v-86a53e9c]{font-size:13px}.profile-page .profile-cards-row[data-v-86a53e9c]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c]{margin-bottom:12px}.profile-page .profile-cards-row .right-cards-col .right-cards-row[data-v-86a53e9c]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-col .right-cards-row .ant-col[data-v-86a53e9c]{margin-bottom:12px}.profile-page .profile-card[data-v-86a53e9c]{border-radius:10px}.profile-page .profile-card .avatar-section[data-v-86a53e9c]{padding:16px 0}.profile-page .profile-card .avatar-section[data-v-86a53e9c] .ant-avatar{width:80px!important;height:80px!important;line-height:80px!important}.profile-page .profile-card .avatar-section .username[data-v-86a53e9c]{font-size:18px;margin:12px 0 6px}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]{padding:10px 0;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .profile-card .profile-info .info-item .anticon[data-v-86a53e9c]{font-size:14px;margin-right:8px}.profile-page .profile-card .profile-info .info-item .label[data-v-86a53e9c]{font-size:13px}.profile-page .profile-card .profile-info .info-item .value[data-v-86a53e9c]{font-size:13px;word-break:break-all}.profile-page .credits-card[data-v-86a53e9c]{border-radius:10px}.profile-page .credits-card .credits-header .credits-title[data-v-86a53e9c]{font-size:15px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-86a53e9c]{font-size:16px}.profile-page .credits-card .credits-body[data-v-86a53e9c]{padding:16px 0}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:32px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:14px}.profile-page .credits-card .credits-body .vip-status[data-v-86a53e9c]{font-size:12px;margin-top:10px}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{height:34px;padding:0 20px;font-size:14px}.profile-page .credits-card .credits-hint[data-v-86a53e9c]{font-size:11px;margin-top:10px}.profile-page .referral-card[data-v-86a53e9c]{border-radius:10px}.profile-page .referral-card .referral-header .referral-title[data-v-86a53e9c]{font-size:15px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-86a53e9c]{font-size:16px}.profile-page .referral-card .referral-body[data-v-86a53e9c]{padding:10px 0}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:24px}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-86a53e9c],.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-86a53e9c]{font-size:11px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input{font-size:12px}.profile-page .referral-card .referral-body .referral-hint[data-v-86a53e9c]{font-size:11px;padding-top:8px}.profile-page .edit-card[data-v-86a53e9c]{border-radius:10px;margin-top:12px!important}.profile-page .edit-card[data-v-86a53e9c] .ant-card-body{padding:12px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav .ant-tabs-tab{padding:10px 12px;font-size:13px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav-scroll::-webkit-scrollbar{display:none}.profile-page .edit-card .password-form[data-v-86a53e9c],.profile-page .edit-card .profile-form[data-v-86a53e9c]{max-width:100%}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-form-item-label,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-form-item-label{padding-bottom:4px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-form-item-label label,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-form-item-label label{font-size:13px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input-password,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input-password{font-size:14px}.profile-page .edit-card .password-form .email-hint[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint[data-v-86a53e9c]{font-size:11px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-alert{font-size:12px;padding:8px 12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-alert{font-size:12px;padding:8px 12px;margin-bottom:16px!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form{max-width:100%}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row{margin-left:0!important;margin-right:0!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row .ant-col{padding-left:0!important;padding-right:8px!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-checkbox-wrapper{font-size:13px;margin-bottom:6px}.profile-page .edit-card .notification-settings-form .field-hint[data-v-86a53e9c]{font-size:11px;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item{margin-bottom:16px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item:last-child .ant-btn{width:100%;margin-bottom:8px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item:last-child .ant-btn+.ant-btn{margin-left:0!important}.profile-page .edit-card[data-v-86a53e9c] .ant-table-wrapper{overflow-x:auto}.profile-page .edit-card[data-v-86a53e9c] .ant-table-wrapper .ant-table{min-width:500px}.profile-page .referral-user-cell[data-v-86a53e9c]{gap:8px}.profile-page .referral-user-cell[data-v-86a53e9c] .ant-avatar{width:28px!important;height:28px!important;line-height:28px!important}.profile-page .referral-user-cell .user-info .nickname[data-v-86a53e9c]{font-size:13px}.profile-page .referral-user-cell .user-info .username[data-v-86a53e9c]{font-size:11px}}@media screen and (max-width:480px){.profile-page[data-v-86a53e9c]{padding:8px}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:12px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:18px;gap:8px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:20px}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:28px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:13px}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{width:100%}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:20px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav .ant-tabs-tab{padding:8px 10px;font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row .ant-col{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}}
\ No newline at end of file
diff --git a/frontend/dist/css/484.c3803b75.css b/frontend/dist/css/185.c3803b75.css
similarity index 100%
rename from frontend/dist/css/484.c3803b75.css
rename to frontend/dist/css/185.c3803b75.css
diff --git a/frontend/dist/css/261.2ed32d66.css b/frontend/dist/css/261.2ed32d66.css
deleted file mode 100644
index f835d1c..0000000
--- a/frontend/dist/css/261.2ed32d66.css
+++ /dev/null
@@ -1 +0,0 @@
-.profile-page[data-v-2b760d50]{padding:24px;min-height:calc(100vh - 120px);background:-webkit-gradient(linear,left top,left bottom,from(#f8fafc),to(#f1f5f9));background:linear-gradient(180deg,#f8fafc,#f1f5f9)}.profile-page .page-header[data-v-2b760d50]{margin-bottom:24px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:24px;font-weight:700;margin:0 0 8px 0;color:#1e3a5f;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:12px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:28px;color:#1890ff}.profile-page .page-header .page-desc[data-v-2b760d50]{color:#64748b;font-size:14px;margin:0}.profile-page .profile-cards-row[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-2b760d50]{height:100%}.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50] .ant-card-body,.profile-page .profile-cards-row .right-cards-col[data-v-2b760d50] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row[data-v-2b760d50]{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-2b760d50]{height:100%}.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-2b760d50] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);text-align:center}.profile-page .profile-card .avatar-section[data-v-2b760d50]{padding:20px 0}.profile-page .profile-card .avatar-section .ant-avatar[data-v-2b760d50]{-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}.profile-page .profile-card .avatar-section .username[data-v-2b760d50]{margin:16px 0 8px;font-size:20px;font-weight:600;color:#1e3a5f}.profile-page .profile-card .avatar-section .user-role[data-v-2b760d50]{margin:0}.profile-page .profile-card .profile-info[data-v-2b760d50]{text-align:left;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:12px 0;border-bottom:1px solid #f0f0f0}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]:last-child{border-bottom:none}.profile-page .profile-card .profile-info .info-item .anticon[data-v-2b760d50]{font-size:16px;color:#1890ff;margin-right:12px}.profile-page .profile-card .profile-info .info-item .label[data-v-2b760d50]{color:#64748b;margin-right:8px}.profile-page .profile-card .profile-info .info-item .value[data-v-2b760d50]{color:#1e3a5f;font-weight:500}.profile-page .edit-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08)}.profile-page .edit-card .password-form[data-v-2b760d50],.profile-page .edit-card .profile-form[data-v-2b760d50]{max-width:500px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input-password,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input-password{border-radius:8px}.profile-page .edit-card .password-form .email-hint[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint[data-v-2b760d50]{margin-top:8px;font-size:12px;color:rgba(0,0,0,.45)}.profile-page .edit-card .password-form .email-hint.email-warning[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint.email-warning[data-v-2b760d50]{color:#faad14}.profile-page .edit-card .amount-positive[data-v-2b760d50]{color:#52c41a;font-weight:600}.profile-page .edit-card .amount-negative[data-v-2b760d50]{color:#ff4d4f;font-weight:600}.profile-page .edit-card .notification-settings-form .field-hint[data-v-2b760d50]{margin-top:6px;font-size:12px;color:rgba(0,0,0,.45);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px}.profile-page .edit-card .notification-settings-form .field-hint .anticon[data-v-2b760d50]{font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group{width:100%}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper{margin-bottom:8px}.profile-page .credits-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.profile-page .credits-card[data-v-2b760d50] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .credits-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .credits-card .credits-header .credits-title[data-v-2b760d50]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-2b760d50]{font-size:18px}.profile-page .credits-card .credits-body[data-v-2b760d50]{padding:20px 0;text-align:center;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:42px;font-weight:700;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.2)}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:16px;color:hsla(0,0%,100%,.9);margin-left:8px}.profile-page .credits-card .credits-body .vip-status[data-v-2b760d50]{margin-top:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px;font-size:13px}.profile-page .credits-card .credits-body .vip-status .vip-active[data-v-2b760d50]{color:gold}.profile-page .credits-card .credits-body .vip-status .vip-expired[data-v-2b760d50]{color:hsla(0,0%,100%,.6)}.profile-page .credits-card .credits-body .vip-status .no-vip[data-v-2b760d50]{color:hsla(0,0%,100%,.7)}.profile-page .credits-card .credits-actions[data-v-2b760d50]{text-align:center;margin-top:auto}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{border-radius:20px;padding:0 24px;height:36px;font-weight:500;background:#fff;color:#667eea;border:none}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]:hover{background:hsla(0,0%,100%,.9);color:#764ba2}.profile-page .credits-card .credits-hint[data-v-2b760d50]{margin-top:12px;text-align:center;font-size:12px;color:hsla(0,0%,100%,.7);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#11998e,#38ef7d);color:#fff}.profile-page .referral-card[data-v-2b760d50] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .referral-card .referral-header .referral-title[data-v-2b760d50]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-2b760d50]{font-size:18px}.profile-page .referral-card .referral-body[data-v-2b760d50]{padding:12px 0;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.profile-page .referral-card .referral-body .referral-stats[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .referral-card .referral-body .referral-stats .stat-item[data-v-2b760d50]{text-align:center}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:28px;font-weight:700;color:#fff;display:block}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-2b760d50]{font-size:12px;color:hsla(0,0%,100%,.8)}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-2b760d50]{font-size:12px;color:hsla(0,0%,100%,.9);margin-bottom:6px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input{background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.3);color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-webkit-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-moz-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input:-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .anticon-copy{color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .anticon-copy:hover{color:gold}.profile-page .referral-card .referral-body .referral-hint[data-v-2b760d50]{margin-top:auto;text-align:center;font-size:12px;color:hsla(0,0%,100%,.85);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card .referral-body .referral-hint .anticon-gift[data-v-2b760d50]{color:gold}.profile-page .referral-user-cell[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:10px}.profile-page .referral-user-cell .user-info[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-user-cell .user-info .nickname[data-v-2b760d50]{font-weight:500;color:#333}.profile-page .referral-user-cell .user-info .username[data-v-2b760d50]{font-size:12px;color:#999}.profile-page .exchange-config-section .credential-hint[data-v-2b760d50]{font-family:SFMono-Regular,Consolas,monospace;font-size:13px;color:#666}.profile-page .test-result-msg[data-v-2b760d50]{margin-top:8px;font-size:13px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:6px}.profile-page .test-result-msg.success[data-v-2b760d50]{color:#52c41a}.profile-page .test-result-msg.error[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark[data-v-2b760d50]{background:-webkit-gradient(linear,left top,left bottom,from(#0d1117),to(#161b22));background:linear-gradient(180deg,#0d1117,#161b22)}.profile-page.theme-dark .page-header .page-title[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .page-header .page-desc[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-2b760d50],.profile-page.theme-dark .profile-card[data-v-2b760d50]{background:#1e222d;-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-card-body,.profile-page.theme-dark .profile-card[data-v-2b760d50] .ant-card-body{background:#1e222d}.profile-page.theme-dark .profile-card .avatar-section .username[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .profile-card .profile-info .info-item[data-v-2b760d50]{border-bottom-color:#30363d}.profile-page.theme-dark .profile-card .profile-info .info-item .label[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .profile-card .profile-info .info-item .value[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-bar{border-bottom-color:#30363d}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab:hover{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab-active{color:#1890ff}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-form-item-label label{color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:hover{border-color:#1890ff}.profile-page.theme-dark .credits-card[data-v-2b760d50]{background:linear-gradient(135deg,#1a1a2e,#16213e 50%,#0f3460);-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .credits-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.1)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-2b760d50]{background:hsla(0,0%,100%,.15);color:#fff;border:1px solid hsla(0,0%,100%,.2)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-2b760d50]:hover{background:hsla(0,0%,100%,.25)}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table{background:#1e222d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-thead>tr>th{background:#2a2e39;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td{background:#1e222d;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr:hover>td{background:#2a2e39}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-placeholder{background:#1e222d;color:#8b949e}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td,.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td :not(.ant-tag):not(.ant-btn),.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td div,.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td span{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .amount-positive{color:#52c41a}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .amount-negative{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-label>label,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-label>label,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-label>label{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-extra,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-extra,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-extra{color:#8b949e}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selection-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selection-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selection-placeholder{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}.profile-page.theme-dark .notification-settings-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .notification-settings-form .field-hint[data-v-2b760d50],.profile-page.theme-dark .password-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .password-form .field-hint[data-v-2b760d50],.profile-page.theme-dark .profile-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .profile-form .field-hint[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .notification-settings-form .email-warning[data-v-2b760d50],.profile-page.theme-dark .password-form .email-warning[data-v-2b760d50],.profile-page.theme-dark .profile-form .email-warning[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert .ant-alert-message{color:#c9d1d9}.profile-page.theme-dark .exchange-config-section .credential-hint[data-v-2b760d50],.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert .ant-alert-description{color:#8b949e}.profile-page.theme-dark .test-result-msg.success[data-v-2b760d50]{color:#52c41a}.profile-page.theme-dark .test-result-msg.error[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark .referral-user-cell .user-info .nickname[data-v-2b760d50]{color:#c9d1d9}.profile-page.theme-dark .referral-user-cell .user-info .username[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark[data-v-2b760d50] .ant-tag{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item{background:#1e222d;border-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item a{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item-active a{color:#fff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-next .ant-pagination-item-link,.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-prev .ant-pagination-item-link{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-options .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-btn.ant-btn-default{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}@media screen and (max-width:768px){.profile-page[data-v-2b760d50]{padding:12px}.profile-page .page-header[data-v-2b760d50]{margin-bottom:16px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:20px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:22px}.profile-page .page-header .page-desc[data-v-2b760d50]{font-size:13px}.profile-page .profile-cards-row[data-v-2b760d50]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50]{margin-bottom:12px}.profile-page .profile-cards-row .right-cards-col .right-cards-row[data-v-2b760d50]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-col .right-cards-row .ant-col[data-v-2b760d50]{margin-bottom:12px}.profile-page .profile-card[data-v-2b760d50]{border-radius:10px}.profile-page .profile-card .avatar-section[data-v-2b760d50]{padding:16px 0}.profile-page .profile-card .avatar-section[data-v-2b760d50] .ant-avatar{width:80px!important;height:80px!important;line-height:80px!important}.profile-page .profile-card .avatar-section .username[data-v-2b760d50]{font-size:18px;margin:12px 0 6px}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]{padding:10px 0;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .profile-card .profile-info .info-item .anticon[data-v-2b760d50]{font-size:14px;margin-right:8px}.profile-page .profile-card .profile-info .info-item .label[data-v-2b760d50]{font-size:13px}.profile-page .profile-card .profile-info .info-item .value[data-v-2b760d50]{font-size:13px;word-break:break-all}.profile-page .credits-card[data-v-2b760d50]{border-radius:10px}.profile-page .credits-card .credits-header .credits-title[data-v-2b760d50]{font-size:15px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-2b760d50]{font-size:16px}.profile-page .credits-card .credits-body[data-v-2b760d50]{padding:16px 0}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:32px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:14px}.profile-page .credits-card .credits-body .vip-status[data-v-2b760d50]{font-size:12px;margin-top:10px}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{height:34px;padding:0 20px;font-size:14px}.profile-page .credits-card .credits-hint[data-v-2b760d50]{font-size:11px;margin-top:10px}.profile-page .referral-card[data-v-2b760d50]{border-radius:10px}.profile-page .referral-card .referral-header .referral-title[data-v-2b760d50]{font-size:15px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-2b760d50]{font-size:16px}.profile-page .referral-card .referral-body[data-v-2b760d50]{padding:10px 0}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:24px}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-2b760d50],.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-2b760d50]{font-size:11px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input{font-size:12px}.profile-page .referral-card .referral-body .referral-hint[data-v-2b760d50]{font-size:11px;padding-top:8px}.profile-page .edit-card[data-v-2b760d50]{border-radius:10px;margin-top:12px!important}.profile-page .edit-card[data-v-2b760d50] .ant-card-body{padding:12px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav .ant-tabs-tab{padding:10px 12px;font-size:13px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav-scroll::-webkit-scrollbar{display:none}.profile-page .edit-card .password-form[data-v-2b760d50],.profile-page .edit-card .profile-form[data-v-2b760d50]{max-width:100%}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-form-item-label,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-form-item-label{padding-bottom:4px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-form-item-label label,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-form-item-label label{font-size:13px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input-password,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input-password{font-size:14px}.profile-page .edit-card .password-form .email-hint[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint[data-v-2b760d50]{font-size:11px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-alert{font-size:12px;padding:8px 12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-alert{font-size:12px;padding:8px 12px;margin-bottom:16px!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form{max-width:100%}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row{margin-left:0!important;margin-right:0!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row .ant-col{padding-left:0!important;padding-right:8px!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-checkbox-wrapper{font-size:13px;margin-bottom:6px}.profile-page .edit-card .notification-settings-form .field-hint[data-v-2b760d50]{font-size:11px;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item{margin-bottom:16px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item:last-child .ant-btn{width:100%;margin-bottom:8px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item:last-child .ant-btn+.ant-btn{margin-left:0!important}.profile-page .edit-card[data-v-2b760d50] .ant-table-wrapper{overflow-x:auto}.profile-page .edit-card[data-v-2b760d50] .ant-table-wrapper .ant-table{min-width:500px}.profile-page .referral-user-cell[data-v-2b760d50]{gap:8px}.profile-page .referral-user-cell[data-v-2b760d50] .ant-avatar{width:28px!important;height:28px!important;line-height:28px!important}.profile-page .referral-user-cell .user-info .nickname[data-v-2b760d50]{font-size:13px}.profile-page .referral-user-cell .user-info .username[data-v-2b760d50]{font-size:11px}}@media screen and (max-width:480px){.profile-page[data-v-2b760d50]{padding:8px}.profile-page .page-header[data-v-2b760d50]{margin-bottom:12px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:18px;gap:8px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:20px}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:28px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:13px}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{width:100%}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:20px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav .ant-tabs-tab{padding:8px 10px;font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row .ant-col{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}}
\ No newline at end of file
diff --git a/frontend/dist/css/271.2ed32d66.css b/frontend/dist/css/271.2ed32d66.css
deleted file mode 100644
index f835d1c..0000000
--- a/frontend/dist/css/271.2ed32d66.css
+++ /dev/null
@@ -1 +0,0 @@
-.profile-page[data-v-2b760d50]{padding:24px;min-height:calc(100vh - 120px);background:-webkit-gradient(linear,left top,left bottom,from(#f8fafc),to(#f1f5f9));background:linear-gradient(180deg,#f8fafc,#f1f5f9)}.profile-page .page-header[data-v-2b760d50]{margin-bottom:24px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:24px;font-weight:700;margin:0 0 8px 0;color:#1e3a5f;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:12px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:28px;color:#1890ff}.profile-page .page-header .page-desc[data-v-2b760d50]{color:#64748b;font-size:14px;margin:0}.profile-page .profile-cards-row[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-2b760d50]{height:100%}.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50] .ant-card-body,.profile-page .profile-cards-row .right-cards-col[data-v-2b760d50] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row[data-v-2b760d50]{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-2b760d50],.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-2b760d50]{height:100%}.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-2b760d50] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);text-align:center}.profile-page .profile-card .avatar-section[data-v-2b760d50]{padding:20px 0}.profile-page .profile-card .avatar-section .ant-avatar[data-v-2b760d50]{-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}.profile-page .profile-card .avatar-section .username[data-v-2b760d50]{margin:16px 0 8px;font-size:20px;font-weight:600;color:#1e3a5f}.profile-page .profile-card .avatar-section .user-role[data-v-2b760d50]{margin:0}.profile-page .profile-card .profile-info[data-v-2b760d50]{text-align:left;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:12px 0;border-bottom:1px solid #f0f0f0}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]:last-child{border-bottom:none}.profile-page .profile-card .profile-info .info-item .anticon[data-v-2b760d50]{font-size:16px;color:#1890ff;margin-right:12px}.profile-page .profile-card .profile-info .info-item .label[data-v-2b760d50]{color:#64748b;margin-right:8px}.profile-page .profile-card .profile-info .info-item .value[data-v-2b760d50]{color:#1e3a5f;font-weight:500}.profile-page .edit-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08)}.profile-page .edit-card .password-form[data-v-2b760d50],.profile-page .edit-card .profile-form[data-v-2b760d50]{max-width:500px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input-password,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input-password{border-radius:8px}.profile-page .edit-card .password-form .email-hint[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint[data-v-2b760d50]{margin-top:8px;font-size:12px;color:rgba(0,0,0,.45)}.profile-page .edit-card .password-form .email-hint.email-warning[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint.email-warning[data-v-2b760d50]{color:#faad14}.profile-page .edit-card .amount-positive[data-v-2b760d50]{color:#52c41a;font-weight:600}.profile-page .edit-card .amount-negative[data-v-2b760d50]{color:#ff4d4f;font-weight:600}.profile-page .edit-card .notification-settings-form .field-hint[data-v-2b760d50]{margin-top:6px;font-size:12px;color:rgba(0,0,0,.45);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px}.profile-page .edit-card .notification-settings-form .field-hint .anticon[data-v-2b760d50]{font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group{width:100%}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper{margin-bottom:8px}.profile-page .credits-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.profile-page .credits-card[data-v-2b760d50] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .credits-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .credits-card .credits-header .credits-title[data-v-2b760d50]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-2b760d50]{font-size:18px}.profile-page .credits-card .credits-body[data-v-2b760d50]{padding:20px 0;text-align:center;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:42px;font-weight:700;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.2)}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:16px;color:hsla(0,0%,100%,.9);margin-left:8px}.profile-page .credits-card .credits-body .vip-status[data-v-2b760d50]{margin-top:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px;font-size:13px}.profile-page .credits-card .credits-body .vip-status .vip-active[data-v-2b760d50]{color:gold}.profile-page .credits-card .credits-body .vip-status .vip-expired[data-v-2b760d50]{color:hsla(0,0%,100%,.6)}.profile-page .credits-card .credits-body .vip-status .no-vip[data-v-2b760d50]{color:hsla(0,0%,100%,.7)}.profile-page .credits-card .credits-actions[data-v-2b760d50]{text-align:center;margin-top:auto}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{border-radius:20px;padding:0 24px;height:36px;font-weight:500;background:#fff;color:#667eea;border:none}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]:hover{background:hsla(0,0%,100%,.9);color:#764ba2}.profile-page .credits-card .credits-hint[data-v-2b760d50]{margin-top:12px;text-align:center;font-size:12px;color:hsla(0,0%,100%,.7);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card[data-v-2b760d50]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#11998e,#38ef7d);color:#fff}.profile-page .referral-card[data-v-2b760d50] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .referral-card .referral-header .referral-title[data-v-2b760d50]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-2b760d50]{font-size:18px}.profile-page .referral-card .referral-body[data-v-2b760d50]{padding:12px 0;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.profile-page .referral-card .referral-body .referral-stats[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .referral-card .referral-body .referral-stats .stat-item[data-v-2b760d50]{text-align:center}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:28px;font-weight:700;color:#fff;display:block}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-2b760d50]{font-size:12px;color:hsla(0,0%,100%,.8)}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-2b760d50]{font-size:12px;color:hsla(0,0%,100%,.9);margin-bottom:6px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input{background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.3);color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-webkit-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-moz-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input:-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input::placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .anticon-copy{color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .anticon-copy:hover{color:gold}.profile-page .referral-card .referral-body .referral-hint[data-v-2b760d50]{margin-top:auto;text-align:center;font-size:12px;color:hsla(0,0%,100%,.85);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card .referral-body .referral-hint .anticon-gift[data-v-2b760d50]{color:gold}.profile-page .referral-user-cell[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:10px}.profile-page .referral-user-cell .user-info[data-v-2b760d50]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-user-cell .user-info .nickname[data-v-2b760d50]{font-weight:500;color:#333}.profile-page .referral-user-cell .user-info .username[data-v-2b760d50]{font-size:12px;color:#999}.profile-page .exchange-config-section .credential-hint[data-v-2b760d50]{font-family:SFMono-Regular,Consolas,monospace;font-size:13px;color:#666}.profile-page .test-result-msg[data-v-2b760d50]{margin-top:8px;font-size:13px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:6px}.profile-page .test-result-msg.success[data-v-2b760d50]{color:#52c41a}.profile-page .test-result-msg.error[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark[data-v-2b760d50]{background:-webkit-gradient(linear,left top,left bottom,from(#0d1117),to(#161b22));background:linear-gradient(180deg,#0d1117,#161b22)}.profile-page.theme-dark .page-header .page-title[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .page-header .page-desc[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-2b760d50],.profile-page.theme-dark .profile-card[data-v-2b760d50]{background:#1e222d;-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-card-body,.profile-page.theme-dark .profile-card[data-v-2b760d50] .ant-card-body{background:#1e222d}.profile-page.theme-dark .profile-card .avatar-section .username[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .profile-card .profile-info .info-item[data-v-2b760d50]{border-bottom-color:#30363d}.profile-page.theme-dark .profile-card .profile-info .info-item .label[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .profile-card .profile-info .info-item .value[data-v-2b760d50]{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-bar{border-bottom-color:#30363d}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab:hover{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab-active{color:#1890ff}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-form-item-label label{color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:hover{border-color:#1890ff}.profile-page.theme-dark .credits-card[data-v-2b760d50]{background:linear-gradient(135deg,#1a1a2e,#16213e 50%,#0f3460);-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .credits-card[data-v-2b760d50] .ant-divider{border-color:hsla(0,0%,100%,.1)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-2b760d50]{background:hsla(0,0%,100%,.15);color:#fff;border:1px solid hsla(0,0%,100%,.2)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-2b760d50]:hover{background:hsla(0,0%,100%,.25)}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table{background:#1e222d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-thead>tr>th{background:#2a2e39;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td{background:#1e222d;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr:hover>td{background:#2a2e39}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-placeholder{background:#1e222d;color:#8b949e}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td,.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td :not(.ant-tag):not(.ant-btn),.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td div,.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .ant-table-tbody>tr>td span{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .amount-positive{color:#52c41a}.profile-page.theme-dark[data-v-2b760d50] .ant-table-wrapper .amount-negative{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-label>label,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-label>label,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-label>label{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-form-item-extra,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-form-item-extra,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-explain,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-form-item-extra{color:#8b949e}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-webkit-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-moz-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector:-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selector::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selector::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-number::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input-password::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-input::placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selector::placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-select-selection-placeholder,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-select-selection-placeholder,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-wrapper,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-wrapper span,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-wrapper,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-wrapper span,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selection-item,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-select-selection-placeholder{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}.profile-page.theme-dark .notification-settings-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .notification-settings-form .field-hint[data-v-2b760d50],.profile-page.theme-dark .password-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .password-form .field-hint[data-v-2b760d50],.profile-page.theme-dark .profile-form .email-hint[data-v-2b760d50],.profile-page.theme-dark .profile-form .field-hint[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark .notification-settings-form .email-warning[data-v-2b760d50],.profile-page.theme-dark .password-form .email-warning[data-v-2b760d50],.profile-page.theme-dark .profile-form .email-warning[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert .ant-alert-message{color:#c9d1d9}.profile-page.theme-dark .exchange-config-section .credential-hint[data-v-2b760d50],.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-alert .ant-alert-description{color:#8b949e}.profile-page.theme-dark .test-result-msg.success[data-v-2b760d50]{color:#52c41a}.profile-page.theme-dark .test-result-msg.error[data-v-2b760d50]{color:#f5222d}.profile-page.theme-dark .referral-user-cell .user-info .nickname[data-v-2b760d50]{color:#c9d1d9}.profile-page.theme-dark .referral-user-cell .user-info .username[data-v-2b760d50]{color:#8b949e}.profile-page.theme-dark[data-v-2b760d50] .ant-tag{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item{background:#1e222d;border-color:#30363d}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item a{color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item-active a{color:#fff}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-next .ant-pagination-item-link,.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-prev .ant-pagination-item-link{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-options .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-btn.ant-btn-default{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-2b760d50] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}@media screen and (max-width:768px){.profile-page[data-v-2b760d50]{padding:12px}.profile-page .page-header[data-v-2b760d50]{margin-bottom:16px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:20px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:22px}.profile-page .page-header .page-desc[data-v-2b760d50]{font-size:13px}.profile-page .profile-cards-row[data-v-2b760d50]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col[data-v-2b760d50]{margin-bottom:12px}.profile-page .profile-cards-row .right-cards-col .right-cards-row[data-v-2b760d50]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-col .right-cards-row .ant-col[data-v-2b760d50]{margin-bottom:12px}.profile-page .profile-card[data-v-2b760d50]{border-radius:10px}.profile-page .profile-card .avatar-section[data-v-2b760d50]{padding:16px 0}.profile-page .profile-card .avatar-section[data-v-2b760d50] .ant-avatar{width:80px!important;height:80px!important;line-height:80px!important}.profile-page .profile-card .avatar-section .username[data-v-2b760d50]{font-size:18px;margin:12px 0 6px}.profile-page .profile-card .profile-info .info-item[data-v-2b760d50]{padding:10px 0;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .profile-card .profile-info .info-item .anticon[data-v-2b760d50]{font-size:14px;margin-right:8px}.profile-page .profile-card .profile-info .info-item .label[data-v-2b760d50]{font-size:13px}.profile-page .profile-card .profile-info .info-item .value[data-v-2b760d50]{font-size:13px;word-break:break-all}.profile-page .credits-card[data-v-2b760d50]{border-radius:10px}.profile-page .credits-card .credits-header .credits-title[data-v-2b760d50]{font-size:15px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-2b760d50]{font-size:16px}.profile-page .credits-card .credits-body[data-v-2b760d50]{padding:16px 0}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:32px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:14px}.profile-page .credits-card .credits-body .vip-status[data-v-2b760d50]{font-size:12px;margin-top:10px}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{height:34px;padding:0 20px;font-size:14px}.profile-page .credits-card .credits-hint[data-v-2b760d50]{font-size:11px;margin-top:10px}.profile-page .referral-card[data-v-2b760d50]{border-radius:10px}.profile-page .referral-card .referral-header .referral-title[data-v-2b760d50]{font-size:15px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-2b760d50]{font-size:16px}.profile-page .referral-card .referral-body[data-v-2b760d50]{padding:10px 0}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:24px}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-2b760d50],.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-2b760d50]{font-size:11px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-2b760d50] .ant-input{font-size:12px}.profile-page .referral-card .referral-body .referral-hint[data-v-2b760d50]{font-size:11px;padding-top:8px}.profile-page .edit-card[data-v-2b760d50]{border-radius:10px;margin-top:12px!important}.profile-page .edit-card[data-v-2b760d50] .ant-card-body{padding:12px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav .ant-tabs-tab{padding:10px 12px;font-size:13px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav-scroll::-webkit-scrollbar{display:none}.profile-page .edit-card .password-form[data-v-2b760d50],.profile-page .edit-card .profile-form[data-v-2b760d50]{max-width:100%}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-form-item-label,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-form-item-label{padding-bottom:4px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-form-item-label label,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-form-item-label label{font-size:13px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .password-form[data-v-2b760d50] .ant-input-password,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input,.profile-page .edit-card .profile-form[data-v-2b760d50] .ant-input-password{font-size:14px}.profile-page .edit-card .password-form .email-hint[data-v-2b760d50],.profile-page .edit-card .profile-form .email-hint[data-v-2b760d50]{font-size:11px}.profile-page .edit-card .password-form[data-v-2b760d50] .ant-alert{font-size:12px;padding:8px 12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-alert{font-size:12px;padding:8px 12px;margin-bottom:16px!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form{max-width:100%}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row{margin-left:0!important;margin-right:0!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row .ant-col{padding-left:0!important;padding-right:8px!important}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-checkbox-wrapper{font-size:13px;margin-bottom:6px}.profile-page .edit-card .notification-settings-form .field-hint[data-v-2b760d50]{font-size:11px;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item{margin-bottom:16px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item:last-child .ant-btn{width:100%;margin-bottom:8px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-form-item:last-child .ant-btn+.ant-btn{margin-left:0!important}.profile-page .edit-card[data-v-2b760d50] .ant-table-wrapper{overflow-x:auto}.profile-page .edit-card[data-v-2b760d50] .ant-table-wrapper .ant-table{min-width:500px}.profile-page .referral-user-cell[data-v-2b760d50]{gap:8px}.profile-page .referral-user-cell[data-v-2b760d50] .ant-avatar{width:28px!important;height:28px!important;line-height:28px!important}.profile-page .referral-user-cell .user-info .nickname[data-v-2b760d50]{font-size:13px}.profile-page .referral-user-cell .user-info .username[data-v-2b760d50]{font-size:11px}}@media screen and (max-width:480px){.profile-page[data-v-2b760d50]{padding:8px}.profile-page .page-header[data-v-2b760d50]{margin-bottom:12px}.profile-page .page-header .page-title[data-v-2b760d50]{font-size:18px;gap:8px}.profile-page .page-header .page-title .anticon[data-v-2b760d50]{font-size:20px}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-2b760d50]{font-size:28px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-2b760d50]{font-size:13px}.profile-page .credits-card .credits-actions .ant-btn[data-v-2b760d50]{width:100%}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-2b760d50]{font-size:20px}.profile-page .edit-card[data-v-2b760d50] .ant-tabs-nav .ant-tabs-tab{padding:8px 10px;font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-2b760d50] .ant-checkbox-group .ant-row .ant-col{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}}
\ No newline at end of file
diff --git a/frontend/dist/css/467.02ed56c8.css b/frontend/dist/css/355.02ed56c8.css
similarity index 100%
rename from frontend/dist/css/467.02ed56c8.css
rename to frontend/dist/css/355.02ed56c8.css
diff --git a/frontend/dist/css/59.729cfe93.css b/frontend/dist/css/367.729cfe93.css
similarity index 100%
rename from frontend/dist/css/59.729cfe93.css
rename to frontend/dist/css/367.729cfe93.css
diff --git a/frontend/dist/css/251.20e2b587.css b/frontend/dist/css/397.20e2b587.css
similarity index 100%
rename from frontend/dist/css/251.20e2b587.css
rename to frontend/dist/css/397.20e2b587.css
diff --git a/frontend/dist/css/70.02ed56c8.css b/frontend/dist/css/40.02ed56c8.css
similarity index 100%
rename from frontend/dist/css/70.02ed56c8.css
rename to frontend/dist/css/40.02ed56c8.css
diff --git a/frontend/dist/css/407.1113ae8a.css b/frontend/dist/css/447.1113ae8a.css
similarity index 100%
rename from frontend/dist/css/407.1113ae8a.css
rename to frontend/dist/css/447.1113ae8a.css
diff --git a/frontend/dist/css/748.9a2b4136.css b/frontend/dist/css/55.9a2b4136.css
similarity index 100%
rename from frontend/dist/css/748.9a2b4136.css
rename to frontend/dist/css/55.9a2b4136.css
diff --git a/frontend/dist/css/472.1cc37f75.css b/frontend/dist/css/635.1cc37f75.css
similarity index 100%
rename from frontend/dist/css/472.1cc37f75.css
rename to frontend/dist/css/635.1cc37f75.css
diff --git a/frontend/dist/css/296.20e2b587.css b/frontend/dist/css/645.20e2b587.css
similarity index 100%
rename from frontend/dist/css/296.20e2b587.css
rename to frontend/dist/css/645.20e2b587.css
diff --git a/frontend/dist/css/359.443c56d4.css b/frontend/dist/css/684.443c56d4.css
similarity index 100%
rename from frontend/dist/css/359.443c56d4.css
rename to frontend/dist/css/684.443c56d4.css
diff --git a/frontend/dist/css/471.1113ae8a.css b/frontend/dist/css/734.1113ae8a.css
similarity index 100%
rename from frontend/dist/css/471.1113ae8a.css
rename to frontend/dist/css/734.1113ae8a.css
diff --git a/frontend/dist/css/438.fea2e0fa.css b/frontend/dist/css/811.fea2e0fa.css
similarity index 100%
rename from frontend/dist/css/438.fea2e0fa.css
rename to frontend/dist/css/811.fea2e0fa.css
diff --git a/frontend/dist/css/829.e48d5d81.css b/frontend/dist/css/829.e48d5d81.css
new file mode 100644
index 0000000..fdd2b37
--- /dev/null
+++ b/frontend/dist/css/829.e48d5d81.css
@@ -0,0 +1 @@
+.profile-page[data-v-86a53e9c]{padding:24px;min-height:calc(100vh - 120px);background:-webkit-gradient(linear,left top,left bottom,from(#f8fafc),to(#f1f5f9));background:linear-gradient(180deg,#f8fafc,#f1f5f9)}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:24px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:24px;font-weight:700;margin:0 0 8px 0;color:#1e3a5f;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:12px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:28px;color:#1890ff}.profile-page .page-header .page-desc[data-v-86a53e9c]{color:#64748b;font-size:14px;margin:0}.profile-page .profile-cards-row[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-col .ant-card[data-v-86a53e9c]{height:100%}.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c] .ant-card-body,.profile-page .profile-cards-row .right-cards-col[data-v-86a53e9c] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row[data-v-86a53e9c]{height:100%;display:-webkit-box;display:-ms-flexbox;display:flex}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-86a53e9c],.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-row .ant-col .ant-card[data-v-86a53e9c]{height:100%}.profile-page .profile-cards-row .right-cards-row .ant-col[data-v-86a53e9c] .ant-card-body{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);text-align:center}.profile-page .profile-card .avatar-section[data-v-86a53e9c]{padding:20px 0}.profile-page .profile-card .avatar-section .ant-avatar[data-v-86a53e9c]{-webkit-box-shadow:0 4px 12px rgba(0,0,0,.15);box-shadow:0 4px 12px rgba(0,0,0,.15)}.profile-page .profile-card .avatar-section .username[data-v-86a53e9c]{margin:16px 0 8px;font-size:20px;font-weight:600;color:#1e3a5f}.profile-page .profile-card .avatar-section .user-role[data-v-86a53e9c]{margin:0}.profile-page .profile-card .profile-info[data-v-86a53e9c]{text-align:left;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:12px 0;border-bottom:1px solid #f0f0f0}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]:last-child{border-bottom:none}.profile-page .profile-card .profile-info .info-item .anticon[data-v-86a53e9c]{font-size:16px;color:#1890ff;margin-right:12px}.profile-page .profile-card .profile-info .info-item .label[data-v-86a53e9c]{color:#64748b;margin-right:8px}.profile-page .profile-card .profile-info .info-item .value[data-v-86a53e9c]{color:#1e3a5f;font-weight:500}.profile-page .edit-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08)}.profile-page .edit-card .password-form[data-v-86a53e9c],.profile-page .edit-card .profile-form[data-v-86a53e9c]{max-width:500px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input-password,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input-password{border-radius:8px}.profile-page .edit-card .password-form .email-hint[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint[data-v-86a53e9c]{margin-top:8px;font-size:12px;color:rgba(0,0,0,.45)}.profile-page .edit-card .password-form .email-hint.email-warning[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint.email-warning[data-v-86a53e9c]{color:#faad14}.profile-page .edit-card .amount-positive[data-v-86a53e9c]{color:#52c41a;font-weight:600}.profile-page .edit-card .amount-negative[data-v-86a53e9c]{color:#ff4d4f;font-weight:600}.profile-page .edit-card .notification-settings-form .field-hint[data-v-86a53e9c]{margin-top:6px;font-size:12px;color:rgba(0,0,0,.45);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px}.profile-page .edit-card .notification-settings-form .field-hint .anticon[data-v-86a53e9c]{font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group{width:100%}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper{margin-bottom:8px}.profile-page .credits-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#667eea,#764ba2);color:#fff}.profile-page .credits-card[data-v-86a53e9c] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .credits-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .credits-card .credits-header .credits-title[data-v-86a53e9c]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-86a53e9c]{font-size:18px}.profile-page .credits-card .credits-body[data-v-86a53e9c]{padding:20px 0;text-align:center;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:42px;font-weight:700;color:#fff;text-shadow:0 2px 4px rgba(0,0,0,.2)}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:16px;color:hsla(0,0%,100%,.9);margin-left:8px}.profile-page .credits-card .credits-body .vip-status[data-v-86a53e9c]{margin-top:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px;font-size:13px}.profile-page .credits-card .credits-body .vip-status .vip-active[data-v-86a53e9c]{color:gold}.profile-page .credits-card .credits-body .vip-status .vip-expired[data-v-86a53e9c]{color:hsla(0,0%,100%,.6)}.profile-page .credits-card .credits-body .vip-status .no-vip[data-v-86a53e9c]{color:hsla(0,0%,100%,.7)}.profile-page .credits-card .credits-actions[data-v-86a53e9c]{text-align:center;margin-top:auto}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{border-radius:20px;padding:0 24px;height:36px;font-weight:500;background:#fff;color:#667eea;border:none}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]:hover{background:hsla(0,0%,100%,.9);color:#764ba2}.profile-page .credits-card .credits-hint[data-v-86a53e9c]{margin-top:12px;text-align:center;font-size:12px;color:hsla(0,0%,100%,.7);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card[data-v-86a53e9c]{border-radius:12px;-webkit-box-shadow:0 4px 20px rgba(0,0,0,.08);box-shadow:0 4px 20px rgba(0,0,0,.08);background:linear-gradient(135deg,#11998e,#38ef7d);color:#fff}.profile-page .referral-card[data-v-86a53e9c] .ant-card-body{background:transparent;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.2)}.profile-page .referral-card .referral-header .referral-title[data-v-86a53e9c]{font-size:16px;font-weight:600;margin:0;color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:8px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-86a53e9c]{font-size:18px}.profile-page .referral-card .referral-body[data-v-86a53e9c]{padding:12px 0;-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.profile-page .referral-card .referral-body .referral-stats[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around}.profile-page .referral-card .referral-body .referral-stats .stat-item[data-v-86a53e9c]{text-align:center}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:28px;font-weight:700;color:#fff;display:block}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-86a53e9c]{font-size:12px;color:hsla(0,0%,100%,.8)}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-86a53e9c]{font-size:12px;color:hsla(0,0%,100%,.9);margin-bottom:6px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input{background:hsla(0,0%,100%,.2);border:1px solid hsla(0,0%,100%,.3);color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-webkit-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-moz-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input:-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::-ms-input-placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input::placeholder{color:hsla(0,0%,100%,.6)}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .anticon-copy{color:#fff}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .anticon-copy:hover{color:gold}.profile-page .referral-card .referral-body .referral-hint[data-v-86a53e9c]{margin-top:auto;text-align:center;font-size:12px;color:hsla(0,0%,100%,.85);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;gap:6px}.profile-page .referral-card .referral-body .referral-hint .anticon-gift[data-v-86a53e9c]{color:gold}.profile-page .referral-user-cell[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:10px}.profile-page .referral-user-cell .user-info[data-v-86a53e9c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .referral-user-cell .user-info .nickname[data-v-86a53e9c]{font-weight:500;color:#333}.profile-page .referral-user-cell .user-info .username[data-v-86a53e9c]{font-size:12px;color:#999}.profile-page .exchange-config-section .credential-hint[data-v-86a53e9c]{font-family:SFMono-Regular,Consolas,monospace;font-size:13px;color:#666}.profile-page .test-result-msg[data-v-86a53e9c]{margin-top:8px;font-size:13px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:6px}.profile-page .test-result-msg.success[data-v-86a53e9c]{color:#52c41a}.profile-page .test-result-msg.error[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark[data-v-86a53e9c]{background:-webkit-gradient(linear,left top,left bottom,from(#0d1117),to(#161b22));background:linear-gradient(180deg,#0d1117,#161b22)}.profile-page.theme-dark .page-header .page-title[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .page-header .page-desc[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-86a53e9c],.profile-page.theme-dark .profile-card[data-v-86a53e9c]{background:#1e222d;-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-card-body,.profile-page.theme-dark .profile-card[data-v-86a53e9c] .ant-card-body{background:#1e222d}.profile-page.theme-dark .profile-card .avatar-section .username[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .profile-card .profile-info .info-item[data-v-86a53e9c]{border-bottom-color:#30363d}.profile-page.theme-dark .profile-card .profile-info .info-item .label[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .profile-card .profile-info .info-item .value[data-v-86a53e9c]{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-bar{border-bottom-color:#30363d}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab{color:#8b949e}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab:hover{color:#e0e6ed}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab-active{color:#1890ff}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-form-item-label label{color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:hover{border-color:#1890ff}.profile-page.theme-dark .credits-card[data-v-86a53e9c]{background:linear-gradient(135deg,#1a1a2e,#16213e 50%,#0f3460);-webkit-box-shadow:0 4px 24px rgba(0,0,0,.25);box-shadow:0 4px 24px rgba(0,0,0,.25)}.profile-page.theme-dark .credits-card[data-v-86a53e9c] .ant-divider{border-color:hsla(0,0%,100%,.1)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{background:hsla(0,0%,100%,.15);color:#fff;border:1px solid hsla(0,0%,100%,.2)}.profile-page.theme-dark .credits-card .credits-actions .ant-btn[data-v-86a53e9c]:hover{background:hsla(0,0%,100%,.25)}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table{background:#1e222d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-thead>tr>th{background:#2a2e39;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td{background:#1e222d;color:#c9d1d9;border-bottom-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr:hover>td{background:#2a2e39}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-placeholder{background:#1e222d;color:#8b949e}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td,.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td :not(.ant-tag):not(.ant-btn),.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td div,.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .ant-table-tbody>tr>td span{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .amount-positive{color:#52c41a}.profile-page.theme-dark[data-v-86a53e9c] .ant-table-wrapper .amount-negative{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-label>label,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-label>label,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-label>label{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-form-item-extra,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-form-item-extra,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-explain,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-form-item-extra{color:#8b949e}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-webkit-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-webkit-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-moz-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-moz-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input:-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector:-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::-ms-input-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::-ms-input-placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selector::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selector::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-number::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input-password::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-input::placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selector::placeholder{color:#6e7681}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-select-selection-placeholder,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-select-selection-placeholder,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-wrapper,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-wrapper span,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-wrapper,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-wrapper span,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selection-item,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-select-selection-placeholder{color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}.profile-page.theme-dark .notification-settings-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .notification-settings-form .field-hint[data-v-86a53e9c],.profile-page.theme-dark .password-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .password-form .field-hint[data-v-86a53e9c],.profile-page.theme-dark .profile-form .email-hint[data-v-86a53e9c],.profile-page.theme-dark .profile-form .field-hint[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark .notification-settings-form .email-warning[data-v-86a53e9c],.profile-page.theme-dark .password-form .email-warning[data-v-86a53e9c],.profile-page.theme-dark .profile-form .email-warning[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert .ant-alert-message{color:#c9d1d9}.profile-page.theme-dark .exchange-config-section .credential-hint[data-v-86a53e9c],.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-alert .ant-alert-description{color:#8b949e}.profile-page.theme-dark .test-result-msg.success[data-v-86a53e9c]{color:#52c41a}.profile-page.theme-dark .test-result-msg.error[data-v-86a53e9c]{color:#f5222d}.profile-page.theme-dark .referral-user-cell .user-info .nickname[data-v-86a53e9c]{color:#c9d1d9}.profile-page.theme-dark .referral-user-cell .user-info .username[data-v-86a53e9c]{color:#8b949e}.profile-page.theme-dark[data-v-86a53e9c] .ant-tag{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item{background:#1e222d;border-color:#30363d}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item a{color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item-active a{color:#fff}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-next .ant-pagination-item-link,.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-prev .ant-pagination-item-link{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-options .ant-select-selector{background:#0d1117;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-btn.ant-btn-default{background:#1e222d;border-color:#30363d;color:#c9d1d9}.profile-page.theme-dark[data-v-86a53e9c] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}@media screen and (max-width:768px){.profile-page[data-v-86a53e9c]{padding:12px}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:16px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:20px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:22px}.profile-page .page-header .page-desc[data-v-86a53e9c]{font-size:13px}.profile-page .profile-cards-row[data-v-86a53e9c]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .profile-card-col[data-v-86a53e9c]{margin-bottom:12px}.profile-page .profile-cards-row .right-cards-col .right-cards-row[data-v-86a53e9c]{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.profile-page .profile-cards-row .right-cards-col .right-cards-row .ant-col[data-v-86a53e9c]{margin-bottom:12px}.profile-page .profile-card[data-v-86a53e9c]{border-radius:10px}.profile-page .profile-card .avatar-section[data-v-86a53e9c]{padding:16px 0}.profile-page .profile-card .avatar-section[data-v-86a53e9c] .ant-avatar{width:80px!important;height:80px!important;line-height:80px!important}.profile-page .profile-card .avatar-section .username[data-v-86a53e9c]{font-size:18px;margin:12px 0 6px}.profile-page .profile-card .profile-info .info-item[data-v-86a53e9c]{padding:10px 0;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .profile-card .profile-info .info-item .anticon[data-v-86a53e9c]{font-size:14px;margin-right:8px}.profile-page .profile-card .profile-info .info-item .label[data-v-86a53e9c]{font-size:13px}.profile-page .profile-card .profile-info .info-item .value[data-v-86a53e9c]{font-size:13px;word-break:break-all}.profile-page .credits-card[data-v-86a53e9c]{border-radius:10px}.profile-page .credits-card .credits-header .credits-title[data-v-86a53e9c]{font-size:15px}.profile-page .credits-card .credits-header .credits-title .anticon[data-v-86a53e9c]{font-size:16px}.profile-page .credits-card .credits-body[data-v-86a53e9c]{padding:16px 0}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:32px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:14px}.profile-page .credits-card .credits-body .vip-status[data-v-86a53e9c]{font-size:12px;margin-top:10px}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{height:34px;padding:0 20px;font-size:14px}.profile-page .credits-card .credits-hint[data-v-86a53e9c]{font-size:11px;margin-top:10px}.profile-page .referral-card[data-v-86a53e9c]{border-radius:10px}.profile-page .referral-card .referral-header .referral-title[data-v-86a53e9c]{font-size:15px}.profile-page .referral-card .referral-header .referral-title .anticon[data-v-86a53e9c]{font-size:16px}.profile-page .referral-card .referral-body[data-v-86a53e9c]{padding:10px 0}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:24px}.profile-page .referral-card .referral-body .referral-link-section .link-label[data-v-86a53e9c],.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-label[data-v-86a53e9c]{font-size:11px}.profile-page .referral-card .referral-body .referral-link-section .link-box[data-v-86a53e9c] .ant-input{font-size:12px}.profile-page .referral-card .referral-body .referral-hint[data-v-86a53e9c]{font-size:11px;padding-top:8px}.profile-page .edit-card[data-v-86a53e9c]{border-radius:10px;margin-top:12px!important}.profile-page .edit-card[data-v-86a53e9c] .ant-card-body{padding:12px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav .ant-tabs-tab{padding:10px 12px;font-size:13px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav-scroll::-webkit-scrollbar{display:none}.profile-page .edit-card .password-form[data-v-86a53e9c],.profile-page .edit-card .profile-form[data-v-86a53e9c]{max-width:100%}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-form-item-label,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-form-item-label{padding-bottom:4px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-form-item-label label,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-form-item-label label{font-size:13px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-input-password,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input,.profile-page .edit-card .profile-form[data-v-86a53e9c] .ant-input-password{font-size:14px}.profile-page .edit-card .password-form .email-hint[data-v-86a53e9c],.profile-page .edit-card .profile-form .email-hint[data-v-86a53e9c]{font-size:11px}.profile-page .edit-card .password-form[data-v-86a53e9c] .ant-alert{font-size:12px;padding:8px 12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-alert{font-size:12px;padding:8px 12px;margin-bottom:16px!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form{max-width:100%}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row{margin-left:0!important;margin-right:0!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row .ant-col{padding-left:0!important;padding-right:8px!important}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-checkbox-wrapper{font-size:13px;margin-bottom:6px}.profile-page .edit-card .notification-settings-form .field-hint[data-v-86a53e9c]{font-size:11px;-ms-flex-wrap:wrap;flex-wrap:wrap}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item{margin-bottom:16px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item:last-child .ant-btn{width:100%;margin-bottom:8px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-form-item:last-child .ant-btn+.ant-btn{margin-left:0!important}.profile-page .edit-card[data-v-86a53e9c] .ant-table-wrapper{overflow-x:auto}.profile-page .edit-card[data-v-86a53e9c] .ant-table-wrapper .ant-table{min-width:500px}.profile-page .referral-user-cell[data-v-86a53e9c]{gap:8px}.profile-page .referral-user-cell[data-v-86a53e9c] .ant-avatar{width:28px!important;height:28px!important;line-height:28px!important}.profile-page .referral-user-cell .user-info .nickname[data-v-86a53e9c]{font-size:13px}.profile-page .referral-user-cell .user-info .username[data-v-86a53e9c]{font-size:11px}}@media screen and (max-width:480px){.profile-page[data-v-86a53e9c]{padding:8px}.profile-page .page-header[data-v-86a53e9c]{margin-bottom:12px}.profile-page .page-header .page-title[data-v-86a53e9c]{font-size:18px;gap:8px}.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{font-size:20px}.profile-page .credits-card .credits-body .credits-amount .amount-value[data-v-86a53e9c]{font-size:28px}.profile-page .credits-card .credits-body .credits-amount .amount-label[data-v-86a53e9c]{font-size:13px}.profile-page .credits-card .credits-actions .ant-btn[data-v-86a53e9c]{width:100%}.profile-page .referral-card .referral-body .referral-stats .stat-item .stat-value[data-v-86a53e9c]{font-size:20px}.profile-page .edit-card[data-v-86a53e9c] .ant-tabs-nav .ant-tabs-tab{padding:8px 10px;font-size:12px}.profile-page .edit-card .notification-settings-form[data-v-86a53e9c] .ant-checkbox-group .ant-row .ant-col{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}}
\ No newline at end of file
diff --git a/frontend/dist/css/576.37fb7134.css b/frontend/dist/css/896.37fb7134.css
similarity index 100%
rename from frontend/dist/css/576.37fb7134.css
rename to frontend/dist/css/896.37fb7134.css
diff --git a/frontend/dist/css/642.443c56d4.css b/frontend/dist/css/916.443c56d4.css
similarity index 100%
rename from frontend/dist/css/642.443c56d4.css
rename to frontend/dist/css/916.443c56d4.css
diff --git a/frontend/dist/css/798.729cfe93.css b/frontend/dist/css/958.729cfe93.css
similarity index 100%
rename from frontend/dist/css/798.729cfe93.css
rename to frontend/dist/css/958.729cfe93.css
diff --git a/frontend/dist/css/715.1cc37f75.css b/frontend/dist/css/978.1cc37f75.css
similarity index 100%
rename from frontend/dist/css/715.1cc37f75.css
rename to frontend/dist/css/978.1cc37f75.css
diff --git a/frontend/dist/css/theme-colors-de962665.css b/frontend/dist/css/theme-colors-32dabe8f.css
similarity index 98%
rename from frontend/dist/css/theme-colors-de962665.css
rename to frontend/dist/css/theme-colors-32dabe8f.css
index 055b759..4e0096a 100644
--- a/frontend/dist/css/theme-colors-de962665.css
+++ b/frontend/dist/css/theme-colors-32dabe8f.css
@@ -189,14 +189,14 @@ body.dark .trading-records ::v-deep .ant-pagination .ant-pagination-next:hover .
.user-manage-page .address-text[data-v-49c4f239],.user-manage-page .hash-text[data-v-49c4f239]{color:#1890ff}
.user-manage-page.theme-dark .manage-tabs[data-v-49c4f239] .ant-tabs-tab-active{color:#1890ff}
.user-manage-page .current-credits-info .value[data-v-49c4f239],.user-manage-page .current-vip-info .value[data-v-49c4f239]{color:#1890ff}
-.profile-page .page-header .page-title .anticon[data-v-2b760d50]{color:#1890ff}
-.profile-page .profile-card .profile-info .info-item .anticon[data-v-2b760d50]{color:#1890ff}
-.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-tabs-tab-active{color:#1890ff}
-.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-2b760d50] .ant-input:hover{border-color:#1890ff}
-.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-2b760d50] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}
-.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}
-.profile-page.theme-dark[data-v-2b760d50] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}
-.profile-page.theme-dark[data-v-2b760d50] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}
+.profile-page .page-header .page-title .anticon[data-v-86a53e9c]{color:#1890ff}
+.profile-page .profile-card .profile-info .info-item .anticon[data-v-86a53e9c]{color:#1890ff}
+.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-tabs-tab-active{color:#1890ff}
+.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input-password:hover,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:focus,.profile-page.theme-dark .edit-card[data-v-86a53e9c] .ant-input:hover{border-color:#1890ff}
+.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .notification-settings-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .password-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-checkbox-checked .ant-checkbox-inner,.profile-page.theme-dark .profile-form[data-v-86a53e9c] .ant-radio-checked .ant-radio-inner{background-color:#1890ff;border-color:#1890ff}
+.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item:hover{border-color:#1890ff}
+.profile-page.theme-dark[data-v-86a53e9c] .ant-pagination .ant-pagination-item-active{background:#1890ff;border-color:#1890ff}
+.profile-page.theme-dark[data-v-86a53e9c] .ant-btn.ant-btn-default:hover{border-color:#1890ff;color:#1890ff}
.billing-page .plan-card.highlight[data-v-0cdd5f9c]{border:1px solid rgba(24,144,255,.35)}
.usdt-checkout .checkout-body .info-section .addr-box .copy-btn:hover,.usdt-checkout .checkout-body .info-section .amt-box .copy-btn:hover{background:rgba(24,144,255,.08);color:#1890ff}
.theme-dark .usdt-checkout .checkout-body .info-section .addr-box .copy-btn:hover,.theme-dark .usdt-checkout .checkout-body .info-section .amt-box .copy-btn:hover,body.realdark .usdt-checkout .checkout-body .info-section .addr-box .copy-btn:hover,body.realdark .usdt-checkout .checkout-body .info-section .amt-box .copy-btn:hover{background:rgba(24,144,255,.15);color:#40a9ff}
diff --git a/frontend/dist/index.html b/frontend/dist/index.html
index c93762a..d03a9f5 100644
--- a/frontend/dist/index.html
+++ b/frontend/dist/index.html
@@ -421,4 +421,4 @@
.brand-text {
font-size: 20px;
}
- }