profile.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <FlexCol :padding="30">
  3. <Form
  4. ref="formRef"
  5. :model="formModel"
  6. :rules="rules"
  7. validateTrigger="submit"
  8. :labelWidth="140"
  9. >
  10. <!-- 头像 -->
  11. <view class="avatar-section">
  12. <view class="avatar-container" @click="handleAvatarClick">
  13. <image
  14. :src="formModel.avatar || DefaultAvatar"
  15. class="avatar-image"
  16. mode="aspectFill"
  17. ></image>
  18. </view>
  19. </view>
  20. <Field name="nickname" label="昵称" placeholder="请输入昵称" required />
  21. <Field name="bio" multiline label="个人简介" placeholder="输入个人简介" :inputStyle="{width: '240px'}" />
  22. </Form>
  23. <Height :height="40" />
  24. <Button type="primary" :loading="loading" @click="submitForm" >
  25. 保存修改
  26. </Button>
  27. <Height :height="20" />
  28. <Button type="primary" :plain="true" @click="navTo('/pages/user/update/password')">
  29. 修改密码
  30. </Button>
  31. </FlexCol>
  32. </template>
  33. <script setup lang="ts">
  34. import { ref, onMounted } from 'vue';
  35. import { useAuthStore } from '@/store/auth';
  36. import UserApi from '@/api/auth/UserApi';
  37. import DefaultAvatar from '/static/images/home/UserHead.png';
  38. import CommonContent from '@/api/CommonContent';
  39. import Form from '@/components/form/Form.vue';
  40. import Field from '@/components/form/Field.vue';
  41. import FlexCol from '@/components/layout/FlexCol.vue';
  42. import Button from '@/components/basic/Button.vue';
  43. import Height from '@/components/layout/space/Height.vue';
  44. import type { Rules } from 'async-validator';
  45. import { navTo } from '@/components/utils/PageAction';
  46. const authStore = useAuthStore();
  47. const formRef = ref<any>(null);
  48. const loading = ref(false);
  49. const uploading = ref(false);
  50. const formModel = ref({
  51. avatar: '',
  52. nickname: '',
  53. bio: '',
  54. });
  55. const rules : Rules = {
  56. nickname: [
  57. { required: true, message: '请输入昵称' },
  58. { min: 2, message: '昵称长度至少2个字符' },
  59. { max: 20, message: '昵称长度最多20个字符' }
  60. ],
  61. bio: [
  62. { max: 100, message: '个人简介最多100个字符' }
  63. ],
  64. };
  65. // 处理头像点击事件
  66. const handleAvatarClick = async () => {
  67. try {
  68. // 选择图片
  69. const chooseResult = await uni.chooseImage({
  70. count: 1,
  71. sizeType: ['compressed'],
  72. sourceType: ['album', 'camera'],
  73. });
  74. const tempFilePath = chooseResult.tempFilePaths[0];
  75. // 上传图片
  76. uploading.value = true;
  77. const uploadResult = await CommonContent.uploadFile(tempFilePath, 'image');
  78. // 更新头像并保存到服务器
  79. await updateAvatar(uploadResult.fullurl);
  80. } catch (error: any) {
  81. if (error.errMsg !== 'chooseImage:fail cancel') {
  82. uni.showToast({
  83. title: '头像更换失败',
  84. icon: 'none',
  85. duration: 2000
  86. });
  87. }
  88. } finally {
  89. uploading.value = false;
  90. }
  91. };
  92. // 更新头像到服务器
  93. const updateAvatar = async (avatarUrl: string) => {
  94. try {
  95. await UserApi.updateSystemUserInfo({
  96. avatar: avatarUrl
  97. });
  98. formModel.value.avatar = avatarUrl;
  99. if (authStore.userInfo) {
  100. authStore.userInfo.avatar = avatarUrl;
  101. authStore.saveLoginState();
  102. }
  103. uni.showToast({
  104. title: '头像更新成功',
  105. icon: 'success',
  106. duration: 2000
  107. });
  108. } catch (error: any) {
  109. throw new Error(error?.message || '头像更新失败');
  110. }
  111. };
  112. onMounted(() => {
  113. console.log(authStore.userInfo);
  114. if (authStore.userInfo) {
  115. formModel.value.avatar = authStore.userInfo.avatar || '';
  116. formModel.value.nickname = authStore.userInfo.nickname || '';
  117. formModel.value.bio = (authStore.userInfo.intro || authStore.userInfo.bio || '') as string;
  118. }
  119. });
  120. // 提交表单
  121. const submitForm = async () => {
  122. try {
  123. await formRef.value?.validate();
  124. } catch {
  125. return;
  126. }
  127. loading.value = true;
  128. try {
  129. await UserApi.updateSystemUserInfo({
  130. nickname: formModel.value.nickname,
  131. bio: formModel.value.bio
  132. });
  133. if (authStore.userInfo) {
  134. authStore.userInfo.nickname = formModel.value.nickname;
  135. authStore.userInfo.avatar = formModel.value.avatar;
  136. authStore.userInfo.intro = formModel.value.bio;
  137. }
  138. uni.showToast({
  139. title: '个人信息更新成功',
  140. icon: 'success',
  141. duration: 2000
  142. });
  143. setTimeout(() => {
  144. uni.navigateBack();
  145. }, 2000);
  146. } catch (error: any) {
  147. uni.showToast({
  148. title: error?.message || '更新失败,请稍后重试',
  149. icon: 'none',
  150. duration: 2000
  151. });
  152. } finally {
  153. loading.value = false;
  154. }
  155. };
  156. </script>
  157. <style scoped>
  158. .avatar-section {
  159. text-align: center;
  160. }
  161. .avatar-label {
  162. font-size: 14px;
  163. color: #333333;
  164. margin-bottom: 12px;
  165. font-weight: 500;
  166. text-align: left;
  167. }
  168. .avatar-container {
  169. position: relative;
  170. display: inline-block;
  171. margin-bottom: 8px;
  172. }
  173. .avatar-image {
  174. width: 100px;
  175. height: 100px;
  176. border-radius: 50%;
  177. border: 2px solid #e0e0e0;
  178. background-color: #f5f5f5;
  179. }
  180. .avatar-hint {
  181. font-size: 12px;
  182. color: #007aff;
  183. margin-top: 4px;
  184. }
  185. /* 上传中遮罩 */
  186. .uploading-mask {
  187. position: fixed;
  188. top: 0;
  189. left: 0;
  190. width: 100%;
  191. height: 100%;
  192. background-color: rgba(0, 0, 0, 0.5);
  193. display: flex;
  194. justify-content: center;
  195. align-items: center;
  196. z-index: 9999;
  197. }
  198. .uploading-content {
  199. background-color: #ffffff;
  200. padding: 20px;
  201. border-radius: 8px;
  202. text-align: center;
  203. }
  204. .uploading-content uni-loading {
  205. margin-bottom: 10px;
  206. }
  207. </style>