n20-common-lib 3.0.98 → 3.0.99

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n20-common-lib",
3
- "version": "3.0.98",
3
+ "version": "3.0.99",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -313,38 +313,24 @@ export default {
313
313
  return this.model
314
314
  },
315
315
  activeClass(item) {
316
- const getFlag = () => {
317
- if (Array.isArray(this.model[item.value])) {
318
- return this.model[item.value].length > 0 ? this.prefixCls + '-active' : ''
319
- } else {
320
- return this.model[item.value] !== null &&
321
- this.model[item.value] !== undefined &&
322
- this.model[item.value] !== ''
323
- ? this.prefixCls + '-active'
324
- : ''
325
- }
316
+ // 判断值是否有效(非空)
317
+ const hasValue = (val) => {
318
+ if (Array.isArray(val)) return val.length > 0
319
+ return val !== null && val !== undefined && val !== ''
326
320
  }
327
- let flag = ''
328
- switch (item.type) {
329
- case 'numberrange':
330
- flag =
331
- this.model[item.startValue] !== null &&
332
- this.model[item.endValue] !== null &&
333
- this.model[item.startValue] !== undefined &&
334
- this.model[item.endValue] !== undefined
335
- ? this.prefixCls + '-active'
336
- : ''
337
- break
338
- case 'daterange':
339
- case 'datetimerange':
340
- case ' monthrange':
341
- flag = this.model[item.startDate] && this.model[item.endDate] ? this.prefixCls + '-active' : ''
342
- break
343
- default:
344
- flag = getFlag()
345
- break
321
+
322
+ // 判断范围类型是否有效(起止值都存在)
323
+ const hasRange = (start, end) => hasValue(this.model[start]) && hasValue(this.model[end])
324
+
325
+ const rangeTypes = ['daterange', 'datetimerange', 'monthrange']
326
+
327
+ if (item.type === 'numberrange') {
328
+ return hasRange(item.startValue, item.endValue) ? this.prefixCls + '-active' : ''
329
+ }
330
+ if (rangeTypes.includes(item.type)) {
331
+ return hasRange(item.startDate, item.endDate) ? this.prefixCls + '-active' : ''
346
332
  }
347
- return flag
333
+ return hasValue(this.model[item.value]) ? this.prefixCls + '-active' : ''
348
334
  },
349
335
  handleClose(item) {
350
336
  switch (item.type) {
@@ -20,7 +20,7 @@
20
20
  @mouseenter="hoveredReportId = report.id"
21
21
  @mouseleave="hoveredReportId = null"
22
22
  >
23
- <i class="v3-icon-report"></i>
23
+ <i :class="getChartTypeIcon(report.chartType)" class="report-icon"></i>
24
24
  <span v-if="editingReportId !== report.id" class="report-name">{{ report.name }}</span>
25
25
  <input
26
26
  v-else
@@ -70,7 +70,7 @@
70
70
  <!-- 内容头部工具栏 -->
71
71
  <div class="main-toolbar">
72
72
  <div class="toolbar-left">
73
- <div >
73
+ <div>
74
74
  <h2 class="report-title">{{ currentReport.name }}</h2>
75
75
  <div class="add-to-home-btn" @click="handleAddToHome">
76
76
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
@@ -195,13 +195,16 @@
195
195
  <!-- 右侧维度配置 -->
196
196
  <div class="pivot-sidebar-right" :class="{ collapsed: isRightSidebarCollapsed }">
197
197
  <div class="sidebar-header">
198
- <span v-if="!isRightSidebarCollapsed" class="sidebar-title">维度配置</span>
198
+ <template v-if="!isRightSidebarCollapsed">
199
+ <i class="v3-icon-move-right sidebar-header-collapse" @click="handleToggleRightSidebar"></i>
200
+ <span class="sidebar-title">维度配置</span>
201
+ </template>
199
202
  </div>
200
203
 
201
204
  <div class="config-form">
202
205
  <!-- 报表名称 -->
203
206
  <div class="form-item">
204
- <label class="form-label">报表名称</label>
207
+ <label class="form-label">报表标题</label>
205
208
 
206
209
  <div class="input-wrapper">
207
210
  <input class="form-input" type="text" v-model="currentReport.name" placeholder="请输入报表名称" />
@@ -226,10 +229,7 @@
226
229
  <label class="form-label">展示形式</label>
227
230
  <div class="select-wrapper" @click="showChartTypeDropdown = !showChartTypeDropdown">
228
231
  <div class="select-value">
229
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
230
- <rect x="1" y="3" width="12" height="8" rx="1" stroke="currentColor" stroke-width="1.5" />
231
- <path d="M3 8V11M7 6V11M11 9V11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
232
- </svg>
232
+ <i :class="getChartTypeIcon(currentReport.chartType)" class="report-icon"></i>
233
233
  <span>{{ getChartTypeLabel(currentReport.chartType) }}</span>
234
234
  </div>
235
235
  <svg class="select-arrow" width="10" height="10" viewBox="0 0 10 10" fill="none">
@@ -255,6 +255,8 @@
255
255
  </div>
256
256
  </div>
257
257
 
258
+ <div class="form-divider"></div>
259
+
258
260
  <!-- 分组维度 -->
259
261
  <div class="form-item">
260
262
  <label class="form-label">
@@ -309,6 +311,8 @@
309
311
  </div>
310
312
  </div>
311
313
 
314
+ <div class="form-divider"></div>
315
+
312
316
  <!-- 计算指标 -->
313
317
  <div class="form-item">
314
318
  <div class="form-label-row">
@@ -897,6 +901,17 @@ export default {
897
901
  return chart ? chart.label : '柱状图'
898
902
  },
899
903
 
904
+ // 获取图表类型图标
905
+ getChartTypeIcon(type) {
906
+ const iconMap = {
907
+ bar: 'v3-icon-chart-histogram',
908
+ line: 'v3-icon-chart-line',
909
+ pie: 'v3-icon-chart-pie',
910
+ horizontalBar: 'v3-icon-histogram'
911
+ }
912
+ return iconMap[type] || 'v3-icon-chart-histogram'
913
+ },
914
+
900
915
  // 获取聚合类型标签
901
916
  getAggregateLabel(type) {
902
917
  const agg = this.aggregateTypes.find((t) => t.value === type)
@@ -1113,11 +1128,14 @@ export default {
1113
1128
  .el-dialog {
1114
1129
  box-sizing: border-box;
1115
1130
  margin-top: 3vh !important;
1116
- max-height: 95vh;
1131
+ max-height: 94vh;
1117
1132
  display: flex;
1118
1133
  flex-direction: column;
1119
1134
  .el-dialog__body {
1120
1135
  padding: 0;
1136
+ flex: 1;
1137
+ min-height: 0;
1138
+ overflow: hidden;
1121
1139
  }
1122
1140
  }
1123
1141
  }
@@ -1125,7 +1143,6 @@ export default {
1125
1143
  display: flex;
1126
1144
  background: #fff;
1127
1145
  height: 100%;
1128
- min-height: 800px;
1129
1146
  border-radius: 8px;
1130
1147
  overflow: hidden;
1131
1148
  font-family: 'PingFang SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -1447,6 +1464,8 @@ export default {
1447
1464
  flex: 1;
1448
1465
  padding: 16px;
1449
1466
  overflow: hidden;
1467
+ border: 1px solid #e5e6eb;
1468
+ border-radius: 4px;
1450
1469
  }
1451
1470
 
1452
1471
  .chart-view,
@@ -1459,7 +1478,6 @@ export default {
1459
1478
  .chart-container {
1460
1479
  width: 100%;
1461
1480
  height: 100%;
1462
- min-height: 500px;
1463
1481
  }
1464
1482
 
1465
1483
  .table-container {
@@ -1577,14 +1595,34 @@ export default {
1577
1595
  line-height: 22px; /* 157.143% */
1578
1596
  }
1579
1597
 
1598
+ .pivot-sidebar-right .sidebar-header-collapse {
1599
+ font-size: 14px;
1600
+ color: #86909c;
1601
+ cursor: pointer;
1602
+ transition: color 0.2s;
1603
+ &:hover {
1604
+ color: #1d2129;
1605
+ }
1606
+ }
1607
+
1580
1608
  .config-form {
1581
1609
  flex: 1;
1582
- padding: 16px;
1610
+ padding: 0 16px;
1583
1611
  overflow-y: auto;
1612
+
1613
+ .form-item:first-child {
1614
+ margin-top: 16px;
1615
+ }
1584
1616
  }
1585
1617
 
1586
1618
  .form-item {
1587
- margin-bottom: 20px;
1619
+ margin-bottom: 16px;
1620
+ }
1621
+
1622
+ .form-divider {
1623
+ height: 1px;
1624
+ background-color: #e5e6eb;
1625
+ margin: 16px 0;
1588
1626
  }
1589
1627
 
1590
1628
  .form-label {
@@ -1669,8 +1707,8 @@ export default {
1669
1707
  position: relative;
1670
1708
  display: flex;
1671
1709
  align-items: center;
1672
- height: 32px;
1673
- padding: 0 12px;
1710
+ gap: 4px;
1711
+ padding: 4px 12px;
1674
1712
  background: #fff;
1675
1713
  border: 1px solid #e5e6eb;
1676
1714
  border-radius: 4px;
@@ -1689,6 +1727,17 @@ export default {
1689
1727
  gap: 8px;
1690
1728
  font-size: 14px;
1691
1729
  color: #1d2129;
1730
+ .report-icon {
1731
+ width: 16px;
1732
+ height: 16px;
1733
+ font-size: 16px;
1734
+ line-height: 16px;
1735
+ display: inline-flex;
1736
+ align-items: center;
1737
+ justify-content: center;
1738
+ margin-right: 0;
1739
+ color: #007aff;
1740
+ }
1692
1741
  }
1693
1742
 
1694
1743
  .select-value .placeholder {
@@ -1696,7 +1745,7 @@ export default {
1696
1745
  }
1697
1746
 
1698
1747
  .select-arrow {
1699
- color: #86909c;
1748
+ color: #4e5969;
1700
1749
  transition: transform 0.2s;
1701
1750
  }
1702
1751
 
@@ -95,10 +95,7 @@
95
95
  <div slot="prefix">
96
96
  <slot name="prefix"></slot>
97
97
  </div>
98
- <template
99
- v-for="filter in slotFilters"
100
- #[filter.slotName]="{ model }"
101
- >
98
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model }">
102
99
  <slot
103
100
  :name="filter.slotName"
104
101
  :model="model"
@@ -106,6 +103,15 @@
106
103
  :input="(val) => handleSlotInput(filter.value, val)"
107
104
  ></slot>
108
105
  </template>
106
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchValue 让父组件自行管理 -->
107
+ <template v-for="filter in slotFiltersWithoutValue" #[filter.slotName]="{ model }">
108
+ <slot
109
+ :name="filter.slotName"
110
+ :model="model"
111
+ :search-value="searchValue"
112
+ :update-search-value="updateSearchValue"
113
+ ></slot>
114
+ </template>
109
115
  <div slot="suffix">
110
116
  <slot name="suffix"></slot>
111
117
  </div>
@@ -146,10 +152,7 @@
146
152
  :check-ids="form.keyIds"
147
153
  @saveCheckData="saveCheckData"
148
154
  >
149
- <template
150
- v-for="filter in slotFilters"
151
- #[filter.slotName]="{ model }"
152
- >
155
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model }">
153
156
  <slot
154
157
  :name="filter.slotName"
155
158
  :model="model"
@@ -157,6 +160,15 @@
157
160
  :input="(val) => (searchForm[filter.value] = val)"
158
161
  ></slot>
159
162
  </template>
163
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchForm 让父组件自行管理 -->
164
+ <template v-for="filter in slotFiltersWithoutValue" #[filter.slotName]="{ model }">
165
+ <slot
166
+ :name="filter.slotName"
167
+ :model="model"
168
+ :search-value="searchForm"
169
+ :update-search-value="(updates) => Object.assign(searchForm, updates)"
170
+ ></slot>
171
+ </template>
160
172
  </cl-advanced-filter>
161
173
  <advancedQuery
162
174
  v-else
@@ -166,10 +178,7 @@
166
178
  :filter-id="filterId"
167
179
  :buss-id="bussId"
168
180
  >
169
- <template
170
- v-for="filter in slotFilters"
171
- #[filter.slotName]="{ model, value, input, disabled }"
172
- >
181
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model, value, input, disabled }">
173
182
  <slot
174
183
  :name="filter.slotName"
175
184
  :model="model"
@@ -178,6 +187,16 @@
178
187
  :disabled="disabled"
179
188
  ></slot>
180
189
  </template>
190
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchForm 让父组件自行管理 -->
191
+ <template v-for="filter in slotFiltersWithoutValue" #[filter.slotName]="{ model, value, input, disabled }">
192
+ <slot
193
+ :name="filter.slotName"
194
+ :model="model"
195
+ :search-value="searchForm"
196
+ :update-search-value="(updates) => Object.assign(searchForm, updates)"
197
+ :disabled="disabled"
198
+ ></slot>
199
+ </template>
181
200
  </advancedQuery>
182
201
  </el-form>
183
202
  <div slot="footer" class="flex-box flex-c flex-v page-button-shadow">
@@ -271,15 +290,22 @@ export default {
271
290
  },
272
291
  // 筛选出类型为 slot 且在父组件提供了对应作用域插槽的筛选项
273
292
  slotFilters() {
274
- return this.filterList.filter(
275
- (item) => item.type === 'slot' && this.$scopedSlots[item.slotName]
276
- )
293
+ return this.filterList.filter((item) => item.type === 'slot' && this.$scopedSlots[item.slotName])
294
+ },
295
+ // 筛选出有 value 字段的 slot 类型筛选项(用于 searchValue 初始化和值传递)
296
+ slotFiltersWithValue() {
297
+ return this.slotFilters.filter((item) => item.value)
298
+ },
299
+ // 筛选出无 value 字段的 slot 类型筛选项(如 startDate/endDate 多值绑定)
300
+ slotFiltersWithoutValue() {
301
+ return this.slotFilters.filter((item) => !item.value)
277
302
  },
278
303
  getInitialSearchValue() {
279
304
  const obj = {}
280
- // 遍历 filterList,为每个 type 为 slot 的项初始化值
305
+ // 遍历 filterList,为每个 type 为 slot 且有 value 字段的项初始化值
306
+ // 注意:跳过无 value 的 slot(如 startDate/endDate 多值绑定),这些由父组件在 slot 内部管理
281
307
  this.filterList.forEach((item) => {
282
- if (item.type === 'slot') {
308
+ if (item.type === 'slot' && item.value) {
283
309
  obj[item.value] = undefined
284
310
  }
285
311
  })
@@ -340,8 +366,14 @@ export default {
340
366
  this.searchValue = { ...this.getInitialSearchValue, ...this.initialValue }
341
367
  },
342
368
  methods: {
369
+ updateSearchValue(updates) {
370
+ console.log(updates, 'updates')
371
+ Object.assign(searchValue, updates)
372
+ },
343
373
  // 处理 slot 类型字段的输入事件,同时更新 searchValue 和 initialValue
344
374
  handleSlotInput(fieldName, val) {
375
+ // 防御性检查:如果 fieldName 无效,不执行任何操作
376
+ if (!fieldName) return
345
377
  // 更新 searchValue
346
378
  this.searchValue[fieldName] = val
347
379
  // 同步更新 initialValue,确保双向绑定生效
@@ -410,12 +442,16 @@ export default {
410
442
  })
411
443
  this.searchValue = { ...this.getInitialSearchValue, ...preserved }
412
444
  // 直接构建 payload,绕过 filterObj 避免 initialValue 重新注入
413
- this.$emit('filter', {
414
- conditionGroups: this.conditionGroups,
415
- searchValue: { ...this.searchValue },
416
- viewId: this.selectItem ? this.selectItem.viewId : null,
417
- viewType: this.selectItem ? this.selectItem.viewType : null
418
- }, 'clear')
445
+ this.$emit(
446
+ 'filter',
447
+ {
448
+ conditionGroups: this.conditionGroups,
449
+ searchValue: { ...this.searchValue },
450
+ viewId: this.selectItem ? this.selectItem.viewId : null,
451
+ viewType: this.selectItem ? this.selectItem.viewType : null
452
+ },
453
+ 'clear'
454
+ )
419
455
  },
420
456
  isEnter() {
421
457
  this.$rulesValidateForm('ruleValidate', (valid) => {