shop-wx/src/pages/profile/index.vue

429 lines
10 KiB
Vue
Raw Normal View History

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { useWxStore } from '@/pinia/stores/wx'
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
import { storeToRefs } from 'pinia'
import { getWxUserByOpenid, updateUserByOpenid } from '@/api/users'
import { useToast } from 'wot-design-uni'
import { getEnvBaseUploadUrl, toHttpsUrl } from '@/utils'
definePage({
style: {
navigationBarTitleText: '设置头像昵称',
},
})
const wxStore = useWxStore()
const ab98UserStore = useAb98UserStore()
const toast = useToast()
const { wxUserDTO, openid } = storeToRefs(wxStore)
const { name: userName, face_img } = storeToRefs(ab98UserStore)
// 头像相关
const avatarUrl = ref('')
const tempAvatarPath = ref('') // 临时头像路径,用于预览
// 昵称相关
const nickName = ref('')
// 初始化数据
onMounted(() => {
// 优先使用微信用户信息,其次使用企业用户信息
if (wxUserDTO.value) {
avatarUrl.value = wxUserDTO.value.avatar || ''
nickName.value = wxUserDTO.value.nickName || ''
}
// 如果没有微信头像,使用企业用户头像
if (!avatarUrl.value && face_img.value) {
avatarUrl.value = toHttpsUrl(face_img.value)
}
// 如果没有微信昵称,使用企业用户名
if (!nickName.value && userName.value) {
nickName.value = userName.value
}
// 默认头像
if (!avatarUrl.value) {
avatarUrl.value = '/static/favicon.ico'
}
})
// 图片上传相关
const uploadUrl = `${getEnvBaseUploadUrl()}/file/upload`
const uploading = ref(false)
// 图片压缩处理 - 参考审批提交页面的实现
const compressImage = (src: string): Promise<string> => {
return new Promise((resolve, reject) => {
// 如果是网络图片,需要先下载到本地再压缩
if (src && (src.startsWith('http://') || src.startsWith('https://'))) {
// 先下载图片
uni.downloadFile({
url: src,
success: (downloadResult) => {
console.log('网络图片下载完成:', downloadResult.tempFilePath)
// 下载成功后压缩图片
compressLocalImage(downloadResult.tempFilePath)
},
fail: (err) => {
console.warn('网络图片下载失败:', err)
reject(new Error('网络图片下载失败'))
}
})
} else {
// 本地图片直接压缩
compressLocalImage(src)
}
// 压缩本地图片的函数
function compressLocalImage(localSrc: string) {
uni.compressImage({
src: localSrc,
quality: 0.8,
maxWidth: 980,
maxHeight: 980,
success: (res) => {
console.log('头像图片压缩成功:', res)
resolve(res.tempFilePath)
},
fail: (err) => {
console.warn('头像图片压缩失败,使用原图:', err)
// 压缩失败时使用原图
resolve(localSrc)
}
})
}
})
}
// 上传头像图片
const uploadAvatar = async (filePath: string): Promise<string> => {
uploading.value = true
try {
// 先压缩图片
const compressedPath = await compressImage(filePath)
console.log('头像压缩完成:', compressedPath)
// 上传到服务器
const uploadResult = await new Promise<any>((resolve, reject) => {
uni.uploadFile({
url: uploadUrl,
filePath: compressedPath,
name: 'file',
success: (res) => {
if (res.statusCode === 200) {
try {
const response = JSON.parse(res.data)
resolve(response)
} catch (e) {
reject(new Error('上传响应解析失败'))
}
} else {
reject(new Error(`上传失败,状态码: ${res.statusCode}`))
}
},
fail: (err) => {
reject(err)
}
})
})
// 解析上传响应
if (uploadResult.code === 0 && uploadResult.data && uploadResult.data.url) {
const avatarUrl = toHttpsUrl(uploadResult.data.url)
console.log('头像上传成功:', avatarUrl)
return avatarUrl
} else {
throw new Error(uploadResult.msg || '上传失败')
}
} catch (error) {
console.error('头像上传失败:', error)
throw error
} finally {
uploading.value = false
}
}
// 选择头像并自动压缩上传
const onChooseAvatar = async (e: any) => {
const { avatarUrl: tempUrl } = e.detail
tempAvatarPath.value = tempUrl
avatarUrl.value = tempUrl
// 显示上传提示
uni.showLoading({
title: '正在压缩上传头像...',
mask: true
})
try {
// 上传头像到服务器
const uploadedUrl = await uploadAvatar(tempUrl)
// 使用上传成功后的URL更新显示
avatarUrl.value = uploadedUrl
tempAvatarPath.value = uploadedUrl
uni.showToast({
title: '头像上传成功',
icon: 'success',
duration: 2000
})
} catch (error) {
console.error('头像上传失败:', error)
uni.showToast({
title: '头像上传失败,请重试',
icon: 'error',
duration: 2000
})
// 上传失败时,保持本地图片预览,但提交时会使用本地路径
} finally {
uni.hideLoading()
}
}
// 提交更新
const loading = ref(false)
const handleSubmit = async () => {
if (!openid.value) {
toast.show('用户信息获取失败,请重新登录')
return
}
if (!nickName.value.trim()) {
toast.show('请输入昵称')
return
}
// 获取要使用的头像路径
const finalAvatarPath = tempAvatarPath.value || avatarUrl.value
loading.value = true
try {
// 使用网络URL或静态资源URL更新用户信息
const result = await updateUserByOpenid(
openid.value,
nickName.value.trim(),
finalAvatarPath
)
if (result?.code === 0) {
// 更新store中的用户信息
const wxUser = await getWxUserByOpenid(openid.value);
console.log('wxUser:', wxUser);
await wxStore.processUserInfo(wxUser.data, wxStore.corpid);
toast.show('更新成功')
// 返回上一页
uni.navigateBack()
} else {
toast.show(result?.msg || '更新失败')
}
} catch (error) {
console.error('更新用户信息失败:', error)
toast.show('更新失败,请稍后重试')
} finally {
loading.value = false
}
}
</script>
<template>
<view class="profile-setting-page">
<!-- 头像设置区域 -->
<view class="setting-card">
<view class="section-title">设置头像</view>
<view class="avatar-section">
<view class="current-avatar">
<image class="avatar" :src="avatarUrl" mode="aspectFill" />
</view>
<view class="avatar-hint">当前头像</view>
</view>
<!-- 微信小程序选择头像按钮 -->
<view class="choose-avatar-btn">
<button
class="avatar-button"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar"
>
选择头像
</button>
<view class="avatar-tip">点击按钮选择微信头像</view>
</view>
</view>
<!-- 昵称设置区域 -->
<view class="setting-card">
<view class="section-title">设置昵称</view>
<view class="nickname-section">
<input
class="nickname-input"
type="nickname"
v-model="nickName"
placeholder="请输入昵称"
:maxlength="20"
/>
<view class="nickname-tip">输入时键盘上方会展示微信昵称</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<wd-button
type="primary"
block
:loading="loading"
@click="handleSubmit"
>
{{ loading ? '更新中...' : '保存设置' }}
</wd-button>
</view>
</view>
</template>
<style scoped lang="scss">
.profile-setting-page {
background: #f7f8fa;
min-height: 100vh;
padding: 12px;
}
.setting-card {
background: white;
border-radius: 16px;
padding: 20px 16px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.05);
.section-title {
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 20px;
padding-left: 8px;
position: relative;
letter-spacing: 0.5px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
border-radius: 2px;
}
}
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 24px;
.current-avatar {
width: 120px;
height: 120px;
border-radius: 50%;
overflow: hidden;
border: 4px solid rgba(64, 158, 255, 0.2);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
margin-bottom: 12px;
.avatar {
width: 100%;
height: 100%;
}
}
.avatar-hint {
font-size: 14px;
color: #666;
}
}
.choose-avatar-btn {
display: flex;
flex-direction: column;
align-items: center;
.avatar-button {
width: 200px;
height: 44px;
line-height: 44px;
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
color: white;
border: none;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
margin-bottom: 12px;
&:active {
opacity: 0.9;
transform: translateY(1px);
}
}
.avatar-tip {
font-size: 12px;
color: #999;
text-align: center;
}
}
.nickname-section {
.nickname-input {
width: 100%;
height: 48px;
padding: 0 16px;
background: #f7f8fa;
border: 1px solid #e5e5e5;
border-radius: 24px;
font-size: 16px;
color: #1a1a1a;
margin-bottom: 12px;
&:focus {
border-color: #409EFF;
background: white;
}
&::placeholder {
color: #999;
}
}
.nickname-tip {
font-size: 12px;
color: #999;
padding-left: 8px;
}
}
.submit-section {
margin-top: 24px;
::v-deep .wd-button {
height: 48px;
border-radius: 24px;
font-size: 16px;
font-weight: 500;
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
border: none;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
&:active {
opacity: 0.9;
}
}
}
</style>