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

905 lines
20 KiB

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