n20-common-lib 3.0.98 → 3.1.0

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.1.0",
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,16 +95,23 @@
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 slotFilters" #[filter.slotName]="{ model }">
99
+ <!-- value 的 slot:传递单个值及 input 回调 -->
102
100
  <slot
101
+ v-if="filter.value"
103
102
  :name="filter.slotName"
104
103
  :model="model"
105
104
  :value="searchValue[filter.value]"
106
105
  :input="(val) => handleSlotInput(filter.value, val)"
107
106
  ></slot>
107
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchValue 让父组件自行管理 -->
108
+ <slot
109
+ v-else
110
+ :name="filter.slotName"
111
+ :model="model"
112
+ :search-value="searchValue"
113
+ :update-search-value="(updates) => Object.assign(searchValue, updates)"
114
+ ></slot>
108
115
  </template>
109
116
  <div slot="suffix">
110
117
  <slot name="suffix"></slot>
@@ -146,16 +153,23 @@
146
153
  :check-ids="form.keyIds"
147
154
  @saveCheckData="saveCheckData"
148
155
  >
149
- <template
150
- v-for="filter in slotFilters"
151
- #[filter.slotName]="{ model }"
152
- >
156
+ <template v-for="filter in slotFilters" #[filter.slotName]="{ model }">
157
+ <!-- value 的 slot:传递单个值及 input 回调 -->
153
158
  <slot
159
+ v-if="filter.value"
154
160
  :name="filter.slotName"
155
161
  :model="model"
156
162
  :value="searchForm[filter.value]"
157
163
  :input="(val) => (searchForm[filter.value] = val)"
158
164
  ></slot>
165
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchForm 让父组件自行管理 -->
166
+ <slot
167
+ v-else
168
+ :name="filter.slotName"
169
+ :model="model"
170
+ :search-value="searchForm"
171
+ :update-search-value="(updates) => Object.assign(searchForm, updates)"
172
+ ></slot>
159
173
  </template>
160
174
  </cl-advanced-filter>
161
175
  <advancedQuery
@@ -166,17 +180,25 @@
166
180
  :filter-id="filterId"
167
181
  :buss-id="bussId"
168
182
  >
169
- <template
170
- v-for="filter in slotFilters"
171
- #[filter.slotName]="{ model, value, input, disabled }"
172
- >
183
+ <template v-for="filter in slotFilters" #[filter.slotName]="{ model, value, input, disabled }">
184
+ <!-- value 的 slot:传递单个值及 input 回调 -->
173
185
  <slot
186
+ v-if="filter.value"
174
187
  :name="filter.slotName"
175
188
  :model="model"
176
189
  :value="searchForm[filter.value]"
177
190
  :input="(val) => (searchForm[filter.value] = val)"
178
191
  :disabled="disabled"
179
192
  ></slot>
193
+ <!-- 无 value 的 slot(如 startDate/endDate 多值绑定),传递 searchForm 让父组件自行管理 -->
194
+ <slot
195
+ v-else
196
+ :name="filter.slotName"
197
+ :model="model"
198
+ :search-value="searchForm"
199
+ :update-search-value="(updates) => Object.assign(searchForm, updates)"
200
+ :disabled="disabled"
201
+ ></slot>
180
202
  </template>
181
203
  </advancedQuery>
182
204
  </el-form>
@@ -271,15 +293,15 @@ export default {
271
293
  },
272
294
  // 筛选出类型为 slot 且在父组件提供了对应作用域插槽的筛选项
273
295
  slotFilters() {
274
- return this.filterList.filter(
275
- (item) => item.type === 'slot' && this.$scopedSlots[item.slotName]
276
- )
296
+ return this.filterList.filter((item) => item.type === 'slot' && this.$scopedSlots[item.slotName])
277
297
  },
298
+
278
299
  getInitialSearchValue() {
279
300
  const obj = {}
280
- // 遍历 filterList,为每个 type 为 slot 的项初始化值
301
+ // 遍历 filterList,为每个 type 为 slot 且有 value 字段的项初始化值
302
+ // 注意:跳过无 value 的 slot(如 startDate/endDate 多值绑定),这些由父组件在 slot 内部管理
281
303
  this.filterList.forEach((item) => {
282
- if (item.type === 'slot') {
304
+ if (item.type === 'slot' && item.value) {
283
305
  obj[item.value] = undefined
284
306
  }
285
307
  })
@@ -342,6 +364,8 @@ export default {
342
364
  methods: {
343
365
  // 处理 slot 类型字段的输入事件,同时更新 searchValue 和 initialValue
344
366
  handleSlotInput(fieldName, val) {
367
+ // 防御性检查:如果 fieldName 无效,不执行任何操作
368
+ if (!fieldName) return
345
369
  // 更新 searchValue
346
370
  this.searchValue[fieldName] = val
347
371
  // 同步更新 initialValue,确保双向绑定生效
@@ -410,12 +434,16 @@ export default {
410
434
  })
411
435
  this.searchValue = { ...this.getInitialSearchValue, ...preserved }
412
436
  // 直接构建 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')
437
+ this.$emit(
438
+ 'filter',
439
+ {
440
+ conditionGroups: this.conditionGroups,
441
+ searchValue: { ...this.searchValue },
442
+ viewId: this.selectItem ? this.selectItem.viewId : null,
443
+ viewType: this.selectItem ? this.selectItem.viewType : null
444
+ },
445
+ 'clear'
446
+ )
419
447
  },
420
448
  isEnter() {
421
449
  this.$rulesValidateForm('ruleValidate', (valid) => {
@@ -587,7 +587,7 @@ export default {
587
587
  },
588
588
  reset() {
589
589
  const baseCols = this.columns.length
590
- ? this.columns
590
+ ? this.columns.filter((col) => !col.defaultHidden)
591
591
  : this.checkColumns.filter((col) => col.static !== 'pre' && col.static !== 'next')
592
592
  this.fixedColumns = baseCols.filter((col) => col.fixed)
593
593
  this.dragList = baseCols.filter((col) => !col.fixed && !col.static)
@@ -642,14 +642,16 @@ export default {
642
642
  }
643
643
  }
644
644
 
645
- function saveTransform(list, labelKey, isFilter) {
645
+ export function saveTransform(list, labelKey, isFilter) {
646
646
  let listN = []
647
647
  if (!isFilter) {
648
648
  // 遍历列配置列表,将每列转换为存储格式
649
649
  list.forEach((c) => {
650
650
  // 普通列:有prop属性、无子列、非新增列、无宽度设置
651
651
  if (c.prop && !c.children && !c.isNew && !c.width & !c.isNew) {
652
- listN.push({ _colKey: 'prop', _colVal: c.prop })
652
+ const entry = { _colKey: 'prop', _colVal: c.prop }
653
+ if (c.fixed) entry.fixed = c.fixed
654
+ listN.push(entry)
653
655
  }
654
656
  // 特殊类型列:多选/序号/展开列
655
657
  else if (c.type && ['selection', 'index', 'expand'].includes(c.type)) {
@@ -703,8 +705,15 @@ function getTransform(list, columnsT, labelKey = 'label') {
703
705
  if (d._colKey) {
704
706
  // 从columnsT中找到d._colKey等于d._colVal的项
705
707
  let column = columnsT.find((c) => c[d._colKey] === d._colVal)
706
- // 如果找到,则将其添加到columns中
707
- column && columns.push(column)
708
+ if (column) {
709
+ // 将保存的 fixed、width 合并到列对象上(避免直接修改 props)
710
+ if (d.fixed !== undefined || d.width !== undefined) {
711
+ column = { ...column }
712
+ if (d.fixed !== undefined) column.fixed = d.fixed
713
+ if (d.width !== undefined) column.width = d.width
714
+ }
715
+ columns.push(column)
716
+ }
708
717
  // 如果d中没有_colKey属性
709
718
  } else {
710
719
  // 将d添加到columns中
@@ -387,7 +387,9 @@
387
387
  import empty from '../../Empty'
388
388
  import tableOperate from '../../TableOperate/index.vue'
389
389
  import tableShowColumn from '../../ShowColumn/index.vue'
390
+ import { saveTransform } from '../../ShowColumn/index.vue'
390
391
  import OperateBtns from '../../TableProOperateColumn/OperateBtns.vue'
392
+ import axios from '../../../utils/axios'
391
393
 
392
394
  import { $lc } from '../../../utils/i18n/index.js'
393
395
 
@@ -590,7 +592,7 @@ export default {
590
592
  isExpand: false,
591
593
  isExport: false, // 是否导出
592
594
  dialogVisible: false, // 显示列设置对话框
593
- checkColumns: this.columns.slice() // 显示列设置对话框中已选择的列(默认使用 columns prop)
595
+ checkColumns: this.filterDefaultHidden(this.columns) // 显示列设置对话框中已选择的列(默认过滤 defaultHidden 列)
594
596
  }
595
597
  },
596
598
  computed: {
@@ -648,7 +650,7 @@ export default {
648
650
  this.colsKey = this.colsKey + 1
649
651
  // 没有 pageId 时,columns 变更同步更新 checkColumns
650
652
  if (!this.pageId) {
651
- this.checkColumns = this.columns.slice()
653
+ this.checkColumns = this.filterDefaultHidden(this.columns)
652
654
  }
653
655
  },
654
656
  size(val) {
@@ -684,13 +686,32 @@ export default {
684
686
  },
685
687
  resizableChange({ resizeWidth, columnIndex }) {
686
688
  this.checkColumns[columnIndex].width = resizeWidth
687
- if(this.showColumn && this.pageId) {
688
- this.$refs.showColumn.saveColumns(this.checkColumns)
689
+ if (this.showColumn && this.pageId) {
690
+ this.saveColumns()
689
691
  }
690
692
  },
693
+ // 过滤 defaultHidden 列:仅在用户未设置过显示列时作为 fallback 使用
694
+ filterDefaultHidden(columns) {
695
+ return columns.filter((col) => !col.defaultHidden)
696
+ },
697
+ // 保存列配置到后端(固定列、列宽调整等场景复用)
698
+ saveColumns() {
699
+ const userNo = sessionStorage.getItem('userNo')
700
+ const columns = saveTransform(this.checkColumns, this.labelKey, this.isFilter)
701
+ axios.post(
702
+ `/bems/prod_1.0/user/pageHabit?t=${Math.random()}`,
703
+ {
704
+ userNo,
705
+ pageId: this.pageId,
706
+ showStructure: JSON.stringify(columns)
707
+ },
708
+ { loading: false }
709
+ )
710
+ },
691
711
  setColumns(list) {
692
712
  if (this.isExport) {
693
713
  this.exportFn(list)
714
+ this.isExport = false
694
715
  } else {
695
716
  this.checkColumns = list
696
717
  }
@@ -698,10 +719,10 @@ export default {
698
719
  async getColumns() {
699
720
  try {
700
721
  const columns = await this.$refs.showColumn.getColumns(this.pageId)
701
- this.checkColumns = columns?.length ? columns : this.columns
722
+ this.checkColumns = columns?.length ? columns : this.filterDefaultHidden(this.columns)
702
723
  } catch (err) {
703
- // API 请求失败时回退到 columns prop
704
- this.checkColumns = this.columns
724
+ // API 请求失败时回退到过滤后的 columns prop
725
+ this.checkColumns = this.filterDefaultHidden(this.columns)
705
726
  }
706
727
  },
707
728
  /**
@@ -742,6 +763,10 @@ export default {
742
763
  this.$set(item, 'fixed', 'left')
743
764
  // 手动触发 colsKey 刷新,让 vxe-table 重新渲染列配置
744
765
  this.colsKey = this.colsKey + 1
766
+ // 固定列后自动保存到后端
767
+ if (this.showColumn && this.pageId) {
768
+ this.saveColumns()
769
+ }
745
770
  },
746
771
  // 解锁列:移除 fixed 属性,将该列恢复为普通列
747
772
  unlockColumn(item) {
@@ -749,6 +774,10 @@ export default {
749
774
  this.$delete(item, 'fixed')
750
775
  // 手动触发 colsKey 刷新,让 vxe-table 重新渲染列配置
751
776
  this.colsKey = this.colsKey + 1
777
+ // 取消固定后自动保存到后端
778
+ if (this.showColumn && this.pageId) {
779
+ this.saveColumns()
780
+ }
752
781
  },
753
782
  // 固定操作列为内联列
754
783
  fixOperateColumn() {
@@ -1222,11 +1251,21 @@ export default {
1222
1251
  }
1223
1252
  },
1224
1253
  // ========== 列宽计算相关方法 ==========
1225
- getRowClassName({ row }) {
1254
+ getRowClassName(args) {
1255
+ const rowClassName = this.$attrs['row-class-name'] || this.$attrs.rowClassName
1256
+ // 用户传入了 row-class-name,以传入的为准
1257
+ if (rowClassName && typeof rowClassName === 'function') {
1258
+ return rowClassName(args)
1259
+ }
1260
+ if (rowClassName && typeof rowClassName === 'string') {
1261
+ return rowClassName
1262
+ }
1263
+ // 用户未传入,选中行高亮
1226
1264
  const checkedRows = this.$refs.vxeTable?.getCheckboxRecords(false) || []
1227
1265
  const checkedSet = new Set(checkedRows)
1228
- return checkedSet.has(row) ? 'v3-n20-table-pro__row-checked' : ''
1266
+ return checkedSet.has(args.row) ? 'v3-n20-table-pro__row-checked' : ''
1229
1267
  },
1268
+
1230
1269
  calcColumnWidth(columns) {
1231
1270
  // 常量配置
1232
1271
  const CHAR_WIDTH = 20 // 每个字符的平均宽度(像素)
@@ -1,551 +0,0 @@
1
- <template>
2
- <dialog-wrap
3
- ref="dialog"
4
- v-drag
5
- class="p-t-0"
6
- v-bind="$attrs"
7
- :visible.sync="visible"
8
- :title="title"
9
- :width="width"
10
- :close-on-click-modal="false"
11
- @open="popOpen"
12
- >
13
- <div class="dialog-view">
14
- <div class="flex-box">
15
- <div class="left-c flex-column p-t-m bd-r">
16
- <div class="flex-box flex-v p-r m-b">
17
- <span>{{ header }}</span>
18
- <span class="m-l-m color-999">
19
- {{ '已选' | $lc }}
20
- <span class="color-main">{{ dragList.length }}</span>
21
- </span>
22
- <el-link :underline="false" type="primary" class="m-l-auto" @click="setAll">{{
23
- setState ? $lc('全部清空') : $lc('全部选中')
24
- }}</el-link>
25
- </div>
26
- <div class="flex-item p-r" style="height: 50%; overflow: auto">
27
- <el-checkbox-group :value="dragListN.map((d) => d[labelKey])">
28
- <template v-if="!columnsGroups">
29
- <el-checkbox
30
- v-for="(column, i) in columnsAs"
31
- :key="i"
32
- v-hover-tooltip="column[labelKey]"
33
- :label="column[labelKey]"
34
- :disabled="column.checked"
35
- class="m-t"
36
- @change="(val) => checkChange(val, column)"
37
- >{{ column[labelKey] }}</el-checkbox
38
- >
39
- </template>
40
- <template v-else>
41
- <div v-for="gp in columnsGroups" :key="gp.groupId" class="m-b">
42
- <div class="f-s-b font-w600">{{ gp.groupName }}</div>
43
- <template v-for="(column, i) in columnsAs">
44
- <el-checkbox
45
- v-if="column.groupId === gp.groupId"
46
- :key="i"
47
- v-hover-tooltip="column[labelKey]"
48
- :label="column[labelKey]"
49
- :disabled="column.checked"
50
- class="m-t"
51
- @change="(val) => checkChange(val, column)"
52
- >{{ column[labelKey] }}</el-checkbox
53
- >
54
- </template>
55
- </div>
56
- </template>
57
- </el-checkbox-group>
58
- </div>
59
- </div>
60
- <div v-if="!hasPX" class="right-c flex-column p-t-m">
61
- <div class="m-b">
62
- {{ '当前选定项' | $lc }}
63
- <el-link type="primary" style="visibility: hidden">{{ '当前选定项' | $lc }}</el-link>
64
- </div>
65
- <div class="flex-1 overflow-y">
66
- <drag-list :list="dragList" :label-key="labelKey" :in-show-column="true" />
67
- </div>
68
- </div>
69
- <div v-else class="right-tree flex-column p-t-m">
70
- <div class="m-b flex-box flex-lr flex-v">
71
- {{ '当前选定项' | $lc }}
72
- <el-link class="m-r-s" :underline="false" icon="n20-icon-xinzeng" @click="add">{{
73
- '新增字段' | $lc
74
- }}</el-link>
75
- </div>
76
- <el-tree
77
- ref="menu-tree"
78
- class="menu-tree"
79
- :data="dragList"
80
- :props="{
81
- children: 'children',
82
- label: 'label'
83
- }"
84
- node-key="prop"
85
- draggable
86
- style="width: 200px"
87
- :allow-drop="allowDrop"
88
- >
89
- <span slot-scope="{ node, data }" class="menu-item flex-item flex-box flex-lr flex-v">
90
- <span>
91
- <i class="n20-drag-icon n20-icon-tuodong"></i>
92
- <span
93
- :class="data.isNew ? 'color-success' : 'color-primary'"
94
- class="text-ellipsis pointer"
95
- @click="clickItem(data)"
96
- >{{ data.label }}</span
97
- >
98
- </span>
99
- <el-link
100
- class="n20-icon-shanchu"
101
- :underline="false"
102
- :disabled="data.checked"
103
- @click.stop="() => removeMenu(node, data)"
104
- />
105
- </span>
106
- </el-tree>
107
- </div>
108
- </div>
109
- </div>
110
- <dialogWrap
111
- v-drag
112
- :visible.sync="configVisible"
113
- :title="'表头设置' | $lc"
114
- width="350px"
115
- :close-on-click-modal="false"
116
- >
117
- <el-form :model="configModel">
118
- <el-form-item>
119
- {{ configModel.label }}
120
- </el-form-item>
121
- <el-form-item
122
- :label="'表头宽度' | $lc"
123
- :rules="[
124
- {
125
- type: 'number',
126
- trigger: ['change', 'blur'],
127
- validator: validator
128
- }
129
- ]"
130
- prop="width"
131
- >
132
- <el-input v-model="configModel.width" class="w-224" />
133
- </el-form-item>
134
- <el-form-item :label="'是否折行' | $lc">
135
- <el-radio-group v-model="configModel.wrap">
136
- <el-radio :label="true">{{ $lc('是') }}</el-radio>
137
- <el-radio :label="false">{{ $lc('否') }}</el-radio>
138
- </el-radio-group>
139
- </el-form-item>
140
- <el-form-item :label="'是否加粗' | $lc">
141
- <el-radio-group v-model="configModel.bold">
142
- <el-radio :label="true">{{ $lc('是') }}</el-radio>
143
- <el-radio :label="false">{{ $lc('否') }}</el-radio>
144
- </el-radio-group>
145
- </el-form-item>
146
- <el-form-item :label="'对齐方式' | $lc">
147
- <el-radio-group v-model="configModel.align">
148
- <el-radio :label="'right'">{{ $lc('右对齐') }}</el-radio>
149
- <el-radio :label="'left'">{{ $lc('左对齐') }}</el-radio>
150
- <el-radio :label="'center'">{{ $lc('居中对齐') }}</el-radio>
151
- </el-radio-group>
152
- </el-form-item>
153
- </el-form>
154
- <div slot="footer">
155
- <el-button type="primary" @click="configSave">{{ '确定' | $lc }}</el-button>
156
- </div>
157
- </dialogWrap>
158
- <dialogWrap v-drag :visible.sync="configVisibleN" :title="'新增' | $lc" width="350px" :close-on-click-modal="false">
159
- <el-form :model="configModelN">
160
- <el-form-item :label="'名称' | $lc" required>
161
- <el-input v-model="configModelN.label" class="input-w" :placeholder="'请输入' | $lc" />
162
- </el-form-item>
163
- </el-form>
164
- <div slot="footer">
165
- <el-button type="primary" @click="configSaveN">{{ '确定' | $lc }}</el-button>
166
- </div>
167
- </dialogWrap>
168
- <div slot="footer" class="text-c">
169
- <el-button type="primary" @click="setChange">{{ '确定' | $lc }}</el-button>
170
- <el-button plain @click="reset">{{ '恢复默认' | $lc }}</el-button>
171
- </div>
172
- </dialog-wrap>
173
- </template>
174
-
175
- <script>
176
- import axios from '../../utils/axios'
177
- import { $lc } from '../../utils/i18n/index'
178
-
179
- import { Message } from 'element-ui'
180
- import forEachs from '../../utils/forEachs'
181
- import dialogWrap from '../Dialog/index.vue'
182
- import dragList from '../DragList/index.vue'
183
- export default {
184
- name: 'ShowColumn',
185
- components: { dialogWrap, dragList },
186
- inheritAttrs: false,
187
- props: {
188
- dialogVisible: {
189
- type: Boolean,
190
- default: false
191
- },
192
- columns: {
193
- type: Array,
194
- default: () => []
195
- },
196
- columnsGroups: {
197
- type: Array,
198
- default: undefined
199
- },
200
- checkColumns: {
201
- type: Array,
202
- default: () => []
203
- },
204
- isExport: {
205
- type: Boolean,
206
- default: false
207
- },
208
- isFilter: {
209
- type: Boolean,
210
- default: false
211
- },
212
- labelKey: {
213
- type: String,
214
- default: 'label'
215
- },
216
- pageId: {
217
- type: String,
218
- default: undefined
219
- },
220
- autoSave: {
221
- type: Boolean,
222
- default: false
223
- },
224
- width: {
225
- type: String,
226
- default: '1000px'
227
- },
228
- hasPX: {
229
- type: Boolean,
230
- default: false
231
- }
232
- },
233
- data() {
234
- return {
235
- dragList: [],
236
- configVisible: false,
237
- enabled: false,
238
- configModel: {
239
- wrap: false,
240
- bold: false,
241
- align: 'center'
242
- },
243
- configModelN: {
244
- label: '',
245
- prop: '',
246
- width: 120,
247
- isNew: true,
248
- children: []
249
- },
250
- userNo: sessionStorage.getItem('userNo'),
251
- configVisibleN: false,
252
- dragListN: []
253
- }
254
- },
255
- computed: {
256
- title() {
257
- if (this.isExport) {
258
- return $lc('导出')
259
- } else if (this.isFilter) {
260
- return $lc('筛选')
261
- } else {
262
- return $lc('设置显示列')
263
- }
264
- },
265
- header() {
266
- if (this.isExport) {
267
- return $lc('导出')
268
- } else if (this.isFilter) {
269
- return $lc('筛选')
270
- } else {
271
- return $lc('显示列')
272
- }
273
- },
274
- visible: {
275
- get() {
276
- return this.dialogVisible
277
- },
278
- set(val) {
279
- this.$emit('update:dialogVisible', val)
280
- }
281
- },
282
- setState() {
283
- const columnsAs = []
284
- forEachs(this.columnsAs, (item) => {
285
- columnsAs.push(item)
286
- })
287
- return columnsAs.length && columnsAs.length === this.dragListN.length
288
- // return this.columnsAs.length && this.columnsAs.length === this.dragListN.length
289
- },
290
- columnsAs: {
291
- get() {
292
- return this.columns.filter((col) => !col.static)
293
- },
294
- set(val) {
295
- return val
296
- }
297
- },
298
- columnsN() {
299
- return this.columns || []
300
- }
301
- },
302
- watch: {
303
- dragList: {
304
- handler() {
305
- let arr = []
306
- forEachs(this.dragList, (item) => {
307
- arr.push(item)
308
- })
309
- this.dragListN = arr
310
- /* const newArr = arr.filter((item) => {
311
- return item.isNew
312
- })
313
- newArr.forEach((item) => {
314
- const index = this.columns.findIndex((col) => col.prop === item.prop || col.label === item.label)
315
- if (index === -1) {
316
- this.columns.push(item)
317
- }
318
- }) */
319
- },
320
- immediate: true,
321
- deep: true
322
- }
323
- },
324
- methods: {
325
- validator(rule, value, callback) {
326
- if (value) {
327
- if (value > 300) {
328
- return callback(new Error($lc('输入最大宽度不能大于300')))
329
- } else if (!/^[1-9]\d*$/.test(value)) {
330
- return callback(new Error($lc('表头宽度只能输入正整数')))
331
- }
332
- }
333
- callback()
334
- },
335
- add() {
336
- this.configVisibleN = true
337
- this.configModelN.prop = `prop${Date.now()}`
338
- },
339
- configSaveN() {
340
- if (!this.configModelN.label) {
341
- return this.$message.warning($lc('请填写名称'))
342
- }
343
- const hasLabel = this.dragListN.find((item) => {
344
- return item.label === this.configModelN.label
345
- })
346
- if (hasLabel) {
347
- return this.$message.warning($lc('已有重名字段,请修改'))
348
- }
349
- this.dragList.unshift(this.configModelN)
350
- // this.columns = [this.configModelN, ...this.columns]
351
- this.$message.success($lc('添加成功'))
352
- this.configVisibleN = false
353
- this.configModelN = {
354
- label: '',
355
- prop: '',
356
- width: 120,
357
- isNew: true,
358
- children: []
359
- }
360
- },
361
- removeMenu(node, data) {
362
- const parent = node.parent
363
- const children = parent.data.children || parent.data
364
- const index = children.findIndex((d) => d.prop === data.prop)
365
- children.splice(index, 1)
366
- },
367
- allowDrop(draggingNode, dropNode, type) {
368
- if (type === 'inner' && draggingNode.data.isNew) {
369
- return !(draggingNode.level <= dropNode.level)
370
- } else if (type === 'inner' && !draggingNode.data.isNew) {
371
- return !(draggingNode.level <= dropNode.level && !dropNode.data.isNew)
372
- } else {
373
- return true
374
- }
375
- },
376
- clickItem(item) {
377
- this.configModel = item
378
- this.configVisible = true
379
- },
380
- configSave() {
381
- this.configVisible = false
382
- },
383
- popOpen() {
384
- this.dragList = this.checkColumns.filter((col) => !col.static)
385
- },
386
- setAll() {
387
- if (this.setState) {
388
- this.dragList = this.dragList.filter((c) => c.checked)
389
- } else {
390
- this.dragList = [...this.columnsAs]
391
- }
392
- },
393
- checkChange(val, column) {
394
- if (val === false) {
395
- let index = this.dragList.findIndex((d) => d[this.labelKey] === column[this.labelKey])
396
- index !== -1 && this.dragList.splice(index, 1)
397
- } else {
398
- let index = this.dragListN.findIndex((d) => d[this.labelKey] === column[this.labelKey])
399
- if (index !== -1) {
400
- Message.warning($lc(`子级请在右侧删除!`))
401
- return false
402
- }
403
- this.dragList.push(column)
404
- }
405
- },
406
- reset() {
407
- let dragList = this.columns.filter((col) => col.isDefault)
408
- this.dragList = dragList.length ? dragList : this.checkColumns.filter((col) => !col.static)
409
- },
410
- setChange() {
411
- if (this.dragList.length < 1) {
412
- this.$message.error(
413
- `${$lc(`至少设置`)}${
414
- this.isExport ? $lc('一列导出数据') : this.isFilter ? $lc('一个筛选条件') : $lc('一列显示列')
415
- }!`
416
- )
417
- return
418
- }
419
-
420
- let list = []
421
- let preCols = this.columns.filter((col) => col.static === 'pre')
422
- let nextCols = this.columns.filter((col) => col.static === 'next')
423
- list.push(...preCols, ...this.dragList, ...nextCols)
424
- // 新增校验
425
- if (!this.isExport) {
426
- let state = list.findIndex((c) => c.isNew && c.children && c.children.length === 0)
427
- if (state !== -1) {
428
- Message.warning($lc(`新增字段必须包含子级!`))
429
- return false
430
- }
431
- }
432
- // 自动保存设置的显示列
433
- this.userNo = sessionStorage.getItem('userNo')
434
- !this.isExport && this.autoSave && this.saveColumns(list)
435
-
436
- this.$emit('setColumns', list)
437
- if (this.isExport) {
438
- this.visible = false
439
- }
440
- },
441
- saveColumns(list) {
442
- let columns = saveTransform(list, this.labelKey, this.isFilter)
443
- axios.post(
444
- `/bems/prod_1.0/user/pageHabit?t=${Math.random()}`,
445
- {
446
- userNo: this.userNo,
447
- pageId: this.pageId,
448
- showStructure: JSON.stringify(columns)
449
- },
450
- { loading: false }
451
- )
452
- this.visible = false
453
- },
454
- getColumns(pageId) {
455
- return getColumns(pageId || this.pageId, this.userNo, this.columnsN, this.labelKey)
456
- }
457
- }
458
- }
459
-
460
- function saveTransform(list, labelKey, isFilter) {
461
- let listN = []
462
- if (!isFilter) {
463
- list.forEach((c) => {
464
- if (c.prop && !c.children && !c.isNew && !c.width & !c.isNew) {
465
- listN.push({ _colKey: 'prop', _colVal: c.prop })
466
- } else if (c.type && ['selection', 'index', 'expand'].includes(c.type)) {
467
- listN.push({ _colKey: 'type', _colVal: c.type })
468
- } else {
469
- let sFn = false
470
- for (let k in c) {
471
- if (typeof c[k] === 'function') sFn = true
472
- }
473
- if (sFn && c[labelKey]) {
474
- listN.push({ _colKey: labelKey, _colVal: c[labelKey] })
475
- } else {
476
- listN.push(c)
477
- }
478
- }
479
- })
480
- } else {
481
- list.forEach((c) => {
482
- if (c.value) {
483
- listN.push({ _colKey: 'value', _colVal: c.value })
484
- } else if (c.startValue) {
485
- listN.push({ _colKey: 'startValue', _colVal: c.startValue })
486
- } else if (c.startDate) {
487
- listN.push({ _colKey: 'startDate', _colVal: c.startDate })
488
- } else {
489
- listN.push(c)
490
- }
491
- })
492
- }
493
- return listN
494
- }
495
-
496
- function getTransform(list, columnsT, labelKey = 'label') {
497
- let columns = []
498
- list.forEach((d) => {
499
- // 如果list中的每一项是字符串
500
- if (typeof d === 'string') {
501
- // 从columnsT中找到label等于d的项
502
- let column = columnsT.find((c) => c[labelKey] === d)
503
- // 如果找到,则将其添加到columns中
504
- column && columns.push(column)
505
- // 如果list中的每一项是对象
506
- } else if (typeof d === 'object') {
507
- // 如果d中有_colKey属性
508
- if (d._colKey) {
509
- // 从columnsT中找到d._colKey等于d._colVal的项
510
- let column = columnsT.find((c) => c[d._colKey] === d._colVal)
511
- // 如果找到,则将其添加到columns中
512
- column && columns.push(column)
513
- // 如果d中没有_colKey属性
514
- } else {
515
- // 将d添加到columns中
516
- columns.push(d)
517
- }
518
- }
519
- })
520
- // 返回columns
521
- return columns
522
- }
523
-
524
- // 这段代码在筛选组件中复用
525
- export function getColumns(pageId, userNo, columnsT, labelKey = 'label') {
526
- return new Promise((resolve, reject) => {
527
- axios
528
- .post(
529
- `/bems/prod_1.0/user/pageHabit/list?t=${Math.random()}`,
530
- {
531
- userNo: userNo,
532
- pageId: pageId
533
- },
534
- { loading: false }
535
- )
536
- .then(({ data }) => {
537
- if (data) {
538
- let _data = JSON.parse(data)
539
- if (_data && _data.length > 0) {
540
- let columns = getTransform(_data, columnsT, labelKey)
541
- return resolve(columns)
542
- }
543
- }
544
- return resolve(null)
545
- })
546
- .catch((err) => {
547
- reject(err)
548
- })
549
- })
550
- }
551
- </script>