imeik-bizui 2.6.5 → 2.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -248,12 +248,16 @@ export default {
248
248
  }
249
249
  },
250
250
  async onViewDetail() {
251
- this.detailVisible = true
252
251
  this.detailError = ''
253
- this.innerDetail = null
252
+ // 先进入 loading,再打开弹窗,避免首帧只有公式行的窄框
253
+ const willFetch =
254
+ this.useDemo ||
255
+ typeof this.fetchDetail === 'function' ||
256
+ typeof this.loadDetail === 'function'
257
+ if (willFetch) this.detailLoading = true
258
+ this.detailVisible = true
254
259
 
255
260
  if (this.useDemo) {
256
- this.detailLoading = true
257
261
  try {
258
262
  this.innerDetail = createDemoDetail({
259
263
  ...this.board,
@@ -266,7 +270,6 @@ export default {
266
270
  }
267
271
 
268
272
  if (typeof this.fetchDetail === 'function') {
269
- this.detailLoading = true
270
273
  try {
271
274
  const base = {
272
275
  customerName: this.customerName,
@@ -277,6 +280,7 @@ export default {
277
280
  const detailPayload = this.detailPayload
278
281
  if (!detailPayload) {
279
282
  this.innerDetail = base
283
+ this.detailLoading = false
280
284
  return
281
285
  }
282
286
  const res = await this.fetchDetail(detailPayload)
@@ -287,6 +291,7 @@ export default {
287
291
  ...(res.data || {})
288
292
  }
289
293
  } else {
294
+ this.innerDetail = null
290
295
  throw new Error((res && res.message) || '底价明细获取失败')
291
296
  }
292
297
  } catch (e) {
@@ -298,12 +303,12 @@ export default {
298
303
  }
299
304
 
300
305
  if (typeof this.loadDetail === 'function') {
301
- this.detailLoading = true
302
306
  try {
303
307
  const data = await this.loadDetail({ overview: this.board, raw: this.rawOverview })
304
308
  this.innerDetail = data || null
305
309
  if (!data) this.detailError = '暂无底价明细'
306
310
  } catch (e) {
311
+ this.innerDetail = null
307
312
  this.detailError = (e && e.message) || '底价明细获取失败'
308
313
  } finally {
309
314
  this.detailLoading = false
@@ -2,7 +2,7 @@
2
2
  通用「底价明细」弹窗(纯展示,无接口依赖)
3
3
  - 顶部汇总:由 Dashboard 传入概览产品行
4
4
  - 下方三区(销售订单 / 非销出库 / 销售费用)等分三列并排
5
- - 三列明细等高:固定表格高度 + 预留分页条高度
5
+ - 三列卡片等高:表格固定高度按 pageSize 撑开,合计底对齐
6
6
  - 分区数据前端分页(pageSize);无底部关闭按钮,右上角关闭即可
7
7
  -->
8
8
  <template>
@@ -16,12 +16,35 @@
16
16
  :custom-class="dialogCustomClass"
17
17
  @close="onClose"
18
18
  >
19
- <div v-loading="loading" class="fpd-body">
19
+ <div
20
+ v-loading="loading && !showLoadingShell"
21
+ :class="['fpd-body', { 'fpd-body--loading': showLoadingShell }]"
22
+ >
20
23
  <div v-if="error" class="fpd-msg fpd-msg--error">{{ error }}</div>
21
24
 
25
+ <!-- 首次加载无内容时用骨架撑住高度,避免先闪出一条窄框;计算说明骨架仍在底部 -->
26
+ <div v-else-if="showLoadingShell" class="fpd-skeleton" aria-hidden="true">
27
+ <div class="fpd-skeleton__summary">
28
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--lg" />
29
+ <div class="fpd-skeleton__bar" />
30
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--sm" />
31
+ </div>
32
+ <div class="fpd-skeleton__sections">
33
+ <div v-for="n in 3" :key="n" class="fpd-skeleton__section">
34
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--title" />
35
+ <div class="fpd-skeleton__block" :style="{ height: resolvedTableHeight + 'px' }" />
36
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--footer" />
37
+ </div>
38
+ </div>
39
+ <div class="fpd-skeleton__formulas">
40
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--label" />
41
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--md" />
42
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--sm" />
43
+ </div>
44
+ </div>
45
+
22
46
  <template v-else>
23
47
  <div v-if="summaryColumns.length" class="fpd-summary">
24
- <div v-if="isNarrow" class="fpd-summary__hint">汇总表可左右滑动</div>
25
48
  <el-table
26
49
  :data="summaryRows"
27
50
  border
@@ -68,7 +91,6 @@
68
91
  <div class="fpd-section__head">
69
92
  <span class="fpd-section__title">{{ section.title }}</span>
70
93
  <span v-if="section.subtitle" class="fpd-section__subtitle">{{ section.subtitle }}</span>
71
- <span v-if="isNarrow" class="fpd-section__hint">左右滑动查看完整信息</span>
72
94
  </div>
73
95
 
74
96
  <div class="fpd-section__table-wrap">
@@ -76,7 +98,7 @@
76
98
  :data="pagedRows(section)"
77
99
  border
78
100
  size="small"
79
- class="fpd-table"
101
+ class="fpd-table fpd-table--section"
80
102
  :height="resolvedTableHeight"
81
103
  :header-cell-style="headerCellStyle"
82
104
  >
@@ -129,9 +151,19 @@
129
151
  </div>
130
152
  </div>
131
153
 
132
- <div v-if="formulaList.length" class="fpd-formulas">
133
- <div v-for="(line, idx) in formulaList" :key="idx" class="fpd-formulas__line">
134
- {{ line }}
154
+ <!-- 计算说明固定底部:有表格内容后再渲染,避免加载时单独成一条窄框 -->
155
+ <div v-if="formulaItems.length && hasDetailContent" class="fpd-formulas" role="note">
156
+ <span class="fpd-formulas__label">计算说明</span>
157
+ <div class="fpd-formulas__list">
158
+ <span
159
+ v-for="(item, idx) in formulaItems"
160
+ :key="idx"
161
+ class="fpd-formulas__item"
162
+ >
163
+ <em class="fpd-formulas__key">{{ item.key }}</em>
164
+ <span class="fpd-formulas__eq">=</span>
165
+ <span class="fpd-formulas__expr">{{ item.expr }}</span>
166
+ </span>
135
167
  </div>
136
168
  </div>
137
169
  </template>
@@ -203,10 +235,13 @@ export default {
203
235
  type: Array,
204
236
  default: null
205
237
  },
206
- /** 三列表格统一高度,保证等高 */
238
+ /**
239
+ * 三区表格统一高度(按 pageSize 撑满),数量少也留白对齐
240
+ * 可传 tableHeight 覆盖
241
+ */
207
242
  tableHeight: {
208
243
  type: [Number, String],
209
- default: 260
244
+ default: null
210
245
  },
211
246
  /** 分区前端分页大小 */
212
247
  pageSize: {
@@ -240,15 +275,42 @@ export default {
240
275
  resolvedTop() {
241
276
  return this.isNarrow ? '5vh' : this.top
242
277
  },
278
+ /** 三区表格等高:优先用 prop,否则按 pageSize × 行高推算 */
243
279
  resolvedTableHeight() {
244
- const base = Number(this.tableHeight)
245
- const fallback = Number.isFinite(base) && base > 0 ? base : 260
246
- return this.isNarrow ? Math.min(fallback, 200) : fallback
280
+ const override = Number(this.tableHeight)
281
+ if (Number.isFinite(override) && override > 0) {
282
+ return this.isNarrow ? Math.min(override, 200) : override
283
+ }
284
+ const size = this.pageSize || 5
285
+ const rowH = this.isNarrow ? 38 : 40
286
+ const headH = this.isNarrow ? 36 : 38
287
+ return headH + size * rowH
247
288
  },
248
289
  formulaList() {
249
290
  if (Array.isArray(this.formulas)) return this.formulas
250
291
  return DEFAULT_DETAIL_FORMULAS
251
292
  },
293
+ /** 拆成「指标名 = 表达式」,便于加粗高亮 */
294
+ formulaItems() {
295
+ return (this.formulaList || []).map((line) => {
296
+ const text = String(line || '').trim()
297
+ const idx = text.indexOf('=')
298
+ if (idx < 0) return { key: text, expr: '' }
299
+ return {
300
+ key: text.slice(0, idx).trim(),
301
+ expr: text.slice(idx + 1).trim()
302
+ }
303
+ }).filter((item) => item.key)
304
+ },
305
+ /** 有汇总或分区时视为已有明细内容(可在旧数据上盖 loading) */
306
+ hasDetailContent() {
307
+ return (this.summaryColumns && this.summaryColumns.length > 0) ||
308
+ (this.sections && this.sections.length > 0)
309
+ },
310
+ /** 首次打开尚无数据:用骨架撑高;计算说明不单独先行渲染 */
311
+ showLoadingShell() {
312
+ return this.loading && !this.error && !this.hasDetailContent
313
+ },
252
314
  /** 任一分区需要分页时,其余无分页列才占位对齐;全都没有则不占空白 */
253
315
  anySectionNeedsPager() {
254
316
  const size = this.pageSize || 5
@@ -405,25 +467,25 @@ export default {
405
467
  box-shadow: 0 24px 64px rgba(16, 24, 40, 0.24);
406
468
 
407
469
  .el-dialog__header {
408
- padding: 14px 22px;
470
+ padding: 12px 20px;
409
471
  border-bottom: 1px solid #e5e7eb;
410
472
  }
411
473
 
412
474
  .el-dialog__title {
413
- font-size: 18px;
475
+ font-size: 17px;
414
476
  font-weight: 600;
415
477
  color: #1f2329;
416
- line-height: 26px;
478
+ line-height: 24px;
417
479
  }
418
480
 
419
481
  .el-dialog__headerbtn {
420
- top: 18px;
482
+ top: 16px;
421
483
  right: 18px;
422
484
  }
423
485
 
424
486
  .el-dialog__body {
425
- padding: 18px 20px 16px;
426
- max-height: calc(92vh - 64px);
487
+ padding: 12px 16px 12px;
488
+ max-height: calc(92vh - 56px);
427
489
  overflow: auto;
428
490
  background: #f7f8fa;
429
491
  }
@@ -476,6 +538,111 @@ export default {
476
538
  min-width: 0;
477
539
  }
478
540
 
541
+ .fpd-body--loading {
542
+ min-height: 400px;
543
+ }
544
+
545
+ .fpd-skeleton {
546
+ display: flex;
547
+ flex-direction: column;
548
+ gap: 12px;
549
+ pointer-events: none;
550
+ }
551
+
552
+ .fpd-skeleton__summary {
553
+ padding: 14px 16px;
554
+ background: #fff;
555
+ border: 1px solid #e5e7eb;
556
+ border-radius: 8px;
557
+ display: flex;
558
+ flex-direction: column;
559
+ gap: 10px;
560
+ }
561
+
562
+ .fpd-skeleton__sections {
563
+ display: grid;
564
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.05fr) minmax(0, 0.9fr);
565
+ gap: 12px;
566
+ }
567
+
568
+ .fpd-skeleton__section {
569
+ display: flex;
570
+ flex-direction: column;
571
+ gap: 10px;
572
+ padding: 12px 14px 14px;
573
+ background: #fff;
574
+ border: 1px solid #e5e7eb;
575
+ border-radius: 8px;
576
+ min-width: 0;
577
+ }
578
+
579
+ .fpd-skeleton__block {
580
+ width: 100%;
581
+ border-radius: 6px;
582
+ background: linear-gradient(90deg, #eef0f3 0%, #f6f7f9 50%, #eef0f3 100%);
583
+ background-size: 200% 100%;
584
+ animation: fpd-skeleton-shine 1.2s ease-in-out infinite;
585
+ }
586
+
587
+ .fpd-skeleton__formulas {
588
+ margin-top: auto;
589
+ padding: 8px 12px;
590
+ background: #eef3ff;
591
+ border: 1px solid #d6e0f5;
592
+ border-radius: 8px;
593
+ display: flex;
594
+ align-items: center;
595
+ gap: 10px;
596
+ flex-wrap: wrap;
597
+ }
598
+
599
+ .fpd-skeleton__bar--label {
600
+ width: 56px;
601
+ height: 18px;
602
+ flex: none;
603
+ }
604
+
605
+ .fpd-skeleton__bar {
606
+ height: 12px;
607
+ border-radius: 4px;
608
+ background: linear-gradient(90deg, #eef0f3 0%, #f6f7f9 50%, #eef0f3 100%);
609
+ background-size: 200% 100%;
610
+ animation: fpd-skeleton-shine 1.2s ease-in-out infinite;
611
+ width: 72%;
612
+ }
613
+
614
+ .fpd-skeleton__bar--lg {
615
+ width: 88%;
616
+ height: 14px;
617
+ }
618
+
619
+ .fpd-skeleton__bar--md {
620
+ width: 64%;
621
+ }
622
+
623
+ .fpd-skeleton__bar--sm {
624
+ width: 48%;
625
+ }
626
+
627
+ .fpd-skeleton__bar--title {
628
+ width: 42%;
629
+ height: 14px;
630
+ }
631
+
632
+ .fpd-skeleton__bar--footer {
633
+ width: 56%;
634
+ margin-top: 2px;
635
+ }
636
+
637
+ @keyframes fpd-skeleton-shine {
638
+ 0% {
639
+ background-position: 100% 0;
640
+ }
641
+ 100% {
642
+ background-position: -100% 0;
643
+ }
644
+ }
645
+
479
646
  .fpd-msg {
480
647
  padding: 12px 16px;
481
648
  font-size: 13px;
@@ -490,7 +657,7 @@ export default {
490
657
  }
491
658
 
492
659
  .fpd-summary {
493
- margin-bottom: 12px;
660
+ margin-bottom: 10px;
494
661
  overflow-x: auto;
495
662
  -webkit-overflow-scrolling: touch;
496
663
  background: #fff;
@@ -498,15 +665,11 @@ export default {
498
665
  border-radius: 8px;
499
666
  }
500
667
 
501
- .fpd-summary__hint {
502
- display: none;
503
- }
504
-
505
668
  .fpd-sections {
506
669
  display: grid;
507
670
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.05fr) minmax(0, 0.9fr);
508
- gap: 12px;
509
- margin-bottom: 12px;
671
+ gap: 10px;
672
+ margin-bottom: 10px;
510
673
  align-items: stretch;
511
674
  }
512
675
 
@@ -514,7 +677,7 @@ export default {
514
677
  display: flex;
515
678
  flex-direction: column;
516
679
  min-width: 0;
517
- min-height: 100%;
680
+ height: 100%;
518
681
  border: 1px solid #e5e7eb;
519
682
  border-radius: 8px;
520
683
  overflow: hidden;
@@ -524,10 +687,10 @@ export default {
524
687
 
525
688
  .fpd-section__head {
526
689
  flex-shrink: 0;
527
- padding: 10px 14px;
690
+ padding: 8px 12px;
528
691
  display: flex;
529
692
  flex-direction: column;
530
- gap: 2px;
693
+ gap: 1px;
531
694
  box-sizing: border-box;
532
695
  background: #fafbfc;
533
696
  border-bottom: 1px solid #eef0f3;
@@ -535,36 +698,35 @@ export default {
535
698
  }
536
699
 
537
700
  .fpd-section__title {
538
- font-size: 15px;
701
+ font-size: 14px;
539
702
  font-weight: 600;
540
- line-height: 22px;
703
+ line-height: 20px;
541
704
  }
542
705
 
543
706
  .fpd-section__subtitle {
544
707
  font-size: 12px;
545
- line-height: 18px;
708
+ line-height: 16px;
546
709
  color: #8f959e;
547
710
  }
548
711
 
549
- .fpd-section__hint {
550
- display: none;
551
- }
552
-
553
712
  .fpd-section__table-wrap {
554
- flex: 0 0 auto;
713
+ flex: 1 1 auto;
555
714
  min-width: 0;
715
+ min-height: 0;
556
716
  overflow-x: auto;
717
+ overflow-y: hidden;
557
718
  -webkit-overflow-scrolling: touch;
558
719
  }
559
720
 
560
721
  .fpd-section__footer {
561
722
  flex-shrink: 0;
723
+ margin-top: auto;
562
724
  display: flex;
563
725
  justify-content: space-between;
564
726
  align-items: center;
565
727
  gap: 8px;
566
- padding: 10px 14px;
567
- font-size: 13px;
728
+ padding: 7px 12px;
729
+ font-size: 12px;
568
730
  font-weight: 600;
569
731
  flex-wrap: wrap;
570
732
  color: #646a73;
@@ -644,6 +806,11 @@ export default {
644
806
  border-bottom: none;
645
807
  }
646
808
 
809
+ /* 分区表固定等高后,末行下方有留白,需保留底部分隔线 */
810
+ &.fpd-table--section .el-table__body tr:last-child td.el-table__cell {
811
+ border-bottom: 1px solid #eef0f3;
812
+ }
813
+
647
814
  .cell {
648
815
  padding: 12px 16px;
649
816
  line-height: 22px;
@@ -673,6 +840,25 @@ export default {
673
840
  }
674
841
  }
675
842
 
843
+ /* 分区表行高与汇总表接近,避免过扁难读 */
844
+ ::v-deep .fpd-table--section {
845
+ .cell {
846
+ padding-top: 10px;
847
+ padding-bottom: 10px;
848
+ line-height: 20px;
849
+ }
850
+
851
+ th .cell {
852
+ padding-top: 9px;
853
+ padding-bottom: 9px;
854
+ }
855
+
856
+ /* 固定等高后仍隐藏纵向滚动条(分页控制行数) */
857
+ .el-table__body-wrapper {
858
+ overflow-y: hidden !important;
859
+ }
860
+ }
861
+
676
862
  .fpd-td--bold {
677
863
  font-weight: 700;
678
864
  color: #1f2329;
@@ -720,26 +906,96 @@ export default {
720
906
  }
721
907
 
722
908
  .fpd-formulas {
723
- padding: 12px 14px;
724
- border-radius: 8px;
725
- background: #fff;
726
- border: 1px solid #e5e7eb;
727
- font-size: 12px;
909
+ display: flex;
910
+ align-items: center;
911
+ gap: 8px 10px;
912
+ margin-top: 0;
913
+ margin-bottom: 0;
914
+ padding: 6px 10px;
915
+ border-radius: 6px;
916
+ background: linear-gradient(90deg, #eef4ff 0%, #f5f8ff 100%);
917
+ border: 1px solid #c9daf8;
918
+ box-shadow: inset 3px 0 0 #3370ff;
919
+ }
920
+
921
+ .fpd-formulas__label {
922
+ flex: none;
923
+ display: inline-flex;
924
+ align-items: center;
925
+ height: 20px;
926
+ padding: 0 7px;
927
+ border-radius: 3px;
928
+ background: #3370ff;
929
+ color: #fff;
930
+ font-size: 11px;
931
+ font-weight: 600;
728
932
  line-height: 20px;
729
- color: #646a73;
933
+ letter-spacing: 0.02em;
934
+ }
935
+
936
+ .fpd-formulas__list {
937
+ flex: 1;
938
+ min-width: 0;
939
+ display: flex;
940
+ flex-wrap: wrap;
941
+ align-items: center;
942
+ gap: 2px 0;
943
+ }
944
+
945
+ .fpd-formulas__item {
946
+ display: inline-flex;
947
+ align-items: baseline;
948
+ flex-wrap: wrap;
949
+ max-width: 100%;
950
+ font-size: 12px;
951
+ line-height: 18px;
952
+ color: #4e5969;
953
+
954
+ &:not(:last-child)::after {
955
+ content: '';
956
+ display: inline-block;
957
+ width: 1px;
958
+ height: 11px;
959
+ margin: 0 10px;
960
+ background: #b7c9ef;
961
+ vertical-align: middle;
962
+ position: relative;
963
+ top: 1px;
964
+ }
965
+ }
966
+
967
+ .fpd-formulas__key {
968
+ font-style: normal;
969
+ font-weight: 700;
970
+ color: #1f2329;
971
+ }
972
+
973
+ .fpd-formulas__eq {
974
+ margin: 0 4px;
975
+ color: #86909c;
976
+ font-weight: 500;
730
977
  }
731
978
 
732
- .fpd-formulas__line + .fpd-formulas__line {
733
- margin-top: 4px;
979
+ .fpd-formulas__expr {
980
+ color: #4e5969;
734
981
  }
735
982
 
736
983
  @media (max-width: 1100px) {
737
- .fpd-sections {
984
+ .fpd-sections,
985
+ .fpd-skeleton__sections {
738
986
  grid-template-columns: 1fr;
739
987
  }
740
988
  }
741
989
 
742
990
  @media (max-width: 768px) {
991
+ .fpd-body--loading {
992
+ min-height: 380px;
993
+ }
994
+
995
+ .fpd-skeleton__sections {
996
+ grid-template-columns: 1fr;
997
+ }
998
+
743
999
  .fpd-summary {
744
1000
  margin-bottom: 12px;
745
1001
  border-radius: 10px;
@@ -747,28 +1003,14 @@ export default {
747
1003
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
748
1004
  }
749
1005
 
750
- .fpd-summary__hint,
751
- .fpd-section__hint {
752
- display: block;
753
- padding: 0;
754
- font-size: 11px;
755
- line-height: 16px;
756
- color: #8f959e;
757
- font-weight: 400;
758
- }
759
-
760
- .fpd-summary__hint {
761
- padding: 8px 12px 0;
762
- }
763
-
764
1006
  .fpd-sections {
765
1007
  gap: 12px;
766
1008
  margin-bottom: 12px;
767
- align-items: start;
1009
+ align-items: stretch;
768
1010
  }
769
1011
 
770
1012
  .fpd-section {
771
- min-height: 0;
1013
+ height: auto;
772
1014
  border-radius: 10px;
773
1015
  border-color: #e8ebf0;
774
1016
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
@@ -842,14 +1084,37 @@ export default {
842
1084
  justify-content: flex-end;
843
1085
  }
844
1086
 
1087
+ .fpd-skeleton__formulas {
1088
+ flex-direction: column;
1089
+ align-items: stretch;
1090
+ gap: 6px;
1091
+ }
1092
+
845
1093
  .fpd-formulas {
846
- padding: 10px 12px;
847
- border-radius: 10px;
848
- border-color: #e8ebf0;
1094
+ flex-direction: column;
1095
+ gap: 6px;
1096
+ padding: 8px 10px 8px 12px;
1097
+ margin-top: 0;
1098
+ margin-bottom: 0;
1099
+ }
1100
+
1101
+ .fpd-formulas__label {
1102
+ align-self: flex-start;
1103
+ }
1104
+
1105
+ .fpd-formulas__list {
1106
+ display: grid;
1107
+ grid-template-columns: 1fr;
1108
+ gap: 4px;
1109
+ }
1110
+
1111
+ .fpd-formulas__item {
849
1112
  font-size: 11px;
850
- line-height: 18px;
851
- color: #6b7280;
852
- box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
1113
+ line-height: 17px;
1114
+
1115
+ &:not(:last-child)::after {
1116
+ display: none;
1117
+ }
853
1118
  }
854
1119
 
855
1120
  ::v-deep .fpd-table {
@@ -77,9 +77,9 @@ export default {
77
77
  display: flex;
78
78
  flex-wrap: wrap;
79
79
  align-items: center;
80
- gap: 6px 10px;
81
- min-height: 32px;
82
- padding: 4px 12px;
80
+ gap: 4px 8px;
81
+ min-height: 28px;
82
+ padding: 2px 12px;
83
83
  box-sizing: border-box;
84
84
  font-size: 12px;
85
85
  color: rgba(0, 0, 0, 0.65);