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

618 lines
18 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <view class="container">
  3. <!-- 顶部导航栏 -->
  4. <view class="nav-bar" :style="{paddingTop: statusBarHeight + 'px'}">
  5. <view class="back" @tap="goBack">
  6. <uni-icons type="left" size="20" color="#fff"></uni-icons>
  7. </view>
  8. <text class="nav-title">钱包流水</text>
  9. <view class="nav-icons">
  10. <!-- 占位元素保持布局对称 -->
  11. </view>
  12. </view>
  13. <!-- banner -->
  14. <view class="banner"
  15. :style="{ marginTop: 'calc(150rpx)', height: (bannerBaseHeight + statusBarHeight + 88) + 'rpx' }">
  16. <swiper :indicator-dots="false" :autoplay="true" :interval="3000" :duration="500" circular
  17. style="width: 100%; height: 100%;">
  18. <swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
  19. <video v-if="item.type == 1" :src="item.voUrl" autoplay muted loop :controls="false"
  20. :show-play-btn="false" :show-center-play-btn="false" object-fit="cover"
  21. style="width: 100%; height: 100%;"></video>
  22. <image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" />
  23. </swiper-item>
  24. </swiper>
  25. </view>
  26. <!-- 账户余额 -->
  27. <view class="balance-card">
  28. <view class="balance-info">
  29. <text class="label">账户</text>
  30. <view class="amount">
  31. <text class="symbol">¥</text>
  32. <text class="value">{{ userInfo.money || '0.00' }}</text>
  33. </view>
  34. </view>
  35. <button class="withdraw-btn" @tap="withdraw">提现</button>
  36. </view>
  37. <!-- 记录切换标签 -->
  38. <view class="record-tabs">
  39. <view class="tab-item" :class="{ active: currentTab === 'settlement' }" @tap="switchTab('settlement')">
  40. 结算日志
  41. </view>
  42. <view class="tab-item" :class="{ active: currentTab === 'withdrawal' }" @tap="switchTab('withdrawal')">
  43. 提现记录
  44. </view>
  45. </view>
  46. <!-- 记录列表 -->
  47. <view class="record-item" v-for="(item, index) in recordList" :key="index">
  48. <view class="record-info">
  49. <view class="record-left">
  50. <text class="type">{{ item.title || (currentTab === 'settlement' ? '结算记录' : '提现记录') }}</text>
  51. <!-- 结算记录显示状态 -->
  52. <view class="status" v-if="currentTab === 'settlement' && item.state === 0">
  53. <text class="status-text">24小时后结算</text>
  54. </view>
  55. <!-- 提现记录显示时间 -->
  56. <text v-if="currentTab === 'withdrawal'" class="date">{{ formatDate(item.createTime) }}</text>
  57. </view>
  58. <view class="record-right">
  59. <text class="amount">¥{{ item.money || '0.00' }}</text>
  60. <!-- 结算记录显示时间 -->
  61. <text v-if="currentTab === 'settlement'" class="date">{{ formatDate(item.createTime) }}</text>
  62. <!-- 提现记录显示操作按钮 -->
  63. <template v-if="currentTab === 'withdrawal'">
  64. <button v-if="item.state === 0" class="withdraw-btn" @tap="receiveWithdrawal(item, index)">
  65. 提现
  66. </button>
  67. <text v-else class="received-text">已到账</text>
  68. </template>
  69. </view>
  70. </view>
  71. </view>
  72. <!-- 初始加载状态 -->
  73. <view v-if="loading && !isInitialized" class="loading-more">
  74. <text>数据加载中...</text>
  75. </view>
  76. <!-- 空状态 -->
  77. <view v-if="recordList.length === 0 && !loading && isInitialized" class="empty-state">
  78. <text>{{ currentTab === 'settlement' ? '暂无结算记录' : '暂无提现记录' }}</text>
  79. </view>
  80. <!-- 调试信息临时启用查看状态 -->
  81. <!-- <view class="debug-info" style="padding: 20rpx; background: #f0f0f0; margin: 20rpx; font-size: 24rpx; border-radius: 10rpx;">
  82. <text>recordList.length: {{ recordList.length }}</text><br/>
  83. <text>loading: {{ loading }}</text><br/>
  84. <text>isInitialized: {{ isInitialized }}</text><br/>
  85. <text>currentTab: {{ currentTab }}</text><br/>
  86. <text>loadingMore: {{ loadingMore }}</text><br/>
  87. <text>noMore: {{ noMore }}</text>
  88. </view> -->
  89. <!-- 加载更多状态 -->
  90. <view v-if="loadingMore" class="loading-more">
  91. <text>加载更多中...</text>
  92. </view>
  93. <!-- 没有更多 -->
  94. <view v-if="noMore && recordList.length > 0" class="no-more">
  95. <text>没有更多了</text>
  96. </view>
  97. </view>
  98. </template>
  99. <script>
  100. import pullRefreshMixin from '@/pages/mixins/pullRefreshMixin.js'
  101. import api from '@/api/api.js'
  102. export default {
  103. mixins: [pullRefreshMixin],
  104. data() {
  105. return {
  106. statusBarHeight: 0,
  107. bannerBaseHeight: 300,
  108. currentTab: 'settlement',
  109. userInfo: {
  110. money: '0.00'
  111. },
  112. recordList: [],
  113. pageNum: 1,
  114. pageSize: 20,
  115. loading: false,
  116. loadingMore: false,
  117. noMore: false,
  118. refreshing: false,
  119. isInitialized: false,
  120. bannerList: []
  121. }
  122. },
  123. computed: {
  124. },
  125. onLoad() {
  126. this.statusBarHeight = uni.getSystemInfoSync().statusBarHeight
  127. this.initData()
  128. },
  129. onPullDownRefresh() {
  130. this.onRefresh()
  131. },
  132. onReachBottom(){
  133. this.loadMore()
  134. },
  135. onUnload() {
  136. uni.$off('bannerListUpdated')
  137. },
  138. methods: {
  139. async initData() {
  140. this.getBannerList()
  141. await this.getUserInfo()
  142. await this.loadRecords()
  143. },
  144. getBannerList() {
  145. // 从全局数据获取轮播图
  146. this.bannerList = getApp().globalData.bannerList || []
  147. // 监听轮播图数据更新
  148. uni.$on('bannerListUpdated', () => {
  149. this.bannerList = getApp().globalData.bannerList || []
  150. })
  151. },
  152. async getUserInfo() {
  153. try {
  154. const res = await api('getUserByToken')
  155. if (res.code === 200 && res.data) {
  156. this.userInfo = res.data
  157. }
  158. } catch (error) {
  159. console.error('获取用户信息失败:', error)
  160. }
  161. },
  162. async loadRecords(isRefresh = false) {
  163. if (this.loading) return
  164. this.loading = true
  165. // 如果是刷新,重置分页状态
  166. if (isRefresh) {
  167. this.pageNum = 1
  168. this.noMore = false
  169. this.recordList = []
  170. }
  171. try {
  172. // 获取记录
  173. const res = await api('getMyMoneyLogPage', {
  174. status: this.currentTab === 'settlement' ? 0 : 1,
  175. pageNum: this.pageNum,
  176. pageSize: this.pageSize
  177. })
  178. if (res.code === 200 && res.data) {
  179. const records = res.data.records || []
  180. if (isRefresh) {
  181. this.recordList = records
  182. } else {
  183. this.recordList = [...this.recordList, ...records]
  184. }
  185. if (records.length < this.pageSize) {
  186. this.noMore = true
  187. }
  188. } else {
  189. this.recordList = []
  190. }
  191. } catch (error) {
  192. console.error('获取流水记录失败:', error)
  193. uni.showToast({
  194. title: '获取数据失败',
  195. icon: 'none'
  196. })
  197. } finally {
  198. this.loading = false
  199. this.isInitialized = true
  200. }
  201. },
  202. formatDate(timestamp) {
  203. if (!timestamp) return ''
  204. const date = new Date(timestamp)
  205. const month = String(date.getMonth() + 1).padStart(2, '0')
  206. const day = String(date.getDate()).padStart(2, '0')
  207. return `${month}-${day}`
  208. },
  209. async onRefresh() {
  210. this.refreshing = true
  211. try {
  212. await this.getUserInfo()
  213. await this.loadRecords(true)
  214. } finally {
  215. this.refreshing = false
  216. uni.stopPullRefresh()
  217. }
  218. },
  219. goBack() {
  220. uni.navigateBack()
  221. },
  222. async switchTab(tab) {
  223. this.currentTab = tab
  224. // 切换标签时重新加载数据
  225. this.pageNum = 1
  226. this.noMore = false
  227. this.recordList = []
  228. this.isInitialized = false
  229. await this.loadRecords()
  230. },
  231. withdraw() {
  232. uni.navigateTo({
  233. url: '/pages/subcomponent/withdraw'
  234. })
  235. },
  236. async receiveWithdrawal(item, index) {
  237. try {
  238. // 验证提现金额
  239. if (!item.money || parseFloat(item.money) <= 0) {
  240. uni.showToast({
  241. title: '提现金额无效',
  242. icon: 'none'
  243. })
  244. return
  245. }
  246. // 获取用户名
  247. const userName = this.userInfo.name || this.userInfo.nickName || this.userInfo.userName
  248. if (!userName) {
  249. uni.showToast({
  250. title: '请先完善个人信息',
  251. icon: 'none'
  252. })
  253. return
  254. }
  255. // 显示确认弹窗
  256. const [error, res] = await uni.showModal({
  257. title: '确认提现',
  258. content: `确认提现金额 ¥${item.money} 到您的账户?`,
  259. confirmText: '确认提现',
  260. cancelText: '取消'
  261. })
  262. if (error || !res.confirm) {
  263. return
  264. }
  265. // 显示加载状态
  266. uni.showLoading({
  267. title: '正在提现...'
  268. })
  269. // 调用提现接口
  270. const result = await api('withdraw', {
  271. userName: userName,
  272. money: parseFloat(item.money)
  273. })
  274. if (result.code === 200) {
  275. // 更新本地状态
  276. this.recordList[index].state = 1
  277. uni.hideLoading()
  278. uni.showToast({
  279. title: '提现成功',
  280. icon: 'success'
  281. })
  282. // 重新获取用户信息更新余额
  283. await this.getUserInfo()
  284. } else {
  285. throw new Error(result.message || result.msg || '提现失败')
  286. }
  287. } catch (error) {
  288. uni.hideLoading()
  289. console.error('提现失败:', error)
  290. uni.showToast({
  291. title: error.message || '提现失败,请重试',
  292. icon: 'none'
  293. })
  294. }
  295. },
  296. async loadMore() {
  297. if (this.noMore || this.loadingMore) return
  298. this.loadingMore = true
  299. this.pageNum++
  300. try {
  301. const res = await api('getMyMoneyLogPage', {
  302. status: this.currentTab === 'settlement' ? 0 : 1,
  303. pageNum: this.pageNum,
  304. pageSize: this.pageSize
  305. })
  306. if (res.code === 200 && res.data) {
  307. const records = res.data.records || []
  308. this.recordList = [...this.recordList, ...records]
  309. if (records.length < this.pageSize) {
  310. this.noMore = true
  311. }
  312. }
  313. } catch (error) {
  314. console.error('加载更多记录失败:', error)
  315. this.pageNum-- // 失败时回退页码
  316. uni.showToast({
  317. title: '加载更多数据失败',
  318. icon: 'none'
  319. })
  320. } finally {
  321. this.loadingMore = false
  322. }
  323. }
  324. }
  325. }
  326. </script>
  327. <style lang="scss" scoped>
  328. .container {
  329. min-height: 100vh;
  330. background: #fff;
  331. }
  332. .nav-bar {
  333. display: flex;
  334. align-items: center;
  335. height: calc(150rpx + var(--status-bar-height));
  336. padding: 0 32rpx;
  337. background: linear-gradient(to right, #f68240 0%, #fc8940 10%);
  338. position: fixed;
  339. top: 0;
  340. left: 0;
  341. right: 0;
  342. z-index: 999;
  343. box-sizing: border-box;
  344. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
  345. }
  346. .back {
  347. padding: 20rpx;
  348. margin-left: -20rpx;
  349. }
  350. .nav-title {
  351. flex: 1;
  352. text-align: center;
  353. font-size: 32rpx;
  354. font-weight: 500;
  355. color: #fff;
  356. }
  357. .nav-icons {
  358. display: flex;
  359. align-items: center;
  360. gap: 12px;
  361. }
  362. .banner {
  363. position: relative;
  364. background: linear-gradient(to right, #f78b49, #fc8940);
  365. overflow: hidden;
  366. border-radius: 0 0 30rpx 30rpx;
  367. margin-top: 0;
  368. }
  369. .banner-content {
  370. position: relative;
  371. z-index: 1;
  372. height: 100%;
  373. display: flex;
  374. justify-content: center;
  375. align-items: center;
  376. .wallet-icon {
  377. width: 240rpx;
  378. height: 240rpx;
  379. }
  380. }
  381. .balance-card {
  382. margin: -60rpx 30rpx 0;
  383. padding: 30rpx;
  384. background: #fff;
  385. border-radius: 20rpx;
  386. box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
  387. display: flex;
  388. justify-content: space-between;
  389. align-items: center;
  390. position: relative;
  391. z-index: 2;
  392. .balance-info {
  393. .label {
  394. font-size: 28rpx;
  395. color: #666;
  396. }
  397. .amount {
  398. margin-top: 8rpx;
  399. display: flex;
  400. align-items: baseline;
  401. .symbol {
  402. font-size: 32rpx;
  403. color: #333;
  404. }
  405. .value {
  406. font-size: 48rpx;
  407. font-weight: bold;
  408. color: #333;
  409. margin-left: 4rpx;
  410. }
  411. }
  412. }
  413. .withdraw-btn {
  414. width: 160rpx;
  415. height: 70rpx;
  416. background: #FFB74D;
  417. color: #fff;
  418. font-size: 28rpx;
  419. border-radius: 35rpx;
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. border: none;
  424. &::after {
  425. border: none;
  426. }
  427. }
  428. }
  429. .record-tabs {
  430. display: flex;
  431. padding: 20rpx 0;
  432. border-bottom: 1rpx solid #f5f5f5;
  433. .tab-item {
  434. position: relative;
  435. padding: 16rpx 0;
  436. font-size: 28rpx;
  437. color: #666;
  438. flex: 1;
  439. text-align: center;
  440. &.active {
  441. color: #333;
  442. font-weight: 500;
  443. &::after {
  444. content: '';
  445. position: absolute;
  446. left: 50%;
  447. transform: translateX(-50%);
  448. bottom: -21rpx;
  449. width: 48rpx;
  450. height: 2rpx;
  451. background: #FFB74D;
  452. }
  453. }
  454. }
  455. }
  456. .record-item {
  457. padding: 30rpx;
  458. border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
  459. .record-info {
  460. display: flex;
  461. justify-content: space-between;
  462. align-items: flex-start;
  463. .record-left {
  464. flex: 1;
  465. .type {
  466. font-size: 28rpx;
  467. color: #333;
  468. margin-bottom: 8rpx;
  469. }
  470. .status {
  471. display: inline-block;
  472. padding: 4rpx 8rpx;
  473. background: #FFB74D;
  474. border-radius: 8rpx;
  475. margin-top: 8rpx;
  476. .status-text {
  477. font-size: 22rpx;
  478. color: #fff;
  479. }
  480. }
  481. .date {
  482. font-size: 24rpx;
  483. color: #999;
  484. margin-top: 8rpx;
  485. }
  486. }
  487. .record-right {
  488. display: flex;
  489. flex-direction: column;
  490. align-items: flex-end;
  491. .amount {
  492. font-size: 28rpx;
  493. color: #333;
  494. font-weight: 500;
  495. margin-bottom: 8rpx;
  496. }
  497. .date {
  498. font-size: 24rpx;
  499. color: #999;
  500. }
  501. .withdraw-btn {
  502. width: 100rpx;
  503. height: 50rpx;
  504. background: #FFB74D;
  505. color: #fff;
  506. font-size: 24rpx;
  507. border-radius: 25rpx;
  508. display: flex;
  509. align-items: center;
  510. justify-content: center;
  511. border: none;
  512. margin-top: 8rpx;
  513. &::after {
  514. border: none;
  515. }
  516. }
  517. .received-text {
  518. font-size: 22rpx;
  519. color: #52C41A;
  520. margin-top: 8rpx;
  521. }
  522. }
  523. }
  524. }
  525. .empty-state {
  526. width: 100%;
  527. text-align: center;
  528. padding: 100rpx 0;
  529. color: #999;
  530. font-size: 28rpx;
  531. }
  532. .loading-more {
  533. text-align: center;
  534. padding: 20rpx 0;
  535. color: #999;
  536. font-size: 28rpx;
  537. }
  538. .no-more {
  539. text-align: center;
  540. padding: 20rpx 0;
  541. color: #999;
  542. font-size: 28rpx;
  543. }
  544. </style>