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

887 lines
20 KiB

1 month ago
2 weeks ago
1 month ago
2 weeks ago
1 month ago
2 weeks ago
1 month ago
2 weeks ago
1 month ago
2 weeks ago
1 month ago
2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <view class="order-manage-container">
  3. <!-- 顶部导航栏 -->
  4. <view class="nav-bar" :style="{
  5. paddingTop: statusBarHeight + 'px',
  6. height: (statusBarHeight + navBarContentHeight) + 'px'
  7. }">
  8. <uni-icons type="left" @tap="goBack" size="24" color="#222" />
  9. <text class="nav-title">{{ historyOrderMode ? '历史订单' : '订单管理' }}</text>
  10. </view>
  11. <!-- Tab栏 -->
  12. <scroll-view v-if="!historyOrderMode" class="order-tabs-scroll" scroll-x :style="{
  13. top: navBarRealHeight + 'px',
  14. height: tabBarHeight + 'px',
  15. position: 'fixed',
  16. left: 0,
  17. width: '100%',
  18. zIndex: 99
  19. }">
  20. <view class="order-tabs" :style="{width: (tabs.length * 20) + '%'}">
  21. <view v-for="(tab, idx) in tabs" :key="tab.value" :class="['tab-item', {active: currentTab === idx}]"
  22. @tap="onTabChange(idx)">
  23. <view class="tab-label-wrap">
  24. {{ tab.label }}
  25. <view v-if="tab.count > 0" class="tab-badge">{{ tab.count }}</view>
  26. </view>
  27. </view>
  28. </view>
  29. </scroll-view>
  30. <!-- 搜索与筛选 -->
  31. <view v-if="!historyOrderMode" class="search-bar" :style="{
  32. position: 'fixed',
  33. zIndex: 10,
  34. top: (navBarRealHeight + tabBarHeight) + 'px',
  35. left: 0,
  36. width: '100vw',
  37. height: '40px'
  38. }">
  39. <view class="search-bar-inner">
  40. <template v-if="!searchMode">
  41. <uni-icons class="search-icon" type="search" size="22" color="#999" @tap="onSearchIconClick" />
  42. <uni-icons class="scan-icon" type="scan" size="22" color="#999" @tap="scanCode" />
  43. </template>
  44. <template v-else>
  45. <view class="search-input-wrap">
  46. <uni-icons type="search" size="22" color="#999" />
  47. <input ref="searchInput" class="search-input" v-model="searchText" placeholder="请输入要查询的内容"
  48. placeholder-style="color:#ccc" />
  49. <uni-icons v-if="searchText" type="close" size="22" color="#ccc" @tap="onClearSearch" />
  50. </view>
  51. <text class="search-cancel" @tap="onCancelSearch">取消</text>
  52. </template>
  53. </view>
  54. </view>
  55. <!-- 订单卡片列表 -->
  56. <view class="order-list"
  57. :style="{paddingTop: (navBarRealHeight + (historyOrderMode ? 0 : (tabBarHeight + 16 + 40))) + 'px'}">
  58. <view class="order-card" v-for="order in filteredOrders" :key="order.id" @tap="goToOrderDetail(order)">
  59. <view class="order-card-header">
  60. <text class="order-id">{{ order.orderNo }}</text>
  61. <view v-if="order.statusText === '不包邮'" class="order-status-tag red">{{ order.statusText }}</view>
  62. </view>
  63. <view class="order-info-wrapper">
  64. <view class="order-info">
  65. <view>
  66. <text class="info-label">用户名</text>
  67. <text class="info-value">{{ order.userName }}</text>
  68. </view>
  69. <view>
  70. <text class="info-label">电话</text>
  71. <text class="info-value">{{ order.phone }}</text>
  72. </view>
  73. <view v-if="order.appointTime">
  74. <text class="info-label">预约时间</text>
  75. <text class="info-value">{{ order.appointTime }}</text>
  76. </view>
  77. <view v-if="order.cancelTime">
  78. <text class="info-label">取消时间</text>
  79. <text class="info-value">{{ order.cancelTime }}</text>
  80. </view>
  81. <view v-if="order.qualityTime">
  82. <text class="info-label">质检时间</text>
  83. <text class="info-value">{{ order.qualityTime }}</text>
  84. </view>
  85. </view>
  86. <view class="order-status-label-bar order-info-status" :class="order.statusClass">
  87. {{ order.statusLabel }}</view>
  88. </view>
  89. <view class="order-card-footer" v-if="order.actions && order.actions.length && !historyOrderMode">
  90. <view class="order-actions-bar">
  91. <view class="action-btn-bar" v-for="action in order.actions" :key="action.text"
  92. @tap="action.text === '审批' ? goToOrderDetail(order) : null">
  93. <uni-icons :type="action.icon" size="28" color="#666" />
  94. <text>{{ action.text }}</text>
  95. </view>
  96. </view>
  97. </view>
  98. </view>
  99. <!-- 加载状态 -->
  100. <view v-if="isLoading && orderList.length === 0" class="loading-container">
  101. <uni-icons type="spinner-cycle" size="24" color="#999" class="loading-icon" />
  102. <text class="loading-text">加载中...</text>
  103. </view>
  104. <!-- 加载更多状态 -->
  105. <view v-if="loadingMore" class="load-more-container">
  106. <uni-icons type="spinner-cycle" size="20" color="#999" class="loading-icon" />
  107. <text class="load-more-text">加载更多...</text>
  108. </view>
  109. <!-- 到底提示 -->
  110. <view v-if="!hasMore && orderList.length > 0" class="no-more-container">
  111. <text class="no-more-text">已加载全部订单</text>
  112. </view>
  113. <!-- 空状态 -->
  114. <view v-if="!isLoading && orderList.length === 0" class="empty-container">
  115. <text class="empty-text">暂无订单数据</text>
  116. </view>
  117. </view>
  118. </view>
  119. </template>
  120. <script>
  121. import pullRefreshMixin from '../mixins/pullRefreshMixin.js'
  122. export default {
  123. mixins: [pullRefreshMixin],
  124. data() {
  125. return {
  126. statusBarHeight: 0,
  127. navBarContentHeight: 44,
  128. tabBarHeight: 48,
  129. navBarHeight: 44,
  130. navBarRealHeight: 44,
  131. tabs: [{
  132. label: '全部',
  133. value: '',
  134. count: 0
  135. },
  136. {
  137. label: '待审核',
  138. value: 0,
  139. count: 0
  140. },
  141. {
  142. label: '已预约',
  143. value: 1,
  144. count: 0
  145. },
  146. {
  147. label: '待质检',
  148. value: 2,
  149. count: 0
  150. },
  151. {
  152. label: '已结款',
  153. value: 3,
  154. count: 0
  155. },
  156. {
  157. label: '已驳回',
  158. value: 4,
  159. count: 0
  160. },
  161. {
  162. label: '已取消',
  163. value: 5,
  164. count: 0
  165. },
  166. ],
  167. currentTab: 0,
  168. orderList: [],
  169. searchMode: false,
  170. searchText: '',
  171. historyOrderMode: false,
  172. pageNo: 1,
  173. pageSize: 10,
  174. hasMore: true,
  175. isLoading: false,
  176. loadingMore: false,
  177. userId: '',
  178. reachBottomTimer: null
  179. }
  180. },
  181. onLoad(options) {
  182. const sys = uni.getSystemInfoSync();
  183. this.statusBarHeight = sys.statusBarHeight;
  184. this.$nextTick(() => {
  185. uni.createSelectorQuery().select('.nav-bar').boundingClientRect(rect => {
  186. if (rect) {
  187. this.navBarRealHeight = rect.height;
  188. }
  189. }).exec();
  190. });
  191. if (options && options.historyOrder) {
  192. this.historyOrderMode = true;
  193. }
  194. if (options && options.userId) {
  195. this.userId = options.userId;
  196. }
  197. this.fetchOrderList()
  198. this.fetchOrderStatusStatistics()
  199. },
  200. onShow() {
  201. this.fetchOrderStatusStatistics()
  202. },
  203. computed: {
  204. filteredOrders() {
  205. if (this.searchText) {
  206. const text = this.searchText.toLowerCase();
  207. return this.orderList.filter(order =>
  208. (order.orderNo && order.orderNo.toLowerCase().includes(text)) ||
  209. (order.userName && order.userName.toLowerCase().includes(text)) ||
  210. (order.phone && order.phone.toLowerCase().includes(text))
  211. );
  212. }
  213. // 现在通过接口参数获取对应Tab的数据,不需要客户端过滤
  214. return this.orderList;
  215. }
  216. },
  217. methods: {
  218. goBack() {
  219. uni.navigateBack()
  220. },
  221. onTabChange(idx) {
  222. this.currentTab = idx
  223. this.pageNo = 1
  224. this.hasMore = true
  225. this.orderList = []
  226. this.isLoading = false
  227. this.loadingMore = false
  228. this.fetchOrderList()
  229. },
  230. onSearchIconClick() {
  231. this.searchMode = true;
  232. this.$nextTick(() => {
  233. this.$refs.searchInput && this.$refs.searchInput.focus();
  234. });
  235. },
  236. onClearSearch() {
  237. this.searchText = '';
  238. },
  239. onCancelSearch() {
  240. this.searchText = '';
  241. this.searchMode = false;
  242. },
  243. goToOrderDetail(order) {
  244. // 新增:记录浏览记录
  245. this.$api && this.$api('adminOrderBrowseRecord', {
  246. orderIds: order.id
  247. }, res => {
  248. // 可选:处理返回结果或错误,但不影响后续跳转
  249. })
  250. uni.navigateTo({
  251. url: '/pages/manager/order-detail?id=' + order.id
  252. })
  253. },
  254. refreshData() {
  255. // TODO: 实现订单列表刷新逻辑,如重新请求接口
  256. },
  257. async onRefresh() {
  258. await this.refreshData && this.refreshData()
  259. },
  260. fetchOrderList(isLoadMore) {
  261. if (this.isLoading) return
  262. if (isLoadMore && !this.hasMore) return
  263. console.log(isLoadMore, 'isLoadMore')
  264. if (isLoadMore) {
  265. this.loadingMore = true
  266. } else {
  267. this.isLoading = true
  268. }
  269. // 根据当前Tab获取对应的status参数
  270. const tabValue = this.tabs[this.currentTab].value
  271. let statusParam = tabValue // 直接用tabValue作为status参数
  272. const params = {
  273. pageNo: isLoadMore ? this.pageNo + 1 : 1,
  274. pageSize: this.pageSize,
  275. status: statusParam
  276. }
  277. console.log(params, 'params')
  278. if (this.userId) {
  279. params.userId = this.userId;
  280. }
  281. this.$api && this.$api('getOrderList', params, res => {
  282. if (res && res.code === 200 && res.result && res.result.records) {
  283. console.log(res.result, 'res.result.records')
  284. const newOrders = res.result.records.map(order => {
  285. const statusInfo = this.getOrderStatusInfo(order.status, order.state)
  286. return {
  287. id: order.id,
  288. orderNo: order.ordeNo,
  289. userName: order.name,
  290. phone: order.phone,
  291. appointTime: order.goTime,
  292. cancelTime: order.state === 3 ? order.updateTime : '',
  293. qualityTime: order.testingTime,
  294. statusText: order.isBy === 'Y' ? statusInfo.label : '不包邮',
  295. statusClass: statusInfo.class,
  296. statusLabel: statusInfo.label,
  297. actions: this.getOrderActions(order.status, order.state),
  298. status: this.getOrderStatus(order.status, order.state)
  299. }
  300. })
  301. if (isLoadMore) {
  302. // 数据去重处理
  303. const existingIds = new Set(this.orderList.map(order => order.id))
  304. const uniqueNewOrders = newOrders.filter(order => !existingIds.has(order.id))
  305. this.orderList = [...this.orderList, ...uniqueNewOrders]
  306. this.pageNo = params.pageNo + 1
  307. } else {
  308. this.orderList = newOrders
  309. this.pageNo = 1
  310. }
  311. // 判断是否还有更多数据
  312. this.hasMore = newOrders.length === this.pageSize
  313. } else {
  314. this.hasMore = false
  315. }
  316. this.isLoading = false
  317. this.loadingMore = false
  318. }, err => {
  319. console.error('获取订单列表失败:', err)
  320. this.isLoading = false
  321. this.loadingMore = false
  322. this.hasMore = false
  323. uni.showToast({
  324. title: '加载失败,请重试',
  325. icon: 'none'
  326. })
  327. })
  328. },
  329. getOrderStatusInfo(status, state) {
  330. // if (state === 3) {
  331. // return { label: '已取消', class: 'gray' }
  332. // }
  333. if ((status === 1 && state === 0) && state != 3 && state != 4) {
  334. return {
  335. label: '已预约',
  336. class: 'green'
  337. }
  338. } else if (state === 1 && status === 2) {
  339. return {
  340. label: '待质检',
  341. class: 'orange'
  342. }
  343. } else if (status === 3 && state === 2) {
  344. return {
  345. label: '已结款',
  346. class: 'blue'
  347. }
  348. } else if (state === 4) {
  349. return {
  350. label: '已驳回',
  351. class: 'red'
  352. }
  353. } else if (state === 3) {
  354. return {
  355. label: '已取消',
  356. class: 'Turquoise2'
  357. }
  358. } else if (state === 0 && status === 0) {
  359. return {
  360. label: '待审核',
  361. class: 'blue'
  362. }
  363. }
  364. return {
  365. label: '未知状态',
  366. class: 'gray'
  367. }
  368. },
  369. getOrderStatus(status, state) {
  370. // // 已取消状态
  371. // if (state === 3) return 4
  372. // 已预约状态 - 快递上门
  373. if (status === 1) return 1
  374. // 待质检状态 - 已取件
  375. if (state === 1) return 2
  376. // 已结款状态 - 现金打款
  377. if (status === 3) return 3
  378. // 已驳回状态 - 快递上门终止
  379. if (state === 4) return 4
  380. return -1
  381. },
  382. getOrderActions(status, state) {
  383. const actions = []
  384. // 只有待审核状态显示操作按钮
  385. if (status == 0 && state == 0) {
  386. actions.push({
  387. icon: 'undo',
  388. text: '驳回'
  389. })
  390. actions.push({
  391. icon: 'person',
  392. text: '审批'
  393. })
  394. }
  395. return actions
  396. },
  397. onLoadMore() {
  398. if (this.hasMore && !this.isLoading && !this.loadingMore) {
  399. this.fetchOrderList(true)
  400. }
  401. },
  402. scanCode() {
  403. uni.scanCode({
  404. scanType: ['qrCode'],
  405. success: (res) => {
  406. console.log('扫码结果:', res);
  407. // 这里可以根据扫码结果进行相应处理
  408. // 比如跳转到订单详情页
  409. if (res.result) {
  410. this.$api('getOrderIdBywliuNo', {
  411. wliuNo : res.result
  412. }).then(e => {
  413. if(e.code == 200){
  414. uni.navigateTo({
  415. url: '/pages/manager/order-detail?id=' + e.result
  416. })
  417. }
  418. })
  419. }
  420. },
  421. fail: (err) => {
  422. console.error('扫码失败:', err);
  423. uni.showToast({
  424. title: '扫码失败',
  425. icon: 'none'
  426. })
  427. }
  428. })
  429. },
  430. fetchOrderStatusStatistics() {
  431. const token = uni.getStorageSync('token') || '';
  432. this.$api && this.$api('orderStatusStatistics', {
  433. token
  434. }, res => {
  435. if (res.code === 200 && res.result) {
  436. const stat = res.result;
  437. this.tabs[1].count = stat.pendingAudit || 0; // 待审核
  438. this.tabs[2].count = stat.appointed || 0; // 已预约
  439. this.tabs[3].count = stat.waitingInspection || 0; // 待质检
  440. this.tabs[4].count = stat.completed || 0; // 已结款
  441. this.tabs[5].count = stat.rejected || 0; // 已驳回
  442. this.tabs[6].count = stat.cancelled || 0; // 已取消
  443. this.tabs[0].count = (stat.pendingAudit || 0) + (stat.appointed || 0) + (stat
  444. .waitingInspection || 0) + (stat.completed || 0) + (stat.rejected || 0) + (stat
  445. .cancelled || 0);
  446. }
  447. });
  448. },
  449. },
  450. onPullDownRefresh() {
  451. this.pageNo = 1;
  452. this.hasMore = true;
  453. this.orderList = [];
  454. this.isLoading = false;
  455. this.loadingMore = false;
  456. this.fetchOrderList();
  457. uni.stopPullDownRefresh();
  458. },
  459. onReachBottom() {
  460. // 防抖处理,避免频繁触发
  461. if (this.reachBottomTimer) {
  462. clearTimeout(this.reachBottomTimer)
  463. }
  464. this.reachBottomTimer = setTimeout(() => {
  465. this.onLoadMore()
  466. }, 100)
  467. }
  468. }
  469. </script>
  470. <style lang="scss" scoped>
  471. .order-manage-container {
  472. background: #f8f8f8;
  473. min-height: 100vh;
  474. padding-bottom: 24px;
  475. }
  476. .nav-bar {
  477. display: flex;
  478. align-items: center;
  479. justify-content: space-between;
  480. position: fixed;
  481. top: 0;
  482. left: 0;
  483. right: 0;
  484. z-index: 100;
  485. background: #fff;
  486. padding: 0 32rpx;
  487. box-sizing: border-box;
  488. .nav-title {
  489. flex: 1;
  490. text-align: center;
  491. font-size: 36rpx;
  492. font-weight: bold;
  493. color: #222;
  494. }
  495. .nav-icons {
  496. display: flex;
  497. align-items: center;
  498. gap: 32rpx;
  499. }
  500. }
  501. .order-tabs-scroll {
  502. position: fixed;
  503. left: 0;
  504. width: 100%;
  505. z-index: 99;
  506. background: #fff;
  507. border-bottom: 1px solid #f0f0f0;
  508. height: 96rpx;
  509. overflow-x: auto;
  510. white-space: nowrap;
  511. -webkit-overflow-scrolling: touch;
  512. &::-webkit-scrollbar {
  513. display: none;
  514. }
  515. }
  516. .order-tabs {
  517. display: flex;
  518. width: 100%;
  519. }
  520. .tab-item {
  521. flex: 1 0 0%;
  522. text-align: center;
  523. font-size: 34rpx;
  524. color: #bfbfbf;
  525. height: 96rpx;
  526. line-height: 96rpx;
  527. position: relative;
  528. font-weight: 500;
  529. transition: color 0.2s;
  530. letter-spacing: 0.5px;
  531. }
  532. .tab-item.active {
  533. color: #ffb400;
  534. font-weight: bold;
  535. }
  536. .tab-item.active::after {
  537. content: '';
  538. display: block;
  539. margin: 0 auto;
  540. margin-top: 2px;
  541. width: 22px;
  542. height: 3px;
  543. border-radius: 2px;
  544. background: #ffb400;
  545. }
  546. .search-bar {
  547. width: 100vw;
  548. height: 40px;
  549. position: fixed;
  550. z-index: 10;
  551. left: 0;
  552. top: 0;
  553. display: flex;
  554. align-items: center;
  555. }
  556. .search-bar-inner {
  557. margin: 0 16px;
  558. background: #fff;
  559. border-radius: 20px;
  560. height: 40px;
  561. flex: 1;
  562. display: flex;
  563. align-items: center;
  564. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02);
  565. padding: 0 12px;
  566. justify-content: space-around;
  567. .search-icon {
  568. margin-right: 8px;
  569. }
  570. .scan-icon {
  571. margin-left: 8px;
  572. }
  573. }
  574. .search-input-wrap {
  575. display: flex;
  576. align-items: center;
  577. flex: 1;
  578. background: #f5f5f5;
  579. border-radius: 20px;
  580. height: 32px;
  581. margin: 0 0;
  582. padding: 0 8px;
  583. .search-input {
  584. flex: 1;
  585. border: none;
  586. outline: none;
  587. background: transparent;
  588. font-size: 15px;
  589. color: #222;
  590. margin-left: 8px;
  591. }
  592. }
  593. .search-cancel {
  594. margin-left: 8px;
  595. color: #999;
  596. font-size: 15px;
  597. line-height: 40px;
  598. }
  599. .order-list {
  600. margin: 0;
  601. padding-top: calc(var(--status-bar-height, 0px) + 44px + 44px + 16px);
  602. }
  603. .order-card {
  604. background: #fff;
  605. border-radius: 20px;
  606. margin: 0 16px 16px 16px;
  607. padding: 20px;
  608. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  609. }
  610. .order-card-header {
  611. display: flex;
  612. justify-content: space-between;
  613. align-items: flex-start;
  614. margin-bottom: 12px;
  615. .order-id {
  616. font-size: 16px;
  617. font-weight: bold;
  618. color: #222;
  619. }
  620. .order-status-tag {
  621. font-size: 14px;
  622. border-radius: 12px;
  623. padding: 2px 12px;
  624. &.green {
  625. background: #e6f9e6;
  626. color: #1ecb1e;
  627. }
  628. &.red {
  629. background: #ffeaea;
  630. color: #ff4d4f;
  631. }
  632. &.orange {
  633. background: #fff7e6;
  634. color: #ffb400;
  635. }
  636. &.blue {
  637. background: #e6f0ff;
  638. color: #409eff;
  639. }
  640. &.gray {
  641. background: #f5f5f5;
  642. color: #999;
  643. }
  644. &.Turquoise2 {
  645. background: #e0f7fa;
  646. color: #009fa8;
  647. }
  648. /* 新增已取消 */
  649. }
  650. }
  651. .order-info-wrapper {
  652. position: relative;
  653. .order-info-status {
  654. position: absolute;
  655. right: 0;
  656. bottom: 0;
  657. font-size: 14px;
  658. border-radius: 12px;
  659. padding: 2px 12px;
  660. &.green {
  661. background: #e6f9e6;
  662. color: #1ecb1e;
  663. }
  664. &.red {
  665. background: #ffeaea;
  666. color: #ff4d4f;
  667. }
  668. &.orange {
  669. background: #fff7e6;
  670. color: #ffb400;
  671. }
  672. &.blue {
  673. background: #e6f0ff;
  674. color: #409eff;
  675. }
  676. &.gray {
  677. background: #f5f5f5;
  678. color: #999;
  679. }
  680. &.Turquoise2 {
  681. background: #e0f7fa;
  682. color: #009fa8;
  683. }
  684. /* 新增已取消 */
  685. }
  686. }
  687. .order-info {
  688. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  689. font-weight: 400;
  690. font-size: 14px;
  691. line-height: 1.4;
  692. letter-spacing: 0;
  693. vertical-align: middle;
  694. color: #666;
  695. margin-bottom: 12px;
  696. view {
  697. margin-bottom: 4px;
  698. display: flex;
  699. align-items: center;
  700. }
  701. .info-label {
  702. color: #999;
  703. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  704. font-weight: 400;
  705. font-size: 14px;
  706. line-height: 1.4;
  707. margin-right: 4px;
  708. }
  709. .info-value {
  710. color: #222;
  711. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  712. font-weight: 400;
  713. font-size: 14px;
  714. line-height: 1.4;
  715. }
  716. }
  717. .order-card-footer {
  718. display: flex;
  719. align-items: center;
  720. justify-content: center;
  721. margin: 0 -20px -20px -20px;
  722. padding: 0 20px;
  723. border-bottom-left-radius: 20px;
  724. border-bottom-right-radius: 20px;
  725. background: #fafbfc;
  726. min-height: 60px;
  727. position: relative;
  728. .order-actions-bar {
  729. display: flex;
  730. flex: 1;
  731. justify-content: center;
  732. align-items: center;
  733. gap: 48px;
  734. .action-btn-bar {
  735. display: flex;
  736. flex-direction: column;
  737. align-items: center;
  738. font-size: 14px;
  739. color: #666;
  740. margin-top: 8px;
  741. margin-bottom: 8px;
  742. uni-icons {
  743. margin-bottom: 2px;
  744. }
  745. }
  746. }
  747. }
  748. /* 加载状态样式 */
  749. .loading-container,
  750. .load-more-container,
  751. .no-more-container,
  752. .empty-container {
  753. display: flex;
  754. flex-direction: column;
  755. align-items: center;
  756. justify-content: center;
  757. padding: 40rpx 0;
  758. color: #999;
  759. }
  760. .loading-icon {
  761. animation: spin 1s linear infinite;
  762. margin-bottom: 16rpx;
  763. }
  764. @keyframes spin {
  765. 0% {
  766. transform: rotate(0deg);
  767. }
  768. 100% {
  769. transform: rotate(360deg);
  770. }
  771. }
  772. .loading-text,
  773. .load-more-text {
  774. font-size: 28rpx;
  775. color: #999;
  776. }
  777. .no-more-text {
  778. font-size: 26rpx;
  779. color: #ccc;
  780. }
  781. .empty-container {
  782. padding: 120rpx 0;
  783. }
  784. .empty-text {
  785. font-size: 30rpx;
  786. color: #ccc;
  787. }
  788. .tab-label-wrap {
  789. position: relative;
  790. display: inline-block;
  791. }
  792. .tab-badge {
  793. position: absolute;
  794. top: 5px;
  795. right: -14px;
  796. display: flex;
  797. align-items: center;
  798. justify-content: center;
  799. background: #ff4d4f;
  800. color: #fff;
  801. font-size: 12px;
  802. border-radius: 50%;
  803. min-width: 18px;
  804. height: 18px;
  805. padding: 0 5px;
  806. font-weight: bold;
  807. box-sizing: border-box;
  808. z-index: 1;
  809. }
  810. </style>