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

615 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 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. status: 0
  245. }
  246. console.log(params,'params')
  247. if (this.userId) {
  248. params.userId = this.userId;
  249. }
  250. this.$api && this.$api('getOrderList', params, res => {
  251. if (res && res.code === 200 && res.result && res.result.records) {
  252. console.log(res.result,'res.result.records')
  253. const newOrders = res.result.records.map(order => {
  254. const statusInfo = this.getOrderStatusInfo(order.status, order.state)
  255. return {
  256. id: order.id,
  257. orderNo: order.ordeNo,
  258. userName: order.name,
  259. phone: order.phone,
  260. appointTime: order.goTime,
  261. cancelTime: order.state === 3 ? order.updateTime : '',
  262. qualityTime: order.status === 2 && order.state === 1 ? order.updateTime : '',
  263. statusText: order.isBy === 'Y' ? statusInfo.label : '不包邮',
  264. statusClass: statusInfo.class,
  265. statusLabel: statusInfo.label,
  266. actions: this.getOrderActions(order.status, order.state),
  267. status: this.getOrderStatus(order.status, order.state)
  268. }
  269. })
  270. if (isLoadMore) {
  271. // 数据去重处理
  272. const existingIds = new Set(this.orderList.map(order => order.id))
  273. const uniqueNewOrders = newOrders.filter(order => !existingIds.has(order.id))
  274. this.orderList = [...this.orderList, ...uniqueNewOrders]
  275. this.pageNo = params.pageNo +1
  276. } else {
  277. this.orderList = newOrders
  278. this.pageNo = 1
  279. }
  280. // 判断是否还有更多数据
  281. this.hasMore = newOrders.length === this.pageSize
  282. } else {
  283. this.hasMore = false
  284. }
  285. this.isLoading = false
  286. })
  287. },
  288. getOrderStatusInfo(status, state) {
  289. // if (state === 3) {
  290. // return { label: '已取消', class: 'gray' }
  291. // }
  292. if ((status === 1||status === 0) && state != 3) {
  293. return { label: '已预约', class: 'green' }
  294. } else if (state === 1) {
  295. return { label: '待质检', class: 'orange' }
  296. } else if (status === 3) {
  297. return { label: '已结款', class: 'blue' }
  298. } else if (state ===4) {
  299. return { label: '已驳回', class: 'red' }
  300. }
  301. return { label: '未知状态', class: 'gray' }
  302. },
  303. getOrderStatus(status, state) {
  304. // // 已取消状态
  305. // if (state === 3) return 4
  306. // 已预约状态 - 快递上门
  307. if (status === 1 ) return 1
  308. // 待质检状态 - 已取件
  309. if (state === 1) return 2
  310. // 已结款状态 - 现金打款
  311. if (status === 3 ) return 3
  312. // 已驳回状态 - 快递上门终止
  313. if (state ===4) return 4
  314. return -1
  315. },
  316. getOrderActions(status, state) {
  317. const actions = []
  318. // 只有待质检状态显示操作按钮
  319. if (status === 2 && state === 1) {
  320. actions.push({ icon: 'undo', text: '驳回' })
  321. actions.push({ icon: 'person', text: '审批' })
  322. }
  323. return actions
  324. },
  325. onLoadMore() {
  326. if (this.hasMore && !this.isLoading) {
  327. this.fetchOrderList(true)
  328. }
  329. },
  330. scanCode() {
  331. uni.scanCode({
  332. scanType: ['qrCode'],
  333. success: (res) => {
  334. console.log('扫码结果:', res);
  335. // 这里可以根据扫码结果进行相应处理
  336. // 比如跳转到订单详情页
  337. if(res.result) {
  338. uni.navigateTo({
  339. url: '/pages/manager/order-detail?id=' + res.result
  340. })
  341. }
  342. },
  343. fail: (err) => {
  344. console.error('扫码失败:', err);
  345. uni.showToast({
  346. title: '扫码失败',
  347. icon: 'none'
  348. })
  349. }
  350. })
  351. },
  352. },
  353. onPullDownRefresh() {
  354. this.pageNo = 1;
  355. this.hasMore = true;
  356. this.orderList = [];
  357. this.isLoading = false;
  358. this.fetchOrderList();
  359. uni.stopPullDownRefresh();
  360. },
  361. onReachBottom() {
  362. this.onLoadMore()
  363. }
  364. }
  365. </script>
  366. <style lang="scss" scoped>
  367. .order-manage-container {
  368. background: #f8f8f8;
  369. min-height: 100vh;
  370. padding-bottom: 24px;
  371. }
  372. .nav-bar {
  373. display: flex;
  374. align-items: center;
  375. justify-content: space-between;
  376. position: fixed;
  377. top: 0;
  378. left: 0;
  379. right: 0;
  380. z-index: 100;
  381. background: #fff;
  382. padding: 0 32rpx;
  383. box-sizing: border-box;
  384. .nav-title {
  385. flex: 1;
  386. text-align: center;
  387. font-size: 36rpx;
  388. font-weight: bold;
  389. color: #222;
  390. }
  391. .nav-icons {
  392. display: flex;
  393. align-items: center;
  394. gap: 32rpx;
  395. }
  396. }
  397. .order-tabs-scroll {
  398. position: fixed;
  399. left: 0;
  400. width: 100%;
  401. z-index: 99;
  402. background: #fff;
  403. border-bottom: 1px solid #f0f0f0;
  404. height: 96rpx;
  405. overflow-x: auto;
  406. white-space: nowrap;
  407. -webkit-overflow-scrolling: touch;
  408. &::-webkit-scrollbar {
  409. display: none;
  410. }
  411. }
  412. .order-tabs {
  413. display: flex;
  414. width: 100%;
  415. }
  416. .tab-item {
  417. flex: 1 0 0%;
  418. text-align: center;
  419. font-size: 34rpx;
  420. color: #bfbfbf;
  421. height: 96rpx;
  422. line-height: 96rpx;
  423. position: relative;
  424. font-weight: 500;
  425. transition: color 0.2s;
  426. letter-spacing: 0.5px;
  427. }
  428. .tab-item.active {
  429. color: #ffb400;
  430. font-weight: bold;
  431. }
  432. .tab-item.active::after {
  433. content: '';
  434. display: block;
  435. margin: 0 auto;
  436. margin-top: 2px;
  437. width: 22px;
  438. height: 3px;
  439. border-radius: 2px;
  440. background: #ffb400;
  441. }
  442. .search-bar {
  443. width: 100vw;
  444. height: 40px;
  445. position: fixed;
  446. z-index: 10;
  447. left: 0;
  448. top: 0;
  449. display: flex;
  450. align-items: center;
  451. }
  452. .search-bar-inner {
  453. margin: 0 16px;
  454. background: #fff;
  455. border-radius: 20px;
  456. height: 40px;
  457. flex: 1;
  458. display: flex;
  459. align-items: center;
  460. box-shadow: 0 2px 8px rgba(0,0,0,0.02);
  461. padding: 0 12px;
  462. justify-content: space-around;
  463. .search-icon {
  464. margin-right: 8px;
  465. }
  466. .scan-icon {
  467. margin-left: 8px;
  468. }
  469. }
  470. .search-input-wrap {
  471. display: flex;
  472. align-items: center;
  473. flex: 1;
  474. background: #f5f5f5;
  475. border-radius: 20px;
  476. height: 32px;
  477. margin: 0 0;
  478. padding: 0 8px;
  479. .search-input {
  480. flex: 1;
  481. border: none;
  482. outline: none;
  483. background: transparent;
  484. font-size: 15px;
  485. color: #222;
  486. margin-left: 8px;
  487. }
  488. }
  489. .search-cancel {
  490. margin-left: 8px;
  491. color: #999;
  492. font-size: 15px;
  493. line-height: 40px;
  494. }
  495. .order-list {
  496. margin: 0;
  497. padding-top: calc(var(--status-bar-height, 0px) + 44px + 44px + 16px);
  498. }
  499. .order-card {
  500. background: #fff;
  501. border-radius: 20px;
  502. margin: 0 16px 16px 16px;
  503. padding: 20px;
  504. box-shadow: 0 2px 8px rgba(0,0,0,0.04);
  505. }
  506. .order-card-header {
  507. display: flex;
  508. justify-content: space-between;
  509. align-items: flex-start;
  510. margin-bottom: 12px;
  511. .order-id {
  512. font-size: 16px;
  513. font-weight: bold;
  514. color: #222;
  515. }
  516. .order-status-tag {
  517. font-size: 14px;
  518. border-radius: 12px;
  519. padding: 2px 12px;
  520. &.green { background: #e6f9e6; color: #1ecb1e; }
  521. &.red { background: #ffeaea; color: #ff4d4f; }
  522. &.orange { background: #fff7e6; color: #ffb400; }
  523. &.blue { background: #e6f0ff; color: #409eff; }
  524. &.gray { background: #f5f5f5; color: #999; }
  525. }
  526. }
  527. .order-info-wrapper {
  528. position: relative;
  529. .order-info-status {
  530. position: absolute;
  531. right: 0;
  532. bottom: 0;
  533. font-size: 14px;
  534. border-radius: 12px;
  535. padding: 2px 12px;
  536. &.green { background: #e6f9e6; color: #1ecb1e; }
  537. &.red { background: #ffeaea; color: #ff4d4f; }
  538. &.orange { background: #fff7e6; color: #ffb400; }
  539. &.blue { background: #e6f0ff; color: #409eff; }
  540. &.gray { background: #f5f5f5; color: #999; }
  541. }
  542. }
  543. .order-info {
  544. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  545. font-weight: 400;
  546. font-size: 14px;
  547. line-height: 1.4;
  548. letter-spacing: 0;
  549. vertical-align: middle;
  550. color: #666;
  551. margin-bottom: 12px;
  552. view {
  553. margin-bottom: 4px;
  554. display: flex;
  555. align-items: center;
  556. }
  557. .info-label {
  558. color: #999;
  559. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  560. font-weight: 400;
  561. font-size: 14px;
  562. line-height: 1.4;
  563. margin-right: 4px;
  564. }
  565. .info-value {
  566. color: #222;
  567. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  568. font-weight: 400;
  569. font-size: 14px;
  570. line-height: 1.4;
  571. }
  572. }
  573. .order-card-footer {
  574. display: flex;
  575. align-items: center;
  576. justify-content: center;
  577. margin: 0 -20px -20px -20px;
  578. padding: 0 20px;
  579. border-bottom-left-radius: 20px;
  580. border-bottom-right-radius: 20px;
  581. background: #fafbfc;
  582. min-height: 60px;
  583. position: relative;
  584. .order-actions-bar {
  585. display: flex;
  586. flex: 1;
  587. justify-content: center;
  588. align-items: center;
  589. gap: 48px;
  590. .action-btn-bar {
  591. display: flex;
  592. flex-direction: column;
  593. align-items: center;
  594. font-size: 14px;
  595. color: #666;
  596. margin-top: 8px;
  597. margin-bottom: 8px;
  598. uni-icons {
  599. margin-bottom: 2px;
  600. }
  601. }
  602. }
  603. }
  604. </style>