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

3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
2 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
6 days ago
3 weeks ago
6 days ago
3 weeks ago
3 weeks ago
6 days ago
6 days ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
6 days ago
6 days ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
3 weeks 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. <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: '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(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>