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

381 lines
10 KiB

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. <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">
  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. },
  106. onLoad() {
  107. const sysInfo = uni.getSystemInfoSync()
  108. this.statusBarHeight = sysInfo.statusBarHeight
  109. let navBarHeight = 44
  110. try {
  111. const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
  112. navBarHeight = menuButtonInfo.bottom + menuButtonInfo.top - sysInfo.statusBarHeight
  113. } catch (e) {}
  114. this.navBarHeight = navBarHeight
  115. this.navBarHeightRpx = Math.round(navBarHeight * 750 / sysInfo.windowWidth)
  116. // 获取个人信息
  117. if (uni.getStorageSync('token')) {
  118. this.$api('getUserByToken', {}, (res) => {
  119. if (res.code === 200 && res.result) {
  120. this.userInfo.nickname = res.result.nickName || ''
  121. this.userInfo.phone = res.result.phone || ''
  122. this.userInfo.avatar = res.result.headImage || ''
  123. }
  124. })
  125. }
  126. },
  127. methods: {
  128. async onRefresh() {
  129. // 模拟刷新数据
  130. await new Promise(resolve => setTimeout(resolve, 1000))
  131. this.stopPullRefresh()
  132. },
  133. goBack() {
  134. uni.navigateBack()
  135. },
  136. async chooseImage() {
  137. try {
  138. const res = await new Promise((resolve, reject) => {
  139. uni.chooseImage({
  140. count: 1,
  141. sizeType: ['compressed'],
  142. sourceType: ['album', 'camera'],
  143. success: resolve,
  144. fail: reject
  145. })
  146. })
  147. const tempFile = res.tempFilePaths[0]
  148. this.uploadStatus = { show: true, type: 'loading' }
  149. // 上传到OSS
  150. const url = await OSS.ossUpload(tempFile)
  151. this.userInfo.avatar = url
  152. this.uploadStatus.type = 'success'
  153. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  154. } catch (error) {
  155. this.uploadStatus = { show: true, type: 'error' }
  156. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  157. }
  158. },
  159. saveProfile() {
  160. // 校验
  161. if (!this.userInfo.avatar) return uni.showToast({ title: '请上传头像', icon: 'none' })
  162. if (!this.userInfo.nickname) return uni.showToast({ title: '请填写昵称', icon: 'none' })
  163. if (!this.userInfo.phone) return uni.showToast({ title: '请填写手机号', icon: 'none' })
  164. this.uploadStatus = { show: true, type: 'loading' }
  165. console.log(this.userInfo,'this.userInfo');
  166. this.$api('updateInfo', {
  167. avatarUrl: this.userInfo.avatar,
  168. nickName: this.userInfo.nickname,
  169. phone: this.userInfo.phone
  170. }, res => {
  171. if (res.code === 200) {
  172. this.uploadStatus.type = 'success'
  173. setTimeout(() => {
  174. this.uploadStatus.show = false
  175. uni.$emit('refreshUserInfo')
  176. uni.showToast({ title: '保存成功', icon: 'success' })
  177. setTimeout(() => { uni.navigateBack() }, 1000)
  178. }, 1000)
  179. } else {
  180. this.uploadStatus.type = 'error'
  181. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  182. }
  183. })
  184. }
  185. }
  186. }
  187. </script>
  188. <style lang="scss" scoped>
  189. .edit-profile {
  190. min-height: 100vh;
  191. background-color: #f5f5f5;
  192. }
  193. .nav-bar {
  194. display: flex;
  195. align-items: center;
  196. height: 88rpx;
  197. background: #fff;
  198. padding: 0 30rpx;
  199. position: fixed;
  200. top: 0;
  201. left: 0;
  202. right: 0;
  203. z-index: 100;
  204. width: 100vw;
  205. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03);
  206. .back {
  207. padding: 20rpx;
  208. margin-left: -20rpx;
  209. display: flex;
  210. align-items: center;
  211. height: 88rpx;
  212. }
  213. .title {
  214. flex: 1;
  215. text-align: center;
  216. font-size: 34rpx;
  217. font-weight: 500;
  218. color: #222;
  219. letter-spacing: 0.5px;
  220. line-height: 88rpx;
  221. }
  222. .nav-bar-right {
  223. width: 44px;
  224. height: 88rpx;
  225. display: flex;
  226. align-items: center;
  227. justify-content: flex-end;
  228. }
  229. }
  230. .info-container {
  231. margin-top: 48rpx;
  232. /* 移除左右padding,留白交给卡片自身 */
  233. }
  234. .info-card {
  235. background: linear-gradient(180deg, #fff7e6 0%, #fff 100%);
  236. border-radius: 24rpx;
  237. box-shadow: 0 8rpx 32rpx rgba(60, 167, 250, 0.08);
  238. padding: 48rpx 32rpx 32rpx 32rpx;
  239. width: 100%;
  240. max-width: 700rpx;
  241. margin: 0 auto;
  242. margin-top: 0;
  243. position: relative;
  244. }
  245. .section-title {
  246. font-size: 36rpx;
  247. font-weight: bold;
  248. color: #222;
  249. margin-bottom: 24rpx;
  250. }
  251. .divider {
  252. border-bottom: 2rpx solid #f2f2f2;
  253. margin-bottom: 24rpx;
  254. }
  255. .info-item {
  256. margin-bottom: 24rpx;
  257. .label {
  258. font-size: 28rpx;
  259. color: #333;
  260. margin-bottom: 12rpx;
  261. display: block;
  262. }
  263. input {
  264. font-size: 32rpx;
  265. color: #222;
  266. width: 100%;
  267. padding: 16rpx 0;
  268. border: none;
  269. background: none;
  270. outline: none;
  271. }
  272. .phone-input {
  273. display: flex;
  274. align-items: center;
  275. input {
  276. flex: 1;
  277. }
  278. }
  279. }
  280. .avatar-section {
  281. .avatar-container {
  282. display: flex;
  283. gap: 20rpx;
  284. }
  285. .avatar-box {
  286. width: 120rpx;
  287. height: 120rpx;
  288. background: #f5f5f5;
  289. border-radius: 18rpx;
  290. display: flex;
  291. align-items: center;
  292. justify-content: center;
  293. overflow: hidden;
  294. image {
  295. width: 100%;
  296. height: 100%;
  297. }
  298. }
  299. .new-avatar {
  300. width: 120rpx;
  301. height: 120rpx;
  302. border-radius: 18rpx;
  303. }
  304. }
  305. .save-button {
  306. position: fixed;
  307. left: 30rpx;
  308. right: 30rpx;
  309. bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  310. height: 90rpx;
  311. background: linear-gradient(90deg, #ffd01e 0%, #ffb400 100%);
  312. border-radius: 45rpx;
  313. display: flex;
  314. align-items: center;
  315. justify-content: center;
  316. box-shadow: 0 4rpx 16rpx rgba(255, 149, 0, 0.08);
  317. text {
  318. color: #fff;
  319. font-size: 32rpx;
  320. font-weight: 500;
  321. }
  322. }
  323. .upload-status {
  324. position: fixed;
  325. top: 0;
  326. left: 0;
  327. right: 0;
  328. bottom: 0;
  329. z-index: 999;
  330. display: flex;
  331. align-items: center;
  332. justify-content: center;
  333. .status-mask {
  334. position: absolute;
  335. top: 0;
  336. left: 0;
  337. right: 0;
  338. bottom: 0;
  339. background: rgba(0, 0, 0, 0.6);
  340. }
  341. .status-content {
  342. position: relative;
  343. width: 240rpx;
  344. height: 240rpx;
  345. background: rgba(0, 0, 0, 0.8);
  346. border-radius: 20rpx;
  347. display: flex;
  348. flex-direction: column;
  349. align-items: center;
  350. justify-content: center;
  351. gap: 20rpx;
  352. text {
  353. color: #fff;
  354. font-size: 28rpx;
  355. }
  356. .loading-icon {
  357. width: 60rpx;
  358. height: 60rpx;
  359. border: 4rpx solid #fff;
  360. border-top-color: transparent;
  361. border-radius: 50%;
  362. animation: spin 1s linear infinite;
  363. }
  364. }
  365. }
  366. @keyframes spin {
  367. from { transform: rotate(0deg); }
  368. to { transform: rotate(360deg); }
  369. }
  370. .safe-area {
  371. padding-bottom: env(safe-area-inset-bottom);
  372. }
  373. </style>