refactor(web): 使用 DiceBear 生成默认头像

This commit is contained in:
Wenjie Zhang 2026-06-05 21:02:13 +08:00
parent 948a5fda9e
commit 63285b8068
2 changed files with 16 additions and 70 deletions

View File

@ -1,7 +1,7 @@
import assert from 'node:assert/strict'
import { generatePixelAvatar } from '../pixelAvatar.js'
const decodeAvatarSvg = (avatar) => decodeURIComponent(avatar.replace('data:image/svg+xml,', ''))
const DICEBEAR_GLYPHS_AVATAR_BASE_URL = 'https://api.dicebear.com/10.x/glyphs/svg'
const run = () => {
{
@ -20,15 +20,22 @@ const run = () => {
{
const avatar = generatePixelAvatar('user-003')
assert.ok(avatar.startsWith('data:image/svg+xml,'), 'Should return an SVG data URL')
console.log('T3 Data URL prefix: PASS')
assert.equal(
avatar,
`${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=user-003`,
'Should return a DiceBear glyphs avatar URL'
)
console.log('T3 DiceBear URL: PASS')
}
{
const svg = decodeAvatarSvg(generatePixelAvatar('user-004'))
assert.ok(svg.includes('<svg'), 'Decoded output should contain an SVG tag')
assert.ok(svg.includes('<rect'), 'Decoded output should contain pixel rects')
console.log('T4 Decodable SVG: PASS')
const avatar = generatePixelAvatar(' user/中文 ')
assert.equal(
avatar,
`${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=user%2F%E4%B8%AD%E6%96%87`,
'Seed should be trimmed and URL encoded'
)
console.log('T4 Encoded seed: PASS')
}
{

View File

@ -1,20 +1,4 @@
const DEFAULT_SIZE = 64
const GRID_SIZE = 5
const MIRROR_COLUMNS = 3
const DEFAULT_BACKGROUND = '#f3f4f6'
const COLOR_PALETTE = [
'#265c96',
'#3996ae',
'#13c2c2',
'#52c41a',
'#faad14',
'#ff7a45',
'#ff4d4f',
'#9254de',
'#597ef7',
'#4f4f4f'
]
const DICEBEAR_GLYPHS_AVATAR_BASE_URL = 'https://api.dicebear.com/10.x/glyphs/svg'
const normalizeSeed = (id) => {
if (id === null || id === undefined || String(id).trim() === '') {
@ -23,52 +7,7 @@ const normalizeSeed = (id) => {
return String(id).trim()
}
const hashSeed = (seed) => {
let hash = 2166136261
for (let i = 0; i < seed.length; i += 1) {
hash ^= seed.charCodeAt(i)
hash = Math.imul(hash, 16777619)
}
return hash >>> 0
}
const buildCells = (hash) => {
const cells = []
for (let row = 0; row < GRID_SIZE; row += 1) {
for (let col = 0; col < MIRROR_COLUMNS; col += 1) {
const bitIndex = row * MIRROR_COLUMNS + col
const filled = ((hash >>> bitIndex) & 1) === 1
if (!filled) continue
cells.push([col, row])
const mirroredCol = GRID_SIZE - col - 1
if (mirroredCol !== col) {
cells.push([mirroredCol, row])
}
}
}
if (cells.length === 0) {
cells.push([2, 2])
}
return cells
}
export const generatePixelAvatar = (id) => {
const seed = normalizeSeed(id)
const hash = hashSeed(seed)
const color = COLOR_PALETTE[(hash >>> 16) % COLOR_PALETTE.length]
const cells = buildCells(hash)
.map(([x, y]) => `<rect x="${x}" y="${y}" width="1" height="1"/>`)
.join('')
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${DEFAULT_SIZE}" height="${DEFAULT_SIZE}" viewBox="0 0 ${GRID_SIZE} ${GRID_SIZE}" shape-rendering="crispEdges"><rect width="${GRID_SIZE}" height="${GRID_SIZE}" fill="${DEFAULT_BACKGROUND}"/><g fill="${color}">${cells}</g></svg>`
return `data:image/svg+xml,${encodeURIComponent(svg)}`
return `${DICEBEAR_GLYPHS_AVATAR_BASE_URL}?seed=${encodeURIComponent(seed)}`
}