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

605 lines
18 KiB

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