hookehuyr

feat(用户资料): 优化头像上传和预览功能

- 添加头像上传至服务器的功能
- 重构头像预览组件为通用图片预览
- 使用 NutConfigProvider 统一性别选择样式
- 将 @click 事件改为 @tap 以兼容移动端
......@@ -11,6 +11,7 @@ declare module 'vue' {
NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutCol: typeof import('@nutui/nutui-taro')['Col']
NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider']
NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker']
NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
NutForm: typeof import('@nutui/nutui-taro')['Form']
......
......@@ -7,7 +7,7 @@
:src="formData.avatar || defaultAvatar"
class="avatar-image"
mode="aspectFill"
@click="previewAvatar"
@tap="previewAvatar(formData.avatar || defaultAvatar)"
/>
</view>
<view class="change-avatar-btn" @click="changeAvatar">
......@@ -36,12 +36,14 @@
</nut-form-item>
<!-- 性别 -->
<nut-form-item label="性别" prop="gender" body-align="right">
<nut-radio-group v-model="formData.gender" direction="horizontal">
<nut-radio label="男">男</nut-radio>
<nut-radio label="女">女</nut-radio>
</nut-radio-group>
</nut-form-item>
<nut-config-provider :theme-vars="themeVars">
<nut-form-item label="性别" prop="gender" body-align="right">
<nut-radio-group v-model="formData.gender" direction="horizontal">
<nut-radio label="男">男</nut-radio>
<nut-radio label="女">女</nut-radio>
</nut-radio-group>
</nut-form-item>
</nut-config-provider>
<!-- 生日 -->
<nut-form-item label="生日" prop="birthday">
......@@ -156,12 +158,8 @@
/>
</nut-popup>
<!-- 头像预览 -->
<nut-image-preview
v-model:show="avatarPreviewVisible"
:images="[formData.avatar || defaultAvatar]"
@close="closePreview"
/>
<!-- 图片预览组件 -->
<nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" @close="closePreview" />
<!-- 成功提示 -->
<nut-toast
......@@ -177,6 +175,12 @@ import { ref, reactive, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import './index.less'
import { Right } from '@nutui/icons-vue-taro'
import BASE_URL from '@/utils/config';
// 主题配置
const themeVars = {
radioLabelFontActiveColor: '#f97316'
}
// 默认头像
const defaultAvatar = 'https://randomuser.me/api/portraits/men/32.jpg'
......@@ -196,11 +200,15 @@ const formData = reactive({
const phoneDialogVisible = ref(false)
const datePickerVisible = ref(false)
const schoolPickerVisible = ref(false)
const avatarPreviewVisible = ref(false)
const toastVisible = ref(false)
// 图片预览相关
const previewVisible = ref(false)
const previewImages = ref([])
const previewIndex = ref(0)
const closePreview = () => {
avatarPreviewVisible.value = false
previewVisible.value = false
}
// 手机号相关
......@@ -234,18 +242,79 @@ const goBack = () => {
// 更换头像
const changeAvatar = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.avatar = res.tempFilePaths[0]
}
})
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
const tempFilePath = res.tempFilePaths[0]
uploadImage(tempFilePath)
},
fail: function () {
Taro.showToast({
title: '选择图片失败',
icon: 'none'
})
}
})
}
// 预览头像
const previewAvatar = () => {
avatarPreviewVisible.value = true
/**
* 上传图片到服务器
* @param {String} filePath - 图片文件路径
*/
const uploadImage = (filePath) => {
// 显示上传中提示
Taro.showLoading({
title: '上传中',
mask: true
})
// 获取上传URL
wx.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath,
name: 'file',
header: {
'content-type': 'multipart/form-data',
},
success: function (res) {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
formData.avatar = upload_data.data.src;
Taro.showToast({
title: '上传成功',
icon: 'success'
})
} else {
Taro.showToast({
icon: 'error',
title: '服务器错误,稍后重试!',
mask: true
})
}
},
});
}
});
}
/**
* 预览头像
* @param {string} imageUrl - 图片URL
*/
const previewAvatar = (imageUrl) => {
if (!imageUrl) {
Taro.showToast({
title: '暂无图片可预览',
icon: 'none'
})
return
}
previewImages.value = [{ src: imageUrl }]
previewIndex.value = 0
previewVisible.value = true
}
// 显示手机号弹框
......
......@@ -7,7 +7,7 @@
:src="formData.avatar || defaultAvatar"
class="avatar-image"
mode="aspectFill"
@click="previewAvatar"
@tap="previewAvatar(formData.avatar || defaultAvatar)"
/>
</view>
<view class="change-avatar-btn" @click="changeAvatar">
......@@ -64,12 +64,14 @@
</nut-form-item>
<!-- 性别 -->
<nut-form-item label="性别" prop="gender" body-align="right" required :rules="[{ required: true, message: '请选择性别' }]">
<nut-radio-group v-model="formData.gender" direction="horizontal">
<nut-radio label="男">男</nut-radio>
<nut-radio label="女">女</nut-radio>
</nut-radio-group>
</nut-form-item>
<nut-config-provider :theme-vars="themeVars">
<nut-form-item label="性别" prop="gender" body-align="right" required :rules="[{ required: true, message: '请选择性别' }]">
<nut-radio-group v-model="formData.gender" direction="horizontal">
<nut-radio label="男">男</nut-radio>
<nut-radio label="女">女</nut-radio>
</nut-radio-group>
</nut-form-item>
</nut-config-provider>
<!-- 生日 -->
<nut-form-item label="生日" prop="birthday" label-position="top">
......@@ -124,11 +126,7 @@
</nut-popup>
<!-- 头像预览 -->
<nut-image-preview
v-model:show="avatarPreviewVisible"
:images="[formData.avatar || defaultAvatar]"
@close="closePreview"
/>
<nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" @close="closePreview" />
<!-- 成功提示 -->
<nut-toast
......@@ -142,13 +140,13 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import { RectLeft, Right } from '@nutui/icons-vue-taro'
import { Right } from '@nutui/icons-vue-taro'
import './index.less'
import BASE_URL from '@/utils/config';
// 主题配置
const themeVars = {
navbarBackground: '#f97316',
navbarColor: '#fff'
radioLabelFontActiveColor: '#f97316'
}
// 默认头像
......@@ -168,13 +166,17 @@ const formData = reactive({
// 弹框控制
const datePickerVisible = ref(false)
const schoolPickerVisible = ref(false)
const avatarPreviewVisible = ref(false)
const toastVisible = ref(false)
const toastMessage = ref('')
const toastType = ref('success')
// 图片预览相关
const previewVisible = ref(false)
const previewImages = ref([])
const previewIndex = ref(0)
const closePreview = () => {
avatarPreviewVisible.value = false
previewVisible.value = false
}
// 验证码相关
......@@ -214,31 +216,82 @@ const isPhoneValid = computed(() => {
})
/**
* 返回上一页
* 更换头像
*/
const goBack = () => {
Taro.navigateBack()
const changeAvatar = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: function (res) {
const tempFilePath = res.tempFilePaths[0]
uploadImage(tempFilePath)
},
fail: function () {
Taro.showToast({
title: '选择图片失败',
icon: 'none'
})
}
})
}
/**
* 更换头像
* 上传图片到服务器
* @param {String} filePath - 图片文件路径
*/
const changeAvatar = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
formData.avatar = res.tempFilePaths[0]
}
})
const uploadImage = (filePath) => {
// 显示上传中提示
Taro.showLoading({
title: '上传中',
mask: true
})
// 获取上传URL
wx.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath,
name: 'file',
header: {
'content-type': 'multipart/form-data',
},
success: function (res) {
let upload_data = JSON.parse(res.data);
Taro.hideLoading({
success: () => {
if (res.statusCode === 200) {
formData.avatar = upload_data.data.src;
Taro.showToast({
title: '上传成功',
icon: 'success'
})
} else {
Taro.showToast({
icon: 'error',
title: '服务器错误,稍后重试!',
mask: true
})
}
},
});
}
});
}
/**
* 预览头像
*/
const previewAvatar = () => {
avatarPreviewVisible.value = true
const previewAvatar = (imageUrl) => {
if (!imageUrl) {
Taro.showToast({
title: '暂无图片可预览',
icon: 'none'
})
return
}
previewImages.value = [{ src: imageUrl }]
previewIndex.value = 0
previewVisible.value = true
}
/**
......