n20-common-lib 3.1.4 → 3.1.5
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.
- package/package.json +1 -1
- package/src/assets/css/_coreLib.scss +1 -0
- package/src/assets/css/pivot.scss +1330 -0
- package/src/components/AdvancedFilter/index.vue +38 -10
- package/src/components/AdvancedFilter/utils.js +0 -8
- package/src/components/Pivot/CLAUDE.md +117 -0
- package/src/components/Pivot/ChartView.vue +109 -0
- package/src/components/Pivot/ConfigSidebar.vue +518 -0
- package/src/components/Pivot/MainToolbar.vue +160 -0
- package/src/components/Pivot/ReportSidebar.vue +172 -0
- package/src/components/Pivot/TableView.vue +263 -0
- package/src/components/Pivot/index.vue +905 -1481
- package/src/components/Pivot//344/275/277/347/224/250/350/257/264/346/230/216.md +240 -0
- package/src/components/Pivot//350/257/264/346/230/216/346/226/207/346/241/243.md +171 -0
- package/src/components/v3/TablePro/index.vue +3 -5
- package/style/index.css +1 -1
- package/theme/blue.css +1 -1
- package/theme/cctcRed.css +1 -1
- package/theme/green.css +1 -1
- package/theme/lightBlue.css +1 -1
- package/theme/orange.css +1 -1
- package/theme/purple.css +1 -1
- package/theme/red.css +1 -1
- package/theme/yellow.css +1 -1
|
@@ -110,11 +110,11 @@
|
|
|
110
110
|
// import formItemInput from './form-item-input.vue'
|
|
111
111
|
import XEUtils from 'xe-utils'
|
|
112
112
|
import axios from '../../utils/axios.js'
|
|
113
|
+
import ClDialog from '../Dialog/index.vue'
|
|
113
114
|
import InputSearch from '../InputSearch/index.vue'
|
|
114
115
|
import filterItem from './filterItem.vue'
|
|
115
116
|
import formItemRender from './formItemRender.vue'
|
|
116
|
-
import { getOnlyKey
|
|
117
|
-
import ClDialog from '../Dialog/index.vue'
|
|
117
|
+
import { getOnlyKey } from './utils'
|
|
118
118
|
|
|
119
119
|
const prefixCls = 'n20-advanced-filter'
|
|
120
120
|
export default {
|
|
@@ -194,7 +194,8 @@ export default {
|
|
|
194
194
|
pageNo: this.filterId,
|
|
195
195
|
name: ''
|
|
196
196
|
},
|
|
197
|
-
stVisible: false
|
|
197
|
+
stVisible: false,
|
|
198
|
+
optionsMap: {}
|
|
198
199
|
}
|
|
199
200
|
},
|
|
200
201
|
computed: {
|
|
@@ -247,6 +248,9 @@ export default {
|
|
|
247
248
|
this.getFilterList()
|
|
248
249
|
this.getDefaultCheck()
|
|
249
250
|
},
|
|
251
|
+
beforeDestroy() {
|
|
252
|
+
this.optionsMap = {}
|
|
253
|
+
},
|
|
250
254
|
methods: {
|
|
251
255
|
// 保存视图
|
|
252
256
|
saveSt() {
|
|
@@ -295,7 +299,7 @@ export default {
|
|
|
295
299
|
return getOnlyKey(this.onlyKey, data)
|
|
296
300
|
},
|
|
297
301
|
setOptions(id, opts) {
|
|
298
|
-
|
|
302
|
+
this.optionsMap[id] = opts
|
|
299
303
|
this.setChoices(id, opts)
|
|
300
304
|
},
|
|
301
305
|
setChoices(id, data) {
|
|
@@ -333,6 +337,11 @@ export default {
|
|
|
333
337
|
return hasValue(this.model[item.value]) ? this.prefixCls + '-active' : ''
|
|
334
338
|
},
|
|
335
339
|
handleClose(item) {
|
|
340
|
+
const key = item[this.onlyKey] || item.value
|
|
341
|
+
if (this.optionsMap[key]) {
|
|
342
|
+
delete this.optionsMap[key]
|
|
343
|
+
}
|
|
344
|
+
|
|
336
345
|
switch (item.type) {
|
|
337
346
|
case 'select':
|
|
338
347
|
this.$set(this.model, item.value, item.multiple ? [] : null)
|
|
@@ -359,12 +368,18 @@ export default {
|
|
|
359
368
|
break
|
|
360
369
|
case 'daterange':
|
|
361
370
|
case 'datetimerange':
|
|
362
|
-
case '
|
|
371
|
+
case 'monthrange':
|
|
363
372
|
this.$set(this.model, item.startDate, null)
|
|
364
373
|
this.$set(this.model, item.endDate, null)
|
|
365
374
|
break
|
|
366
375
|
case 'slot': {
|
|
367
376
|
this.$set(this.model, item.value, null)
|
|
377
|
+
// 清空 slotFields 中定义的所有字段
|
|
378
|
+
if (item.slotFields?.length) {
|
|
379
|
+
item.slotFields.forEach((field) => {
|
|
380
|
+
this.$set(this.model, field, null)
|
|
381
|
+
})
|
|
382
|
+
}
|
|
368
383
|
break
|
|
369
384
|
}
|
|
370
385
|
}
|
|
@@ -384,15 +399,28 @@ export default {
|
|
|
384
399
|
setModelData(list) {
|
|
385
400
|
let arr = []
|
|
386
401
|
list.forEach((res) => {
|
|
402
|
+
// 收集普通字段的 value
|
|
387
403
|
if (res.value) {
|
|
388
404
|
arr.push(res.value)
|
|
389
|
-
}
|
|
405
|
+
}
|
|
406
|
+
// 收集日期范围类型的字段
|
|
407
|
+
if (res.startDate && res.endDate) {
|
|
390
408
|
arr.push(res.startDate)
|
|
391
409
|
arr.push(res.endDate)
|
|
392
|
-
}
|
|
410
|
+
}
|
|
411
|
+
// 收集数字范围类型的字段
|
|
412
|
+
if (res.startValue && res.endValue) {
|
|
393
413
|
arr.push(res.startValue)
|
|
394
414
|
arr.push(res.endValue)
|
|
395
415
|
}
|
|
416
|
+
// 收集插槽类型可能的自定义字段(slotFields 数组)
|
|
417
|
+
if (res.type === 'slot' && Array.isArray(res?.slotFields)) {
|
|
418
|
+
res?.slotFields.forEach((field) => {
|
|
419
|
+
if (field && !arr.includes(field)) {
|
|
420
|
+
arr.push(field)
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
}
|
|
396
424
|
})
|
|
397
425
|
for (const key in this.model) {
|
|
398
426
|
if (Object.hasOwnProperty.call(this.model, key)) {
|
|
@@ -523,9 +551,9 @@ export default {
|
|
|
523
551
|
this.mackData(_data)
|
|
524
552
|
})
|
|
525
553
|
.finally(() => {
|
|
526
|
-
for (const key in
|
|
527
|
-
if (Object.hasOwnProperty.call(
|
|
528
|
-
const data =
|
|
554
|
+
for (const key in this.optionsMap) {
|
|
555
|
+
if (Object.hasOwnProperty.call(this.optionsMap, key)) {
|
|
556
|
+
const data = this.optionsMap[key]
|
|
529
557
|
this.setChoices(key, data)
|
|
530
558
|
}
|
|
531
559
|
}
|
|
@@ -1,10 +1,2 @@
|
|
|
1
1
|
export const getOnlyKey = (key, data) =>
|
|
2
2
|
key ? data[key] : Date.now().toString(36) + Math.random().toString(36).slice(2, 7)
|
|
3
|
-
let options = {}
|
|
4
|
-
export const setOptionsMap = (key, value) => {
|
|
5
|
-
options[key] = value
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const getOptionsMap = () => {
|
|
9
|
-
return options
|
|
10
|
-
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Pivot 数据透视组件
|
|
2
|
+
|
|
3
|
+
## 组件作用描述
|
|
4
|
+
|
|
5
|
+
Pivot 是一个基于 Vue 2.6 和 vxe-table 的数据透视组件,通过 Dialog 弹窗呈现。核心功能是将外部传入的表格数据进行多维度聚合分析,支持分组统计和交叉统计两种模式,可视化方式包括图表视图和表格视图。
|
|
6
|
+
|
|
7
|
+
## 依赖说明
|
|
8
|
+
|
|
9
|
+
| 依赖 | 版本 | 用途 |
|
|
10
|
+
| ---------- | ---- | ------------------------------ |
|
|
11
|
+
| vxe-table | \* | 表格核心,支持大数据虚拟滚动 |
|
|
12
|
+
| xe-utils | \* | vxe-table 工具库,数据聚合计算 |
|
|
13
|
+
| echarts | \* | 图表渲染(仅分组统计模式可用) |
|
|
14
|
+
| Dialog | 内置 | 弹窗容器 |
|
|
15
|
+
| ViewToggle | 内置 | 视图切换控件 |
|
|
16
|
+
|
|
17
|
+
## Props 接口
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
{
|
|
21
|
+
visible: Boolean // 控制弹窗显示,默认 false
|
|
22
|
+
dataSource: Array // 数据源,外部传入的原始表格数据
|
|
23
|
+
columns: Array // 列定义,格式: { key, label, type }
|
|
24
|
+
dimensionsField: Array // 维度字段,未指定默认columns中string类型的字段
|
|
25
|
+
metricsField: Array // 聚合计算字段,未指定默认columns中number类型的字段
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### columns 列定义
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// 维度字段 typeof dataSource[index].region === 'string'
|
|
33
|
+
{ prop: 'region', label: '地区' }
|
|
34
|
+
// 指标字段 typeof dataSource[index].amount === 'number'
|
|
35
|
+
{ prop: 'amount', label: '金额' }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> 分组维度 (dimensions) 和计算指标 (metrics) 的可选项由外部通过 `columns` 配置传入,组件根据 dataSource 数据源中对应的 columns 中的 prop 自动区分:
|
|
39
|
+
>
|
|
40
|
+
> - `'string' | 'date'` → 作为维度字段
|
|
41
|
+
> - `'number'` → 作为指标字段(支持聚合)
|
|
42
|
+
|
|
43
|
+
## 核心功能
|
|
44
|
+
|
|
45
|
+
### 1. 统计模式
|
|
46
|
+
|
|
47
|
+
| 模式 | 说明 | 可视化 |
|
|
48
|
+
| ---------------- | -------------------------------- | ------------------- |
|
|
49
|
+
| 分组统计 (group) | 按维度字段分组,聚合计算每个指标 | 图表视图 + 表格视图 |
|
|
50
|
+
| 交叉统计 (cross) | 行列交叉分析 | 仅表格视图 |
|
|
51
|
+
|
|
52
|
+
### 2. 聚合方式
|
|
53
|
+
|
|
54
|
+
每项指标支持独立设置聚合类型:
|
|
55
|
+
|
|
56
|
+
| 类型 | 说明 |
|
|
57
|
+
| ----- | ------ |
|
|
58
|
+
| sum | 求和 |
|
|
59
|
+
| count | 计数 |
|
|
60
|
+
| avg | 平均值 |
|
|
61
|
+
| max | 最大值 |
|
|
62
|
+
| min | 最小值 |
|
|
63
|
+
|
|
64
|
+
### 3. 图表类型(仅分组统计模式)
|
|
65
|
+
|
|
66
|
+
- 柱状图
|
|
67
|
+
- 折线图
|
|
68
|
+
- 饼图
|
|
69
|
+
- 条形图
|
|
70
|
+
|
|
71
|
+
> 交叉统计模式仅支持表格展示,不渲染图表。
|
|
72
|
+
|
|
73
|
+
### 4. 多报表管理
|
|
74
|
+
|
|
75
|
+
- 新建报表
|
|
76
|
+
- 重命名报表
|
|
77
|
+
- 删除报表
|
|
78
|
+
- 报表配置本地持久化 (localStorage)
|
|
79
|
+
|
|
80
|
+
## 布局结构
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
84
|
+
│ Dialog │
|
|
85
|
+
│ width: 1360px (固定) │
|
|
86
|
+
├──────────────┬─────────────────────────────┬─────────────────┤
|
|
87
|
+
│ 左侧边栏 │ 主内容区域 │ 右侧边栏 │
|
|
88
|
+
│ 280px │ flex: 1 │ 280px │
|
|
89
|
+
│ │ │ │
|
|
90
|
+
│ 我的报表清单 │ 工具栏 (标题/统计信息) │ 维度配置 │
|
|
91
|
+
│ - 报表1 ├─────────────────────────────┤ │
|
|
92
|
+
│ - 报表2 │ │ 报表标题 │
|
|
93
|
+
│ - 报表3 │ 图表视图 / 表格视图 │ 统计类型 │
|
|
94
|
+
│ │ │ 展示形式 │
|
|
95
|
+
│ │ │ 分组维度 │
|
|
96
|
+
│ │ │ 计算指标 │
|
|
97
|
+
└──────────────┴─────────────────────────────┴─────────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 开发进度
|
|
101
|
+
|
|
102
|
+
| 功能 | 状态 | 说明 |
|
|
103
|
+
| ---------- | --------------- | -------------------------- |
|
|
104
|
+
| 分组统计 | ✅ 完成,待优化 | 支持图表和表格双视图 |
|
|
105
|
+
| 交叉统计 | ✅ 完成,待优化 | 仅表格视图,无图表 |
|
|
106
|
+
| 图表渲染 | ✅ 完成,待优化 | echarts 实现,分组模式可用 |
|
|
107
|
+
| 虚拟滚动 | ⬜ 待实现 | 依赖 vxe-table,大数据优化 |
|
|
108
|
+
| 指标聚合 | ✅ 完成,待优化 | 支持每指标独立设置聚合类型 |
|
|
109
|
+
| 多报表管理 | ✅ 完成,待优化 | CRUD + localStorage 持久化 |
|
|
110
|
+
| 分页 | ✅ 完成,待优化 | 表格统计支持 |
|
|
111
|
+
|
|
112
|
+
## 注意事项
|
|
113
|
+
|
|
114
|
+
1. **组件宽度固定为 1360px**,不接受动态调整
|
|
115
|
+
2. **dimensions 和 metrics 由外部传入**,如未配置,组件根据 columns 配置自动派生可用选项
|
|
116
|
+
3. **交叉统计模式不渲染图表**,切换到该模式时会自动切换为表格视图
|
|
117
|
+
4. **虚拟滚动待实现**,当前版本在大数据量场景下可能存在性能问题
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 图表视图 -->
|
|
3
|
+
<div class="chart-view">
|
|
4
|
+
<div v-if="hasData" class="chart-wrapper">
|
|
5
|
+
<div class="chart-header">
|
|
6
|
+
<div class="chart-title">数据概览</div>
|
|
7
|
+
<div class="chart-desc">按维度「{{ dimensionLabel }}」聚合后的结果,共 {{ dataCount }} 条</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-if="unitLabel" class="chart-unit-label">{{ unitLabel }}</div>
|
|
10
|
+
<div ref="chartContainer" class="chart-container"></div>
|
|
11
|
+
</div>
|
|
12
|
+
<div v-else class="empty-state">
|
|
13
|
+
<Empty type="noData2" :height="200" :width="200" content="暂无数据" />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import Empty from '../Empty/index.vue'
|
|
20
|
+
import importG from '../../utils/importGlobal.js'
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'ChartView',
|
|
24
|
+
components: {
|
|
25
|
+
Empty
|
|
26
|
+
},
|
|
27
|
+
props: {
|
|
28
|
+
// 是否有数据
|
|
29
|
+
hasData: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
// 数据条数
|
|
34
|
+
dataCount: {
|
|
35
|
+
type: Number,
|
|
36
|
+
default: 0
|
|
37
|
+
},
|
|
38
|
+
// 维度标签
|
|
39
|
+
dimensionLabel: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: ''
|
|
42
|
+
},
|
|
43
|
+
// 单位标签
|
|
44
|
+
unitLabel: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: ''
|
|
47
|
+
},
|
|
48
|
+
// 图表配置
|
|
49
|
+
chartOption: {
|
|
50
|
+
type: Object,
|
|
51
|
+
default: () => ({})
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
data() {
|
|
55
|
+
return {
|
|
56
|
+
chartInstance: null
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
watch: {
|
|
60
|
+
// 监听图表配置变化
|
|
61
|
+
chartOption: {
|
|
62
|
+
deep: true,
|
|
63
|
+
handler() {
|
|
64
|
+
if (this.chartInstance) {
|
|
65
|
+
this.chartInstance.setOption(this.chartOption, true)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
methods: {
|
|
71
|
+
// 初始化图表
|
|
72
|
+
async initChart() {
|
|
73
|
+
if (!this.$refs.chartContainer) return
|
|
74
|
+
if (!this.hasData) return
|
|
75
|
+
|
|
76
|
+
const echarts = await importG('echarts', () => import(/*webpackChunkName "echarts"*/ 'echarts'))
|
|
77
|
+
|
|
78
|
+
if (!this.chartInstance) {
|
|
79
|
+
this.chartInstance = echarts.init(this.$refs.chartContainer)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (this.chartOption) {
|
|
83
|
+
this.chartInstance.setOption(this.chartOption, true)
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// 调整图表大小
|
|
88
|
+
resizeChart() {
|
|
89
|
+
if (this.chartInstance) {
|
|
90
|
+
this.chartInstance.resize()
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// 销毁图表
|
|
95
|
+
destroyChart() {
|
|
96
|
+
if (this.chartInstance) {
|
|
97
|
+
this.chartInstance.dispose()
|
|
98
|
+
this.chartInstance = null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
mounted() {
|
|
103
|
+
this.initChart()
|
|
104
|
+
},
|
|
105
|
+
beforeDestroy() {
|
|
106
|
+
this.destroyChart()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
</script>
|