346 lines
6.9 KiB
Vue
346 lines
6.9 KiB
Vue
<template>
|
|
<view class="edit-page">
|
|
<!-- 头像区 -->
|
|
<view class="avatar-section">
|
|
<text class="section-label">头像</text>
|
|
<view class="avatar-row">
|
|
<button
|
|
class="avatar-btn"
|
|
:class="{ uploading }"
|
|
open-type="chooseAvatar"
|
|
:disabled="uploading"
|
|
@chooseavatar="onChooseAvatar"
|
|
>
|
|
<image
|
|
class="avatar-img"
|
|
:src="displayAvatarUrl || '/static/logo.jpg'"
|
|
mode="aspectFill"
|
|
/>
|
|
<view class="avatar-edit-icon">
|
|
<TnIcon name="edit-write-fill" :size="32" color="#FFFFFF" />
|
|
</view>
|
|
<view v-if="uploading" class="avatar-uploading-mask">
|
|
<TnIcon name="loading" :size="48" color="#FFFFFF" />
|
|
</view>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 表单区 -->
|
|
<view class="form-section">
|
|
<view class="form-item">
|
|
<text class="form-label">昵称</text>
|
|
<input
|
|
class="form-input"
|
|
type="nickname"
|
|
v-model="form.nickName"
|
|
placeholder="请输入昵称"
|
|
maxlength="20"
|
|
@blur="onNicknameBlur"
|
|
/>
|
|
</view>
|
|
|
|
<view class="form-item">
|
|
<text class="form-label">手机号</text>
|
|
<input
|
|
class="form-input"
|
|
type="number"
|
|
v-model="form.phone"
|
|
placeholder="选填,用于重要通知"
|
|
maxlength="11"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 保存按钮 -->
|
|
<view class="btn-section">
|
|
<button
|
|
class="save-btn"
|
|
:disabled="saving"
|
|
:loading="saving"
|
|
@click="handleSave"
|
|
>
|
|
保存
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, ref, onMounted } from 'vue'
|
|
|
|
interface ProfileForm {
|
|
avatarUrl: string
|
|
nickName: string
|
|
phone: string
|
|
}
|
|
|
|
const saving = ref(false)
|
|
const uploading = ref(false)
|
|
const displayAvatarUrl = ref('')
|
|
|
|
const form = reactive<ProfileForm>({
|
|
avatarUrl: '',
|
|
nickName: '',
|
|
phone: '',
|
|
})
|
|
|
|
// 从本地缓存加载用户信息,如果是 cloud:// 则需要转换为可访问 URL
|
|
const cached = uni.getStorageSync('userInfo')
|
|
if (cached) {
|
|
form.avatarUrl = cached.avatarUrl || ''
|
|
form.nickName = cached.nickName || ''
|
|
form.phone = cached.phone || ''
|
|
}
|
|
|
|
onMounted(() => {
|
|
resolveAvatarUrl()
|
|
})
|
|
|
|
async function resolveAvatarUrl() {
|
|
const url = form.avatarUrl
|
|
if (!url) return
|
|
if (url.startsWith('cloud://')) {
|
|
try {
|
|
const res = await uniCloud.getTempFileURL({ fileList: [url] })
|
|
if (res.fileList && res.fileList[0]?.tempFileURL) {
|
|
displayAvatarUrl.value = res.fileList[0].tempFileURL
|
|
}
|
|
} catch (e) {
|
|
console.warn('获取头像临时URL失败', e)
|
|
}
|
|
} else {
|
|
displayAvatarUrl.value = url
|
|
}
|
|
}
|
|
|
|
// 微信小程序选择头像 → 上传到 uniCloud
|
|
async function onChooseAvatar(e: any) {
|
|
const tempPath = e.detail.avatarUrl
|
|
if (!tempPath) return
|
|
|
|
uploading.value = true
|
|
uni.showLoading({ title: '上传头像...', mask: true })
|
|
try {
|
|
const uploadRes = await uniCloud.uploadFile({
|
|
filePath: tempPath,
|
|
cloudPath: `avatars/${Date.now()}_${Math.random().toString(36).slice(2, 8)}.png`,
|
|
})
|
|
form.avatarUrl = uploadRes.fileID
|
|
|
|
// 转换为临时 HTTP URL 用于显示
|
|
const urlRes = await uniCloud.getTempFileURL({ fileList: [uploadRes.fileID] })
|
|
if (urlRes.fileList && urlRes.fileList[0]?.tempFileURL) {
|
|
displayAvatarUrl.value = urlRes.fileList[0].tempFileURL
|
|
}
|
|
} catch (err: any) {
|
|
console.error('头像上传失败:', err)
|
|
uni.showToast({ title: '头像上传失败', icon: 'none' })
|
|
} finally {
|
|
uni.hideLoading()
|
|
uploading.value = false
|
|
}
|
|
}
|
|
|
|
// 微信小程序昵称输入完成
|
|
function onNicknameBlur(e: any) {
|
|
if (e.detail?.value) {
|
|
form.nickName = e.detail.value
|
|
}
|
|
}
|
|
|
|
async function handleSave() {
|
|
saving.value = true
|
|
uni.showLoading({ title: '保存中...', mask: true })
|
|
try {
|
|
const uid = uni.getStorageSync('uid')
|
|
const res = await uniCloud.callFunction({
|
|
name: 'userManager',
|
|
data: {
|
|
action: 'updateProfile',
|
|
uid,
|
|
nickName: form.nickName,
|
|
avatarUrl: form.avatarUrl,
|
|
phone: form.phone,
|
|
},
|
|
})
|
|
|
|
if (res.result?.code !== 0) {
|
|
throw new Error(res.result?.message || '保存失败')
|
|
}
|
|
|
|
// 更新本地缓存
|
|
const info = uni.getStorageSync('userInfo') || {}
|
|
info.nickName = form.nickName
|
|
info.avatarUrl = form.avatarUrl
|
|
info.phone = form.phone
|
|
uni.setStorageSync('userInfo', info)
|
|
|
|
uni.hideLoading()
|
|
uni.showToast({ title: '保存成功', icon: 'success' })
|
|
setTimeout(() => {
|
|
uni.navigateBack()
|
|
}, 800)
|
|
} catch (err: any) {
|
|
console.error('保存用户信息失败:', err)
|
|
uni.hideLoading()
|
|
uni.showToast({ title: err.message || '保存失败', icon: 'none' })
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
$primary: #F97316;
|
|
$primary-bg: #FFF7ED;
|
|
$text-dark: #431407;
|
|
$text-muted: #9CA3AF;
|
|
$card-bg: #FFFFFF;
|
|
$border-color: #FED7AA;
|
|
$radius-lg: 28rpx;
|
|
$shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.08);
|
|
|
|
.edit-page {
|
|
min-height: 100vh;
|
|
background-color: $primary-bg;
|
|
padding: 24rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
// === 头像 ===
|
|
.avatar-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 48rpx 0 32rpx;
|
|
}
|
|
|
|
.section-label {
|
|
font-size: 26rpx;
|
|
font-weight: 600;
|
|
color: $text-muted;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.avatar-row {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-btn {
|
|
position: relative;
|
|
width: 180rpx;
|
|
height: 180rpx;
|
|
padding: 0;
|
|
margin: 0;
|
|
border-radius: 50%;
|
|
border: 6rpx solid $border-color;
|
|
background: $card-bg;
|
|
overflow: hidden;
|
|
box-shadow: $shadow;
|
|
|
|
&::after {
|
|
border: none;
|
|
}
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.avatar-edit-icon {
|
|
position: absolute;
|
|
bottom: 6rpx;
|
|
right: 6rpx;
|
|
width: 52rpx;
|
|
height: 52rpx;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, #FB923C, #F97316);
|
|
border: 3rpx solid #FFFFFF;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4rpx 8rpx rgba(124, 45, 18, 0.2);
|
|
}
|
|
|
|
.avatar-uploading-mask {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 50%;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-btn.uploading::after {
|
|
border: none;
|
|
}
|
|
|
|
// === 表单 ===
|
|
.form-section {
|
|
background: $card-bg;
|
|
border-radius: $radius-lg;
|
|
border: 2rpx solid $border-color;
|
|
box-shadow: $shadow;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.form-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 28rpx 28rpx;
|
|
border-bottom: 2rpx solid #FEF3C7;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
|
|
.form-label {
|
|
font-size: 28rpx;
|
|
font-weight: 600;
|
|
color: $text-dark;
|
|
width: 120rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.form-input {
|
|
flex: 1;
|
|
font-size: 28rpx;
|
|
color: $text-dark;
|
|
text-align: right;
|
|
|
|
&::placeholder {
|
|
color: #D1D5DB;
|
|
}
|
|
}
|
|
|
|
// === 保存按钮 ===
|
|
.btn-section {
|
|
padding: 48rpx 24rpx;
|
|
}
|
|
|
|
.save-btn {
|
|
width: 100%;
|
|
height: 96rpx;
|
|
line-height: 96rpx;
|
|
background: linear-gradient(135deg, #F97316 0%, #FB923C 100%) !important;
|
|
border-radius: 48rpx;
|
|
font-size: 32rpx;
|
|
font-weight: 600;
|
|
color: #FFFFFF;
|
|
box-shadow: 0 12rpx 28rpx rgba(249, 115, 22, 0.3);
|
|
|
|
&::after {
|
|
border: none;
|
|
}
|
|
|
|
&[disabled] {
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
</style>
|