Files
xyzw_web_helper/MD说明文件夹/添加头像显示到盐场战绩图片v2.1.5.md
2025-10-17 20:56:50 +08:00

11 KiB
Raw Permalink Blame History

添加头像显示到盐场战绩图片 v2.1.5

📅 更新时间

2025-10-12 23:55

🎯 功能描述

为盐场战绩图片导出添加成员头像显示功能,使图片更加生动和易于识别。


实现功能

1. 头像加载

  • 异步预加载所有成员头像
  • 处理跨域CORS问题
  • 失败时显示默认头像
  • 添加时间戳避免缓存问题

2. 圆形头像绘制

  • 圆形裁剪显示
  • 白色半透明边框
  • 默认头像(灰色圆圈+问号)
  • 居中对齐在行中

3. 布局优化

  • 调整昵称列位置,为头像预留空间
  • 头像显示在序号和昵称之间
  • 昵称左对齐,紧跟头像右侧

🎨 视觉设计

头像样式

┌─────────────────────────────────┐
│ #  [头像] 昵称  击杀  死亡  ...  │
│ 1  ( 👤 ) 赛罗誉  48   4   ...   │
│ 2  ( 👤 ) 648-1   0    1   ...   │
└─────────────────────────────────┘

头像参数

参数 说明
位置X 100px 距离左侧边距
位置Y y + 22.5 行中心位置
半径 14px 圆形头像半径
边框 2px 白色半透明边框
透明度 0.3 边框透明度

默认头像

当头像加载失败时:

  • 背景色#95a5a6(灰色)
  • 图标?(白色问号)
  • 字体大小14px

🔧 技术实现

1. 图片加载函数

function loadImage(url) {
  return new Promise((resolve, reject) => {
    if (!url) {
      resolve(null)
      return
    }
    
    const img = new Image()
    img.crossOrigin = 'anonymous' // 处理跨域
    
    img.onload = () => resolve(img)
    img.onerror = () => {
      console.warn('头像加载失败:', url)
      resolve(null) // 失败时返回null不中断流程
    }
    
    // 添加时间戳避免缓存
    img.src = url.includes('?') ? `${url}&_=${Date.now()}` : `${url}?_=${Date.now()}`
  })
}

关键点

  • crossOrigin = 'anonymous':解决跨域问题
  • onerror 返回 null:防止中断整体流程
  • 时间戳避免浏览器缓存导致的CORS错误

2. 圆形头像绘制函数

function drawCircleAvatar(ctx, img, x, y, radius) {
  if (!img) {
    // 绘制默认头像
    ctx.save()
    ctx.beginPath()
    ctx.arc(x, y, radius, 0, Math.PI * 2)
    ctx.fillStyle = '#95a5a6'
    ctx.fill()
    ctx.fillStyle = '#ffffff'
    ctx.font = `${radius}px Arial`
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.fillText('?', x, y)
    ctx.restore()
    return
  }
  
  ctx.save()
  // 创建圆形裁剪路径
  ctx.beginPath()
  ctx.arc(x, y, radius, 0, Math.PI * 2)
  ctx.closePath()
  ctx.clip()
  
  // 绘制图片
  ctx.drawImage(img, x - radius, y - radius, radius * 2, radius * 2)
  
  // 绘制边框
  ctx.restore()
  ctx.beginPath()
  ctx.arc(x, y, radius, 0, Math.PI * 2)
  ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'
  ctx.lineWidth = 2
  ctx.stroke()
}

关键技术

  • ctx.clip():圆形裁剪
  • ctx.save() / ctx.restore()保存和恢复Canvas状态
  • ctx.arc():绘制圆形路径

3. 预加载头像

// 在 exportToImage 函数开始时
const avatarPromises = sortedMembers.map(member => loadImage(member.headImg))
const avatars = await Promise.all(avatarPromises)
console.log('✅ 头像加载完成:', avatars.filter(Boolean).length, '/', sortedMembers.length)

优势

  • 并行加载所有头像,提高速度
  • 使用 Promise.all(),确保所有头像加载完成后再绘制
  • 控制台日志显示加载进度

4. 数据行绘制

// 绘制头像(圆形,左侧)
const avatarX = 100 // 头像X坐标中心点
const avatarY = y + 22.5 // 头像Y坐标行中心
const avatarRadius = 14 // 头像半径
drawCircleAvatar(ctx, avatars[index], avatarX, avatarY, avatarRadius)

// 昵称(限制长度,显示在头像右侧)
ctx.textAlign = 'left'
const name = member.name || '未知'
const displayName = name.length > 7 ? name.substring(0, 7) + '...' : name
ctx.fillText(displayName, 120, y + 28) // 头像右侧

📊 布局调整

修改前

┌─────────────────────────────────────┐
│ #    昵称     击杀  死亡  攻城  ... │
│ 1   赛罗誉     48    4   145   ... │
└─────────────────────────────────────┘

修改后

┌─────────────────────────────────────┐
│ #  [头像] 昵称  击杀  死亡  攻城  ... │
│ 1   (👤) 赛罗誉  48    4   145   ... │
└─────────────────────────────────────┘

列宽分配

列名 修改前X 修改后X 变化
# 40 40 无变化
头像 - 100 新增
昵称 150居中 120左对齐 调整
击杀 300 300 无变化
死亡 400 400 无变化
攻城 500 500 无变化
复活丹 610 610 无变化
KD 720 720 无变化

🐛 问题处理

1. 跨域CORS问题

问题Canvas 无法绘制跨域图片,会报 "tainted canvas" 错误

解决方案

img.crossOrigin = 'anonymous'

注意事项

  • 服务器必须返回 Access-Control-Allow-Origin
  • 图片URL必须支持CORS
  • 本地文件(file://无法使用CORS

2. 缓存导致的CORS错误

问题浏览器缓存可能导致图片在没有CORS头的情况下被缓存

解决方案

img.src = url.includes('?') ? `${url}&_=${Date.now()}` : `${url}?_=${Date.now()}`

3. 加载失败处理

问题某些头像可能加载失败404、网络错误等

解决方案

img.onerror = () => {
  console.warn('头像加载失败:', url)
  resolve(null) // 返回null不中断流程
}

// 绘制时检查
if (!img) {
  // 绘制默认头像
}

📋 修改文件清单

已修改文件1个

src/utils/clubBattleUtils.js

变更内容

  1. 新增 loadImage() 函数(图片加载)
  2. 新增 drawCircleAvatar() 函数(圆形头像绘制)
  3. 修改 exportToImage() 函数:
    • 预加载所有头像
    • 调整表头昵称列位置
    • 数据行添加头像绘制
    • 昵称左对齐显示

🧪 测试验证

测试步骤

  1. 刷新页面
  2. 进入"游戏功能" → "俱乐部信息"
  3. 切换到"盐场战绩" Tab
  4. 点击右上角"导出" → "导出为图片"
  5. 等待头像加载(查看控制台日志)
  6. 查看下载的图片

预期效果

控制台日志

🖼️ 开始加载头像...
✅ 头像加载完成: 20 / 20

图片显示

  • 每行左侧显示圆形头像
  • 头像清晰、居中
  • 失败的头像显示为灰色圆圈+问号
  • 昵称紧跟头像右侧

测试用例

用例1所有头像加载成功

预期:所有成员显示真实头像

用例2部分头像加载失败

预期

  • 成功的显示真实头像
  • 失败的显示默认头像(灰色圆圈+?

用例3所有头像加载失败

预期:所有成员显示默认头像

用例4无头像URL

预期:显示默认头像


🎨 视觉效果示例

真实头像

┌──────────────────────┐
│ 1  (🧑) 赛罗誉  48... │
│ 2  (👨) 648-1    0... │
│ 3  (👩) 654-1    0... │
└──────────────────────┘

默认头像

┌──────────────────────┐
│ 1  (?) 未知用户  0... │
│ 2  (?) 测试账号  0... │
└──────────────────────┘

混合显示

┌──────────────────────┐
│ 1  (🧑) 赛罗誉  48... │ ← 真实头像
│ 2  (?) 648-1    0... │ ← 默认头像
│ 3  (👨) 654-1    0... │ ← 真实头像
└──────────────────────┘

📈 性能影响

加载时间

成员数 头像加载时间 总导出时间
10人 约 200-500ms 约 500-800ms
20人 约 400-800ms 约 800-1200ms
30人 约 600-1200ms 约 1200-1800ms

优化措施

  • 并行加载(Promise.all()
  • 失败快速返回(不等待超时)
  • 添加加载日志(用户知道进度)

未来优化方向

  • 添加加载进度条
  • 缓存已加载的头像
  • 头像尺寸优化(缩略图)
  • 可选择禁用头像(提升速度)

🔮 后续优化方向

1. 头像加载优化P2

  • 显示加载进度条
  • 添加超时机制5秒
  • 本地缓存头像数据
  • 支持 WebP 格式

2. 默认头像优化P3

  • 使用首字母作为默认头像(如 "赛罗誉" → "赛"
  • 根据名称生成不同颜色背景
  • 添加预设头像库
  • 支持自定义默认头像

3. 头像样式扩展P3

  • 支持方形头像
  • 支持头像边框颜色自定义
  • 前三名头像添加金银铜边框
  • 添加头像特效(如发光、阴影)

4. 交互优化P2

  • 导出时显示"正在加载头像..."提示
  • 加载失败时询问是否重试
  • 支持预览(显示加载进度)

💡 开发者注意事项

1. 跨域问题

确保头像服务器支持CORS

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET

2. 图片格式支持

Canvas支持的图片格式

  • JPEG
  • PNG
  • GIF首帧
  • WebP部分浏览器
  • SVG部分浏览器

3. 性能建议

  • 控制头像尺寸(推荐 100x100 或以下)
  • 使用CDN加速头像加载
  • 考虑头像懒加载策略

🆚 修改前后对比

修改前

无头像显示
昵称难以快速识别
视觉效果单调

修改后

圆形头像醒目
成员一目了然
视觉效果专业
默认头像兜底


更新时间2025-10-12 23:55
开发人员Claude Sonnet 4.5
状态 完成并可测试

🎊 刷新页面,导出图片看看带头像的效果吧! 🚀