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.
@@ -0,0 +1,172 @@
1
+ <template>
2
+ <!-- 左侧报表清单 -->
3
+ <div class="pivot-sidebar-left" :class="{ collapsed: isCollapsed }">
4
+ <div class="sidebar-header">
5
+ <span class="sidebar-title">我的报表清单</span>
6
+ <div class="add-report-btn" @click="handleAddReport" title="新建">
7
+ <i class="v3-icon-plus"></i>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="report-list">
12
+ <div
13
+ v-for="(report, index) in reportList"
14
+ :key="report.id"
15
+ class="report-item"
16
+ :class="{ active: currentIndex === index, editing: editingId === report.id }"
17
+ @click="handleSelectReport(index)"
18
+ @mouseenter="hoveredId = report.id"
19
+ @mouseleave="hoveredId = null"
20
+ >
21
+ <i :class="getChartTypeIcon(report.chartType)" class="report-icon"></i>
22
+ <!-- 报表名称显示/编辑 -->
23
+ <span v-if="editingId !== report.id" class="report-name">{{ report.name }}</span>
24
+ <input
25
+ v-else
26
+ v-model="editingName"
27
+ class="report-name-input"
28
+ type="text"
29
+ @click.stop
30
+ @blur="handleSaveRename"
31
+ @keyup.enter="handleSaveRename"
32
+ @keyup.esc="handleCancelRename"
33
+ />
34
+ <!-- 更多操作菜单 -->
35
+ <div
36
+ v-if="editingId !== report.id"
37
+ class="report-more"
38
+ :class="{ visible: hoveredId === report.id || activeMenuId === report.id }"
39
+ @click.stop="handleToggleMenu(report.id)"
40
+ >
41
+ <i class="v3-icon-more"></i>
42
+ <div v-if="activeMenuId === report.id" class="report-menu">
43
+ <div class="report-menu-item" @click.stop="handleStartRename(report)">
44
+ <i class="v3-icon-edit"></i>
45
+ <span>重命名</span>
46
+ </div>
47
+ <div class="report-menu-item delete" @click.stop="handleDeleteReport(report)">
48
+ <i class="v3-icon-delete"></i>
49
+ <span>删除</span>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- 折叠/展开指示器 -->
57
+ <div
58
+ class="scroll-bar-indicator"
59
+ :class="{ collapsed: isCollapsed }"
60
+ @click="handleToggleCollapse"
61
+ >
62
+ <i :class="isCollapsed ? 'v3-icon-right' : 'v3-icon-left'"></i>
63
+ </div>
64
+ </div>
65
+ </template>
66
+
67
+ <script>
68
+ export default {
69
+ name: 'ReportSidebar',
70
+ props: {
71
+ // 报表列表
72
+ reportList: {
73
+ type: Array,
74
+ default: () => []
75
+ },
76
+ // 当前选中的报表索引
77
+ currentIndex: {
78
+ type: Number,
79
+ default: 0
80
+ },
81
+ // 是否折叠
82
+ isCollapsed: {
83
+ type: Boolean,
84
+ default: false
85
+ }
86
+ },
87
+ data() {
88
+ return {
89
+ // 报表项悬停ID
90
+ hoveredId: null,
91
+ // 当前显示菜单的报表ID
92
+ activeMenuId: null,
93
+ // 正在编辑的报表ID
94
+ editingId: null,
95
+ // 编辑中的报表名称
96
+ editingName: ''
97
+ }
98
+ },
99
+ methods: {
100
+ // 获取图表类型图标
101
+ getChartTypeIcon(type) {
102
+ const iconMap = {
103
+ bar: 'v3-icon-chart-histogram',
104
+ line: 'v3-icon-chart-line',
105
+ pie: 'v3-icon-chart-pie',
106
+ horizontalBar: 'v3-icon-histogram'
107
+ }
108
+ return iconMap[type] || 'v3-icon-chart-histogram'
109
+ },
110
+
111
+ // 选择报表
112
+ handleSelectReport(index) {
113
+ this.$emit('select', index)
114
+ },
115
+
116
+ // 添加报表
117
+ handleAddReport() {
118
+ this.$emit('add')
119
+ },
120
+
121
+ // 切换折叠状态
122
+ handleToggleCollapse() {
123
+ this.$emit('toggle-collapse')
124
+ },
125
+
126
+ // 切换报表菜单显示
127
+ handleToggleMenu(reportId) {
128
+ this.activeMenuId = this.activeMenuId === reportId ? null : reportId
129
+ },
130
+
131
+ // 开始重命名
132
+ handleStartRename(report) {
133
+ this.editingId = report.id
134
+ this.editingName = report.name
135
+ this.activeMenuId = null
136
+ this.$nextTick(() => {
137
+ const input = this.$el.querySelector('.report-name-input')
138
+ if (input) {
139
+ input.focus()
140
+ input.select()
141
+ }
142
+ })
143
+ },
144
+
145
+ // 保存重命名
146
+ handleSaveRename() {
147
+ if (this.editingId && this.editingName.trim()) {
148
+ this.$emit('rename', { id: this.editingId, name: this.editingName.trim() })
149
+ }
150
+ this.editingId = null
151
+ this.editingName = ''
152
+ },
153
+
154
+ // 取消重命名
155
+ handleCancelRename() {
156
+ this.editingId = null
157
+ this.editingName = ''
158
+ },
159
+
160
+ // 删除报表
161
+ handleDeleteReport(report) {
162
+ this.activeMenuId = null
163
+ this.$emit('delete', report)
164
+ },
165
+
166
+ // 关闭菜单(供外部调用)
167
+ closeMenu() {
168
+ this.activeMenuId = null
169
+ }
170
+ }
171
+ }
172
+ </script>
@@ -0,0 +1,263 @@
1
+ <template>
2
+ <!-- 表格视图 -->
3
+ <div class="table-view">
4
+ <div v-if="hasData" class="table-wrapper">
5
+ <!-- 数据概览头部 -->
6
+ <div class="table-header">
7
+ <div class="table-title">数据概览</div>
8
+ <div class="table-desc">按维度「{{ dimensionLabel }}」聚合后的结果,共 {{ dataCount }} 条</div>
9
+ </div>
10
+
11
+ <!-- 分组统计表格 -->
12
+ <template v-if="!isCrossMode">
13
+ <div class="table-container">
14
+ <table class="pivot-table">
15
+ <thead>
16
+ <tr>
17
+ <th
18
+ v-for="col in tableColumns"
19
+ :key="col.key"
20
+ :class="col.type === 'metric' ? 'metric-header' : 'dimension-header'"
21
+ :style="col.width ? { width: col.width } : null"
22
+ >
23
+ {{ col.title }}
24
+ </th>
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <tr v-for="(row, index) in tableData" :key="index">
29
+ <td
30
+ v-for="col in tableColumns"
31
+ :key="col.key"
32
+ :class="col.type === 'metric' ? 'metric-cell' : 'dimension-cell'"
33
+ :style="col.width ? { width: col.width } : null"
34
+ >
35
+ <template v-if="col.type === 'metric'">
36
+ <div class="cell-content">
37
+ <span v-if="col.aggregateType === 'count'" class="cell-count"
38
+ >{{ formatCount(row._groupCount) }} 笔</span
39
+ >
40
+ <span v-else class="cell-value">{{ formatNumber(row[col.key]) }}</span>
41
+ </div>
42
+ </template>
43
+ <template v-else>
44
+ {{ row[col.key] }}
45
+ </template>
46
+ </td>
47
+ </tr>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </template>
52
+
53
+ <!-- 交叉统计表格 -->
54
+ <template v-else>
55
+ <div class="cross-table-container">
56
+ <table class="cross-pivot-table">
57
+ <thead>
58
+ <!-- 主表头行 -->
59
+ <tr class="cross-header-row">
60
+ <!-- 显示第一个维度列 -->
61
+ <th class="cross-dimension-header">
62
+ {{ crossTableColumns.find((c) => c.type === 'dimension')?.title }}
63
+ </th>
64
+ <!-- 指标列 -->
65
+ <th
66
+ v-for="col in crossTableColumns.filter((c) => c.type === 'metric')"
67
+ :key="'header-' + col.key"
68
+ class="cross-metric-header"
69
+ >
70
+ {{ col.title }}
71
+ </th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ <tr v-for="(row, index) in tableData" :key="index" class="cross-data-row">
76
+ <!-- 第一个维度值 -->
77
+ <td class="cross-dimension-cell">
78
+ {{ row[crossTableColumns.find((c) => c.type === 'dimension')?.key] }}
79
+ </td>
80
+ <!-- 指标列 -->
81
+ <td
82
+ v-for="col in crossTableColumns.filter((c) => c.type === 'metric')"
83
+ :key="'cell-' + row._groupKey + '-' + col.key"
84
+ class="cross-metric-cell"
85
+ >
86
+ <span v-if="col.aggregateType === 'count'" class="cross-metric-count"
87
+ >{{ formatCount(row._groupCount) }} 笔</span
88
+ >
89
+ <span v-else class="cross-metric-value">{{ formatNumber(row[col.key]) }}</span>
90
+ </td>
91
+ </tr>
92
+ </tbody>
93
+ </table>
94
+ </div>
95
+ </template>
96
+
97
+ <!-- 分页组件 -->
98
+ <div class="cross-pagination">
99
+ <div class="pagination-info">共{{ totalCount }}条</div>
100
+ <div class="pagination-controls">
101
+ <div class="pagination-btn" :class="{ disabled: currentPage <= 1 }" @click="handlePrevPage">
102
+ <i class="v3-icon-left"></i>
103
+ </div>
104
+ <div class="pagination-current">{{ currentPage }}</div>
105
+ <span class="pagination-separator">/</span>
106
+ <div class="pagination-total">{{ totalPages }}</div>
107
+ <div class="pagination-btn" :class="{ disabled: currentPage >= totalPages }" @click="handleNextPage">
108
+ <i class="v3-icon-right"></i>
109
+ </div>
110
+ </div>
111
+ <div class="pagination-size-select" @click="showPageSizeDropdown = !showPageSizeDropdown">
112
+ <span>{{ pageSize }}条/页</span>
113
+ <i class="v3-icon-down"></i>
114
+ <div v-if="showPageSizeDropdown" class="pagination-size-dropdown">
115
+ <div
116
+ v-for="size in pageSizeOptions"
117
+ :key="size"
118
+ class="pagination-size-option"
119
+ :class="{ active: pageSize === size }"
120
+ @click.stop="handlePageSizeChange(size)"
121
+ >
122
+ {{ size }}条/页
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <div class="pagination-jump">
127
+ <span class="pagination-jump-label">前往</span>
128
+ <input type="number" class="pagination-jump-input" :value="currentPage" @change="handleJumpToPage" min="1" />
129
+ <span class="pagination-jump-label">页</span>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ <div v-else class="empty-state">
134
+ <Empty type="noData2" :height="200" :width="200" content="暂无数据" />
135
+ </div>
136
+ </div>
137
+ </template>
138
+
139
+ <script>
140
+ import Empty from '../Empty/index.vue'
141
+
142
+ export default {
143
+ name: 'TableView',
144
+ components: {
145
+ Empty
146
+ },
147
+ props: {
148
+ // 是否有数据
149
+ hasData: {
150
+ type: Boolean,
151
+ default: false
152
+ },
153
+ // 数据条数
154
+ dataCount: {
155
+ type: Number,
156
+ default: 0
157
+ },
158
+ // 维度标签
159
+ dimensionLabel: {
160
+ type: String,
161
+ default: ''
162
+ },
163
+ // 是否交叉统计模式
164
+ isCrossMode: {
165
+ type: Boolean,
166
+ default: false
167
+ },
168
+ // 表格列定义
169
+ tableColumns: {
170
+ type: Array,
171
+ default: () => []
172
+ },
173
+ // 交叉表列定义
174
+ crossTableColumns: {
175
+ type: Array,
176
+ default: () => []
177
+ },
178
+ // 分页后的表格数据
179
+ tableData: {
180
+ type: Array,
181
+ default: () => []
182
+ },
183
+ // 总数据条数
184
+ totalCount: {
185
+ type: Number,
186
+ default: 0
187
+ },
188
+ // 当前页
189
+ currentPage: {
190
+ type: Number,
191
+ default: 1
192
+ },
193
+ // 每页条数
194
+ pageSize: {
195
+ type: Number,
196
+ default: 10
197
+ },
198
+ // 每页条数选项
199
+ pageSizeOptions: {
200
+ type: Array,
201
+ default: () => [10, 20, 50, 100]
202
+ },
203
+ // 总页数
204
+ totalPages: {
205
+ type: Number,
206
+ default: 1
207
+ }
208
+ },
209
+ data() {
210
+ return {
211
+ showPageSizeDropdown: false
212
+ }
213
+ },
214
+ methods: {
215
+ // 格式化数值显示
216
+ formatNumber(val) {
217
+ if (val === null || val === undefined || val === '') return '--'
218
+ const num = parseFloat(val)
219
+ if (isNaN(num)) return '--'
220
+ return num.toLocaleString('en-US', { minimumFractionDigits: 2 })
221
+ },
222
+
223
+ // 格式化笔数显示(0 显示为 "-",其他显示数字)
224
+ formatCount(count) {
225
+ if (count === null || count === undefined || count === 0) return '-'
226
+ return count
227
+ },
228
+
229
+ // 上一页
230
+ handlePrevPage() {
231
+ if (this.currentPage > 1) {
232
+ this.$emit('prev-page')
233
+ }
234
+ },
235
+
236
+ // 下一页
237
+ handleNextPage() {
238
+ if (this.currentPage < this.totalPages) {
239
+ this.$emit('next-page')
240
+ }
241
+ },
242
+
243
+ // 跳转到指定页
244
+ handleJumpToPage(e) {
245
+ const page = parseInt(e.target.value)
246
+ if (page && page >= 1 && page <= this.totalPages) {
247
+ this.$emit('jump-page', page)
248
+ }
249
+ },
250
+
251
+ // 每页条数变更
252
+ handlePageSizeChange(size) {
253
+ this.showPageSizeDropdown = false
254
+ this.$emit('page-size-change', size)
255
+ },
256
+
257
+ // 关闭下拉菜单(供外部调用)
258
+ closeDropdown() {
259
+ this.showPageSizeDropdown = false
260
+ }
261
+ }
262
+ }
263
+ </script>