first commit

This commit is contained in:
ytc1012
2026-02-04 16:11:55 +08:00
commit 0f3ee050dc
165 changed files with 25795 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
const app = getApp()
const db = wx.cloud.database()
Page({
data: {
roomId: '',
roomName: '',
roomTime: '',
keywords: '',
keywordOptions: ['咖啡馆', '餐厅', '公园', '商场', '电影院', 'KTV', '酒吧', '火锅', '烧烤', '其他'],
keywordIndex: 0,
requirements: '',
members: [],
readyCount: 0,
hasResult: false,
currentUserOpenId: '',
isCreator: false,
calculating: false,
editingRequirements: false,
meetDate: '',
meetTimeOnly: '',
hasJoined: false,
userInfo: {
avatarUrl: '',
nickName: ''
},
showUserInfoModal: false,
previousStatus: '' // 新增:记录上一次的状态,用于判断是否刚完成计算
},
onLoad: function (options) {
const roomId = options.roomId
const isCreator = options.isCreator === 'true'
this.setData({
roomId,
isCreator,
// 如果是创建者,已经在创建时加入了房间
hasJoined: isCreator
})
// 直接开始监听房间,不需要先加入
this.startWatch()
},
onUnload: function () {
if (this.watcher) {
this.watcher.close()
}
},
async joinGroup() {
// 显示用户信息填写弹窗
this.setData({ showUserInfoModal: true })
},
onChooseAvatar(e) {
const { avatarUrl } = e.detail
this.setData({
'userInfo.avatarUrl': avatarUrl
})
},
onNicknameChange(e) {
const nickName = e.detail.value
this.setData({
'userInfo.nickName': nickName
})
},
onCancelUserInfo() {
this.setData({ showUserInfoModal: false })
},
async onConfirmUserInfo() {
const { userInfo } = this.data
// 检查是否填写了昵称
if (!userInfo.nickName || !userInfo.nickName.trim()) {
wx.showToast({
title: '请输入你的昵称',
icon: 'none'
})
return
}
// 关闭弹窗
this.setData({ showUserInfoModal: false })
// 调用云函数加入
wx.showLoading({ title: '加入房间...' })
try {
const res = await wx.cloud.callFunction({
name: 'joinRoom',
data: {
roomId: this.data.roomId,
userInfo: userInfo
}
})
wx.hideLoading()
if (!res.result.success) {
wx.showModal({ title: '错误', content: res.result.msg, showCancel: false })
return false
}
this.setData({ hasJoined: true })
// 加入成功后,继续添加位置
if (this.pendingAddLocation) {
this.pendingAddLocation()
this.pendingAddLocation = null
}
return true
} catch (err) {
console.error(err)
wx.hideLoading()
wx.showToast({ title: '加入失败', icon: 'none' })
return false
}
},
startWatch() {
// 先获取当前用户的 openid
this.getCurrentUserOpenId()
this.watcher = db.collection('rooms').doc(this.data.roomId).watch({
onChange: snapshot => {
if (snapshot.docs && snapshot.docs.length > 0) {
const roomData = snapshot.docs[0]
const members = roomData.members || []
const readyCount = members.filter(m => m.location).length
const hasResult = roomData.result && roomData.result.success && roomData.status === 'calculated'
const isCalculating = roomData.status === 'calculating'
const currentStatus = roomData.status
const roomTime = roomData.meetTime || ''
const [date, time] = roomTime.includes(' ') ? roomTime.split(' ') : [roomTime, '']
const keyword = roomData.keywords || '咖啡馆'
const keywordIndex = this.data.keywordOptions.indexOf(keyword)
// 检查当前用户是否已经在成员列表中
const currentUserOpenId = this.data.currentUserOpenId
const hasJoined = currentUserOpenId && members.some(m => m.openid === currentUserOpenId)
// 判断是否刚完成计算(从 calculating 变为 calculated
const justFinishedCalculating = this.data.previousStatus === 'calculating' && currentStatus === 'calculated'
this.setData({
members,
readyCount,
hasResult,
calculating: isCalculating, // 同步云端计算状态
roomName: roomData.name || '未命名聚会',
roomTime: roomTime,
meetDate: date,
meetTimeOnly: time,
keywords: keyword,
keywordIndex: keywordIndex >= 0 ? keywordIndex : 0,
requirements: roomData.requirements || '',
hasJoined: hasJoined || this.data.hasJoined,
previousStatus: currentStatus // 更新状态记录
})
// 只有刚完成计算时才自动跳转到结果页
if (justFinishedCalculating && hasResult) {
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
}
}
},
onError: err => {
console.error('Watch Error', err)
}
})
},
async getCurrentUserOpenId() {
try {
const res = await wx.cloud.callFunction({
name: 'getOpenId'
})
if (res.result && res.result.openid) {
this.setData({ currentUserOpenId: res.result.openid })
}
} catch (err) {
console.error('获取openid失败', err)
}
},
onAddLocation() {
if (!this.data.roomId) return
// 定义添加位置的函数
const doAddLocation = () => {
wx.chooseLocation({
success: (res) => {
const { latitude, longitude, address, name } = res
wx.showLoading({ title: '提交位置...' })
wx.cloud.callFunction({
name: 'updateLocation',
data: {
roomId: this.data.roomId,
location: {
lng: longitude,
lat: latitude,
address: address || '地图选点',
name: name || '我的位置'
}
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: '已更新', icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
fail: (err) => {
if (err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '选择位置失败', icon: 'none' })
}
}
})
}
// 如果还没加入房间,先加入
if (!this.data.hasJoined) {
// 保存待执行的操作
this.pendingAddLocation = doAddLocation
this.joinGroup()
} else {
doAddLocation()
}
},
onShareAppMessage() {
return {
title: '快来填位置,我们去找个中间点聚会!',
path: `/pages/room/room?roomId=${this.data.roomId}`
}
},
// 添加测试成员(仅用于开发测试)
onAddTestMember() {
wx.showModal({
title: '添加测试成员',
content: '请输入成员名称',
editable: true,
placeholderText: '测试成员',
success: (res) => {
if (res.confirm && res.content) {
const nickName = res.content.trim() || '测试成员'
wx.showLoading({ title: '添加中...' })
wx.cloud.callFunction({
name: 'addTestMember',
data: {
roomId: this.data.roomId,
nickName: nickName
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: '添加成功', icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '添加失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
}
}
})
},
// 为测试成员添加/修改位置
onAddMemberLocation(e) {
const { openid, nickname, istest } = e.currentTarget.dataset
// 检查是否是测试成员,如果不是则不处理
if (!istest) {
return
}
wx.chooseLocation({
success: (res) => {
const { latitude, longitude, address, name } = res
wx.showLoading({ title: '提交中...' })
wx.cloud.callFunction({
name: 'updateMemberLocation',
data: {
roomId: this.data.roomId,
memberOpenid: openid,
location: {
lng: longitude,
lat: latitude,
address: address || '地图选点',
name: name || '选定位置'
}
},
success: res => {
wx.hideLoading()
if (res.result.success) {
wx.showToast({ title: `${nickname}位置已更新`, icon: 'success' })
} else {
wx.showToast({ title: res.result.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error(err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
fail: (err) => {
if (err.errMsg.indexOf('cancel') === -1) {
wx.showToast({ title: '选择位置失败', icon: 'none' })
}
}
})
},
onCalculate() {
// 前端防抖:检查是否正在计算
if (this.data.calculating) {
wx.showToast({
title: '正在计算中,请稍候...',
icon: 'none',
duration: 2000
})
return
}
const membersWithLocation = this.data.members.filter(m => m.location).length
if (membersWithLocation < 2) {
wx.showToast({
title: '至少需要2人添加位置才能计算',
icon: 'none',
duration: 2000
})
return
}
// 设置计算状态,禁用按钮
this.setData({ calculating: true })
wx.showLoading({ title: '计算最佳地点...' })
wx.cloud.callFunction({
name: 'calculateMeetSpot',
data: { roomId: this.data.roomId },
success: res => {
wx.hideLoading()
this.setData({ calculating: false })
// 处理后端返回的状态锁信息
if (res.result.isCalculating) {
wx.showToast({
title: res.result.msg || '正在计算中',
icon: 'none',
duration: 2000
})
return
}
// 处理重复计算拦截
if (res.result.isDuplicate) {
wx.showModal({
title: '提示',
content: res.result.msg || '成员位置未变化,无需重复计算',
confirmText: '查看结果',
cancelText: '知道了',
success: (modalRes) => {
if (modalRes.confirm) {
// 跳转到结果页
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
}
}
})
return
}
if (!res.result.success) {
wx.showModal({
title: '计算失败',
content: res.result.msg,
showCancel: false
})
}
// 成功后依靠 watch 自动跳转,或者这里手动跳转也可以
},
fail: err => {
wx.hideLoading()
this.setData({ calculating: false })
console.error(err)
wx.showToast({ title: '调用失败', icon: 'none' })
}
})
},
onViewResult() {
if (!this.data.hasResult) {
wx.showToast({ title: '暂无计算结果', icon: 'none' })
return
}
wx.navigateTo({
url: `/pages/result/result?roomId=${this.data.roomId}`
})
},
onEditRoomName() {
wx.showModal({
title: '修改聚会名称',
content: '请输入新的聚会名称',
editable: true,
placeholderText: this.data.roomName,
success: (res) => {
if (res.confirm && res.content.trim()) {
const newName = res.content.trim()
this.updateRoomInfo({ name: newName })
}
}
})
},
onEditRoomTime() {
// 不需要做任何事,交给 picker 组件处理
},
onDateChange(e) {
const date = e.detail.value
this.setData({ meetDate: date })
this.updateMeetTime()
},
onTimeChange(e) {
const time = e.detail.value
this.setData({ meetTimeOnly: time })
this.updateMeetTime()
},
updateMeetTime() {
const { meetDate, meetTimeOnly } = this.data
let newTime = ''
if (meetDate && meetTimeOnly) {
newTime = `${meetDate} ${meetTimeOnly}`
} else if (meetDate) {
newTime = meetDate
}
if (newTime) {
this.setData({ roomTime: newTime })
this.updateRoomInfo({ meetTime: newTime })
}
},
onKeywordChange(e) {
const index = e.detail.value
const keyword = this.data.keywordOptions[index]
this.setData({
keywordIndex: index,
keywords: keyword
})
this.updateRoomInfo({ keywords: keyword })
},
onEditKeywords() {
// 已改用 picker此方法不再需要
},
onEditRequirements() {
this.setData({ editingRequirements: true })
},
onRequirementsInput(e) {
this.setData({ requirements: e.detail.value })
},
onRequirementsBlur() {
this.setData({ editingRequirements: false })
this.updateRoomInfo({ requirements: this.data.requirements })
},
onRequirementsConfirm() {
this.setData({ editingRequirements: false })
this.updateRoomInfo({ requirements: this.data.requirements })
},
updateRoomInfo(updateData) {
wx.showLoading({ title: '更新中...' })
wx.cloud.callFunction({
name: 'updateRoomInfo',
data: {
roomId: this.data.roomId,
...updateData
},
success: res => {
wx.hideLoading()
if (res.result && res.result.success) {
wx.showToast({ title: '更新成功', icon: 'success' })
} else {
wx.showToast({ title: res.result?.msg || '更新失败', icon: 'none' })
}
},
fail: err => {
wx.hideLoading()
console.error('更新失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
},
// 退出聚会(普通成员)
onLeaveRoom() {
wx.showModal({
title: '确认退出',
content: '确定要退出这个聚会吗?',
success: (res) => {
if (res.confirm) {
this.removeMember()
}
}
})
},
// 移除成员(创建者权限)
onRemoveMember(e) {
const { openid, nickname } = e.currentTarget.dataset
// 不能移除测试成员(测试成员应该用专门的删除逻辑)
const member = this.data.members.find(m => m.openid === openid)
if (member && member.isTestMember) {
wx.showToast({ title: '请使用删除功能移除测试成员', icon: 'none' })
return
}
wx.showModal({
title: '移除成员',
content: `确定要移除 ${nickname} 吗?`,
success: (res) => {
if (res.confirm) {
this.removeMember(openid, nickname)
}
}
})
},
// 执行移除操作
removeMember(memberOpenid = null, nickName = null) {
wx.showLoading({ title: '处理中...' })
wx.cloud.callFunction({
name: 'removeMember',
data: {
roomId: this.data.roomId,
memberOpenid: memberOpenid // 不传则退出自己
},
success: res => {
wx.hideLoading()
if (res.result.success) {
const msg = memberOpenid ? `已移除 ${nickName}` : res.result.msg
wx.showToast({
title: msg,
icon: 'success',
duration: 2000
})
// 如果是退出自己,返回首页
if (!memberOpenid) {
setTimeout(() => {
wx.navigateBack({ delta: 1 })
}, 2000)
}
} else {
wx.showModal({
title: '操作失败',
content: res.result.msg,
showCancel: false
})
}
},
fail: err => {
wx.hideLoading()
console.error('移除成员失败', err)
wx.showToast({ title: '网络错误', icon: 'none' })
}
})
}
})

View File

@@ -0,0 +1,5 @@
{
"usingComponents": {},
"navigationBarTitleText": "聚会房间",
"enablePullDownRefresh": false
}

View File

@@ -0,0 +1,112 @@
<view class="container">
<!-- 用户信息填写弹窗 -->
<view class="user-info-modal" wx:if="{{showUserInfoModal}}">
<view class="modal-mask"></view>
<view class="modal-content">
<view class="modal-title">填写你的信息</view>
<view class="modal-body">
<view class="avatar-section">
<button class="avatar-wrapper" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<image class="avatar" src="{{userInfo.avatarUrl || '/images/default-avatar.png'}}" mode="aspectFill"></image>
<view class="avatar-tip">点击选择头像</view>
</button>
</view>
<input type="nickname" class="nickname-input-modal" placeholder="请输入昵称" value="{{userInfo.nickName}}" bindchange="onNicknameChange" />
</view>
<view class="modal-footer">
<button class="modal-btn cancel" bindtap="onCancelUserInfo">取消</button>
<button class="modal-btn confirm" bindtap="onConfirmUserInfo">确定</button>
</view>
</view>
</view>
<!-- 聚会信息 -->
<view class="room-info">
<view class="room-title" bindtap="onEditRoomName">{{roomName}}</view>
<view class="room-tags">
<picker mode="selector" range="{{keywordOptions}}" value="{{keywordIndex}}" bindchange="onKeywordChange">
<view class="tag keyword-tag">{{keywords}}</view>
</picker>
<view class="time-tag-picker">
<picker mode="date" value="{{meetDate}}" bindchange="onDateChange" class="time-picker">
<view class="tag time-tag">
{{meetDate || '选择日期'}}
</view>
</picker>
<picker mode="time" value="{{meetTimeOnly}}" bindchange="onTimeChange" class="time-picker">
<view class="tag time-tag">
{{meetTimeOnly || '选择时间'}}
</view>
</picker>
</view>
</view>
<!-- 特殊需求直接编辑 -->
<view class="requirements-section {{requirements || editingRequirements ? '' : 'empty'}}" bindtap="onEditRequirements">
<view class="requirements-label" wx:if="{{requirements || editingRequirements}}">特殊需求</view>
<textarea
wx:if="{{editingRequirements}}"
class="requirements-textarea"
placeholder="如需要停车位、环境安静、有Wi-Fi等"
value="{{requirements}}"
bindinput="onRequirementsInput"
bindblur="onRequirementsBlur"
bindconfirm="onRequirementsConfirm"
maxlength="200"
auto-focus="{{true}}"
show-confirm-bar="{{false}}"
/>
<view class="requirements-text" wx:elif="{{requirements}}">{{requirements}}</view>
<view class="requirements-text" wx:else>点击添加特殊需求</view>
</view>
</view>
<!-- 顶部统计信息 -->
<view class="header">
<view class="stats">
<text class="stat-item">参与成员 {{members.length}}人</text>
<text class="stat-divider">|</text>
<text class="stat-item ready-count">已就位 {{readyCount}}人</text>
</view>
</view>
<!-- 成员列表 -->
<scroll-view scroll-y class="member-list">
<block wx:for="{{members}}" wx:key="openid">
<view class="member-item {{item.location ? 'ready' : 'waiting'}}"
bindtap="onAddMemberLocation"
bindlongpress="{{isCreator && item.openid !== currentUserOpenId ? 'onRemoveMember' : ''}}"
data-openid="{{item.openid}}"
data-nickname="{{item.nickName}}"
data-istest="{{item.isTestMember}}">
<image class="avatar" src="{{item.avatarUrl || '/images/default-avatar.png'}}" mode="aspectFill"></image>
<view class="info">
<view class="nickname">
{{item.nickName}}
<text wx:if="{{item.openid === currentUserOpenId}}" class="me-tag">(我)</text>
</view>
<view class="status">
{{item.location ? item.location.name || '已选定位置' : '等待添加位置...'}}
</view>
</view>
<view class="status-icon">{{item.location ? '√' : '○'}}</view>
</view>
</block>
</scroll-view>
<!-- 测试按钮(浮动) -->
<view class="test-float-btn" bindtap="onAddTestMember" wx:if="{{true}}">
<text>+</text>
</view>
<!-- 底部操作按钮 -->
<view class="footer-actions">
<button class="action-btn secondary-btn" bindtap="onAddLocation">添加/修改我的位置</button>
<button class="action-btn secondary-btn" open-type="share">邀请好友</button>
<button wx:if="{{!isCreator && hasJoined}}" class="action-btn warning-btn" bindtap="onLeaveRoom">退出聚会</button>
<button class="action-btn primary-btn" bindtap="onCalculate" loading="{{calculating}}" disabled="{{calculating}}">
{{calculating ? '计算中...' : '开始计算'}}
</button>
<button class="action-btn result-btn {{hasResult ? '' : 'disabled'}}" bindtap="onViewResult">查看结果</button>
</view>
</view>

View File

@@ -0,0 +1,392 @@
.container {
height: 100vh;
background-color: #f6f7f9;
display: flex;
flex-direction: column;
padding: 0;
align-items: stretch;
}
/* 聚会信息 */
.room-info {
background-color: transparent;
padding: 30rpx 80rpx;
}
.room-title {
font-size: 44rpx;
font-weight: bold;
color: #333;
text-align: center;
margin-bottom: 20rpx;
}
.room-tags {
display: flex;
justify-content: center;
align-items: center;
gap: 16rpx;
flex-wrap: wrap;
}
.time-tag-picker {
display: flex;
gap: 12rpx;
}
.time-picker {
display: inline-block;
}
.tag {
font-size: 26rpx;
background-color: #e8f7f0;
color: #07c160;
padding: 10rpx 24rpx;
border-radius: 30rpx;
white-space: nowrap;
}
.keyword-tag {
cursor: pointer;
user-select: none;
}
.time-tag {
background-color: #f0f0f0;
color: #666;
}
.requirements-section {
margin-top: 20rpx;
padding: 20rpx;
background-color: #fff;
border-radius: 12rpx;
border: 1rpx solid #e5e5e5;
}
.requirements-section.empty {
background-color: #f8f9fa;
border-style: dashed;
}
.requirements-label {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.requirements-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
.requirements-section.empty .requirements-text {
color: #999;
text-align: center;
}
.requirements-textarea {
width: 100%;
min-height: 120rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
padding: 0;
border: none;
background-color: transparent;
}
/* 顶部统计 */
.header {
padding: 10rpx 80rpx 30rpx;
background-color: transparent;
}
.stats {
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.stat-item {
font-size: 32rpx;
}
.stat-divider {
margin: 0 20rpx;
color: #ddd;
}
.ready-count {
color: #07c160;
}
/* 成员列表 */
.member-list {
flex: 1;
padding: 20rpx 40rpx;
padding-bottom: 20rpx;
box-sizing: border-box;
}
.member-item {
display: flex;
align-items: center;
background-color: #fff;
padding: 24rpx;
border-radius: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.04);
transition: all 0.3s;
box-sizing: border-box;
width: 100%;
}
.member-item.ready {
border-left: 4rpx solid #07c160;
}
.member-item.waiting {
border-left: 4rpx solid #ddd;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
background-color: #f0f0f0;
flex-shrink: 0;
overflow: hidden;
}
.info {
flex: 1;
min-width: 0;
}
.nickname {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 8rpx;
}
.me-tag {
font-size: 24rpx;
color: #07c160;
font-weight: normal;
margin-left: 8rpx;
}
.status {
font-size: 26rpx;
color: #666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.status-icon {
font-size: 40rpx;
color: #07c160;
flex-shrink: 0;
}
.member-item.waiting .status-icon {
color: #ddd;
}
/* 测试浮动按钮 */
.test-float-btn {
position: fixed;
right: 30rpx;
bottom: 350rpx;
width: 80rpx;
height: 80rpx;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
z-index: 999;
}
/* 底部操作按钮 */
.footer-actions {
padding: 20rpx 80rpx;
padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
background-color: transparent;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.action-btn {
border-radius: 40rpx;
font-size: 28rpx;
padding: 20rpx 16rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
min-height: 80rpx;
box-sizing: border-box;
width: 100%;
}
.action-btn::after {
border: none;
}
.primary-btn {
background-color: #07c160;
color: white;
}
.secondary-btn {
background-color: #fff;
color: #07c160;
border: 2rpx solid #07c160;
}
.warning-btn {
background-color: #fff;
color: #fa5151;
border: 2rpx solid #fa5151;
}
.result-btn {
background-color: #fff;
color: #07c160;
border: 2rpx solid #07c160;
}
.result-btn.disabled {
background-color: #f5f5f5;
color: #999;
border: 2rpx solid #ddd;
}
/* 用户信息填写弹窗 */
.user-info-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.modal-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600rpx;
background-color: #fff;
border-radius: 24rpx;
padding: 40rpx;
box-sizing: border-box;
}
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 40rpx;
}
.modal-body {
display: flex;
flex-direction: column;
align-items: center;
gap: 30rpx;
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
}
.user-info-modal .avatar-wrapper {
padding: 0;
margin: 0;
border: none;
background: none;
line-height: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
}
.user-info-modal .avatar-wrapper::after {
border: none;
}
.user-info-modal .avatar {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background-color: #f0f0f0;
border: 2rpx solid #e5e5e5;
}
.avatar-tip {
font-size: 24rpx;
color: #999;
}
.nickname-input-modal {
width: 100%;
font-size: 32rpx;
color: #333;
border: 1rpx solid #e5e5e5;
border-radius: 12rpx;
padding: 20rpx;
text-align: center;
box-sizing: border-box;
}
.modal-footer {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.modal-btn {
flex: 1;
font-size: 32rpx;
padding: 24rpx 0;
border-radius: 50rpx;
}
.modal-btn.cancel {
background-color: #f5f5f5;
color: #666;
}
.modal-btn.confirm {
background-color: #07c160;
color: white;
}