Merge branch 'brokermr810:main' into main
This commit is contained in:
@@ -354,8 +354,16 @@ def summary():
|
||||
""",
|
||||
(user_id,)
|
||||
)
|
||||
recent_trades = cur.fetchall() or []
|
||||
recent_trades_raw = cur.fetchall() or []
|
||||
cur.close()
|
||||
|
||||
# Convert datetime to timestamp for frontend compatibility
|
||||
recent_trades = []
|
||||
for t in recent_trades_raw:
|
||||
trade = dict(t)
|
||||
if trade.get('created_at') and hasattr(trade['created_at'], 'timestamp'):
|
||||
trade['created_at'] = int(trade['created_at'].timestamp())
|
||||
recent_trades.append(trade)
|
||||
|
||||
# Compute performance statistics
|
||||
perf_stats = _compute_performance_stats(recent_trades)
|
||||
|
||||
@@ -228,22 +228,6 @@ CONFIG_SCHEMA = {
|
||||
'default': '10',
|
||||
'description': 'Time-to-live for cached price data in seconds'
|
||||
},
|
||||
{
|
||||
'key': 'MARKET_TYPES_JSON',
|
||||
'label': 'Market Types (JSON)',
|
||||
'type': 'text',
|
||||
'default': '[]',
|
||||
'required': False,
|
||||
'description': 'Custom market type definitions in JSON format'
|
||||
},
|
||||
{
|
||||
'key': 'TRADING_SUPPORTED_SYMBOLS_JSON',
|
||||
'label': 'Supported Symbols (JSON)',
|
||||
'type': 'text',
|
||||
'default': '[]',
|
||||
'required': False,
|
||||
'description': 'List of supported trading symbols in JSON format'
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
@@ -353,50 +337,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 7. 通知推送 ====================
|
||||
'notification': {
|
||||
'title': 'Notifications',
|
||||
'icon': 'notification',
|
||||
'order': 7,
|
||||
'items': [
|
||||
{
|
||||
'key': 'SIGNAL_WEBHOOK_URL',
|
||||
'label': 'Webhook URL',
|
||||
'type': 'text',
|
||||
'required': False,
|
||||
'description': 'Custom webhook URL for signal notifications (POST JSON)'
|
||||
},
|
||||
{
|
||||
'key': 'SIGNAL_WEBHOOK_TOKEN',
|
||||
'label': 'Webhook Token',
|
||||
'type': 'password',
|
||||
'required': False,
|
||||
'description': 'Authentication token sent in webhook header'
|
||||
},
|
||||
{
|
||||
'key': 'SIGNAL_NOTIFY_TIMEOUT_SEC',
|
||||
'label': 'Notify Timeout (sec)',
|
||||
'type': 'number',
|
||||
'default': '6',
|
||||
'description': 'Notification request timeout'
|
||||
},
|
||||
{
|
||||
'key': 'TELEGRAM_BOT_TOKEN',
|
||||
'label': 'Telegram Bot Token',
|
||||
'type': 'password',
|
||||
'required': False,
|
||||
'link': 'https://t.me/BotFather',
|
||||
'link_text': 'settings.link.createBot',
|
||||
'description': 'Telegram bot token from @BotFather for signal notifications'
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 8. 邮件配置 ====================
|
||||
# ==================== 7. 邮件配置 (公共 SMTP) ====================
|
||||
'email': {
|
||||
'title': 'Email (SMTP)',
|
||||
'icon': 'mail',
|
||||
'order': 8,
|
||||
'order': 7,
|
||||
'items': [
|
||||
{
|
||||
'key': 'SMTP_HOST',
|
||||
@@ -454,7 +399,7 @@ CONFIG_SCHEMA = {
|
||||
'sms': {
|
||||
'title': 'SMS (Twilio)',
|
||||
'icon': 'phone',
|
||||
'order': 9,
|
||||
'order': 8,
|
||||
'items': [
|
||||
{
|
||||
'key': 'TWILIO_ACCOUNT_SID',
|
||||
@@ -482,11 +427,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 10. AI Agent 配置 ====================
|
||||
# ==================== 9. AI Agent 配置 ====================
|
||||
'agent': {
|
||||
'title': 'AI Agent',
|
||||
'icon': 'experiment',
|
||||
'order': 10,
|
||||
'order': 9,
|
||||
'items': [
|
||||
{
|
||||
'key': 'ENABLE_AGENT_MEMORY',
|
||||
@@ -568,11 +513,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 11. 网络代理 ====================
|
||||
# ==================== 10. 网络代理 ====================
|
||||
'network': {
|
||||
'title': 'Network & Proxy',
|
||||
'icon': 'global',
|
||||
'order': 11,
|
||||
'order': 10,
|
||||
'items': [
|
||||
{
|
||||
'key': 'PROXY_HOST',
|
||||
@@ -606,11 +551,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 12. 搜索配置 ====================
|
||||
# ==================== 11. 搜索配置 ====================
|
||||
'search': {
|
||||
'title': 'Web Search',
|
||||
'icon': 'search',
|
||||
'order': 12,
|
||||
'order': 11,
|
||||
'items': [
|
||||
{
|
||||
'key': 'SEARCH_PROVIDER',
|
||||
@@ -664,11 +609,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 13. 注册与安全 ====================
|
||||
# ==================== 12. 注册与安全 ====================
|
||||
'security': {
|
||||
'title': 'Registration & Security',
|
||||
'icon': 'safety',
|
||||
'order': 13,
|
||||
'order': 12,
|
||||
'items': [
|
||||
{
|
||||
'key': 'ENABLE_REGISTRATION',
|
||||
@@ -826,11 +771,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 14. 计费配置 ====================
|
||||
# ==================== 13. 计费配置 ====================
|
||||
'billing': {
|
||||
'title': 'Billing & Credits',
|
||||
'icon': 'dollar',
|
||||
'order': 14,
|
||||
'order': 13,
|
||||
'items': [
|
||||
{
|
||||
'key': 'BILLING_ENABLED',
|
||||
@@ -898,11 +843,11 @@ CONFIG_SCHEMA = {
|
||||
]
|
||||
},
|
||||
|
||||
# ==================== 15. 应用配置 ====================
|
||||
# ==================== 14. 应用配置 ====================
|
||||
'app': {
|
||||
'title': 'Application',
|
||||
'icon': 'appstore',
|
||||
'order': 15,
|
||||
'order': 14,
|
||||
'items': [
|
||||
{
|
||||
'key': 'CORS_ORIGINS',
|
||||
|
||||
@@ -471,7 +471,13 @@ def get_equity_curve():
|
||||
equity += float(r.get('profit') or 0)
|
||||
except Exception:
|
||||
pass
|
||||
ts = int(r.get('created_at').timestamp() or time.time())
|
||||
created_at = r.get('created_at')
|
||||
if created_at and hasattr(created_at, 'timestamp'):
|
||||
ts = int(created_at.timestamp())
|
||||
elif created_at:
|
||||
ts = int(created_at)
|
||||
else:
|
||||
ts = int(time.time())
|
||||
curve.append({'time': ts, 'equity': equity})
|
||||
|
||||
return jsonify({'code': 1, 'msg': 'success', 'data': curve})
|
||||
|
||||
@@ -348,9 +348,11 @@ def get_user_credits_log():
|
||||
@user_bp.route('/profile', methods=['GET'])
|
||||
@login_required
|
||||
def get_profile():
|
||||
"""Get current user's profile with billing info"""
|
||||
"""Get current user's profile with billing info and notification settings"""
|
||||
try:
|
||||
import json
|
||||
from app.services.billing_service import get_billing_service
|
||||
from app.utils.db import get_db_connection
|
||||
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
if not user_id:
|
||||
@@ -367,6 +369,27 @@ def get_profile():
|
||||
billing_info = get_billing_service().get_user_billing_info(user_id)
|
||||
user['billing'] = billing_info
|
||||
|
||||
# Add notification settings
|
||||
with get_db_connection() as db:
|
||||
cur = db.cursor()
|
||||
cur.execute("SELECT notification_settings FROM qd_users WHERE id = ?", (user_id,))
|
||||
row = cur.fetchone()
|
||||
cur.close()
|
||||
|
||||
settings_str = (row.get('notification_settings') if row else '') or ''
|
||||
notification_settings = {}
|
||||
if settings_str:
|
||||
try:
|
||||
notification_settings = json.loads(settings_str)
|
||||
except Exception:
|
||||
notification_settings = {}
|
||||
|
||||
# Default values
|
||||
if 'default_channels' not in notification_settings:
|
||||
notification_settings['default_channels'] = ['browser']
|
||||
|
||||
user['notification_settings'] = notification_settings
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
@@ -529,6 +552,129 @@ def get_my_referrals():
|
||||
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
|
||||
|
||||
|
||||
@user_bp.route('/notification-settings', methods=['GET'])
|
||||
@login_required
|
||||
def get_notification_settings():
|
||||
"""
|
||||
Get current user's notification settings.
|
||||
|
||||
Returns:
|
||||
notification_settings: {
|
||||
default_channels: ['browser', 'telegram', ...],
|
||||
telegram_chat_id: str,
|
||||
email: str (optional, override for notifications),
|
||||
discord_webhook: str (optional)
|
||||
}
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
from app.utils.db import get_db_connection
|
||||
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
if not user_id:
|
||||
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
|
||||
|
||||
with get_db_connection() as db:
|
||||
cur = db.cursor()
|
||||
cur.execute("SELECT notification_settings, email FROM qd_users WHERE id = ?", (user_id,))
|
||||
row = cur.fetchone()
|
||||
cur.close()
|
||||
|
||||
if not row:
|
||||
return jsonify({'code': 0, 'msg': 'User not found', 'data': None}), 404
|
||||
|
||||
# Parse notification_settings JSON
|
||||
settings_str = row.get('notification_settings') or ''
|
||||
settings = {}
|
||||
if settings_str:
|
||||
try:
|
||||
settings = json.loads(settings_str)
|
||||
except Exception:
|
||||
settings = {}
|
||||
|
||||
# Default values
|
||||
if 'default_channels' not in settings:
|
||||
settings['default_channels'] = ['browser']
|
||||
if 'email' not in settings:
|
||||
settings['email'] = row.get('email') or ''
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': settings
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"get_notification_settings failed: {e}")
|
||||
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
|
||||
|
||||
|
||||
@user_bp.route('/notification-settings', methods=['PUT'])
|
||||
@login_required
|
||||
def update_notification_settings():
|
||||
"""
|
||||
Update current user's notification settings.
|
||||
|
||||
Request body:
|
||||
default_channels: list of str (optional, e.g. ['browser', 'telegram'])
|
||||
telegram_bot_token: str (optional, user's own Telegram bot token)
|
||||
telegram_chat_id: str (optional)
|
||||
email: str (optional, for notification override)
|
||||
discord_webhook: str (optional)
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
from app.utils.db import get_db_connection
|
||||
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
if not user_id:
|
||||
return jsonify({'code': 0, 'msg': 'Not authenticated', 'data': None}), 401
|
||||
|
||||
data = request.get_json() or {}
|
||||
|
||||
# Validate channels
|
||||
valid_channels = ['browser', 'email', 'telegram', 'discord', 'webhook', 'phone']
|
||||
default_channels = data.get('default_channels', [])
|
||||
if not isinstance(default_channels, list):
|
||||
default_channels = ['browser']
|
||||
default_channels = [c for c in default_channels if c in valid_channels]
|
||||
if not default_channels:
|
||||
default_channels = ['browser']
|
||||
|
||||
# Build settings object
|
||||
settings = {
|
||||
'default_channels': default_channels,
|
||||
'telegram_bot_token': str(data.get('telegram_bot_token') or '').strip(),
|
||||
'telegram_chat_id': str(data.get('telegram_chat_id') or '').strip(),
|
||||
'email': str(data.get('email') or '').strip(),
|
||||
'discord_webhook': str(data.get('discord_webhook') or '').strip(),
|
||||
'webhook_url': str(data.get('webhook_url') or '').strip(),
|
||||
'phone': str(data.get('phone') or '').strip(),
|
||||
}
|
||||
|
||||
# Remove empty values (but keep default_channels and telegram_bot_token even if partially filled)
|
||||
settings = {k: v for k, v in settings.items() if v or k == 'default_channels'}
|
||||
|
||||
settings_json = json.dumps(settings, ensure_ascii=False)
|
||||
|
||||
with get_db_connection() as db:
|
||||
cur = db.cursor()
|
||||
cur.execute(
|
||||
"UPDATE qd_users SET notification_settings = ?, updated_at = NOW() WHERE id = ?",
|
||||
(settings_json, user_id)
|
||||
)
|
||||
db.commit()
|
||||
cur.close()
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'Notification settings updated',
|
||||
'data': settings
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"update_notification_settings failed: {e}")
|
||||
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
|
||||
|
||||
|
||||
@user_bp.route('/change-password', methods=['POST'])
|
||||
@login_required
|
||||
def change_password():
|
||||
|
||||
@@ -92,18 +92,18 @@ class SignalNotifier:
|
||||
"""
|
||||
Notify signal events across channels.
|
||||
|
||||
Provider environment variables:
|
||||
- SIGNAL_NOTIFY_TIMEOUT_SEC: HTTP timeout (default: 6)
|
||||
|
||||
- SIGNAL_WEBHOOK_TOKEN: optional bearer token for generic webhook channel
|
||||
|
||||
- TELEGRAM_BOT_TOKEN: Telegram bot token (required for telegram channel)
|
||||
通知配置说明:
|
||||
- 用户在个人中心配置自己的通知设置(telegram_bot_token, telegram_chat_id, email 等)
|
||||
- 创建策略/监控时,系统自动使用用户配置的通知目标
|
||||
|
||||
公共服务配置(管理员在系统设置中配置):
|
||||
- SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, SMTP_USE_TLS
|
||||
(required for email channel)
|
||||
|
||||
(邮件服务,所有用户共用)
|
||||
- TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER
|
||||
(optional for phone/SMS channel)
|
||||
(短信服务,所有用户共用)
|
||||
|
||||
可选的环境变量:
|
||||
- SIGNAL_NOTIFY_TIMEOUT_SEC: HTTP timeout (default: 6)
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
@@ -112,10 +112,7 @@ class SignalNotifier:
|
||||
except Exception:
|
||||
self.timeout_sec = 6.0
|
||||
|
||||
self.webhook_token = (os.getenv("SIGNAL_WEBHOOK_TOKEN") or "").strip()
|
||||
|
||||
self.telegram_token = (os.getenv("TELEGRAM_BOT_TOKEN") or "").strip()
|
||||
|
||||
# 公共 SMTP 配置(管理员在系统设置中配置)
|
||||
self.smtp_host = (os.getenv("SMTP_HOST") or "").strip()
|
||||
try:
|
||||
self.smtp_port = int(os.getenv("SMTP_PORT") or "587")
|
||||
@@ -184,7 +181,7 @@ class SignalNotifier:
|
||||
payload=payload,
|
||||
)
|
||||
elif c == "webhook":
|
||||
url = (targets.get("webhook") or os.getenv("SIGNAL_WEBHOOK_URL") or "").strip()
|
||||
url = (targets.get("webhook") or "").strip()
|
||||
ok, err = self._notify_webhook(
|
||||
url=url,
|
||||
payload=payload,
|
||||
@@ -201,19 +198,18 @@ class SignalNotifier:
|
||||
ok, err = self._notify_discord(url=url, payload=payload, fallback_text=message_plain)
|
||||
elif c == "telegram":
|
||||
chat_id = (targets.get("telegram") or "").strip()
|
||||
# Support per-strategy token override (local mode). Falls back to env TELEGRAM_BOT_TOKEN.
|
||||
# User's token takes priority, then falls back to env TELEGRAM_BOT_TOKEN.
|
||||
token_override = ""
|
||||
if not self.telegram_token:
|
||||
try:
|
||||
token_override = str(
|
||||
targets.get("telegram_bot_token")
|
||||
or targets.get("telegram_token")
|
||||
or cfg.get("telegram_bot_token")
|
||||
or cfg.get("telegram_token")
|
||||
or ""
|
||||
).strip()
|
||||
except Exception:
|
||||
token_override = ""
|
||||
try:
|
||||
token_override = str(
|
||||
targets.get("telegram_bot_token")
|
||||
or targets.get("telegram_token")
|
||||
or cfg.get("telegram_bot_token")
|
||||
or cfg.get("telegram_token")
|
||||
or ""
|
||||
).strip()
|
||||
except Exception:
|
||||
token_override = ""
|
||||
ok, err = self._notify_telegram(
|
||||
chat_id=chat_id,
|
||||
text=rendered.get("telegram_html") or message_plain,
|
||||
@@ -500,15 +496,15 @@ class SignalNotifier:
|
||||
"""
|
||||
Generic webhook delivery.
|
||||
|
||||
Supports (best-effort):
|
||||
- per-strategy headers: notification_config.targets.webhook_headers (dict or JSON string)
|
||||
- per-strategy bearer token: notification_config.targets.webhook_token
|
||||
- global bearer token: SIGNAL_WEBHOOK_TOKEN
|
||||
- optional signing secret: notification_config.targets.webhook_signing_secret or env SIGNAL_WEBHOOK_SIGNING_SECRET
|
||||
Adds headers:
|
||||
- X-QD-Timestamp: unix seconds
|
||||
- X-QD-Signature: hex(HMAC_SHA256("{ts}.{body}", secret))
|
||||
- retry once on 429/5xx
|
||||
用户在个人中心配置:
|
||||
- webhook_url: Webhook 地址
|
||||
- webhook_token: Bearer Token(可选)
|
||||
|
||||
支持功能:
|
||||
- 自定义 headers: notification_config.targets.webhook_headers
|
||||
- Bearer Token: notification_config.targets.webhook_token
|
||||
- 签名验证: notification_config.targets.webhook_signing_secret
|
||||
- 自动重试: 429/5xx 时重试一次
|
||||
"""
|
||||
if not url:
|
||||
return False, "missing_webhook_url"
|
||||
@@ -535,10 +531,8 @@ class SignalNotifier:
|
||||
continue
|
||||
headers[kk] = str(v if v is not None else "")
|
||||
|
||||
# Auth (per-strategy token first, fallback to global token)
|
||||
# Auth (user's token from notification_config.targets.webhook_token)
|
||||
tok = str(token_override or "").strip()
|
||||
if not tok:
|
||||
tok = self.webhook_token
|
||||
if tok and "Authorization" not in headers:
|
||||
headers["Authorization"] = f"Bearer {tok}"
|
||||
|
||||
@@ -662,9 +656,10 @@ class SignalNotifier:
|
||||
token_override: str = "",
|
||||
parse_mode: str = "",
|
||||
) -> Tuple[bool, str]:
|
||||
token = (token_override or "").strip() or (self.telegram_token or "").strip()
|
||||
# 用户必须在个人中心配置自己的 telegram_bot_token
|
||||
token = (token_override or "").strip()
|
||||
if not token:
|
||||
return False, "missing_TELEGRAM_BOT_TOKEN"
|
||||
return False, "missing_telegram_bot_token (请在个人中心配置 Telegram Bot Token)"
|
||||
if not chat_id:
|
||||
return False, "missing_telegram_chat_id"
|
||||
url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||
|
||||
@@ -71,10 +71,6 @@ def load_addon_config() -> Dict[str, Any]:
|
||||
('OPENROUTER_CONNECT_TIMEOUT', 'openrouter.connect_timeout', 'int'),
|
||||
('AI_MODELS_JSON', 'ai.models', 'json'),
|
||||
|
||||
# Market
|
||||
('MARKET_TYPES_JSON', 'market.types', 'json'),
|
||||
('TRADING_SUPPORTED_SYMBOLS_JSON', 'trading.supported_symbols', 'json'),
|
||||
|
||||
# App
|
||||
('CORS_ORIGINS', 'app.cors_origins', 'string'),
|
||||
('RATE_LIMIT', 'app.rate_limit', 'int'),
|
||||
|
||||
@@ -68,21 +68,9 @@ MAKER_WAIT_SEC=10
|
||||
MAKER_OFFSET_BPS=2
|
||||
|
||||
# =========================
|
||||
# Strategy signal notifications (optional)
|
||||
# Email / SMTP (公共邮件服务,所有用户共用)
|
||||
# =========================
|
||||
# The frontend stores per-strategy notification_config.targets.*, but you can also set
|
||||
# a global fallback webhook URL used when a strategy enables "webhook" channel but
|
||||
# doesn't provide targets.webhook.
|
||||
SIGNAL_WEBHOOK_URL=
|
||||
SIGNAL_WEBHOOK_TOKEN=
|
||||
|
||||
# HTTP timeout for outbound notification requests (seconds).
|
||||
SIGNAL_NOTIFY_TIMEOUT_SEC=6
|
||||
|
||||
# Telegram (required if you enable telegram channel)
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
# Email / SMTP (required if you enable email channel)
|
||||
# 用户在个人中心配置自己的通知邮箱,SMTP 服务器由管理员统一配置
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
@@ -170,15 +158,6 @@ OPENROUTER_CONNECT_TIMEOUT=30
|
||||
# Optional: override model list shown in UI (JSON object: {"model_id":"Display Name", ...})
|
||||
AI_MODELS_JSON={}
|
||||
|
||||
# =========================
|
||||
# Market presets (optional)
|
||||
# =========================
|
||||
# Optional: override market types shown in UI (JSON array)
|
||||
MARKET_TYPES_JSON=[]
|
||||
|
||||
# Optional: supported crypto symbols list (JSON array)
|
||||
TRADING_SUPPORTED_SYMBOLS_JSON=[]
|
||||
|
||||
# =========================
|
||||
# Data sources (Kline / pricing)
|
||||
# =========================
|
||||
|
||||
@@ -18,6 +18,7 @@ CREATE TABLE IF NOT EXISTS qd_users (
|
||||
vip_expires_at TIMESTAMP, -- VIP过期时间
|
||||
email_verified BOOLEAN DEFAULT FALSE, -- 邮箱是否已验证
|
||||
referred_by INTEGER, -- 邀请人ID
|
||||
notification_settings TEXT DEFAULT '', -- 用户通知配置 JSON (telegram_chat_id, default_channels等)
|
||||
last_login_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
"build:preview": "vue-cli-service build --no-module --mode preview",
|
||||
"lint:nofix": "vue-cli-service lint --no-fix",
|
||||
"lint:js": "eslint src/**/*.js --fix",
|
||||
"lint:css": "stylelint src/**/*.*ss --fix --custom-syntax postcss-less",
|
||||
"prepare": "husky install"
|
||||
"lint:css": "stylelint src/**/*.*ss --fix --custom-syntax postcss-less"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design-vue/pro-layout": "^1.0.12",
|
||||
@@ -49,8 +48,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/colors": "^3.2.2",
|
||||
"@commitlint/cli": "^12.1.4",
|
||||
"@commitlint/config-conventional": "^12.1.4",
|
||||
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
@@ -63,17 +60,13 @@
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-import": "^1.13.3",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"commitizen": "^4.2.4",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint-plugin-html": "^5.0.5",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-html": "^6.2.0",
|
||||
"eslint-plugin-vue": "^7.20.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"git-revision-webpack-plugin": "^3.0.6",
|
||||
"husky": "^6.0.0",
|
||||
"less": "^3.13.1",
|
||||
"less-loader": "^5.0.0",
|
||||
"lint-staged": "^12.5.0",
|
||||
"postcss": "^8.3.5",
|
||||
"postcss-less": "^6.0.0",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
@@ -86,19 +79,5 @@
|
||||
"vue-svg-icon-loader": "^2.1.1",
|
||||
"vue-svg-loader": "0.16.0",
|
||||
"webpack-theme-color-replacer": "^1.3.26"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"gitHooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-11874
File diff suppressed because it is too large
Load Diff
@@ -125,6 +125,28 @@ export function changePassword (data) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's notification settings
|
||||
*/
|
||||
export function getNotificationSettings () {
|
||||
return request({
|
||||
url: '/api/users/notification-settings',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current user's notification settings
|
||||
* @param {Object} data - { default_channels, telegram_chat_id, email, discord_webhook, webhook_url, phone }
|
||||
*/
|
||||
export function updateNotificationSettings (data) {
|
||||
return request({
|
||||
url: '/api/users/notification-settings',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's credits log
|
||||
* @param {Object} params - { page, page_size }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<a-avatar size="small" :src="currentUser.avatar" class="antd-pro-global-header-index-avatar" />
|
||||
<span>{{ currentUser.name }}</span>
|
||||
</span>
|
||||
<template v-slot:overlay>
|
||||
<template #overlay>
|
||||
<a-menu class="ant-pro-drop-down menu" :selected-keys="[]">
|
||||
<a-menu-item key="profile" @click="handleProfile">
|
||||
<a-icon type="user" />
|
||||
|
||||
@@ -76,9 +76,20 @@ export default {
|
||||
|
||||
/* 浅色主题(默认) */
|
||||
.ant-pro-global-header-index-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.ant-pro-global-header-index-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: @layout-header-height;
|
||||
padding: 0 12px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
vertical-align: top;
|
||||
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
@@ -87,6 +98,20 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.ant-pro-global-header-index-right {
|
||||
.ant-pro-global-header-index-action {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.ant-pro-drop-down,
|
||||
.ant-pro-account-avatar {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 暗黑主题 - 强制覆盖 */
|
||||
/* 只要 body 或 layout 有 dark/realdark 类,就应用这些样式 */
|
||||
body.dark,
|
||||
|
||||
@@ -34,7 +34,7 @@ MultiTab.install = function (Vue) {
|
||||
}
|
||||
api.instance = events
|
||||
Vue.prototype.$multiTab = api
|
||||
Vue.component('multi-tab', MultiTab)
|
||||
Vue.component('MultiTab', MultiTab)
|
||||
}
|
||||
|
||||
export default MultiTab
|
||||
|
||||
@@ -399,15 +399,23 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import '~ant-design-vue/es/style/themes/default.less';
|
||||
|
||||
.notice-icon-wrapper {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.header-notice {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
padding: 0 12px;
|
||||
vertical-align: top;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
@@ -418,6 +426,13 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.header-notice {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.notice-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -14,9 +14,19 @@
|
||||
}
|
||||
|
||||
.@{header-drop-down-prefix-cls} {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
padding: 0 12px;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
> i {
|
||||
font-size: 16px !important;
|
||||
@@ -28,3 +38,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 手机端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.@{header-drop-down-prefix-cls} {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
v-bind="settings"
|
||||
>
|
||||
|
||||
<template v-slot:menuHeaderRender>
|
||||
<template #menuHeaderRender>
|
||||
<div>
|
||||
<img src="~@/assets/slogo.png" />
|
||||
<h1>{{ title }}</h1>
|
||||
@@ -20,7 +20,7 @@
|
||||
<!-- 1.0.0+ 版本 pro-layout 提供 API,
|
||||
增加 Header 左侧内容区自定义
|
||||
-->
|
||||
<template v-slot:headerContentRender>
|
||||
<template #headerContentRender>
|
||||
<div>
|
||||
<a-tooltip :title="$t('menu.header.refreshPage')">
|
||||
<a-icon type="reload" style="font-size: 18px;cursor: pointer;" @click="handleRefresh" />
|
||||
@@ -53,11 +53,11 @@
|
||||
This is SettingDrawer custom footer content.
|
||||
</div>
|
||||
</setting-drawer>
|
||||
<template v-slot:rightContentRender>
|
||||
<template #rightContentRender>
|
||||
<right-content :top-menu="settings.layout === 'topmenu'" :is-mobile="isMobile" :theme="settings.theme" />
|
||||
</template>
|
||||
<!-- custom footer removed -->
|
||||
<template v-slot:footerRender>
|
||||
<template #footerRender>
|
||||
<div style="display: none;"></div>
|
||||
</template>
|
||||
<router-view :key="refreshKey" />
|
||||
|
||||
@@ -1781,7 +1781,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': 'وزن الحداثة',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': 'وزن العائد',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': 'تمكين التحقق التلقائي',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'فاصل التحقق (ثانية)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'فاصل التحقق (ثانية)',
|
||||
|
||||
// Profile - Notification Settings (إعدادات الإشعارات)
|
||||
'profile.notifications.title': 'إعدادات الإشعارات',
|
||||
'profile.notifications.hint': 'قم بتكوين طرق الإشعارات الافتراضية، سيتم استخدامها تلقائياً عند إنشاء مراقبة الأصول والتنبيهات',
|
||||
'profile.notifications.defaultChannels': 'قنوات الإشعارات الافتراضية',
|
||||
'profile.notifications.browser': 'إشعار داخل التطبيق',
|
||||
'profile.notifications.email': 'البريد الإلكتروني',
|
||||
'profile.notifications.phone': 'رسالة نصية',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'أدخل Telegram Bot Token الخاص بك',
|
||||
'profile.notifications.telegramBotTokenHint': 'أنشئ بوت عبر @BotFather للحصول على Token',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'أدخل Telegram Chat ID الخاص بك (مثال: 123456789)',
|
||||
'profile.notifications.telegramHint': 'أرسل /start إلى @userinfobot للحصول على Chat ID',
|
||||
'profile.notifications.notifyEmail': 'بريد الإشعارات',
|
||||
'profile.notifications.emailPlaceholder': 'عنوان البريد الإلكتروني لاستلام الإشعارات',
|
||||
'profile.notifications.emailHint': 'يستخدم بريد الحساب بشكل افتراضي، يمكنك تعيين بريد آخر',
|
||||
'profile.notifications.phonePlaceholder': 'أدخل رقم الهاتف (مثال: +966501234567)',
|
||||
'profile.notifications.phoneHint': 'يحتاج المسؤول إلى تكوين خدمة Twilio',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'أنشئ Webhook في إعدادات خادم Discord',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'عنوان Webhook مخصص، يرسل الإشعارات عبر POST JSON',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (اختياري)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token للتحقق من الطلب',
|
||||
'profile.notifications.webhookTokenHint': 'يُرسل كـ Authorization: Bearer Token إلى Webhook',
|
||||
'profile.notifications.testBtn': 'إرسال إشعار تجريبي',
|
||||
'profile.notifications.saveSuccess': 'تم حفظ إعدادات الإشعارات بنجاح',
|
||||
'profile.notifications.selectChannel': 'الرجاء اختيار قناة إشعار واحدة على الأقل',
|
||||
'profile.notifications.fillTelegramToken': 'الرجاء إدخال Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': 'الرجاء إدخال Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': 'الرجاء إدخال بريد الإشعارات',
|
||||
'profile.notifications.testSent': 'تم إرسال الإشعار التجريبي، يرجى التحقق من قنوات الإشعارات'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1831,7 +1831,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': 'Aktualitätsgewicht',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': 'Renditegewicht',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': 'Automatische Verifikation aktivieren',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Verifikationsintervall (Sek.)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Verifikationsintervall (Sek.)',
|
||||
|
||||
// Profile - Notification Settings (Benachrichtigungseinstellungen)
|
||||
'profile.notifications.title': 'Benachrichtigungseinstellungen',
|
||||
'profile.notifications.hint': 'Konfigurieren Sie Ihre Standard-Benachrichtigungsmethoden, die automatisch beim Erstellen von Asset-Monitoren und Alarmen verwendet werden',
|
||||
'profile.notifications.defaultChannels': 'Standard-Benachrichtigungskanäle',
|
||||
'profile.notifications.browser': 'In-App-Benachrichtigung',
|
||||
'profile.notifications.email': 'E-Mail',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Geben Sie Ihren Telegram Bot Token ein',
|
||||
'profile.notifications.telegramBotTokenHint': 'Erstellen Sie einen Bot über @BotFather um den Token zu erhalten',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'Geben Sie Ihre Telegram Chat ID ein (z.B. 123456789)',
|
||||
'profile.notifications.telegramHint': 'Senden Sie /start an @userinfobot um Ihre Chat ID zu erhalten',
|
||||
'profile.notifications.notifyEmail': 'Benachrichtigungs-E-Mail',
|
||||
'profile.notifications.emailPlaceholder': 'E-Mail-Adresse für Benachrichtigungen',
|
||||
'profile.notifications.emailHint': 'Verwendet standardmäßig die Konto-E-Mail, Sie können eine andere festlegen',
|
||||
'profile.notifications.phonePlaceholder': 'Telefonnummer eingeben (z.B. +49151234567)',
|
||||
'profile.notifications.phoneHint': 'Administrator muss Twilio-Dienst konfigurieren',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Erstellen Sie einen Webhook in den Discord-Servereinstellungen',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'Benutzerdefinierte Webhook-URL, Benachrichtigungen per POST JSON',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (Optional)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token zur Anfrage-Authentifizierung',
|
||||
'profile.notifications.webhookTokenHint': 'Wird als Authorization: Bearer Token an Webhook gesendet',
|
||||
'profile.notifications.testBtn': 'Testbenachrichtigung senden',
|
||||
'profile.notifications.saveSuccess': 'Benachrichtigungseinstellungen gespeichert',
|
||||
'profile.notifications.selectChannel': 'Bitte wählen Sie mindestens einen Benachrichtigungskanal',
|
||||
'profile.notifications.fillTelegramToken': 'Bitte Telegram Bot Token eingeben',
|
||||
'profile.notifications.fillTelegram': 'Bitte Telegram Chat ID eingeben',
|
||||
'profile.notifications.fillEmail': 'Bitte Benachrichtigungs-E-Mail eingeben',
|
||||
'profile.notifications.testSent': 'Testbenachrichtigung gesendet, bitte Benachrichtigungskanäle überprüfen'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -2505,6 +2505,41 @@ const locale = {
|
||||
'profile.referral.noReferrals': 'No referrals yet',
|
||||
'profile.referral.shareNow': 'Share Now',
|
||||
|
||||
// Profile - Notification Settings
|
||||
'profile.notifications.title': 'Notification Settings',
|
||||
'profile.notifications.hint': 'Configure your default notification methods, which will be used automatically when creating asset monitors and alerts',
|
||||
'profile.notifications.defaultChannels': 'Default Notification Channels',
|
||||
'profile.notifications.browser': 'In-App Notification',
|
||||
'profile.notifications.email': 'Email',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Enter your Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenHint': 'Get Token by creating a bot via @BotFather',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'Enter your Telegram Chat ID (e.g. 123456789)',
|
||||
'profile.notifications.telegramHint': 'Send /start to @userinfobot to get your Chat ID',
|
||||
'profile.notifications.notifyEmail': 'Notification Email',
|
||||
'profile.notifications.emailPlaceholder': 'Email address to receive notifications',
|
||||
'profile.notifications.emailHint': 'Uses your account email by default, or set a different one',
|
||||
'profile.notifications.phonePlaceholder': 'Enter phone number (e.g. +1234567890)',
|
||||
'profile.notifications.phoneHint': 'Requires admin to configure Twilio service',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Create Webhook in Discord server settings',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'Custom Webhook URL, notifications sent via POST JSON',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (Optional)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token for request authentication',
|
||||
'profile.notifications.webhookTokenHint': 'Sent as Authorization: Bearer Token to Webhook',
|
||||
'profile.notifications.testBtn': 'Send Test Notification',
|
||||
'profile.notifications.saveSuccess': 'Notification settings saved successfully',
|
||||
'profile.notifications.selectChannel': 'Please select at least one notification channel',
|
||||
'profile.notifications.fillTelegramToken': 'Please fill in Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': 'Please fill in Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': 'Please fill in notification email',
|
||||
'profile.notifications.testSent': 'Test notification sent, please check your notification channels',
|
||||
|
||||
// User Manage - Credits & VIP
|
||||
'userManage.credits': 'Credits',
|
||||
'userManage.adjustCredits': 'Adjust Credits',
|
||||
|
||||
@@ -1782,7 +1782,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': 'Poids de récence',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': 'Poids des rendements',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': 'Activer la vérification automatique',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Intervalle de vérification (s)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Intervalle de vérification (s)',
|
||||
|
||||
// Profile - Notification Settings (Paramètres de notification)
|
||||
'profile.notifications.title': 'Paramètres de notification',
|
||||
'profile.notifications.hint': 'Configurez vos méthodes de notification par défaut, utilisées automatiquement lors de la création de surveillances et alertes',
|
||||
'profile.notifications.defaultChannels': 'Canaux de notification par défaut',
|
||||
'profile.notifications.browser': 'Notification dans l\'app',
|
||||
'profile.notifications.email': 'E-mail',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Token du Bot Telegram',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Entrez votre Token Bot Telegram',
|
||||
'profile.notifications.telegramBotTokenHint': 'Créez un bot via @BotFather pour obtenir le Token',
|
||||
'profile.notifications.telegramChatId': 'Chat ID Telegram',
|
||||
'profile.notifications.telegramPlaceholder': 'Entrez votre Chat ID Telegram (ex: 123456789)',
|
||||
'profile.notifications.telegramHint': 'Envoyez /start à @userinfobot pour obtenir votre Chat ID',
|
||||
'profile.notifications.notifyEmail': 'E-mail de notification',
|
||||
'profile.notifications.emailPlaceholder': 'Adresse e-mail pour recevoir les notifications',
|
||||
'profile.notifications.emailHint': 'Utilise l\'e-mail du compte par défaut, vous pouvez en définir un autre',
|
||||
'profile.notifications.phonePlaceholder': 'Entrez le numéro de téléphone (ex: +33612345678)',
|
||||
'profile.notifications.phoneHint': 'L\'administrateur doit configurer le service Twilio',
|
||||
'profile.notifications.discordWebhook': 'Webhook Discord',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Créez un Webhook dans les paramètres du serveur Discord',
|
||||
'profile.notifications.webhookUrl': 'URL Webhook',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'URL Webhook personnalisée, notifications envoyées via POST JSON',
|
||||
'profile.notifications.webhookToken': 'Token Webhook (Optionnel)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token pour l\'authentification',
|
||||
'profile.notifications.webhookTokenHint': 'Envoyé comme Authorization: Bearer Token au Webhook',
|
||||
'profile.notifications.testBtn': 'Envoyer une notification test',
|
||||
'profile.notifications.saveSuccess': 'Paramètres de notification enregistrés',
|
||||
'profile.notifications.selectChannel': 'Veuillez sélectionner au moins un canal de notification',
|
||||
'profile.notifications.fillTelegramToken': 'Veuillez remplir le Token Bot Telegram',
|
||||
'profile.notifications.fillTelegram': 'Veuillez remplir le Chat ID Telegram',
|
||||
'profile.notifications.fillEmail': 'Veuillez remplir l\'e-mail de notification',
|
||||
'profile.notifications.testSent': 'Notification test envoyée, vérifiez vos canaux de notification'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1786,7 +1786,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': '新しさ重み',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': '収益重み',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': '自動検証を有効化',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': '検証間隔(秒)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': '検証間隔(秒)',
|
||||
|
||||
// Profile - Notification Settings (通知設定)
|
||||
'profile.notifications.title': '通知設定',
|
||||
'profile.notifications.hint': 'デフォルトの通知方法を設定します。資産モニターやアラート作成時に自動的に使用されます',
|
||||
'profile.notifications.defaultChannels': 'デフォルト通知チャンネル',
|
||||
'profile.notifications.browser': 'アプリ内通知',
|
||||
'profile.notifications.email': 'メール',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Telegram Bot Tokenを入力してください',
|
||||
'profile.notifications.telegramBotTokenHint': '@BotFatherでボットを作成してTokenを取得してください',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'Telegram Chat IDを入力してください(例:123456789)',
|
||||
'profile.notifications.telegramHint': '@userinfobot に /start を送信してChat IDを取得してください',
|
||||
'profile.notifications.notifyEmail': '通知メール',
|
||||
'profile.notifications.emailPlaceholder': '通知を受け取るメールアドレス',
|
||||
'profile.notifications.emailHint': 'デフォルトはアカウントメール、別のメールも設定可能',
|
||||
'profile.notifications.phonePlaceholder': '電話番号を入力してください(例:+81901234567)',
|
||||
'profile.notifications.phoneHint': '管理者がTwilioサービスを設定する必要があります',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Discordサーバー設定でWebhookを作成してください',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'カスタムWebhook URL、POST JSONで通知を送信',
|
||||
'profile.notifications.webhookToken': 'Webhook Token(任意)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'リクエスト認証用Bearer Token',
|
||||
'profile.notifications.webhookTokenHint': 'Authorization: Bearer TokenとしてWebhookに送信',
|
||||
'profile.notifications.testBtn': 'テスト通知を送信',
|
||||
'profile.notifications.saveSuccess': '通知設定を保存しました',
|
||||
'profile.notifications.selectChannel': '少なくとも1つの通知チャンネルを選択してください',
|
||||
'profile.notifications.fillTelegramToken': 'Telegram Bot Tokenを入力してください',
|
||||
'profile.notifications.fillTelegram': 'Telegram Chat IDを入力してください',
|
||||
'profile.notifications.fillEmail': '通知メールを入力してください',
|
||||
'profile.notifications.testSent': 'テスト通知を送信しました。通知チャンネルを確認してください'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1785,7 +1785,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': '시간 가중치',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': '수익 가중치',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': '자동 검증 사용',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': '검증 주기(초)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': '검증 주기(초)',
|
||||
|
||||
// Profile - Notification Settings (알림 설정)
|
||||
'profile.notifications.title': '알림 설정',
|
||||
'profile.notifications.hint': '기본 알림 방법을 설정하세요. 자산 모니터링 및 알림 생성 시 자동으로 사용됩니다',
|
||||
'profile.notifications.defaultChannels': '기본 알림 채널',
|
||||
'profile.notifications.browser': '앱 내 알림',
|
||||
'profile.notifications.email': '이메일',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Telegram Bot Token을 입력하세요',
|
||||
'profile.notifications.telegramBotTokenHint': '@BotFather에서 봇을 만들어 Token을 받으세요',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'Telegram Chat ID를 입력하세요 (예: 123456789)',
|
||||
'profile.notifications.telegramHint': '@userinfobot에 /start를 보내 Chat ID를 받으세요',
|
||||
'profile.notifications.notifyEmail': '알림 이메일',
|
||||
'profile.notifications.emailPlaceholder': '알림을 받을 이메일 주소',
|
||||
'profile.notifications.emailHint': '기본값은 계정 이메일이며 다른 이메일도 설정 가능',
|
||||
'profile.notifications.phonePlaceholder': '전화번호를 입력하세요 (예: +821012345678)',
|
||||
'profile.notifications.phoneHint': '관리자가 Twilio 서비스를 설정해야 합니다',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Discord 서버 설정에서 Webhook을 생성하세요',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': '커스텀 Webhook URL, POST JSON으로 알림 전송',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (선택)',
|
||||
'profile.notifications.webhookTokenPlaceholder': '요청 인증용 Bearer Token',
|
||||
'profile.notifications.webhookTokenHint': 'Authorization: Bearer Token으로 Webhook에 전송',
|
||||
'profile.notifications.testBtn': '테스트 알림 보내기',
|
||||
'profile.notifications.saveSuccess': '알림 설정이 저장되었습니다',
|
||||
'profile.notifications.selectChannel': '최소 하나의 알림 채널을 선택하세요',
|
||||
'profile.notifications.fillTelegramToken': 'Telegram Bot Token을 입력하세요',
|
||||
'profile.notifications.fillTelegram': 'Telegram Chat ID를 입력하세요',
|
||||
'profile.notifications.fillEmail': '알림 이메일을 입력하세요',
|
||||
'profile.notifications.testSent': '테스트 알림이 전송되었습니다. 알림 채널을 확인하세요'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1782,7 +1782,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': 'ค่าน้ำหนักเวลา',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': 'ค่าน้ำหนักผลตอบแทน',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': 'เปิดใช้การตรวจสอบอัตโนมัติ',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'ช่วงเวลาตรวจสอบ (วินาที)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'ช่วงเวลาตรวจสอบ (วินาที)',
|
||||
|
||||
// Profile - Notification Settings (การตั้งค่าการแจ้งเตือน)
|
||||
'profile.notifications.title': 'การตั้งค่าการแจ้งเตือน',
|
||||
'profile.notifications.hint': 'กำหนดค่าวิธีการแจ้งเตือนเริ่มต้น จะถูกใช้โดยอัตโนมัติเมื่อสร้างการติดตามสินทรัพย์และการแจ้งเตือน',
|
||||
'profile.notifications.defaultChannels': 'ช่องทางแจ้งเตือนเริ่มต้น',
|
||||
'profile.notifications.browser': 'การแจ้งเตือนในแอป',
|
||||
'profile.notifications.email': 'อีเมล',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'กรอก Telegram Bot Token ของคุณ',
|
||||
'profile.notifications.telegramBotTokenHint': 'สร้างบอทผ่าน @BotFather เพื่อรับ Token',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'กรอก Telegram Chat ID ของคุณ (เช่น 123456789)',
|
||||
'profile.notifications.telegramHint': 'ส่ง /start ไปที่ @userinfobot เพื่อรับ Chat ID',
|
||||
'profile.notifications.notifyEmail': 'อีเมลแจ้งเตือน',
|
||||
'profile.notifications.emailPlaceholder': 'ที่อยู่อีเมลสำหรับรับการแจ้งเตือน',
|
||||
'profile.notifications.emailHint': 'ใช้อีเมลบัญชีเป็นค่าเริ่มต้น สามารถตั้งค่าอีเมลอื่นได้',
|
||||
'profile.notifications.phonePlaceholder': 'กรอกหมายเลขโทรศัพท์ (เช่น +66812345678)',
|
||||
'profile.notifications.phoneHint': 'ผู้ดูแลระบบต้องกำหนดค่าบริการ Twilio',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'สร้าง Webhook ในการตั้งค่าเซิร์ฟเวอร์ Discord',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'URL Webhook แบบกำหนดเอง ส่งการแจ้งเตือนผ่าน POST JSON',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (ไม่บังคับ)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token สำหรับยืนยันตัวตนคำขอ',
|
||||
'profile.notifications.webhookTokenHint': 'ส่งเป็น Authorization: Bearer Token ไปยัง Webhook',
|
||||
'profile.notifications.testBtn': 'ส่งการแจ้งเตือนทดสอบ',
|
||||
'profile.notifications.saveSuccess': 'บันทึกการตั้งค่าการแจ้งเตือนสำเร็จ',
|
||||
'profile.notifications.selectChannel': 'กรุณาเลือกอย่างน้อยหนึ่งช่องทางแจ้งเตือน',
|
||||
'profile.notifications.fillTelegramToken': 'กรุณากรอก Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': 'กรุณากรอก Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': 'กรุณากรอกอีเมลแจ้งเตือน',
|
||||
'profile.notifications.testSent': 'ส่งการแจ้งเตือนทดสอบแล้ว กรุณาตรวจสอบช่องทางแจ้งเตือนของคุณ'
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -2915,7 +2915,42 @@ const locale = {
|
||||
'settings.field.AGENT_MEMORY_W_RECENCY': 'Trọng số thời gian',
|
||||
'settings.field.AGENT_MEMORY_W_RETURNS': 'Trọng số lợi nhuận',
|
||||
'settings.field.ENABLE_REFLECTION_WORKER': 'Bật xác minh tự động',
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Khoảng thời gian xác minh (giây)'
|
||||
'settings.field.REFLECTION_WORKER_INTERVAL_SEC': 'Khoảng thời gian xác minh (giây)',
|
||||
|
||||
// Profile - Notification Settings (Cài đặt thông báo)
|
||||
'profile.notifications.title': 'Cài đặt thông báo',
|
||||
'profile.notifications.hint': 'Cấu hình phương thức thông báo mặc định, sẽ được sử dụng tự động khi tạo giám sát tài sản và cảnh báo',
|
||||
'profile.notifications.defaultChannels': 'Kênh thông báo mặc định',
|
||||
'profile.notifications.browser': 'Thông báo trong ứng dụng',
|
||||
'profile.notifications.email': 'Email',
|
||||
'profile.notifications.phone': 'SMS',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': 'Nhập Telegram Bot Token của bạn',
|
||||
'profile.notifications.telegramBotTokenHint': 'Tạo bot qua @BotFather để lấy Token',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': 'Nhập Telegram Chat ID của bạn (ví dụ: 123456789)',
|
||||
'profile.notifications.telegramHint': 'Gửi /start cho @userinfobot để lấy Chat ID',
|
||||
'profile.notifications.notifyEmail': 'Email thông báo',
|
||||
'profile.notifications.emailPlaceholder': 'Địa chỉ email nhận thông báo',
|
||||
'profile.notifications.emailHint': 'Mặc định sử dụng email tài khoản, có thể đặt email khác',
|
||||
'profile.notifications.phonePlaceholder': 'Nhập số điện thoại (ví dụ: +84123456789)',
|
||||
'profile.notifications.phoneHint': 'Cần quản trị viên cấu hình dịch vụ Twilio',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': 'Tạo Webhook trong cài đặt máy chủ Discord',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': 'URL Webhook tùy chỉnh, gửi thông báo qua POST JSON',
|
||||
'profile.notifications.webhookToken': 'Webhook Token (Tùy chọn)',
|
||||
'profile.notifications.webhookTokenPlaceholder': 'Bearer Token để xác thực yêu cầu',
|
||||
'profile.notifications.webhookTokenHint': 'Gửi dưới dạng Authorization: Bearer Token đến Webhook',
|
||||
'profile.notifications.testBtn': 'Gửi thông báo thử nghiệm',
|
||||
'profile.notifications.saveSuccess': 'Đã lưu cài đặt thông báo thành công',
|
||||
'profile.notifications.selectChannel': 'Vui lòng chọn ít nhất một kênh thông báo',
|
||||
'profile.notifications.fillTelegramToken': 'Vui lòng điền Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': 'Vui lòng điền Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': 'Vui lòng điền email thông báo',
|
||||
'profile.notifications.testSent': 'Đã gửi thông báo thử nghiệm, vui lòng kiểm tra các kênh thông báo'
|
||||
|
||||
}
|
||||
export default {
|
||||
|
||||
@@ -2315,6 +2315,41 @@ const locale = {
|
||||
'profile.referral.noReferrals': '暂无邀请记录',
|
||||
'profile.referral.shareNow': '立即分享邀请',
|
||||
|
||||
// Profile - Notification Settings (通知设置)
|
||||
'profile.notifications.title': '通知设置',
|
||||
'profile.notifications.hint': '配置您的默认通知方式,在创建资产监控和预警时将自动使用这些设置',
|
||||
'profile.notifications.defaultChannels': '默认通知渠道',
|
||||
'profile.notifications.browser': '站内通知',
|
||||
'profile.notifications.email': '邮件',
|
||||
'profile.notifications.phone': '短信',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': '请输入您的 Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenHint': '通过 @BotFather 创建机器人获取 Token',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': '请输入您的 Telegram Chat ID(如 123456789)',
|
||||
'profile.notifications.telegramHint': '发送 /start 给 @userinfobot 可获取您的 Chat ID',
|
||||
'profile.notifications.notifyEmail': '通知邮箱',
|
||||
'profile.notifications.emailPlaceholder': '接收通知的邮箱地址',
|
||||
'profile.notifications.emailHint': '默认使用账户邮箱,可设置其他邮箱接收通知',
|
||||
'profile.notifications.phonePlaceholder': '请输入手机号(如 +8613800138000)',
|
||||
'profile.notifications.phoneHint': '需要管理员配置 Twilio 服务后才能使用短信通知',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': '在 Discord 服务器设置中创建 Webhook',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': '自定义 Webhook 地址,将以 POST JSON 方式推送通知',
|
||||
'profile.notifications.webhookToken': 'Webhook Token(可选)',
|
||||
'profile.notifications.webhookTokenPlaceholder': '用于验证请求的 Bearer Token',
|
||||
'profile.notifications.webhookTokenHint': '将作为 Authorization: Bearer Token 发送到 Webhook',
|
||||
'profile.notifications.testBtn': '发送测试通知',
|
||||
'profile.notifications.saveSuccess': '通知设置保存成功',
|
||||
'profile.notifications.selectChannel': '请至少选择一个通知渠道',
|
||||
'profile.notifications.fillTelegramToken': '请填写 Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': '请填写 Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': '请填写通知邮箱',
|
||||
'profile.notifications.testSent': '测试通知已发送,请检查您的通知渠道',
|
||||
|
||||
// User Manage - Credits & VIP
|
||||
'userManage.credits': '积分',
|
||||
'userManage.adjustCredits': '调整积分',
|
||||
|
||||
@@ -2188,6 +2188,41 @@ const locale = {
|
||||
'profile.referral.noReferrals': '暫無邀請記錄',
|
||||
'profile.referral.shareNow': '立即分享邀請',
|
||||
|
||||
// Profile - Notification Settings (通知設定)
|
||||
'profile.notifications.title': '通知設定',
|
||||
'profile.notifications.hint': '配置您的預設通知方式,在建立資產監控和預警時將自動使用這些設定',
|
||||
'profile.notifications.defaultChannels': '預設通知管道',
|
||||
'profile.notifications.browser': '站內通知',
|
||||
'profile.notifications.email': '郵件',
|
||||
'profile.notifications.phone': '簡訊',
|
||||
'profile.notifications.telegramBotToken': 'Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenPlaceholder': '請輸入您的 Telegram Bot Token',
|
||||
'profile.notifications.telegramBotTokenHint': '透過 @BotFather 建立機器人取得 Token',
|
||||
'profile.notifications.telegramChatId': 'Telegram Chat ID',
|
||||
'profile.notifications.telegramPlaceholder': '請輸入您的 Telegram Chat ID(如 123456789)',
|
||||
'profile.notifications.telegramHint': '發送 /start 給 @userinfobot 可取得您的 Chat ID',
|
||||
'profile.notifications.notifyEmail': '通知信箱',
|
||||
'profile.notifications.emailPlaceholder': '接收通知的信箱地址',
|
||||
'profile.notifications.emailHint': '預設使用帳戶信箱,可設定其他信箱接收通知',
|
||||
'profile.notifications.phonePlaceholder': '請輸入手機號(如 +886912345678)',
|
||||
'profile.notifications.phoneHint': '需要管理員配置 Twilio 服務後才能使用簡訊通知',
|
||||
'profile.notifications.discordWebhook': 'Discord Webhook',
|
||||
'profile.notifications.discordPlaceholder': 'https://discord.com/api/webhooks/...',
|
||||
'profile.notifications.discordHint': '在 Discord 伺服器設定中建立 Webhook',
|
||||
'profile.notifications.webhookUrl': 'Webhook URL',
|
||||
'profile.notifications.webhookPlaceholder': 'https://your-server.com/webhook',
|
||||
'profile.notifications.webhookHint': '自訂 Webhook 地址,將以 POST JSON 方式推送通知',
|
||||
'profile.notifications.webhookToken': 'Webhook Token(可選)',
|
||||
'profile.notifications.webhookTokenPlaceholder': '用於驗證請求的 Bearer Token',
|
||||
'profile.notifications.webhookTokenHint': '將作為 Authorization: Bearer Token 傳送到 Webhook',
|
||||
'profile.notifications.testBtn': '發送測試通知',
|
||||
'profile.notifications.saveSuccess': '通知設定儲存成功',
|
||||
'profile.notifications.selectChannel': '請至少選擇一個通知管道',
|
||||
'profile.notifications.fillTelegramToken': '請填寫 Telegram Bot Token',
|
||||
'profile.notifications.fillTelegram': '請填寫 Telegram Chat ID',
|
||||
'profile.notifications.fillEmail': '請填寫通知信箱',
|
||||
'profile.notifications.testSent': '測試通知已發送,請檢查您的通知管道',
|
||||
|
||||
// Settings - Billing
|
||||
'settings.group.billing': '計費配置',
|
||||
'settings.field.BILLING_ENABLED': '啟用計費',
|
||||
|
||||
@@ -40,9 +40,11 @@ if (typeof window !== 'undefined') {
|
||||
// mount axios to `Vue.$http` and `this.$http`
|
||||
Vue.use(VueAxios)
|
||||
// use pro-layout components
|
||||
/* eslint-disable vue/component-definition-name-casing */
|
||||
Vue.component('pro-layout', ProLayout)
|
||||
Vue.component('page-container', PageHeaderWrapper)
|
||||
Vue.component('page-header-wrapper', PageHeaderWrapper)
|
||||
/* eslint-enable vue/component-definition-name-casing */
|
||||
|
||||
window.umi_plugin_ant_themeVar = themePluginConfig.theme
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import storage from 'store'
|
||||
import expirePlugin from 'store/plugins/expire'
|
||||
import { login, emailLogin, mobileLogin, logout, getUserInfo, apiLogout, updateUserInfo, oauthCallback } from '@/api/login'
|
||||
import { login, logout, getUserInfo } from '@/api/login'
|
||||
import { ACCESS_TOKEN, USER_INFO, USER_ROLES } from '@/store/mutation-types'
|
||||
import { welcome } from '@/utils/util'
|
||||
|
||||
@@ -117,100 +117,6 @@ const user = {
|
||||
})
|
||||
},
|
||||
|
||||
// 邮箱验证码登录
|
||||
EmailLogin ({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
emailLogin(userInfo).then(response => {
|
||||
// 新API响应格式: { code: 1, msg: "登录成功", data: { token, userInfo } }
|
||||
if (response.code === 1 && response.data) {
|
||||
const token = response.data.token
|
||||
storage.set(ACCESS_TOKEN, token, new Date().getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||
commit('SET_TOKEN', token)
|
||||
// 保存用户信息(从登录接口返回的 userInfo)
|
||||
if (response.data.userInfo) {
|
||||
const userInfoData = response.data.userInfo
|
||||
commit('SET_INFO', userInfoData)
|
||||
|
||||
// 设置用户名
|
||||
if (userInfoData.nickname) {
|
||||
commit('SET_NAME', { name: userInfoData.nickname, welcome: welcome() })
|
||||
} else if (userInfoData.username) {
|
||||
commit('SET_NAME', { name: userInfoData.username, welcome: welcome() })
|
||||
}
|
||||
|
||||
// 设置头像
|
||||
if (userInfoData.avatar) {
|
||||
commit('SET_AVATAR', userInfoData.avatar)
|
||||
}
|
||||
|
||||
// 设置角色(如果有)
|
||||
if (userInfoData.role) {
|
||||
commit('SET_ROLES', userInfoData.role)
|
||||
} else if (userInfoData.roles) {
|
||||
commit('SET_ROLES', userInfoData.roles)
|
||||
} else {
|
||||
// 如果没有角色信息,设置一个默认角色对象,避免路由守卫卡住
|
||||
// 设置一个标记,表示已经初始化过用户信息
|
||||
commit('SET_ROLES', [{ id: 'default', permissionList: [] }])
|
||||
}
|
||||
}
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(new Error(response.msg || '登录失败'))
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 手机号验证码登录
|
||||
MobileLogin ({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
mobileLogin(userInfo).then(response => {
|
||||
// 新API响应格式: { code: 1, msg: "登录成功", data: { token, userInfo } }
|
||||
if (response.code === 1 && response.data) {
|
||||
const token = response.data.token
|
||||
storage.set(ACCESS_TOKEN, token, new Date().getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||
commit('SET_TOKEN', token)
|
||||
// 保存用户信息(从登录接口返回的 userInfo)
|
||||
if (response.data.userInfo) {
|
||||
const userInfoData = response.data.userInfo
|
||||
commit('SET_INFO', userInfoData)
|
||||
|
||||
// 设置用户名
|
||||
if (userInfoData.nickname) {
|
||||
commit('SET_NAME', { name: userInfoData.nickname, welcome: welcome() })
|
||||
} else if (userInfoData.username) {
|
||||
commit('SET_NAME', { name: userInfoData.username, welcome: welcome() })
|
||||
}
|
||||
|
||||
// 设置头像
|
||||
if (userInfoData.avatar) {
|
||||
commit('SET_AVATAR', userInfoData.avatar)
|
||||
}
|
||||
|
||||
// 设置角色(如果有)
|
||||
if (userInfoData.role) {
|
||||
commit('SET_ROLES', userInfoData.role)
|
||||
} else if (userInfoData.roles) {
|
||||
commit('SET_ROLES', userInfoData.roles)
|
||||
} else {
|
||||
// 如果没有角色信息,设置一个默认角色对象,避免路由守卫卡住
|
||||
// 设置一个标记,表示已经初始化过用户信息
|
||||
commit('SET_ROLES', [{ id: 'default', permissionList: [] }])
|
||||
}
|
||||
}
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(new Error(response.msg || '登录失败'))
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// Web3 登录完成后的统一处理
|
||||
Web3LoginFinalize ({ commit }, payload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -272,25 +178,6 @@ const user = {
|
||||
})
|
||||
},
|
||||
|
||||
// 更新用户基本信息
|
||||
UpdateUserInfo ({ commit, state }, payload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
updateUserInfo(payload).then(res => {
|
||||
if (res && res.code === 1) {
|
||||
// 合并本地 store 的 info
|
||||
const newInfo = { ...state.info, ...payload }
|
||||
commit('SET_INFO', newInfo)
|
||||
if (newInfo.nickname) {
|
||||
commit('SET_NAME', { name: newInfo.nickname, welcome: welcome() })
|
||||
}
|
||||
resolve(res)
|
||||
} else {
|
||||
reject(new Error((res && res.msg) || '修改失败'))
|
||||
}
|
||||
}).catch(err => reject(err))
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息(从 store 中获取,不再请求接口)
|
||||
GetInfo ({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -349,58 +236,10 @@ const user = {
|
||||
})
|
||||
},
|
||||
|
||||
// OAuth 登录(Google/GitHub)
|
||||
OAuthLogin ({ commit }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
oauthCallback(userInfo).then(response => {
|
||||
// 新API响应格式: { code: 1, msg: "登录成功", data: { token, userInfo } }
|
||||
if (response.code === 1 && response.data) {
|
||||
const token = response.data.token
|
||||
storage.set(ACCESS_TOKEN, token, new Date().getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||
commit('SET_TOKEN', token)
|
||||
// 保存用户信息(从登录接口返回的 userInfo)
|
||||
if (response.data.userInfo) {
|
||||
const userInfoData = response.data.userInfo
|
||||
commit('SET_INFO', userInfoData)
|
||||
|
||||
// 设置用户名
|
||||
if (userInfoData.nickname) {
|
||||
commit('SET_NAME', { name: userInfoData.nickname, welcome: welcome() })
|
||||
} else if (userInfoData.username) {
|
||||
commit('SET_NAME', { name: userInfoData.username, welcome: welcome() })
|
||||
}
|
||||
|
||||
// 设置头像
|
||||
if (userInfoData.avatar) {
|
||||
commit('SET_AVATAR', userInfoData.avatar)
|
||||
}
|
||||
|
||||
// 设置角色(如果有)
|
||||
if (userInfoData.role) {
|
||||
commit('SET_ROLES', userInfoData.role)
|
||||
} else if (userInfoData.roles) {
|
||||
commit('SET_ROLES', userInfoData.roles)
|
||||
} else {
|
||||
// 如果没有角色信息,设置一个默认角色对象,避免路由守卫卡住
|
||||
commit('SET_ROLES', [{ id: 'default', permissionList: [] }])
|
||||
}
|
||||
}
|
||||
resolve(response)
|
||||
} else {
|
||||
reject(new Error(response.msg || '登录失败'))
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 登出
|
||||
Logout ({ commit, state, dispatch }) {
|
||||
Logout ({ commit, dispatch }) {
|
||||
return new Promise((resolve) => {
|
||||
// 兼容旧登出与新后端登出
|
||||
const req = typeof apiLogout === 'function' ? apiLogout() : logout(state.token)
|
||||
req.then(() => {
|
||||
logout().then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_INFO', {})
|
||||
|
||||
@@ -992,12 +992,20 @@ export default {
|
||||
formatTime (timestamp) {
|
||||
if (!timestamp) return '-'
|
||||
try {
|
||||
const raw = typeof timestamp === 'string' ? parseInt(timestamp, 10) : Number(timestamp)
|
||||
if (!raw || Number.isNaN(raw)) return '-'
|
||||
const ms = raw < 1e12 ? raw * 1000 : raw
|
||||
const d = new Date(ms)
|
||||
if (isNaN(d.getTime())) return '-'
|
||||
return d.toLocaleString()
|
||||
let date
|
||||
// Handle ISO 8601 date strings (e.g., "2024-01-17T01:58:10.000Z")
|
||||
if (typeof timestamp === 'string' && timestamp.includes('-') && timestamp.includes(':')) {
|
||||
date = new Date(timestamp)
|
||||
} else if (typeof timestamp === 'number' || (typeof timestamp === 'string' && /^\d+$/.test(timestamp))) {
|
||||
// Handle numeric timestamps (seconds or milliseconds)
|
||||
const numTimestamp = typeof timestamp === 'string' ? parseInt(timestamp, 10) : timestamp
|
||||
const ms = numTimestamp < 1e12 ? numTimestamp * 1000 : numTimestamp
|
||||
date = new Date(ms)
|
||||
} else {
|
||||
return '-'
|
||||
}
|
||||
if (isNaN(date.getTime())) return '-'
|
||||
return date.toLocaleString()
|
||||
} catch (e) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@
|
||||
<a-icon type="eye" />
|
||||
<span>{{ $t('portfolio.monitors.title') }}</span>
|
||||
</h3>
|
||||
<a-button type="primary" @click="showAddMonitorModal = true">
|
||||
<a-button type="primary" @click="openAddMonitorModal">
|
||||
<a-icon type="plus" />
|
||||
{{ $t('portfolio.monitors.add') }}
|
||||
</a-button>
|
||||
@@ -401,7 +401,7 @@
|
||||
<div class="monitors-list">
|
||||
<div v-if="monitors.length === 0" class="empty-state small">
|
||||
<a-empty :description="$t('portfolio.monitors.empty')" :image="simpleImage">
|
||||
<a-button type="primary" size="small" @click="showAddMonitorModal = true">
|
||||
<a-button type="primary" size="small" @click="openAddMonitorModal">
|
||||
<a-icon type="plus" />
|
||||
{{ $t('portfolio.monitors.addFirst') }}
|
||||
</a-button>
|
||||
@@ -718,31 +718,22 @@
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Telegram Chat ID -->
|
||||
<a-form-item
|
||||
v-show="alertChannels.includes('telegram')"
|
||||
:label="$t('portfolio.form.telegramChatId')"
|
||||
<!-- Notification settings hint -->
|
||||
<a-alert
|
||||
v-if="alertChannels.includes('telegram') || alertChannels.includes('email')"
|
||||
type="info"
|
||||
showIcon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<a-input
|
||||
v-model="alertTelegramChatId"
|
||||
:placeholder="$t('portfolio.form.enterTelegramChatId')"
|
||||
>
|
||||
<a-icon slot="prefix" type="message" />
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Email -->
|
||||
<a-form-item
|
||||
v-show="alertChannels.includes('email')"
|
||||
:label="$t('portfolio.form.emailAddress')"
|
||||
>
|
||||
<a-input
|
||||
v-model="alertEmail"
|
||||
:placeholder="$t('portfolio.form.enterEmail')"
|
||||
>
|
||||
<a-icon slot="prefix" type="mail" />
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<template #message>
|
||||
<span>
|
||||
{{ $t('portfolio.form.notificationFromProfile') || '通知将发送到您在个人中心配置的地址' }}
|
||||
<router-link to="/profile" style="margin-left: 8px">
|
||||
<a-icon type="setting" /> {{ $t('portfolio.form.goToProfile') || '前往配置' }}
|
||||
</router-link>
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<!-- 启用状态 -->
|
||||
<a-form-item :label="$t('portfolio.alerts.enabled')">
|
||||
@@ -806,21 +797,22 @@
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Telegram Chat ID -->
|
||||
<a-form-item :label="$t('portfolio.form.telegramChatId')" v-show="monitorChannels.includes('telegram')">
|
||||
<a-input
|
||||
v-model="monitorTelegramChatId"
|
||||
:placeholder="$t('portfolio.form.enterTelegramChatId')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Email -->
|
||||
<a-form-item :label="$t('portfolio.form.emailAddress')" v-show="monitorChannels.includes('email')">
|
||||
<a-input
|
||||
v-model="monitorEmail"
|
||||
:placeholder="$t('portfolio.form.enterEmail')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- Notification settings hint -->
|
||||
<a-alert
|
||||
v-if="monitorChannels.includes('telegram') || monitorChannels.includes('email')"
|
||||
type="info"
|
||||
showIcon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #message>
|
||||
<span>
|
||||
{{ $t('portfolio.form.notificationFromProfile') || '通知将发送到您在个人中心配置的地址' }}
|
||||
<router-link to="/profile" style="margin-left: 8px">
|
||||
<a-icon type="setting" /> {{ $t('portfolio.form.goToProfile') || '前往配置' }}
|
||||
</router-link>
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<!-- 监控范围 -->
|
||||
<a-form-item :label="$t('portfolio.form.monitorScope')">
|
||||
@@ -894,6 +886,7 @@ import {
|
||||
getGroups,
|
||||
searchSymbols, getMarketTypes
|
||||
} from '@/api/portfolio'
|
||||
import { getNotificationSettings } from '@/api/user'
|
||||
|
||||
export default {
|
||||
name: 'Portfolio',
|
||||
@@ -954,18 +947,23 @@ export default {
|
||||
alertPosition: null,
|
||||
// Alert channels (for reactive display)
|
||||
alertChannels: ['browser'],
|
||||
// Alert notification targets
|
||||
alertTelegramChatId: '',
|
||||
alertEmail: '',
|
||||
// Monitor channels (for reactive display)
|
||||
monitorChannels: ['browser'],
|
||||
// Monitor notification targets
|
||||
monitorTelegramChatId: '',
|
||||
monitorEmail: '',
|
||||
// Monitor scope (all or selected positions)
|
||||
monitorScope: 'all',
|
||||
// Selected positions for monitoring
|
||||
selectedMonitorPositions: []
|
||||
selectedMonitorPositions: [],
|
||||
// User's default notification settings (loaded from profile)
|
||||
userNotificationSettings: {
|
||||
default_channels: ['browser'],
|
||||
telegram_bot_token: '',
|
||||
telegram_chat_id: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
discord_webhook: '',
|
||||
webhook_url: '',
|
||||
webhook_token: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -1053,6 +1051,8 @@ export default {
|
||||
this.priceRefreshTimer = setInterval(() => {
|
||||
this.refreshPrices()
|
||||
}, 30000)
|
||||
// Load user's notification settings for default values
|
||||
this.loadUserNotificationSettings()
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.priceRefreshTimer) {
|
||||
@@ -1063,6 +1063,26 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadUserNotificationSettings () {
|
||||
// Load user's default notification settings
|
||||
try {
|
||||
const res = await getNotificationSettings()
|
||||
if (res.code === 1 && res.data) {
|
||||
this.userNotificationSettings = {
|
||||
default_channels: res.data.default_channels || ['browser'],
|
||||
telegram_bot_token: res.data.telegram_bot_token || '',
|
||||
telegram_chat_id: res.data.telegram_chat_id || '',
|
||||
email: res.data.email || '',
|
||||
phone: res.data.phone || '',
|
||||
discord_webhook: res.data.discord_webhook || '',
|
||||
webhook_url: res.data.webhook_url || '',
|
||||
webhook_token: res.data.webhook_token || ''
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail, use default values
|
||||
}
|
||||
},
|
||||
async loadData () {
|
||||
await Promise.all([
|
||||
this.loadPositions(),
|
||||
@@ -1314,12 +1334,10 @@ export default {
|
||||
this.editAlert(existingAlert)
|
||||
return
|
||||
}
|
||||
// 创建新的 Alert
|
||||
// 创建新的 Alert - 使用用户默认通知设置
|
||||
this.editingAlert = null
|
||||
this.alertPosition = pos
|
||||
this.alertChannels = ['browser']
|
||||
this.alertTelegramChatId = ''
|
||||
this.alertEmail = ''
|
||||
this.alertChannels = [...(this.userNotificationSettings.default_channels || ['browser'])]
|
||||
this.showAddAlertModal = true
|
||||
this.$nextTick(() => {
|
||||
this.alertForm.setFieldsValue({
|
||||
@@ -1341,10 +1359,8 @@ export default {
|
||||
current_price: 0,
|
||||
entry_price: 0
|
||||
}
|
||||
// Set channels and targets directly with v-model binding
|
||||
// Set channels directly with v-model binding
|
||||
this.alertChannels = [...(alert.notification_config?.channels || ['browser'])]
|
||||
this.alertTelegramChatId = alert.notification_config?.targets?.telegram || ''
|
||||
this.alertEmail = alert.notification_config?.targets?.email || ''
|
||||
// Show modal
|
||||
this.showAddAlertModal = true
|
||||
// Set form values for fields still using v-decorator
|
||||
@@ -1366,13 +1382,28 @@ export default {
|
||||
if (err) return
|
||||
this.savingAlert = true
|
||||
try {
|
||||
// 构建通知目标 - 使用 v-model 绑定的值
|
||||
// 构建通知目标 - 使用用户在个人中心配置的值
|
||||
const targets = {}
|
||||
if (this.alertChannels.includes('telegram') && this.alertTelegramChatId) {
|
||||
targets.telegram = this.alertTelegramChatId.trim()
|
||||
if (this.alertChannels.includes('telegram') && this.userNotificationSettings.telegram_chat_id) {
|
||||
targets.telegram = this.userNotificationSettings.telegram_chat_id
|
||||
if (this.userNotificationSettings.telegram_bot_token) {
|
||||
targets.telegram_bot_token = this.userNotificationSettings.telegram_bot_token
|
||||
}
|
||||
}
|
||||
if (this.alertChannels.includes('email') && this.alertEmail) {
|
||||
targets.email = this.alertEmail.trim()
|
||||
if (this.alertChannels.includes('email') && this.userNotificationSettings.email) {
|
||||
targets.email = this.userNotificationSettings.email
|
||||
}
|
||||
if (this.alertChannels.includes('phone') && this.userNotificationSettings.phone) {
|
||||
targets.phone = this.userNotificationSettings.phone
|
||||
}
|
||||
if (this.alertChannels.includes('discord') && this.userNotificationSettings.discord_webhook) {
|
||||
targets.discord = this.userNotificationSettings.discord_webhook
|
||||
}
|
||||
if (this.alertChannels.includes('webhook') && this.userNotificationSettings.webhook_url) {
|
||||
targets.webhook = this.userNotificationSettings.webhook_url
|
||||
if (this.userNotificationSettings.webhook_token) {
|
||||
targets.webhook_token = this.userNotificationSettings.webhook_token
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
@@ -1430,9 +1461,7 @@ export default {
|
||||
this.showAddAlertModal = false
|
||||
this.editingAlert = null
|
||||
this.alertPosition = null
|
||||
this.alertChannels = ['browser']
|
||||
this.alertTelegramChatId = ''
|
||||
this.alertEmail = ''
|
||||
this.alertChannels = [...(this.userNotificationSettings.default_channels || ['browser'])]
|
||||
this.alertForm.resetFields()
|
||||
},
|
||||
confirmDeleteAlert () {
|
||||
@@ -1468,6 +1497,19 @@ export default {
|
||||
}
|
||||
},
|
||||
// Monitor methods
|
||||
openAddMonitorModal () {
|
||||
// Initialize with user's default notification settings
|
||||
this.editingMonitor = null
|
||||
this.monitorChannels = [...(this.userNotificationSettings.default_channels || ['browser'])]
|
||||
this.monitorScope = 'all'
|
||||
this.selectedMonitorPositions = []
|
||||
this.showAddMonitorModal = true
|
||||
this.$nextTick(() => {
|
||||
if (this.monitorForm) {
|
||||
this.monitorForm.resetFields()
|
||||
}
|
||||
})
|
||||
},
|
||||
handleMonitorChannelsChange (channels) {
|
||||
this.monitorChannels = channels || []
|
||||
},
|
||||
@@ -1475,8 +1517,6 @@ export default {
|
||||
this.editingMonitor = monitor
|
||||
// Set channels directly with v-model binding
|
||||
this.monitorChannels = [...(monitor.notification_config?.channels || ['browser'])]
|
||||
this.monitorTelegramChatId = monitor.notification_config?.targets?.telegram || ''
|
||||
this.monitorEmail = monitor.notification_config?.targets?.email || ''
|
||||
// Update monitor scope and selected positions
|
||||
let positionIds = []
|
||||
if (monitor.position_ids) {
|
||||
@@ -1540,11 +1580,16 @@ export default {
|
||||
language: this.$store.getters.lang || 'en-US'
|
||||
},
|
||||
notification_config: {
|
||||
// Use v-model bound values instead of form values
|
||||
// Use user's profile notification settings
|
||||
channels: this.monitorChannels.length > 0 ? this.monitorChannels : ['browser'],
|
||||
targets: {
|
||||
telegram: this.monitorTelegramChatId || '',
|
||||
email: this.monitorEmail || ''
|
||||
telegram: this.userNotificationSettings.telegram_chat_id || '',
|
||||
telegram_bot_token: this.userNotificationSettings.telegram_bot_token || '',
|
||||
email: this.userNotificationSettings.email || '',
|
||||
phone: this.userNotificationSettings.phone || '',
|
||||
discord: this.userNotificationSettings.discord_webhook || '',
|
||||
webhook: this.userNotificationSettings.webhook_url || '',
|
||||
webhook_token: this.userNotificationSettings.webhook_token || ''
|
||||
}
|
||||
},
|
||||
is_active: true
|
||||
@@ -1644,9 +1689,7 @@ export default {
|
||||
this.showAddMonitorModal = false
|
||||
this.editingMonitor = null
|
||||
this.monitorForm.resetFields()
|
||||
this.monitorChannels = ['browser'] // Reset to default
|
||||
this.monitorTelegramChatId = '' // Reset telegram
|
||||
this.monitorEmail = '' // Reset email
|
||||
this.monitorChannels = [...(this.userNotificationSettings.default_channels || ['browser'])] // Reset to user default
|
||||
this.monitorScope = 'all' // Reset monitor scope
|
||||
this.selectedMonitorPositions = [] // Reset selected positions
|
||||
},
|
||||
@@ -1704,7 +1747,25 @@ export default {
|
||||
},
|
||||
formatTime (timestamp) {
|
||||
if (!timestamp) return '-'
|
||||
const d = new Date(timestamp * 1000)
|
||||
let d
|
||||
// 如果是数字(秒级时间戳),乘以 1000
|
||||
if (typeof timestamp === 'number') {
|
||||
d = new Date(timestamp * 1000)
|
||||
} else if (typeof timestamp === 'string') {
|
||||
// 如果是纯数字字符串(秒级时间戳)
|
||||
if (/^\d+$/.test(timestamp)) {
|
||||
d = new Date(parseInt(timestamp, 10) * 1000)
|
||||
} else {
|
||||
// ISO 日期字符串或其他格式,直接解析
|
||||
d = new Date(timestamp)
|
||||
}
|
||||
} else {
|
||||
return '-'
|
||||
}
|
||||
// 检查日期是否有效
|
||||
if (isNaN(d.getTime())) {
|
||||
return '-'
|
||||
}
|
||||
return d.toLocaleString()
|
||||
},
|
||||
getIntervalText (minutes) {
|
||||
|
||||
@@ -284,6 +284,174 @@
|
||||
</a-table>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- Notification Settings Tab (通知设置) -->
|
||||
<a-tab-pane key="notifications" :tab="$t('profile.notifications.title') || '通知设置'">
|
||||
<div class="notification-settings-form">
|
||||
<a-alert
|
||||
:message="$t('profile.notifications.hint') || '配置您的默认通知方式,在创建资产监控和预警时将自动使用这些设置'"
|
||||
type="info"
|
||||
showIcon
|
||||
style="margin-bottom: 24px"
|
||||
/>
|
||||
|
||||
<a-form :form="notificationForm" layout="vertical" style="max-width: 600px;">
|
||||
<!-- Default Channels -->
|
||||
<a-form-item :label="$t('profile.notifications.defaultChannels') || '默认通知渠道'">
|
||||
<a-checkbox-group
|
||||
v-decorator="['default_channels', { initialValue: notificationSettings.default_channels || ['browser'] }]"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="browser">
|
||||
<a-icon type="bell" /> {{ $t('profile.notifications.browser') || '站内通知' }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="telegram">
|
||||
<a-icon type="send" /> Telegram
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="email">
|
||||
<a-icon type="mail" /> {{ $t('profile.notifications.email') || '邮件' }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16" style="margin-top: 8px">
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="phone">
|
||||
<a-icon type="phone" /> {{ $t('profile.notifications.phone') || '短信' }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="discord">
|
||||
<a-icon type="message" /> Discord
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-checkbox value="webhook">
|
||||
<a-icon type="api" /> Webhook
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Telegram Bot Token -->
|
||||
<a-form-item :label="$t('profile.notifications.telegramBotToken') || 'Telegram Bot Token'">
|
||||
<a-input-password
|
||||
v-decorator="['telegram_bot_token', { initialValue: notificationSettings.telegram_bot_token }]"
|
||||
:placeholder="$t('profile.notifications.telegramBotTokenPlaceholder') || '请输入您的 Telegram Bot Token'"
|
||||
>
|
||||
<a-icon slot="prefix" type="robot" />
|
||||
</a-input-password>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>
|
||||
{{ $t('profile.notifications.telegramBotTokenHint') || '通过 @BotFather 创建机器人获取 Token' }}
|
||||
<a href="https://t.me/BotFather" target="_blank" rel="noopener noreferrer">@BotFather</a>
|
||||
</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Telegram Chat ID -->
|
||||
<a-form-item :label="$t('profile.notifications.telegramChatId') || 'Telegram Chat ID'">
|
||||
<a-input
|
||||
v-decorator="['telegram_chat_id', { initialValue: notificationSettings.telegram_chat_id }]"
|
||||
:placeholder="$t('profile.notifications.telegramPlaceholder') || '请输入您的 Telegram Chat ID(如 123456789)'"
|
||||
>
|
||||
<a-icon slot="prefix" type="message" />
|
||||
</a-input>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.telegramHint') || '发送 /start 给 @userinfobot 可获取您的 Chat ID' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Notification Email -->
|
||||
<a-form-item :label="$t('profile.notifications.notifyEmail') || '通知邮箱'">
|
||||
<a-input
|
||||
v-decorator="['email', { initialValue: notificationSettings.email || profile.email }]"
|
||||
:placeholder="$t('profile.notifications.emailPlaceholder') || '接收通知的邮箱地址'"
|
||||
>
|
||||
<a-icon slot="prefix" type="mail" />
|
||||
</a-input>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.emailHint') || '默认使用账户邮箱,可设置其他邮箱接收通知' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Phone Number (SMS) -->
|
||||
<a-form-item :label="$t('profile.notifications.phone') || '手机号(短信通知)'">
|
||||
<a-input
|
||||
v-decorator="['phone', { initialValue: notificationSettings.phone }]"
|
||||
:placeholder="$t('profile.notifications.phonePlaceholder') || '请输入手机号(如 +8613800138000)'"
|
||||
>
|
||||
<a-icon slot="prefix" type="phone" />
|
||||
</a-input>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.phoneHint') || '需要管理员配置 Twilio 服务后才能使用短信通知' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Discord Webhook -->
|
||||
<a-form-item :label="$t('profile.notifications.discordWebhook') || 'Discord Webhook'">
|
||||
<a-input
|
||||
v-decorator="['discord_webhook', { initialValue: notificationSettings.discord_webhook }]"
|
||||
:placeholder="$t('profile.notifications.discordPlaceholder') || 'https://discord.com/api/webhooks/...'"
|
||||
>
|
||||
<a-icon slot="prefix" type="message" />
|
||||
</a-input>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.discordHint') || '在 Discord 服务器设置中创建 Webhook' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Webhook URL -->
|
||||
<a-form-item :label="$t('profile.notifications.webhookUrl') || 'Webhook URL'">
|
||||
<a-input
|
||||
v-decorator="['webhook_url', { initialValue: notificationSettings.webhook_url }]"
|
||||
:placeholder="$t('profile.notifications.webhookPlaceholder') || 'https://your-server.com/webhook'"
|
||||
>
|
||||
<a-icon slot="prefix" type="api" />
|
||||
</a-input>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.webhookHint') || '自定义 Webhook 地址,将以 POST JSON 方式推送通知' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Webhook Token -->
|
||||
<a-form-item :label="$t('profile.notifications.webhookToken') || 'Webhook Token(可选)'">
|
||||
<a-input-password
|
||||
v-decorator="['webhook_token', { initialValue: notificationSettings.webhook_token }]"
|
||||
:placeholder="$t('profile.notifications.webhookTokenPlaceholder') || '用于验证请求的 Bearer Token'"
|
||||
>
|
||||
<a-icon slot="prefix" type="key" />
|
||||
</a-input-password>
|
||||
<div class="field-hint">
|
||||
<a-icon type="info-circle" />
|
||||
<span>{{ $t('profile.notifications.webhookTokenHint') || '将作为 Authorization: Bearer Token 发送到 Webhook' }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button type="primary" :loading="savingNotifications" @click="handleSaveNotifications">
|
||||
<a-icon type="save" />
|
||||
{{ $t('common.save') || '保存' }}
|
||||
</a-button>
|
||||
<a-button style="margin-left: 12px" @click="handleTestNotification" :loading="testingNotification">
|
||||
<a-icon type="experiment" />
|
||||
{{ $t('profile.notifications.testBtn') || '发送测试通知' }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- Referral List Tab (邀请列表) -->
|
||||
<a-tab-pane key="referrals" :tab="$t('profile.referral.listTab') || '邀请列表'">
|
||||
<a-table
|
||||
@@ -327,7 +495,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProfile, updateProfile, getMyCreditsLog, getMyReferrals } from '@/api/user'
|
||||
import { getProfile, updateProfile, getMyCreditsLog, getMyReferrals, getNotificationSettings, updateNotificationSettings } from '@/api/user'
|
||||
import { getSettingsValues } from '@/api/settings'
|
||||
import { baseMixin } from '@/store/app-mixin'
|
||||
|
||||
@@ -383,7 +551,20 @@ export default {
|
||||
feature_costs: {},
|
||||
recharge_telegram_url: ''
|
||||
},
|
||||
rechargeTelegramUrl: 'https://t.me/your_support_bot'
|
||||
rechargeTelegramUrl: 'https://t.me/your_support_bot',
|
||||
// Notification settings
|
||||
notificationSettings: {
|
||||
default_channels: ['browser'],
|
||||
telegram_bot_token: '',
|
||||
telegram_chat_id: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
discord_webhook: '',
|
||||
webhook_url: '',
|
||||
webhook_token: ''
|
||||
},
|
||||
savingNotifications: false,
|
||||
testingNotification: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -456,11 +637,15 @@ export default {
|
||||
if (val === 'referrals' && (!this.referralData.list || this.referralData.list.length === 0)) {
|
||||
this.loadReferrals()
|
||||
}
|
||||
if (val === 'notifications' && !this.notificationSettings.telegram_chat_id && !this.notificationSettings.discord_webhook) {
|
||||
this.loadNotificationSettings()
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.profileForm = this.$form.createForm(this, { name: 'profile' })
|
||||
this.passwordForm = this.$form.createForm(this, { name: 'password' })
|
||||
this.notificationForm = this.$form.createForm(this, { name: 'notification' })
|
||||
},
|
||||
mounted () {
|
||||
this.loadProfile()
|
||||
@@ -486,6 +671,19 @@ export default {
|
||||
this.rechargeTelegramUrl = this.billing.recharge_telegram_url
|
||||
}
|
||||
}
|
||||
// 提取通知设置
|
||||
if (res.data.notification_settings) {
|
||||
this.notificationSettings = {
|
||||
default_channels: res.data.notification_settings.default_channels || ['browser'],
|
||||
telegram_bot_token: res.data.notification_settings.telegram_bot_token || '',
|
||||
telegram_chat_id: res.data.notification_settings.telegram_chat_id || '',
|
||||
email: res.data.notification_settings.email || res.data.email || '',
|
||||
phone: res.data.notification_settings.phone || '',
|
||||
discord_webhook: res.data.notification_settings.discord_webhook || '',
|
||||
webhook_url: res.data.notification_settings.webhook_url || '',
|
||||
webhook_token: res.data.notification_settings.webhook_token || ''
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.profileForm.setFieldsValue({
|
||||
nickname: this.profile.nickname,
|
||||
@@ -781,6 +979,124 @@ export default {
|
||||
referral_bonus: this.$t('profile.creditsLog.actionReferralBonus') || '邀请奖励'
|
||||
}
|
||||
return labels[action] || action
|
||||
},
|
||||
|
||||
// Notification settings methods
|
||||
async loadNotificationSettings () {
|
||||
try {
|
||||
const res = await getNotificationSettings()
|
||||
if (res.code === 1 && res.data) {
|
||||
this.notificationSettings = {
|
||||
default_channels: res.data.default_channels || ['browser'],
|
||||
telegram_bot_token: res.data.telegram_bot_token || '',
|
||||
telegram_chat_id: res.data.telegram_chat_id || '',
|
||||
email: res.data.email || this.profile.email || '',
|
||||
phone: res.data.phone || '',
|
||||
discord_webhook: res.data.discord_webhook || '',
|
||||
webhook_url: res.data.webhook_url || '',
|
||||
webhook_token: res.data.webhook_token || ''
|
||||
}
|
||||
// Update form values
|
||||
this.$nextTick(() => {
|
||||
this.notificationForm.setFieldsValue({
|
||||
default_channels: this.notificationSettings.default_channels,
|
||||
telegram_bot_token: this.notificationSettings.telegram_bot_token,
|
||||
telegram_chat_id: this.notificationSettings.telegram_chat_id,
|
||||
email: this.notificationSettings.email,
|
||||
phone: this.notificationSettings.phone,
|
||||
discord_webhook: this.notificationSettings.discord_webhook,
|
||||
webhook_url: this.notificationSettings.webhook_url,
|
||||
webhook_token: this.notificationSettings.webhook_token
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Use default values
|
||||
}
|
||||
},
|
||||
|
||||
handleSaveNotifications () {
|
||||
this.notificationForm.validateFields(async (err, values) => {
|
||||
if (err) return
|
||||
|
||||
this.savingNotifications = true
|
||||
try {
|
||||
const res = await updateNotificationSettings({
|
||||
default_channels: values.default_channels || ['browser'],
|
||||
telegram_bot_token: values.telegram_bot_token || '',
|
||||
telegram_chat_id: values.telegram_chat_id || '',
|
||||
email: values.email || '',
|
||||
phone: values.phone || '',
|
||||
discord_webhook: values.discord_webhook || '',
|
||||
webhook_url: values.webhook_url || '',
|
||||
webhook_token: values.webhook_token || ''
|
||||
})
|
||||
if (res.code === 1) {
|
||||
this.$message.success(this.$t('profile.notifications.saveSuccess') || '通知设置保存成功')
|
||||
this.notificationSettings = res.data || this.notificationSettings
|
||||
} else {
|
||||
this.$message.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error('保存失败')
|
||||
} finally {
|
||||
this.savingNotifications = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async handleTestNotification () {
|
||||
const values = this.notificationForm.getFieldsValue()
|
||||
const channels = values.default_channels || []
|
||||
|
||||
if (channels.length === 0) {
|
||||
this.$message.warning(this.$t('profile.notifications.selectChannel') || '请至少选择一个通知渠道')
|
||||
return
|
||||
}
|
||||
|
||||
// Check if required fields are filled
|
||||
if (channels.includes('telegram')) {
|
||||
if (!values.telegram_bot_token) {
|
||||
this.$message.warning(this.$t('profile.notifications.fillTelegramToken') || '请填写 Telegram Bot Token')
|
||||
return
|
||||
}
|
||||
if (!values.telegram_chat_id) {
|
||||
this.$message.warning(this.$t('profile.notifications.fillTelegram') || '请填写 Telegram Chat ID')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (channels.includes('email') && !values.email) {
|
||||
this.$message.warning(this.$t('profile.notifications.fillEmail') || '请填写通知邮箱')
|
||||
return
|
||||
}
|
||||
|
||||
this.testingNotification = true
|
||||
try {
|
||||
// First save settings, then test
|
||||
const saveRes = await updateNotificationSettings({
|
||||
default_channels: channels,
|
||||
telegram_bot_token: values.telegram_bot_token || '',
|
||||
telegram_chat_id: values.telegram_chat_id || '',
|
||||
email: values.email || '',
|
||||
phone: values.phone || '',
|
||||
discord_webhook: values.discord_webhook || '',
|
||||
webhook_url: values.webhook_url || '',
|
||||
webhook_token: values.webhook_token || ''
|
||||
})
|
||||
|
||||
if (saveRes.code !== 1) {
|
||||
this.$message.error(saveRes.msg || '保存设置失败')
|
||||
return
|
||||
}
|
||||
|
||||
this.$message.info(this.$t('profile.notifications.testSent') || '测试通知已发送,请检查您的通知渠道')
|
||||
// Note: Actual test notification would require a backend endpoint
|
||||
// For now, we just show a success message after saving
|
||||
} catch (e) {
|
||||
this.$message.error('发送测试通知失败')
|
||||
} finally {
|
||||
this.testingNotification = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,6 +1275,30 @@ export default {
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// Notification settings form
|
||||
.notification-settings-form {
|
||||
.field-hint {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.anticon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-checkbox-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-checkbox-wrapper {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Credits Card 积分卡片
|
||||
|
||||
@@ -146,20 +146,27 @@ export default {
|
||||
if (!time) return '--'
|
||||
|
||||
try {
|
||||
// 只处理时间戳格式(数字或字符串数字)
|
||||
if (typeof time !== 'number' && (typeof time !== 'string' || !/^\d+$/.test(time))) {
|
||||
let date
|
||||
|
||||
if (typeof time === 'number') {
|
||||
// 数字类型:判断是秒级还是毫秒级时间戳
|
||||
const timestampMs = time < 1e12 ? time * 1000 : time
|
||||
date = new Date(timestampMs)
|
||||
} else if (typeof time === 'string') {
|
||||
// 字符串类型
|
||||
if (/^\d+$/.test(time)) {
|
||||
// 纯数字字符串(时间戳)
|
||||
const timestamp = parseInt(time, 10)
|
||||
const timestampMs = timestamp < 1e12 ? timestamp * 1000 : timestamp
|
||||
date = new Date(timestampMs)
|
||||
} else {
|
||||
// ISO 日期字符串或其他格式,直接解析
|
||||
date = new Date(time)
|
||||
}
|
||||
} else {
|
||||
return '--'
|
||||
}
|
||||
|
||||
// 转换为数字
|
||||
const timestamp = typeof time === 'string' ? parseInt(time, 10) : time
|
||||
|
||||
// 判断是秒级还是毫秒级时间戳
|
||||
// 如果时间戳小于 1e12,认为是秒级,需要乘以 1000
|
||||
// 如果大于等于 1e12,认为是毫秒级
|
||||
const timestampMs = timestamp < 1e12 ? timestamp * 1000 : timestamp
|
||||
const date = new Date(timestampMs)
|
||||
|
||||
// 检查日期是否有效
|
||||
if (isNaN(date.getTime())) {
|
||||
return '--'
|
||||
|
||||
@@ -892,63 +892,28 @@
|
||||
>
|
||||
<a-checkbox value="browser">{{ $t('trading-assistant.notify.browser') }}</a-checkbox>
|
||||
<a-checkbox value="email">{{ $t('trading-assistant.notify.email') }}</a-checkbox>
|
||||
<a-checkbox value="phone">{{ $t('trading-assistant.notify.phone') }}</a-checkbox>
|
||||
<a-checkbox value="telegram">{{ $t('trading-assistant.notify.telegram') }}</a-checkbox>
|
||||
<a-checkbox value="discord">{{ $t('trading-assistant.notify.discord') }}</a-checkbox>
|
||||
<a-checkbox value="webhook">{{ $t('trading-assistant.notify.webhook') }}</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<div class="form-item-hint">{{ $t('trading-assistant.form.notifyChannelsHint') }}</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="notifyChannelsUi.includes('email')"
|
||||
:label="$t('trading-assistant.form.notifyEmail')"
|
||||
<!-- Notification settings hint -->
|
||||
<a-alert
|
||||
v-if="notifyChannelsUi.includes('telegram') || notifyChannelsUi.includes('email') || notifyChannelsUi.includes('discord')"
|
||||
type="info"
|
||||
showIcon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<a-input
|
||||
v-decorator="['notify_email', { rules: [{ type: 'email', message: $t('trading-assistant.validation.emailInvalid') }] }]"
|
||||
:placeholder="$t('trading-assistant.placeholders.inputEmail')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="notifyChannelsUi.includes('phone')"
|
||||
:label="$t('trading-assistant.form.notifyPhone')"
|
||||
>
|
||||
<a-input
|
||||
v-decorator="['notify_phone']"
|
||||
:placeholder="$t('trading-assistant.placeholders.inputPhone')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="notifyChannelsUi.includes('telegram')"
|
||||
:label="$t('trading-assistant.form.notifyTelegram')"
|
||||
>
|
||||
<a-input
|
||||
v-decorator="['notify_telegram']"
|
||||
:placeholder="$t('trading-assistant.placeholders.inputTelegram')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="notifyChannelsUi.includes('discord')"
|
||||
:label="$t('trading-assistant.form.notifyDiscord')"
|
||||
>
|
||||
<a-input
|
||||
v-decorator="['notify_discord']"
|
||||
:placeholder="$t('trading-assistant.placeholders.inputDiscord')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="notifyChannelsUi.includes('webhook')"
|
||||
:label="$t('trading-assistant.form.notifyWebhook')"
|
||||
>
|
||||
<a-input
|
||||
v-decorator="['notify_webhook']"
|
||||
:placeholder="$t('trading-assistant.placeholders.inputWebhook')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<template #message>
|
||||
<span>
|
||||
{{ $t('trading-assistant.form.notificationFromProfile') || '通知将发送到您在个人中心配置的地址' }}
|
||||
<router-link to="/profile" style="margin-left: 8px">
|
||||
<a-icon type="setting" /> {{ $t('trading-assistant.form.goToProfile') || '前往配置' }}
|
||||
</router-link>
|
||||
</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<a-divider v-if="executionModeUi === 'live' && canUseLiveTrading" />
|
||||
|
||||
@@ -1267,6 +1232,7 @@
|
||||
import { getStrategyList, startStrategy, stopStrategy, deleteStrategy, updateStrategy, testExchangeConnection, getStrategyEquityCurve, batchCreateStrategies, batchStartStrategies, batchStopStrategies, batchDeleteStrategies } from '@/api/strategy'
|
||||
import { getWatchlist } from '@/api/market'
|
||||
import { listExchangeCredentials, getExchangeCredential, createExchangeCredential } from '@/api/credentials'
|
||||
import { getNotificationSettings } from '@/api/user'
|
||||
import { baseMixin } from '@/store/app-mixin'
|
||||
import request from '@/utils/request'
|
||||
import TradingRecords from './components/TradingRecords.vue'
|
||||
@@ -1563,6 +1529,17 @@ export default {
|
||||
supportedIPs: [], // 白名单IP列表
|
||||
executionModeUi: 'signal',
|
||||
notifyChannelsUi: ['browser'],
|
||||
// User's notification settings from profile
|
||||
userNotificationSettings: {
|
||||
default_channels: ['browser'],
|
||||
telegram_bot_token: '',
|
||||
telegram_chat_id: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
discord_webhook: '',
|
||||
webhook_url: '',
|
||||
webhook_token: ''
|
||||
},
|
||||
// Exchange credentials vault
|
||||
loadingExchangeCredentials: false,
|
||||
exchangeCredentials: [],
|
||||
@@ -1580,11 +1557,32 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
this.loadStrategies()
|
||||
this.loadUserNotificationSettings()
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.stopEquityPolling()
|
||||
},
|
||||
methods: {
|
||||
async loadUserNotificationSettings () {
|
||||
// Load user's default notification settings from profile
|
||||
try {
|
||||
const res = await getNotificationSettings()
|
||||
if (res.code === 1 && res.data) {
|
||||
this.userNotificationSettings = {
|
||||
default_channels: res.data.default_channels || ['browser'],
|
||||
telegram_bot_token: res.data.telegram_bot_token || '',
|
||||
telegram_chat_id: res.data.telegram_chat_id || '',
|
||||
email: res.data.email || '',
|
||||
phone: res.data.phone || '',
|
||||
discord_webhook: res.data.discord_webhook || '',
|
||||
webhook_url: res.data.webhook_url || '',
|
||||
webhook_token: res.data.webhook_token || ''
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail, use default values
|
||||
}
|
||||
},
|
||||
async loadWatchlist () {
|
||||
this.loadingWatchlist = true
|
||||
try {
|
||||
@@ -2965,14 +2963,17 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Use user's notification settings from profile for targets
|
||||
const notificationConfig = {
|
||||
channels: values.notify_channels || [],
|
||||
targets: {
|
||||
email: values.notify_email || '',
|
||||
phone: values.notify_phone || '',
|
||||
telegram: values.notify_telegram || '',
|
||||
discord: values.notify_discord || '',
|
||||
webhook: values.notify_webhook || ''
|
||||
email: this.userNotificationSettings.email || '',
|
||||
phone: this.userNotificationSettings.phone || '',
|
||||
telegram: this.userNotificationSettings.telegram_chat_id || '',
|
||||
telegram_bot_token: this.userNotificationSettings.telegram_bot_token || '',
|
||||
discord: this.userNotificationSettings.discord_webhook || '',
|
||||
webhook: this.userNotificationSettings.webhook_url || '',
|
||||
webhook_token: this.userNotificationSettings.webhook_token || ''
|
||||
}
|
||||
}
|
||||
if (!notificationConfig.channels || notificationConfig.channels.length === 0) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user