Jelajahi Sumber

花卉宝-新增头像更换

ouyang 2 hari lalu
induk
melakukan
f43274be3a
1 mengubah file dengan 187 tambahan dan 3 penghapusan
  1. 187 3
      mallApp/src/pages/user/edit.vue

+ 187 - 3
mallApp/src/pages/user/edit.vue

@@ -1,11 +1,35 @@
 <!--
 <!--
-  商城「我的」资料编辑:改昵称、看/改生日、退出登录。
+  商城「我的」资料编辑:改头像、改昵称、看/改生日、退出登录。
+  头像走 /upload/save-file?actType=saveAvatar,后端会写 user 与客户表;成功后同步 vuex loginInfo。
   改昵称成功后必须写入 vuex loginInfo,否则返回「我的」仍显示旧名字。
   改昵称成功后必须写入 vuex loginInfo,否则返回「我的」仍显示旧名字。
 -->
 -->
 <template>
 <template>
 	<div class="app-content">
 	<div class="app-content">
 		<form @submit="formSubmit">
 		<form @submit="formSubmit">
 			<div class="module-com input-line-wrap">
 			<div class="module-com input-line-wrap">
+				<!-- 头像:微信用 chooseAvatar;其它端选相册/相机后上传 -->
+				<tui-list-cell class="line-cell avatar-cell" :hover="false">
+					<div class="tui-title">头像</div>
+					<div class="avatar-wrap">
+						<!-- #ifdef MP-WEIXIN -->
+						<button
+							class="avatar-btn"
+							open-type="chooseAvatar"
+							:disabled="uploadingAvatar"
+							@chooseavatar="onChooseAvatar"
+						>
+							<image class="avatar-img" :src="avatarDisplay" mode="aspectFill" />
+							<text class="avatar-tip">更换</text>
+						</button>
+						<!-- #endif -->
+						<!-- #ifndef MP-WEIXIN -->
+						<div class="avatar-btn" @click="chooseLocalAvatar">
+							<image class="avatar-img" :src="avatarDisplay" mode="aspectFill" />
+							<text class="avatar-tip">更换</text>
+						</div>
+						<!-- #endif -->
+					</div>
+				</tui-list-cell>
 				<tui-list-cell class="line-cell" :hover="false">
 				<tui-list-cell class="line-cell" :hover="false">
 					<div class="tui-title">昵称</div>
 					<div class="tui-title">昵称</div>
 					<input v-model="form.name" placeholder-class="phcolor" class="tui-input" name="name" placeholder="请填写" type="nickname" />
 					<input v-model="form.name" placeholder-class="phcolor" class="tui-input" name="name" placeholder="请填写" type="nickname" />
@@ -34,6 +58,8 @@
 </template>
 </template>
 <script>
 <script>
 import TuiListCell from '@/components/plugin/list-cell'
 import TuiListCell from '@/components/plugin/list-cell'
+import constant from '@/constant/index'
+import { TOKEN_STORAGE_KEY } from '@/constant/storageKeys'
 const form = require('@/utils/formValidation.js')
 const form = require('@/utils/formValidation.js')
 import { currentInfo,updateInfo,clearLogin } from "@/api/user";
 import { currentInfo,updateInfo,clearLogin } from "@/api/user";
 export default {
 export default {
@@ -46,7 +72,31 @@ export default {
 			form: {
 			form: {
 				name: ''
 				name: ''
 			},
 			},
-			userInfo:{}
+			userInfo:{},
+			/** 头像上传中,防止重复点选 */
+			uploadingAvatar: false
+		}
+	},
+	computed: {
+		/**
+		 * 展示用头像:优先 smallAvatar,无则用大图,再回落默认图
+		 */
+		avatarDisplay() {
+			const info = this.userInfo || {}
+			if (info.smallAvatar) {
+				return info.smallAvatar
+			}
+			if (info.avatar) {
+				return info.avatar
+			}
+			const login = this.$store.getters.getLoginInfo || {}
+			if (login.smallAvatar) {
+				return login.smallAvatar
+			}
+			if (login.avatar) {
+				return login.avatar
+			}
+			return `${this.constant.imgUrl}/retail/default-img.png`
 		}
 		}
 	},
 	},
 	onLoad() {
 	onLoad() {
@@ -82,7 +132,7 @@ export default {
 			//this.getLoginInfo();
 			//this.getLoginInfo();
 		},
 		},
 		/**
 		/**
-		 * 拉取当前登录用户信息:回填生日展示,并把已有昵称写入 form.name 供输入框渲染
+		 * 拉取当前登录用户信息:回填生日/头像展示,并把已有昵称写入 form.name 供输入框渲染
 		 */
 		 */
 		getLoginInfo(){
 		getLoginInfo(){
 			currentInfo().then(res=>{
 			currentInfo().then(res=>{
@@ -97,6 +147,97 @@ export default {
 		goToBirthday() {
 		goToBirthday() {
 			this.pageTo({ url: '/pages/user/birthday'})
 			this.pageTo({ url: '/pages/user/birthday'})
 		},
 		},
+		/**
+		 * 微信头像能力回调:拿到临时路径后上传并落库
+		 * @param {Object} e chooseavatar 事件,e.detail.avatarUrl 为临时文件路径
+		 */
+		onChooseAvatar(e) {
+			const filePath = e && e.detail && e.detail.avatarUrl
+			if (!filePath) {
+				this.$msg('未获取到头像')
+				return
+			}
+			this.uploadAvatar(filePath)
+		},
+		/**
+		 * 非微信端:从相册/相机选图后上传为头像
+		 */
+		chooseLocalAvatar() {
+			if (this.uploadingAvatar) {
+				return
+			}
+			uni.chooseImage({
+				count: 1,
+				sizeType: ['compressed'],
+				sourceType: ['album', 'camera'],
+				success: (res) => {
+					const paths = res.tempFilePaths || []
+					if (!paths.length) {
+						return
+					}
+					this.uploadAvatar(paths[0])
+				}
+			})
+		},
+		/**
+		 * 上传头像到 /upload/save-file?actType=saveAvatar。
+		 * 后端会更新 user.avatar 与客户表 avatar;前端用返回的 url/smallUrl 刷新本页与 loginInfo。
+		 * @param {String} filePath 本地临时图片路径
+		 */
+		uploadAvatar(filePath) {
+			if (!filePath || this.uploadingAvatar) {
+				return
+			}
+			this.uploadingAvatar = true
+			uni.showLoading({ title: '上传中', mask: true })
+			const token = uni.getStorageSync(TOKEN_STORAGE_KEY) || uni.getStorageSync('token') || ''
+			const account = uni.getStorageSync('account') || ''
+			const header = { token }
+			if (account) {
+				header.account = account
+			}
+			uni.uploadFile({
+				url: constant.hostUrl + '/upload/save-file?actType=saveAvatar',
+				filePath,
+				name: 'file',
+				header,
+				success: (uploadRes) => {
+					try {
+						const body = typeof uploadRes.data === 'string'
+							? JSON.parse(uploadRes.data)
+							: uploadRes.data
+						if (body && body.code == 1 && body.data) {
+							const avatar = body.data.url || body.data.bigUrl || ''
+							const smallAvatar = body.data.smallUrl || avatar
+							// 本页立即换图,避免再拉一次接口
+							this.userInfo = Object.assign({}, this.userInfo, {
+								avatar,
+								smallAvatar,
+								shortAvatar: body.data.shortUrl || this.userInfo.shortAvatar
+							})
+							// 「我的」页读 loginInfo.avatar,必须同步 vuex
+							const loginInfo = Object.assign({}, this.$store.getters.getLoginInfo || {}, {
+								avatar,
+								smallAvatar
+							})
+							this.$store.commit('setLoginInfo', loginInfo)
+							this.$msg('头像已更新')
+							return
+						}
+						this.$msg((body && body.msg) || '上传失败')
+					} catch (err) {
+						this.$msg('上传失败')
+					}
+				},
+				fail: () => {
+					this.$msg('上传失败')
+				},
+				complete: () => {
+					this.uploadingAvatar = false
+					uni.hideLoading()
+				}
+			})
+		},
 		/**
 		/**
 		 * 提交昵称:校验后调 /user/update,并把新名字写入 loginInfo(含本地缓存)。
 		 * 提交昵称:校验后调 /user/update,并把新名字写入 loginInfo(含本地缓存)。
 		 * 「我的」页读的是 loginInfo.name,只改接口不写 vuex 会看起来没更新。
 		 * 「我的」页读的是 loginInfo.name,只改接口不写 vuex 会看起来没更新。
@@ -138,24 +279,67 @@ export default {
 			color: $fontColor2;
 			color: $fontColor2;
 			line-height: 80upx;
 			line-height: 80upx;
 		}
 		}
+		/* 昵称内容靠右,与头像/生日行一致 */
 		.tui-input {
 		.tui-input {
+			flex: 1;
 			width: calc(100% - 210upx);
 			width: calc(100% - 210upx);
 			font-size: 28upx;
 			font-size: 28upx;
 			min-height: 80upx;
 			min-height: 80upx;
 			padding: 20upx;
 			padding: 20upx;
+			text-align: right;
 		}
 		}
 		.tui-placeholder {
 		.tui-placeholder {
 			color: #ccc;
 			color: #ccc;
 		}
 		}
 		.phcolor {
 		.phcolor {
 			color: #ccc;
 			color: #ccc;
+			text-align: right;
 		}
 		}
+		/* 生日文案、去设置靠右 */
 		.birthday-display {
 		.birthday-display {
+			flex: 1;
 			min-height: 80upx;
 			min-height: 80upx;
 			padding: 20upx;
 			padding: 20upx;
 			display: flex;
 			display: flex;
 			align-items: center;
 			align-items: center;
+			justify-content: flex-end;
 			font-size: 28upx;
 			font-size: 28upx;
+			text-align: right;
+		}
+	}
+	/* 头像行:右侧预览 + 更换入口,与昵称行对齐 */
+	.avatar-cell {
+		.avatar-wrap {
+			flex: 1;
+			display: flex;
+			justify-content: flex-end;
+			align-items: center;
+			min-height: 80upx;
+			padding: 10upx 20upx;
+		}
+		.avatar-btn {
+			display: flex;
+			align-items: center;
+			justify-content: flex-end;
+			margin: 0;
+			padding: 0;
+			background: transparent;
+			border: none;
+			line-height: 1;
+			&::after {
+				border: none;
+			}
+		}
+		.avatar-img {
+			width: 96upx;
+			height: 96upx;
+			border-radius: 50%;
+			background: #f5f5f5;
+		}
+		.avatar-tip {
+			margin-left: 16upx;
+			font-size: 26upx;
+			color: #007aff;
 		}
 		}
 	}
 	}
 	.btn-wrap {
 	.btn-wrap {