我的页面中的头像

This commit is contained in:
2026-07-21 08:10:52 +08:00
parent e1f5130c18
commit c5f526d51d
4 changed files with 330 additions and 160 deletions
+77 -47
View File
@@ -1,7 +1,5 @@
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { request } from './api'; import { API_BASE, CLIENT_APP, getToken, request, type UserProfile } from './api';
import type { UserProfile } from './api';
export type MiniWechatProfile = { export type MiniWechatProfile = {
nickname?: string; nickname?: string;
@@ -32,26 +30,81 @@ export function getCachedWxProfile(): MiniWechatProfile | null {
} }
export function isDefaultMiniNickname(nickname?: string | null): boolean { export function isDefaultMiniNickname(nickname?: string | null): boolean {
if (!nickname || nickname === '访客') return true; if (!nickname || nickname === '访客' || nickname === '微信用户' || nickname === '用户') return true;
return /^用户\d{4}$/.test(nickname); return /^用户\d{4}$/.test(nickname);
} }
/** 是否缺少可展示的微信头像/昵称(需走 chooseAvatar + nickname 填写) */
export function needsWxProfileFill(profile: UserProfile | null | undefined): boolean {
if (!profile) return true;
return !profile.avatarUrl || isDefaultMiniNickname(profile.nickname);
}
export function mergeWxDisplayProfile(profile: UserProfile): UserProfile { export function mergeWxDisplayProfile(profile: UserProfile): UserProfile {
if (!profile.hasWechat) return profile;
const cached = getCachedWxProfile(); const cached = getCachedWxProfile();
if (!cached) return profile; if (!cached && !profile.hasWechat) return profile;
const nickname =
(!isDefaultMiniNickname(profile.nickname) ? profile.nickname : undefined) ||
cached?.nickname ||
profile.nickname ||
'微信用户';
return { return {
...profile, ...profile,
nickname: nickname,
cached.nickname || avatarUrl: profile.avatarUrl || cached?.avatarUrl || null,
(!isDefaultMiniNickname(profile.nickname) ? profile.nickname : undefined) ||
profile.nickname ||
'微信用户',
avatarUrl: profile.avatarUrl || cached.avatarUrl || null,
}; };
} }
/** 用户点击触发:拉取微信昵称/头像 */ /** 上传 chooseAvatar 临时文件到 OSS,返回永久 URL */
export async function uploadAvatarTempFile(tempFilePath: string): Promise<string> {
const token = getToken();
if (!token) throw new Error('请先登录');
const res = await Taro.uploadFile({
url: `${API_BASE}/common/resources/upload`,
filePath: tempFilePath,
name: 'file',
formData: {
bizType: 'AVATAR',
mediaType: 'IMAGE',
},
header: {
Authorization: `Bearer ${token}`,
'X-Client-App': CLIENT_APP,
},
});
let body: { code?: number; message?: string; data?: { url?: string } } = {};
try {
body = JSON.parse(String(res.data || '{}')) as typeof body;
} catch {
throw new Error('头像上传响应异常');
}
if (res.statusCode === 401 || body.code === 401) {
throw new Error(body.message || '登录已过期,请重新登录');
}
if (res.statusCode >= 400 || body.code !== 0 || !body.data?.url) {
throw new Error(body.message || '头像上传失败');
}
return body.data.url;
}
export async function uploadMiniWechatProfile(info: MiniWechatProfile): Promise<UserProfile | null> {
if (!info.nickname && !info.avatarUrl) return null;
const updated = await request<UserProfile>('/auth/wechat/mini-profile', {
method: 'POST',
data: info,
});
cacheWxProfile({
nickname: updated?.nickname ?? info.nickname,
avatarUrl: updated?.avatarUrl ?? info.avatarUrl,
});
return updated;
}
/**
* @deprecated getUserProfile 已收回真实头像昵称,仅作兼容;小程序请用 chooseAvatar + nickname
*/
export async function fetchMiniWechatUserInfo(): Promise<MiniWechatProfile> { export async function fetchMiniWechatUserInfo(): Promise<MiniWechatProfile> {
if (process.env.TARO_ENV !== 'weapp') { if (process.env.TARO_ENV !== 'weapp') {
throw new Error('请在微信小程序中授权'); throw new Error('请在微信小程序中授权');
@@ -64,45 +117,22 @@ export async function fetchMiniWechatUserInfo(): Promise<MiniWechatProfile> {
if (!info.nickname && !info.avatarUrl) { if (!info.nickname && !info.avatarUrl) {
throw new Error('未获取到微信头像或昵称'); throw new Error('未获取到微信头像或昵称');
} }
// 灰色默认头像 / 「微信用户」视为无效,需走填写能力
if (info.nickname === '微信用户' || !info.avatarUrl) {
throw new Error('请使用头像昵称填写能力完善资料');
}
cacheWxProfile(info); cacheWxProfile(info);
return info; return info;
} }
export async function uploadMiniWechatProfile(info: MiniWechatProfile): Promise<MiniWechatProfile | null> { /** 绑定后上报微信资料(优先使用已拉取的信息) */
if (!info.nickname && !info.avatarUrl) return null; export async function syncMiniWechatProfile(
try { prefetched?: MiniWechatProfile | null,
const updated = await request<{ ): Promise<MiniWechatProfile | null> {
nickname?: string | null;
avatarUrl?: string | null;
}>('/auth/wechat/mini-profile', {
method: 'POST',
data: info,
});
if (updated?.nickname || updated?.avatarUrl) {
cacheWxProfile({
nickname: updated.nickname ?? info.nickname,
avatarUrl: updated.avatarUrl ?? info.avatarUrl,
});
}
return info;
} catch {
return null;
}
}
/** 绑定后上报微信资料(优先使用已拉取的信息,避免重复弹窗) */
export async function syncMiniWechatProfile(prefetched?: MiniWechatProfile | null): Promise<MiniWechatProfile | null> {
if (process.env.TARO_ENV !== 'weapp') return null; if (process.env.TARO_ENV !== 'weapp') return null;
if (!prefetched?.nickname && !prefetched?.avatarUrl) {
let info = prefetched ?? null;
if (!info) {
try {
info = await fetchMiniWechatUserInfo();
} catch {
return getCachedWxProfile(); return getCachedWxProfile();
} }
} await uploadMiniWechatProfile(prefetched);
return prefetched;
await uploadMiniWechatProfile(info);
return info;
} }
+3 -11
View File
@@ -18,7 +18,6 @@ import {
import { fetchUserProfile } from '../../lib/pay-wechat'; import { fetchUserProfile } from '../../lib/pay-wechat';
import { saveUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone'; import { saveUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
import { import {
fetchMiniWechatUserInfo,
getCachedWxProfile, getCachedWxProfile,
syncMiniWechatProfile, syncMiniWechatProfile,
type MiniWechatProfile, type MiniWechatProfile,
@@ -235,15 +234,8 @@ export default function LoginPage() {
setSentHint(''); setSentHint('');
setWxLoading(true); setWxLoading(true);
try { try {
let wxInfo: MiniWechatProfile | null = null; // 头像昵称改由「我的」页 chooseAvatar / nickname 填写;登录仅换 openId
if (process.env.TARO_ENV === 'weapp') { const wxInfo = getCachedWxProfile();
try {
wxInfo = await fetchMiniWechatUserInfo();
} catch (e) {
toast(e instanceof Error ? e.message : '需要授权微信头像和昵称');
return;
}
}
if (completeMode === 'wechat' && isLoggedIn()) { if (completeMode === 'wechat' && isLoggedIn()) {
const result = await bindWechatForUser(wxInfo); const result = await bindWechatForUser(wxInfo);
@@ -258,7 +250,7 @@ export default function LoginPage() {
return; return;
} }
if (result.ok) { if (result.ok) {
await syncMiniWechatProfile(wxInfo); if (wxInfo) await syncMiniWechatProfile(wxInfo);
toast('微信授权成功', 'success'); toast('微信授权成功', 'success');
finishLoginNavigate(returnTo); finishLoginNavigate(returnTo);
return; return;
+196 -89
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { View, Text, Image } from '@tarojs/components'; import { View, Text, Image, Button, Input } from '@tarojs/components';
import Taro, { useDidShow } from '@tarojs/taro'; import Taro, { useDidShow } from '@tarojs/taro';
import { isWxAuthorizeEnabled, type ClientRuntimeConfig } from '@dukang/shared-types'; import { isWxAuthorizeEnabled, type ClientRuntimeConfig } from '@dukang/shared-types';
import PageShell from '../../components/PageShell'; import PageShell from '../../components/PageShell';
@@ -10,8 +10,10 @@ import { goLogin } from '../../lib/auth-nav';
import { bindWechatForUser } from '../../lib/wechat-auth'; import { bindWechatForUser } from '../../lib/wechat-auth';
import { fetchUserProfile } from '../../lib/pay-wechat'; import { fetchUserProfile } from '../../lib/pay-wechat';
import { import {
fetchMiniWechatUserInfo,
mergeWxDisplayProfile, mergeWxDisplayProfile,
needsWxProfileFill,
uploadAvatarTempFile,
uploadMiniWechatProfile,
} from '../../lib/mini-wechat-profile'; } from '../../lib/mini-wechat-profile';
import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api'; import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api';
import { isWechatEnv } from '../../lib/weixin'; import { isWechatEnv } from '../../lib/weixin';
@@ -29,6 +31,8 @@ const SERVICES = [
{ icon: '关', label: '关于我们', action: 'about' as const }, { icon: '关', label: '关于我们', action: 'about' as const },
] as const; ] as const;
const isWeapp = process.env.TARO_ENV === 'weapp';
function formatMoney(amount: number) { function formatMoney(amount: number) {
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
} }
@@ -40,11 +44,30 @@ export default function MinePage() {
const [orderCounts, setOrderCounts] = useState<Record<string, number>>({}); const [orderCounts, setOrderCounts] = useState<Record<string, number>>({});
const [wxAuthorize, setWxAuthorize] = useState(true); const [wxAuthorize, setWxAuthorize] = useState(true);
const [bindingWx, setBindingWx] = useState(false); const [bindingWx, setBindingWx] = useState(false);
const [savingProfile, setSavingProfile] = useState(false);
const [editingProfile, setEditingProfile] = useState(false);
const [draftNickname, setDraftNickname] = useState('');
function resetGuestState() { function resetGuestState() {
setProfile(null); setProfile(null);
setBenefitBalance(0); setBenefitBalance(0);
setOrderCounts({}); setOrderCounts({});
setDraftNickname('');
setEditingProfile(false);
}
function applyProfile(me: UserProfile) {
const merged = mergeWxDisplayProfile(me);
setProfile(merged);
if (!needsWxProfileFill(merged)) {
setDraftNickname(merged.nickname || '');
setEditingProfile(false);
return;
}
// 缺头像但有真实昵称时预填
if (merged.nickname && !/^用户\d{4}$/.test(merged.nickname) && merged.nickname !== '微信用户') {
setDraftNickname(merged.nickname);
}
} }
function loadProfile() { function loadProfile() {
@@ -57,7 +80,7 @@ export default function MinePage() {
), ),
]) ])
.then(([me, coupons, ...totals]) => { .then(([me, coupons, ...totals]) => {
setProfile(mergeWxDisplayProfile(me)); applyProfile(me);
const balance = (coupons as Array<Record<string, unknown>>).reduce((sum, c) => { const balance = (coupons as Array<Record<string, unknown>>).reduce((sum, c) => {
if (String(c.status) === 'ACTIVE') return sum + Number(c.balance || 0); if (String(c.status) === 'ACTIVE') return sum + Number(c.balance || 0);
return sum; return sum;
@@ -89,93 +112,134 @@ export default function MinePage() {
.catch(() => setWxAuthorize(true)); .catch(() => setWxAuthorize(true));
}, []); }, []);
async function handleAvatarTap() { async function ensureWechatBound(): Promise<boolean> {
if (!isLoggedIn()) { if (profile?.hasWechat) return true;
goLogin('/pages/mine/index');
return;
}
if (bindingWx) return;
let current = profile;
if (!current) {
try {
current = await fetchUserProfile();
setProfile(current);
} catch {
/* ignore */
}
}
if (current?.hasWechat) return;
if (!wxAuthorize) { if (!wxAuthorize) {
toast('当前环境未开启微信授权'); toast('当前环境未开启微信授权');
return; return false;
} }
setBindingWx(true); setBindingWx(true);
try { try {
if (process.env.TARO_ENV === 'h5') { if (!isWeapp) {
if (!isWechatEnv()) { if (!isWechatEnv()) {
toast('请在微信内打开后授权'); toast('请在微信内打开后授权');
return; return false;
} }
const result = await bindWechatForUser(); const result = await bindWechatForUser();
if (!result.ok && 'redirecting' in result && result.redirecting) { if (!result.ok && 'redirecting' in result && result.redirecting) return false;
return;
}
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) { if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
goLogin('/pages/mine/index', { goLogin('/pages/mine/index', {
bindMode: '1', bindMode: '1',
wxSessionKey: result.wxSessionKey, wxSessionKey: result.wxSessionKey,
}); });
return; return false;
} }
if (result.ok) { if (result.ok && result.profile) {
setProfile( applyProfile({ ...result.profile, hasWechat: true });
mergeWxDisplayProfile({ return true;
...(result.profile ?? {}),
id: result.profile?.id ?? profile?.id ?? '',
hasWechat: true,
}),
);
loadProfile();
toast('微信授权成功', 'success');
} }
return; return false;
} }
let wxInfo = null; const result = await bindWechatForUser(null);
try {
wxInfo = await fetchMiniWechatUserInfo();
} catch (e) {
toast(e instanceof Error ? e.message : '需要授权微信头像和昵称');
return;
}
const result = await bindWechatForUser(wxInfo);
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) { if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
goLogin('/pages/mine/index', { bindMode: '1', wxSessionKey: result.wxSessionKey }); goLogin('/pages/mine/index', { bindMode: '1', wxSessionKey: result.wxSessionKey });
return; return false;
} }
if (result.ok) { if (result.ok && result.profile) {
const merged = mergeWxDisplayProfile({ applyProfile({ ...result.profile, hasWechat: true });
...(result.profile ?? {}), return true;
id: result.profile?.id ?? profile?.id ?? '',
hasWechat: true,
nickname: result.profile?.nickname || wxInfo.nickname || profile?.nickname,
avatarUrl: result.profile?.avatarUrl || wxInfo.avatarUrl || profile?.avatarUrl,
});
setProfile(merged);
loadProfile();
toast('微信授权成功', 'success');
} }
return false;
} catch (e) { } catch (e) {
toast(e instanceof Error ? e.message : '微信授权失败'); toast(e instanceof Error ? e.message : '微信授权失败');
return false;
} finally { } finally {
setBindingWx(false); setBindingWx(false);
} }
} }
async function persistProfile(patch: { nickname?: string; avatarUrl?: string }, opts?: { silent?: boolean }) {
setSavingProfile(true);
try {
const bound = await ensureWechatBound();
if (!bound) return false;
const updated = await uploadMiniWechatProfile(patch);
if (updated) {
applyProfile(updated);
} else {
const me = await fetchUserProfile();
applyProfile(me);
}
if (!opts?.silent) toast('资料已更新', 'success');
return true;
} catch (e) {
toast(e instanceof Error ? e.message : '保存失败');
return false;
} finally {
setSavingProfile(false);
}
}
async function handleChooseAvatar(e: { detail?: { avatarUrl?: string } }) {
const tempPath = e.detail?.avatarUrl?.trim();
if (!tempPath) {
toast('未获取到头像');
return;
}
if (!isLoggedIn()) {
goLogin('/pages/mine/index');
return;
}
try {
setSavingProfile(true);
const bound = await ensureWechatBound();
if (!bound) return;
const url = await uploadAvatarTempFile(tempPath);
setProfile((prev) =>
prev
? mergeWxDisplayProfile({ ...prev, hasWechat: true, avatarUrl: url })
: { id: '', hasWechat: true, avatarUrl: url, nickname: draftNickname || null },
);
setSavingProfile(false);
const ok = await persistProfile(
{
avatarUrl: url,
nickname: draftNickname.trim() || undefined,
},
{ silent: true },
);
if (ok) toast('头像已更新', 'success');
} catch (err) {
toast(err instanceof Error ? err.message : '头像更新失败');
setSavingProfile(false);
}
}
async function handleNicknameConfirm() {
const nickname = draftNickname.trim();
if (!nickname) {
toast('请填写微信昵称');
return;
}
if (!isLoggedIn()) {
goLogin('/pages/mine/index');
return;
}
await persistProfile({ nickname });
}
/** H5:点击头像走公众号 OAuth */
async function handleH5AvatarTap() {
if (!isLoggedIn()) {
goLogin('/pages/mine/index');
return;
}
if (profile?.hasWechat && !needsWxProfileFill(profile)) return;
await ensureWechatBound();
loadProfile();
}
function handleService(item: (typeof SERVICES)[number]) { function handleService(item: (typeof SERVICES)[number]) {
if ('url' in item && item.url) { if ('url' in item && item.url) {
Taro.navigateTo({ url: item.url }); Taro.navigateTo({ url: item.url });
@@ -190,17 +254,6 @@ export default function MinePage() {
} }
} }
function resolveDisplayProfile(profile: UserProfile | null, hasWechat: boolean) {
if (!profile) {
return { nickname: '用户', avatarUrl: null as string | null };
}
const merged = hasWechat ? mergeWxDisplayProfile(profile) : profile;
return {
nickname: merged.nickname || '用户',
avatarUrl: merged.avatarUrl || null,
};
}
function renderAvatarContent(displayAvatarUrl: string | null) { function renderAvatarContent(displayAvatarUrl: string | null) {
if (displayAvatarUrl) { if (displayAvatarUrl) {
return <Image className="mine-avatar-img" src={displayAvatarUrl} mode="aspectFill" />; return <Image className="mine-avatar-img" src={displayAvatarUrl} mode="aspectFill" />;
@@ -228,10 +281,7 @@ export default function MinePage() {
</View> </View>
<View className="mine-login-gate"> <View className="mine-login-gate">
<View className="mine-login-gate-hint"></View> <View className="mine-login-gate-hint"></View>
<View <View className="mine-login-btn" onClick={() => goLogin('/pages/mine/index')}>
className="mine-login-btn"
onClick={() => goLogin('/pages/mine/index')}
>
<Text></Text> <Text></Text>
</View> </View>
</View> </View>
@@ -241,10 +291,19 @@ export default function MinePage() {
} }
const hasWechat = !!profile?.hasWechat; const hasWechat = !!profile?.hasWechat;
const canWxBind = wxAuthorize && process.env.TARO_ENV === 'weapp'; const needFill = needsWxProfileFill(profile) || editingProfile;
const display = resolveDisplayProfile(profile, hasWechat); const canWxProfile = wxAuthorize && isWeapp;
const nickname = display.nickname; const display = mergeWxDisplayProfile(
const memberLabel = hasWechat ? '好客会员' : canWxBind ? '微信未授权' : '未授权微信'; profile || { id: '', nickname: null, avatarUrl: null, hasWechat: false },
);
const nickname = display.nickname || '用户';
const memberLabel = hasWechat
? needFill
? '完善头像昵称'
: '好客会员'
: canWxProfile
? '微信未授权'
: '未授权微信';
return ( return (
<PageShell variant="tab" className="mine-page no-tab-header"> <PageShell variant="tab" className="mine-page no-tab-header">
@@ -252,29 +311,77 @@ export default function MinePage() {
<View className="mine-header"> <View className="mine-header">
<View className="mine-header-texture" /> <View className="mine-header-texture" />
<View className="mine-profile"> <View className="mine-profile">
<View {canWxProfile ? (
className={`mine-avatar-wrap${!hasWechat && canWxBind ? ' mine-avatar-wrap--action' : ''}`} <Button
onClick={() => void handleAvatarTap()} className="mine-avatar-btn"
openType="chooseAvatar"
onChooseAvatar={(e) => void handleChooseAvatar(e)}
disabled={bindingWx || savingProfile}
> >
<View <View
className={`mine-avatar${hasWechat ? ' mine-avatar--wx-ok' : canWxBind ? ' mine-avatar--wx-pending' : ''}`} className={`mine-avatar${hasWechat && !needFill ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'}`}
> >
{renderAvatarContent(display.avatarUrl)} {renderAvatarContent(display.avatarUrl)}
</View> </View>
{!hasWechat && canWxBind ? ( {needFill || !hasWechat ? (
<View className="mine-avatar-status mine-avatar-status--pending">
<Text>{bindingWx || savingProfile ? '处理中' : hasWechat ? '选头像' : '去授权'}</Text>
</View>
) : null}
</Button>
) : (
<View
className={`mine-avatar-wrap${!hasWechat ? ' mine-avatar-wrap--action' : ''}`}
onClick={() => void handleH5AvatarTap()}
>
<View
className={`mine-avatar${hasWechat ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'}`}
>
{renderAvatarContent(display.avatarUrl)}
</View>
{!hasWechat ? (
<View className="mine-avatar-status mine-avatar-status--pending"> <View className="mine-avatar-status mine-avatar-status--pending">
<Text>{bindingWx ? '授权中' : '去授权'}</Text> <Text>{bindingWx ? '授权中' : '去授权'}</Text>
</View> </View>
) : null} ) : null}
</View> </View>
<View> )}
<View className="mine-profile-meta">
{canWxProfile && needFill ? (
<>
<Input
className="mine-nickname-input"
type="nickname"
value={draftNickname}
placeholder="点击填写微信昵称"
maxlength={32}
onInput={(e) => setDraftNickname(e.detail.value)}
onBlur={() => void handleNicknameConfirm()}
onConfirm={() => void handleNicknameConfirm()}
/>
<Text className="mine-member-tag">{memberLabel}</Text>
<Text className="mine-wechat-hint"></Text>
</>
) : (
<>
<Text className="mine-profile-name">{nickname}</Text> <Text className="mine-profile-name">{nickname}</Text>
<Text className={`mine-member-tag${hasWechat ? ' mine-member-tag--wechat' : ''}`}> <Text className={`mine-member-tag${hasWechat && !needFill ? ' mine-member-tag--wechat' : ''}`}>
{memberLabel} {memberLabel}
</Text> </Text>
{!hasWechat && canWxBind ? ( {canWxProfile ? (
<Text className="mine-wechat-hint"></Text> <Text
className="mine-wechat-hint mine-wechat-hint--link"
onClick={() => {
setDraftNickname(needsWxProfileFill(profile) ? '' : nickname);
setEditingProfile(true);
}}
>
</Text>
) : null} ) : null}
</>
)}
</View> </View>
</View> </View>
</View> </View>
+41
View File
@@ -46,6 +46,42 @@
cursor: pointer; cursor: pointer;
} }
/* 小程序 chooseAvatar:去掉 Button 默认样式,保留圆形头像 */
.mine-avatar-btn {
position: relative;
flex-shrink: 0;
margin: 0 12px 0 0 !important;
padding: 0 !important;
background: transparent !important;
border: none !important;
border-radius: 0 !important;
line-height: 1 !important;
overflow: visible !important;
}
.mine-avatar-btn::after {
border: none !important;
}
.mine-profile-meta {
min-width: 0;
flex: 1;
}
.mine-nickname-input {
width: 100%;
max-width: 200px;
height: 32px;
padding: 0 10px;
margin-bottom: 4px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.95);
color: var(--color-ink-black);
font-size: 15px;
font-weight: 600;
box-sizing: border-box;
}
.mine-avatar-status { .mine-avatar-status {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -93,6 +129,11 @@
color: rgba(255, 255, 255, 0.75); color: rgba(255, 255, 255, 0.75);
} }
.mine-wechat-hint--link {
text-decoration: underline;
text-underline-offset: 2px;
}
.mine-avatar { .mine-avatar {
width: 64px; width: 64px;
height: 64px; height: 64px;