index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <template>
  2. <view class="list-page-container">
  3. <erp-nav-bar title="全部订单" />
  4. <!-- 2. 分类切换:使用翻译位移,彻底消除红框2残影 -->
  5. <view class="tabs-fixed">
  6. <view class="tabs-box">
  7. <view v-for="(tab, index) in tabs" :key="index" class="tab-item"
  8. :class="{ active: currentTab === index }" @click="switchTab(index)">
  9. <text class="tab-txt">{{ tab }}</text>
  10. </view>
  11. <!-- 指示器:只负责位移,不负责显隐,彻底杜绝虚影 -->
  12. <view class="indicator-track">
  13. <view class="indicator-bar" :style="{ transform: 'translateX(' + (currentTab * 100) + '%)' }">
  14. <view class="bar-inner"></view>
  15. </view>
  16. </view>
  17. </view>
  18. </view>
  19. <!-- 3. 固定高度的滚动区:强制启用滚动,解决不能滑动问题 -->
  20. <scroll-view scroll-y class="order-scroll-view" :style="{ height: scrollHeight }" @scrolltolower="onReachEnd"
  21. :refresher-enabled="false" :show-scrollbar="false">
  22. <view class="order-list-inner">
  23. <view class="order-card" v-for="(item, index) in displayList" :key="index" @click="goDetail(item)">
  24. <view class="card-head">
  25. <text class="order-id">单号:{{ item.orderNo }}</text>
  26. <view class="status-badge" :class="item.statusType">{{ item.statusName }}</view>
  27. </view>
  28. <view class="card-body">
  29. <view class="model-row" v-for="(model, mIdx) in item.models.slice(0, 2)" :key="mIdx">
  30. <text class="m-type">{{ model.type }}</text>
  31. <text class="m-spec">{{ model.surface }} | {{ model.length }}mm</text>
  32. <text class="m-count">{{ model.count }} 支</text>
  33. </view>
  34. <view class="more-hint" v-if="item.models.length > 2">
  35. <text>查看更多 {{ item.models.length - 2 }} 个型号...</text>
  36. </view>
  37. <view class="total-summary">
  38. <text class="summary-label">共计:</text>
  39. <text class="summary-val">{{ item.models.length }}</text>
  40. <text class="summary-unit">个型号</text>
  41. <view class="summary-split"></view>
  42. <text class="summary-val highlight">{{ item.totalCount }}</text>
  43. <text class="summary-unit">支</text>
  44. </view>
  45. </view>
  46. <view class="card-foot">
  47. <text class="time">{{ item.time }}</text>
  48. <view class="btns">
  49. <view class="btn-audit" v-if="item.status > 0" @click.stop="showAuditHistory(item)">审核记录
  50. </view>
  51. <view class="btn-cancel" v-if="item.status === 0" @click.stop="onCancel(item)">撤销</view>
  52. <view class="btn-view primary" @click.stop="goDetail(item)">订单详情</view>
  53. </view>
  54. </view>
  55. </view>
  56. <!-- 加载提示区 -->
  57. <view class="list-status-info" v-if="displayList.length > 0">
  58. <view class="loading-wrap" v-if="loading">
  59. <view class="load-dot"></view><text>排队加载中...</text>
  60. </view>
  61. <view class="nomore-wrap" v-if="noMore">
  62. <text class="nomore-line"></text>
  63. <text class="nomore-text">已加载全部数据</text>
  64. <text class="nomore-line"></text>
  65. </view>
  66. </view>
  67. <!-- 缺省态 -->
  68. <view class="empty-state" v-if="displayList.length === 0 && !loading">
  69. <image src="https://img.icons8.com/clouds/200/open-box.png" mode="aspectFit"></image>
  70. <text>暂无订单记录</text>
  71. </view>
  72. <view class="safe-bottom"></view>
  73. </view>
  74. </scroll-view>
  75. <!-- 底部菜单栏 -->
  76. <erp-tab-bar active="order"></erp-tab-bar>
  77. <!-- 审核记录弹窗 -->
  78. <view class="overlay" v-if="auditHistoryVisible" @click="auditHistoryVisible = false">
  79. <view class="audit-history-modal" @click.stop>
  80. <view class="modal-head">
  81. <text class="modal-title">审核记录</text>
  82. <text class="modal-close" @click="auditHistoryVisible = false">关闭</text>
  83. </view>
  84. <scroll-view scroll-y class="modal-body">
  85. <view class="audit-item" v-for="(item, idx) in auditHistoryList" :key="idx">
  86. <view class="audit-dot" :class="item.auditResult === 1 ? 'pass' : 'reject'"></view>
  87. <view class="audit-content">
  88. <view class="audit-row">
  89. <text class="audit-result" :class="item.auditResult === 1 ? 'pass' : 'reject'">{{
  90. item.auditResult === 1 ? '通过' : '驳回' }}</text>
  91. <text class="audit-auditor">{{ item.auditorName || '未知' }}</text>
  92. </view>
  93. <text class="audit-reason" v-if="item.rejectReason">驳回理由:{{ item.rejectReason }}</text>
  94. <text class="audit-time">{{ item.auditTime }}</text>
  95. </view>
  96. </view>
  97. <view class="audit-empty" v-if="auditHistoryList.length === 0">
  98. <text>暂无审核记录</text>
  99. </view>
  100. </scroll-view>
  101. </view>
  102. </view>
  103. </view>
  104. </template>
  105. <script>
  106. import ErpTabBar from '@/components/erp-tab-bar.vue';
  107. import ErpNavBar from '@/components/erp-nav-bar.vue';
  108. import { listOrder, cancelOrder, getAuditHistory } from '@/api/erp/order.js';
  109. export default {
  110. components: { ErpTabBar, ErpNavBar },
  111. data() {
  112. return {
  113. statusBarHeight: 20,
  114. navBarHeight: 44,
  115. tabBarHeight: 50, // 对应 100rpx
  116. currentTab: 0,
  117. loading: false,
  118. noMore: false,
  119. tabs: ['全部', '已撤销', '待审核', '已驳回', '待签批', '生产中', '已完成'],
  120. allOrders: [],
  121. displayList: [],
  122. auditHistoryVisible: false,
  123. auditHistoryList: []
  124. }
  125. },
  126. computed: {
  127. scrollHeight() {
  128. // 减去状态栏、导航栏、选项卡的高度
  129. return `calc(100vh - ${this.statusBarHeight + this.navBarHeight + this.tabBarHeight}px)`;
  130. }
  131. },
  132. onLoad(options) {
  133. const info = uni.getSystemInfoSync();
  134. this.statusBarHeight = info.statusBarHeight;
  135. // 修正选项卡高度(单位px)
  136. this.tabBarHeight = info.windowWidth / 750 * 110;
  137. if (options.tab) this.currentTab = parseInt(options.tab);
  138. this.refresh();
  139. },
  140. methods: {
  141. goBack() { uni.navigateBack(); },
  142. switchTab(i) { this.currentTab = i; this.refresh(); },
  143. refresh() { this.displayList = []; this.noMore = false; this.loadData(); },
  144. onReachEnd() { if (!this.loading && !this.noMore) this.loadData(); },
  145. async loadData() {
  146. if (this.loading || this.noMore) return;
  147. this.loading = true;
  148. try {
  149. const params = {
  150. pageNum: 1, // 简易处理,先加载第一页
  151. pageSize: 50
  152. };
  153. // 状态映射
  154. if (this.currentTab > 0) {
  155. const tabToStatus = [undefined, -1, 0, 1, 2, 3, 4];
  156. params.status = tabToStatus[this.currentTab];
  157. }
  158. const res = await listOrder(params);
  159. const rows = res.rows || [];
  160. const formattedRows = rows.map(item => {
  161. // 状态展示映射(与 ErpOrderStatus 枚举对齐)
  162. const statusMap = {
  163. '-1': { name: '已撤销', type: 'cancelled' },
  164. 0: { name: '待审核', type: 'pending' },
  165. 1: { name: '已驳回', type: 'expired' },
  166. 2: { name: '待签批', type: 'process' },
  167. 3: { name: '生产中', type: 'making' },
  168. 4: { name: '已完成', type: 'finish' }
  169. };
  170. const s = statusMap[item.status] || { name: '未知', type: 'expired' };
  171. return {
  172. orderNo: item.code,
  173. rowId: item.rowId,
  174. status: item.status,
  175. statusName: s.name,
  176. statusType: s.type,
  177. time: item.createTime,
  178. totalCount: item.totalCount || 0,
  179. models: (item.details || []).map(d => ({
  180. type: d.modelNum || '未知型号',
  181. length: Number(d.length || 0).toFixed(4),
  182. surface: d.surfaceName || '无',
  183. count: d.count || 0
  184. }))
  185. };
  186. });
  187. this.displayList = [...this.displayList, ...formattedRows];
  188. this.noMore = this.displayList.length >= (res.total || 0);
  189. } catch (e) {
  190. console.error('加载订单列表失败', e);
  191. uni.showToast({ title: e || '加载订单列表失败', icon: 'none' });
  192. } finally {
  193. this.loading = false;
  194. }
  195. },
  196. async onCancel(item) {
  197. uni.showModal({
  198. title: '确认撤销',
  199. content: `确认要撤销订单:${item.orderNo} 吗?`,
  200. confirmColor: '#ff4d4f',
  201. success: async (res) => {
  202. if (res.confirm) {
  203. uni.showLoading({ title: '撤销中' });
  204. try {
  205. await cancelOrder(item.rowId);
  206. uni.hideLoading();
  207. uni.showToast({ title: '已撤销', icon: 'success' });
  208. this.refresh();
  209. } catch (e) {
  210. uni.hideLoading();
  211. uni.showToast({ title: e || '撤销失败', icon: 'none' });
  212. }
  213. }
  214. }
  215. });
  216. },
  217. async showAuditHistory(item) {
  218. uni.showLoading({ title: '加载中' });
  219. try {
  220. const res = await getAuditHistory(item.rowId);
  221. this.auditHistoryList = res.data || [];
  222. } catch (e) {
  223. uni.showToast({ title: e || '加载审核记录失败', icon: 'none' });
  224. this.auditHistoryList = [];
  225. } finally {
  226. uni.hideLoading();
  227. this.auditHistoryVisible = true;
  228. }
  229. },
  230. goDetail(item) {
  231. uni.navigateTo({
  232. url: `/pages/order/detail/index?rowId=${item.rowId}`
  233. });
  234. }
  235. }
  236. }
  237. </script>
  238. <style scoped>
  239. /deep/ ::-webkit-scrollbar {
  240. display: none !important;
  241. width: 0 !important;
  242. height: 0 !important;
  243. -webkit-appearance: none;
  244. background: transparent;
  245. }
  246. .list-page-container {
  247. width: 100vw;
  248. height: 100vh;
  249. background: #f8fafc;
  250. overflow: hidden;
  251. display: flex;
  252. flex-direction: column;
  253. }
  254. /* 2. 选项卡:解决残影红框2 */
  255. .tabs-fixed {
  256. background: #fff;
  257. width: 100%;
  258. flex-shrink: 0;
  259. border-bottom: 1rpx solid #f0f0f0;
  260. }
  261. .tabs-box {
  262. height: 110rpx;
  263. position: relative;
  264. display: flex;
  265. width: 100%;
  266. }
  267. .tab-item {
  268. flex: 1;
  269. display: flex;
  270. align-items: center;
  271. justify-content: center;
  272. z-index: 5;
  273. }
  274. .tab-txt {
  275. font-size: 28rpx;
  276. color: #888;
  277. transition: all 0.2s;
  278. }
  279. .tab-item.active .tab-txt {
  280. color: #C1001C;
  281. font-weight: bold;
  282. font-size: 32rpx;
  283. }
  284. /* 指示器轨道:通过位移消除虚影 */
  285. .indicator-track {
  286. position: absolute;
  287. bottom: 10rpx;
  288. left: 0;
  289. width: 100%;
  290. height: 6rpx;
  291. display: flex;
  292. }
  293. .indicator-bar {
  294. width: 14.285%;
  295. height: 100%;
  296. display: flex;
  297. align-items: center;
  298. justify-content: center;
  299. transition: transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
  300. }
  301. .bar-inner {
  302. width: 40rpx;
  303. height: 100%;
  304. background: #C1001C;
  305. border-radius: 6rpx;
  306. }
  307. /* 3. 滚动区:解决不可滑动问题 */
  308. .order-scroll-view {
  309. width: 100%;
  310. }
  311. .order-list-inner {
  312. padding: 30rpx;
  313. padding-bottom: 60rpx;
  314. }
  315. .order-card {
  316. background: #fff;
  317. border-radius: 24rpx;
  318. padding: 36rpx;
  319. margin-bottom: 30rpx;
  320. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
  321. }
  322. .card-head {
  323. display: flex;
  324. justify-content: space-between;
  325. align-items: center;
  326. margin-bottom: 30rpx;
  327. }
  328. .order-id {
  329. font-size: 28rpx;
  330. color: #1a1a1a;
  331. font-weight: bold;
  332. }
  333. .status-badge {
  334. font-size: 20rpx;
  335. padding: 4rpx 16rpx;
  336. border-radius: 8rpx;
  337. }
  338. .status-badge.pending {
  339. background: #FFF1F2;
  340. color: #C1001C;
  341. }
  342. .status-badge.expired {
  343. background: #f5f5f5;
  344. color: #999;
  345. }
  346. .status-badge.cancelled {
  347. background: #f0f0f0;
  348. color: #666;
  349. }
  350. .status-badge.process {
  351. background: #fff7e6;
  352. color: #ffa940;
  353. }
  354. .status-badge.making {
  355. background: #e6fffb;
  356. color: #36cfc9;
  357. }
  358. .status-badge.finish {
  359. background: #f6ffed;
  360. color: #52c41a;
  361. }
  362. .card-body {
  363. padding: 24rpx 0;
  364. border-top: 1rpx dashed #f0f0f0;
  365. margin-bottom: 24rpx;
  366. }
  367. .model-row {
  368. display: flex;
  369. align-items: center;
  370. margin-bottom: 12rpx;
  371. font-size: 24rpx;
  372. }
  373. .m-type {
  374. font-size: 28rpx;
  375. font-weight: bold;
  376. color: #333;
  377. width: 140rpx;
  378. }
  379. .m-spec {
  380. color: #888;
  381. flex: 1;
  382. padding: 0 20rpx;
  383. overflow: hidden;
  384. text-overflow: ellipsis;
  385. white-space: nowrap;
  386. }
  387. .m-count {
  388. color: #555;
  389. width: 100rpx;
  390. text-align: right;
  391. font-weight: 500;
  392. }
  393. .more-hint {
  394. padding: 10rpx 0;
  395. font-size: 24rpx;
  396. color: #999;
  397. text-align: center;
  398. background: #fafafa;
  399. border-radius: 8rpx;
  400. margin-bottom: 16rpx;
  401. }
  402. .total-summary {
  403. display: flex;
  404. align-items: center;
  405. justify-content: flex-end;
  406. padding-top: 20rpx;
  407. border-top: 1rpx solid #fafafa;
  408. }
  409. .summary-label {
  410. font-size: 24rpx;
  411. color: #999;
  412. }
  413. .summary-val {
  414. font-size: 32rpx;
  415. font-weight: bold;
  416. color: #333;
  417. margin: 0 4rpx;
  418. }
  419. .summary-val.highlight {
  420. color: #C1001C;
  421. }
  422. .summary-unit {
  423. font-size: 22rpx;
  424. color: #999;
  425. margin-right: 12rpx;
  426. }
  427. .summary-split {
  428. width: 1rpx;
  429. height: 20rpx;
  430. background: #eee;
  431. margin: 0 16rpx;
  432. }
  433. .card-foot {
  434. display: flex;
  435. justify-content: space-between;
  436. align-items: center;
  437. padding-top: 10rpx;
  438. }
  439. .time {
  440. font-size: 24rpx;
  441. color: #bbb;
  442. }
  443. .btns {
  444. display: flex;
  445. gap: 16rpx;
  446. }
  447. .btn-cancel,
  448. .btn-view {
  449. padding: 12rpx 30rpx;
  450. border-radius: 30rpx;
  451. font-size: 24rpx;
  452. }
  453. .btn-cancel {
  454. border: 1rpx solid #ddd;
  455. color: #666;
  456. }
  457. .btn-audit {
  458. border: 1rpx solid #ddd;
  459. color: #C1001C;
  460. padding: 12rpx 20rpx;
  461. border-radius: 30rpx;
  462. font-size: 24rpx;
  463. }
  464. .btn-view.primary {
  465. background: #C1001C;
  466. color: #fff;
  467. }
  468. .list-status-info {
  469. padding: 40rpx 0;
  470. display: flex;
  471. justify-content: center;
  472. }
  473. .nomore-wrap {
  474. display: flex;
  475. align-items: center;
  476. color: #ccc;
  477. font-size: 24rpx;
  478. }
  479. .nomore-line {
  480. width: 40rpx;
  481. height: 1rpx;
  482. background: #eee;
  483. margin: 0 20rpx;
  484. }
  485. .loading-wrap {
  486. color: #999;
  487. font-size: 24rpx;
  488. display: flex;
  489. align-items: center;
  490. }
  491. .load-dot {
  492. width: 10rpx;
  493. height: 10rpx;
  494. background: #C1001C;
  495. border-radius: 50%;
  496. margin-right: 10rpx;
  497. animation: flash 0.6s infinite alternate;
  498. }
  499. @keyframes flash {
  500. from {
  501. opacity: 0.3;
  502. }
  503. to {
  504. opacity: 1;
  505. }
  506. }
  507. .empty-state {
  508. display: flex;
  509. flex-direction: column;
  510. align-items: center;
  511. padding-top: 200rpx;
  512. color: #bbb;
  513. font-size: 28rpx;
  514. }
  515. .empty-state image {
  516. width: 220rpx;
  517. height: 220rpx;
  518. margin-bottom: 30rpx;
  519. opacity: 0.6;
  520. }
  521. .safe-bottom {
  522. height: 40rpx;
  523. }
  524. /* 审核记录弹窗 */
  525. .overlay {
  526. position: fixed;
  527. top: 0;
  528. left: 0;
  529. right: 0;
  530. bottom: 0;
  531. background: rgba(0, 0, 0, 0.45);
  532. z-index: 1000;
  533. display: flex;
  534. align-items: center;
  535. justify-content: center;
  536. }
  537. .audit-history-modal {
  538. width: 640rpx;
  539. max-height: 70vh;
  540. background: #fff;
  541. border-radius: 24rpx;
  542. overflow: hidden;
  543. display: flex;
  544. flex-direction: column;
  545. }
  546. .modal-head {
  547. display: flex;
  548. justify-content: space-between;
  549. align-items: center;
  550. padding: 30rpx 36rpx;
  551. border-bottom: 1rpx solid #f0f0f0;
  552. flex-shrink: 0;
  553. }
  554. .modal-title {
  555. font-size: 32rpx;
  556. font-weight: bold;
  557. color: #1a1a1a;
  558. }
  559. .modal-close {
  560. font-size: 26rpx;
  561. color: #999;
  562. padding: 8rpx;
  563. }
  564. .modal-body {
  565. flex: 1;
  566. padding: 20rpx 36rpx 36rpx;
  567. max-height: 60vh;
  568. }
  569. .audit-item {
  570. display: flex;
  571. padding: 24rpx 0;
  572. border-left: 2rpx solid #eee;
  573. margin-left: 12rpx;
  574. position: relative;
  575. }
  576. .audit-item:last-child {
  577. border-left-color: transparent;
  578. }
  579. .audit-dot {
  580. width: 16rpx;
  581. height: 16rpx;
  582. border-radius: 50%;
  583. position: absolute;
  584. left: -9rpx;
  585. top: 28rpx;
  586. }
  587. .audit-dot.pass {
  588. background: #52c41a;
  589. }
  590. .audit-dot.reject {
  591. background: #ff4d4f;
  592. }
  593. .audit-content {
  594. margin-left: 30rpx;
  595. flex: 1;
  596. }
  597. .audit-row {
  598. display: flex;
  599. align-items: center;
  600. gap: 16rpx;
  601. margin-bottom: 8rpx;
  602. }
  603. .audit-result {
  604. font-size: 26rpx;
  605. font-weight: bold;
  606. }
  607. .audit-result.pass {
  608. color: #52c41a;
  609. }
  610. .audit-result.reject {
  611. color: #ff4d4f;
  612. }
  613. .audit-auditor {
  614. font-size: 24rpx;
  615. color: #666;
  616. }
  617. .audit-reason {
  618. display: block;
  619. font-size: 24rpx;
  620. color: #ff4d4f;
  621. background: #fff1f0;
  622. padding: 12rpx 16rpx;
  623. border-radius: 8rpx;
  624. margin-bottom: 8rpx;
  625. line-height: 1.5;
  626. }
  627. .audit-time {
  628. display: block;
  629. font-size: 22rpx;
  630. color: #bbb;
  631. margin-top: 4rpx;
  632. }
  633. .audit-empty {
  634. text-align: center;
  635. padding: 60rpx 0;
  636. color: #bbb;
  637. font-size: 28rpx;
  638. }
  639. </style>