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

406 lines
10 KiB

10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
10 months ago
9 months ago
10 months ago
10 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months ago
9 months ago
9 months ago
10 months ago
10 months ago
10 months ago
10 months ago
10 months 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 => { 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. <button class="avatar-box" open-type="chooseAvatar" @chooseavatar="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. </button>
  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: '',
  90. avatar: '',
  91. newAvatar: ''
  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(res) {
  142. try {
  143. this.uploadStatus = { show: true, type: 'loading' }
  144. // 上传到OSS
  145. const url = await OSS.ossUpload(res.target.avatarUrl)
  146. console.log(url,'url');
  147. this.userInfo.avatar = url
  148. this.uploadStatus.type = 'success'
  149. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  150. } catch (error) {
  151. this.uploadStatus = { show: true, type: 'error' }
  152. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  153. }
  154. },
  155. saveProfile() {
  156. // 校验
  157. if (!this.userInfo.avatar) return uni.showToast({ title: '请上传头像', icon: 'none' })
  158. if (!this.userInfo.nickname) return uni.showToast({ title: '请填写昵称', icon: 'none' })
  159. if (!this.userInfo.phone) return uni.showToast({ title: '请填写手机号', icon: 'none' })
  160. this.uploadStatus = { show: true, type: 'loading' }
  161. console.log(this.userInfo,'this.userInfo');
  162. this.$api('updateInfo', {
  163. avatarUrl: this.userInfo.avatar,
  164. nickName: this.userInfo.nickname,
  165. phone: this.userInfo.phone
  166. }, res => {
  167. if (res.code === 200) {
  168. // this.uploadStatus.type = 'success'
  169. setTimeout(() => {
  170. // this.uploadStatus.show = false
  171. uni.$emit('refreshUserInfo')
  172. uni.showToast({ title: '保存成功', icon: 'success' })
  173. setTimeout(() => { uni.navigateBack() }, 1000)
  174. }, 1000)
  175. } else {
  176. this.uploadStatus.type = 'error'
  177. setTimeout(() => { this.uploadStatus.show = false }, 1000)
  178. }
  179. })
  180. }
  181. }
  182. }
  183. </script>
  184. <style lang="scss" scoped>
  185. .edit-profile {
  186. min-height: 100vh;
  187. background-color: #f5f5f5;
  188. position: fixed;
  189. top: 0;
  190. left: 0;
  191. right: 0;
  192. bottom: 0;
  193. display: flex;
  194. flex-direction: column;
  195. padding-bottom: 140rpx;
  196. box-sizing: border-box;
  197. }
  198. .nav-bar {
  199. display: flex;
  200. align-items: center;
  201. height: 88rpx;
  202. background: #fff;
  203. padding: 0 30rpx;
  204. position: fixed;
  205. top: 0;
  206. left: 0;
  207. right: 0;
  208. z-index: 100;
  209. width: 100vw;
  210. box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03);
  211. .back {
  212. padding: 20rpx;
  213. margin-left: -20rpx;
  214. display: flex;
  215. align-items: center;
  216. height: 88rpx;
  217. }
  218. .title {
  219. flex: 1;
  220. text-align: center;
  221. font-size: 34rpx;
  222. font-weight: 500;
  223. color: #222;
  224. letter-spacing: 0.5px;
  225. line-height: 88rpx;
  226. }
  227. .nav-bar-right {
  228. width: 44px;
  229. height: 88rpx;
  230. display: flex;
  231. align-items: center;
  232. justify-content: flex-end;
  233. }
  234. }
  235. .info-container {
  236. flex: 1;
  237. overflow-y: auto;
  238. padding: 0 40rpx 0;
  239. box-sizing: border-box;
  240. }
  241. .info-card {
  242. background: linear-gradient(180deg, #fff7e6 0%, #fff 100%);
  243. border-radius: 24rpx;
  244. box-shadow: 0 8rpx 32rpx rgba(60, 167, 250, 0.08);
  245. padding: 48rpx 40rpx 32rpx 40rpx;
  246. width: 100%;
  247. max-width: 700rpx;
  248. margin: 0 auto;
  249. position: relative;
  250. box-sizing: border-box;
  251. }
  252. .section-title {
  253. font-size: 36rpx;
  254. font-weight: bold;
  255. color: #222;
  256. margin-bottom: 24rpx;
  257. }
  258. .divider {
  259. border-bottom: 2rpx solid #f2f2f2;
  260. margin-bottom: 24rpx;
  261. }
  262. .info-item {
  263. margin-bottom: 24rpx;
  264. width: 100%;
  265. box-sizing: border-box;
  266. min-height: 60rpx;
  267. display: flex;
  268. flex-direction: column;
  269. justify-content: center;
  270. .label {
  271. font-size: 28rpx;
  272. color: #333;
  273. margin-bottom: 12rpx;
  274. display: block;
  275. }
  276. input {
  277. font-size: 32rpx;
  278. color: #222;
  279. width: 100%;
  280. padding: 16rpx 0;
  281. border: none;
  282. background: none;
  283. outline: none;
  284. box-sizing: border-box;
  285. height: 80rpx;
  286. line-height: 48rpx;
  287. vertical-align: middle;
  288. }
  289. .phone-input {
  290. display: flex;
  291. align-items: center;
  292. width: 100%;
  293. box-sizing: border-box;
  294. input {
  295. flex: 1;
  296. width: 100%;
  297. box-sizing: border-box;
  298. height: 80rpx;
  299. line-height: 48rpx;
  300. vertical-align: middle;
  301. }
  302. }
  303. }
  304. .avatar-section {
  305. .avatar-container {
  306. display: flex;
  307. gap: 20rpx;
  308. width: 100%;
  309. box-sizing: border-box;
  310. }
  311. .avatar-box, .new-avatar {
  312. // max-width: 120rpx;
  313. padding: 0;
  314. margin: 0;
  315. width: 150rpx;
  316. height: 120rpx;
  317. border-radius: 18rpx;
  318. background: #f5f5f5;
  319. display: flex;
  320. align-items: center;
  321. justify-content: center;
  322. // overflow: hidden;
  323. }
  324. .avatar-box image, .new-avatar {
  325. width: 100%;
  326. height: 100%;
  327. }
  328. }
  329. .save-button {
  330. position: fixed;
  331. left: 30rpx;
  332. right: 30rpx;
  333. bottom: calc(env(safe-area-inset-bottom) + 30rpx);
  334. height: 90rpx;
  335. background: linear-gradient(90deg, #ffd01e 0%, #ffb400 100%);
  336. border-radius: 45rpx;
  337. display: flex;
  338. align-items: center;
  339. justify-content: center;
  340. box-shadow: 0 4rpx 16rpx rgba(255, 149, 0, 0.08);
  341. z-index: 10;
  342. text {
  343. color: #fff;
  344. font-size: 32rpx;
  345. font-weight: 500;
  346. }
  347. }
  348. .upload-status {
  349. position: fixed;
  350. top: 0;
  351. left: 0;
  352. right: 0;
  353. bottom: 0;
  354. z-index: 999;
  355. display: flex;
  356. align-items: center;
  357. justify-content: center;
  358. .status-mask {
  359. position: absolute;
  360. top: 0;
  361. left: 0;
  362. right: 0;
  363. bottom: 0;
  364. background: rgba(0, 0, 0, 0.6);
  365. }
  366. .status-content {
  367. position: relative;
  368. width: 240rpx;
  369. height: 240rpx;
  370. background: rgba(0, 0, 0, 0.8);
  371. border-radius: 20rpx;
  372. display: flex;
  373. flex-direction: column;
  374. align-items: center;
  375. justify-content: center;
  376. gap: 20rpx;
  377. text {
  378. color: #fff;
  379. font-size: 28rpx;
  380. }
  381. .loading-icon {
  382. width: 60rpx;
  383. height: 60rpx;
  384. border: 4rpx solid #fff;
  385. border-top-color: transparent;
  386. border-radius: 50%;
  387. animation: spin 1s linear infinite;
  388. }
  389. }
  390. }
  391. @keyframes spin {
  392. from { transform: rotate(0deg); }
  393. to { transform: rotate(360deg); }
  394. }
  395. .safe-area {
  396. padding-bottom: env(safe-area-inset-bottom);
  397. }
  398. </style>