全面优化:小程序 + 后台管理

## 小程序优化
1. 我的页面:
   - 头像点击获取微信头像(button open-type="chooseAvatar")
   - 昵称点击直接修改
   - 去掉"创业伙伴"图标
   - ID优先显示微信号

2. 设置页面:
   - 一键获取微信手机号
   - 微信号/支付宝绑定优化

## 后台管理优化
1. 内容管理:
   - 章节编辑增加"免费章节"开关
   - 保存时自动去重标题(如#1.2重复)

2. 用户管理:
   - 修复绑定关系显示(优先查referral_bindings表)
This commit is contained in:
卡若
2026-01-29 11:58:07 +08:00
parent a228911170
commit d17150154c
8 changed files with 199 additions and 182 deletions

View File

@@ -102,138 +102,41 @@ Page({
}
},
// 编辑用户资料(头像和昵称
editProfile() {
wx.showActionSheet({
itemList: ['获取微信头像', '获取微信昵称', '从相册选择头像', '手动输入昵称'],
success: (res) => {
if (res.tapIndex === 0) {
this.getWechatAvatar()
} else if (res.tapIndex === 1) {
this.getWechatNickname()
} else if (res.tapIndex === 2) {
this.chooseAvatar()
} else if (res.tapIndex === 3) {
this.editNickname()
}
}
})
// 微信头像选择回调button open-type="chooseAvatar"
async onChooseAvatar(e) {
const avatarUrl = e.detail.avatarUrl
if (!avatarUrl) return
wx.showLoading({ title: '更新中...', mask: true })
try {
// 更新本地显示
const userInfo = this.data.userInfo
userInfo.avatar = avatarUrl
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, avatar: avatarUrl }
})
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '更新失败', icon: 'none' })
}
},
// 获取微信头像(原生能力)
getWechatAvatar() {
// 使用chooseAvatar API微信原生头像选择
wx.chooseAvatar({
success: async (res) => {
const avatarUrl = res.avatarUrl
wx.showLoading({ title: '更新中...', mask: true })
try {
// 更新本地显示
const userInfo = this.data.userInfo
userInfo.avatar = avatarUrl
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, avatar: avatarUrl }
})
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '更新失败', icon: 'none' })
}
},
fail: () => {
wx.showToast({ title: '取消选择', icon: 'none' })
}
})
},
// 获取微信昵称(原生能力)
getWechatNickname() {
// 引导用户在弹窗中输入微信昵称
wx.showModal({
title: '获取微信昵称',
content: '请在下方输入您的微信昵称',
editable: true,
placeholderText: '请输入微信昵称',
success: async (res) => {
if (res.confirm && res.content) {
const nickname = res.content.trim()
if (nickname.length < 1 || nickname.length > 20) {
wx.showToast({ title: '昵称1-20个字符', icon: 'none' })
return
}
// 更新本地
const userInfo = this.data.userInfo
userInfo.nickname = nickname
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
try {
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, nickname }
})
} catch (e) {
console.log('同步昵称到服务器失败', e)
}
wx.showToast({ title: '昵称已更新', icon: 'success' })
}
}
})
},
// 从相册选择头像
chooseAvatar() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempFilePath = res.tempFiles[0].tempFilePath
wx.showLoading({ title: '上传中...', mask: true })
try {
// 更新本地显示
const userInfo = this.data.userInfo
userInfo.avatar = tempFilePath
this.setData({ userInfo })
app.globalData.userInfo = userInfo
wx.setStorageSync('userInfo', userInfo)
// 同步到服务器
await app.request('/api/user/update', {
method: 'POST',
data: { userId: userInfo.id, avatar: tempFilePath }
})
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: '上传失败', icon: 'none' })
}
}
})
},
// 手动输入昵称
// 点击昵称修改
editNickname() {
wx.showModal({
title: '修改昵称',
editable: true,
placeholderText: '请输入昵称',
placeholderText: '请输入昵称',
success: async (res) => {
if (res.confirm && res.content) {
const newNickname = res.content.trim()

View File

@@ -27,8 +27,8 @@
<!-- 用户卡片 - 已登录状态 -->
<view class="user-card card-gradient" wx:else>
<view class="user-header-row">
<!-- 头像点击修改 -->
<view class="avatar-wrapper" bindtap="editProfile">
<!-- 头像点击获取微信头像 -->
<button class="avatar-btn" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<view class="avatar">
<image class="avatar-img" wx:if="{{userInfo.avatar}}" src="{{userInfo.avatar}}" mode="aspectFill"/>
<text class="avatar-text" wx:else>{{userInfo.nickname[0] || '微'}}</text>
@@ -36,19 +36,16 @@
<view class="avatar-edit-hint">
<text class="edit-icon">✎</text>
</view>
</view>
</button>
<!-- 用户信息 -->
<!-- 用户信息 - 点击昵称修改 -->
<view class="user-info-block">
<view class="user-name-row">
<view class="user-name-row" bindtap="editNickname">
<text class="user-name">{{userInfo.nickname || '微信用户'}}</text>
<!-- 创业伙伴标签 -->
<view class="user-badge-small">
<text class="badge-star">⭐</text>
<text class="badge-label">创业伙伴</text>
</view>
<text class="edit-name-icon">✎</text>
</view>
<text class="user-id">ID: {{userIdShort}}</text>
<text class="user-wechat" wx:if="{{userInfo.wechatId}}">微信: {{userInfo.wechatId}}</text>
<text class="user-id" wx:else>ID: {{userIdShort}}</text>
</view>
</view>

View File

@@ -69,6 +69,20 @@
margin-bottom: 36rpx;
}
/* 头像按钮样式 */
.avatar-btn {
position: relative;
flex-shrink: 0;
padding: 0;
margin: 0;
background: transparent;
border: none;
line-height: normal;
}
.avatar-btn::after {
border: none;
}
.avatar-wrapper {
position: relative;
flex-shrink: 0;
@@ -146,6 +160,18 @@
color: #ffffff;
}
.edit-name-icon {
font-size: 24rpx;
color: rgba(255,255,255,0.4);
margin-left: 8rpx;
}
.user-wechat {
font-size: 24rpx;
color: #00CED1;
margin-top: 4rpx;
}
.user-badge-small {
display: inline-flex;
align-items: center;

View File

@@ -206,8 +206,10 @@ Page({
}
},
// 获取微信手机号(需要button组件配合
async getPhoneNumber(e) {
// 一键获取微信手机号button组件回调
async onGetPhoneNumber(e) {
console.log('[Settings] 获取手机号回调:', e.detail)
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
wx.showToast({ title: '授权失败', icon: 'none' })
return
@@ -217,30 +219,44 @@ Page({
// 需要将code发送到服务器解密获取手机号
const code = e.detail.code
if (!code) {
wx.showToast({ title: '获取失败,请手动输入', icon: 'none' })
// 如果没有code弹出手动输入
this.bindPhone()
return
}
wx.showLoading({ title: '获取中...', mask: true })
// 调用服务器解密手机号
const res = await app.request('/api/wechat/phone', {
const res = await app.request('/api/miniprogram/phone', {
method: 'POST',
data: { code }
})
wx.hideLoading()
if (res.success && res.phoneNumber) {
wx.setStorageSync('user_phone', res.phoneNumber)
this.setData({ phoneNumber: res.phoneNumber })
// 更新用户信息
if (app.globalData.userInfo) {
app.globalData.userInfo.phone = res.phoneNumber
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
// 同步到服务器
this.syncProfileToServer()
wx.showToast({ title: '手机号绑定成功', icon: 'success' })
} else {
wx.showToast({ title: '获取失败,手动输入', icon: 'none' })
// 获取失败,弹出手动输入
this.bindPhone()
}
} catch (e) {
wx.hideLoading()
console.log('[Settings] 获取手机号失败:', e)
wx.showToast({ title: '获取失败,手动输入', icon: 'none' })
// 获取失败,弹出手动输入
this.bindPhone()
}
},

View File

@@ -21,7 +21,7 @@
</view>
<view class="bind-list">
<!-- 手机号 -->
<!-- 手机号 - 使用微信一键获取 -->
<view class="bind-item">
<view class="bind-left">
<view class="bind-icon phone-icon">📱</view>
@@ -32,12 +32,14 @@
</view>
<view class="bind-right">
<text class="bind-check" wx:if="{{phoneNumber}}">✓</text>
<text class="bind-btn" wx:else bindtap="bindPhone">去绑定</text>
<button wx:else class="get-phone-btn" open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
一键获取
</button>
</view>
</view>
<!-- 微信号 -->
<view class="bind-item">
<view class="bind-item" bindtap="bindWechat">
<view class="bind-left">
<view class="bind-icon wechat-icon">💬</view>
<view class="bind-info">
@@ -47,12 +49,12 @@
</view>
<view class="bind-right">
<text class="bind-check" wx:if="{{wechatId}}">✓</text>
<text class="bind-btn" wx:else bindtap="bindWechat">去绑定</text>
<text class="bind-btn" wx:else>去绑定</text>
</view>
</view>
<!-- 支付宝 -->
<view class="bind-item">
<view class="bind-item" bindtap="bindAlipay">
<view class="bind-left">
<view class="bind-icon alipay-icon">💳</view>
<view class="bind-info">
@@ -62,7 +64,7 @@
</view>
<view class="bind-right">
<text class="bind-check" wx:if="{{alipayAccount}}">✓</text>
<text class="bind-btn" wx:else bindtap="bindAlipay">去绑定</text>
<text class="bind-btn" wx:else>去绑定</text>
</view>
</view>
</view>

View File

@@ -32,6 +32,18 @@
.bind-check { color: #00CED1; font-size: 32rpx; }
.bind-btn { color: #00CED1; font-size: 26rpx; }
/* 一键获取手机号按钮 */
.get-phone-btn {
padding: 12rpx 24rpx;
background: rgba(0,206,209,0.2);
border: 2rpx solid rgba(0,206,209,0.3);
border-radius: 16rpx;
font-size: 24rpx;
color: #00CED1;
line-height: normal;
}
.get-phone-btn::after { border: none; }
/* 提现提示 */
.tip-banner { background: rgba(255,165,0,0.1); border: 2rpx solid rgba(255,165,0,0.3); border-radius: 20rpx; padding: 20rpx 24rpx; margin-bottom: 24rpx; }
.tip-text { font-size: 24rpx; color: #FFA500; line-height: 1.5; }