Update evolution indices and enhance user experience in mini program

- Added new entries for content ranking algorithm adjustments and cross-platform reuse in the evolution indices for backend, team, and mini program development.
- Improved user interface elements in the mini program, including the addition of a hidden settings entry and refined guidance for profile modifications.
- Enhanced reading statistics display with formatted numbers for better clarity and user engagement.
- Updated reading tracker logic to prevent duplicate duration accumulation and ensure accurate reporting of reading progress.
This commit is contained in:
Alex-larget
2026-03-14 17:13:06 +08:00
parent c936371165
commit 1edceda4db
33 changed files with 773 additions and 75 deletions

View File

@@ -0,0 +1,155 @@
/**
* Soul创业派对 - 头像昵称引导页
* 登录后资料未完善时引导用户修改默认头像和昵称,仅包含头像+昵称两项
*/
const app = getApp()
Page({
data: {
statusBarHeight: 44,
avatar: '',
nickname: '',
saving: false,
showAvatarModal: false,
},
onLoad() {
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 44 })
this.loadFromUser()
},
loadFromUser() {
const user = app.globalData.userInfo
if (!app.globalData.isLoggedIn || !user?.id) {
wx.showToast({ title: '请先登录', icon: 'none' })
setTimeout(() => getApp().goBackOrToHome(), 1500)
return
}
const nickname = (user.nickname || user.nickName || '').trim()
const avatar = user.avatar || user.avatarUrl || ''
this.setData({ nickname, avatar })
},
goBack() {
getApp().goBackOrToHome()
},
onNicknameInput(e) {
this.setData({ nickname: e.detail.value })
},
onNicknameChange(e) {
this.setData({ nickname: e.detail.value })
},
onAvatarTap() {
wx.showActionSheet({
itemList: ['使用微信头像', '从相册选择'],
success: (res) => {
if (res.tapIndex === 0) {
this.setData({ showAvatarModal: true })
} else if (res.tapIndex === 1) {
this.chooseAvatarFromAlbum()
}
},
})
},
closeAvatarModal() {
this.setData({ showAvatarModal: false })
},
chooseAvatarFromAlbum() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempPath = res.tempFiles[0].tempFilePath
await this.uploadAndSaveAvatar(tempPath)
},
})
},
async onChooseAvatar(e) {
const tempAvatarUrl = e.detail?.avatarUrl
this.setData({ showAvatarModal: false })
if (!tempAvatarUrl) return
await this.uploadAndSaveAvatar(tempAvatarUrl)
},
async uploadAndSaveAvatar(tempPath) {
wx.showLoading({ title: '上传中...', mask: true })
try {
const uploadRes = await new Promise((resolve, reject) => {
wx.uploadFile({
url: app.globalData.baseUrl + '/api/miniprogram/upload',
filePath: tempPath,
name: 'file',
formData: { folder: 'avatars' },
success: (r) => {
try {
const data = JSON.parse(r.data)
if (data.success) resolve(data)
else reject(new Error(data.error || '上传失败'))
} catch {
reject(new Error('解析失败'))
}
},
fail: reject,
})
})
const avatarUrl = app.globalData.baseUrl + uploadRes.data.url
this.setData({ avatar: avatarUrl })
await app.request({
url: '/api/miniprogram/user/profile',
method: 'POST',
data: { userId: app.globalData.userInfo?.id, avatar: avatarUrl },
})
if (app.globalData.userInfo) {
app.globalData.userInfo.avatar = avatarUrl
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
wx.hideLoading()
wx.showToast({ title: '头像已更新', icon: 'success' })
} catch (e) {
wx.hideLoading()
wx.showToast({ title: e.message || '上传失败', icon: 'none' })
}
},
async saveProfile() {
const userId = app.globalData.userInfo?.id
if (!userId) {
wx.showToast({ title: '请先登录', icon: 'none' })
return
}
const nickname = (this.data.nickname || '').trim()
const avatar = (this.data.avatar || '').trim()
if (!nickname) {
wx.showToast({ title: '请输入昵称', icon: 'none' })
return
}
this.setData({ saving: true })
try {
await app.request({
url: '/api/miniprogram/user/profile',
method: 'POST',
data: { userId, avatar: avatar || undefined, nickname },
})
if (app.globalData.userInfo) {
if (nickname) app.globalData.userInfo.nickname = nickname
if (avatar) app.globalData.userInfo.avatar = avatar
wx.setStorageSync('userInfo', app.globalData.userInfo)
}
wx.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => getApp().goBackOrToHome(), 800)
} catch (e) {
wx.showToast({ title: e.message || '保存失败', icon: 'none' })
}
this.setData({ saving: false })
},
goToFullProfile() {
wx.navigateTo({ url: '/pages/profile-edit/profile-edit' })
},
})

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "完善资料",
"usingComponents": {}
}

View File

@@ -0,0 +1,67 @@
<!--Soul创业派对 - 头像昵称引导页,仅头像+昵称-->
<view class="page">
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px;">
<view class="nav-back" bindtap="goBack"><text class="back-icon"></text></view>
<text class="nav-title">完善资料</text>
<view class="nav-placeholder"></view>
</view>
<view style="height: {{statusBarHeight + 44}}px;"></view>
<view class="content">
<!-- 引导文案 -->
<view class="guide-card">
<text class="guide-icon">👋</text>
<text class="guide-title">完善头像和昵称</text>
<text class="guide-desc">让他人更好地认识你,展示更专业的形象</text>
</view>
<!-- 头像 -->
<view class="avatar-section">
<view class="avatar-wrap" bindtap="onAvatarTap">
<view class="avatar-inner">
<image wx:if="{{avatar}}" class="avatar-img" src="{{avatar}}" mode="aspectFill"/>
<view wx:else class="avatar-placeholder">{{nickname ? nickname[0] : '?'}}</view>
</view>
<view class="avatar-camera">📷</view>
</view>
<text class="avatar-change">点击更换头像</text>
</view>
<!-- 昵称 -->
<view class="form-section">
<text class="form-label">昵称</text>
<view class="form-input-wrap">
<input
class="form-input-inner"
type="nickname"
placeholder="请输入昵称"
value="{{nickname}}"
bindinput="onNicknameInput"
bindchange="onNicknameChange"
maxlength="20"
/>
</view>
<text class="input-tip">微信用户可点击输入框自动填充昵称,或手动输入</text>
</view>
<view class="save-btn" bindtap="saveProfile" disabled="{{saving}}">
{{saving ? '保存中...' : '完成'}}
</view>
<view class="link-row" bindtap="goToFullProfile">
<text class="link-text">完善更多资料</text>
<text class="link-arrow">→</text>
</view>
</view>
<!-- 头像弹窗:使用微信头像 -->
<view class="modal-overlay" wx:if="{{showAvatarModal}}" bindtap="closeAvatarModal">
<view class="modal-content avatar-modal" catchtap="stopPropagation">
<view class="modal-close" bindtap="closeAvatarModal">✕</view>
<text class="avatar-modal-title">使用微信头像</text>
<text class="avatar-modal-desc">点击下方按钮,一键同步当前微信头像</text>
<button class="btn-choose-avatar" open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">使用微信头像</button>
<view class="avatar-modal-cancel" bindtap="closeAvatarModal">取消</view>
</view>
</view>
</view>

View File

@@ -0,0 +1,271 @@
/* Soul创业派对 - 头像昵称引导页 */
.page {
background: #050B14;
min-height: 100vh;
color: #fff;
width: 100%;
box-sizing: border-box;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 24rpx;
background: rgba(5, 11, 20, 0.9);
backdrop-filter: blur(8rpx);
border-bottom: 1rpx solid rgba(255, 255, 255, 0.08);
}
.nav-back {
width: 60rpx;
padding: 16rpx 0;
}
.back-icon {
font-size: 44rpx;
color: #5EEAD4;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
}
.nav-placeholder {
width: 60rpx;
}
.content {
padding: 32rpx 24rpx;
box-sizing: border-box;
}
.guide-card {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 48rpx 32rpx;
background: rgba(94, 234, 212, 0.08);
border: 1rpx solid rgba(94, 234, 212, 0.25);
border-radius: 24rpx;
margin-bottom: 48rpx;
}
.guide-icon {
font-size: 64rpx;
margin-bottom: 16rpx;
}
.guide-title {
font-size: 36rpx;
font-weight: 700;
color: #5EEAD4;
margin-bottom: 12rpx;
}
.guide-desc {
font-size: 26rpx;
color: rgba(148, 163, 184, 0.95);
line-height: 1.5;
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 48rpx;
}
.avatar-wrap {
position: relative;
width: 192rpx;
height: 192rpx;
border-radius: 50%;
border: 4rpx solid #5EEAD4;
box-shadow: 0 0 30rpx rgba(94, 234, 212, 0.3);
}
.avatar-inner {
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
}
.avatar-img {
width: 100%;
height: 100%;
display: block;
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 72rpx;
font-weight: bold;
color: #5EEAD4;
background: rgba(94, 234, 212, 0.2);
}
.avatar-camera {
position: absolute;
bottom: -8rpx;
right: -8rpx;
width: 56rpx;
height: 56rpx;
background: #5EEAD4;
color: #000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
border: 4rpx solid #050B14;
box-sizing: border-box;
}
.avatar-change {
font-size: 28rpx;
color: #5EEAD4;
font-weight: 500;
margin-top: 16rpx;
}
.form-section {
margin-bottom: 32rpx;
}
.form-label {
display: block;
font-size: 24rpx;
color: #94A3B8;
margin-bottom: 12rpx;
margin-left: 8rpx;
}
.form-input-wrap {
padding: 24rpx 32rpx;
background: #17212F;
border: 1rpx solid rgba(255, 255, 255, 0.08);
border-radius: 24rpx;
box-sizing: border-box;
}
.form-input-inner {
width: 100%;
font-size: 28rpx;
color: #fff;
background: transparent;
box-sizing: border-box;
display: block;
}
.input-tip {
margin-top: 8rpx;
font-size: 22rpx;
color: #94A3B8;
margin-left: 8rpx;
display: block;
}
.save-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
text-align: center;
background: #5EEAD4;
color: #050B14;
font-size: 36rpx;
font-weight: bold;
border-radius: 24rpx;
margin-top: 24rpx;
margin-bottom: 32rpx;
}
.save-btn[disabled] {
opacity: 0.6;
}
.link-row {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 24rpx;
}
.link-text {
font-size: 28rpx;
color: #94A3B8;
}
.link-arrow {
font-size: 28rpx;
color: #5EEAD4;
}
/* 头像弹窗 */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(16rpx);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 48rpx;
box-sizing: border-box;
}
.modal-content {
width: 100%;
max-width: 640rpx;
background: #0b1220;
border-radius: 32rpx;
padding: 48rpx;
position: relative;
box-sizing: border-box;
}
.modal-close {
position: absolute;
top: 24rpx;
right: 24rpx;
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: rgba(255, 255, 255, 0.7);
}
.avatar-modal-title {
display: block;
font-size: 36rpx;
font-weight: 700;
text-align: center;
margin-bottom: 12rpx;
}
.avatar-modal-desc {
display: block;
font-size: 26rpx;
color: #94A3B8;
text-align: center;
margin-bottom: 32rpx;
}
.btn-choose-avatar {
width: 100%;
height: 88rpx;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
background: #5EEAD4;
color: #050B14;
font-size: 30rpx;
font-weight: 600;
border-radius: 44rpx;
border: none;
}
.btn-choose-avatar::after {
border: none;
}
.avatar-modal-cancel {
margin-top: 24rpx;
text-align: center;
font-size: 28rpx;
color: #9CA3AF;
}