图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
5.8 KiB

2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
1 month ago
2 months ago
  1. <template>
  2. <view class="user-info-page">
  3. <view class="user-info-section">
  4. <!-- 点击获取头像 -->
  5. <button open-type="chooseAvatar" @chooseavatar="onChooseAvatar" class="avatar-btn">
  6. <image v-if="avatarUrl" :src="avatarUrl" class="avatar-img"></image>
  7. <image v-else src="@/static/images/logo.jpg" class="avatar-img"></image>
  8. <text class="tip-text">点击更换头像</text>
  9. </button>
  10. </view>
  11. <!-- 昵称 -->
  12. <view class="form-item">
  13. <text class="label">昵称</text>
  14. <input
  15. class="input"
  16. type="nickname"
  17. v-model="nickname"
  18. placeholder="请输入昵称"
  19. />
  20. <uni-icons type="right" size="18" color="#ccc"></uni-icons>
  21. </view>
  22. <!-- 保存按钮 -->
  23. <view class="save-box">
  24. <button class="save-btn" @click="saveUserInfo">保存修改</button>
  25. </view>
  26. </view>
  27. </template>
  28. <script>
  29. import config from '@/utils/config'
  30. import { FetchBindRead } from '@/api/user';
  31. const USER_KEY = 'user-info';
  32. export default {
  33. data() {
  34. return {
  35. nickname: '',
  36. avatarUrl: '', // 仅存放 base64,用于显示
  37. imgId: '', // 仅用于提交后端
  38. saving: false // 防重复提交
  39. };
  40. },
  41. onLoad() {
  42. this.loadUserInfo();
  43. },
  44. methods:{
  45. // 加载用户信息
  46. async loadUserInfo() {
  47. const userInfo = uni.getStorageSync(USER_KEY) || {};
  48. this.nickname = userInfo.nickname || '';
  49. const avatarId = userInfo.avatarId || '';
  50. this.imgId = avatarId;
  51. if (avatarId) {
  52. try {
  53. uni.showLoading({ title: '加载头像中...' });
  54. const url = `${config.baseUrl}/api/fileRelevant/getImg?imgType=5&imgId=${avatarId}`;
  55. this.avatarUrl = await this.urlToBase64(url);
  56. } catch (err) {
  57. console.error('头像加载失败', err);
  58. this.avatarUrl = '';
  59. } finally {
  60. uni.hideLoading();
  61. }
  62. } else {
  63. this.avatarUrl = '';
  64. }
  65. },
  66. // 选择头像 + 上传 + 转base64预览
  67. async onChooseAvatar(e) {
  68. const tempFilePath = e.detail.avatarUrl;
  69. uni.showLoading({ title: '上传中...' });
  70. try {
  71. // 上传头像
  72. const uploadRes = await new Promise((resolve, reject) => {
  73. uni.uploadFile({
  74. url: config.baseUrl + '/api/fileRelevant/uploadWxAvatarImg',
  75. filePath: tempFilePath,
  76. name: 'file',
  77. success: resolve,
  78. fail: reject
  79. });
  80. });
  81. const resData = JSON.parse(uploadRes.data);
  82. const imgId = resData.data;
  83. this.imgId = imgId;
  84. // 转base64显示
  85. const url = `${config.baseUrl}/api/fileRelevant/getImg?imgType=5&imgId=${imgId}`;
  86. this.avatarUrl = await this.urlToBase64(url);
  87. uni.showToast({ title: '头像上传成功', icon: 'success' });
  88. } catch (err) {
  89. console.error(err);
  90. uni.showToast({ title: '头像上传失败', icon: 'none' });
  91. } finally {
  92. uni.hideLoading();
  93. }
  94. },
  95. // 最简稳定版:图片链接转base64
  96. urlToBase64(url) {
  97. return new Promise((resolve, reject) => {
  98. uni.request({
  99. url,
  100. method: 'GET',
  101. responseType: 'arraybuffer',
  102. success: (res) => {
  103. try {
  104. const base64 = uni.arrayBufferToBase64(res.data);
  105. resolve(`data:image/jpeg;base64,${base64}`);
  106. } catch (e) {
  107. reject(e);
  108. }
  109. },
  110. fail: reject
  111. });
  112. });
  113. },
  114. // 保存(防重复提交 + 同步缓存)
  115. async saveUserInfo() {
  116. if (this.saving) return;
  117. if (!this.nickname.trim()) {
  118. uni.showToast({ title: '请输入昵称', icon: 'none' });
  119. return;
  120. }
  121. this.saving = true;
  122. uni.showLoading({ title: '保存中...' });
  123. try {
  124. const openId = uni.getStorageSync('wx_login_code') || '';
  125. const params = {
  126. avatar: this.imgId,
  127. libcode: config.LIB_CODE,
  128. nickname: this.nickname,
  129. openid: openId
  130. };
  131. await FetchBindRead(params);
  132. // 保存到缓存
  133. uni.setStorageSync(USER_KEY, {
  134. nickname: this.nickname,
  135. avatarId: this.imgId
  136. });
  137. uni.showToast({ title: '保存成功', icon: 'success' });
  138. setTimeout(() => {
  139. uni.navigateBack();
  140. }, 1500);
  141. } catch (error) {
  142. console.error(error);
  143. uni.showToast({ title: '保存失败', icon: 'none' });
  144. } finally {
  145. uni.hideLoading();
  146. this.saving = false;
  147. }
  148. }
  149. }
  150. };
  151. </script>
  152. <style lang="scss" scoped>
  153. .user-info-page {
  154. background-color: #f5f5f5;
  155. min-height: 100vh;
  156. padding: 20rpx;
  157. box-sizing: border-box;
  158. }
  159. .user-info-section {
  160. background: #fff;
  161. padding: 40rpx 0;
  162. border-radius: 12rpx;
  163. margin-bottom: 20rpx;
  164. display: flex;
  165. flex-direction: column;
  166. align-items: center;
  167. }
  168. .avatar-btn {
  169. background: transparent;
  170. display: flex;
  171. flex-direction: column;
  172. align-items: center;
  173. &::after {
  174. border: none;
  175. }
  176. }
  177. .avatar-img {
  178. width: 120rpx;
  179. height: 120rpx;
  180. border-radius: 50%;
  181. object-fit: cover;
  182. margin-bottom: 10rpx;
  183. }
  184. .tip-text {
  185. font-size: 26rpx;
  186. color: #999;
  187. }
  188. .form-item {
  189. background-color: #fff;
  190. display: flex;
  191. justify-content: space-between;
  192. align-items: center;
  193. padding: 0 30rpx;
  194. height: 88rpx;
  195. border-radius: 12rpx;
  196. margin-bottom: 20rpx;
  197. }
  198. .label {
  199. font-size: 30rpx;
  200. color: #333;
  201. width: 120rpx;
  202. }
  203. .input {
  204. flex: 1;
  205. font-size: 30rpx;
  206. color: #333;
  207. text-align: right;
  208. }
  209. .save-box {
  210. margin-top: 60rpx;
  211. padding: 0 20rpx;
  212. }
  213. .save-btn {
  214. height: 88rpx;
  215. line-height: 88rpx;
  216. background-color: #007aff;
  217. color: #fff;
  218. border-radius: 44rpx;
  219. font-size: 32rpx;
  220. }
  221. </style>