n20-common-lib 3.1.11 → 3.1.13

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.
@@ -113,17 +113,41 @@
113
113
  </template>
114
114
 
115
115
  <script>
116
- import XEUtils from 'xe-utils'
117
- import Dialog from '../Dialog/index.vue'
118
- import ReportSidebar from './ReportSidebar.vue'
119
- import MainToolbar from './MainToolbar.vue'
116
+ import XEUtils, { merge } from 'xe-utils'
117
+
120
118
  import ChartView from './ChartView.vue'
121
- import TableView from './TableView.vue'
122
119
  import ConfigSidebar from './ConfigSidebar.vue'
120
+ import MainToolbar from './MainToolbar.vue'
121
+ import ReportSidebar from './ReportSidebar.vue'
122
+ import TableView from './TableView.vue'
123
+
124
+ import Dialog from '../Dialog/index.vue'
123
125
 
124
126
  const STORAGE_KEY = 'pivot_report_configs'
125
127
  const DEBOUNCE_DELAY = 300
126
128
 
129
+ // 图表配色(从 CSS 变量读取)
130
+ const CHARTS_COLORS_KEYS = [
131
+ '--charts-blue',
132
+ '--charts-teal',
133
+ '--charts-red',
134
+ '--charts-orange',
135
+ '--charts-warn',
136
+ '--charts-yellow',
137
+ '--charts-bright-yellow',
138
+ '--charts-lime',
139
+ '--charts-green',
140
+ '--charts-light-blue',
141
+ '--charts-purple',
142
+ '--charts-magenta',
143
+ '--charts-pink'
144
+ ]
145
+ const CHARTS_COLORS = CHARTS_COLORS_KEYS.map((key) => getCSSVariableValue(key))
146
+
147
+ function getCSSVariableValue(key) {
148
+ return getComputedStyle(document.documentElement).getPropertyValue(key).trim()
149
+ }
150
+
127
151
  // 防抖函数
128
152
  function debounce(func, wait) {
129
153
  let timeout
@@ -217,8 +241,7 @@ export default {
217
241
  chartTypes: [
218
242
  { value: 'bar', label: '柱状图' },
219
243
  { value: 'line', label: '折线图' },
220
- { value: 'pie', label: '饼图' },
221
- { value: 'horizontalBar', label: '条形图' }
244
+ { value: 'pie', label: '饼图' }
222
245
  ],
223
246
  // 聚合类型选项
224
247
  aggregateTypes: [
@@ -522,9 +545,7 @@ export default {
522
545
 
523
546
  // 获取列维度的唯一值,使用 getEnumDisplayValue 处理枚举和对象类型
524
547
  const uniqueColumnValues = [
525
- ...new Set(
526
- this.actualData.map((item) => this.getEnumDisplayValue(item, columnDimension))
527
- )
548
+ ...new Set(this.actualData.map((item) => this.getEnumDisplayValue(item, columnDimension)))
528
549
  ]
529
550
 
530
551
  // 如果有多个指标,使用多级表头结构
@@ -635,40 +656,149 @@ export default {
635
656
  const gridConfig = {
636
657
  left: '3%',
637
658
  right: '4%',
638
- bottom: '15%',
659
+ bottom: '0',
639
660
  top: '10%',
640
661
  containLabel: true
641
662
  }
663
+ const tooltip = {
664
+ backgroundColor: '#fff',
665
+ borderColor: '#e5e6eb',
666
+ borderWidth: 1,
667
+ padding: [12, 12],
668
+ textStyle: {
669
+ color: '#1d2129'
670
+ },
671
+ extraCssText: 'box-shadow: 0px 8px 10px rgba(0,0,0,0.1); border-radius: 4px;',
672
+ formatter: (params) => {
673
+ if (params.seriesType === 'pie') {
674
+ let html = `<div style="font-size:14px;font-weight:500;margin-bottom:8px;color:#1d2129;">${params.marker}${params.name}</div>`
675
+ const rowData = this.processedData[params.dataIndex] || {}
676
+ const groupCount = rowData._groupCount || 0
677
+ const value =
678
+ typeof params.value === 'number'
679
+ ? params.value.toLocaleString('en-US', { minimumFractionDigits: 2 })
680
+ : params.value
681
+ html += `<div style="display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:4px;">`
682
+ html += `<div style="display:flex;align-items:center;gap:8px;">`
683
+ html += `<span style="font-size:12px;color:#1d2129;font-weight:600;font-family:'Open Sans',sans-serif;">${value}</span>`
684
+ html += `<span style="font-size:12px;color:#4e5969;font-family:'PingFang SC',sans-serif;">${groupCount} 笔</span>`
685
+ html += `</div>`
686
+ html += `</div>`
687
+
688
+ return html
689
+ }
690
+ const title = params[0].axisValue
691
+ const dataIndex = params[0].dataIndex
692
+ const rowData = this.processedData[dataIndex] || {}
693
+ const groupCount = rowData._groupCount || 0
694
+ let html = `<div style="font-size:14px;font-weight:500;margin-bottom:8px;color:#1d2129;">${title}</div>`
695
+ params.forEach((p) => {
696
+ const color = p.color
697
+ const value =
698
+ typeof p.value === 'number' ? p.value.toLocaleString('en-US', { minimumFractionDigits: 2 }) : p.value
699
+ html += `<div style="display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:4px;">`
700
+ html += `<div style="display:flex;align-items:center;gap:4px;">`
701
+ html += `<span style="display:inline-block;width:8px;height:3px;background:${color};"></span>`
702
+ html += `<span style="font-size:12px;color:#4e5969;font-family:'PingFang SC',sans-serif;">${p.seriesName}</span>`
703
+ html += `</div>`
704
+ html += `<div style="display:flex;align-items:center;gap:8px;">`
705
+ html += `<span style="font-size:12px;color:#1d2129;font-weight:600;font-family:'Open Sans',sans-serif;">${value}</span>`
706
+ html += `<span style="font-size:12px;color:#4e5969;font-family:'PingFang SC',sans-serif;">${groupCount} 笔</span>`
707
+ html += `</div>`
708
+ html += `</div>`
709
+ })
710
+ return html
711
+ }
712
+ }
642
713
 
643
714
  if (chartType === 'pie') {
644
715
  const metricProp = metrics[0]?.prop || ''
645
716
  const metricCol = this.columns.find((c) => c.prop === metricProp)
646
717
  const metricName = metricCol ? metricCol.label : metricProp
718
+ const total = data.reduce((sum, row) => sum + (Number(row[metricProp]) || 0), 0)
647
719
 
648
720
  return {
649
- tooltip: {
650
- trigger: 'item',
651
- formatter: '{b}: {c} ({d}%)'
652
- },
653
- legend: {
654
- orient: 'horizontal',
655
- bottom: 10,
656
- left: 'center'
657
- },
721
+ color: CHARTS_COLORS,
722
+ tooltip: merge(tooltip, {
723
+ trigger: 'item'
724
+ }),
725
+ legend: false,
726
+ graphic: [
727
+ {
728
+ type: 'group',
729
+ left: 'center',
730
+ top: 'center',
731
+ children: [
732
+ {
733
+ type: 'text',
734
+ style: {
735
+ text: '总计',
736
+ textAlign: 'center',
737
+ fill: getCSSVariableValue('--color-text-regular'),
738
+ fontSize: 13,
739
+ lineHeight: 22,
740
+ fontWeight: 400
741
+ },
742
+ top: 0
743
+ },
744
+ {
745
+ type: 'text',
746
+ style: {
747
+ text: total.toLocaleString(),
748
+ textAlign: 'center',
749
+ fill: getCSSVariableValue('--color-text-primary'),
750
+ fontSize: 20,
751
+ lineHeight: 28,
752
+ fontWeight: 600
753
+ },
754
+ top: 20
755
+ }
756
+ ]
757
+ }
758
+ ],
658
759
  series: [
659
760
  {
660
761
  name: metricName,
661
762
  type: 'pie',
662
763
  radius: ['40%', '70%'],
663
- avoidLabelOverlap: false,
664
- itemStyle: {
665
- borderRadius: 4,
666
- borderColor: '#fff',
667
- borderWidth: 2
668
- },
764
+ center: ['50%', '50%'],
765
+ avoidLabelOverlap: true,
669
766
  label: {
670
767
  show: true,
671
- formatter: '{b}: {d}%'
768
+ position: 'outside',
769
+ edgeDistance: 10,
770
+ formatter: function (params) {
771
+ const value = Number(params.value).toLocaleString('zh-CN')
772
+ return ['{name|' + params.name + '}', '{value|' + value + '}'].join('\n')
773
+ },
774
+ rich: {
775
+ name: {
776
+ fontSize: 12,
777
+ color: '#86909c',
778
+ lineHeight: 20
779
+ },
780
+ value: {
781
+ fontSize: 16,
782
+ color: '#1d2129',
783
+ fontWeight: 600,
784
+ fontFamily: 'Open Sans',
785
+ lineHeight: 24
786
+ }
787
+ }
788
+ },
789
+ labelLine: {
790
+ show: true,
791
+ length: 20,
792
+ length2: 50,
793
+ minimalAngle: 15,
794
+ lineStyle: {
795
+ width: 1
796
+ },
797
+ emphasis: {
798
+ lineStyle: {
799
+ width: 1
800
+ }
801
+ }
672
802
  },
673
803
  emphasis: {
674
804
  label: {
@@ -677,13 +807,15 @@ export default {
677
807
  fontWeight: 'bold'
678
808
  }
679
809
  },
680
- data: data.map((row) => ({
810
+ data: data.map((row, index) => ({
681
811
  name: row[selectedDimension],
682
- value: row[metricProp]
812
+ value: row[metricProp],
813
+ itemStyle: {
814
+ color: CHARTS_COLORS[index % CHARTS_COLORS.length]
815
+ }
683
816
  }))
684
817
  }
685
- ],
686
- color: ['#165DFF', '#14C9C9', '#F7BA1E', '#F53F3F', '#86909C', '#00B42A', '#FF7D00', '#F5319D']
818
+ ]
687
819
  }
688
820
  }
689
821
 
@@ -691,50 +823,43 @@ export default {
691
823
  const metricCol = this.columns.find((c) => c.prop === metric.prop)
692
824
  const metricName = metricCol ? metricCol.label : metric.prop
693
825
 
694
- return {
695
- name: metricName,
696
- type: chartType === 'line' ? 'line' : 'bar',
697
- data: data.map((row) => row[metric.prop]),
698
- barMaxWidth: 24,
699
- smooth: true
700
- }
826
+ return merge(
827
+ {
828
+ name: metricName,
829
+ data: data.map((row) => row[metric.prop]),
830
+ barMaxWidth: 24
831
+ },
832
+ chartType === 'line'
833
+ ? {
834
+ type: 'line',
835
+ lineStyle: {
836
+ width: 3
837
+ },
838
+ showSymbol: false,
839
+ emphasis: {
840
+ showSymbol: true,
841
+ itemStyle: {
842
+ borderWidth: 2,
843
+ borderColor: '#ffffff',
844
+ shadowBlur: 10,
845
+ shadowColor: 'rgba(0, 0, 0, 0.1)',
846
+ shadowOffsetX: 0,
847
+ shadowOffsetY: 4
848
+ }
849
+ },
850
+ symbolSize: 10,
851
+ symbol: 'circle',
852
+ smooth: false
853
+ }
854
+ : {
855
+ type: 'bar'
856
+ }
857
+ )
701
858
  })
702
859
 
703
860
  return {
704
- tooltip: {
705
- trigger: 'axis',
706
- backgroundColor: '#fff',
707
- borderColor: '#e5e6eb',
708
- borderWidth: 1,
709
- padding: [12, 12],
710
- textStyle: {
711
- color: '#1d2129'
712
- },
713
- extraCssText: 'box-shadow: 0px 8px 10px rgba(0,0,0,0.1); border-radius: 4px;',
714
- formatter: (params) => {
715
- const title = params[0].axisValue
716
- const dataIndex = params[0].dataIndex
717
- const rowData = this.processedData[dataIndex] || {}
718
- const groupCount = rowData._groupCount || 0
719
- let html = `<div style="font-size:14px;font-weight:500;margin-bottom:8px;color:#1d2129;">${title}</div>`
720
- params.forEach((p) => {
721
- const color = p.color
722
- const value =
723
- typeof p.value === 'number' ? p.value.toLocaleString('en-US', { minimumFractionDigits: 2 }) : p.value
724
- html += `<div style="display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:4px;">`
725
- html += `<div style="display:flex;align-items:center;gap:4px;">`
726
- html += `<span style="display:inline-block;width:8px;height:3px;background:${color};"></span>`
727
- html += `<span style="font-size:12px;color:#4e5969;font-family:'PingFang SC',sans-serif;">${p.seriesName}</span>`
728
- html += `</div>`
729
- html += `<div style="display:flex;align-items:center;gap:8px;">`
730
- html += `<span style="font-size:12px;color:#1d2129;font-weight:600;font-family:'Open Sans',sans-serif;">${value}</span>`
731
- html += `<span style="font-size:12px;color:#4e5969;font-family:'PingFang SC',sans-serif;">${groupCount} 笔</span>`
732
- html += `</div>`
733
- html += `</div>`
734
- })
735
- return html
736
- }
737
- },
861
+ color: CHARTS_COLORS,
862
+ tooltip: merge(tooltip, { trigger: 'axis' }),
738
863
  legend: {
739
864
  data: series.map((s) => s.name),
740
865
  top: 0,
@@ -794,8 +919,7 @@ export default {
794
919
  show: false
795
920
  }
796
921
  },
797
- series,
798
- color: ['#165DFF', '#14C9C9', '#F7BA1E', '#F53F3F', '#86909C']
922
+ series
799
923
  }
800
924
  }
801
925
  },
@@ -1013,11 +1137,7 @@ export default {
1013
1137
  const numericCols = this.availableMetrics
1014
1138
 
1015
1139
  // 分组统计模式:初始化分组维度
1016
- if (
1017
- this.currentReport.statType === 'group' &&
1018
- !this.currentReport.selectedDimension &&
1019
- allColumns.length > 0
1020
- ) {
1140
+ if (this.currentReport.statType === 'group' && !this.currentReport.selectedDimension && allColumns.length > 0) {
1021
1141
  this.currentReport.selectedDimension = allColumns[0].prop
1022
1142
  }
1023
1143
 
@@ -1132,11 +1252,11 @@ export default {
1132
1252
 
1133
1253
  handleToggleLeftSidebar() {
1134
1254
  this.isLeftSidebarCollapsed = !this.isLeftSidebarCollapsed
1135
- this.$nextTick(() => {
1255
+ setTimeout(() => {
1136
1256
  if (this.viewMode === 'chart' && this.$refs.chartView) {
1137
1257
  this.$refs.chartView.resizeChart()
1138
1258
  }
1139
- })
1259
+ }, 300)
1140
1260
  },
1141
1261
 
1142
1262
  handleRenameReport({ id, name }) {
@@ -1257,11 +1377,11 @@ export default {
1257
1377
  // ========== ConfigSidebar 事件处理 ==========
1258
1378
  handleToggleRightSidebar() {
1259
1379
  this.isRightSidebarCollapsed = !this.isRightSidebarCollapsed
1260
- this.$nextTick(() => {
1380
+ setTimeout(() => {
1261
1381
  if (this.viewMode === 'chart' && this.$refs.chartView) {
1262
1382
  this.$refs.chartView.resizeChart()
1263
1383
  }
1264
- })
1384
+ }, 300)
1265
1385
  },
1266
1386
 
1267
1387
  handleNameChange(value) {
@@ -1424,4 +1544,4 @@ export default {
1424
1544
 
1425
1545
  <style lang="scss">
1426
1546
  @import '../../assets/css/pivot.scss';
1427
- </style>
1547
+ </style>
@@ -24,13 +24,25 @@
24
24
  :disabled="item2.disabled | dbdBtn(row)"
25
25
  >
26
26
  <el-badge v-if="resolveBadge(item2)" :value="resolveBadge(item2)" :max="item2.badgeMax || 99">
27
- <i v-if="item2.icon" :class="item2.icon" class="m-r-ss" />
28
- <el-link v-if="item2.type" :type="item2.type" :underline="false">{{ item2.label }}</el-link>
27
+ <i v-if="item2.icon" :class="item2.icon" class="m-r-ss"></i>
28
+ <el-link
29
+ v-if="item2.type"
30
+ :diabled="item2.disabled | dbdBtn(row)"
31
+ :type="item2.type"
32
+ :underline="false"
33
+ >{{ item2.label }}</el-link
34
+ >
29
35
  <template v-else>{{ item2.label }}</template>
30
36
  </el-badge>
31
37
  <template v-else>
32
- <i v-if="item2.icon" :class="item2.icon" class="m-r-ss" />
33
- <el-link v-if="item2.type" :type="item2.type" :underline="false">{{ item2.label }}</el-link>
38
+ <i v-if="item2.icon" :class="item2.icon" class="m-r-ss"></i>
39
+ <el-link
40
+ v-if="item2.type"
41
+ :diabled="item2.disabled | dbdBtn(row)"
42
+ :type="item2.type"
43
+ :underline="false"
44
+ >{{ item2.label }}</el-link
45
+ >
34
46
  <template v-else>{{ item2.label }}</template>
35
47
  </template>
36
48
  </el-dropdown-item>
@@ -39,24 +51,28 @@
39
51
  </el-dropdown-item>
40
52
  <el-dropdown-item v-else :key="item.label" :command="item.command" :disabled="item.disabled | dbdBtn(row)">
41
53
  <el-badge v-if="resolveBadge(item)" :value="resolveBadge(item)" :max="item.badgeMax || 99">
42
- <i v-if="item.icon" :class="item.icon" class="m-r-ss" />
43
- <el-link v-if="item.type" :type="item.type" :underline="false">{{ item.label }}</el-link>
54
+ <i v-if="item.icon" :class="item.icon" class="m-r-ss"></i>
55
+ <el-link v-if="item.type" :disabled="item.disabled | dbdBtn(row)" :type="item.type" :underline="false">{{
56
+ item.label
57
+ }}</el-link>
44
58
  <template v-else>{{ item.label }}</template>
45
59
  </el-badge>
46
60
  <template v-else>
47
- <i v-if="item.icon" :class="item.icon" class="m-r-ss" />
48
- <el-link v-if="item.type" :type="item.type" :underline="false">{{ item.label }}</el-link>
61
+ <i v-if="item.icon" :class="item.icon" class="m-r-ss"></i>
62
+ <el-link v-if="item.type" :disabled="item.disabled | dbdBtn(row)" :type="item.type" :underline="false">{{
63
+ item.label
64
+ }}</el-link>
49
65
  <template v-else>{{ item.label }}</template>
50
66
  </template>
51
67
  </el-dropdown-item>
52
68
  </template>
53
69
  <!-- 动态按钮加载中 -->
54
70
  <el-dropdown-item v-if="dynamicLoading" disabled class="table-operate-loading">
55
- <i class="el-icon-loading" /> {{ $lc('加载中') }}
71
+ <i class="el-icon-loading"></i> {{ $lc('加载中') }}
56
72
  </el-dropdown-item>
57
73
  <!-- 动态按钮加载失败 -->
58
74
  <el-dropdown-item v-else-if="dynamicError" disabled class="table-operate-error">
59
- <i class="el-icon-warning" /> {{ $lc('加载失败') }}
75
+ <i class="el-icon-warning"></i> {{ $lc('加载失败') }}
60
76
  </el-dropdown-item>
61
77
  <!-- 动态按钮列表 -->
62
78
  <template v-else>
@@ -67,13 +83,17 @@
67
83
  :disabled="item.disabled | dbdBtn(row)"
68
84
  >
69
85
  <el-badge v-if="resolveBadge(item)" :value="resolveBadge(item)" :max="item.badgeMax || 99">
70
- <i v-if="item.icon" :class="item.icon" class="m-r-ss" />
71
- <el-link v-if="item.type" :type="item.type" :underline="false">{{ item.label }}</el-link>
86
+ <i v-if="item.icon" :class="item.icon" class="m-r-ss"></i>
87
+ <el-link v-if="item.type" :disabled="item.disabled | dbdBtn(row)" :type="item.type" :underline="false">{{
88
+ item.label
89
+ }}</el-link>
72
90
  <template v-else>{{ item.label }}</template>
73
91
  </el-badge>
74
92
  <template v-else>
75
- <i v-if="item.icon" :class="item.icon" class="m-r-ss" />
76
- <el-link v-if="item.type" :type="item.type" :underline="false">{{ item.label }}</el-link>
93
+ <i v-if="item.icon" :class="item.icon" class="m-r-ss"></i>
94
+ <el-link v-if="item.type" :disabled="item.disabled | dbdBtn(row)" :type="item.type" :underline="false">{{
95
+ item.label
96
+ }}</el-link>
77
97
  <template v-else>{{ item.label }}</template>
78
98
  </template>
79
99
  </el-dropdown-item>
@@ -84,6 +104,7 @@
84
104
  </template>
85
105
  <script>
86
106
  import childrenOperateBtn from './childrenOperateBtn.vue'
107
+
87
108
  import { $lc } from '../../utils/i18n/index.js'
88
109
 
89
110
  export default {
@@ -188,4 +209,4 @@ export default {
188
209
  }
189
210
  }
190
211
  }
191
- </script>
212
+ </script>
@@ -67,7 +67,7 @@
67
67
  <vxe-column
68
68
  v-else-if="item.render"
69
69
  :key="'vxe-table__render_' + i"
70
- v-bind="item"
70
+ v-bind="{ headerAlign: 'center', ...item }"
71
71
  :formatter="item.formatter ? item.formatter : 'formatName'"
72
72
  :filters="item.filters"
73
73
  :filter-render="item.filters && item.filters.length > 0 ? { name: 'FilterInput' } : null"
@@ -153,7 +153,7 @@
153
153
  :filter-render="subItem.filterRender"
154
154
  :title="subItem.label"
155
155
  :field="subItem.prop"
156
- v-bind="subItem"
156
+ v-bind="{ headerAlign: 'center', ...subItem }"
157
157
  :class-name="`${subItem.wrap && `vxe-table-custom-wrap`} ${subItem.bold && `font-w600`}`"
158
158
  />
159
159
  </vxe-colgroup>
@@ -166,7 +166,7 @@
166
166
  :filter-render="item.filters && item.filters.length > 0 ? { name: 'FilterInput' } : null"
167
167
  :title="item.label"
168
168
  :field="item.prop"
169
- v-bind="item"
169
+ v-bind="{ headerAlign: 'center', ...item }"
170
170
  >
171
171
  <template #header="{ column }">
172
172
  <div
@@ -213,7 +213,7 @@
213
213
  :filter-render="item.filters && item.filters.length > 0 ? { name: 'FilterInput' } : null"
214
214
  :title="item.label"
215
215
  :field="item.prop"
216
- v-bind="item"
216
+ v-bind="{ headerAlign: 'center', ...item }"
217
217
  >
218
218
  <template #header="{ column }">
219
219
  <div
@@ -430,12 +430,12 @@
430
430
  </template>
431
431
 
432
432
  <script>
433
+ import axios from '../../../utils/axios'
433
434
  import empty from '../../Empty'
434
- import tableOperate from '../../TableOperate/index.vue'
435
435
  import tableShowColumn from '../../ShowColumn/index.vue'
436
436
  import { saveTransform } from '../../ShowColumn/index.vue'
437
+ import tableOperate from '../../TableOperate/index.vue'
437
438
  import OperateBtns from '../../TableProOperateColumn/OperateBtns.vue'
438
- import axios from '../../../utils/axios'
439
439
 
440
440
  const renderer = {
441
441
  name: 'renderer',