profile.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <template>
  2. <view class="profile-page">
  3. <uni-forms ref="formRef" :model="formModel" :rules="rules" validate-trigger="submit">
  4. <!-- 头像 -->
  5. <view class="avatar-section">
  6. <view class="avatar-container" @click="handleAvatarClick">
  7. <image
  8. :src="formModel.avatar || '/static/images/default-avatar.png'"
  9. class="avatar-image"
  10. mode="aspectFill"
  11. ></image>
  12. <view class="avatar-edit-mask">
  13. <uni-icons type="camera" size="24" color="#ffffff"></uni-icons>
  14. </view>
  15. </view>
  16. </view>
  17. <!-- 昵称 -->
  18. <uni-forms-item name="nickname" label="昵称" required>
  19. <uni-easyinput
  20. v-model="formModel.nickname"
  21. placeholder="请输入昵称"
  22. maxlength="20"
  23. />
  24. </uni-forms-item>
  25. <!-- 个人简介 -->
  26. <uni-forms-item name="bio" label="个人简介">
  27. <uni-easyinput
  28. v-model="formModel.bio"
  29. type="textarea"
  30. placeholder="介绍一下自己吧"
  31. maxlength="100"
  32. :height="100"
  33. show-word-limit
  34. />
  35. </uni-forms-item>
  36. <!-- 提交按钮 -->
  37. <u-button type="primary" :loading="loading" @click="submitForm" >
  38. 保存修改
  39. </u-button>
  40. <view class="mt-3" />
  41. <u-button type="primary" :plain="true" @click="navTo('/pages/user/update/password')">
  42. 修改密码
  43. </u-button>
  44. </uni-forms>
  45. </view>
  46. </template>
  47. <script setup lang="ts">
  48. import { ref, onMounted } from 'vue';
  49. import userApi from '@/api/auth/UserApi';
  50. import CommonContent from '@/api/CommonContent';
  51. import { useAuthStore } from '@/store/auth';
  52. import { navTo } from '@imengyu/imengyu-utils/dist/uniapp/PageAction';
  53. const authStore = useAuthStore();
  54. const formRef = ref<any>(null);
  55. const loading = ref(false);
  56. const uploading = ref(false);
  57. // 表单数据
  58. const formModel = ref({
  59. avatar: '',
  60. nickname: '',
  61. bio: '',
  62. });
  63. // 表单验证规则
  64. const rules = {
  65. nickname: {
  66. rules: [
  67. { required: true, errorMessage: '请输入昵称' },
  68. { minLength: 2, errorMessage: '昵称长度至少2个字符' },
  69. { maxLength: 20, errorMessage: '昵称长度最多20个字符' }
  70. ]
  71. },
  72. bio: {
  73. rules: [
  74. { maxLength: 100, errorMessage: '个人简介最多100个字符' }
  75. ]
  76. }
  77. };
  78. // 处理头像点击事件
  79. const handleAvatarClick = async () => {
  80. try {
  81. // 选择图片
  82. const chooseResult = await uni.chooseImage({
  83. count: 1,
  84. sizeType: ['compressed'],
  85. sourceType: ['album', 'camera'],
  86. });
  87. const tempFilePath = chooseResult.tempFilePaths[0];
  88. // 上传图片
  89. uploading.value = true;
  90. const uploadResult = await CommonContent.uploadFile(tempFilePath, 'image');
  91. // 更新头像并保存到服务器
  92. await updateAvatar(uploadResult.fullurl);
  93. } catch (error: any) {
  94. if (error.errMsg !== 'chooseImage:fail cancel') {
  95. uni.showToast({
  96. title: '头像更换失败',
  97. icon: 'none',
  98. duration: 2000
  99. });
  100. }
  101. } finally {
  102. uploading.value = false;
  103. }
  104. };
  105. // 更新头像到服务器
  106. const updateAvatar = async (avatarUrl: string) => {
  107. try {
  108. // 调用修改头像API
  109. await userApi.updateSystemUserInfo({
  110. avatar: avatarUrl
  111. });
  112. // 更新表单数据和store
  113. formModel.value.avatar = avatarUrl;
  114. if (authStore.userInfo) {
  115. authStore.userInfo.avatar = avatarUrl;
  116. }
  117. // 显示成功提示
  118. uni.showToast({
  119. title: '头像更新成功',
  120. icon: 'success',
  121. duration: 2000
  122. });
  123. } catch (error: any) {
  124. throw new Error(error?.message || '头像更新失败');
  125. }
  126. };
  127. // 页面加载时获取用户信息
  128. onMounted(() => {
  129. if (authStore.userInfo) {
  130. formModel.value.avatar = authStore.userInfo.avatar || '';
  131. formModel.value.nickname = authStore.userInfo.nickname || '';
  132. formModel.value.bio = (authStore.userInfo.intro || authStore.userInfo.bio || '') as string;
  133. }
  134. });
  135. // 提交表单
  136. const submitForm = async () => {
  137. // 表单验证
  138. const valid = await formRef.value?.validate();
  139. if (!valid) return;
  140. loading.value = true;
  141. try {
  142. // 调用修改个人信息API
  143. await userApi.updateSystemUserInfo({
  144. nickname: formModel.value.nickname,
  145. bio: formModel.value.bio
  146. });
  147. // 更新store中的用户信息
  148. if (authStore.userInfo) {
  149. authStore.userInfo.nickname = formModel.value.nickname;
  150. authStore.userInfo.avatar = formModel.value.avatar;
  151. authStore.userInfo.intro = formModel.value.bio;
  152. }
  153. // 显示成功提示
  154. uni.showToast({
  155. title: '个人信息更新成功',
  156. icon: 'success',
  157. duration: 2000
  158. });
  159. // 成功后返回上一页
  160. setTimeout(() => {
  161. uni.navigateBack();
  162. }, 2000);
  163. } catch (error: any) {
  164. // 显示错误提示
  165. uni.showToast({
  166. title: error?.message || '更新失败,请稍后重试',
  167. icon: 'none',
  168. duration: 2000
  169. });
  170. } finally {
  171. loading.value = false;
  172. }
  173. };
  174. </script>
  175. <style scoped>
  176. .profile-page {
  177. min-height: 100vh;
  178. background-color: #f5f5f5;
  179. padding: 16px;
  180. }
  181. .avatar-section {
  182. text-align: center;
  183. }
  184. .avatar-label {
  185. font-size: 14px;
  186. color: #333333;
  187. margin-bottom: 12px;
  188. font-weight: 500;
  189. text-align: left;
  190. }
  191. .avatar-container {
  192. position: relative;
  193. display: inline-block;
  194. margin-bottom: 8px;
  195. }
  196. .avatar-image {
  197. width: 100px;
  198. height: 100px;
  199. border-radius: 50%;
  200. border: 2px solid #e0e0e0;
  201. background-color: #f5f5f5;
  202. }
  203. .avatar-edit-mask {
  204. position: absolute;
  205. top: 0;
  206. left: 0;
  207. width: 100%;
  208. height: 100%;
  209. border-radius: 50%;
  210. background-color: rgba(0, 0, 0, 0.3);
  211. display: flex;
  212. justify-content: center;
  213. align-items: center;
  214. opacity: 0;
  215. transition: opacity 0.3s ease;
  216. }
  217. .avatar-container:hover .avatar-edit-mask {
  218. opacity: 1;
  219. }
  220. .avatar-hint {
  221. font-size: 12px;
  222. color: #007aff;
  223. margin-top: 4px;
  224. }
  225. /* 上传中遮罩 */
  226. .uploading-mask {
  227. position: fixed;
  228. top: 0;
  229. left: 0;
  230. width: 100%;
  231. height: 100%;
  232. background-color: rgba(0, 0, 0, 0.5);
  233. display: flex;
  234. justify-content: center;
  235. align-items: center;
  236. z-index: 9999;
  237. }
  238. .uploading-content {
  239. background-color: #ffffff;
  240. padding: 20px;
  241. border-radius: 8px;
  242. text-align: center;
  243. }
  244. .uploading-content uni-loading {
  245. margin-bottom: 10px;
  246. }
  247. uni-forms {
  248. background-color: #ffffff;
  249. border-radius: 8px;
  250. padding: 16px;
  251. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
  252. }
  253. uni-forms-item {
  254. margin-bottom: 20px;
  255. }
  256. uni-forms-item:last-child {
  257. margin-bottom: 0;
  258. }
  259. uni-easyinput {
  260. border-bottom: 1px solid #f0f0f0;
  261. padding: 8px 0;
  262. }
  263. uni-easyinput:focus {
  264. border-bottom-color: #007aff;
  265. }
  266. .submit-btn {
  267. margin-top: 32px;
  268. border-radius: 8px;
  269. height: 44px;
  270. font-size: 16px;
  271. }
  272. </style>