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

751 lines
21 KiB

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