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

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