index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <template>
  2. <view class="detail-page-container">
  3. <erp-nav-bar title="订单详情" />
  4. <!-- 2. 状态横幅区域:不再包含状态栏边距 -->
  5. <view class="status-banner-container" :class="order.statusType" id="nav-header">
  6. <view class="status-banner-content">
  7. <view class="header-main">
  8. <view class="header-title-row">
  9. <text class="status-title">{{ order.statusName }}</text>
  10. <view class="urgent-badge" v-if="order.isUrgent">
  11. <text>加急</text>
  12. </view>
  13. </view>
  14. <text class="status-sub">{{ statusSubText }}</text>
  15. </view>
  16. <view class="header-icon-wrap">
  17. <view class="status-visual-icon"></view>
  18. </view>
  19. </view>
  20. </view>
  21. <!-- 2. 主内容滚动区:计算锁定高度以支持 100% 滚动 -->
  22. <scroll-view scroll-y class="main-content-scroll" :style="{ height: scrollHeight }" :show-scrollbar="false">
  23. <view class="detail-inner-box">
  24. <!-- 卡片:规格清单 (支持多个型号展现) -->
  25. <view class="model-item-card-inner" v-for="(model, mIdx) in (order.models || [order])" :key="mIdx">
  26. <view class="data-group-card" :class="{ 'first-card': mIdx === 0 }">
  27. <view class="card-head">规格清单 #{{ mIdx + 1 }}</view>
  28. <view class="data-item"><text class="l">产品型号</text><text class="v bold">{{ model.type }}</text>
  29. </view>
  30. <view class="data-item"><text class="l">型号名称</text><text class="v">{{ model.typeName ||
  31. '铝型材主料' }}</text></view>
  32. <view class="data-item"><text class="l">单据编号</text><text class="v">{{ model.docCode || '-'
  33. }}</text></view>
  34. <view class="data-item"><text class="l">项目号</text><text class="v">{{ model.itemNo || '-'
  35. }}</text></view>
  36. <view class="data-item"><text class="l">型材材质</text><text class="v">{{ model.material ||
  37. '6063-T5' }}</text></view>
  38. <view class="line-split"></view>
  39. <view class="data-item"><text class="l">表面处理</text><text class="v">{{ model.surface }}</text>
  40. </view>
  41. <view class="data-item"><text class="l">包装方式</text><text class="v">{{ model.package ||
  42. '普通包装' }}</text></view>
  43. <view class="line-split"></view>
  44. <view class="data-item"><text class="l">订单长度</text><text class="v">{{ model.length }} mm</text>
  45. </view>
  46. <view class="data-item"><text class="l">型材壁厚</text><text class="v">{{ model.wallThickness ||
  47. '1.2' }} mm</text></view>
  48. <view class="data-item"><text class="l">需求支数</text><text class="v highlight">{{ model.count }}
  49. 支</text></view>
  50. <view class="line-split"></view>
  51. <view class="data-item"><text class="l">挤压入库数</text><text class="v">{{ model.semiInQty != null ?
  52. model.semiInQty : '-' }}</text></view>
  53. <view class="data-item"><text class="l">成品入库数</text><text class="v">{{ model.goodsInQty != null
  54. ? model.goodsInQty : '-' }}</text></view>
  55. </view>
  56. </view>
  57. <!-- 卡片:订单详情 -->
  58. <view class="data-group-card shadow-less">
  59. <view class="card-head">订单详情</view>
  60. <view class="data-item"><text class="l">订单单号</text><text class="v selectable">{{ order.orderNo
  61. }}</text></view>
  62. <view class="data-item"><text class="l">单据编号</text><text class="v selectable">{{ order.docCode ||
  63. '-' }}</text></view>
  64. <view class="data-item"><text class="l">下单日期</text><text class="v">{{ order.time }}</text></view>
  65. <view class="data-item"><text class="l">支付方式</text><text class="v">月结扣款</text></view>
  66. </view>
  67. <!-- 底部占位 -->
  68. <view class="list-bottom-placeholder"></view>
  69. </view>
  70. </scroll-view>
  71. <!-- 3. 底部固定操作栏 -->
  72. <view class="detail-action-bar-fixed" id="footer-bar">
  73. <view class="action-btn-wrap" v-if="!order.isUrgent && order.isConfirmed !== null">
  74. <button class="action-btn urgency" @click="doMarkUrgent">加急订单</button>
  75. </view>
  76. <view class="action-btn-wrap single" v-if="order.isConfirmed === 0">
  77. <button class="action-btn primary" @click="callSales">呼叫业务员</button>
  78. </view>
  79. <view class="action-btn-wrap single" v-else>
  80. <button class="action-btn primary" @click="goHome">再下一单</button>
  81. </view>
  82. <view class="safe-area-bottom-support"></view>
  83. </view>
  84. </view>
  85. </template>
  86. <script>
  87. import ErpNavBar from '@/components/erp-nav-bar.vue';
  88. import { getOrderDetail, markUrgent } from '@/api/erp/order.js';
  89. import { getPhone } from '@/api/system/phone.js';
  90. export default {
  91. components: { ErpNavBar },
  92. data() {
  93. return {
  94. footerHeight: 80, // px
  95. headerHeight: 120, // px
  96. rowId: '',
  97. loading: false,
  98. salesPhone: '',
  99. order: {
  100. orderNo: '-',
  101. docCode: '',
  102. isConfirmed: null,
  103. isUrgent: false,
  104. status: null,
  105. statusName: '加载中',
  106. statusType: 'pending',
  107. models: [],
  108. erpNo: '',
  109. time: '-'
  110. }
  111. }
  112. },
  113. computed: {
  114. statusSubText() {
  115. const map = {
  116. pending: '您的订单已提交,正在等待管理端同步确认中...',
  117. finish: '订单已确认,并已成功同步到 ERP 系统中。',
  118. exFinished: '订单关联的产品型材已挤压完成。',
  119. productionFinish: '订单所含产品型材已全部生产完成!'
  120. };
  121. return map[this.order.statusType] || '订单状态更新中';
  122. },
  123. scrollHeight() {
  124. const info = uni.getSystemInfoSync();
  125. const safeBottom = info.safeAreaInsets ? info.safeAreaInsets.bottom : 0;
  126. const statusBarHeight = info.statusBarHeight || 20;
  127. return `calc(100vh - ${statusBarHeight + 164}px - ${80 + safeBottom}px)`;
  128. }
  129. },
  130. onLoad(options) {
  131. if (options.rowId) {
  132. this.rowId = options.rowId;
  133. this.loadOrderDetail();
  134. this.loadPhone();
  135. }
  136. },
  137. methods: {
  138. async loadOrderDetail() {
  139. if (this.loading) return;
  140. this.loading = true;
  141. try {
  142. const res = await getOrderDetail(this.rowId);
  143. const data = res.data || res;
  144. if (!data) {
  145. uni.showToast({ title: '订单不存在', icon: 'none' });
  146. return;
  147. }
  148. // 状态展示映射
  149. const statusMap = {
  150. 0: { name: '待确认', type: 'pending' },
  151. 1: { name: '已确认', type: 'finish' },
  152. 2: { name: '挤压完成', type: 'exFinished' },
  153. 3: { name: '生产完成', type: 'productionFinish' }
  154. };
  155. const s = statusMap[data.status] || { name: '待确认', type: 'pending' };
  156. this.order = {
  157. orderNo: data.code || '-',
  158. docCode: data.docCode || '',
  159. rowId: data.rowId,
  160. isConfirmed: data.isConfirmed,
  161. isUrgent: data.urgentFlag === 1,
  162. status: data.status,
  163. statusName: s.name,
  164. statusType: s.type,
  165. time: data.createTime || '-',
  166. totalCount: data.totalCount || 0,
  167. models: (data.details || []).map(d => ({
  168. type: d.modelNum || '未知型号',
  169. typeName: d.modelName || '铝型材主料',
  170. docCode: d.docCode || '',
  171. itemNo: d.itemNo || '',
  172. material: d.material || '6063-T5',
  173. surface: d.surfaceName || '无',
  174. package: d.packName || '普通包装',
  175. length: d.length ? Number(d.length).toFixed(4) : '0',
  176. wallThickness: d.wallThickness ? Number(d.wallThickness).toFixed(4) : '1.2',
  177. count: d.count || 0,
  178. goodsInQty: d.goodsInQty,
  179. semiInQty: d.semiInQty
  180. }))
  181. };
  182. } catch (e) {
  183. console.error('加载订单详情失败', e);
  184. uni.showToast({ title: e || '加载失败', icon: 'none' });
  185. } finally {
  186. this.loading = false;
  187. }
  188. },
  189. goBack() { uni.navigateBack(); },
  190. callSales() { uni.makePhoneCall({ phoneNumber: this.salesPhone }); },
  191. goHome() { uni.reLaunch({ url: '/pages/order/index' }); },
  192. doMarkUrgent() {
  193. uni.showModal({
  194. title: '加急确认',
  195. content: '确认将该订单标记为加急吗?',
  196. confirmColor: '#FF6600',
  197. success: async (res) => {
  198. if (!res.confirm) return;
  199. try {
  200. uni.showLoading({ title: '处理中' });
  201. await markUrgent(this.order.rowId);
  202. uni.hideLoading();
  203. this.order.isUrgent = true;
  204. uni.showToast({ title: '已标记为加急订单', icon: 'success' });
  205. } catch (e) {
  206. uni.hideLoading();
  207. uni.showToast({ title: e || '操作失败', icon: 'none' });
  208. }
  209. }
  210. });
  211. },
  212. async loadPhone() {
  213. try {
  214. const res = await getPhone();
  215. this.salesPhone = res.data.salesPhone || '13888888888';
  216. } catch (e) {
  217. console.error('加载联系电话失败', e);
  218. uni.showToast({ title: e || '加载联系电话失败', icon: 'none' });
  219. }
  220. }
  221. }
  222. }
  223. </script>
  224. <style scoped>
  225. /deep/ ::-webkit-scrollbar {
  226. display: none !important;
  227. width: 0 !important;
  228. height: 0 !important;
  229. -webkit-appearance: none;
  230. background: transparent;
  231. }
  232. .detail-page-container {
  233. width: 100vw;
  234. height: 100vh;
  235. background: #f8fbfd;
  236. display: flex;
  237. flex-direction: column;
  238. overflow: hidden;
  239. position: relative;
  240. }
  241. .status-banner-container {
  242. color: #fff;
  243. flex-shrink: 0;
  244. }
  245. .status-banner-container.pending {
  246. background: linear-gradient(135deg, #FF9900 0%, #FFB84D 100%);
  247. }
  248. .status-banner-container.finish {
  249. background: linear-gradient(135deg, #1890FF 0%, #69C0FF 100%);
  250. }
  251. .status-banner-container.exFinished {
  252. background: linear-gradient(135deg, #13C2C2 0%, #5CDBD3 100%);
  253. }
  254. .status-banner-container.productionFinish {
  255. background: linear-gradient(135deg, #2F54EB 0%, #85A5FF 100%);
  256. }
  257. /* 状态横幅内容 */
  258. .status-banner-content {
  259. padding: 40rpx;
  260. padding-bottom: 60rpx;
  261. display: flex;
  262. justify-content: space-between;
  263. align-items: center;
  264. }
  265. .header-main {
  266. flex: 1;
  267. }
  268. .status-title {
  269. font-size: 48rpx;
  270. font-weight: bold;
  271. display: block;
  272. margin-bottom: 12rpx;
  273. }
  274. .header-title-row {
  275. display: flex;
  276. align-items: center;
  277. gap: 16rpx;
  278. margin-bottom: 12rpx;
  279. }
  280. .header-title-row .status-title {
  281. margin-bottom: 0;
  282. }
  283. .urgent-badge {
  284. display: flex;
  285. align-items: center;
  286. padding: 6rpx 18rpx;
  287. background: rgba(255, 255, 255, 0.25);
  288. border: 2rpx solid rgba(255, 255, 255, 0.6);
  289. border-radius: 8rpx;
  290. animation: urgent-pulse 1.5s ease-in-out infinite;
  291. }
  292. .urgent-badge text {
  293. font-size: 22rpx;
  294. color: #fff;
  295. font-weight: 700;
  296. letter-spacing: 2rpx;
  297. }
  298. @keyframes urgent-pulse {
  299. 0%,
  300. 100% {
  301. opacity: 1;
  302. }
  303. 50% {
  304. opacity: 0.6;
  305. }
  306. }
  307. .status-sub {
  308. font-size: 26rpx;
  309. opacity: 0.9;
  310. }
  311. .status-visual-icon {
  312. width: 60rpx;
  313. height: 60rpx;
  314. border: 4rpx solid rgba(255, 255, 255, 0.3);
  315. border-radius: 50%;
  316. opacity: 0.6;
  317. }
  318. /* 修正:移除负边距,并明确滚动方向 */
  319. .main-content-scroll {
  320. width: 100%;
  321. flex: 1;
  322. }
  323. .detail-inner-box {
  324. padding: 30rpx;
  325. padding-top: 10rpx;
  326. }
  327. .data-group-card {
  328. background: #fff;
  329. border-radius: 30rpx;
  330. padding: 40rpx;
  331. margin-bottom: 30rpx;
  332. box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.02);
  333. }
  334. /* 第一张卡片增加顶部间隔(红框1优化点) */
  335. .first-card {
  336. margin-top: 20rpx;
  337. }
  338. .card-head {
  339. font-size: 30rpx;
  340. font-weight: bold;
  341. color: #333;
  342. margin-bottom: 30rpx;
  343. border-left: 8rpx solid #C1001C;
  344. padding-left: 20rpx;
  345. }
  346. .line-split {
  347. height: 1rpx;
  348. background: #f5f5f5;
  349. margin: 24rpx 0;
  350. }
  351. .data-item {
  352. display: flex;
  353. justify-content: space-between;
  354. margin-bottom: 24rpx;
  355. font-size: 28rpx;
  356. align-items: center;
  357. }
  358. .l {
  359. color: #999;
  360. }
  361. .v {
  362. color: #333;
  363. font-weight: 500;
  364. }
  365. .v.bold {
  366. font-weight: bold;
  367. font-size: 30rpx;
  368. }
  369. .v.erp-no {
  370. color: #C1001C;
  371. font-weight: bold;
  372. }
  373. .v.highlight {
  374. color: #ff3b30;
  375. font-weight: bold;
  376. font-size: 34rpx;
  377. }
  378. .list-bottom-placeholder {
  379. height: 260rpx;
  380. }
  381. .detail-action-bar-fixed {
  382. position: fixed;
  383. bottom: 0;
  384. left: 0;
  385. right: 0;
  386. background: #fff;
  387. padding: 30rpx 40rpx;
  388. box-shadow: 0 -10rpx 40rpx rgba(0, 0, 0, 0.04);
  389. z-index: 999;
  390. flex-shrink: 0;
  391. }
  392. .action-btn-wrap {
  393. display: flex;
  394. gap: 24rpx;
  395. }
  396. .action-btn {
  397. flex: 1;
  398. height: 96rpx;
  399. border-radius: 48rpx;
  400. display: flex;
  401. align-items: center;
  402. justify-content: center;
  403. font-size: 32rpx;
  404. font-weight: bold;
  405. }
  406. .action-btn.primary {
  407. background: #C1001C;
  408. color: #fff;
  409. border: none;
  410. }
  411. .action-btn.cancel {
  412. background: #fff1f0;
  413. color: #ff3b30;
  414. border: 1rpx solid #ffccc7;
  415. font-weight: normal;
  416. }
  417. .action-btn.urgency {
  418. background: #C1001C;
  419. color: #fff;
  420. border: none;
  421. margin-bottom: 16rpx;
  422. }
  423. .safe-area-bottom-support {
  424. height: constant(safe-area-inset-bottom);
  425. height: env(safe-area-inset-bottom);
  426. }
  427. </style>