feat(用户资料): 优化头像上传和预览功能
- 添加头像上传至服务器的功能 - 重构头像预览组件为通用图片预览 - 使用 NutConfigProvider 统一性别选择样式 - 将 @click 事件改为 @tap 以兼容移动端
Showing
3 changed files
with
180 additions
and
57 deletions
| ... | @@ -11,6 +11,7 @@ declare module 'vue' { | ... | @@ -11,6 +11,7 @@ declare module 'vue' { |
| 11 | NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet'] | 11 | NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet'] |
| 12 | NutButton: typeof import('@nutui/nutui-taro')['Button'] | 12 | NutButton: typeof import('@nutui/nutui-taro')['Button'] |
| 13 | NutCol: typeof import('@nutui/nutui-taro')['Col'] | 13 | NutCol: typeof import('@nutui/nutui-taro')['Col'] |
| 14 | + NutConfigProvider: typeof import('@nutui/nutui-taro')['ConfigProvider'] | ||
| 14 | NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker'] | 15 | NutDatePicker: typeof import('@nutui/nutui-taro')['DatePicker'] |
| 15 | NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] | 16 | NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] |
| 16 | NutForm: typeof import('@nutui/nutui-taro')['Form'] | 17 | NutForm: typeof import('@nutui/nutui-taro')['Form'] | ... | ... |
| ... | @@ -7,7 +7,7 @@ | ... | @@ -7,7 +7,7 @@ |
| 7 | :src="formData.avatar || defaultAvatar" | 7 | :src="formData.avatar || defaultAvatar" |
| 8 | class="avatar-image" | 8 | class="avatar-image" |
| 9 | mode="aspectFill" | 9 | mode="aspectFill" |
| 10 | - @click="previewAvatar" | 10 | + @tap="previewAvatar(formData.avatar || defaultAvatar)" |
| 11 | /> | 11 | /> |
| 12 | </view> | 12 | </view> |
| 13 | <view class="change-avatar-btn" @click="changeAvatar"> | 13 | <view class="change-avatar-btn" @click="changeAvatar"> |
| ... | @@ -36,12 +36,14 @@ | ... | @@ -36,12 +36,14 @@ |
| 36 | </nut-form-item> | 36 | </nut-form-item> |
| 37 | 37 | ||
| 38 | <!-- 性别 --> | 38 | <!-- 性别 --> |
| 39 | - <nut-form-item label="性别" prop="gender" body-align="right"> | 39 | + <nut-config-provider :theme-vars="themeVars"> |
| 40 | - <nut-radio-group v-model="formData.gender" direction="horizontal"> | 40 | + <nut-form-item label="性别" prop="gender" body-align="right"> |
| 41 | - <nut-radio label="男">男</nut-radio> | 41 | + <nut-radio-group v-model="formData.gender" direction="horizontal"> |
| 42 | - <nut-radio label="女">女</nut-radio> | 42 | + <nut-radio label="男">男</nut-radio> |
| 43 | - </nut-radio-group> | 43 | + <nut-radio label="女">女</nut-radio> |
| 44 | - </nut-form-item> | 44 | + </nut-radio-group> |
| 45 | + </nut-form-item> | ||
| 46 | + </nut-config-provider> | ||
| 45 | 47 | ||
| 46 | <!-- 生日 --> | 48 | <!-- 生日 --> |
| 47 | <nut-form-item label="生日" prop="birthday"> | 49 | <nut-form-item label="生日" prop="birthday"> |
| ... | @@ -156,12 +158,8 @@ | ... | @@ -156,12 +158,8 @@ |
| 156 | /> | 158 | /> |
| 157 | </nut-popup> | 159 | </nut-popup> |
| 158 | 160 | ||
| 159 | - <!-- 头像预览 --> | 161 | + <!-- 图片预览组件 --> |
| 160 | - <nut-image-preview | 162 | + <nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" @close="closePreview" /> |
| 161 | - v-model:show="avatarPreviewVisible" | ||
| 162 | - :images="[formData.avatar || defaultAvatar]" | ||
| 163 | - @close="closePreview" | ||
| 164 | - /> | ||
| 165 | 163 | ||
| 166 | <!-- 成功提示 --> | 164 | <!-- 成功提示 --> |
| 167 | <nut-toast | 165 | <nut-toast |
| ... | @@ -177,6 +175,12 @@ import { ref, reactive, onMounted } from 'vue' | ... | @@ -177,6 +175,12 @@ import { ref, reactive, onMounted } from 'vue' |
| 177 | import Taro from '@tarojs/taro' | 175 | import Taro from '@tarojs/taro' |
| 178 | import './index.less' | 176 | import './index.less' |
| 179 | import { Right } from '@nutui/icons-vue-taro' | 177 | import { Right } from '@nutui/icons-vue-taro' |
| 178 | +import BASE_URL from '@/utils/config'; | ||
| 179 | + | ||
| 180 | +// 主题配置 | ||
| 181 | +const themeVars = { | ||
| 182 | + radioLabelFontActiveColor: '#f97316' | ||
| 183 | +} | ||
| 180 | 184 | ||
| 181 | // 默认头像 | 185 | // 默认头像 |
| 182 | const defaultAvatar = 'https://randomuser.me/api/portraits/men/32.jpg' | 186 | const defaultAvatar = 'https://randomuser.me/api/portraits/men/32.jpg' |
| ... | @@ -196,11 +200,15 @@ const formData = reactive({ | ... | @@ -196,11 +200,15 @@ const formData = reactive({ |
| 196 | const phoneDialogVisible = ref(false) | 200 | const phoneDialogVisible = ref(false) |
| 197 | const datePickerVisible = ref(false) | 201 | const datePickerVisible = ref(false) |
| 198 | const schoolPickerVisible = ref(false) | 202 | const schoolPickerVisible = ref(false) |
| 199 | -const avatarPreviewVisible = ref(false) | ||
| 200 | const toastVisible = ref(false) | 203 | const toastVisible = ref(false) |
| 201 | 204 | ||
| 205 | +// 图片预览相关 | ||
| 206 | +const previewVisible = ref(false) | ||
| 207 | +const previewImages = ref([]) | ||
| 208 | +const previewIndex = ref(0) | ||
| 209 | + | ||
| 202 | const closePreview = () => { | 210 | const closePreview = () => { |
| 203 | - avatarPreviewVisible.value = false | 211 | + previewVisible.value = false |
| 204 | } | 212 | } |
| 205 | 213 | ||
| 206 | // 手机号相关 | 214 | // 手机号相关 |
| ... | @@ -234,18 +242,79 @@ const goBack = () => { | ... | @@ -234,18 +242,79 @@ const goBack = () => { |
| 234 | // 更换头像 | 242 | // 更换头像 |
| 235 | const changeAvatar = () => { | 243 | const changeAvatar = () => { |
| 236 | Taro.chooseImage({ | 244 | Taro.chooseImage({ |
| 237 | - count: 1, | 245 | + count: 1, |
| 238 | - sizeType: ['compressed'], | 246 | + sizeType: ['compressed'], |
| 239 | - sourceType: ['album', 'camera'], | 247 | + sourceType: ['album', 'camera'], |
| 240 | - success: (res) => { | 248 | + success: function (res) { |
| 241 | - formData.avatar = res.tempFilePaths[0] | 249 | + const tempFilePath = res.tempFilePaths[0] |
| 242 | - } | 250 | + uploadImage(tempFilePath) |
| 243 | - }) | 251 | + }, |
| 252 | + fail: function () { | ||
| 253 | + Taro.showToast({ | ||
| 254 | + title: '选择图片失败', | ||
| 255 | + icon: 'none' | ||
| 256 | + }) | ||
| 257 | + } | ||
| 258 | + }) | ||
| 244 | } | 259 | } |
| 245 | 260 | ||
| 246 | -// 预览头像 | 261 | +/** |
| 247 | -const previewAvatar = () => { | 262 | + * 上传图片到服务器 |
| 248 | - avatarPreviewVisible.value = true | 263 | + * @param {String} filePath - 图片文件路径 |
| 264 | + */ | ||
| 265 | +const uploadImage = (filePath) => { | ||
| 266 | + // 显示上传中提示 | ||
| 267 | + Taro.showLoading({ | ||
| 268 | + title: '上传中', | ||
| 269 | + mask: true | ||
| 270 | + }) | ||
| 271 | + | ||
| 272 | + // 获取上传URL | ||
| 273 | + wx.uploadFile({ | ||
| 274 | + url: BASE_URL + '/admin/?m=srv&a=upload', | ||
| 275 | + filePath, | ||
| 276 | + name: 'file', | ||
| 277 | + header: { | ||
| 278 | + 'content-type': 'multipart/form-data', | ||
| 279 | + }, | ||
| 280 | + success: function (res) { | ||
| 281 | + let upload_data = JSON.parse(res.data); | ||
| 282 | + Taro.hideLoading({ | ||
| 283 | + success: () => { | ||
| 284 | + if (res.statusCode === 200) { | ||
| 285 | + formData.avatar = upload_data.data.src; | ||
| 286 | + Taro.showToast({ | ||
| 287 | + title: '上传成功', | ||
| 288 | + icon: 'success' | ||
| 289 | + }) | ||
| 290 | + } else { | ||
| 291 | + Taro.showToast({ | ||
| 292 | + icon: 'error', | ||
| 293 | + title: '服务器错误,稍后重试!', | ||
| 294 | + mask: true | ||
| 295 | + }) | ||
| 296 | + } | ||
| 297 | + }, | ||
| 298 | + }); | ||
| 299 | + } | ||
| 300 | + }); | ||
| 301 | +} | ||
| 302 | + | ||
| 303 | +/** | ||
| 304 | + * 预览头像 | ||
| 305 | + * @param {string} imageUrl - 图片URL | ||
| 306 | + */ | ||
| 307 | +const previewAvatar = (imageUrl) => { | ||
| 308 | + if (!imageUrl) { | ||
| 309 | + Taro.showToast({ | ||
| 310 | + title: '暂无图片可预览', | ||
| 311 | + icon: 'none' | ||
| 312 | + }) | ||
| 313 | + return | ||
| 314 | + } | ||
| 315 | + previewImages.value = [{ src: imageUrl }] | ||
| 316 | + previewIndex.value = 0 | ||
| 317 | + previewVisible.value = true | ||
| 249 | } | 318 | } |
| 250 | 319 | ||
| 251 | // 显示手机号弹框 | 320 | // 显示手机号弹框 | ... | ... |
| ... | @@ -7,7 +7,7 @@ | ... | @@ -7,7 +7,7 @@ |
| 7 | :src="formData.avatar || defaultAvatar" | 7 | :src="formData.avatar || defaultAvatar" |
| 8 | class="avatar-image" | 8 | class="avatar-image" |
| 9 | mode="aspectFill" | 9 | mode="aspectFill" |
| 10 | - @click="previewAvatar" | 10 | + @tap="previewAvatar(formData.avatar || defaultAvatar)" |
| 11 | /> | 11 | /> |
| 12 | </view> | 12 | </view> |
| 13 | <view class="change-avatar-btn" @click="changeAvatar"> | 13 | <view class="change-avatar-btn" @click="changeAvatar"> |
| ... | @@ -64,12 +64,14 @@ | ... | @@ -64,12 +64,14 @@ |
| 64 | </nut-form-item> | 64 | </nut-form-item> |
| 65 | 65 | ||
| 66 | <!-- 性别 --> | 66 | <!-- 性别 --> |
| 67 | - <nut-form-item label="性别" prop="gender" body-align="right" required :rules="[{ required: true, message: '请选择性别' }]"> | 67 | + <nut-config-provider :theme-vars="themeVars"> |
| 68 | - <nut-radio-group v-model="formData.gender" direction="horizontal"> | 68 | + <nut-form-item label="性别" prop="gender" body-align="right" required :rules="[{ required: true, message: '请选择性别' }]"> |
| 69 | - <nut-radio label="男">男</nut-radio> | 69 | + <nut-radio-group v-model="formData.gender" direction="horizontal"> |
| 70 | - <nut-radio label="女">女</nut-radio> | 70 | + <nut-radio label="男">男</nut-radio> |
| 71 | - </nut-radio-group> | 71 | + <nut-radio label="女">女</nut-radio> |
| 72 | - </nut-form-item> | 72 | + </nut-radio-group> |
| 73 | + </nut-form-item> | ||
| 74 | + </nut-config-provider> | ||
| 73 | 75 | ||
| 74 | <!-- 生日 --> | 76 | <!-- 生日 --> |
| 75 | <nut-form-item label="生日" prop="birthday" label-position="top"> | 77 | <nut-form-item label="生日" prop="birthday" label-position="top"> |
| ... | @@ -124,11 +126,7 @@ | ... | @@ -124,11 +126,7 @@ |
| 124 | </nut-popup> | 126 | </nut-popup> |
| 125 | 127 | ||
| 126 | <!-- 头像预览 --> | 128 | <!-- 头像预览 --> |
| 127 | - <nut-image-preview | 129 | + <nut-image-preview v-model:show="previewVisible" :images="previewImages" :init-no="previewIndex" @close="closePreview" /> |
| 128 | - v-model:show="avatarPreviewVisible" | ||
| 129 | - :images="[formData.avatar || defaultAvatar]" | ||
| 130 | - @close="closePreview" | ||
| 131 | - /> | ||
| 132 | 130 | ||
| 133 | <!-- 成功提示 --> | 131 | <!-- 成功提示 --> |
| 134 | <nut-toast | 132 | <nut-toast |
| ... | @@ -142,13 +140,13 @@ | ... | @@ -142,13 +140,13 @@ |
| 142 | <script setup> | 140 | <script setup> |
| 143 | import { ref, reactive, computed, onMounted } from 'vue' | 141 | import { ref, reactive, computed, onMounted } from 'vue' |
| 144 | import Taro from '@tarojs/taro' | 142 | import Taro from '@tarojs/taro' |
| 145 | -import { RectLeft, Right } from '@nutui/icons-vue-taro' | 143 | +import { Right } from '@nutui/icons-vue-taro' |
| 146 | import './index.less' | 144 | import './index.less' |
| 145 | +import BASE_URL from '@/utils/config'; | ||
| 147 | 146 | ||
| 148 | // 主题配置 | 147 | // 主题配置 |
| 149 | const themeVars = { | 148 | const themeVars = { |
| 150 | - navbarBackground: '#f97316', | 149 | + radioLabelFontActiveColor: '#f97316' |
| 151 | - navbarColor: '#fff' | ||
| 152 | } | 150 | } |
| 153 | 151 | ||
| 154 | // 默认头像 | 152 | // 默认头像 |
| ... | @@ -168,13 +166,17 @@ const formData = reactive({ | ... | @@ -168,13 +166,17 @@ const formData = reactive({ |
| 168 | // 弹框控制 | 166 | // 弹框控制 |
| 169 | const datePickerVisible = ref(false) | 167 | const datePickerVisible = ref(false) |
| 170 | const schoolPickerVisible = ref(false) | 168 | const schoolPickerVisible = ref(false) |
| 171 | -const avatarPreviewVisible = ref(false) | ||
| 172 | const toastVisible = ref(false) | 169 | const toastVisible = ref(false) |
| 173 | const toastMessage = ref('') | 170 | const toastMessage = ref('') |
| 174 | const toastType = ref('success') | 171 | const toastType = ref('success') |
| 175 | 172 | ||
| 173 | +// 图片预览相关 | ||
| 174 | +const previewVisible = ref(false) | ||
| 175 | +const previewImages = ref([]) | ||
| 176 | +const previewIndex = ref(0) | ||
| 177 | + | ||
| 176 | const closePreview = () => { | 178 | const closePreview = () => { |
| 177 | - avatarPreviewVisible.value = false | 179 | + previewVisible.value = false |
| 178 | } | 180 | } |
| 179 | 181 | ||
| 180 | // 验证码相关 | 182 | // 验证码相关 |
| ... | @@ -214,31 +216,82 @@ const isPhoneValid = computed(() => { | ... | @@ -214,31 +216,82 @@ const isPhoneValid = computed(() => { |
| 214 | }) | 216 | }) |
| 215 | 217 | ||
| 216 | /** | 218 | /** |
| 217 | - * 返回上一页 | 219 | + * 更换头像 |
| 218 | */ | 220 | */ |
| 219 | -const goBack = () => { | 221 | +const changeAvatar = () => { |
| 220 | - Taro.navigateBack() | 222 | + Taro.chooseImage({ |
| 223 | + count: 1, | ||
| 224 | + sizeType: ['compressed'], | ||
| 225 | + sourceType: ['album', 'camera'], | ||
| 226 | + success: function (res) { | ||
| 227 | + const tempFilePath = res.tempFilePaths[0] | ||
| 228 | + uploadImage(tempFilePath) | ||
| 229 | + }, | ||
| 230 | + fail: function () { | ||
| 231 | + Taro.showToast({ | ||
| 232 | + title: '选择图片失败', | ||
| 233 | + icon: 'none' | ||
| 234 | + }) | ||
| 235 | + } | ||
| 236 | + }) | ||
| 221 | } | 237 | } |
| 222 | 238 | ||
| 223 | /** | 239 | /** |
| 224 | - * 更换头像 | 240 | + * 上传图片到服务器 |
| 241 | + * @param {String} filePath - 图片文件路径 | ||
| 225 | */ | 242 | */ |
| 226 | -const changeAvatar = () => { | 243 | +const uploadImage = (filePath) => { |
| 227 | - Taro.chooseImage({ | 244 | + // 显示上传中提示 |
| 228 | - count: 1, | 245 | + Taro.showLoading({ |
| 229 | - sizeType: ['compressed'], | 246 | + title: '上传中', |
| 230 | - sourceType: ['album', 'camera'], | 247 | + mask: true |
| 231 | - success: (res) => { | 248 | + }) |
| 232 | - formData.avatar = res.tempFilePaths[0] | 249 | + |
| 233 | - } | 250 | + // 获取上传URL |
| 234 | - }) | 251 | + wx.uploadFile({ |
| 252 | + url: BASE_URL + '/admin/?m=srv&a=upload', | ||
| 253 | + filePath, | ||
| 254 | + name: 'file', | ||
| 255 | + header: { | ||
| 256 | + 'content-type': 'multipart/form-data', | ||
| 257 | + }, | ||
| 258 | + success: function (res) { | ||
| 259 | + let upload_data = JSON.parse(res.data); | ||
| 260 | + Taro.hideLoading({ | ||
| 261 | + success: () => { | ||
| 262 | + if (res.statusCode === 200) { | ||
| 263 | + formData.avatar = upload_data.data.src; | ||
| 264 | + Taro.showToast({ | ||
| 265 | + title: '上传成功', | ||
| 266 | + icon: 'success' | ||
| 267 | + }) | ||
| 268 | + } else { | ||
| 269 | + Taro.showToast({ | ||
| 270 | + icon: 'error', | ||
| 271 | + title: '服务器错误,稍后重试!', | ||
| 272 | + mask: true | ||
| 273 | + }) | ||
| 274 | + } | ||
| 275 | + }, | ||
| 276 | + }); | ||
| 277 | + } | ||
| 278 | + }); | ||
| 235 | } | 279 | } |
| 236 | 280 | ||
| 237 | /** | 281 | /** |
| 238 | * 预览头像 | 282 | * 预览头像 |
| 239 | */ | 283 | */ |
| 240 | -const previewAvatar = () => { | 284 | +const previewAvatar = (imageUrl) => { |
| 241 | - avatarPreviewVisible.value = true | 285 | + if (!imageUrl) { |
| 286 | + Taro.showToast({ | ||
| 287 | + title: '暂无图片可预览', | ||
| 288 | + icon: 'none' | ||
| 289 | + }) | ||
| 290 | + return | ||
| 291 | + } | ||
| 292 | + previewImages.value = [{ src: imageUrl }] | ||
| 293 | + previewIndex.value = 0 | ||
| 294 | + previewVisible.value = true | ||
| 242 | } | 295 | } |
| 243 | 296 | ||
| 244 | /** | 297 | /** | ... | ... |
-
Please register or login to post a comment