fix bugs
This commit is contained in:
@@ -0,0 +1,212 @@
|
|||||||
|
# 缠论Web应用部署指南
|
||||||
|
|
||||||
|
本指南提供在服务器上直接部署缠论Web应用的步骤,无需使用Nginx等反向代理。
|
||||||
|
|
||||||
|
## 注意:Ubuntu 22.04+系统的特殊处理
|
||||||
|
|
||||||
|
最新版本的Ubuntu系统对Python包管理有额外限制,不允许直接用pip在系统级别安装包。因此,我们提供了使用虚拟环境的部署方法。
|
||||||
|
|
||||||
|
## 方法一:使用虚拟环境部署脚本(适用于Ubuntu 22.04+,推荐)
|
||||||
|
|
||||||
|
这是最适合现代Ubuntu系统的方法:
|
||||||
|
|
||||||
|
1. 登录到服务器
|
||||||
|
2. 进入应用目录:
|
||||||
|
```
|
||||||
|
cd /path/to/user_data/Chan/web
|
||||||
|
```
|
||||||
|
3. 给脚本添加执行权限:
|
||||||
|
```
|
||||||
|
chmod +x deploy_venv.sh stop_venv.sh status_venv.sh
|
||||||
|
```
|
||||||
|
4. 启动服务:
|
||||||
|
```
|
||||||
|
./deploy_venv.sh
|
||||||
|
```
|
||||||
|
5. 检查服务状态:
|
||||||
|
```
|
||||||
|
./status_venv.sh
|
||||||
|
```
|
||||||
|
6. 如需停止服务:
|
||||||
|
```
|
||||||
|
./stop_venv.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 方法二:标准部署脚本(适用于旧版Ubuntu或其他系统)
|
||||||
|
|
||||||
|
如果您的系统允许直接安装Python包:
|
||||||
|
|
||||||
|
1. 登录到服务器
|
||||||
|
2. 进入应用目录:
|
||||||
|
```
|
||||||
|
cd /path/to/user_data/Chan/web
|
||||||
|
```
|
||||||
|
3. 给脚本添加执行权限:
|
||||||
|
```
|
||||||
|
chmod +x deploy.sh stop.sh status.sh
|
||||||
|
```
|
||||||
|
4. 启动服务:
|
||||||
|
```
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
5. 检查服务状态:
|
||||||
|
```
|
||||||
|
./status.sh
|
||||||
|
```
|
||||||
|
6. 如需停止服务:
|
||||||
|
```
|
||||||
|
./stop.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 方法三:系统服务(systemd)
|
||||||
|
|
||||||
|
如果希望应用随系统启动自动运行,可以设置systemd服务:
|
||||||
|
|
||||||
|
### 使用虚拟环境的systemd服务(推荐)
|
||||||
|
|
||||||
|
1. 编辑服务文件中的用户名、用户组和路径:
|
||||||
|
```
|
||||||
|
vi chanlun-web-venv.service
|
||||||
|
```
|
||||||
|
修改以下行:
|
||||||
|
- `User=<用户名>` - 改为您的用户名
|
||||||
|
- `Group=<用户组>` - 改为您的用户组
|
||||||
|
- 所有包含`/path/to/`的路径
|
||||||
|
|
||||||
|
2. 将服务文件复制到systemd目录:
|
||||||
|
```
|
||||||
|
sudo cp chanlun-web-venv.service /etc/systemd/system/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 重新加载systemd配置:
|
||||||
|
```
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 启用并启动服务:
|
||||||
|
```
|
||||||
|
sudo systemctl enable chanlun-web-venv
|
||||||
|
sudo systemctl start chanlun-web-venv
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 检查服务状态:
|
||||||
|
```
|
||||||
|
sudo systemctl status chanlun-web-venv
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用系统Python的systemd服务(仅适用于旧版系统)
|
||||||
|
|
||||||
|
1. 编辑服务文件:
|
||||||
|
```
|
||||||
|
vi chanlun-web.service
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 按照上述相同的步骤配置和启动服务。
|
||||||
|
|
||||||
|
## 方法四:手动启动
|
||||||
|
|
||||||
|
如果只需要临时运行,可以手动启动:
|
||||||
|
|
||||||
|
### 使用虚拟环境(推荐)
|
||||||
|
|
||||||
|
1. 创建并激活虚拟环境:
|
||||||
|
```
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 安装依赖:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 使用gunicorn启动(前台运行):
|
||||||
|
```
|
||||||
|
venv/bin/gunicorn -w 4 -b 0.0.0.0:8123 app:app
|
||||||
|
```
|
||||||
|
|
||||||
|
或后台运行:
|
||||||
|
```
|
||||||
|
venv/bin/gunicorn -w 4 -b 0.0.0.0:8123 --access-logfile logs/access.log --error-logfile logs/error.log --daemon app:app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 直接使用系统Python(仅适用于旧版系统)
|
||||||
|
|
||||||
|
1. 安装依赖:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 使用gunicorn启动(前台运行):
|
||||||
|
```
|
||||||
|
gunicorn -w 4 -b 0.0.0.0:8123 app:app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 防火墙设置
|
||||||
|
|
||||||
|
请确保服务器防火墙开放了8123端口:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 对于使用ufw的系统(如Ubuntu)
|
||||||
|
sudo ufw allow 8123/tcp
|
||||||
|
|
||||||
|
# 对于使用firewalld的系统(如CentOS)
|
||||||
|
sudo firewall-cmd --permanent --add-port=8123/tcp
|
||||||
|
sudo firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## 访问应用
|
||||||
|
|
||||||
|
部署完成后,可以通过以下URL访问应用:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://服务器IP:8123
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可选:使用screen运行
|
||||||
|
|
||||||
|
如果SSH连接可能不稳定,可以使用screen在后台会话中运行:
|
||||||
|
|
||||||
|
### 使用虚拟环境的screen会话(推荐)
|
||||||
|
|
||||||
|
```
|
||||||
|
screen -S chanlun-web
|
||||||
|
cd /path/to/user_data/Chan/web
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
然后按下`Ctrl+A`然后按`D`来分离会话。要重新连接会话:
|
||||||
|
|
||||||
|
```
|
||||||
|
screen -r chanlun-web
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
1. 如果服务不能启动,检查日志:
|
||||||
|
```
|
||||||
|
cat logs/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 确保安装了所有依赖:
|
||||||
|
```
|
||||||
|
# 在虚拟环境中
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 检查端口是否被占用:
|
||||||
|
```
|
||||||
|
netstat -tulpn | grep 8123
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 如果gunicorn无法找到,确保使用完整路径:
|
||||||
|
```
|
||||||
|
# 对于虚拟环境
|
||||||
|
./venv/bin/gunicorn -w 4 -b 0.0.0.0:8123 app:app
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 如果出现"externally-managed-environment"错误,这表明您正在使用最新的Ubuntu系统,必须使用虚拟环境方法。
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ChanLun Web Application
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=<用户名>
|
||||||
|
Group=<用户组>
|
||||||
|
WorkingDirectory=/path/to/user_data/Chan/web
|
||||||
|
ExecStart=/path/to/user_data/Chan/web/venv/bin/gunicorn -w 4 -b 0.0.0.0:8123 app:app
|
||||||
|
Restart=on-failure
|
||||||
|
StandardOutput=file:/path/to/user_data/Chan/web/logs/output.log
|
||||||
|
StandardError=file:/path/to/user_data/Chan/web/logs/error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ChanLun Web Application
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=<用户名>
|
||||||
|
Group=<用户组>
|
||||||
|
WorkingDirectory=/path/to/user_data/Chan/web
|
||||||
|
ExecStart=/usr/local/bin/gunicorn -w 4 -b 0.0.0.0:8123 app:app
|
||||||
|
Restart=on-failure
|
||||||
|
StandardOutput=file:/path/to/user_data/Chan/web/logs/output.log
|
||||||
|
StandardError=file:/path/to/user_data/Chan/web/logs/error.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 创建日志目录(如果不存在)
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# 激活虚拟环境(如果使用)
|
||||||
|
# source venv/bin/activate
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 使用gunicorn启动应用
|
||||||
|
# 参数说明:
|
||||||
|
# -w 4: 使用4个工作进程
|
||||||
|
# -b 0.0.0.0:8123: 绑定到所有接口的8123端口
|
||||||
|
# --access-logfile: 访问日志文件路径
|
||||||
|
# --error-logfile: 错误日志文件路径
|
||||||
|
# --daemon: 以守护进程(后台)方式运行
|
||||||
|
# app:app: app.py中的app变量
|
||||||
|
|
||||||
|
gunicorn -w 4 -b 0.0.0.0:8123 --access-logfile logs/access.log --error-logfile logs/error.log --daemon app:app
|
||||||
|
|
||||||
|
echo "服务已启动在 http://服务器IP:8123"
|
||||||
|
echo "查看进程状态: ps aux | grep gunicorn"
|
||||||
|
echo "停止服务: pkill gunicorn"
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 确保安装了虚拟环境工具
|
||||||
|
echo "检查并安装虚拟环境工具..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3-venv python3-full
|
||||||
|
|
||||||
|
# 创建日志目录(如果不存在)
|
||||||
|
mkdir -p logs
|
||||||
|
|
||||||
|
# 创建虚拟环境(如果不存在)
|
||||||
|
if [ ! -d "venv" ]; then
|
||||||
|
echo "创建虚拟环境..."
|
||||||
|
python3 -m venv venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
echo "激活虚拟环境..."
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
echo "安装依赖..."
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 使用gunicorn启动应用
|
||||||
|
echo "启动应用..."
|
||||||
|
venv/bin/gunicorn -w 4 -b 0.0.0.0:8123 --access-logfile logs/access.log --error-logfile logs/error.log --daemon app:app
|
||||||
|
|
||||||
|
echo "服务已启动在 http://服务器IP:8123"
|
||||||
|
echo "查看进程状态: ps aux | grep gunicorn"
|
||||||
|
echo "停止服务: ./stop_venv.sh"
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 检查gunicorn进程
|
||||||
|
echo "Gunicorn进程状态:"
|
||||||
|
ps aux | grep gunicorn | grep -v grep
|
||||||
|
|
||||||
|
# 检查端口
|
||||||
|
echo -e "\n端口8123状态:"
|
||||||
|
netstat -tulpn 2>/dev/null | grep 8123 || echo "端口8123未被占用"
|
||||||
|
|
||||||
|
# 检查日志文件最后几行
|
||||||
|
echo -e "\n访问日志(最后5行):"
|
||||||
|
tail -n 5 logs/access.log 2>/dev/null || echo "访问日志尚未创建"
|
||||||
|
|
||||||
|
echo -e "\n错误日志(最后5行):"
|
||||||
|
tail -n 5 logs/error.log 2>/dev/null || echo "错误日志尚未创建"
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 检查虚拟环境中的gunicorn进程
|
||||||
|
echo "Gunicorn进程状态:"
|
||||||
|
ps aux | grep "venv/bin/gunicorn" | grep -v grep
|
||||||
|
|
||||||
|
# 检查端口
|
||||||
|
echo -e "\n端口8123状态:"
|
||||||
|
netstat -tulpn 2>/dev/null | grep 8123 || echo "端口8123未被占用"
|
||||||
|
|
||||||
|
# 检查日志文件最后几行
|
||||||
|
echo -e "\n访问日志(最后5行):"
|
||||||
|
tail -n 5 logs/access.log 2>/dev/null || echo "访问日志尚未创建"
|
||||||
|
|
||||||
|
echo -e "\n错误日志(最后5行):"
|
||||||
|
tail -n 5 logs/error.log 2>/dev/null || echo "错误日志尚未创建"
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 停止所有gunicorn进程
|
||||||
|
pkill gunicorn
|
||||||
|
|
||||||
|
echo "已停止所有gunicorn进程"
|
||||||
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 找到并终止gunicorn进程
|
||||||
|
echo "停止gunicorn进程..."
|
||||||
|
pkill -f "venv/bin/gunicorn"
|
||||||
|
|
||||||
|
echo "已停止所有gunicorn进程"
|
||||||
+232
-11
@@ -224,6 +224,22 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crosshair-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(40, 40, 40, 0.9);
|
||||||
|
color: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
max-width: 300px;
|
||||||
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主次周期元素样式区分 */
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -729,9 +745,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 从日期字符串获取时间戳,应用时区偏移
|
// 从日期字符串获取时间戳,应用时区偏移
|
||||||
function getAdjustedTimestamp(dateStr) {
|
function getAdjustedTimestamp(dateStr, applyOffset = true) {
|
||||||
// 基础时间戳(秒)- 直接使用UTC时间
|
const date = new Date(dateStr);
|
||||||
return Math.floor(new Date(dateStr).getTime() / 1000);
|
const timestamp = Math.floor(date.getTime() / 1000);
|
||||||
|
|
||||||
|
if (!applyOffset) {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不再手动调整时区偏移,使用JavaScript的内置时区支持
|
||||||
|
return timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加时区选择器变更事件
|
// 添加时区选择器变更事件
|
||||||
@@ -1029,6 +1052,10 @@
|
|||||||
const timezone = $('#timezone').val();
|
const timezone = $('#timezone').val();
|
||||||
const date = new Date(timeStr);
|
const date = new Date(timeStr);
|
||||||
|
|
||||||
|
// 添加调试信息
|
||||||
|
console.debug('表格时间格式化:', timeStr, '->',
|
||||||
|
date.toISOString(), '使用时区:', timezone);
|
||||||
|
|
||||||
// 使用toLocaleString带时区参数
|
// 使用toLocaleString带时区参数
|
||||||
return date.toLocaleString('zh-CN', {
|
return date.toLocaleString('zh-CN', {
|
||||||
timeZone: timezone,
|
timeZone: timezone,
|
||||||
@@ -1041,15 +1068,10 @@
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('时间格式化错误:', e, timeStr);
|
console.error('时间格式化错误:', e, timeStr);
|
||||||
// 如果出错,尝试使用简单格式化
|
// 如果格式化失败,返回原始时间字符串
|
||||||
try {
|
|
||||||
return new Date(timeStr).toLocaleString();
|
|
||||||
} catch (e2) {
|
|
||||||
// 如果还是出错,返回原始字符串
|
|
||||||
return timeStr;
|
return timeStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function formatDirection(direction) {
|
function formatDirection(direction) {
|
||||||
const dirText = direction === 1 ? '向上' : '向下';
|
const dirText = direction === 1 ? '向上' : '向下';
|
||||||
@@ -1077,6 +1099,9 @@
|
|||||||
const timezone = $('#timezone').val() || 'Asia/Shanghai';
|
const timezone = $('#timezone').val() || 'Asia/Shanghai';
|
||||||
const elementTimeframe = $('#elementTimeframe').val() || '1m';
|
const elementTimeframe = $('#elementTimeframe').val() || '1m';
|
||||||
|
|
||||||
|
// 确保时区参数有效
|
||||||
|
console.log('更新图表使用时区:', timezone);
|
||||||
|
|
||||||
// 如果symbol为空,不发送请求
|
// 如果symbol为空,不发送请求
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
console.error('交易对不能为空');
|
console.error('交易对不能为空');
|
||||||
@@ -1084,7 +1109,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`更新图表: symbol=${symbol}, timeframe=${timeframe}, elementTimeframe=${elementTimeframe}`);
|
console.log(`更新图表: symbol=${symbol}, timeframe=${timeframe}, elementTimeframe=${elementTimeframe}, timezone=${timezone}`);
|
||||||
|
|
||||||
// 获取开始和结束时间(如果已设置)
|
// 获取开始和结束时间(如果已设置)
|
||||||
let startTimeMs = null;
|
let startTimeMs = null;
|
||||||
@@ -1117,6 +1142,9 @@
|
|||||||
// 保存当前数据
|
// 保存当前数据
|
||||||
currentData = data;
|
currentData = data;
|
||||||
|
|
||||||
|
// 检查和记录服务器返回的时区
|
||||||
|
console.log('服务器返回的时区:', data.timezone || '未指定');
|
||||||
|
|
||||||
// 刷新图表
|
// 刷新图表
|
||||||
refreshChart(data);
|
refreshChart(data);
|
||||||
},
|
},
|
||||||
@@ -1238,10 +1266,55 @@
|
|||||||
},
|
},
|
||||||
crosshair: {
|
crosshair: {
|
||||||
mode: LightweightCharts.CrosshairMode.Normal,
|
mode: LightweightCharts.CrosshairMode.Normal,
|
||||||
|
// 添加十字线工具提示本地化配置
|
||||||
|
horzLine: {
|
||||||
|
labelVisible: true,
|
||||||
|
},
|
||||||
|
vertLine: {
|
||||||
|
labelVisible: true,
|
||||||
|
// 自定义时间格式化
|
||||||
|
labelFormatter: (time) => {
|
||||||
|
const selectedTimezone = $('#timezone').val();
|
||||||
|
try {
|
||||||
|
return new Date(time * 1000).toLocaleString('zh-CN', {
|
||||||
|
timeZone: selectedTimezone,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('十字线时间格式化错误:', e);
|
||||||
|
return new Date(time * 1000).toLocaleString();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rightPriceScale: {
|
rightPriceScale: {
|
||||||
borderColor: '#ddd',
|
borderColor: '#ddd',
|
||||||
},
|
},
|
||||||
|
// 添加本地化选项,确保所有时间显示都使用选定的时区
|
||||||
|
localization: {
|
||||||
|
timeFormatter: (time) => {
|
||||||
|
const selectedTimezone = $('#timezone').val();
|
||||||
|
try {
|
||||||
|
return new Date(time * 1000).toLocaleString('zh-CN', {
|
||||||
|
timeZone: selectedTimezone,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('全局时间格式化错误:', e);
|
||||||
|
return new Date(time * 1000).toLocaleString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
timeScale: {
|
timeScale: {
|
||||||
timeVisible: true,
|
timeVisible: true,
|
||||||
secondsVisible: false,
|
secondsVisible: false,
|
||||||
@@ -1251,7 +1324,11 @@
|
|||||||
tickMarkFormatter: (time) => {
|
tickMarkFormatter: (time) => {
|
||||||
const selectedTimezone = $('#timezone').val();
|
const selectedTimezone = $('#timezone').val();
|
||||||
try {
|
try {
|
||||||
return new Date(time * 1000).toLocaleString('zh-CN', {
|
// 使用完整的配置确保时区正确应用
|
||||||
|
const date = new Date(time * 1000);
|
||||||
|
console.log('格式化时间:', time, '转换为:', date.toISOString(), '时区:', selectedTimezone);
|
||||||
|
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
timeZone: selectedTimezone,
|
timeZone: selectedTimezone,
|
||||||
month: 'numeric',
|
month: 'numeric',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
@@ -1287,12 +1364,16 @@
|
|||||||
|
|
||||||
// 输出K线周期选择状态
|
// 输出K线周期选择状态
|
||||||
console.log('K线周期选择:', useElementPeriod ? '小周期' : '主周期');
|
console.log('K线周期选择:', useElementPeriod ? '小周期' : '主周期');
|
||||||
|
console.log('当前选择时区:', $('#timezone').val());
|
||||||
|
|
||||||
if (useElementPeriod) {
|
if (useElementPeriod) {
|
||||||
// 使用小周期K线数据
|
// 使用小周期K线数据
|
||||||
candles = currentData.element_kline_data.map((kline) => {
|
candles = currentData.element_kline_data.map((kline) => {
|
||||||
|
// 使用原始日期字符串创建Date对象
|
||||||
const date = new Date(kline.date);
|
const date = new Date(kline.date);
|
||||||
|
// 获取时间戳(秒)- 不手动调整时区
|
||||||
const timestamp = date.getTime() / 1000;
|
const timestamp = date.getTime() / 1000;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
open: parseFloat(kline.open),
|
open: parseFloat(kline.open),
|
||||||
@@ -1304,8 +1385,11 @@
|
|||||||
} else {
|
} else {
|
||||||
// 使用主周期K线数据
|
// 使用主周期K线数据
|
||||||
candles = currentData.kline_data.map((kline) => {
|
candles = currentData.kline_data.map((kline) => {
|
||||||
|
// 使用原始日期字符串创建Date对象
|
||||||
const date = new Date(kline.date);
|
const date = new Date(kline.date);
|
||||||
|
// 获取时间戳(秒)- 不手动调整时区
|
||||||
const timestamp = date.getTime() / 1000;
|
const timestamp = date.getTime() / 1000;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
open: parseFloat(kline.open),
|
open: parseFloat(kline.open),
|
||||||
@@ -1350,6 +1434,7 @@
|
|||||||
// 转换成交量数据 - 始终使用主K线周期数据
|
// 转换成交量数据 - 始终使用主K线周期数据
|
||||||
const volumes = useElementPeriod ?
|
const volumes = useElementPeriod ?
|
||||||
currentData.element_kline_data.map(kline => {
|
currentData.element_kline_data.map(kline => {
|
||||||
|
// 创建日期对象并获取时间戳,不手动调整时区
|
||||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||||
return {
|
return {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
@@ -1358,6 +1443,7 @@
|
|||||||
};
|
};
|
||||||
}) :
|
}) :
|
||||||
currentData.kline_data.map(kline => {
|
currentData.kline_data.map(kline => {
|
||||||
|
// 创建日期对象并获取时间戳,不手动调整时区
|
||||||
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
|
||||||
return {
|
return {
|
||||||
time: timestamp,
|
time: timestamp,
|
||||||
@@ -2942,17 +3028,65 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupTooltip() {
|
function setupTooltip() {
|
||||||
|
// 调试变量
|
||||||
|
window.debugMode = true;
|
||||||
|
|
||||||
// 添加买卖点悬浮提示元素
|
// 添加买卖点悬浮提示元素
|
||||||
const tooltipElement = document.createElement('div');
|
const tooltipElement = document.createElement('div');
|
||||||
tooltipElement.className = 'point-tooltip';
|
tooltipElement.className = 'point-tooltip';
|
||||||
document.body.appendChild(tooltipElement);
|
document.body.appendChild(tooltipElement);
|
||||||
|
|
||||||
|
// 添加自定义十字线信息显示
|
||||||
|
const crosshairTooltip = document.createElement('div');
|
||||||
|
crosshairTooltip.className = 'crosshair-tooltip';
|
||||||
|
crosshairTooltip.style.position = 'absolute';
|
||||||
|
crosshairTooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
|
||||||
|
crosshairTooltip.style.color = 'white';
|
||||||
|
crosshairTooltip.style.padding = '5px 10px';
|
||||||
|
crosshairTooltip.style.borderRadius = '4px';
|
||||||
|
crosshairTooltip.style.fontSize = '12px';
|
||||||
|
crosshairTooltip.style.zIndex = '1000';
|
||||||
|
crosshairTooltip.style.pointerEvents = 'none';
|
||||||
|
crosshairTooltip.style.display = 'none';
|
||||||
|
document.body.appendChild(crosshairTooltip);
|
||||||
|
|
||||||
// 添加鼠标悬停事件显示提示
|
// 添加鼠标悬停事件显示提示
|
||||||
mainChart.subscribeCrosshairMove(param => {
|
mainChart.subscribeCrosshairMove(param => {
|
||||||
if (param.time && param.point) {
|
if (param.time && param.point) {
|
||||||
const timeStr = param.time;
|
const timeStr = param.time;
|
||||||
const markers = [...buyMarkers, ...sellMarkers].filter(m => m.time === timeStr);
|
const markers = [...buyMarkers, ...sellMarkers].filter(m => m.time === timeStr);
|
||||||
|
|
||||||
|
// 显示时区调试信息
|
||||||
|
if (window.debugMode) {
|
||||||
|
const timezone = $('#timezone').val();
|
||||||
|
const formattedTime = formatTimeWithTimezone(timeStr * 1000, timezone);
|
||||||
|
|
||||||
|
// 获取当前价格 - 通过param.seriesPrices获取
|
||||||
|
let priceInfo = '';
|
||||||
|
if (param.seriesPrices && param.seriesPrices.size > 0) {
|
||||||
|
// 尝试从蜡烛图系列获取价格
|
||||||
|
if (tvWidget.series.candleSeries && param.seriesPrices.get(tvWidget.series.candleSeries)) {
|
||||||
|
const price = param.seriesPrices.get(tvWidget.series.candleSeries);
|
||||||
|
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
// 如果没有蜡烛图系列价格,尝试从线图系列获取
|
||||||
|
else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
||||||
|
const price = param.seriesPrices.get(tvWidget.series.lineSeries);
|
||||||
|
priceInfo = `价格: ${price.toFixed(2)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅记录最简短的调试信息
|
||||||
|
console.debug(`十字线: ${timeStr} -> ${formattedTime} (${timezone})`);
|
||||||
|
|
||||||
|
// 显示自定义时区工具提示,包含价格信息
|
||||||
|
crosshairTooltip.innerHTML = `<div style="font-weight:bold">时间: ${formattedTime}</div>` +
|
||||||
|
(priceInfo ? `<div>${priceInfo}</div>` : '');
|
||||||
|
crosshairTooltip.style.display = 'block';
|
||||||
|
crosshairTooltip.style.left = (param.point.x + 15) + 'px';
|
||||||
|
crosshairTooltip.style.top = (param.point.y - 30) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
if (markers.length > 0) {
|
if (markers.length > 0) {
|
||||||
// 有买卖点标记,显示自定义提示
|
// 有买卖点标记,显示自定义提示
|
||||||
const tooltips = markers.map(m => m.tooltip).join('<br>');
|
const tooltips = markers.map(m => m.tooltip).join('<br>');
|
||||||
@@ -2967,15 +3101,35 @@
|
|||||||
} else {
|
} else {
|
||||||
// 隐藏提示
|
// 隐藏提示
|
||||||
tooltipElement.style.display = 'none';
|
tooltipElement.style.display = 'none';
|
||||||
|
crosshairTooltip.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理图表缩放、平移等事件,隐藏提示
|
// 处理图表缩放、平移等事件,隐藏提示
|
||||||
mainChart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
mainChart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||||
tooltipElement.style.display = 'none';
|
tooltipElement.style.display = 'none';
|
||||||
|
crosshairTooltip.style.display = 'none';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助函数:使用指定时区格式化时间戳
|
||||||
|
function formatTimeWithTimezone(timestamp, timezone) {
|
||||||
|
try {
|
||||||
|
return new Date(timestamp).toLocaleString('zh-CN', {
|
||||||
|
timeZone: timezone,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('时区格式化错误:', e);
|
||||||
|
return new Date(timestamp).toLocaleString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function displayTradePoints() {
|
function displayTradePoints() {
|
||||||
// 优先使用小周期数据,如果不存在则使用主周期数据
|
// 优先使用小周期数据,如果不存在则使用主周期数据
|
||||||
const tradePointsData = currentData.element_trade_points || currentData.trade_points;
|
const tradePointsData = currentData.element_trade_points || currentData.trade_points;
|
||||||
@@ -3454,6 +3608,13 @@
|
|||||||
|
|
||||||
// 页面加载时初始化
|
// 页面加载时初始化
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
// 从本地存储中恢复时区设置
|
||||||
|
const savedTimezone = localStorage.getItem('selectedTimezone');
|
||||||
|
if (savedTimezone) {
|
||||||
|
$('#timezone').val(savedTimezone);
|
||||||
|
console.log('从本地存储恢复时区设置:', savedTimezone);
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化交易对下拉菜单
|
// 初始化交易对下拉菜单
|
||||||
$('#symbol').val('BTC/USDT:USDT');
|
$('#symbol').val('BTC/USDT:USDT');
|
||||||
$('#timeframe').val('5m');
|
$('#timeframe').val('5m');
|
||||||
@@ -3485,6 +3646,66 @@
|
|||||||
|
|
||||||
// 初始化自动刷新功能
|
// 初始化自动刷新功能
|
||||||
initAutoRefresh();
|
initAutoRefresh();
|
||||||
|
|
||||||
|
// 确保在文档加载完成后初始化时区设置
|
||||||
|
// 默认设置为Shanghai时区
|
||||||
|
if (!$('#timezone').val()) {
|
||||||
|
$('#timezone').val('Asia/Shanghai');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录当前时区设置
|
||||||
|
console.log('页面加载完成,当前时区设置:', $('#timezone').val());
|
||||||
|
|
||||||
|
// 添加自定义事件处理 - 让时区选择变更立即生效
|
||||||
|
$('#timezone').on('change', function() {
|
||||||
|
const newTimezone = $(this).val();
|
||||||
|
console.log('时区已更改为:', newTimezone);
|
||||||
|
|
||||||
|
// 保存到本地存储,下次访问时自动使用
|
||||||
|
localStorage.setItem('selectedTimezone', newTimezone);
|
||||||
|
|
||||||
|
// 如果已有数据,重新渲染图表和表格
|
||||||
|
if (currentData) {
|
||||||
|
// 先销毁现有图表实例
|
||||||
|
if (tvWidget.mainChart) {
|
||||||
|
try {
|
||||||
|
// 销毁主图表及其关联的线系列
|
||||||
|
tvWidget.mainChart = null;
|
||||||
|
tvWidget.volumeChart = null;
|
||||||
|
tvWidget.macdChart = null;
|
||||||
|
// 重置系列数据
|
||||||
|
tvWidget.series = {
|
||||||
|
candleSeries: null,
|
||||||
|
lineSeries: null,
|
||||||
|
volumeSeries: null,
|
||||||
|
macdLineSeries: null,
|
||||||
|
signalLineSeries: null,
|
||||||
|
histogramSeries: null,
|
||||||
|
mainBiSeries: [],
|
||||||
|
mainSegSeries: [],
|
||||||
|
mainZsSeries: [],
|
||||||
|
mainUncompletedZsSeries: [],
|
||||||
|
elementBiSeries: [],
|
||||||
|
elementSegSeries: [],
|
||||||
|
elementZsSeries: [],
|
||||||
|
elementUncompletedZsSeries: [],
|
||||||
|
tradePointSeries: []
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error('销毁图表错误:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新的时区重新初始化图表
|
||||||
|
initTradingView($('#symbol').val(), $('#timeframe').val());
|
||||||
|
|
||||||
|
// 重新渲染图表数据
|
||||||
|
renderChart();
|
||||||
|
|
||||||
|
// 更新表格
|
||||||
|
updateTables(currentData);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 自动刷新相关变量
|
// 自动刷新相关变量
|
||||||
|
|||||||
Reference in New Issue
Block a user