From be4e2d7c2b330c3b8991a0286ea16cc1285b9ec0 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Fri, 08 May 2026 17:47:32 +0800
Subject: [PATCH] feat: 微信小程序支持上传头像

---
 src/subPackages/userDetail/infos/index.vue |  470 ++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 291 insertions(+), 179 deletions(-)

diff --git a/src/subPackages/userDetail/infos/index.vue b/src/subPackages/userDetail/infos/index.vue
index e31e2de..2bec63c 100644
--- a/src/subPackages/userDetail/infos/index.vue
+++ b/src/subPackages/userDetail/infos/index.vue
@@ -1,194 +1,306 @@
 <template>
-	<view class="container">
-		<div class="avatarBox">
-			<u-avatar :src="userInfo.avatar" size="114" mode="aspectFill" />
-		</div>
-		<view class="detailBox">
-			<div class="orderRow">
-				<div class="rowTitle">姓名</div>
-				<div>{{userInfo.realName}}</div>
-			</div>
-			<div class="orderRow">
-				<div class="rowTitle">所属单位</div>
-				<div>{{userInfo.deptName}}</div>
-			</div>
-			<div class="orderRow">
-				<div class="rowTitle">手机号</div>
-				<u-input input-align="right" v-model="userInfo.phone" type="number" placeholder="请输入手机号"
-					class="input-item" />
+  <view class="container">
+    <div class="avatarBox">
 
-			</div>
-			<div class="orderRow">
-				<div class="rowTitle">邮箱</div>
-				<u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
-					class="input-item" />
-			</div>
-		</view>
-		<view class="btngroup">
-			<u-button color="#AEAEAE" class="custom-style" shape="circle" @click="reset">重置</u-button>
-			<u-button color="#1D6FE9" class="custom-style" shape="circle" @click="submit">提交</u-button>
-		</view>
-	</view>
+      <u-avatar @click="uploadAvatar" :src="userInfo.avatar" size="114" mode="aspectFill"/>
+    </div>
+    <view class="detailBox">
+      <div class="detailCon">
+        <div class="orderRow">
+          <div class="rowTitle">姓名</div>
+          <div>{{ userInfo.realName }}</div>
+        </div>
+        <div class="orderRow">
+          <div class="rowTitle">所属单位</div>
+          <div>{{ userInfo.deptName }}</div>
+        </div>
+        <div class="orderRow">
+          <div class="rowTitle">手机号</div>
+          <u-input input-align="right" v-model="userInfo.phone" type="number" placeholder="请输入手机号"
+                   class="input-item"/>
+
+        </div>
+        <div class="orderRow">
+          <div class="rowTitle">邮箱</div>
+          <u-input input-align="right" v-model="userInfo.email" type="email" placeholder="请输入邮箱"
+                   class="input-item"/>
+        </div>
+      </div>
+    </view>
+    <view class="btngroup">
+      <u-button color="#AEAEAE" class="custom-style" shape="circle" @click="reset">重置</u-button>
+      <u-button color="#1D6FE9" class="custom-style" shape="circle" @click="submit">提交</u-button>
+    </view>
+  </view>
 </template>
 
 <script setup>
-	import {
-		getUserInfo,
-		updateInfo,
-		updatePassword
-	} from '@/api/user/index.js';
-	const userInfo = ref({
-		id: '',
-		avatar: '',
-		realName: '',
-		name: '',
-		phone: '',
-		email: '',
-		deptName: '',
-	});
-	// 校验手机号
-	const validatePhone = () => {
-		const phone = userInfo.value.phone
-		if (!phone) return true
-		const phoneRegex = /^1[3-9]\d{9}$/
-		if (!phoneRegex.test(phone)) {
-			
-			uni.showToast({
-				title: '请输入正确的手机号码',
-				icon: 'none',
-				duration: 2000
-			});
-			return false
-		}
-		return true
-	}
-	// 校验邮箱
-	const validateEmail = () => {
-		if (!userInfo.value.email) return true 
-		const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
-		if (!emailRegex.test(userInfo.value.email)) {
-		
-			uni.showToast({
-				title: '请输入正确的邮箱地址',
-				icon: 'none',
-				duration: 2000
-			});
-			return false
-		}
-		return true
-	}
-	const getUserInfoData = () => {
-		getUserInfo().then(res => {
-			const user = res.data.data;
-			userInfo.value = {
-				id: user.id,
-				avatar: user.avatar,
-				name: user.name,
-				realName: user.realName,
-				phone: user.phone,
-				email: user.email,
-				deptName: user.deptName,
-			};
+import {getEnvObj} from "@/utils/index.js";
+import {getUserInfo, updateInfo} from '@/api/user/index.js';
+import {useUserStore} from "@/store/index.js";
+import {ref} from "vue";
+import {onShow} from "@dcloudio/uni-app";
 
-		});
-	};
-	const reset = () => {
-		getUserInfoData();
-	};
-	const submit = () => {
-		if (!validatePhone() || !validateEmail()) return
-		userInfo.value.name = userInfo.value.realName;
-		updateInfo(userInfo.value).then(res => {
-			if (res.data.code === 200) {
-				uni.showToast({
-					title: '修改信息成功',
-					icon: 'none',
-					duration: 2000
-				});
+const userInfo = ref({
+  id: '',
+  avatar: '',
+  realName: '',
+  name: '',
+  phone: '',
+  email: '',
+  deptName: '',
+});
 
-			} else {
-				uni.showToast({
-					title: res.msg,
-					icon: 'none',
-					duration: 2000
-				});
-			}
-			
-		});
-		getUserInfoData()
-	};
-	onShow(async () => {
-		getUserInfoData()
-	});
+// 校验手机号
+const validatePhone = () => {
+  const phone = userInfo.value.phone
+  if (!phone) return true
+  const phoneRegex = /^1[3-9]\d{9}$/
+  if (!phoneRegex.test(phone)) {
+    uni.showToast({
+      title: '请输入正确的手机号码',
+      icon: 'none',
+      duration: 2000
+    });
+    return false
+  }
+  return true
+}
+
+// 校验邮箱
+const validateEmail = () => {
+  if (!userInfo.value.email) return true
+  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
+  if (!emailRegex.test(userInfo.value.email)) {
+
+    uni.showToast({
+      title: '请输入正确的邮箱地址',
+      icon: 'none',
+      duration: 2000
+    });
+    return false
+  }
+  return true
+}
+
+const getUserInfoData = () => {
+  getUserInfo().then(res => {
+    const user = res.data.data;
+    userInfo.value = {
+      id: user.id,
+      avatar: user.avatar,
+      name: user.name,
+      realName: user.realName,
+      phone: user.phone,
+      email: user.email,
+      deptName: user.deptName,
+    };
+
+  });
+};
+const reset = () => {
+  getUserInfoData();
+};
+
+const {
+  VITE_API_BASE_URL
+} = getEnvObj()
+
+// 头像上传到服务器
+function uploadUtil(options) {
+  const {formData, filePath, url} = options;
+  uni.showLoading({title: '上传中...'})
+  return new Promise((resolve, reject) => {
+    let accessToken = useUserStore()?.$state?.userInfo?.access_token;
+    uni.uploadFile({
+      url: `${VITE_API_BASE_URL}${url}`,
+      name: 'file',
+      header: {
+        'Blade-Auth': 'bearer ' + accessToken
+      },
+      filePath: filePath,
+      formData,
+      success: (res) => {
+        const resData = JSON.parse(res.data)
+        if (resData.code === 200 || resData.code === 0) {
+          resolve(res)
+        } else {
+          uni.showToast(resData.message)
+          reject(res)
+        }
+      },
+      fail: (err) => {
+        reject(err)
+      },
+      complete: () => {
+        uni.hideLoading()
+      }
+    });
+  })
+}
+
+const handleUploadResult = (filePath, fileName) => {
+  uploadUtil({
+    filePath: filePath,
+    formData: {
+      fileName: fileName,
+      sn: 'avatar_upload'
+    },
+    url: '/blade-resource/oss/endpoint/put-file'
+  }).then(res => {
+    const resData = JSON.parse(res.data);
+    if (resData.code === 200 || resData.code === 0) {
+      // 更新头像显示
+      userInfo.value.avatar = resData.data.link || resData.data.url;
+      uni.showToast({title: '头像上传成功', icon: 'success'});
+    } else {
+      throw new Error(resData.message || '上传失败');
+    }
+  }).catch(err => {
+    uni.showToast({title: err.message || '上传失败', icon: 'none'});
+  });
+};
+
+// 上传头像功能
+const uploadAvatar = () => {
+  // #ifdef MP-WEIXIN
+  uni.chooseMedia({
+    count: 1,
+    mediaType: ['image'],
+    sourceType: ['album', 'camera'],
+    maxDuration: 30,
+    camera: 'back',
+    success(res) {
+      const filePath = res.tempFiles[0].tempFilePath;
+      handleUploadResult(filePath, filePath);
+    }
+  })
+  // #endif
+
+  // #ifndef MP-WEIXIN
+  uni.chooseImage({
+    count: 1,
+    success: (res) => {
+      const tempFile = res.tempFiles[0]; // 获取文件对象
+      const filePath = tempFile.path || tempFile.tempFilePath;
+      if (!filePath) {
+        uni.showToast({title: '获取文件路径失败', icon: 'none'});
+        return;
+      }
+      let fileName = tempFile.name;
+      if (!fileName) {
+        const pathWithoutProtocol = filePath.replace(/^file:\/\//, '');
+        fileName = pathWithoutProtocol.split('/').pop() || 'unknown.png';
+      }
+      // 上传文件
+      handleUploadResult(filePath, fileName);
+    }
+  });
+  // #endif
+}
+const submit = () => {
+  if (!validatePhone() || !validateEmail()) return
+  userInfo.value.name = userInfo.value.realName;
+  updateInfo(userInfo.value).then(res => {
+    if (res.data.code === 200) {
+      uni.showToast({
+        title: '修改信息成功',
+        icon: 'none',
+        duration: 2000
+      });
+      getUserInfoData()
+
+    } else {
+      uni.showToast({
+        title: res.msg,
+        icon: 'none',
+        duration: 2000
+      });
+      getUserInfoData()
+    }
+  });
+};
+onShow(async () => {
+  getUserInfoData()
+});
 </script>
 
 <style lang="scss" scoped>
-	.container {
-		width: 100%;	
-		height: 100%;
-		display: flex;
-		flex-direction: column;
-		align-items: center;
-		background: url('/src/static/images/user/allBg.svg') no-repeat center center;
-		background-size: cover;
-		background-attachment: fixed;
+.container {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
 
-	}
+.avatarBox {
+  width: 228rpx;
+  height: 228rpx;
+  margin: 76rpx 0;
+}
 
-	.avatarBox {
-		width: 114px;
-		height: 114px;
-		margin: 38px 0;
-	}
+.detailBox {
+  width: 702rpx;
+  min-height: 430rpx;
+  background: #FFFFFF;
+  border-radius: 12rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
 
-	.detailBox {
-		width: 351px;
-		min-height: 215px;
-		background: #FFFFFF;
-		border-radius: 6px;
-		box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
-		.orderRow {
-			display: flex;
-			justify-content: space-between;
-			align-items: center;
-			height: 48px;
-			border-bottom: 1px solid #f5f5f5;
-			color: #7b7b7b;
-			padding: 0 12px;
-			.rowTitle {
-				font-family: Source Han Sans CN, Source Han Sans CN;
-				font-weight: 400;
-				font-size: 15px;
-				color: #222324;
-				white-space: nowrap;
-			}
-			::v-deep .u-input {
-				border: none !important;
-				background: transparent !important;
-			}
+  .detailCon {
+    padding: 0 24rpx;
+  }
 
-			::v-deep .u-input__input {
-				border: none !important;
-				box-shadow: none !important;
-				background: transparent !important;
-				padding: 0 !important;
-				margin: 0 !important;
-				height: auto !important;
-			}
-		}
-	}
+  .orderRow {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 96rpx;
+    border-bottom: 2rpx solid #f5f5f5;
+    color: #7b7b7b;
 
-	.btngroup {
-		display: flex;
-		position: absolute;
-		bottom:75px;
-	}
-	.u-button:first-child{
-		margin-right: 15px;
-	}
-	
-	.custom-style {
-		width: 138px;
-		height: 38px;
-	}
-</style>
\ No newline at end of file
+    .rowTitle {
+      font-family: Source Han Sans CN, Source Han Sans CN;
+      font-weight: 400;
+      font-size: 30rpx;
+      color: #222324;
+      white-space: nowrap;
+    }
+
+    ::v-deep .u-input {
+      border: none !important;
+      background: transparent !important;
+      padding: 0 !important;
+    }
+
+    ::v-deep .u-input__input {
+      border: none !important;
+      box-shadow: none !important;
+      background: transparent !important;
+      padding: 0 !important;
+      margin: 0 !important;
+      height: auto !important;
+    }
+  }
+}
+
+.btngroup {
+  display: flex;
+  position: absolute;
+  bottom: 150rpx;
+}
+
+:deep(.u-button:first-child) {
+  margin-right: 30rpx !important;
+}
+
+.custom-style {
+  width: 276rpx;
+  height: 76rpx;
+}
+
+:deep(.u-button) {
+  width: 276rpx !important;
+  height: 76rpx !important;
+}
+</style>

--
Gitblit v1.9.3