imeik-bizui 2.6.5 → 2.6.6

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
+ - 三列卡片按内容收高(不强制等高撑白),表格按当前页行数自适应、无内部滚动条
6
6
  - 分区数据前端分页(pageSize);无底部关闭按钮,右上角关闭即可
7
7
  -->
8
8
  <template>
@@ -16,10 +16,49 @@
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__formulas">
28
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--label" />
29
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--md" />
30
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--sm" />
31
+ </div>
32
+ <div class="fpd-skeleton__summary">
33
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--lg" />
34
+ <div class="fpd-skeleton__bar" />
35
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--sm" />
36
+ </div>
37
+ <div class="fpd-skeleton__sections">
38
+ <div v-for="n in 3" :key="n" class="fpd-skeleton__section">
39
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--title" />
40
+ <div class="fpd-skeleton__block" :style="{ height: resolvedTableHeight + 'px' }" />
41
+ <div class="fpd-skeleton__bar fpd-skeleton__bar--footer" />
42
+ </div>
43
+ </div>
44
+ </div>
45
+
22
46
  <template v-else>
47
+ <div v-if="formulaItems.length" class="fpd-formulas" role="note">
48
+ <span class="fpd-formulas__label">计算说明</span>
49
+ <div class="fpd-formulas__list">
50
+ <span
51
+ v-for="(item, idx) in formulaItems"
52
+ :key="idx"
53
+ class="fpd-formulas__item"
54
+ >
55
+ <em class="fpd-formulas__key">{{ item.key }}</em>
56
+ <span class="fpd-formulas__eq">=</span>
57
+ <span class="fpd-formulas__expr">{{ item.expr }}</span>
58
+ </span>
59
+ </div>
60
+ </div>
61
+
23
62
  <div v-if="summaryColumns.length" class="fpd-summary">
24
63
  <div v-if="isNarrow" class="fpd-summary__hint">汇总表可左右滑动</div>
25
64
  <el-table
@@ -76,8 +115,7 @@
76
115
  :data="pagedRows(section)"
77
116
  border
78
117
  size="small"
79
- class="fpd-table"
80
- :height="resolvedTableHeight"
118
+ class="fpd-table fpd-table--section"
81
119
  :header-cell-style="headerCellStyle"
82
120
  >
83
121
  <el-table-column
@@ -128,12 +166,6 @@
128
166
  </div>
129
167
  </div>
130
168
  </div>
131
-
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 }}
135
- </div>
136
- </div>
137
169
  </template>
138
170
  </div>
139
171
  </el-dialog>
@@ -203,15 +235,17 @@ export default {
203
235
  type: Array,
204
236
  default: null
205
237
  },
206
- /** 三列表格统一高度,保证等高 */
238
+ /**
239
+ * 骨架屏占位高度(表格本身按当前页行数自适应,不再用固定 height,避免内部滚动条)
240
+ */
207
241
  tableHeight: {
208
242
  type: [Number, String],
209
- default: 260
243
+ default: 180
210
244
  },
211
- /** 分区前端分页大小 */
245
+ /** 分区前端分页大小(略少以控制弹窗总高) */
212
246
  pageSize: {
213
247
  type: Number,
214
- default: 5
248
+ default: 4
215
249
  }
216
250
  },
217
251
  data() {
@@ -240,18 +274,40 @@ export default {
240
274
  resolvedTop() {
241
275
  return this.isNarrow ? '5vh' : this.top
242
276
  },
277
+ /** 仅骨架屏使用;真实表格按页内行数撑开,不设固定 height */
243
278
  resolvedTableHeight() {
244
279
  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 fallback = Number.isFinite(base) && base > 0 ? base : 220
281
+ return this.isNarrow ? Math.min(fallback, 180) : fallback
247
282
  },
248
283
  formulaList() {
249
284
  if (Array.isArray(this.formulas)) return this.formulas
250
285
  return DEFAULT_DETAIL_FORMULAS
251
286
  },
287
+ /** 拆成「指标名 = 表达式」,便于加粗高亮 */
288
+ formulaItems() {
289
+ return (this.formulaList || []).map((line) => {
290
+ const text = String(line || '').trim()
291
+ const idx = text.indexOf('=')
292
+ if (idx < 0) return { key: text, expr: '' }
293
+ return {
294
+ key: text.slice(0, idx).trim(),
295
+ expr: text.slice(idx + 1).trim()
296
+ }
297
+ }).filter((item) => item.key)
298
+ },
299
+ /** 有汇总或分区时视为已有明细内容(可在旧数据上盖 loading) */
300
+ hasDetailContent() {
301
+ return (this.summaryColumns && this.summaryColumns.length > 0) ||
302
+ (this.sections && this.sections.length > 0)
303
+ },
304
+ /** 首次打开尚无数据:用骨架撑高,避免窄框闪一下 */
305
+ showLoadingShell() {
306
+ return this.loading && !this.error && !this.hasDetailContent
307
+ },
252
308
  /** 任一分区需要分页时,其余无分页列才占位对齐;全都没有则不占空白 */
253
309
  anySectionNeedsPager() {
254
- const size = this.pageSize || 5
310
+ const size = this.pageSize || 4
255
311
  return (this.sections || []).some((section) => this.sectionRowCount(section) > size)
256
312
  },
257
313
  /** 窄屏三区已改为单列,无需为空分页占位 */
@@ -329,7 +385,7 @@ export default {
329
385
  pagedRows(section) {
330
386
  const rows = section.rows || []
331
387
  const page = this.sectionPage(section)
332
- const size = this.pageSize || 5
388
+ const size = this.pageSize || 4
333
389
  const start = (page - 1) * size
334
390
  return rows.slice(start, start + size)
335
391
  },
@@ -405,25 +461,25 @@ export default {
405
461
  box-shadow: 0 24px 64px rgba(16, 24, 40, 0.24);
406
462
 
407
463
  .el-dialog__header {
408
- padding: 14px 22px;
464
+ padding: 12px 20px;
409
465
  border-bottom: 1px solid #e5e7eb;
410
466
  }
411
467
 
412
468
  .el-dialog__title {
413
- font-size: 18px;
469
+ font-size: 17px;
414
470
  font-weight: 600;
415
471
  color: #1f2329;
416
- line-height: 26px;
472
+ line-height: 24px;
417
473
  }
418
474
 
419
475
  .el-dialog__headerbtn {
420
- top: 18px;
476
+ top: 16px;
421
477
  right: 18px;
422
478
  }
423
479
 
424
480
  .el-dialog__body {
425
- padding: 18px 20px 16px;
426
- max-height: calc(92vh - 64px);
481
+ padding: 12px 16px 12px;
482
+ max-height: calc(92vh - 56px);
427
483
  overflow: auto;
428
484
  background: #f7f8fa;
429
485
  }
@@ -476,6 +532,110 @@ export default {
476
532
  min-width: 0;
477
533
  }
478
534
 
535
+ .fpd-body--loading {
536
+ min-height: 400px;
537
+ }
538
+
539
+ .fpd-skeleton {
540
+ display: flex;
541
+ flex-direction: column;
542
+ gap: 12px;
543
+ pointer-events: none;
544
+ }
545
+
546
+ .fpd-skeleton__summary {
547
+ padding: 14px 16px;
548
+ background: #fff;
549
+ border: 1px solid #e5e7eb;
550
+ border-radius: 8px;
551
+ display: flex;
552
+ flex-direction: column;
553
+ gap: 10px;
554
+ }
555
+
556
+ .fpd-skeleton__sections {
557
+ display: grid;
558
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.05fr) minmax(0, 0.9fr);
559
+ gap: 12px;
560
+ }
561
+
562
+ .fpd-skeleton__section {
563
+ display: flex;
564
+ flex-direction: column;
565
+ gap: 10px;
566
+ padding: 12px 14px 14px;
567
+ background: #fff;
568
+ border: 1px solid #e5e7eb;
569
+ border-radius: 8px;
570
+ min-width: 0;
571
+ }
572
+
573
+ .fpd-skeleton__block {
574
+ width: 100%;
575
+ border-radius: 6px;
576
+ background: linear-gradient(90deg, #eef0f3 0%, #f6f7f9 50%, #eef0f3 100%);
577
+ background-size: 200% 100%;
578
+ animation: fpd-skeleton-shine 1.2s ease-in-out infinite;
579
+ }
580
+
581
+ .fpd-skeleton__formulas {
582
+ padding: 8px 12px;
583
+ background: #eef3ff;
584
+ border: 1px solid #d6e0f5;
585
+ border-radius: 8px;
586
+ display: flex;
587
+ align-items: center;
588
+ gap: 10px;
589
+ flex-wrap: wrap;
590
+ }
591
+
592
+ .fpd-skeleton__bar--label {
593
+ width: 56px;
594
+ height: 18px;
595
+ flex: none;
596
+ }
597
+
598
+ .fpd-skeleton__bar {
599
+ height: 12px;
600
+ border-radius: 4px;
601
+ background: linear-gradient(90deg, #eef0f3 0%, #f6f7f9 50%, #eef0f3 100%);
602
+ background-size: 200% 100%;
603
+ animation: fpd-skeleton-shine 1.2s ease-in-out infinite;
604
+ width: 72%;
605
+ }
606
+
607
+ .fpd-skeleton__bar--lg {
608
+ width: 88%;
609
+ height: 14px;
610
+ }
611
+
612
+ .fpd-skeleton__bar--md {
613
+ width: 64%;
614
+ }
615
+
616
+ .fpd-skeleton__bar--sm {
617
+ width: 48%;
618
+ }
619
+
620
+ .fpd-skeleton__bar--title {
621
+ width: 42%;
622
+ height: 14px;
623
+ }
624
+
625
+ .fpd-skeleton__bar--footer {
626
+ width: 56%;
627
+ margin-top: 2px;
628
+ }
629
+
630
+ @keyframes fpd-skeleton-shine {
631
+ 0% {
632
+ background-position: 100% 0;
633
+ }
634
+ 100% {
635
+ background-position: -100% 0;
636
+ }
637
+ }
638
+
479
639
  .fpd-msg {
480
640
  padding: 12px 16px;
481
641
  font-size: 13px;
@@ -490,7 +650,7 @@ export default {
490
650
  }
491
651
 
492
652
  .fpd-summary {
493
- margin-bottom: 12px;
653
+ margin-bottom: 10px;
494
654
  overflow-x: auto;
495
655
  -webkit-overflow-scrolling: touch;
496
656
  background: #fff;
@@ -505,16 +665,16 @@ export default {
505
665
  .fpd-sections {
506
666
  display: grid;
507
667
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.05fr) minmax(0, 0.9fr);
508
- gap: 12px;
509
- margin-bottom: 12px;
510
- align-items: stretch;
668
+ gap: 10px;
669
+ margin-bottom: 0;
670
+ align-items: start;
511
671
  }
512
672
 
513
673
  .fpd-section {
514
674
  display: flex;
515
675
  flex-direction: column;
516
676
  min-width: 0;
517
- min-height: 100%;
677
+ min-height: 0;
518
678
  border: 1px solid #e5e7eb;
519
679
  border-radius: 8px;
520
680
  overflow: hidden;
@@ -524,10 +684,10 @@ export default {
524
684
 
525
685
  .fpd-section__head {
526
686
  flex-shrink: 0;
527
- padding: 10px 14px;
687
+ padding: 8px 12px;
528
688
  display: flex;
529
689
  flex-direction: column;
530
- gap: 2px;
690
+ gap: 1px;
531
691
  box-sizing: border-box;
532
692
  background: #fafbfc;
533
693
  border-bottom: 1px solid #eef0f3;
@@ -535,14 +695,14 @@ export default {
535
695
  }
536
696
 
537
697
  .fpd-section__title {
538
- font-size: 15px;
698
+ font-size: 14px;
539
699
  font-weight: 600;
540
- line-height: 22px;
700
+ line-height: 20px;
541
701
  }
542
702
 
543
703
  .fpd-section__subtitle {
544
704
  font-size: 12px;
545
- line-height: 18px;
705
+ line-height: 16px;
546
706
  color: #8f959e;
547
707
  }
548
708
 
@@ -554,6 +714,7 @@ export default {
554
714
  flex: 0 0 auto;
555
715
  min-width: 0;
556
716
  overflow-x: auto;
717
+ overflow-y: hidden;
557
718
  -webkit-overflow-scrolling: touch;
558
719
  }
559
720
 
@@ -563,8 +724,8 @@ export default {
563
724
  justify-content: space-between;
564
725
  align-items: center;
565
726
  gap: 8px;
566
- padding: 10px 14px;
567
- font-size: 13px;
727
+ padding: 7px 12px;
728
+ font-size: 12px;
568
729
  font-weight: 600;
569
730
  flex-wrap: wrap;
570
731
  color: #646a73;
@@ -673,6 +834,25 @@ export default {
673
834
  }
674
835
  }
675
836
 
837
+ /* 分区表略收紧行高,一页内更紧凑且无内部滚动 */
838
+ ::v-deep .fpd-table--section {
839
+ .cell {
840
+ padding-top: 6px;
841
+ padding-bottom: 6px;
842
+ line-height: 18px;
843
+ }
844
+
845
+ th .cell {
846
+ padding-top: 6px;
847
+ padding-bottom: 6px;
848
+ }
849
+
850
+ /* 无固定 height 时仍兜底隐藏纵向滚动条 */
851
+ .el-table__body-wrapper {
852
+ overflow-y: hidden !important;
853
+ }
854
+ }
855
+
676
856
  .fpd-td--bold {
677
857
  font-weight: 700;
678
858
  color: #1f2329;
@@ -720,26 +900,95 @@ export default {
720
900
  }
721
901
 
722
902
  .fpd-formulas {
723
- padding: 12px 14px;
724
- border-radius: 8px;
725
- background: #fff;
726
- border: 1px solid #e5e7eb;
727
- font-size: 12px;
903
+ display: flex;
904
+ align-items: center;
905
+ gap: 8px 10px;
906
+ margin-bottom: 10px;
907
+ padding: 6px 10px;
908
+ border-radius: 6px;
909
+ background: linear-gradient(90deg, #eef4ff 0%, #f5f8ff 100%);
910
+ border: 1px solid #c9daf8;
911
+ box-shadow: inset 3px 0 0 #3370ff;
912
+ }
913
+
914
+ .fpd-formulas__label {
915
+ flex: none;
916
+ display: inline-flex;
917
+ align-items: center;
918
+ height: 20px;
919
+ padding: 0 7px;
920
+ border-radius: 3px;
921
+ background: #3370ff;
922
+ color: #fff;
923
+ font-size: 11px;
924
+ font-weight: 600;
728
925
  line-height: 20px;
729
- color: #646a73;
926
+ letter-spacing: 0.02em;
730
927
  }
731
928
 
732
- .fpd-formulas__line + .fpd-formulas__line {
733
- margin-top: 4px;
929
+ .fpd-formulas__list {
930
+ flex: 1;
931
+ min-width: 0;
932
+ display: flex;
933
+ flex-wrap: wrap;
934
+ align-items: center;
935
+ gap: 2px 0;
936
+ }
937
+
938
+ .fpd-formulas__item {
939
+ display: inline-flex;
940
+ align-items: baseline;
941
+ flex-wrap: wrap;
942
+ max-width: 100%;
943
+ font-size: 12px;
944
+ line-height: 18px;
945
+ color: #4e5969;
946
+
947
+ &:not(:last-child)::after {
948
+ content: '';
949
+ display: inline-block;
950
+ width: 1px;
951
+ height: 11px;
952
+ margin: 0 10px;
953
+ background: #b7c9ef;
954
+ vertical-align: middle;
955
+ position: relative;
956
+ top: 1px;
957
+ }
958
+ }
959
+
960
+ .fpd-formulas__key {
961
+ font-style: normal;
962
+ font-weight: 700;
963
+ color: #1f2329;
964
+ }
965
+
966
+ .fpd-formulas__eq {
967
+ margin: 0 4px;
968
+ color: #86909c;
969
+ font-weight: 500;
970
+ }
971
+
972
+ .fpd-formulas__expr {
973
+ color: #4e5969;
734
974
  }
735
975
 
736
976
  @media (max-width: 1100px) {
737
- .fpd-sections {
977
+ .fpd-sections,
978
+ .fpd-skeleton__sections {
738
979
  grid-template-columns: 1fr;
739
980
  }
740
981
  }
741
982
 
742
983
  @media (max-width: 768px) {
984
+ .fpd-body--loading {
985
+ min-height: 380px;
986
+ }
987
+
988
+ .fpd-skeleton__sections {
989
+ grid-template-columns: 1fr;
990
+ }
991
+
743
992
  .fpd-summary {
744
993
  margin-bottom: 12px;
745
994
  border-radius: 10px;
@@ -763,7 +1012,7 @@ export default {
763
1012
 
764
1013
  .fpd-sections {
765
1014
  gap: 12px;
766
- margin-bottom: 12px;
1015
+ margin-bottom: 0;
767
1016
  align-items: start;
768
1017
  }
769
1018
 
@@ -842,14 +1091,36 @@ export default {
842
1091
  justify-content: flex-end;
843
1092
  }
844
1093
 
1094
+ .fpd-skeleton__formulas {
1095
+ flex-direction: column;
1096
+ align-items: stretch;
1097
+ gap: 6px;
1098
+ }
1099
+
845
1100
  .fpd-formulas {
846
- padding: 10px 12px;
847
- border-radius: 10px;
848
- border-color: #e8ebf0;
1101
+ flex-direction: column;
1102
+ gap: 6px;
1103
+ padding: 8px 10px 8px 12px;
1104
+ margin-bottom: 12px;
1105
+ }
1106
+
1107
+ .fpd-formulas__label {
1108
+ align-self: flex-start;
1109
+ }
1110
+
1111
+ .fpd-formulas__list {
1112
+ display: grid;
1113
+ grid-template-columns: 1fr;
1114
+ gap: 4px;
1115
+ }
1116
+
1117
+ .fpd-formulas__item {
849
1118
  font-size: 11px;
850
- line-height: 18px;
851
- color: #6b7280;
852
- box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
1119
+ line-height: 17px;
1120
+
1121
+ &:not(:last-child)::after {
1122
+ display: none;
1123
+ }
853
1124
  }
854
1125
 
855
1126
  ::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);