n20-common-lib 3.0.97 → 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.97",
3
+ "version": "3.0.99",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -7,7 +7,7 @@
7
7
  :key="getOnlyKey(item)"
8
8
  :class="[prefixCls + '-item', activeClass(item)]"
9
9
  :label="item.label"
10
- :required="item.required !== undefined || item.required !== null ? item.required : false"
10
+ :required="!!item.required"
11
11
  :disabled="item.props && item.props.disabled"
12
12
  >
13
13
  <div :class="prefixCls + '-content'">
@@ -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
 
@@ -5,7 +5,7 @@
5
5
  <div class="flex-box flex-v">
6
6
  <el-popover
7
7
  v-if="bussId"
8
- v-model="stVisible"
8
+ v-model="viewPopoverVisible"
9
9
  popper-class="pro-filter-view-popover"
10
10
  placement="bottom-start"
11
11
  trigger="click"
@@ -19,10 +19,10 @@
19
19
  </div>
20
20
  </el-button>
21
21
  <div class="p-s">
22
- <cl-input-search v-model="val" class="w-100p" placeholder="请输入关键字" />
22
+ <cl-input-search v-model="searchKeyword" class="w-100p" placeholder="请输入关键字" />
23
23
  <div style="color: var(--color-text-secondary); margin: 12px 0 8px 0">{{ $lc('自定义视图') }}</div>
24
24
  <div class="filter-view-wrapper">
25
- <cl-drag-list class="flex-item" :list="filteredList" :hide-delete="true" @change="fn">
25
+ <cl-drag-list class="flex-item" :list="filteredList" :hide-delete="true" @change="handleDragSort">
26
26
  <template v-slot="{ item }">
27
27
  <div class="proFilterView-list-item" :class="{ 'is-selected': selectedItem === item.viewName }">
28
28
  <span class="proFilterView-list-item__name" :title="item.viewName" @click="handleSelect(item)">{{
@@ -67,7 +67,7 @@
67
67
  </div>
68
68
  </el-button>
69
69
  <div class="flex-box flex-v">
70
- <el-button icon="v3-icon-filter" class="m-r-s botton" @click="multiple = !multiple">{{
70
+ <el-button icon="v3-icon-filter" class="m-r-s botton" @click="filterExpanded = !filterExpanded">{{
71
71
  '筛选' | $lc
72
72
  }}</el-button>
73
73
  <slot name="rightContent"></slot>
@@ -77,8 +77,8 @@
77
77
  <cl-advanced-filter
78
78
  ref="filter"
79
79
  class="filter"
80
- :class="{ 'is-hidden': !multiple }"
81
- :visible.sync="multiple"
80
+ :class="{ 'is-hidden': !filterExpanded }"
81
+ :visible.sync="filterExpanded"
82
82
  :filter-id="filterId"
83
83
  :buss-id="bussId"
84
84
  :model="searchValue"
@@ -95,11 +95,7 @@
95
95
  <div slot="prefix">
96
96
  <slot name="prefix"></slot>
97
97
  </div>
98
- <!-- 使用作用域插槽传递 searchValue -->
99
- <template
100
- v-for="filter in filterList.filter((item) => item.type === 'slot' && $scopedSlots[item.slotName])"
101
- #[filter.slotName]="{ model }"
102
- >
98
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model }">
103
99
  <slot
104
100
  :name="filter.slotName"
105
101
  :model="model"
@@ -107,6 +103,15 @@
107
103
  :input="(val) => handleSlotInput(filter.value, val)"
108
104
  ></slot>
109
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>
110
115
  <div slot="suffix">
111
116
  <slot name="suffix"></slot>
112
117
  </div>
@@ -131,27 +136,23 @@
131
136
  <div>{{ $lc('数据范围') }}</div>
132
137
  <div style="color: var(--color-primary); cursor: pointer" @click="typeChange">
133
138
  <i class="v3-icon-switch"></i>
134
- <span v-if="form.viewType === '0'"> {{ $lc('高级查询') }}</span>
139
+ <span v-if="form.viewType === VIEW_TYPE.ADVANCED"> {{ $lc('高级查询') }}</span>
135
140
  <span v-else> {{ $lc('基础查询') }}</span>
136
141
  </div>
137
142
  </div>
138
143
  <cl-advanced-filter
139
- v-if="form.viewType === '0'"
144
+ v-if="form.viewType === VIEW_TYPE.ADVANCED"
140
145
  ref="filter"
141
- :key="key"
146
+ :key="dialogFilterKey"
142
147
  class="filter"
143
- :visible.sync="multiple"
148
+ :visible="visible"
144
149
  :model="searchForm"
145
150
  only-key="id"
146
151
  :filter-list="filterList"
147
152
  :check-ids="form.keyIds"
148
153
  @saveCheckData="saveCheckData"
149
154
  >
150
- <!-- 使用作用域插槽传递 searchValue -->
151
- <template
152
- v-for="filter in filterList.filter((item) => item.type === 'slot' && $scopedSlots[item.slotName])"
153
- #[filter.slotName]="{ model }"
154
- >
155
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model }">
155
156
  <slot
156
157
  :name="filter.slotName"
157
158
  :model="model"
@@ -159,6 +160,15 @@
159
160
  :input="(val) => (searchForm[filter.value] = val)"
160
161
  ></slot>
161
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>
162
172
  </cl-advanced-filter>
163
173
  <advancedQuery
164
174
  v-else
@@ -168,11 +178,7 @@
168
178
  :filter-id="filterId"
169
179
  :buss-id="bussId"
170
180
  >
171
- <!-- 使用作用域插槽传递 slot 类型的过滤器 -->
172
- <template
173
- v-for="filter in filterList.filter((item) => item.type === 'slot' && $scopedSlots[item.slotName])"
174
- #[filter.slotName]="{ model, value, input, disabled }"
175
- >
181
+ <template v-for="filter in slotFiltersWithValue" #[filter.slotName]="{ model, value, input, disabled }">
176
182
  <slot
177
183
  :name="filter.slotName"
178
184
  :model="model"
@@ -181,6 +187,16 @@
181
187
  :disabled="disabled"
182
188
  ></slot>
183
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>
184
200
  </advancedQuery>
185
201
  </el-form>
186
202
  <div slot="footer" class="flex-box flex-c flex-v page-button-shadow">
@@ -199,6 +215,8 @@ import ClDragList from '../DragList/index.vue'
199
215
  import ClDialog from '../Dialog/index.vue'
200
216
  import axios from '../../utils/axios.js'
201
217
 
218
+ const VIEW_TYPE = { ADVANCED: '0', BASIC: '1' }
219
+
202
220
  export default {
203
221
  name: 'ProFilterView',
204
222
  components: {
@@ -234,19 +252,17 @@ export default {
234
252
  },
235
253
  data() {
236
254
  return {
237
- val: '',
255
+ VIEW_TYPE,
256
+ searchKeyword: '',
238
257
  list: ['无视图'],
239
258
  selectedItem: '无视图', // 默认选中第一项
240
- stVisible: false,
259
+ viewPopoverVisible: false,
241
260
  visible: false,
242
261
  isAdd: false,
243
262
  form: { userNo: sessionStorage.getItem('userNo'), pageNo: this.filterId },
244
263
 
245
- multiple: this.defaultExpanded,
246
- // 保存的筛选字段
247
- checkData: [],
248
- key: 0,
249
- keyIds: [],
264
+ filterExpanded: this.defaultExpanded,
265
+ dialogFilterKey: 0,
250
266
  selectItem: '',
251
267
  // 高级视图筛选
252
268
  conditionGroups: [],
@@ -255,26 +271,41 @@ export default {
255
271
  // 普通筛选值
256
272
  searchValue: {},
257
273
  viewId: undefined,
258
- isRefresh: false
274
+ isRefresh: false,
275
+ // 视图类型切换时缓存对方数据
276
+ savedViewConfigs: {}
259
277
  }
260
278
  },
261
279
  computed: {
262
280
  /**
263
281
  * 本地搜索过滤视图列表
264
- * @returns {Array} 根据 val 关键字过滤后的视图列表;val 为空时返回完整列表
282
+ * @returns {Array} 根据 searchKeyword 过滤后的视图列表;searchKeyword 为空时返回完整列表
265
283
  */
266
284
  filteredList() {
267
- if (!this.val) {
285
+ if (!this.searchKeyword) {
268
286
  return this.list
269
287
  }
270
- const keyword = this.val.toLowerCase()
288
+ const keyword = this.searchKeyword.toLowerCase()
271
289
  return this.list.filter((item) => item.viewName && item.viewName.toLowerCase().includes(keyword))
272
290
  },
291
+ // 筛选出类型为 slot 且在父组件提供了对应作用域插槽的筛选项
292
+ slotFilters() {
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)
302
+ },
273
303
  getInitialSearchValue() {
274
304
  const obj = {}
275
- // 遍历 filterList,为每个 type 为 slot 的项初始化值
305
+ // 遍历 filterList,为每个 type 为 slot 且有 value 字段的项初始化值
306
+ // 注意:跳过无 value 的 slot(如 startDate/endDate 多值绑定),这些由父组件在 slot 内部管理
276
307
  this.filterList.forEach((item) => {
277
- if (item.type === 'slot') {
308
+ if (item.type === 'slot' && item.value) {
278
309
  obj[item.value] = undefined
279
310
  }
280
311
  })
@@ -335,8 +366,14 @@ export default {
335
366
  this.searchValue = { ...this.getInitialSearchValue, ...this.initialValue }
336
367
  },
337
368
  methods: {
369
+ updateSearchValue(updates) {
370
+ console.log(updates, 'updates')
371
+ Object.assign(searchValue, updates)
372
+ },
338
373
  // 处理 slot 类型字段的输入事件,同时更新 searchValue 和 initialValue
339
374
  handleSlotInput(fieldName, val) {
375
+ // 防御性检查:如果 fieldName 无效,不执行任何操作
376
+ if (!fieldName) return
340
377
  // 更新 searchValue
341
378
  this.searchValue[fieldName] = val
342
379
  // 同步更新 initialValue,确保双向绑定生效
@@ -381,8 +418,40 @@ export default {
381
418
  this.$emit('filter', this.filterObj, 'clean')
382
419
  },
383
420
  handleClear() {
384
- this.searchValue = { ...this.getInitialSearchValue }
385
- this.$emit('filter', this.filterObj, 'clear')
421
+ // 收集 required 项的当前值,清空时保留
422
+ const preserved = {}
423
+ this.filterList.forEach((item) => {
424
+ if (!item.required) return
425
+ if (item.type === 'daterange' || item.type === 'datetimerange' || item.type === 'monthrange') {
426
+ if (item.startDate && this.searchValue[item.startDate] !== undefined) {
427
+ preserved[item.startDate] = this.searchValue[item.startDate]
428
+ }
429
+ if (item.endDate && this.searchValue[item.endDate] !== undefined) {
430
+ preserved[item.endDate] = this.searchValue[item.endDate]
431
+ }
432
+ } else if (item.type === 'numberrange') {
433
+ if (item.startValue && this.searchValue[item.startValue] !== undefined) {
434
+ preserved[item.startValue] = this.searchValue[item.startValue]
435
+ }
436
+ if (item.endValue && this.searchValue[item.endValue] !== undefined) {
437
+ preserved[item.endValue] = this.searchValue[item.endValue]
438
+ }
439
+ } else if (item.value && this.searchValue[item.value] !== undefined) {
440
+ preserved[item.value] = this.searchValue[item.value]
441
+ }
442
+ })
443
+ this.searchValue = { ...this.getInitialSearchValue, ...preserved }
444
+ // 直接构建 payload,绕过 filterObj 避免 initialValue 重新注入
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
+ )
386
455
  },
387
456
  isEnter() {
388
457
  this.$rulesValidateForm('ruleValidate', (valid) => {
@@ -406,7 +475,7 @@ export default {
406
475
  .get(`/bems/query/viewColumn/getViewInfo`, { bussId: this.bussId })
407
476
  .then((res) => {
408
477
  list = res.data.map((item) => {
409
- item.keyIds = item.keyIds ? JSON.parse(item.keyIds) : []
478
+ item.keyIds = this.safeParse(item.keyIds, [])
410
479
  return item
411
480
  })
412
481
  this.list = [
@@ -434,13 +503,13 @@ export default {
434
503
  // 新增
435
504
  add() {
436
505
  this.isRefresh = false
437
- this.stVisible = false
506
+ this.viewPopoverVisible = false
438
507
  this.visible = true
439
508
  this.isAdd = true
440
509
  this.form = {
441
510
  userNo: sessionStorage.getItem('userNo'),
442
511
  pageNo: this.filterId,
443
- viewType: '0' // 明确设置 type 为 1
512
+ viewType: VIEW_TYPE.ADVANCED
444
513
  }
445
514
  this.viewId = undefined
446
515
  this.form.keyIds = []
@@ -449,30 +518,37 @@ export default {
449
518
  ...this.getInitialSearchValue
450
519
  }
451
520
  // 新建时重置切换缓存
452
- this._savedViewConfigs = {}
521
+ this.savedViewConfigs = {}
453
522
  },
454
523
  // 修改
455
524
  edit(item, isRefresh = false) {
456
525
  this.isRefresh = isRefresh
457
526
  console.log(item)
458
- this.stVisible = false
527
+ this.viewPopoverVisible = false
459
528
  this.visible = true
460
529
  this.isAdd = false
461
- this.key++
530
+ this.dialogFilterKey++
462
531
  // 赋值操作
463
532
  this.form = item
464
533
  this.viewId = item.viewId
465
534
  // 初始化两种类型的已保存数据缓存,供 typeChange 切换时恢复使用
466
- this._savedViewConfigs = {}
467
- if (item.viewType === '0') {
468
- const parsed = JSON.parse(item.viewConfig || '{}')
535
+ this.savedViewConfigs = {}
536
+ if (item.viewType === VIEW_TYPE.ADVANCED) {
537
+ const parsed = this.safeParse(item.viewConfig)
469
538
  this.searchForm = { ...this.getInitialSearchValue, ...parsed }
470
- this._savedViewConfigs['0'] = { ...parsed }
539
+ this.savedViewConfigs[VIEW_TYPE.ADVANCED] = { ...parsed }
471
540
  } else {
472
- const parsed = JSON.parse(item.viewConfig || '{}')
541
+ const parsed = this.safeParse(item.viewConfig)
473
542
  this.conditionGroups = parsed.conditionGroups || []
474
543
 
475
- this._savedViewConfigs['1'] = { conditionGroups: this.conditionGroups }
544
+ this.savedViewConfigs[VIEW_TYPE.BASIC] = { conditionGroups: this.conditionGroups }
545
+ }
546
+ },
547
+ safeParse(str, fallback = {}) {
548
+ try {
549
+ return JSON.parse(str || '{}')
550
+ } catch {
551
+ return fallback
476
552
  }
477
553
  },
478
554
  // 保存筛选条件
@@ -481,29 +557,31 @@ export default {
481
557
  return item.id
482
558
  })
483
559
  },
484
- // 保存视图
485
- saveSt() {
486
- // 如果没有 bussId,不保存视图
487
- if (!this.bussId) {
488
- this.$message.warning('缺少业务ID,无法保存视图')
489
- return
490
- }
491
-
560
+ // 构建视图保存请求体
561
+ buildViewPayload() {
492
562
  let viewConfig
493
563
  let keyIds
494
- if (this.form.viewType === '0') {
564
+ if (this.form.viewType === VIEW_TYPE.ADVANCED) {
495
565
  viewConfig = JSON.stringify(this.searchForm)
496
566
  keyIds = this.form.keyIds
497
567
  } else {
498
568
  viewConfig = JSON.stringify({ conditionGroups: this.conditionGroups })
499
569
  keyIds = []
500
570
  }
501
- console.log(this.conditionGroups)
502
571
  this.form.viewConfig = viewConfig
503
-
504
- const hasEmptyValue = this.conditionGroups.some((group) =>
572
+ return {
573
+ viewName: this.form.viewName,
574
+ viewType: this.form.viewType,
575
+ bussId: this.bussId,
576
+ viewConfig,
577
+ keyIds: JSON.stringify(keyIds),
578
+ viewId: this.viewId
579
+ }
580
+ },
581
+ // 检查基础查询条件是否有空值
582
+ hasEmptyCondition() {
583
+ return this.conditionGroups.some((group) =>
505
584
  group.conditions.some((condition) => {
506
- // operator 5(为空) 和 6(不为空) 不需要填写 value
507
585
  if (condition.operator === 5 || condition.operator === 6) {
508
586
  return false
509
587
  }
@@ -511,35 +589,34 @@ export default {
511
589
  return val === null || val === undefined || val === '' || (Array.isArray(val) && val.length === 0)
512
590
  })
513
591
  )
592
+ },
593
+ // 保存视图
594
+ saveSt() {
595
+ if (!this.bussId) {
596
+ this.$message.warning('缺少业务ID,无法保存视图')
597
+ return
598
+ }
599
+
600
+ const payload = this.buildViewPayload()
601
+ const hasEmptyValue = this.hasEmptyCondition()
514
602
 
515
603
  this.$refs.form.validate(async (valid) => {
516
604
  if (!valid) {
517
605
  this.$message.error('请输入视图名称')
518
606
  return
519
607
  }
520
-
521
- if (this.form.viewType === '0' && !keyIds.length) {
608
+ if (this.form.viewType === VIEW_TYPE.ADVANCED && !this.form.keyIds.length) {
522
609
  this.$message.warning('至少添加一个条件')
523
610
  return
524
611
  }
525
- if (this.form.viewType === '1') {
612
+ if (this.form.viewType === VIEW_TYPE.BASIC) {
526
613
  if (!this.conditionGroups.length || hasEmptyValue) {
527
614
  this.$message.warning('至少添加一个条件')
528
615
  return
529
616
  }
530
617
  }
531
618
 
532
- // 保存筛选视图
533
- const obj = {
534
- viewName: this.form.viewName,
535
- viewType: this.form.viewType,
536
- bussId: this.bussId,
537
- viewConfig: this.form.viewConfig,
538
- keyIds: JSON.stringify(keyIds),
539
- viewId: this.viewId
540
- }
541
-
542
- axios.post('/bems/query/viewColumn/saveOrUpdateViewInfo', obj).then((res) => {
619
+ axios.post('/bems/query/viewColumn/saveOrUpdateViewInfo', payload).then((res) => {
543
620
  if (res.code !== 200) {
544
621
  return false
545
622
  }
@@ -567,7 +644,7 @@ export default {
567
644
  // 选中视图
568
645
  handleSelect(item) {
569
646
  console.log(item)
570
- this.stVisible = false
647
+ this.viewPopoverVisible = false
571
648
  this.selectItem = item
572
649
  this.selectedItem = item.viewName
573
650
  if (item.viewName === '无视图') {
@@ -575,17 +652,18 @@ export default {
575
652
  this.searchValue = {}
576
653
  this.conditionGroups = []
577
654
  }
578
- if (item.viewType === '0') {
579
- this.searchForm = JSON.parse(item.viewConfig)
580
- this.searchValue = JSON.parse(item.viewConfig)
581
- } else if (item.viewType === '1') {
582
- this.conditionGroups = JSON.parse(item.viewConfig)?.conditionGroups || []
655
+ if (item.viewType === VIEW_TYPE.ADVANCED) {
656
+ const config = this.safeParse(item.viewConfig)
657
+ this.searchForm = config
658
+ this.searchValue = config
659
+ } else if (item.viewType === VIEW_TYPE.BASIC) {
660
+ this.conditionGroups = this.safeParse(item.viewConfig).conditionGroups || []
583
661
  this.searchValue = { ...this.getInitialSearchValue }
584
662
  }
585
663
  this.$emit('filter', this.filterObj, 'filter')
586
664
  },
587
665
  // 拖动保存视图列表
588
- fn(list) {
666
+ handleDragSort(list) {
589
667
  console.log('当前list:', list)
590
668
  },
591
669
  /**
@@ -594,23 +672,22 @@ export default {
594
672
  * 若无已保存数据则重置为初始值
595
673
  */
596
674
  typeChange() {
597
- this.form.viewType = this.form.viewType === '0' ? '1' : '0'
598
- // 切换到基础查询(viewType === '0')
599
- if (this.form.viewType === '0') {
675
+ this.form.viewType = this.form.viewType === VIEW_TYPE.ADVANCED ? VIEW_TYPE.BASIC : VIEW_TYPE.ADVANCED
676
+ // 切换到高级查询
677
+ if (this.form.viewType === VIEW_TYPE.ADVANCED) {
600
678
  this.conditionGroups = []
601
679
  // 若已有保存的 viewConfig 且原始类型也是基础查询,则恢复已保存的搜索条件
602
- const savedConfig = this._savedViewConfigs && this._savedViewConfigs['0']
680
+ const savedConfig = this.savedViewConfigs[VIEW_TYPE.ADVANCED]
603
681
  if (savedConfig) {
604
682
  this.searchForm = { ...this.getInitialSearchValue, ...savedConfig }
605
683
  } else {
606
684
  this.searchForm = { ...this.getInitialSearchValue }
607
685
  }
608
686
  } else {
609
- // 切换到高级查询(viewType === '1'),先保存当前基础查询数据
610
- if (!this._savedViewConfigs) this._savedViewConfigs = {}
611
- this._savedViewConfigs['0'] = { ...this.searchForm }
687
+ // 切换到基础查询,保存当前高级查询数据
688
+ this.savedViewConfigs[VIEW_TYPE.ADVANCED] = { ...this.searchForm }
612
689
  // 恢复已保存的高级查询数据,若无则重置
613
- const savedConfig = this._savedViewConfigs['1']
690
+ const savedConfig = this.savedViewConfigs[VIEW_TYPE.BASIC]
614
691
  if (savedConfig) {
615
692
  this.conditionGroups = savedConfig.conditionGroups || []
616
693
  } else {
@@ -624,17 +701,15 @@ export default {
624
701
  * 供下游调用以恢复组件初始状态
625
702
  */
626
703
  resetState() {
627
- this.val = ''
704
+ this.searchKeyword = ''
628
705
  this.selectedItem = '无视图'
629
706
  this.selectItem = ''
630
707
  this.searchValue = { ...this.getInitialSearchValue, ...this.initialValue }
631
708
  this.searchForm = { ...this.getInitialSearchValue }
632
709
  this.conditionGroups = []
633
- this.multiple = this.defaultExpanded
634
- this.checkData = []
635
- this.keyIds = []
710
+ this.filterExpanded = this.defaultExpanded
636
711
  this.viewId = undefined
637
- this._savedViewConfigs = {}
712
+ this.savedViewConfigs = {}
638
713
  }
639
714
  }
640
715
  }