feat: 公开个人主页(/u/:id 名片+统计+帖子/回复内容流,全站头像可点跳转,bio 签名+最后活跃)

This commit is contained in:
2026-08-13 02:12:14 +08:00
parent 4c2017cd2c
commit c8707cf704
15 changed files with 808 additions and 12 deletions
+34 -5
View File
@@ -1,7 +1,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
/** 名称哈希 → 0..11 色调组(复用 style.css 的 --fi-* 12 色 MD3 变量,深浅色自适应) */
function hashTone(name) {
/** 名称哈希 → 0..11 色调组(复用 style.css 的 --fi-* 12 色 MD3 变量,深浅色自适应;供 Banner 渐变等复用 */
export function hashTone(name) {
let h = 0;
const k = String(name || '');
for (let i = 0; i < k.length; i += 1) h = (h * 31 + k.charCodeAt(i)) >>> 0;
@@ -12,12 +13,40 @@ function hashTone(name) {
* 圆形用户头像(MD3):
* - src 非空 → 圆形 <img>object-fit: cover
* - src 空 → 首字兜底(name[0] 大写 + 名称哈希底色)
* props: { src, name, size=36, className }
* - to 非空 → 外层包 <Link to={to}>(点击跳公开主页;内层 stopPropagation 防嵌套 Link 冒泡)
* props: { src, name, size=36, className, to }
*/
export default function Avatar({ src = '', name = '', size = 36, className = '' }) {
export default function Avatar({ src = '', name = '', size = 36, className = '', to }) {
const style = { width: size, height: size, fontSize: Math.round(size * 0.42) };
const base = 'avatar' + (className ? ' ' + className : '');
const alt = name ? `${name} 的头像` : '头像';
const letter = String(name || '?').charAt(0).toUpperCase();
// 跳转版本:Link 作为圆形容器(cursor 指针),fallback 底色放内层 span
if (to) {
return (
<Link
to={to}
className={base + ' avatar-link'}
style={style}
aria-label={alt}
title={name}
onClick={(e) => e.stopPropagation()}
>
{src
? <img src={src} alt={alt} />
: (
<span
className={'avatar-fallback tone-' + hashTone(name)}
style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: style.fontSize }}
>
{letter}
</span>
)}
</Link>
);
}
if (src) {
return (
<span className={base} style={style}>
@@ -27,7 +56,7 @@ export default function Avatar({ src = '', name = '', size = 36, className = ''
}
return (
<span className={`${base} avatar-fallback tone-${hashTone(name)}`} style={style} role="img" aria-label={alt}>
{String(name || '?').charAt(0).toUpperCase()}
{letter}
</span>
);
}