index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  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-cancel" v-if="item.status === 1" @click.stop="onCancel(item)">撤销</view>
  50. <view class="btn-view primary" @click.stop="goDetail(item)">订单详情</view>
  51. </view>
  52. </view>
  53. </view>
  54. <!-- 加载提示区 -->
  55. <view class="list-status-info" v-if="displayList.length > 0">
  56. <view class="loading-wrap" v-if="loading">
  57. <view class="load-dot"></view><text>排队加载中...</text>
  58. </view>
  59. <view class="nomore-wrap" v-if="noMore">
  60. <text class="nomore-line"></text>
  61. <text class="nomore-text">已加载全部数据</text>
  62. <text class="nomore-line"></text>
  63. </view>
  64. </view>
  65. <!-- 缺省态 -->
  66. <view class="empty-state" v-if="displayList.length === 0 && !loading">
  67. <image src="https://img.icons8.com/clouds/200/open-box.png" mode="aspectFit"></image>
  68. <text>暂无订单记录</text>
  69. </view>
  70. <view class="safe-bottom"></view>
  71. </view>
  72. </scroll-view>
  73. <!-- 底部菜单栏 -->
  74. <erp-tab-bar active="order"></erp-tab-bar>
  75. </view>
  76. </template>
  77. <script>
  78. import ErpTabBar from '@/components/erp-tab-bar.vue';
  79. import ErpNavBar from '@/components/erp-nav-bar.vue';
  80. import { listOrder } from '@/api/erp/order.js';
  81. export default {
  82. components: { ErpTabBar, ErpNavBar },
  83. data() {
  84. return {
  85. statusBarHeight: 20,
  86. navBarHeight: 44,
  87. tabBarHeight: 50, // 对应 100rpx
  88. currentTab: 0,
  89. loading: false,
  90. noMore: false,
  91. tabs: ['全部', '待审核', '待签批', '生产中', '已完成'],
  92. allOrders: [
  93. {
  94. orderNo: 'ORD20240428001',
  95. models: [
  96. { type: 'TY0019', length: '6000', surface: '阳极氧化', count: '100' },
  97. { type: 'TY0018', length: '6000', surface: 'PL坯料', count: '50' }
  98. ],
  99. totalCount: 150,
  100. status: 1, statusName: '待审核', statusType: 'pending', time: '2024-04-28 10:20'
  101. },
  102. {
  103. orderNo: 'ORD20240428002',
  104. models: [
  105. { type: 'TY0020', length: '5800', surface: '粉末喷涂', count: '80' }
  106. ],
  107. totalCount: 80,
  108. status: 2, statusName: '待签批', statusType: 'process', time: '2024-04-28 09:15'
  109. },
  110. {
  111. orderNo: 'ORD20240427088',
  112. models: [
  113. { type: 'TY0018', length: '6000', surface: 'PL坯料', count: '300' }
  114. ],
  115. totalCount: 300,
  116. status: 4, statusName: '已完成', statusType: 'finish', time: '2024-04-27 15:40'
  117. },
  118. {
  119. orderNo: 'ORD20240427045',
  120. models: [
  121. { type: 'TY0021', length: '3000', surface: '电泳涂漆', count: '50' },
  122. { type: 'TY0022', length: '4000', surface: '木纹转印', count: '40' },
  123. { type: 'TY0023', length: '5000', surface: '氟碳喷涂', count: '30' }
  124. ],
  125. totalCount: 120,
  126. status: 3, statusName: '生产中', statusType: 'making', time: '2024-04-27 11:05'
  127. }
  128. ],
  129. displayList: []
  130. }
  131. },
  132. computed: {
  133. scrollHeight() {
  134. // 减去状态栏、导航栏、选项卡的高度
  135. return `calc(100vh - ${this.statusBarHeight + this.navBarHeight + this.tabBarHeight}px)`;
  136. }
  137. },
  138. onLoad(options) {
  139. const info = uni.getSystemInfoSync();
  140. this.statusBarHeight = info.statusBarHeight;
  141. // 修正选项卡高度(单位px)
  142. this.tabBarHeight = info.windowWidth / 750 * 110;
  143. if (options.tab) this.currentTab = parseInt(options.tab);
  144. this.refresh();
  145. },
  146. methods: {
  147. goBack() { uni.navigateBack(); },
  148. switchTab(i) { this.currentTab = i; this.refresh(); },
  149. refresh() { this.displayList = []; this.noMore = false; this.loadData(); },
  150. onReachEnd() { if (!this.loading && !this.noMore) this.loadData(); },
  151. async loadData() {
  152. if (this.loading || this.noMore) return;
  153. this.loading = true;
  154. try {
  155. const params = {};
  156. // 状态映射:0全部,1待审核(后台0),2待签批(后台1) ... 仅做简单对应,视后端枚举而定
  157. if (this.currentTab > 0) {
  158. params.fStatus = this.currentTab - 1;
  159. }
  160. const res = await listOrder(params);
  161. const rows = res.data || [];
  162. const formattedRows = rows.map(item => {
  163. // 状态展示映射
  164. let sName = '待审核', sType = 'pending';
  165. if (item.fStatus === 1) { sName = '已审核'; sType = 'process'; }
  166. else if (item.fStatus === 2) { sName = '已驳回'; sType = 'expired'; }
  167. else if (item.fStatus === 3) { sName = '生产中'; sType = 'making'; }
  168. else if (item.fStatus === 4) { sName = '已完成'; sType = 'finish'; }
  169. else if (item.fStatus === 0) { sName = '待审核'; sType = 'pending'; }
  170. return {
  171. orderNo: item.fCode,
  172. fRowId: item.fRowId,
  173. status: item.fStatus,
  174. statusName: sName,
  175. statusType: sType,
  176. time: item.createTime,
  177. totalCount: item.totalCount || 0,
  178. models: (item.details || []).map(d => ({
  179. type: d.modelNum || '未知型号',
  180. length: Number(d.length || 0).toFixed(4),
  181. surface: d.surfaceName || '无',
  182. count: d.count || 0
  183. }))
  184. };
  185. });
  186. this.displayList = [...this.displayList, ...formattedRows];
  187. this.noMore = true; // 此处暂且假设一页加载完所有,如果需要分页请加上 pageNum 和 total 判断
  188. } catch (e) {
  189. console.error('加载订单列表失败', e);
  190. } finally {
  191. this.loading = false;
  192. }
  193. },
  194. onCancel(item) {
  195. uni.showModal({
  196. title: '确认撤销',
  197. content: `确认要撤销订单:${item.orderNo} 吗?`,
  198. confirmColor: '#ff4d4f',
  199. success: (res) => {
  200. if (res.confirm) {
  201. uni.showLoading({ title: '撤销中' });
  202. setTimeout(() => {
  203. uni.hideLoading();
  204. uni.showToast({ title: '已撤销' });
  205. // 逻辑修复:修改状态而非移除
  206. const target = this.allOrders.find(o => o.orderNo === item.orderNo);
  207. if (target) {
  208. target.status = 0;
  209. target.statusName = '已撤销';
  210. target.statusType = 'expired';
  211. }
  212. // 再次过滤刷新当前视图
  213. this.refresh();
  214. }, 800);
  215. }
  216. }
  217. });
  218. },
  219. goDetail(item) {
  220. const dataStr = encodeURIComponent(JSON.stringify(item));
  221. uni.navigateTo({
  222. url: `/pages/order/detail/index?data=${dataStr}`
  223. });
  224. }
  225. }
  226. }
  227. </script>
  228. <style scoped>
  229. /deep/ ::-webkit-scrollbar {
  230. display: none !important;
  231. width: 0 !important;
  232. height: 0 !important;
  233. -webkit-appearance: none;
  234. background: transparent;
  235. }
  236. .list-page-container {
  237. width: 100vw;
  238. height: 100vh;
  239. background: #f8fafc;
  240. overflow: hidden;
  241. display: flex;
  242. flex-direction: column;
  243. }
  244. /* 2. 选项卡:解决残影红框2 */
  245. .tabs-fixed {
  246. background: #fff;
  247. width: 100%;
  248. flex-shrink: 0;
  249. border-bottom: 1rpx solid #f0f0f0;
  250. }
  251. .tabs-box {
  252. height: 110rpx;
  253. position: relative;
  254. display: flex;
  255. width: 100%;
  256. }
  257. .tab-item {
  258. flex: 1;
  259. display: flex;
  260. align-items: center;
  261. justify-content: center;
  262. z-index: 5;
  263. }
  264. .tab-txt {
  265. font-size: 28rpx;
  266. color: #888;
  267. transition: all 0.2s;
  268. }
  269. .tab-item.active .tab-txt {
  270. color: #C1001C;
  271. font-weight: bold;
  272. font-size: 32rpx;
  273. }
  274. /* 指示器轨道:通过位移消除虚影 */
  275. .indicator-track {
  276. position: absolute;
  277. bottom: 10rpx;
  278. left: 0;
  279. width: 100%;
  280. height: 6rpx;
  281. display: flex;
  282. }
  283. .indicator-bar {
  284. width: 20%;
  285. height: 100%;
  286. display: flex;
  287. align-items: center;
  288. justify-content: center;
  289. transition: transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
  290. }
  291. .bar-inner {
  292. width: 40rpx;
  293. height: 100%;
  294. background: #C1001C;
  295. border-radius: 6rpx;
  296. }
  297. /* 3. 滚动区:解决不可滑动问题 */
  298. .order-scroll-view {
  299. width: 100%;
  300. }
  301. .order-list-inner {
  302. padding: 30rpx;
  303. padding-bottom: 60rpx;
  304. }
  305. .order-card {
  306. background: #fff;
  307. border-radius: 24rpx;
  308. padding: 36rpx;
  309. margin-bottom: 30rpx;
  310. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
  311. }
  312. .card-head {
  313. display: flex;
  314. justify-content: space-between;
  315. align-items: center;
  316. margin-bottom: 30rpx;
  317. }
  318. .order-id {
  319. font-size: 28rpx;
  320. color: #1a1a1a;
  321. font-weight: bold;
  322. }
  323. .status-badge {
  324. font-size: 20rpx;
  325. padding: 4rpx 16rpx;
  326. border-radius: 8rpx;
  327. }
  328. .status-badge.pending {
  329. background: #FFF1F2;
  330. color: #C1001C;
  331. }
  332. .status-badge.expired {
  333. background: #f5f5f5;
  334. color: #999;
  335. }
  336. .status-badge.process {
  337. background: #fff7e6;
  338. color: #ffa940;
  339. }
  340. .status-badge.making {
  341. background: #e6fffb;
  342. color: #36cfc9;
  343. }
  344. /* 已撤销样式 */
  345. .status-badge.expired {
  346. background: #f5f5f5;
  347. color: #999;
  348. }
  349. .status-badge.finish {
  350. background: #f6ffed;
  351. color: #52c41a;
  352. }
  353. .card-body {
  354. padding: 24rpx 0;
  355. border-top: 1rpx dashed #f0f0f0;
  356. margin-bottom: 24rpx;
  357. }
  358. .model-row {
  359. display: flex;
  360. align-items: center;
  361. margin-bottom: 12rpx;
  362. font-size: 24rpx;
  363. }
  364. .m-type {
  365. font-size: 28rpx;
  366. font-weight: bold;
  367. color: #333;
  368. width: 140rpx;
  369. }
  370. .m-spec {
  371. color: #888;
  372. flex: 1;
  373. padding: 0 20rpx;
  374. overflow: hidden;
  375. text-overflow: ellipsis;
  376. white-space: nowrap;
  377. }
  378. .m-count {
  379. color: #555;
  380. width: 100rpx;
  381. text-align: right;
  382. font-weight: 500;
  383. }
  384. .more-hint {
  385. padding: 10rpx 0;
  386. font-size: 24rpx;
  387. color: #999;
  388. text-align: center;
  389. background: #fafafa;
  390. border-radius: 8rpx;
  391. margin-bottom: 16rpx;
  392. }
  393. .total-summary {
  394. display: flex;
  395. align-items: center;
  396. justify-content: flex-end;
  397. padding-top: 20rpx;
  398. border-top: 1rpx solid #fafafa;
  399. }
  400. .summary-label {
  401. font-size: 24rpx;
  402. color: #999;
  403. }
  404. .summary-val {
  405. font-size: 32rpx;
  406. font-weight: bold;
  407. color: #333;
  408. margin: 0 4rpx;
  409. }
  410. .summary-val.highlight {
  411. color: #C1001C;
  412. }
  413. .summary-unit {
  414. font-size: 22rpx;
  415. color: #999;
  416. margin-right: 12rpx;
  417. }
  418. .summary-split {
  419. width: 1rpx;
  420. height: 20rpx;
  421. background: #eee;
  422. margin: 0 16rpx;
  423. }
  424. .card-foot {
  425. display: flex;
  426. justify-content: space-between;
  427. align-items: center;
  428. padding-top: 10rpx;
  429. }
  430. .time {
  431. font-size: 24rpx;
  432. color: #bbb;
  433. }
  434. .btns {
  435. display: flex;
  436. gap: 16rpx;
  437. }
  438. .btn-cancel,
  439. .btn-view {
  440. padding: 12rpx 30rpx;
  441. border-radius: 30rpx;
  442. font-size: 24rpx;
  443. }
  444. .btn-cancel {
  445. border: 1rpx solid #ddd;
  446. color: #666;
  447. }
  448. .btn-view.primary {
  449. background: #C1001C;
  450. color: #fff;
  451. }
  452. .list-status-info {
  453. padding: 40rpx 0;
  454. display: flex;
  455. justify-content: center;
  456. }
  457. .nomore-wrap {
  458. display: flex;
  459. align-items: center;
  460. color: #ccc;
  461. font-size: 24rpx;
  462. }
  463. .nomore-line {
  464. width: 40rpx;
  465. height: 1rpx;
  466. background: #eee;
  467. margin: 0 20rpx;
  468. }
  469. .loading-wrap {
  470. color: #999;
  471. font-size: 24rpx;
  472. display: flex;
  473. align-items: center;
  474. }
  475. .load-dot {
  476. width: 10rpx;
  477. height: 10rpx;
  478. background: #C1001C;
  479. border-radius: 50%;
  480. margin-right: 10rpx;
  481. animation: flash 0.6s infinite alternate;
  482. }
  483. @keyframes flash {
  484. from {
  485. opacity: 0.3;
  486. }
  487. to {
  488. opacity: 1;
  489. }
  490. }
  491. .empty-state {
  492. display: flex;
  493. flex-direction: column;
  494. align-items: center;
  495. padding-top: 200rpx;
  496. color: #bbb;
  497. font-size: 28rpx;
  498. }
  499. .empty-state image {
  500. width: 220rpx;
  501. height: 220rpx;
  502. margin-bottom: 30rpx;
  503. opacity: 0.6;
  504. }
  505. .safe-bottom {
  506. height: 40rpx;
  507. }
  508. </style>