index.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <template>
  2. <div class="profile-page">
  3. <!-- 返回按钮 -->
  4. <div class="header">
  5. <van-icon name="arrow-left" @click="goBack" />
  6. <span>{{ $t('user.personalInfo') }}</span>
  7. <span></span>
  8. </div>
  9. <!-- 个人信息列表 -->
  10. <div class="info-list">
  11. <!-- 头像 -->
  12. <div class="info-item" @click="handleAvatarClick">
  13. <span class="label">{{ $t('user.avatar') }}</span>
  14. <div class="value">
  15. <img :src="userInfo?.avatar || defaultAvatar" class="avatar" />
  16. <van-icon name="arrow" />
  17. </div>
  18. </div>
  19. <!-- 昵称 -->
  20. <div class="info-item" @click="goEdit('nickname')">
  21. <span class="label">{{ $t('user.nickname') }}</span>
  22. <div class="value">
  23. <span>{{ userInfo?.nickname || '-' }}</span>
  24. <van-icon name="arrow" />
  25. </div>
  26. </div>
  27. <!-- 绑定手机 -->
  28. <div class="info-item" @click="goEdit('phone')">
  29. <span class="label">{{ $t('user.bindPhone') }}</span>
  30. <div class="value">
  31. <span>{{ formatPhone(userInfo?.phone) }}</span>
  32. <van-icon name="arrow" />
  33. </div>
  34. </div>
  35. <!-- 绑定邮箱 -->
  36. <div class="info-item" @click="goEdit('email')">
  37. <span class="label">{{ $t('user.bindEmail') }}</span>
  38. <div class="value">
  39. <span>{{ userInfo?.email || $t('common.no') }}</span>
  40. <van-icon name="arrow" />
  41. </div>
  42. </div>
  43. <!-- 绑定 Telegram -->
  44. <div class="info-item" @click="goEdit('telegram')">
  45. <span class="label">Bind Telegram</span>
  46. <div class="value">
  47. <span>{{ telegramAccount || $t('common.no') }}</span>
  48. <van-icon name="arrow" />
  49. </div>
  50. </div>
  51. <!-- 地区 -->
  52. <div class="info-item" @click="goEdit('region')">
  53. <span class="label">{{ $t('user.region') }}</span>
  54. <div class="value">
  55. <span>{{ userInfo?.region || '-' }}</span>
  56. <van-icon name="arrow" />
  57. </div>
  58. </div>
  59. <!-- 年龄 -->
  60. <div class="info-item" @click="goEdit('age')">
  61. <span class="label">{{ $t('user.age') }}</span>
  62. <div class="value">
  63. <span>{{ userInfo?.age || '-' }}</span>
  64. <van-icon name="arrow" />
  65. </div>
  66. </div>
  67. <!-- 性别 -->
  68. <div class="info-item" @click="goEdit('gender')">
  69. <span class="label">{{ $t('user.gender') }}</span>
  70. <div class="value">
  71. <span>{{ getGenderText() }}</span>
  72. <van-icon name="arrow" />
  73. </div>
  74. </div>
  75. <!-- 实名认证 -->
  76. <div class="info-item" @click="goEdit('realName')">
  77. <span class="label">{{ $t('user.realName') }}</span>
  78. <div class="value">
  79. <span :class="{ verified: userInfo?.realNameStatus === 1 }">
  80. {{ getRealNameStatus() }}
  81. </span>
  82. <van-icon name="arrow" />
  83. </div>
  84. </div>
  85. <!-- 修改密码 -->
  86. <div class="info-item" @click="goPassword">
  87. <span class="label">{{ $t('user.changePassword') }}</span>
  88. <div class="value">
  89. <van-icon name="arrow" />
  90. </div>
  91. </div>
  92. <!-- 收款账户 -->
  93. <div class="info-item" @click="goPaymentAccount">
  94. <span class="label">{{ $t('user.paymentAccount') }}</span>
  95. <div class="value">
  96. <span>{{ userInfo?.paymentAccountCount || 0 }}</span>
  97. <van-icon name="arrow" />
  98. </div>
  99. </div>
  100. </div>
  101. <!-- 头像上传 -->
  102. <van-uploader
  103. ref="uploaderRef"
  104. v-show="false"
  105. :after-read="afterAvatarRead"
  106. accept="image/*"
  107. />
  108. </div>
  109. </template>
  110. <script setup lang="ts">
  111. import { ref, computed, onMounted } from "vue";
  112. import { useRouter } from "vue-router";
  113. import { useI18n } from "vue-i18n";
  114. import { showToast } from "vant";
  115. import { useUserStore } from "@/store/modules/userStore";
  116. import { requestUpdateUserInfo, requestGetSocialList } from "@/api/user";
  117. import { uploadFileToOss } from "@/api/upload";
  118. import defaultAvatar from "@/assets/images/common/icon_avatar.svg";
  119. const telegramAccount = ref('');
  120. const { t } = useI18n();
  121. const router = useRouter();
  122. const userStore = useUserStore();
  123. const uploaderRef = ref<any>(null);
  124. const userInfo = computed(() => userStore.userInfo);
  125. const formatPhone = (phone: string | undefined) => {
  126. if (!phone) return t('common.no');
  127. if (phone.length > 7) {
  128. return phone.slice(0, 3) + '****' + phone.slice(-4);
  129. }
  130. return phone;
  131. };
  132. const getGenderText = () => {
  133. const gender = userInfo.value?.gender;
  134. if (gender === 1) return t('user.male');
  135. if (gender === 2) return t('user.female');
  136. return '-';
  137. };
  138. const getRealNameStatus = () => {
  139. const status = userInfo.value?.realNameStatus;
  140. if (status === 1) return t('task.completed');
  141. if (status === 2) return t('taskStatus.pendingReview');
  142. return t('common.no');
  143. };
  144. const handleAvatarClick = () => {
  145. uploaderRef.value?.chooseFile();
  146. };
  147. const afterAvatarRead = async (file: any) => {
  148. try {
  149. // 先上传图片获取URL
  150. const url = await uploadFileToOss(file.file);
  151. if (!url) {
  152. showToast('Upload failed');
  153. return;
  154. }
  155. // 再用URL更新头像
  156. const res = await requestUpdateUserInfo({ avatar: url });
  157. if (res.code === 200) {
  158. showToast(t('common.save') + ' ' + t('task.completed'));
  159. userStore.fetchUserInfo();
  160. }
  161. } catch (e) {
  162. showToast('Upload failed');
  163. }
  164. };
  165. const goBack = () => {
  166. router.back();
  167. };
  168. const goEdit = (type: string) => {
  169. router.push(`/profile/edit?type=${type}`);
  170. };
  171. const goPassword = () => {
  172. router.push('/password');
  173. };
  174. const goPaymentAccount = () => {
  175. router.push('/payment-account');
  176. };
  177. const loadTelegramBind = async () => {
  178. try {
  179. const res = await requestGetSocialList();
  180. if (res.code === 200 && res.data) {
  181. const tg = (res.data as any[]).find((item: any) => item.platform === 'telegram');
  182. if (tg) {
  183. telegramAccount.value = tg.account || '';
  184. }
  185. }
  186. } catch (e) {
  187. // ignore
  188. }
  189. };
  190. onMounted(() => {
  191. userStore.fetchUserInfo();
  192. loadTelegramBind();
  193. });
  194. </script>
  195. <style lang="scss" scoped>
  196. .profile-page {
  197. min-height: 100vh;
  198. background: var(--bg-primary, #121212);
  199. }
  200. .header {
  201. display: flex;
  202. align-items: center;
  203. justify-content: space-between;
  204. padding: 16px;
  205. color: var(--text-primary, #fff);
  206. font-size: 16px;
  207. font-weight: 600;
  208. .van-icon {
  209. font-size: 20px;
  210. }
  211. }
  212. .info-list {
  213. padding: 0 16px;
  214. .info-item {
  215. display: flex;
  216. align-items: center;
  217. justify-content: space-between;
  218. padding: 16px;
  219. background: var(--bg-card, rgba(255, 255, 255, 0.05));
  220. border-radius: 12px;
  221. margin-bottom: 12px;
  222. .label {
  223. font-size: 14px;
  224. color: var(--text-primary, rgba(255, 255, 255, 0.7));
  225. }
  226. .value {
  227. display: flex;
  228. align-items: center;
  229. gap: 8px;
  230. span {
  231. font-size: 14px;
  232. color: var(--text-primary, #fff);
  233. &.verified {
  234. color: #4caf50;
  235. }
  236. }
  237. .avatar {
  238. width: 40px;
  239. height: 40px;
  240. border-radius: 50%;
  241. object-fit: cover;
  242. }
  243. .van-icon {
  244. color: var(--text-tertiary, rgba(255, 255, 255, 0.3));
  245. }
  246. }
  247. }
  248. }
  249. </style>