lambo-design-bi 0.0.187 → 0.0.188

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.
@@ -29,6 +29,7 @@ import { getPageConfig } from '../api/config'
29
29
  import { deepCopy } from "@lambo-design/shared/utils/assist"
30
30
  import { getWidgetName } from '../util/widget-map'
31
31
  import { setRouteCfg2Search, getInitSearchRes, getSearchFormRes } from './util/common'
32
+ import { EventBus, EVENT_TYPES } from '../util/event-bus'
32
33
 
33
34
  export default {
34
35
  name: 'BiChildPageTm',
@@ -95,11 +96,11 @@ export default {
95
96
  }
96
97
  if (parentRouteName) {
97
98
  console.log("child-page-tm goBack parentRouteName:", parentRouteName)
98
- this.$router.push({
99
+ this.$router.push({
99
100
  name: parentRouteName,
100
101
  query: { pageId: this.parentPageId }
101
102
  })
102
- } else {
103
+ } else {
103
104
  console.log("child-page-tm goBack history.back()")
104
105
  this.$router.go(-1)
105
106
  }
@@ -127,12 +128,19 @@ export default {
127
128
  }
128
129
  this.params = deepCopy(formParams)
129
130
  console.log("child-page-tm doSearch 2 this.params:", this.params)
131
+ },
132
+ handleNumIdxActivated(eventData) {
133
+ console.log('BiPage handleNumIdxActivated eventData:', eventData)
134
+ // 处理数值指标激活事件的逻辑
135
+ this.$set(this.params, 'idxBizCode', eventData["idxBizCode"]);
130
136
  }
131
137
  },
132
138
  created() {
133
139
  this.getConfig()
134
140
  },
135
141
  mounted() {
142
+ // 监听卡片激活事件
143
+ EventBus.$on(EVENT_TYPES.NUM_IDX_ACTIVATED, this.handleNumIdxActivated)
136
144
 
137
145
  },
138
146
  watch: {
@@ -158,6 +166,10 @@ export default {
158
166
  )
159
167
  return filteredWidgets.length > 0
160
168
  }
169
+ },
170
+ beforeDestroy() {
171
+ // 组件销毁前移除事件监听
172
+ EventBus.$off(EVENT_TYPES.NUM_IDX_ACTIVATED, this.handleOtherCardActivated)
161
173
  }
162
174
  }
163
175
 
@@ -3,9 +3,13 @@
3
3
  <LamboPagingTableBi ref="table" :auto-search="false" :columns="columns" :dataUrl="widget.options.url"
4
4
  :export-batch-size="20000" :show-table-option="false" :exportName="widget.name"
5
5
  :page-disable="widget.options.isPaging === '0'" :page-size="widget.options.pageSize" :export-request-limit="200"
6
- :isSingleSelectModel="true" :showSummary="widget.options.isPageSum === '1'" :summary-method="handleSummary"
7
- @on-data-load="onDataLoad" :searchParams="tableSearchParams">
6
+ :isSingleSelectModel="true" @on-data-load="onDataLoad" :searchParams="tableSearchParams">
8
7
  <div slot="search"></div>
8
+ <div slot="footer">
9
+ <Table class="ivu-table-summary" v-if="widget.options.isAllSum && allSumData && allSumData.length > 0"
10
+ cellspacing="0" cellpadding="0" :columns="sumColumns" :data="allSumData" :border="true" :show-header="false">
11
+ </Table>
12
+ </div>
9
13
  </LamboPagingTableBi>
10
14
  </div>
11
15
  </template>
@@ -13,6 +17,7 @@
13
17
  import biConfig from '../config/config'
14
18
  import { getListMapData } from '../api/data'
15
19
  import LamboPagingTableBi from '../../../pub/table/paging-table/index.vue'
20
+ import { accAdd } from '../../../util/math-util'
16
21
 
17
22
  export default {
18
23
  name: 'BiPagingTable',
@@ -31,7 +36,8 @@ export default {
31
36
  return {
32
37
  tableSearchParams: {
33
38
  inJson: ''
34
- }
39
+ },
40
+ allSumData: [],
35
41
  }
36
42
  },
37
43
  methods: {
@@ -51,84 +57,82 @@ export default {
51
57
  query: newQuery
52
58
  })
53
59
  },
54
- onDataLoad() {
55
- if (this.widget.options.isAllSum === '1' && this.widget.options.sumApiId !== '' && this.$refs.table.tableData.length > 0) {
56
- getListMapData(this.widget.options.sumApiId, this.params).then(res => {
57
- if (res.data.code === 0) {
58
- console.error("调用总计服务[" + this.widget.options.sumApiId + "]失败:" + res.data.message)
59
- } else {
60
- if (res.data.data.rows.length > 0) {
61
- let sumRows = res.data.data.rows[0];
62
- if (this.columns.length > 0) {
63
- console.log(JSON.stringify(this.columns))
64
- if (this.columns[0].key === 'index' || this.columns[0].key === 'checkbox') {
65
- sumRows[this.columns[1].key] = '总计';
66
- } else {
67
- sumRows[this.columns[0].key] = '总计';
60
+ onDataLoad(res) {
61
+ console.log("onDataLoad res:", res)
62
+ console.log("onDataLoad this.$refs.table.tableData:", this.$refs.table.tableData)
63
+ if (this.widget.options.isAllSum === '1') {
64
+ if (this.widget.options.isPaging === '1' && this.widget.options.sumApiId) {
65
+ // 请求后端做合计
66
+ getListMapData(this.widget.options.sumApiId, this.params).then(res => {
67
+ if (res && res.data.code === 1) {
68
+ if (res.data.data.rows.length > 0) {
69
+ let sumRows = res.data.data.rows[0]
70
+ let bakSumColumns = this.sumColumns
71
+ console.log("onDataLoad this.sumColumns:", this.sumColumns)
72
+ if (bakSumColumns && bakSumColumns.length > 0) {
73
+ sumRows[bakSumColumns[0].key] = '总计'
68
74
  }
75
+ this.allSumData = [sumRows]
76
+ console.log("onDataLoad this.allSumData:", this.allSumData)
69
77
  }
70
- sumRows.is_append_page_all_sum = true;
71
- this.$refs.table.tableData.push(sumRows)
72
- console.log("列表增加总计成功")
78
+ } else {
79
+ console.error("调用总计服务[" + this.widget.options.sumApiId + "]失败:" + res.data.message)
80
+ }
81
+ }).catch(err => {
82
+ console.error("调用总计服务[" + this.widget.options.sumApiId + "]异常:", err)
83
+ })
84
+ } else {
85
+ if (this.$refs.table.tableData && this.$refs.table.tableData.length > 0) {
86
+ // 前端遍历求和
87
+ let sumObj = {}
88
+ let bakSumColumns = this.getLeafNodes(this.sumColumns)
89
+ let tableData = this.$refs.table.tableData
90
+ console.log("onDataLoad bakSumColumns:", bakSumColumns)
91
+ if (bakSumColumns && bakSumColumns.length > 0 && tableData && tableData.length > 0) {
92
+ bakSumColumns.forEach((column, index) => {
93
+ const key = column.key
94
+ if (index === 0) {
95
+ sumObj[key] = "总计"
96
+ } else {
97
+ const values = tableData.map(item => Number(item[key]))
98
+ console.log("onDataLoad values:", values)
99
+ if (!values.every(value => isNaN(value))) {
100
+ const v = values.reduce((prev, curr) => {
101
+ const value = Number(curr)
102
+ if (!isNaN(value)) {
103
+ return accAdd(prev, curr)
104
+ } else {
105
+ return prev
106
+ }
107
+ }, 0)
108
+ sumObj[key] = v
109
+ }
110
+ }
111
+ })
112
+ }
113
+ if (sumObj) {
114
+ this.allSumData = [sumObj]
73
115
  }
116
+ console.log("onDataLoad this.allSumData:", this.allSumData)
74
117
  }
75
- }).catch(err => {
76
- console.error("调用总计服务[" + this.widget.options.sumApiId + "]异常:", err)
77
- })
118
+ }
78
119
  }
79
120
  },
80
- /*合计 根据columns中的hasSum是否合计来进行计算*/
81
- handleSummary({ columns, data }) {
82
- const sums = {};
83
- let tempData = [...data];
84
- if (tempData.length > 0 && tempData[tempData.length - 1].is_append_page_all_sum) {
85
- tempData.pop();
86
- }
87
- columns.forEach((column, index) => {
88
- const key = column.key;
89
- const hasSum = column.hasSum;
90
- if (hasSum === 'true') {
91
- const values = tempData.map(item => Number(item[key]));
92
- if (!values.every(value => isNaN(value))) {
93
- const v = values.reduce((prev, curr) => {
94
- const value = Number(curr);
95
- if (!isNaN(value)) {
96
- return prev + curr;
97
- } else {
98
- return prev;
99
- }
100
- }, 0);
101
- sums[key] = {
102
- key,
103
- value: v
104
- };
105
- } else {
106
- sums[key] = {
107
- key,
108
- value: ''
109
- };
110
- }
111
- } else {
112
- if (key === 'index' || key === 'checkbox') {
113
- sums[key] = {
114
- key,
115
- value: '合计'
116
- };
117
- } else if (index === 0) {
118
- sums[key] = {
119
- key,
120
- value: '合计'
121
- };
121
+ getLeafNodes(columns) {
122
+ const leafNodes = [];
123
+ function traverse(nodes) {
124
+ if (!Array.isArray(nodes)) return;
125
+ for (const node of nodes) {
126
+ if (!node.children || !Array.isArray(node.children) || node.children.length === 0) {
127
+ leafNodes.push(node);
122
128
  } else {
123
- sums[key] = {
124
- key,
125
- value: ''
126
- };
129
+ traverse(node.children);
127
130
  }
128
131
  }
129
- });
130
- return sums;
131
- },
132
+ }
133
+ traverse(columns);
134
+ return leafNodes;
135
+ }
132
136
  },
133
137
  created() {
134
138
  // eslint-disable-next-line
@@ -141,9 +145,7 @@ export default {
141
145
  computed: {
142
146
  columns() {
143
147
  let columns = []
144
- if (this.widget &&
145
- Object.prototype.hasOwnProperty.call(this.widget, "options") &&
146
- Object.prototype.hasOwnProperty.call(this.widget.options, "columns")) {
148
+ if (this.widget && Object.prototype.hasOwnProperty.call(this.widget, "options") && Object.prototype.hasOwnProperty.call(this.widget.options, "columns")) {
147
149
  if (this.widget.options.columns) {
148
150
  try {
149
151
  columns = JSON.parse(this.widget.options.columns)
@@ -174,6 +176,26 @@ export default {
174
176
  }
175
177
  }
176
178
  return columns
179
+ },
180
+ sumColumns() {
181
+ let sumColumnsStr = this.widget.options.columns
182
+ let sumColumns = []
183
+ if (this.widget && Object.prototype.hasOwnProperty.call(this.widget, "options") && Object.prototype.hasOwnProperty.call(this.widget.options, "columns")) {
184
+ if (this.widget.options.columns) {
185
+ try {
186
+ sumColumns = JSON.parse(sumColumnsStr)
187
+ if (sumColumns && sumColumns.length > 0) {
188
+ sumColumns[0].type = '-'
189
+ }
190
+ } catch (e) {
191
+ console.log("BiPagingTable -- sumColumns() -- this.widget.options.columns:", this.widget.options.columns)
192
+ console.error("BiPagingTable -- sumColumns() JSON解析异常:", e)
193
+ this.$Message.info("合计的表格头配置数据解析异常")
194
+ sumColumns = []
195
+ }
196
+ }
197
+ }
198
+ return sumColumns
177
199
  }
178
200
  },
179
201
  watch: {
@@ -183,7 +205,7 @@ export default {
183
205
  this.doSearch()
184
206
  },
185
207
  deep: true
186
- },
208
+ }
187
209
  }
188
210
  }
189
211
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambo-design-bi",
3
- "version": "0.0.187",
3
+ "version": "0.0.188",
4
4
  "description": "Vue2 BI 数据可视化组件库",
5
5
  "main": "index.js",
6
6
  "author": "finch",
@@ -1,6 +1,7 @@
1
+ /* eslint-disable */
2
+
1
3
  import * as echarts from 'echarts'
2
4
  import { accDiv, accMul } from '../../../util/math-util'
3
-
4
5
  function generateTestData(dataX, dataY) {
5
6
  const colors = ['#2D8CF0', '#45D5D0', '#FFBF37', '#FD7336', '#655EF3', '#47D791', '#F85968', '#33BFF3', '#C74EF4', '#F84CEE', '#F5D000', '#B7E518'];
6
7
  const total = dataY.reduce((a, b) => a + b, 0);
@@ -97,6 +98,8 @@ function getLevelOption() {
97
98
  * 43 仪表盘进度条)
98
99
  * 2A:单个柱状图以及多条折线,
99
100
  * 2B:单个柱状图以及左右 <br/>
101
+ * 44:雷达图
102
+ * 45:圆形进度图
100
103
  * dataX:x轴数据 <br/>
101
104
  * dataY:y轴数据 <br/>
102
105
  * dataDim:组数据 (例如柱状图、折线图多组数据) <br/>
@@ -471,8 +474,8 @@ export function getECharts(type, dataX, dataY, dataDim) {
471
474
  shadowBlur: 4
472
475
  },
473
476
  data: [
474
- {type: 'max', name: '最大值'},
475
- {type: 'min', name: '最小值',}
477
+ {type: 'max', name: 'Max'},
478
+ {type: 'min', name: 'Min',}
476
479
  ]
477
480
  },
478
481
  markLine: {
@@ -2590,6 +2593,283 @@ export function getECharts(type, dataX, dataY, dataDim) {
2590
2593
  series: series
2591
2594
  };
2592
2595
  }
2596
+ else if (type === '44') {
2597
+ const indicators = dataX.map((name, index) => {
2598
+ let maxValue = 0;
2599
+ for (let i = 0; i < dataY.length; i++) {
2600
+ if (dataY[i] && dataY[i][index] > maxValue) {
2601
+ maxValue = dataY[i][index];
2602
+ }
2603
+ }
2604
+ return {
2605
+ name: name,
2606
+ max: Math.ceil(maxValue * 1.2)
2607
+ };
2608
+ });
2609
+
2610
+ const seriesData = [];
2611
+
2612
+ dataY.forEach((data, index) => {
2613
+ seriesData.push({
2614
+ name: dataDim[index],
2615
+ value: data,
2616
+ lineStyle: {
2617
+ width: 3,
2618
+ color: colors[index % colors.length]
2619
+ },
2620
+ itemStyle: {
2621
+ color: colors[index % colors.length]
2622
+ },
2623
+ areaStyle: {
2624
+ color: areaColors[index % areaColors.length]
2625
+ }
2626
+ });
2627
+ });
2628
+ opt = {
2629
+ legend: {
2630
+ data: seriesData.map(item => item.name),
2631
+ bottom:0,
2632
+ textStyle: {
2633
+ fontSize: 12
2634
+ }
2635
+ },
2636
+ tooltip: {},
2637
+ radar: {
2638
+ center: ['50%', '45%'],
2639
+ radius: '70%',
2640
+ indicator: indicators,
2641
+ name: {
2642
+ textStyle: {
2643
+ color: '#666',
2644
+ fontSize: 14,
2645
+ fontWeight: 'bold'
2646
+ }
2647
+ },
2648
+ axisLine: {
2649
+ lineStyle: {
2650
+ color: '#ddd'
2651
+ }
2652
+ },
2653
+ splitLine: {
2654
+ lineStyle: {
2655
+ color: '#eee'
2656
+ }
2657
+ },
2658
+ splitArea: {
2659
+ show: true,
2660
+ areaStyle: {
2661
+ color: ['rgba(250,250,250,0.8)', 'rgba(240,240,240,0.8)']
2662
+ }
2663
+ }
2664
+ },
2665
+ series: [{
2666
+ type: 'radar',
2667
+ data: seriesData,
2668
+ symbol: 'circle',
2669
+ symbolSize: 6,
2670
+ lineStyle: {
2671
+ type: 'solid'
2672
+ }
2673
+ }],
2674
+ animation: true,
2675
+ animationDuration: 1000
2676
+ };
2677
+ }
2678
+ else if (type === '45') {
2679
+ const progressValue = dataY[0] && dataY[0][0] ? dataY[0][0] : 0;
2680
+ const maxValue = 100;
2681
+
2682
+ const colorIndex = dataY[0] && dataY[0][1] ? dataY[0][1] % colors.length : 0;
2683
+ const baseColor = colors[colorIndex];
2684
+ const lightColor = hexToRgba(baseColor, 0.6);
2685
+
2686
+ const progressGradient = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
2687
+ { offset: 0, color: baseColor },
2688
+ { offset: 0.5, color: lightColor },
2689
+ { offset: 1, color: hexToRgba(baseColor, 0.3) }
2690
+ ]);
2691
+
2692
+ const bgColorIndex = (colorIndex + 1) % colors.length;
2693
+ const bgColor = colors[bgColorIndex];
2694
+ const bgColorTransparent = hexToRgba(bgColor, 0);
2695
+ const bgColorSolid = hexToRgba(bgColor, 0.4);
2696
+
2697
+ opt = {
2698
+ title: [
2699
+ {
2700
+ text: dataDim[0] || '',
2701
+ x: 'center',
2702
+ top: '32%',
2703
+ textStyle: {
2704
+ fontSize: 26,
2705
+ fontFamily: 'Microsoft YaHei',
2706
+ fontWeight: '400',
2707
+ color: '#777',
2708
+ textShadow: '0 1px 2px rgba(0,0,0,0.05)'
2709
+ },
2710
+ },
2711
+ {
2712
+ text: `${progressValue}%`,
2713
+ x: 'center',
2714
+ top: '50%',
2715
+ textStyle: {
2716
+ fontSize: 52,
2717
+ color: baseColor,
2718
+ fontWeight: '500',
2719
+ fontFamily: 'Microsoft YaHei',
2720
+ textShadow: '0 2px 4px rgba(0,0,0,0.1)'
2721
+ },
2722
+ },
2723
+ ],
2724
+ polar: {
2725
+ radius: ['44%', '50%'],
2726
+ center: ['50%', '50%'],
2727
+ },
2728
+ angleAxis: {
2729
+ max: maxValue,
2730
+ show: false,
2731
+ },
2732
+ radiusAxis: {
2733
+ type: 'category',
2734
+ show: true,
2735
+ axisLabel: {
2736
+ show: false,
2737
+ },
2738
+ axisLine: {
2739
+ show: false,
2740
+ },
2741
+ axisTick: {
2742
+ show: false,
2743
+ },
2744
+ },
2745
+ series: [
2746
+ {
2747
+ type: 'pie',
2748
+ startAngle: 90,
2749
+ radius: ['83%', '90%'],
2750
+ center: ['50%', '50%'],
2751
+ hoverAnimation: true,
2752
+ animationType: 'scale',
2753
+ animationEasing: 'cubicOut',
2754
+ itemStyle: {
2755
+ borderRadius: 10,
2756
+ borderWidth: 0,
2757
+ shadowColor: hexToRgba(baseColor, 0.3),
2758
+ shadowBlur: 10,
2759
+ shadowOffsetY: 2
2760
+ },
2761
+ data: [
2762
+ {
2763
+ hoverOffset: 5,
2764
+ value: progressValue,
2765
+ name: '',
2766
+ itemStyle: {
2767
+ color: progressGradient,
2768
+ borderWidth: 0,
2769
+ borderColor: hexToRgba(baseColor, 0.8),
2770
+ },
2771
+ label: {
2772
+ show: false,
2773
+ },
2774
+ labelLine: {
2775
+ lineStyle: {
2776
+ width: 0,
2777
+ },
2778
+ },
2779
+ emphasis: {
2780
+ itemStyle: {
2781
+ shadowColor: hexToRgba(baseColor, 0.5),
2782
+ shadowBlur: 15,
2783
+ }
2784
+ }
2785
+ },
2786
+ {
2787
+ label: {
2788
+ show: false,
2789
+ },
2790
+ labelLine: {
2791
+ lineStyle: {
2792
+ width: 0,
2793
+ },
2794
+ },
2795
+ value: maxValue - progressValue,
2796
+ itemStyle: {
2797
+ color: 'rgba(200, 200, 200, 0.1)',
2798
+ borderWidth: 0,
2799
+ },
2800
+ },
2801
+ ],
2802
+ },
2803
+ {
2804
+ name: '',
2805
+ type: 'pie',
2806
+ startAngle: 90,
2807
+ radius: ['78%', '82%'],
2808
+ hoverAnimation: false,
2809
+ center: ['50%', '50%'],
2810
+ itemStyle: {
2811
+ color: new echarts.graphic.RadialGradient(0.5, 0.5, 0.7, [
2812
+ {
2813
+ offset: 0,
2814
+ color: bgColorSolid,
2815
+ },
2816
+ {
2817
+ offset: 0.8,
2818
+ color: hexToRgba(bgColor, 0.2),
2819
+ },
2820
+ {
2821
+ offset: 1,
2822
+ color: bgColorTransparent,
2823
+ },
2824
+ ]),
2825
+ borderWidth: 0,
2826
+ shadowBlur: 8,
2827
+ shadowColor: hexToRgba(bgColor, 0.15),
2828
+ },
2829
+ data: [
2830
+ {
2831
+ value: maxValue,
2832
+ itemStyle: {
2833
+ borderWidth: 0,
2834
+ }
2835
+ },
2836
+ ],
2837
+ label: {
2838
+ show: false
2839
+ },
2840
+ },
2841
+ {
2842
+ name: '',
2843
+ type: 'pie',
2844
+ radius: '70%',
2845
+ center: ['50%', '50%'],
2846
+ startAngle: 90,
2847
+ hoverAnimation: false,
2848
+ silent: true,
2849
+ itemStyle: {
2850
+ color: new echarts.graphic.RadialGradient(0.5, 0.5, 0.5, [
2851
+ { offset: 0, color: hexToRgba(baseColor, 0.08) },
2852
+ { offset: 0.7, color: hexToRgba(baseColor, 0.03) },
2853
+ { offset: 1, color: hexToRgba(baseColor, 0) }
2854
+ ]),
2855
+ borderWidth: 0,
2856
+ },
2857
+ label: {
2858
+ show: false
2859
+ },
2860
+ data: [{
2861
+ value: 100,
2862
+ itemStyle: {
2863
+ borderWidth: 0
2864
+ }
2865
+ }]
2866
+ }
2867
+ ],
2868
+ animation: true,
2869
+ animationDuration: 1000,
2870
+ animationEasing: 'cubicOut'
2871
+ };
2872
+ }
2593
2873
  return opt
2594
2874
  }
2595
2875
 
package/util/math-util.js CHANGED
@@ -1,78 +1,252 @@
1
- //js除法计算
2
- export function accDiv(arg1, arg2) {
3
- if (arg2 === 0) {
4
- throw new Error('Division by zero');
1
+ /**
2
+ * 高精度加法计算(修复JS浮点数精度问题)
3
+ * @param {number|string} arg1 - 第一个加数
4
+ * @param {number|string} arg2 - 第二个加数
5
+ * @returns {number} 计算结果
6
+ */
7
+ export function accAdd(arg1, arg2) {
8
+ // 参数校验
9
+ if (arg1 == null || arg2 == null) {
10
+ console.warn('accAdd: 参数不能为null或undefined');
11
+ return NaN;
5
12
  }
6
13
 
7
- let t1 = 0, t2 = 0;
8
- const str1 = arg1.toString();
9
- const str2 = arg2.toString();
14
+ // 转换为数字
15
+ const num1 = Number(arg1);
16
+ const num2 = Number(arg2);
10
17
 
11
- // 处理arg1的小数位
12
- if (str1.includes('.')) {
13
- t1 = str1.split('.')[1].length;
18
+ // 检查是否为有效数字
19
+ if (isNaN(num1) || isNaN(num2)) {
20
+ console.warn(`accAdd: 参数无效,arg1=${arg1}, arg2=${arg2}`);
21
+ return NaN;
14
22
  }
15
23
 
16
- // 处理arg2的小数位
17
- if (str2.includes('.')) {
18
- t2 = str2.split('.')[1].length;
24
+ // 如果是整数,直接相加(不会有精度问题)
25
+ if (Number.isInteger(num1) && Number.isInteger(num2)) {
26
+ return num1 + num2;
19
27
  }
20
28
 
21
- const r1 = Number(str1.replace('.', ''));
22
- const r2 = Number(str2.replace('.', ''));
29
+ // 处理科学计数法
30
+ const str1 = num1.toString();
31
+ const str2 = num2.toString();
23
32
 
24
- return (r1 / r2) * Math.pow(10, t2 - t1);
33
+ // 获取小数位数
34
+ const getDecimalLength = (numStr) => {
35
+ // 处理科学计数法
36
+ if (numStr.includes('e') || numStr.includes('E')) {
37
+ // 将科学计数法转换为普通小数
38
+ const [base, exponent] = numStr.toLowerCase().split('e');
39
+ const baseDecimalLength = base.includes('.') ? base.split('.')[1].length : 0;
40
+ const exp = parseInt(exponent);
41
+ return Math.max(0, baseDecimalLength - exp);
42
+ }
43
+
44
+ // 普通小数
45
+ const decimalPart = numStr.split('.')[1];
46
+ return decimalPart ? decimalPart.length : 0;
47
+ };
48
+
49
+ try {
50
+ const decimalLen1 = getDecimalLength(str1);
51
+ const decimalLen2 = getDecimalLength(str2);
52
+ const maxDecimalLen = Math.max(decimalLen1, decimalLen2);
53
+
54
+ // 使用放大倍数法计算
55
+ const multiplier = Math.pow(10, maxDecimalLen);
56
+
57
+ // 使用精确的乘法避免中间步骤的精度丢失
58
+ const multiply = (num, multiplier) => {
59
+ // 分离整数和小数部分
60
+ const [integerPart, decimalPart = ''] = num.toString().split('.');
61
+ const decimalNum = parseInt(decimalPart.padEnd(maxDecimalLen, '0'));
62
+ const integerNum = parseInt(integerPart || '0');
63
+ return integerNum * multiplier + decimalNum;
64
+ };
65
+
66
+ const result = (multiply(num1, multiplier) + multiply(num2, multiplier)) / multiplier;
67
+
68
+ // 处理可能的精度误差
69
+ return parseFloat(result.toFixed(maxDecimalLen));
70
+ } catch (error) {
71
+ console.error('accAdd 计算错误:', error);
72
+ // 降级处理:使用toFixed
73
+ const maxDecimal = Math.max(
74
+ (str1.split('.')[1] || '').length,
75
+ (str2.split('.')[1] || '').length
76
+ );
77
+ return parseFloat((num1 + num2).toFixed(maxDecimal));
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 高精度减法
83
+ * @param {number|string} arg1 - 被减数
84
+ * @param {number|string} arg2 - 减数
85
+ * @returns {number} 计算结果
86
+ */
87
+ export function accSub(arg1, arg2) {
88
+ return accAdd(arg1, -arg2);
25
89
  }
26
90
 
27
- //js乘法计算
91
+ /**
92
+ * 高精度乘法
93
+ * @param {number|string} arg1 - 第一个乘数
94
+ * @param {number|string} arg2 - 第二个乘数
95
+ * @returns {number} 计算结果
96
+ */
28
97
  export function accMul(arg1, arg2) {
29
- let m = 0, s1 = arg1.toString(), s2 = arg2.toString();
30
-
31
- // 安全地获取小数位数
32
- if (s1.includes('.')) {
33
- m += s1.split(".")[1].length;
34
- }
35
- if (s2.includes('.')) {
36
- m += s2.split(".")[1].length;
98
+ // 参数校验
99
+ if (arg1 == null || arg2 == null) {
100
+ console.warn('accMul: 参数不能为null或undefined');
101
+ return NaN;
37
102
  }
38
103
 
39
- return accDiv(Number(s1.replace(".", "")) * Number(s2.replace(".", "")), Math.pow(10, m));
40
- }
41
-
42
- //js加法计算
43
- export function accAdd(arg1, arg2) {
44
- let r1, r2, m;
45
- try {
46
- r1 = arg1.toString().split(".")[1].length
47
- } catch (e) {
48
- r1 = 0
49
- console.error('accAdd:', e)
104
+ const num1 = Number(arg1);
105
+ const num2 = Number(arg2);
106
+
107
+ if (isNaN(num1) || isNaN(num2)) {
108
+ console.warn(`accMul: 参数无效,arg1=${arg1}, arg2=${arg2}`);
109
+ return NaN;
50
110
  }
111
+
112
+ // 处理科学计数法
113
+ const str1 = num1.toString();
114
+ const str2 = num2.toString();
115
+
116
+ const getDecimalLength = (numStr) => {
117
+ if (numStr.includes('e') || numStr.includes('E')) {
118
+ const [base, exponent] = numStr.toLowerCase().split('e');
119
+ const baseDecimalLength = base.includes('.') ? base.split('.')[1].length : 0;
120
+ const exp = parseInt(exponent);
121
+ return Math.max(0, baseDecimalLength - exp);
122
+ }
123
+
124
+ const decimalPart = numStr.split('.')[1];
125
+ return decimalPart ? decimalPart.length : 0;
126
+ };
127
+
51
128
  try {
52
- r2 = arg2.toString().split(".")[1].length
53
- } catch (e) {
54
- r2 = 0
55
- console.error('accAdd:', e)
129
+ const decimalLen1 = getDecimalLength(str1);
130
+ const decimalLen2 = getDecimalLength(str2);
131
+ const totalDecimalLen = decimalLen1 + decimalLen2;
132
+
133
+ // 转换为整数相乘
134
+ const int1 = parseInt(str1.replace('.', '').replace(/e[+-]?\d+/i, ''));
135
+ const int2 = parseInt(str2.replace('.', '').replace(/e[+-]?\d+/i, ''));
136
+
137
+ // 处理指数
138
+ const getExponent = (numStr) => {
139
+ if (numStr.includes('e')) {
140
+ return parseInt(numStr.toLowerCase().split('e')[1]);
141
+ }
142
+ return 0;
143
+ };
144
+
145
+ const exponent = getExponent(str1) + getExponent(str2);
146
+
147
+ // 计算结果
148
+ let result = int1 * int2;
149
+
150
+ // 应用小数位数
151
+ if (totalDecimalLen > 0) {
152
+ result /= Math.pow(10, totalDecimalLen);
153
+ }
154
+
155
+ // 应用指数
156
+ if (exponent !== 0) {
157
+ result *= Math.pow(10, exponent);
158
+ }
159
+
160
+ // 处理可能的精度误差
161
+ return parseFloat(result.toFixed(Math.min(15, totalDecimalLen)));
162
+ } catch (error) {
163
+ console.error('accMul 计算错误:', error);
164
+ // 降级处理
165
+ return parseFloat((num1 * num2).toFixed(12));
56
166
  }
57
- m = Math.pow(10, Math.max(r1, r2));
58
- return accDiv((accMul(arg1, m) + accMul(arg2, m)), m);
59
167
  }
60
168
 
61
- //js减法计算
62
- export function accSub(arg1, arg2) {
63
- let r1, r2, m;
64
- try {
65
- r1 = arg1.toString().split(".")[1].length
66
- } catch (e) {
67
- r1 = 0
68
- console.error('accSub:', e)
169
+ /**
170
+ * 高精度除法
171
+ * @param {number|string} arg1 - 被除数
172
+ * @param {number|string} arg2 - 除数
173
+ * @returns {number} 计算结果
174
+ * @throws {Error} 除数为0时抛出错误
175
+ */
176
+ export function accDiv(arg1, arg2) {
177
+ // 参数校验
178
+ if (arg1 == null || arg2 == null) {
179
+ console.warn('accDiv: 参数不能为null或undefined');
180
+ return NaN;
181
+ }
182
+
183
+ const num1 = Number(arg1);
184
+ const num2 = Number(arg2);
185
+
186
+ if (isNaN(num1) || isNaN(num2)) {
187
+ console.warn(`accDiv: 参数无效,arg1=${arg1}, arg2=${arg2}`);
188
+ return NaN;
189
+ }
190
+
191
+ // 检查除数是否为0
192
+ if (num2 === 0) {
193
+ console.error('accDiv: 除数不能为0');
194
+ return Infinity * Math.sign(num1);
69
195
  }
196
+
197
+ // 处理科学计数法
198
+ const str1 = num1.toString();
199
+ const str2 = num2.toString();
200
+
201
+ const getDecimalLength = (numStr) => {
202
+ if (numStr.includes('e') || numStr.includes('E')) {
203
+ const [base, exponent] = numStr.toLowerCase().split('e');
204
+ const baseDecimalLength = base.includes('.') ? base.split('.')[1].length : 0;
205
+ const exp = parseInt(exponent);
206
+ return Math.max(0, baseDecimalLength - exp);
207
+ }
208
+
209
+ const decimalPart = numStr.split('.')[1];
210
+ return decimalPart ? decimalPart.length : 0;
211
+ };
212
+
70
213
  try {
71
- r2 = arg2.toString().split(".")[1].length
72
- } catch (e) {
73
- r2 = 0
74
- console.error('accSub:', e)
214
+ const decimalLen1 = getDecimalLength(str1);
215
+ const decimalLen2 = getDecimalLength(str2);
216
+ const maxDecimalLen = Math.max(decimalLen1, decimalLen2);
217
+
218
+ // 转换为整数相除
219
+ const int1 = parseInt(str1.replace('.', '').replace(/e[+-]?\d+/i, ''));
220
+ const int2 = parseInt(str2.replace('.', '').replace(/e[+-]?\d+/i, ''));
221
+
222
+ // 调整小数位数差异
223
+ const adjustFactor = Math.pow(10, Math.abs(decimalLen1 - decimalLen2));
224
+
225
+ let result;
226
+ if (decimalLen1 >= decimalLen2) {
227
+ result = int1 / (int2 * adjustFactor);
228
+ } else {
229
+ result = (int1 * adjustFactor) / int2;
230
+ }
231
+
232
+ // 处理指数
233
+ const getExponent = (numStr) => {
234
+ if (numStr.includes('e')) {
235
+ return parseInt(numStr.toLowerCase().split('e')[1]);
236
+ }
237
+ return 0;
238
+ };
239
+
240
+ const exponent = getExponent(str1) - getExponent(str2);
241
+ if (exponent !== 0) {
242
+ result *= Math.pow(10, exponent);
243
+ }
244
+
245
+ // 处理可能的精度误差
246
+ return parseFloat(result.toFixed(Math.min(15, maxDecimalLen + 4)));
247
+ } catch (error) {
248
+ console.error('accDiv 计算错误:', error);
249
+ // 降级处理
250
+ return parseFloat((num1 / num2).toFixed(12));
75
251
  }
76
- m = Math.pow(10, Math.max(r1, r2));
77
- return accDiv((accMul(arg1, m) - accMul(arg2, m)), m);
78
252
  }