feat(user): enable opening profile edit modal by clicking avatar or nickname
这个提交包含在:
@@ -2,11 +2,16 @@
|
||||
<view class="user-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
|
||||
<!-- 用户头部个人资料卡 -->
|
||||
<view class="profile-card" @click="!isLoggedIn && openLoginModal()">
|
||||
<image class="user-avatar" :src="isLoggedIn && user.avatar ? user.avatar : DEFAULT_GUEST_AVATAR" mode="aspectFill" />
|
||||
<image
|
||||
class="user-avatar btn-active"
|
||||
:src="isLoggedIn && user.avatar ? user.avatar : DEFAULT_GUEST_AVATAR"
|
||||
mode="aspectFill"
|
||||
@click.stop="handleAvatarOrNameClick"
|
||||
/>
|
||||
<view class="user-info">
|
||||
<!-- 顶层水平对齐行:昵称/VIP 与 月亮太阳切换/设置 在同一水平线 -->
|
||||
<view class="user-top-row">
|
||||
<view class="name-row">
|
||||
<view class="name-row btn-active" @click.stop="handleAvatarOrNameClick">
|
||||
<text class="user-name">{{ isLoggedIn ? user.nickname : '点击登录 / 注册' }}</text>
|
||||
<text class="vip-badge" v-if="isLoggedIn">VIP 黄金玩家</text>
|
||||
<text class="guest-badge" v-else>未登录</text>
|
||||
@@ -29,12 +34,12 @@
|
||||
<image class="theme-icon-svg" :src="ICONS.sun" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit-btn btn-active" v-if="isLoggedIn" @click.stop="editProfile">
|
||||
<view class="edit-btn btn-active" v-if="isLoggedIn" @click.stop="editProfile" title="设置资料">
|
||||
<image class="edit-svg" :src="ICONS.settings" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="user-uid">{{ isLoggedIn ? `UID: ${user.id}` : '登录后享受完整陪伴服务' }}</text>
|
||||
<text class="user-uid" @click.stop="handleAvatarOrNameClick">{{ isLoggedIn ? `UID: ${user.id}` : '登录后享受完整陪伴服务' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -164,13 +169,20 @@
|
||||
</view>
|
||||
|
||||
<view class="modal-body">
|
||||
<!-- 预设头像切换与真实上传 -->
|
||||
<!-- 预设头像切换与真实上传 (支持微信官方头像与相册上传) -->
|
||||
<view class="edit-avatar-section">
|
||||
<image class="avatar-preview btn-active" :src="editForm.avatar" mode="aspectFill" @click="chooseCustomAvatar" />
|
||||
<view class="avatar-meta">
|
||||
<view class="avatar-label-row">
|
||||
<text class="avatar-label">点击更换推荐头像</text>
|
||||
<text class="btn-custom-upload btn-active" @click="chooseCustomAvatar">相册上传</text>
|
||||
<view class="avatar-action-links">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="btn-wechat-choose btn-active" open-type="chooseAvatar" @chooseavatar="onWechatChooseAvatar">
|
||||
微信头像
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<text class="btn-custom-upload btn-active" @click="chooseCustomAvatar">相册上传</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="preset-avatars-row">
|
||||
<image
|
||||
@@ -186,14 +198,15 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 昵称输入 -->
|
||||
<!-- 昵称输入 (支持微信官方 type="nickname" 快速轻触填入) -->
|
||||
<view class="edit-field">
|
||||
<text class="field-label">用户昵称</text>
|
||||
<input
|
||||
class="field-input"
|
||||
type="nickname"
|
||||
v-model="editForm.nickname"
|
||||
maxlength="15"
|
||||
placeholder="请输入新昵称"
|
||||
placeholder="请输入新昵称或轻触填入微信昵称"
|
||||
placeholder-class="field-placeholder"
|
||||
/>
|
||||
</view>
|
||||
@@ -520,6 +533,32 @@ const editProfile = () => {
|
||||
showEditModal.value = true;
|
||||
};
|
||||
|
||||
// 点击头像或名称行:已登录时直接打开个人资料设置弹窗,未登录时引导登录
|
||||
const handleAvatarOrNameClick = () => {
|
||||
if (isLoggedIn.value) {
|
||||
editProfile();
|
||||
} else {
|
||||
openLoginModal();
|
||||
}
|
||||
};
|
||||
|
||||
// 微信官方快捷选取头像回调
|
||||
const onWechatChooseAvatar = async (e) => {
|
||||
const avatarUrl = e?.detail?.avatarUrl;
|
||||
if (!avatarUrl) return;
|
||||
uni.showLoading({ title: '正在同步微信头像...' });
|
||||
try {
|
||||
const onlineUrl = await uploadImage(avatarUrl);
|
||||
editForm.value.avatar = onlineUrl || avatarUrl;
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '微信头像获取成功', icon: 'success' });
|
||||
} catch (err) {
|
||||
uni.hideLoading();
|
||||
editForm.value.avatar = avatarUrl;
|
||||
uni.showToast({ title: '已选取微信头像', icon: 'none' });
|
||||
}
|
||||
};
|
||||
|
||||
const chooseCustomAvatar = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
@@ -873,6 +912,13 @@ const navTo = (url) => {
|
||||
border: 4rpx solid #6366F1;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 16rpx rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@@ -892,6 +938,12 @@ const navTo = (url) => {
|
||||
gap: 12rpx;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease;
|
||||
|
||||
&:active {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 32rpx;
|
||||
@@ -986,6 +1038,12 @@ const navTo = (url) => {
|
||||
font-size: 22rpx;
|
||||
color: #64748B;
|
||||
font-family: monospace;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1328,15 +1386,40 @@ const navTo = (url) => {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.btn-custom-upload {
|
||||
font-size: 22rpx;
|
||||
color: #6366F1;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 14rpx;
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||
border-radius: 8rpx;
|
||||
cursor: pointer;
|
||||
.avatar-action-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.btn-wechat-choose {
|
||||
margin: 0;
|
||||
padding: 4rpx 14rpx;
|
||||
font-size: 22rpx;
|
||||
color: #10B981;
|
||||
font-weight: 600;
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
border-radius: 8rpx;
|
||||
line-height: 1.4;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-custom-upload {
|
||||
font-size: 22rpx;
|
||||
color: #6366F1;
|
||||
font-weight: 600;
|
||||
padding: 4rpx 14rpx;
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||
border-radius: 8rpx;
|
||||
cursor: pointer;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1551,8 +1634,17 @@ const navTo = (url) => {
|
||||
}
|
||||
|
||||
.avatar-meta {
|
||||
.avatar-label {
|
||||
color: #78716C;
|
||||
.avatar-action-links {
|
||||
.btn-wechat-choose {
|
||||
color: #059669;
|
||||
background: #ECFDF5;
|
||||
border-color: rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
.btn-custom-upload {
|
||||
color: #EA580C;
|
||||
background: #FFF7ED;
|
||||
border-color: rgba(234, 88, 12, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.preset-avatars-row .preset-avatar-item.selected {
|
||||
|
||||
@@ -397,6 +397,17 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
expect(content).toContain('if (!ensureLoggedIn()) return;');
|
||||
});
|
||||
|
||||
it('个人中心支持轻触头像、名称与UID呼出个人信息设置弹窗,并支持微信官方选头像与快速填入昵称', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain('handleAvatarOrNameClick');
|
||||
expect(content).toContain('showEditModal');
|
||||
expect(content).toContain('editProfile');
|
||||
expect(content).toContain('open-type="chooseAvatar"');
|
||||
expect(content).toContain('type="nickname"');
|
||||
expect(content).toContain('onWechatChooseAvatar');
|
||||
});
|
||||
|
||||
it('所有个人中心改造代码严禁包含任何系统 Emoji', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const text = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
|
||||
在新工单中引用
屏蔽一个用户