profile.vue 5.5 KB

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