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

553 lines
14 KiB

1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week ago
1 week 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="switchTab(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" />
  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.pickupTime">
  100. <text class="info-label">取件时间</text>
  101. <text class="info-value">{{ order.pickupTime }}</text>
  102. </view>
  103. <view v-if="order.cancelTime">
  104. <text class="info-label">取消时间</text>
  105. <text class="info-value">{{ order.cancelTime }}</text>
  106. </view>
  107. <view v-if="order.qualityTime">
  108. <text class="info-label">质检时间</text>
  109. <text class="info-value">{{ order.qualityTime }}</text>
  110. </view>
  111. </view>
  112. <view class="order-status-label-bar order-info-status" :class="order.statusClass">{{ order.statusLabel }}</view>
  113. </view>
  114. <view class="order-card-footer" v-if="order.actions && order.actions.length && !historyOrderMode">
  115. <view class="order-actions-bar">
  116. <view class="action-btn-bar" v-for="action in order.actions" :key="action.text">
  117. <uni-icons :type="action.icon" size="28" color="#666" />
  118. <text>{{ action.text }}</text>
  119. </view>
  120. </view>
  121. </view>
  122. </view>
  123. </view>
  124. </view>
  125. </template>
  126. <script>
  127. import pullRefreshMixin from '../mixins/pullRefreshMixin.js'
  128. export default {
  129. mixins: [pullRefreshMixin],
  130. data() {
  131. return {
  132. statusBarHeight: 0,
  133. navBarContentHeight: 44, // px
  134. tabBarHeight: 48, // px
  135. navBarHeight: 44, // 默认值,后续动态获取
  136. navBarRealHeight: 44, // 实际高度
  137. tabs: [
  138. { label: '全部', value: -1 },
  139. { label: '已预约', value: 0 },
  140. { label: '待质检', value: 1 },
  141. { label: '已结款', value: 2 },
  142. { label: '已驳回', value: 3 },
  143. { label: '已取消', value: 4 }
  144. ],
  145. currentTab: 0,
  146. orderList: [
  147. {
  148. id: 1,
  149. orderNo: 'RE82738127861524',
  150. userName: '周小艺',
  151. phone: '138****1234',
  152. appointTime: '周四 11:00~13:00',
  153. statusText: '已预约',
  154. statusClass: 'green',
  155. statusLabel: '已预约',
  156. actions: [],
  157. status: 0
  158. },
  159. {
  160. id: 2,
  161. orderNo: 'RE82738127861524',
  162. userName: '周小艺',
  163. phone: '138****1234',
  164. appointTime: '周四 11:00~13:00',
  165. statusText: '不包邮',
  166. statusClass: 'green',
  167. statusLabel: '已预约',
  168. actions: [],
  169. status: 0
  170. },
  171. {
  172. id: 3,
  173. orderNo: 'RE82738127861526',
  174. userName: '周小艺',
  175. phone: '138****1234',
  176. pickupTime: '2025-03-20 11:00',
  177. statusText: '待质检',
  178. statusClass: 'orange',
  179. statusLabel: '待质检',
  180. actions: [
  181. { icon: 'undo', text: '驳回' },
  182. { icon: 'person', text: '审批' }
  183. ],
  184. status: 1
  185. },
  186. {
  187. id: 4,
  188. orderNo: 'RE82738127861525',
  189. userName: '周小艺',
  190. phone: '138****1234',
  191. pickupTime: '2025-03-20 12:00',
  192. statusText: '已驳回',
  193. statusClass: 'red',
  194. statusLabel: '已驳回',
  195. actions: [],
  196. status: 3
  197. },
  198. {
  199. id: 5,
  200. orderNo: 'RE82738127861525',
  201. userName: '周小艺',
  202. phone: '138****1234',
  203. cancelTime: '2025-03-20 12:00',
  204. statusText: '已取消',
  205. statusClass: 'gray',
  206. statusLabel: '已取消',
  207. actions: [],
  208. status: 4
  209. },
  210. {
  211. id: 6,
  212. orderNo: 'RE82738127861526',
  213. userName: '周小艺',
  214. phone: '138****1234',
  215. qualityTime: '2025-03-20 12:00',
  216. statusText: '已结款',
  217. statusClass: 'blue',
  218. statusLabel: '已结款',
  219. actions: [],
  220. status: 2
  221. }
  222. ],
  223. searchMode: false,
  224. searchText: '',
  225. historyOrderMode: false,
  226. }
  227. },
  228. onLoad(options) {
  229. const sys = uni.getSystemInfoSync();
  230. this.statusBarHeight = sys.statusBarHeight;
  231. this.$nextTick(() => {
  232. uni.createSelectorQuery().select('.nav-bar').boundingClientRect(rect => {
  233. if (rect) {
  234. this.navBarRealHeight = rect.height;
  235. }
  236. }).exec();
  237. });
  238. if (options && options.historyOrder) {
  239. this.historyOrderMode = true;
  240. }
  241. },
  242. computed: {
  243. filteredOrders() {
  244. if (this.searchText) {
  245. const text = this.searchText.toLowerCase();
  246. return this.orderList.filter(order =>
  247. (order.orderNo && order.orderNo.toLowerCase().includes(text)) ||
  248. (order.userName && order.userName.toLowerCase().includes(text)) ||
  249. (order.phone && order.phone.toLowerCase().includes(text))
  250. );
  251. }
  252. const tabValue = this.tabs[this.currentTab].value;
  253. if (tabValue === -1) return this.orderList;
  254. return this.orderList.filter(order => order.status === tabValue);
  255. }
  256. },
  257. methods: {
  258. goBack() {
  259. uni.navigateBack()
  260. },
  261. switchTab(idx) {
  262. this.currentTab = idx
  263. },
  264. onSearchIconClick() {
  265. this.searchMode = true;
  266. this.$nextTick(() => {
  267. this.$refs.searchInput && this.$refs.searchInput.focus();
  268. });
  269. },
  270. onClearSearch() {
  271. this.searchText = '';
  272. },
  273. onCancelSearch() {
  274. this.searchText = '';
  275. this.searchMode = false;
  276. },
  277. goToOrderDetail(order) {
  278. // 根据订单状态动态设置订单详情页的显示内容
  279. const orderDetail = {
  280. status: order.status,
  281. statusText: order.statusText,
  282. statusLabel: order.statusLabel,
  283. statusClass: order.statusClass,
  284. estimate: '73.6~75.8',
  285. items: [
  286. { name: '羽绒服', desc: '允许脏破烂,160码以上', price: 8, count: 8, total: 64, img: '/static/coat1.png' },
  287. { name: '品牌羽绒服', desc: '允许脏破烂,160码以上', price: 10, count: 8, total: 8, img: '/static/coat2.png' }
  288. ]
  289. }
  290. // 跳转到订单详情页
  291. uni.navigateTo({
  292. url: '/pages/manager/order-detail',
  293. success: (res) => {
  294. res.eventChannel.emit('orderDetail', orderDetail)
  295. }
  296. })
  297. },
  298. refreshData() {
  299. // TODO: 实现订单列表刷新逻辑,如重新请求接口
  300. },
  301. async onRefresh() {
  302. await this.refreshData && this.refreshData()
  303. },
  304. },
  305. onPullDownRefresh() {
  306. this.refreshData && this.refreshData()
  307. uni.stopPullDownRefresh()
  308. }
  309. }
  310. </script>
  311. <style lang="scss" scoped>
  312. .order-manage-container {
  313. background: #f8f8f8;
  314. min-height: 100vh;
  315. padding-bottom: 24px;
  316. }
  317. .nav-bar {
  318. display: flex;
  319. align-items: center;
  320. justify-content: space-between;
  321. position: fixed;
  322. top: 0;
  323. left: 0;
  324. right: 0;
  325. z-index: 100;
  326. background: #fff;
  327. padding: 0 32rpx;
  328. box-sizing: border-box;
  329. .nav-title {
  330. flex: 1;
  331. text-align: center;
  332. font-size: 36rpx;
  333. font-weight: bold;
  334. color: #222;
  335. }
  336. .nav-icons {
  337. display: flex;
  338. align-items: center;
  339. gap: 32rpx;
  340. }
  341. }
  342. .order-tabs-scroll {
  343. position: fixed;
  344. left: 0;
  345. width: 100%;
  346. z-index: 99;
  347. background: #fff;
  348. border-bottom: 1px solid #f0f0f0;
  349. height: 96rpx;
  350. overflow-x: auto;
  351. white-space: nowrap;
  352. -webkit-overflow-scrolling: touch;
  353. &::-webkit-scrollbar {
  354. display: none;
  355. }
  356. }
  357. .order-tabs {
  358. display: flex;
  359. width: 100%;
  360. }
  361. .tab-item {
  362. flex: 1 0 0%;
  363. text-align: center;
  364. font-size: 34rpx;
  365. color: #bfbfbf;
  366. height: 96rpx;
  367. line-height: 96rpx;
  368. position: relative;
  369. font-weight: 500;
  370. transition: color 0.2s;
  371. letter-spacing: 0.5px;
  372. }
  373. .tab-item.active {
  374. color: #ffb400;
  375. font-weight: bold;
  376. }
  377. .tab-item.active::after {
  378. content: '';
  379. display: block;
  380. margin: 0 auto;
  381. margin-top: 2px;
  382. width: 22px;
  383. height: 3px;
  384. border-radius: 2px;
  385. background: #ffb400;
  386. }
  387. .search-bar {
  388. width: 100vw;
  389. height: 40px;
  390. position: fixed;
  391. z-index: 10;
  392. left: 0;
  393. top: 0;
  394. display: flex;
  395. align-items: center;
  396. }
  397. .search-bar-inner {
  398. margin: 0 16px;
  399. background: #fff;
  400. border-radius: 20px;
  401. height: 40px;
  402. flex: 1;
  403. display: flex;
  404. align-items: center;
  405. box-shadow: 0 2px 8px rgba(0,0,0,0.02);
  406. padding: 0 12px;
  407. justify-content: space-around;
  408. .search-icon {
  409. margin-right: 8px;
  410. }
  411. .scan-icon {
  412. margin-left: 8px;
  413. }
  414. }
  415. .search-input-wrap {
  416. display: flex;
  417. align-items: center;
  418. flex: 1;
  419. background: #f5f5f5;
  420. border-radius: 20px;
  421. height: 32px;
  422. margin: 0 0;
  423. padding: 0 8px;
  424. .search-input {
  425. flex: 1;
  426. border: none;
  427. outline: none;
  428. background: transparent;
  429. font-size: 15px;
  430. color: #222;
  431. margin-left: 8px;
  432. }
  433. }
  434. .search-cancel {
  435. margin-left: 8px;
  436. color: #999;
  437. font-size: 15px;
  438. line-height: 40px;
  439. }
  440. .order-list {
  441. margin: 0;
  442. padding-top: calc(var(--status-bar-height, 0px) + 44px + 44px + 16px);
  443. }
  444. .order-card {
  445. background: #fff;
  446. border-radius: 20px;
  447. margin: 0 16px 16px 16px;
  448. padding: 20px;
  449. box-shadow: 0 2px 8px rgba(0,0,0,0.04);
  450. }
  451. .order-card-header {
  452. display: flex;
  453. justify-content: space-between;
  454. align-items: flex-start;
  455. margin-bottom: 12px;
  456. .order-id {
  457. font-size: 16px;
  458. font-weight: bold;
  459. color: #222;
  460. }
  461. .order-status-tag {
  462. font-size: 14px;
  463. border-radius: 12px;
  464. padding: 2px 12px;
  465. &.green { background: #e6f9e6; color: #1ecb1e; }
  466. &.red { background: #ffeaea; color: #ff4d4f; }
  467. &.orange { background: #fff7e6; color: #ffb400; }
  468. &.blue { background: #e6f0ff; color: #409eff; }
  469. &.gray { background: #f5f5f5; color: #999; }
  470. }
  471. }
  472. .order-info-wrapper {
  473. position: relative;
  474. .order-info-status {
  475. position: absolute;
  476. right: 0;
  477. bottom: 0;
  478. font-size: 14px;
  479. border-radius: 12px;
  480. padding: 2px 12px;
  481. &.green { background: #e6f9e6; color: #1ecb1e; }
  482. &.red { background: #ffeaea; color: #ff4d4f; }
  483. &.orange { background: #fff7e6; color: #ffb400; }
  484. &.blue { background: #e6f0ff; color: #409eff; }
  485. &.gray { background: #f5f5f5; color: #999; }
  486. }
  487. }
  488. .order-info {
  489. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  490. font-weight: 400;
  491. font-size: 14px;
  492. line-height: 1.4;
  493. letter-spacing: 0;
  494. vertical-align: middle;
  495. color: #666;
  496. margin-bottom: 12px;
  497. view {
  498. margin-bottom: 4px;
  499. display: flex;
  500. align-items: center;
  501. }
  502. .info-label {
  503. color: #999;
  504. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  505. font-weight: 400;
  506. font-size: 14px;
  507. line-height: 1.4;
  508. margin-right: 4px;
  509. }
  510. .info-value {
  511. color: #222;
  512. font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
  513. font-weight: 400;
  514. font-size: 14px;
  515. line-height: 1.4;
  516. }
  517. }
  518. .order-card-footer {
  519. display: flex;
  520. align-items: center;
  521. justify-content: center;
  522. margin: 0 -20px -20px -20px;
  523. padding: 0 20px;
  524. border-bottom-left-radius: 20px;
  525. border-bottom-right-radius: 20px;
  526. background: #fafbfc;
  527. min-height: 60px;
  528. position: relative;
  529. .order-actions-bar {
  530. display: flex;
  531. flex: 1;
  532. justify-content: center;
  533. align-items: center;
  534. gap: 48px;
  535. .action-btn-bar {
  536. display: flex;
  537. flex-direction: column;
  538. align-items: center;
  539. font-size: 14px;
  540. color: #666;
  541. margin-top: 8px;
  542. margin-bottom: 8px;
  543. uni-icons {
  544. margin-bottom: 2px;
  545. }
  546. }
  547. }
  548. }
  549. </style>