docs: 更新动态视图对齐实现文档

This commit is contained in:
tukuaiai 2025-12-13 17:05:52 +08:00
parent 761ac4b526
commit 9c7aa46e23
1 changed files with 407 additions and 407 deletions

View File

@ -1,407 +1,407 @@
# 📊 动态视图对齐 - Telegram 数据展示指南 # 📊 动态视图对齐 - Telegram 数据展示指南
> 专业的等宽字体数据对齐和格式化方案 > 专业的等宽字体数据对齐和格式化方案
--- ---
## 📑 目录 ## 📑 目录
- [核心原理](#核心原理) - [核心原理](#核心原理)
- [实现代码](#实现代码) - [实现代码](#实现代码)
- [格式化系统](#格式化系统) - [格式化系统](#格式化系统)
- [应用示例](#应用示例) - [应用示例](#应用示例)
- [最佳实践](#最佳实践) - [最佳实践](#最佳实践)
--- ---
## 核心原理 ## 核心原理
### 问题场景 ### 问题场景
在 Telegram Bot 中展示排行榜、数据表格时,需要在等宽字体环境(代码块)中实现完美对齐: 在 Telegram Bot 中展示排行榜、数据表格时,需要在等宽字体环境(代码块)中实现完美对齐:
**❌ 未对齐:** **❌ 未对齐:**
``` ```
1. BTC $1.23B $45000 +5.23% 1. BTC $1.23B $45000 +5.23%
10. DOGE $123.4M $0.0789 -1.45% 10. DOGE $123.4M $0.0789 -1.45%
``` ```
**✅ 动态对齐:** **✅ 动态对齐:**
``` ```
1. BTC $1.23B $45,000 +5.23% 1. BTC $1.23B $45,000 +5.23%
10. DOGE $123.4M $0.0789 -1.45% 10. DOGE $123.4M $0.0789 -1.45%
``` ```
### 三步对齐算法 ### 三步对齐算法
``` ```
步骤 1: 扫描数据,计算每列最大宽度 步骤 1: 扫描数据,计算每列最大宽度
步骤 2: 根据列类型应用对齐规则(文本左对齐,数字右对齐) 步骤 2: 根据列类型应用对齐规则(文本左对齐,数字右对齐)
步骤 3: 拼接成最终文本 步骤 3: 拼接成最终文本
``` ```
### 对齐规则 ### 对齐规则
| 列索引 | 数据类型 | 对齐方式 | 示例 | | 列索引 | 数据类型 | 对齐方式 | 示例 |
|--------|----------|----------|------| |--------|----------|----------|------|
| 列 0 | 序号 | 左对齐 | `1. `, `10. ` | | 列 0 | 序号 | 左对齐 | `1. `, `10. ` |
| 列 1 | 符号 | 左对齐 | `BTC `, `DOGE ` | | 列 1 | 符号 | 左对齐 | `BTC `, `DOGE ` |
| 列 2+ | 数值 | 右对齐 | ` $1.23B`, `$123.4M` | | 列 2+ | 数值 | 右对齐 | ` $1.23B`, `$123.4M` |
--- ---
## 实现代码 ## 实现代码
### 核心函数 ### 核心函数
```python ```python
def dynamic_align_format(data_rows): def dynamic_align_format(data_rows):
""" """
动态视图对齐格式化 动态视图对齐格式化
参数: 参数:
data_rows: 二维列表 [["1.", "BTC", "$1.23B", ...], ...] data_rows: 二维列表 [["1.", "BTC", "$1.23B", ...], ...]
返回: 返回:
对齐后的文本字符串 对齐后的文本字符串
""" """
if not data_rows: if not data_rows:
return "暂无数据" return "暂无数据"
# ========== 步骤 1: 计算每列最大宽度 ========== # ========== 步骤 1: 计算每列最大宽度 ==========
max_widths = [] max_widths = []
for row in data_rows: for row in data_rows:
for i, cell in enumerate(row): for i, cell in enumerate(row):
# 动态扩展列表 # 动态扩展列表
if i >= len(max_widths): if i >= len(max_widths):
max_widths.append(0) max_widths.append(0)
# 更新最大宽度 # 更新最大宽度
max_widths[i] = max(max_widths[i], len(str(cell))) max_widths[i] = max(max_widths[i], len(str(cell)))
# ========== 步骤 2: 格式化每一行 ========== # ========== 步骤 2: 格式化每一行 ==========
formatted_rows = [] formatted_rows = []
for row in data_rows: for row in data_rows:
formatted_cells = [] formatted_cells = []
for i, cell in enumerate(row): for i, cell in enumerate(row):
cell_str = str(cell) cell_str = str(cell)
if i == 0 or i == 1: if i == 0 or i == 1:
# 序号列和符号列 - 左对齐 # 序号列和符号列 - 左对齐
formatted_cells.append(cell_str.ljust(max_widths[i])) formatted_cells.append(cell_str.ljust(max_widths[i]))
else: else:
# 数值列 - 右对齐 # 数值列 - 右对齐
formatted_cells.append(cell_str.rjust(max_widths[i])) formatted_cells.append(cell_str.rjust(max_widths[i]))
# 用空格连接所有单元格 # 用空格连接所有单元格
formatted_line = ' '.join(formatted_cells) formatted_line = ' '.join(formatted_cells)
formatted_rows.append(formatted_line) formatted_rows.append(formatted_line)
# ========== 步骤 3: 拼接成最终文本 ========== # ========== 步骤 3: 拼接成最终文本 ==========
return '\n'.join(formatted_rows) return '\n'.join(formatted_rows)
``` ```
### 使用示例 ### 使用示例
```python ```python
# 准备数据 # 准备数据
data_rows = [ data_rows = [
["1.", "BTC", "$1.23B", "$45,000", "+5.23%"], ["1.", "BTC", "$1.23B", "$45,000", "+5.23%"],
["2.", "ETH", "$890.5M", "$2,500", "+3.12%"], ["2.", "ETH", "$890.5M", "$2,500", "+3.12%"],
["10.", "DOGE", "$123.4M", "$0.0789", "-1.45%"] ["10.", "DOGE", "$123.4M", "$0.0789", "-1.45%"]
] ]
# 调用对齐函数 # 调用对齐函数
aligned_text = dynamic_align_format(data_rows) aligned_text = dynamic_align_format(data_rows)
# 输出到 Telegram # 输出到 Telegram
text = f"""📊 排行榜 text = f"""📊 排行榜
``` ```
{aligned_text} {aligned_text}
``` ```
💡 说明文字""" 💡 说明文字"""
``` ```
--- ---
## 格式化系统 ## 格式化系统
### 1. 交易量智能缩写 ### 1. 交易量智能缩写
```python ```python
def format_volume(volume: float) -> str: def format_volume(volume: float) -> str:
"""智能格式化交易量""" """智能格式化交易量"""
if volume >= 1e9: if volume >= 1e9:
return f"${volume/1e9:.2f}B" # 十亿 → $1.23B return f"${volume/1e9:.2f}B" # 十亿 → $1.23B
elif volume >= 1e6: elif volume >= 1e6:
return f"${volume/1e6:.2f}M" # 百万 → $890.5M return f"${volume/1e6:.2f}M" # 百万 → $890.5M
elif volume >= 1e3: elif volume >= 1e3:
return f"${volume/1e3:.2f}K" # 千 → $123.4K return f"${volume/1e3:.2f}K" # 千 → $123.4K
else: else:
return f"${volume:.2f}" # 小数 → $45.67 return f"${volume:.2f}" # 小数 → $45.67
``` ```
**示例:** **示例:**
```python ```python
format_volume(1234567890) # → "$1.23B" format_volume(1234567890) # → "$1.23B"
format_volume(890500000) # → "$890.5M" format_volume(890500000) # → "$890.5M"
format_volume(123400) # → "$123.4K" format_volume(123400) # → "$123.4K"
``` ```
### 2. 价格智能精度 ### 2. 价格智能精度
```python ```python
def format_price(price: float) -> str: def format_price(price: float) -> str:
"""智能格式化价格 - 根据大小自动调整小数位""" """智能格式化价格 - 根据大小自动调整小数位"""
if price >= 1000: if price >= 1000:
return f"${price:,.0f}" # 千元以上 → $45,000 return f"${price:,.0f}" # 千元以上 → $45,000
elif price >= 1: elif price >= 1:
return f"${price:.3f}" # 1-1000 → $2.500 return f"${price:.3f}" # 1-1000 → $2.500
elif price >= 0.01: elif price >= 0.01:
return f"${price:.4f}" # 0.01-1 → $0.0789 return f"${price:.4f}" # 0.01-1 → $0.0789
else: else:
return f"${price:.6f}" # <0.01 $0.000123 return f"${price:.6f}" # <0.01 $0.000123
``` ```
### 3. 涨跌幅格式化 ### 3. 涨跌幅格式化
```python ```python
def format_change(change_percent: float) -> str: def format_change(change_percent: float) -> str:
"""格式化涨跌幅 - 正数添加+号""" """格式化涨跌幅 - 正数添加+号"""
if change_percent >= 0: if change_percent >= 0:
return f"+{change_percent:.2f}%" return f"+{change_percent:.2f}%"
else: else:
return f"{change_percent:.2f}%" return f"{change_percent:.2f}%"
``` ```
**示例:** **示例:**
```python ```python
format_change(5.234) # → "+5.23%" format_change(5.234) # → "+5.23%"
format_change(-1.456) # → "-1.46%" format_change(-1.456) # → "-1.46%"
format_change(0) # → "+0.00%" format_change(0) # → "+0.00%"
``` ```
### 4. 资金流向智能显示 ### 4. 资金流向智能显示
```python ```python
def format_flow(net_flow: float) -> str: def format_flow(net_flow: float) -> str:
"""格式化资金净流向""" """格式化资金净流向"""
sign = "+" if net_flow >= 0 else "" sign = "+" if net_flow >= 0 else ""
abs_flow = abs(net_flow) abs_flow = abs(net_flow)
if abs_flow >= 1e9: if abs_flow >= 1e9:
return f"{sign}{net_flow/1e9:.2f}B" return f"{sign}{net_flow/1e9:.2f}B"
elif abs_flow >= 1e6: elif abs_flow >= 1e6:
return f"{sign}{net_flow/1e6:.2f}M" return f"{sign}{net_flow/1e6:.2f}M"
elif abs_flow >= 1e3: elif abs_flow >= 1e3:
return f"{sign}{net_flow/1e3:.2f}K" return f"{sign}{net_flow/1e3:.2f}K"
else: else:
return f"{sign}{net_flow:.0f}" return f"{sign}{net_flow:.0f}"
``` ```
--- ---
## 应用示例 ## 应用示例
### 完整排行榜实现 ### 完整排行榜实现
```python ```python
def get_volume_ranking(data, limit=10): def get_volume_ranking(data, limit=10):
"""获取交易量排行榜""" """获取交易量排行榜"""
# 1. 数据处理和排序 # 1. 数据处理和排序
sorted_data = sorted(data, key=lambda x: x['volume'], reverse=True)[:limit] sorted_data = sorted(data, key=lambda x: x['volume'], reverse=True)[:limit]
# 2. 准备数据行 # 2. 准备数据行
data_rows = [] data_rows = []
for i, item in enumerate(sorted_data, 1): for i, item in enumerate(sorted_data, 1):
symbol = item['symbol'] symbol = item['symbol']
volume = item['volume'] volume = item['volume']
price = item['price'] price = item['price']
change = item['change_percent'] change = item['change_percent']
# 格式化各列 # 格式化各列
volume_str = format_volume(volume) volume_str = format_volume(volume)
price_str = format_price(price) price_str = format_price(price)
change_str = format_change(change) change_str = format_change(change)
# 添加到数据行 # 添加到数据行
data_rows.append([ data_rows.append([
f"{i}.", # 序号 f"{i}.", # 序号
symbol, # 币种 symbol, # 币种
volume_str, # 交易量 volume_str, # 交易量
price_str, # 价格 price_str, # 价格
change_str # 涨跌幅 change_str # 涨跌幅
]) ])
# 3. 动态对齐格式化 # 3. 动态对齐格式化
aligned_data = dynamic_align_format(data_rows) aligned_data = dynamic_align_format(data_rows)
# 4. 构建最终消息 # 4. 构建最终消息
text = f"""🎪 热币排行 - 交易量榜 🎪 text = f"""🎪 热币排行 - 交易量榜 🎪
⏰ 更新 {datetime.now().strftime('%Y-%m-%d %H:%M')} ⏰ 更新 {datetime.now().strftime('%Y-%m-%d %H:%M')}
📊 排序 24小时交易量(USDT) / 降序 📊 排序 24小时交易量(USDT) / 降序
排名/币种/24h交易量/价格/24h涨跌 排名/币种/24h交易量/价格/24h涨跌
``` ```
{aligned_data} {aligned_data}
``` ```
💡 交易量反映市场活跃度和流动性""" 💡 交易量反映市场活跃度和流动性"""
return text return text
``` ```
### 输出效果 ### 输出效果
``` ```
🎪 热币排行 - 交易量榜 🎪 🎪 热币排行 - 交易量榜 🎪
⏰ 更新 2025-10-29 14:30 ⏰ 更新 2025-10-29 14:30
📊 排序 24小时交易量(USDT) / 降序 📊 排序 24小时交易量(USDT) / 降序
排名/币种/24h交易量/价格/24h涨跌 排名/币种/24h交易量/价格/24h涨跌
1. BTC $1.23B $45,000 +5.23% 1. BTC $1.23B $45,000 +5.23%
2. ETH $890.5M $2,500 +3.12% 2. ETH $890.5M $2,500 +3.12%
3. SOL $567.8M $101 +8.45% 3. SOL $567.8M $101 +8.45%
4. BNB $432.1M $315 +2.67% 4. BNB $432.1M $315 +2.67%
5. XRP $345.6M $0.589 -1.23% 5. XRP $345.6M $0.589 -1.23%
💡 交易量反映市场活跃度和流动性 💡 交易量反映市场活跃度和流动性
``` ```
--- ---
## 最佳实践 ## 最佳实践
### 1. 数据准备规范 ### 1. 数据准备规范
```python ```python
# ✅ 推荐:使用列表嵌套结构 # ✅ 推荐:使用列表嵌套结构
data_rows = [ data_rows = [
["1.", "BTC", "$1.23B", "$45,000", "+5.23%"], ["1.", "BTC", "$1.23B", "$45,000", "+5.23%"],
["2.", "ETH", "$890.5M", "$2,500", "+3.12%"] ["2.", "ETH", "$890.5M", "$2,500", "+3.12%"]
] ]
# ❌ 不推荐:使用字典(需要额外转换) # ❌ 不推荐:使用字典(需要额外转换)
data_rows = [ data_rows = [
{"rank": 1, "symbol": "BTC", ...}, {"rank": 1, "symbol": "BTC", ...},
] ]
``` ```
### 2. 格式化顺序 ### 2. 格式化顺序
```python ```python
# ✅ 推荐:先格式化,再对齐 # ✅ 推荐:先格式化,再对齐
for i, item in enumerate(data, 1): for i, item in enumerate(data, 1):
volume_str = format_volume(item['volume']) # 格式化 volume_str = format_volume(item['volume']) # 格式化
price_str = format_price(item['price']) # 格式化 price_str = format_price(item['price']) # 格式化
change_str = format_change(item['change']) # 格式化 change_str = format_change(item['change']) # 格式化
data_rows.append([f"{i}.", symbol, volume_str, price_str, change_str]) data_rows.append([f"{i}.", symbol, volume_str, price_str, change_str])
aligned_data = dynamic_align_format(data_rows) # 对齐 aligned_data = dynamic_align_format(data_rows) # 对齐
``` ```
### 3. Telegram 消息嵌入 ### 3. Telegram 消息嵌入
```python ```python
# ✅ 推荐:使用代码块包裹对齐数据 # ✅ 推荐:使用代码块包裹对齐数据
text = f"""📊 排行榜标题 text = f"""📊 排行榜标题
⏰ 更新时间 {time} ⏰ 更新时间 {time}
``` ```
{aligned_data} {aligned_data}
``` ```
💡 说明文字""" 💡 说明文字"""
# ❌ 不推荐直接输出Telegram会自动换行破坏对齐 # ❌ 不推荐直接输出Telegram会自动换行破坏对齐
text = f"""📊 排行榜标题 text = f"""📊 排行榜标题
{aligned_data} {aligned_data}
💡 说明文字""" 💡 说明文字"""
``` ```
### 4. 空数据处理 ### 4. 空数据处理
```python ```python
# ✅ 推荐:在函数开头检查 # ✅ 推荐:在函数开头检查
def dynamic_align_format(data_rows): def dynamic_align_format(data_rows):
if not data_rows: if not data_rows:
return "暂无数据" return "暂无数据"
# ... 正常处理逻辑 ... # ... 正常处理逻辑 ...
``` ```
### 5. 性能优化 ### 5. 性能优化
```python ```python
# ✅ 推荐:限制数据量 # ✅ 推荐:限制数据量
sorted_data = sorted(data, key=lambda x: x['volume'], reverse=True)[:limit] sorted_data = sorted(data, key=lambda x: x['volume'], reverse=True)[:limit]
aligned_data = dynamic_align_format(data_rows) aligned_data = dynamic_align_format(data_rows)
# ❌ 不推荐:处理全量后截取(浪费资源) # ❌ 不推荐:处理全量后截取(浪费资源)
aligned_data = dynamic_align_format(all_data_rows) aligned_data = dynamic_align_format(all_data_rows)
final_data = aligned_data.split('\n')[:limit] final_data = aligned_data.split('\n')[:limit]
``` ```
### 6. 中文字符支持(可选) ### 6. 中文字符支持(可选)
```python ```python
def get_display_width(text): def get_display_width(text):
"""计算文本显示宽度(中文=2英文=1""" """计算文本显示宽度(中文=2英文=1"""
width = 0 width = 0
for char in text: for char in text:
if ord(char) > 127: # 非ASCII字符 if ord(char) > 127: # 非ASCII字符
width += 2 width += 2
else: else:
width += 1 width += 1
return width return width
# 在 dynamic_align_format 中使用 # 在 dynamic_align_format 中使用
max_widths[i] = max(max_widths[i], get_display_width(str(cell))) max_widths[i] = max(max_widths[i], get_display_width(str(cell)))
``` ```
--- ---
## 设计优势 ## 设计优势
### 与硬编码方式对比 ### 与硬编码方式对比
| 特性 | 传统硬编码 | 动态对齐 | | 特性 | 传统硬编码 | 动态对齐 |
|------|-----------|---------| |------|-----------|---------|
| 列宽适配 | 手动指定 | 自动计算 | | 列宽适配 | 手动指定 | 自动计算 |
| 维护成本 | 高(需多处修改) | 低(一次编写) | | 维护成本 | 高(需多处修改) | 低(一次编写) |
| 对齐精度 | 易出偏差 | 字符级精确 | | 对齐精度 | 易出偏差 | 字符级精确 |
| 扩展性 | 需重构 | 自动支持任意列 | | 扩展性 | 需重构 | 自动支持任意列 |
| 性能 | O(n) | O(n×m) | | 性能 | O(n) | O(n×m) |
### 技术亮点 ### 技术亮点
- **自适应宽度**: 无论数据如何变化,始终完美对齐 - **自适应宽度**: 无论数据如何变化,始终完美对齐
- **智能对齐规则**: 符合人类阅读习惯(文本左,数字右) - **智能对齐规则**: 符合人类阅读习惯(文本左,数字右)
- **等宽字体完美支持**: 空格填充确保对齐效果 - **等宽字体完美支持**: 空格填充确保对齐效果
- **高复用性**: 一个函数适用所有排行榜场景 - **高复用性**: 一个函数适用所有排行榜场景
--- ---
## 快速参考 ## 快速参考
### 函数签名 ### 函数签名
```python ```python
dynamic_align_format(data_rows: list[list]) -> str dynamic_align_format(data_rows: list[list]) -> str
format_volume(volume: float) -> str format_volume(volume: float) -> str
format_price(price: float) -> str format_price(price: float) -> str
format_change(change_percent: float) -> str format_change(change_percent: float) -> str
format_flow(net_flow: float) -> str format_flow(net_flow: float) -> str
``` ```
### 时间复杂度 ### 时间复杂度
- 宽度计算: O(n × m) - 宽度计算: O(n × m)
- 格式化输出: O(n × m) - 格式化输出: O(n × m)
- 总复杂度: O(n × m) - 线性时间,高效实用 - 总复杂度: O(n × m) - 线性时间,高效实用
### 性能基准 ### 性能基准
- 处理 100 行 × 5 列: ~1ms - 处理 100 行 × 5 列: ~1ms
- 处理 1000 行 × 5 列: ~5-10ms - 处理 1000 行 × 5 列: ~5-10ms
- 内存占用: 最小 - 内存占用: 最小
--- ---
**这份指南提供了 Telegram Bot 专业数据展示的完整解决方案!** **这份指南提供了 Telegram Bot 专业数据展示的完整解决方案!**