爱简收旧衣按件回收前端代码仓库
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.

413 lines
11 KiB

1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
5 days ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
  1. <template>
  2. <view class="edit-profile safe-area" :style="{paddingTop: navBarHeightRpx + 'rpx'}">
  3. <!-- 顶部导航栏 -->
  4. <view class="nav-bar" :style="{height: (statusBarHeight + 88) + 'rpx', paddingTop: statusBarHeight + 'px'}">
  5. <view class="back" @tap="goBack">
  6. <uni-icons type="left" size="20" color="#222"></uni-icons>
  7. </view>
  8. <text class="title">修改信息</text>
  9. <view class="nav-bar-right"></view>
  10. </view>
  11. <!-- 个人信息表单 -->
  12. <view class="info-container" :style="infoContainerStyle">
  13. <view class="info-card">
  14. <text class="section-title">个人信息</text>
  15. <view class="divider"></view>
  16. <!-- 昵称 -->
  17. <view class="info-item">
  18. <text class="label">昵称</text>
  19. <input type="text" v-model="userInfo.nickname" placeholder="请输入昵称" />
  20. </view>
  21. <view class="divider"></view>
  22. <!-- 电话 -->
  23. <view class="info-item">
  24. <text class="label">电话</text>
  25. <view class="phone-input">
  26. <input
  27. type="text"
  28. :value="showPhone ? userInfo.phone : maskedPhone"
  29. :readonly="!showPhone"
  30. placeholder="请输入手机号"
  31. @input="e => { if (showPhone) userInfo.phone = e.detail.value }"
  32. />
  33. <uni-icons :type="showPhone ? 'eye' : 'eye-slash'" size="20" color="#999" @tap="showPhone = !showPhone"></uni-icons>
  34. </view>
  35. </view>
  36. <view class="divider"></view>
  37. <!-- 头像 -->
  38. <view class="info-item avatar-section">
  39. <text class="label">头像</text>
  40. <view class="avatar-container">
  41. <view class="avatar-box" @tap="chooseImage">
  42. <uni-icons v-if="!userInfo.avatar" type="reload" size="24" color="#999"></uni-icons>
  43. <image v-else :src="userInfo.avatar" mode="aspectFill"></image>
  44. </view>
  45. <image v-if="userInfo.newAvatar" :src="userInfo.newAvatar" mode="aspectFill" class="new-avatar"></image>
  46. </view>
  47. </view>
  48. </view>
  49. </view>
  50. <!-- 头像上传状态提示 -->
  51. <view class="upload-status" v-if="uploadStatus.show">
  52. <view class="status-mask"></view>
  53. <view class="status-content">
  54. <!-- 加载中 -->
  55. <template v-if="uploadStatus.type === 'loading'">
  56. <view class="loading-icon"></view>
  57. <text>正在加载修改</text>
  58. </template>
  59. <!-- 成功 -->
  60. <template v-else-if="uploadStatus.type === 'success'">
  61. <uni-icons type="checkmark" size="24" color="#fff"></uni-icons>
  62. <text>修改成功</text>
  63. </template>
  64. <!-- 失败 -->
  65. <template v-else-if="uploadStatus.type === 'error'">
  66. <uni-icons type="closeempty" size="24" color="#fff"></uni-icons>
  67. <text>修改失败</text>
  68. </template>
  69. </view>
  70. </view>
  71. <!-- 保存按钮 -->
  72. <view class="save-button" @tap="saveProfile">
  73. <text>保存</text>
  74. </view>
  75. </view>
  76. </template>
  77. <script>
  78. import pullRefreshMixin from '@/pages/mixins/pullRefreshMixin.js'
  79. import OSS from '@/utils/oss-upload/oss/index.js'
  80. export default {
  81. mixins: [pullRefreshMixin],
  82. data() {
  83. return {
  84. statusBarHeight: 0,
  85. navBarHeight: 44, // px
  86. navBarHeightRpx: 88, // rpx
  87. userInfo: {
  88. nickname: '吴彦谋',
  89. phone: '15888977617',
  90. avatar: '',
  91. newAvatar: '/static/logo.png'
  92. },
  93. uploadStatus: {
  94. show: false,
  95. type: 'loading' // loading, success, error
  96. },
  97. showPhone: false
  98. }
  99. },
  100. computed: {
  101. maskedPhone() {
  102. // 返回和手机号等长的点点
  103. return this.userInfo.phone ? '●'.repeat(this.userInfo.phone.length) : ''
  104. },
  105. infoContainerStyle() {
  106. return {
  107. marginTop: this.navBarHeightRpx + 'rpx'
  108. }
  109. }
  110. },
  111. onLoad() {
  112. const sysInfo = uni.getSystemInfoSync()
  113. this.statusBarHeight = sysInfo.statusBarHeight
  114. let navBarHeight = 44
  115. try {
  116. const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
  117. navBarHeight = menuButtonInfo.bottom + menuButtonInfo.top - sysInfo.statusBarHeight
  118. } catch (e) {}
  119. this.navBarHeight = navBarHeight
  120. this.navBarHeightRpx = Math.round(navBarHeight * 750 / sysInfo.windowWidth)
  121. // 获取个人信息
  122. if (uni.getStorageSync('token')) {
  123. this.$api('getUserByToken', {}, (res) => {
  124. if (res.code === 200 && res.result) {
  125. this.userInfo.nickname = res.result.nickName || ''
  126. this.userInfo.phone = res.result.phone || ''
  127. this.userInfo.avatar = res.result.headImage || ''
  128. }
  129. })
  130. }
  131. },
  132. methods: {
  133. async onRefresh() {
  134. // 模拟刷新数据
  135. await new Promise(resolve => setTimeout(resolve, 1000))
  136. uni.stopPullRefresh()
  137. },
  138. goBack() {
  139. uni.navigateBack()
  140. },
  141. async chooseImage() {
  142. try {
  143. const res = await new Promise((resolve, reject) => {
  144. uni.chooseImage({
  145. count: 1,
  146. sizeType: ['compressed'],
  147. sourceType: ['album', 'camera'],
  148. success: resolve,
  149. fail: reject
  150. })
  151. })
  152. const tempFile = res.tempFilePaths[0]
  153. this.uploadStatus = { show: true, type: 'loading' }
  154. // 上传到OSS
  155. const url = await OSS.ossUpload(tempFile)
  156. this.userInfo.avatar = url
  157. this.uploadStatus.type = 'success'
  158. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  159. } catch (error) {
  160. this.uploadStatus = { show: true, type: 'error' }
  161. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  162. }
  163. },
  164. saveProfile() {
  165. // 校验
  166. if (!this.userInfo.avatar) return uni.showToast({ title: '请上传头像', icon: 'none' })
  167. if (!this.userInfo.nickname) return uni.showToast({ title: '请填写昵称', icon: 'none' })
  168. if (!this.userInfo.phone) return uni.showToast({ title: '请填写手机号', icon: 'none' })
  169. this.uploadStatus = { show: true, type: 'loading' }
  170. console.log(this.userInfo,'this.userInfo');
  171. this.$api('updateInfo', {
  172. avatarUrl: this.userInfo.avatar,
  173. nickName: this.userInfo.nickname,
  174. phone: this.userInfo.phone
  175. }, res => {
  176. if (res.code === 200) {
  177. this.uploadStatus.type = 'success'
  178. setTimeout(() => {
  179. this.uploadStatus.show = false
  180. uni.$emit('refreshUserInfo')
  181. uni.showToast({ title: '保存成功', icon: 'success' })
  182. setTimeout(() => { uni.navigateBack() }, 1000)
  183. }, 1000)
  184. } else {
  185. this.uploadStatus.type = 'error'
  186. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  187. }
  188. })
  189. }
  190. }
  191. }
  192. </script>
  193. <style lang="scss" scoped>
  194. .edit-profile {
  195. min-height: 100vh;
  196. background-color: #f5f5f5;
  197. position: fixed;
  198. top: 0;
  199. left: 0;
  200. right: 0;
  201. bottom: 0;
  202. display: flex;
  203. flex-direction: column;
  204. padding-bottom: 140rpx;
  205. box-sizing: border-box;
  206. }
  207. .nav-bar {
  208. display: flex;
  209. align-items: center;
  210. height: 88rpx;
  211. background: #fff;
  212. padding: 0 30rpx;
  213. position: fixed;
  214. top: 0;
  215. left: 0;
  216. right: 0;
  217. z-index: 100;
  218. width: 100vw;
  219. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03);
  220. .back {
  221. padding: 20rpx;
  222. margin-left: -20rpx;
  223. display: flex;
  224. align-items: center;
  225. height: 88rpx;
  226. }
  227. .title {
  228. flex: 1;
  229. text-align: center;
  230. font-size: 34rpx;
  231. font-weight: 500;
  232. color: #222;
  233. letter-spacing: 0.5px;
  234. line-height: 88rpx;
  235. }
  236. .nav-bar-right {
  237. width: 44px;
  238. height: 88rpx;
  239. display: flex;
  240. align-items: center;
  241. justify-content: flex-end;
  242. }
  243. }
  244. .info-container {
  245. flex: 1;
  246. overflow-y: auto;
  247. padding: 0 40rpx 0;
  248. box-sizing: border-box;
  249. }
  250. .info-card {
  251. background: linear-gradient(180deg, #fff7e6 0%, #fff 100%);
  252. border-radius: 24rpx;
  253. box-shadow: 0 8rpx 32rpx rgba(60, 167, 250, 0.08);
  254. padding: 48rpx 40rpx 32rpx 40rpx;
  255. width: 100%;
  256. max-width: 700rpx;
  257. margin: 0 auto;
  258. position: relative;
  259. box-sizing: border-box;
  260. }
  261. .section-title {
  262. font-size: 36rpx;
  263. font-weight: bold;
  264. color: #222;
  265. margin-bottom: 24rpx;
  266. }
  267. .divider {
  268. border-bottom: 2rpx solid #f2f2f2;
  269. margin-bottom: 24rpx;
  270. }
  271. .info-item {
  272. margin-bottom: 24rpx;
  273. width: 100%;
  274. box-sizing: border-box;
  275. min-height: 60rpx;
  276. display: flex;
  277. flex-direction: column;
  278. justify-content: center;
  279. .label {
  280. font-size: 28rpx;
  281. color: #333;
  282. margin-bottom: 12rpx;
  283. display: block;
  284. }
  285. input {
  286. font-size: 32rpx;
  287. color: #222;
  288. width: 100%;
  289. padding: 16rpx 0;
  290. border: none;
  291. background: none;
  292. outline: none;
  293. box-sizing: border-box;
  294. height: 80rpx;
  295. line-height: 48rpx;
  296. vertical-align: middle;
  297. }
  298. .phone-input {
  299. display: flex;
  300. align-items: center;
  301. width: 100%;
  302. box-sizing: border-box;
  303. input {
  304. flex: 1;
  305. width: 100%;
  306. box-sizing: border-box;
  307. height: 80rpx;
  308. line-height: 48rpx;
  309. vertical-align: middle;
  310. }
  311. }
  312. }
  313. .avatar-section {
  314. .avatar-container {
  315. display: flex;
  316. gap: 20rpx;
  317. width: 100%;
  318. box-sizing: border-box;
  319. }
  320. .avatar-box, .new-avatar {
  321. max-width: 120rpx;
  322. width: 120rpx;
  323. height: 120rpx;
  324. border-radius: 18rpx;
  325. background: #f5f5f5;
  326. display: flex;
  327. align-items: center;
  328. justify-content: center;
  329. overflow: hidden;
  330. }
  331. .avatar-box image, .new-avatar {
  332. width: 100%;
  333. height: 100%;
  334. }
  335. }
  336. .save-button {
  337. position: fixed;
  338. left: 30rpx;
  339. right: 30rpx;
  340. bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  341. height: 90rpx;
  342. background: linear-gradient(90deg, #ffd01e 0%, #ffb400 100%);
  343. border-radius: 45rpx;
  344. display: flex;
  345. align-items: center;
  346. justify-content: center;
  347. box-shadow: 0 4rpx 16rpx rgba(255, 149, 0, 0.08);
  348. z-index: 10;
  349. text {
  350. color: #fff;
  351. font-size: 32rpx;
  352. font-weight: 500;
  353. }
  354. }
  355. .upload-status {
  356. position: fixed;
  357. top: 0;
  358. left: 0;
  359. right: 0;
  360. bottom: 0;
  361. z-index: 999;
  362. display: flex;
  363. align-items: center;
  364. justify-content: center;
  365. .status-mask {
  366. position: absolute;
  367. top: 0;
  368. left: 0;
  369. right: 0;
  370. bottom: 0;
  371. background: rgba(0, 0, 0, 0.6);
  372. }
  373. .status-content {
  374. position: relative;
  375. width: 240rpx;
  376. height: 240rpx;
  377. background: rgba(0, 0, 0, 0.8);
  378. border-radius: 20rpx;
  379. display: flex;
  380. flex-direction: column;
  381. align-items: center;
  382. justify-content: center;
  383. gap: 20rpx;
  384. text {
  385. color: #fff;
  386. font-size: 28rpx;
  387. }
  388. .loading-icon {
  389. width: 60rpx;
  390. height: 60rpx;
  391. border: 4rpx solid #fff;
  392. border-top-color: transparent;
  393. border-radius: 50%;
  394. animation: spin 1s linear infinite;
  395. }
  396. }
  397. }
  398. @keyframes spin {
  399. from { transform: rotate(0deg); }
  400. to { transform: rotate(360deg); }
  401. }
  402. .safe-area {
  403. padding-bottom: env(safe-area-inset-bottom);
  404. }
  405. </style>