wkjp-list-page 1.0.14 → 1.0.16

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/README.md CHANGED
@@ -86,9 +86,10 @@ deviceListApi(params) {
86
86
 
87
87
  ### 2.1 想让用户在页面上自己勾选显示哪些列、拖顺序
88
88
 
89
- **情况:** 运营想「配置表单」、列太多想藏起来。
90
- **加:** 写上 **`enable-column-config`**(或 `:enable-column-config="true"`)
91
- **作用:** 出现「配置表单」按钮和弹窗;左右固定列在弹窗里一般不让拖乱顺序。点确定会 **`columns-change`** 把当前列配置抛给父页面。
89
+ **情况:** 运营想自定义表头、列太多想藏起来。
90
+ **默认:** **`columns` 达到 10 列及以上**(可用 **`column-config-auto-min`** 改阈值)时,表格最右侧自动出现 **表头设置** 齿轮;悬停齿轮显示 **「配置字段」** 提示(可用 **`column-config-tooltip`** 修改)。
91
+ **可选:** `:enable-column-config="true"` 强制开启;`:enable-column-config="false"` 强制关闭。
92
+ **作用:** 弹层内可 **搜索字段**、**显示/未显示** 分组、开关显隐、拖拽排序(固定列不可拖不可关);点 **确认** 后才会作用到表格并触发 **`columns-change`**,点 **取消** / 关闭 / 再次点齿轮则放弃本次修改。
92
93
 
93
94
  ### 2.2 不想要「查询」按钮,或想改按钮上的字
94
95
 
@@ -97,8 +98,8 @@ deviceListApi(params) {
97
98
 
98
99
  ### 2.3 搜索条件太多,一屏排不下
99
100
 
100
- **情况:** 想默认只露前几项,其余用「展开查询 / 收起」收起来。
101
- **加:** `:collapse-search="true"`;**`:search-visible-max`** 可省略,省略时默认只露出前 **6** 条查询项,其余用「展开查询 / 收起」。需要别的条数时再写数字(如 `4`)。
101
+ **默认:** **`searchOption` 超过 6 条**(可用 **`search-visible-max`** 改阈值)时,自动只露出前若干项,并显示「展开查询 / 收起」。
102
+ **可选:** `:collapse-search="false"` 关闭折叠;`:collapse-search="true"` 强制开启(未传时亦默认开启,由条数决定)。
102
103
  **可选:** `:expand-search-text`、`:fold-search-text` 改展开、收起两个词。
103
104
 
104
105
  ### 2.4 想改表格条纹、边框、高度,或表头颜色
@@ -109,7 +110,7 @@ deviceListApi(params) {
109
110
  ### 2.5 想在「查询 / 清除」右边加「设备入网」等自己的按钮
110
111
 
111
112
  **情况:** 和组件自带按钮排在一行,靠右。
112
- **加:** 用插槽 **`search-extra`** 里写你的按钮(有「配置表单」时,设备入网一般在它左边那一块)。
113
+ **加:** 用插槽 **`search-extra`** 里写你的按钮(与内置导出按钮同一右侧区域)。
113
114
 
114
115
  ```vue
115
116
  <template #search-extra>
@@ -139,7 +140,7 @@ exportDeviceList(params, headers) {
139
140
 
140
141
  **可选:** `export-button-text`、`export-button-type`(同导出组件的按钮类型)、`export-disabled`、`export-headers`、`export-before`(`(querys) => boolean`,返回 `false` 则不调接口)、`export-max-limit`(与当前列表 `total` 比较,超出则只提示不导出)。
141
142
 
142
- > 与 **§2.5 `search-extra` 里自己写导出** 可以并存;内置导出在 **`search-extra` 与「配置表单」之间**。
143
+ > 与 **§2.5 `search-extra` 里自己写导出** 可以并存;内置导出在 **`search-extra` 右侧**。
143
144
 
144
145
  ### 2.7 接口里不叫 `list` / `num`,组件认不出来
145
146
 
@@ -274,7 +275,13 @@ deviceListTransformList(list) {
274
275
  | `transformList` | `null` | 入表前改 `list`。 |
275
276
  | `blockListRequest` | `false` | **`true`**:拦截组件内所有列表请求(`fetchList` 不调 `api`);拉表用 **`fetchList({ force: true })`**。**`false`**:不拦截,照常请求。 |
276
277
  | `fetchOnCreated` | `true` | **`false`**:不在 **`created`** 里自动发起首次列表请求,由父页面择机 **`fetchList`**;查询、分页、`autoQuery` 等仍照常。 |
277
- | `enableColumnConfig` | `false` | `true` 显示「配置表单」弹窗。 |
278
+ | `enableColumnConfig` | (未传) | 未传时 **`columns.length >= columnConfigAutoMin`** 自动显示;`true` / `false` 强制开/关。 |
279
+ | `columnConfigAutoMin` | `10` | 自动显示表头设置的列数阈值(列数 **达到该值及以上** 时显示)。 |
280
+ | `columnConfigTooltip` | `配置字段` | 表头设置齿轮图标悬停提示文案。 |
281
+ | `columnConfigConfirmText` | `确认` | 表头设置弹层确认按钮文案。 |
282
+ | `columnConfigCancelText` | `取消` | 表头设置弹层取消按钮文案。 |
283
+ | `columnConfigTitle` | `表头设置` | 表头设置弹层标题。 |
284
+ | `columnConfigSearchPlaceholder` | `搜索添加更多字段` | 表头设置内搜索框占位。 |
278
285
  | `showExport` | `false` | 为 `true` 且提供 `exportApi` 时,在搜索区右侧显示内置 **`exportFile`** 导出按钮。 |
279
286
  | `exportApi` | `null` | `(params, headers?) => Promise`,导出请求;成功返回需含 `success`、`object`(下载链接),与内置导出组件约定一致。 |
280
287
  | `exportButtonText` | `导出` | 导出按钮文案。 |
@@ -290,8 +297,8 @@ deviceListTransformList(list) {
290
297
  | `queryButtonText` | `查询` | 查询按钮文案。 |
291
298
  | `showClearQueryButton` | `true` | 是否显示清除查询。 |
292
299
  | `clearQueryButtonText` | `清除查询` | 清除按钮文案。 |
293
- | `collapseSearch` | `false` | `true` 时启用搜索区折叠。 |
294
- | `searchVisibleMax` | `null` | 折叠时默认展示条数;**未传且已开启 `collapse-search` 时按 6**;显式传 ≤0 则不折叠。 |
300
+ | `collapseSearch` | (未传) | 未传时 **`searchOption.length > searchVisibleMax`** 自动折叠;`true` / `false` 强制开/关。 |
301
+ | `searchVisibleMax` | `null` | 折叠时默认展示条数,**未传按 6**;查询项 **超过** 该值时出现「展开查询」;显式传 ≤0 则不折叠。 |
295
302
  | `expandSearchText` / `foldSearchText` | 展开查询 / 收起 | 折叠按钮文案。 |
296
303
 
297
304
  ---
@@ -310,7 +317,7 @@ deviceListTransformList(list) {
310
317
  | `input` | **`clearable` 默认 `true`**(有内容时出现清除);`false` 关闭。另有 `maxlength`、`height`;回车触发 `query` 事件(不自动 `fetchList`);其它类型若设 `autoQuery: true` 则变更后自动查。 |
311
318
  | `select` | `options`(平铺选项)、`multiple`、`clearable`(默认 true)、`filterable`、`disabled`。 |
312
319
  | `cascader` | **`options`** 为级联树;**`props` 或 `cascaderProps`** 为 `el-cascader` 的字段映射;`filters[prop]` 为 **路径数组**;可选 `filterable`、`collapseTags`、`showAllLevels`、`separator`、`debounce`、`height`。 |
313
- | 日期范围 | **`daterange` 未传 `valueFormat` 时默认 `yyyy-MM-dd`**(`date` / `month` / `year` / `datetime` 等同理,与 Element 约定不符时请自行传 `valueFormat`)。**`startPlaceholder` / `endPlaceholder` 未传时默认「开始日期」「结束日期」**(与上浮文案一致)。**`unlinkPanels`** 默认 `true`(对应 Element `unlink-panels`,左右面板月份独立);设为 `false` 则两面板联动。另有 `rangeSeparator`(默认「至」);可选 **`gmtBeginKey` / `gmtEndKey`** 同步写带时间的起止字段。 |
320
+ | 日期范围 | **`daterange` 未传 `valueFormat` 时默认 `yyyy-MM-dd`**(`date` / `month` / `year` / `datetime` 等同理,与 Element 约定不符时请自行传 `valueFormat`)。范围上浮文案用 **`placeholder: [开始文案, 结束文案]`**(与 input 字段名一致;亦支持 `placeholders`),未传时默认「开始日期」「结束日期」(兼容 `startPlaceholder` / `endPlaceholder`)。**`unlinkPanels`** 默认 `true`;另有 `rangeSeparator`(默认「至」)。可选 **`gmtKeys: [beginFilterKey, endFilterKey]`**(亦支持 `gmtKey`)同步写带 `00:00:00` / `23:59:59` 的起止字段(兼容 `gmtBeginKey` / `gmtEndKey`)。 |
314
321
 
315
322
  ---
316
323
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wkjp-list-page",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Vue2 + ElementUI CommonListPage component",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -96,9 +96,6 @@
96
96
  :disabled="exportDisabled"
97
97
  />
98
98
  </el-form-item>
99
- <el-form-item v-if="enableColumnConfig" class="common-list-page__column-config-item">
100
- <el-button type="primary" plain @click="openColumnConfig">配置表单</el-button>
101
- </el-form-item>
102
99
  </div>
103
100
  </el-form>
104
101
 
@@ -134,8 +131,111 @@
134
131
  </template>
135
132
  </el-table-column>
136
133
  </template>
134
+ <el-table-column
135
+ v-if="showColumnConfig"
136
+ key="__wkjp-column-config__"
137
+ width="48"
138
+ fixed="right"
139
+ align="center"
140
+ class-name="common-list-page__config-col"
141
+ >
142
+ <template slot="header">
143
+ <span
144
+ v-popover:columnConfigPopover
145
+ class="column-config-trigger"
146
+ :class="{ 'is-active': columnConfigVisible }"
147
+ @click.stop="toggleColumnConfig"
148
+ >
149
+ <el-tooltip :content="columnConfigTooltip" placement="top" :enterable="false">
150
+ <span class="column-config-trigger__inner">
151
+ <i class="el-icon-setting"></i>
152
+ </span>
153
+ </el-tooltip>
154
+ </span>
155
+ </template>
156
+ </el-table-column>
137
157
  </el-table>
138
158
 
159
+ <el-popover
160
+ ref="columnConfigPopover"
161
+ v-model="columnConfigVisible"
162
+ placement="bottom-end"
163
+ width="320"
164
+ trigger="manual"
165
+ :append-to-body="true"
166
+ :popper-options="columnConfigPopperOptions"
167
+ popper-class="common-list-page-column-config-popper"
168
+ @show="onColumnConfigShow"
169
+ >
170
+ <div class="column-config-panel">
171
+ <div class="column-config-panel__head">
172
+ <span class="column-config-panel__title">{{ columnConfigTitle }}</span>
173
+ <i class="el-icon-close column-config-panel__close" @click="cancelColumnConfig"></i>
174
+ </div>
175
+ <el-input
176
+ v-model="columnConfigKeyword"
177
+ size="small"
178
+ prefix-icon="el-icon-search"
179
+ :placeholder="columnConfigSearchPlaceholder"
180
+ clearable
181
+ class="column-config-panel__search"
182
+ />
183
+ <div class="column-config-panel__body">
184
+ <div class="column-config-panel__section">
185
+ <div class="column-config-panel__section-title">显示字段</div>
186
+ <ul v-if="visibleConfigIndexes.length" class="column-config-panel__list">
187
+ <li
188
+ v-for="idx in visibleConfigIndexes"
189
+ :key="configColumns[idx]._configKey"
190
+ :data-config-idx="idx"
191
+ class="column-config-panel__item"
192
+ :class="{
193
+ 'column-config-panel__item--fixed': isFixedColumn(configColumns[idx]),
194
+ 'column-config-panel__item--dragging': dragFromIndex === idx,
195
+ 'column-config-panel__item--drag-over': dragOverIndex === idx
196
+ }"
197
+ >
198
+ <i
199
+ class="column-config-panel__drag el-icon-rank"
200
+ :class="{ 'is-disabled': !canDragConfigIndex(idx) }"
201
+ @mousedown.prevent="onDragMouseDown(idx)"
202
+ ></i>
203
+ <span class="column-config-panel__label">{{ columnConfigLabel(configColumns[idx]) }}</span>
204
+ <el-switch
205
+ :value="configColumns[idx]._visible !== false"
206
+ :disabled="isFixedColumn(configColumns[idx])"
207
+ @input="(val) => setColumnVisible(idx, val)"
208
+ />
209
+ </li>
210
+ </ul>
211
+ <div v-else class="column-config-panel__empty">暂无匹配字段</div>
212
+ </div>
213
+ <div v-if="hiddenConfigIndexes.length" class="column-config-panel__section">
214
+ <div class="column-config-panel__section-title">未显示字段</div>
215
+ <ul class="column-config-panel__list">
216
+ <li
217
+ v-for="idx in hiddenConfigIndexes"
218
+ :key="configColumns[idx]._configKey"
219
+ :data-config-idx="idx"
220
+ class="column-config-panel__item column-config-panel__item--hidden"
221
+ >
222
+ <span class="column-config-panel__drag column-config-panel__drag--placeholder"></span>
223
+ <span class="column-config-panel__label">{{ columnConfigLabel(configColumns[idx]) }}</span>
224
+ <el-switch
225
+ :value="configColumns[idx]._visible !== false"
226
+ @input="(val) => setColumnVisible(idx, val)"
227
+ />
228
+ </li>
229
+ </ul>
230
+ </div>
231
+ </div>
232
+ <div class="column-config-panel__footer">
233
+ <el-button size="small" @click="cancelColumnConfig">{{ columnConfigCancelText }}</el-button>
234
+ <el-button type="primary" size="small" @click="confirmColumnConfig">{{ columnConfigConfirmText }}</el-button>
235
+ </div>
236
+ </div>
237
+ </el-popover>
238
+
139
239
  <el-pagination
140
240
  v-if="innerPagination"
141
241
  background
@@ -148,34 +248,6 @@
148
248
  @current-change="handlePageChange"
149
249
  ></el-pagination>
150
250
 
151
- <el-dialog
152
- title="配置表单"
153
- :visible.sync="columnConfigVisible"
154
- width="900px"
155
- :close-on-click-modal="false"
156
- >
157
- <div class="column-config-desc">拖动调整列顺序,勾选控制是否显示</div>
158
- <ul class="column-config-list">
159
- <li
160
- v-for="(col, index) in configColumns"
161
- :key="col._configKey"
162
- :draggable="!isFixedColumn(col)"
163
- class="column-config-item"
164
- :class="{ 'column-config-item--fixed': isFixedColumn(col) }"
165
- @dragstart="onDragStart(index)"
166
- @dragover.prevent
167
- @drop="onDrop(index)"
168
- >
169
- <i class="el-icon-rank"></i>
170
- <el-checkbox v-model="col._visible">{{ col.label || col.prop || col.slot }}</el-checkbox>
171
- <el-tag v-if="isFixedColumn(col)" type="info" size="mini">固定</el-tag>
172
- </li>
173
- </ul>
174
- <span slot="footer" class="dialog-footer">
175
- <el-button @click="columnConfigVisible = false">取 消</el-button>
176
- <el-button type="primary" @click="confirmColumnConfig">确 定</el-button>
177
- </span>
178
- </el-dialog>
179
251
  </section>
180
252
  </template>
181
253
 
@@ -228,7 +300,21 @@ export default {
228
300
  */
229
301
  pagination: { type: Object, default: null },
230
302
  tableAttrs: { type: Object, default: () => ({}) },
231
- enableColumnConfig: { type: Boolean, default: false },
303
+ /**
304
+ * 表头设置开关:显式 `true` / `false` 强制开/关;
305
+ * 未传时列数 **达到 `columnConfigAutoMin`(默认 10)及以上** 自动显示。
306
+ */
307
+ enableColumnConfig: { type: Boolean, default: undefined },
308
+ /** 未传 `enable-column-config` 时,列数达到该值及以上则自动显示表头设置 */
309
+ columnConfigAutoMin: { type: Number, default: 10 },
310
+ /** 表头设置齿轮图标悬停提示 */
311
+ columnConfigTooltip: { type: String, default: "配置字段" },
312
+ columnConfigConfirmText: { type: String, default: "确认" },
313
+ columnConfigCancelText: { type: String, default: "取消" },
314
+ /** 表头设置弹层标题 */
315
+ columnConfigTitle: { type: String, default: "表头设置" },
316
+ /** 表头设置内搜索框占位 */
317
+ columnConfigSearchPlaceholder: { type: String, default: "搜索添加更多字段" },
232
318
  /** 表头背景色,对应 Element `header-cell-style` 的 backgroundColor,可被 tableAttrs.header-cell-style 覆盖 */
233
319
  tableHeaderBg: { type: String, default: "#e5e9f2" },
234
320
  /**
@@ -244,11 +330,11 @@ export default {
244
330
  showClearQueryButton: { type: Boolean, default: true },
245
331
  clearQueryButtonText: { type: String, default: "清除查询" },
246
332
  /**
247
- * true `searchOption` 条数大于有效折叠上限时,默认只展示前若干条,其后显示「展开查询」;展开后显示全部并可「收起」。
248
- * 开启折叠且未传 `searchVisibleMax` 时,默认展示 **6** 条。
333
+ * 搜索区折叠:显式 `true` / `false` 强制开/关;
334
+ * 未传时 **`searchOption` 超过 `searchVisibleMax`(默认 6)** 自动显示「展开查询」。
249
335
  */
250
- collapseSearch: { type: Boolean, default: false },
251
- /** 折叠时最多展示的查询项条数(含 slot);未开启 `collapse-search` 时不生效;开启后未传本项则默认 **6**;显式传 ≤0 表示不折叠 */
336
+ collapseSearch: { type: Boolean, default: undefined },
337
+ /** 折叠时默认展示的查询项条数;未传时按 **6**;显式传 ≤0 表示不折叠 */
252
338
  searchVisibleMax: { type: Number, default: null },
253
339
  expandSearchText: { type: String, default: "展开查询" },
254
340
  foldSearchText: { type: String, default: "收起" },
@@ -282,7 +368,9 @@ export default {
282
368
  pSizes: [10, 20, 30, 50, 100, 200]
283
369
  },
284
370
  columnConfigVisible: false,
285
- draggingIndex: -1,
371
+ columnConfigKeyword: "",
372
+ dragFromIndex: -1,
373
+ dragOverIndex: -1,
286
374
  configColumns: [],
287
375
  appliedColumns: [],
288
376
  /** 进入页时 filters 的快照,用于「清除查询」 */
@@ -291,10 +379,43 @@ export default {
291
379
  };
292
380
  },
293
381
  computed: {
382
+ columnsList() {
383
+ return Array.isArray(this.columns) ? this.columns : [];
384
+ },
385
+ /** 是否展示表头设置(齿轮列 + 列显隐/排序能力) */
386
+ showColumnConfig() {
387
+ if (this.enableColumnConfig === true) return true;
388
+ if (this.enableColumnConfig === false) return false;
389
+ const min = Number(this.columnConfigAutoMin);
390
+ const threshold = Number.isFinite(min) && min > 0 ? Math.floor(min) : 10;
391
+ return this.columnsList.length >= threshold;
392
+ },
294
393
  visibleColumns() {
295
- const source = this.enableColumnConfig ? this.appliedColumns : this.columns;
394
+ const source = this.showColumnConfig ? this.appliedColumns : this.columns;
296
395
  return source.filter((item) => item && item.hidden !== true && item._visible !== false);
297
396
  },
397
+ visibleConfigIndexes() {
398
+ const indexes = [];
399
+ for (let i = 0; i < this.configColumns.length; i++) {
400
+ const col = this.configColumns[i];
401
+ if (col._visible !== false && this.matchesColumnConfigSearch(col)) indexes.push(i);
402
+ }
403
+ return indexes;
404
+ },
405
+ hiddenConfigIndexes() {
406
+ const indexes = [];
407
+ for (let i = 0; i < this.configColumns.length; i++) {
408
+ const col = this.configColumns[i];
409
+ if (col._visible === false && this.matchesColumnConfigSearch(col)) indexes.push(i);
410
+ }
411
+ return indexes;
412
+ },
413
+ columnConfigPopperOptions() {
414
+ return { boundariesElement: "body", gpuAcceleration: false };
415
+ },
416
+ columnConfigSearchActive() {
417
+ return String(this.columnConfigKeyword || "").trim() !== "";
418
+ },
298
419
  mergedHeaderCellStyle() {
299
420
  const baseBg = this.tableHeaderBg || "#e5e9f2";
300
421
  const base = { backgroundColor: baseBg };
@@ -311,9 +432,13 @@ export default {
311
432
  searchOptionList() {
312
433
  return Array.isArray(this.searchOption) ? this.searchOption : [];
313
434
  },
314
- /** 有效的折叠上限:`collapse-search` false 时不折叠;为 true 且未传 `searchVisibleMax` 时默认 6;显式传 ≤0 时不折叠 */
435
+ /** 是否启用搜索折叠(未传 `collapse-search` 时默认开启,由条数决定是否展示「展开查询」) */
436
+ searchCollapseEnabled() {
437
+ return this.collapseSearch !== false;
438
+ },
439
+ /** 有效的折叠上限:未传 `searchVisibleMax` 时默认 6;显式传 ≤0 时不折叠 */
315
440
  effectiveSearchVisibleMax() {
316
- if (!this.collapseSearch) return null;
441
+ if (!this.searchCollapseEnabled) return null;
317
442
  var raw = this.searchVisibleMax;
318
443
  if (raw === null || raw === undefined || raw === "") {
319
444
  return 6;
@@ -325,7 +450,7 @@ export default {
325
450
  searchCollapseActive() {
326
451
  var max = this.effectiveSearchVisibleMax;
327
452
  return (
328
- this.collapseSearch === true &&
453
+ this.searchCollapseEnabled &&
329
454
  max != null &&
330
455
  this.searchOptionList.length > max
331
456
  );
@@ -339,7 +464,7 @@ export default {
339
464
  },
340
465
  /** 右侧成组:扩展区插槽 + 导出 + 配置表单,整体贴右且相邻 */
341
466
  hasSearchRightCluster() {
342
- return this.enableColumnConfig || this.hasSearchExtraSlot || this.showExportWithApi;
467
+ return this.hasSearchExtraSlot || this.showExportWithApi;
343
468
  },
344
469
  hasSearchExtraSlot() {
345
470
  var s = this.$slots["search-extra"];
@@ -394,7 +519,7 @@ export default {
394
519
  handler(list) {
395
520
  var len = Array.isArray(list) ? list.length : 0;
396
521
  var max = this.effectiveSearchVisibleMax;
397
- if (!this.collapseSearch || max == null || len <= max) {
522
+ if (!this.searchCollapseEnabled || max == null || len <= max) {
398
523
  this.searchExpanded = false;
399
524
  }
400
525
  },
@@ -404,7 +529,7 @@ export default {
404
529
  this.searchExpanded = false;
405
530
  },
406
531
  collapseSearch(val) {
407
- if (!val) this.searchExpanded = false;
532
+ if (val === false) this.searchExpanded = false;
408
533
  },
409
534
  columns: {
410
535
  immediate: true,
@@ -430,6 +555,9 @@ export default {
430
555
  this.fetchList();
431
556
  }
432
557
  },
558
+ beforeDestroy() {
559
+ this.removeColumnDragListeners();
560
+ },
433
561
  methods: {
434
562
  forwardSelectionChange(selection) {
435
563
  this.$emit("selection-change", selection);
@@ -498,17 +626,54 @@ export default {
498
626
  return [];
499
627
  },
500
628
  floatingLabelFor(item) {
501
- const text = (item.label || item.placeholder || "").trim();
629
+ const ph = item.placeholder;
630
+ const placeholderText = typeof ph === "string" ? ph : "";
631
+ const text = (item.label || placeholderText || "").trim();
502
632
  return text || " ";
503
633
  },
634
+ /**
635
+ * 范围上浮文案:`placeholder` / `placeholders` 传 `[开始, 结束]`;
636
+ * 兼容 `startPlaceholder` / `endPlaceholder`。
637
+ */
638
+ resolveRangePlaceholders(item) {
639
+ if (!item) return ["", ""];
640
+ const pick = function (arr) {
641
+ if (!Array.isArray(arr) || arr.length < 2) return null;
642
+ return [
643
+ String(arr[0] != null ? arr[0] : "").trim(),
644
+ String(arr[1] != null ? arr[1] : "").trim()
645
+ ];
646
+ };
647
+ const fromArray =
648
+ pick(item.placeholders) ||
649
+ pick(item.rangePlaceholders) ||
650
+ (Array.isArray(item.placeholder) ? pick(item.placeholder) : null);
651
+ if (fromArray) return fromArray;
652
+ return [(item.startPlaceholder || "").trim(), (item.endPlaceholder || "").trim()];
653
+ },
504
654
  dateRangeStartText(item) {
505
- const t = (item.startPlaceholder || "").trim();
655
+ const t = this.resolveRangePlaceholders(item)[0];
506
656
  return t || "开始日期";
507
657
  },
508
658
  dateRangeEndText(item) {
509
- const t = (item.endPlaceholder || "").trim();
659
+ const t = this.resolveRangePlaceholders(item)[1];
510
660
  return t || "结束日期";
511
661
  },
662
+ /** `gmtKeys` / `gmtKey` 传 `[begin, end]`;兼容 `gmtBeginKey` / `gmtEndKey` */
663
+ resolveGmtKeys(item) {
664
+ if (!item) return null;
665
+ const pick = function (arr) {
666
+ if (!Array.isArray(arr) || arr.length < 2) return null;
667
+ const begin = arr[0];
668
+ const end = arr[1];
669
+ if (begin && end) return [begin, end];
670
+ return null;
671
+ };
672
+ const fromArray = pick(item.gmtKeys) || pick(item.gmtKey);
673
+ if (fromArray) return fromArray;
674
+ if (item.gmtBeginKey && item.gmtEndKey) return [item.gmtBeginKey, item.gmtEndKey];
675
+ return null;
676
+ },
512
677
  resolveInputString(item) {
513
678
  if (!item.prop) return "";
514
679
  const v = this.filters[item.prop];
@@ -520,13 +685,16 @@ export default {
520
685
  },
521
686
  handleFieldChange(item, value) {
522
687
  if (item.prop) this.$set(this.filters, item.prop, value);
523
- if (item.gmtBeginKey && item.gmtEndKey) {
688
+ const gmtKeys = this.resolveGmtKeys(item);
689
+ if (gmtKeys) {
690
+ const beginKey = gmtKeys[0];
691
+ const endKey = gmtKeys[1];
524
692
  if (Array.isArray(value) && value.length === 2 && value[0] && value[1]) {
525
- this.$set(this.filters, item.gmtBeginKey, `${value[0]} 00:00:00`);
526
- this.$set(this.filters, item.gmtEndKey, `${value[1]} 23:59:59`);
693
+ this.$set(this.filters, beginKey, `${value[0]} 00:00:00`);
694
+ this.$set(this.filters, endKey, `${value[1]} 23:59:59`);
527
695
  } else {
528
- this.$set(this.filters, item.gmtBeginKey, "");
529
- this.$set(this.filters, item.gmtEndKey, "");
696
+ this.$set(this.filters, beginKey, "");
697
+ this.$set(this.filters, endKey, "");
530
698
  }
531
699
  }
532
700
  if (typeof item.onChange === "function") item.onChange(value);
@@ -681,37 +849,124 @@ export default {
681
849
  this.appliedColumns = sorted.map((col) => Object.assign({}, col));
682
850
  this.configColumns = sorted.map((col) => Object.assign({}, col));
683
851
  },
684
- openColumnConfig() {
685
- this.configColumns = this.sortConfigColumns(this.appliedColumns.map((col) => Object.assign({}, col)));
852
+ columnConfigLabel(col) {
853
+ return String((col && (col.label || col.prop || col.slot)) || "").trim();
854
+ },
855
+ matchesColumnConfigSearch(col) {
856
+ const kw = String(this.columnConfigKeyword || "")
857
+ .trim()
858
+ .toLowerCase();
859
+ if (!kw) return true;
860
+ return this.columnConfigLabel(col).toLowerCase().indexOf(kw) !== -1;
861
+ },
862
+ cloneConfigColumnsFrom(source) {
863
+ return this.sortConfigColumns(
864
+ (source || []).map((col) =>
865
+ Object.assign({}, col, {
866
+ _visible: col._visible !== false && col.hidden !== true
867
+ })
868
+ )
869
+ );
870
+ },
871
+ closeColumnConfigPopover() {
872
+ this.columnConfigVisible = false;
873
+ this.columnConfigKeyword = "";
874
+ this.dragFromIndex = -1;
875
+ this.dragOverIndex = -1;
876
+ this.removeColumnDragListeners();
877
+ const pop = this.$refs.columnConfigPopover;
878
+ if (pop && typeof pop.doClose === "function") pop.doClose();
879
+ },
880
+ toggleColumnConfig() {
881
+ if (this.columnConfigVisible) {
882
+ this.cancelColumnConfig();
883
+ return;
884
+ }
885
+ this.onColumnConfigShow();
686
886
  this.columnConfigVisible = true;
887
+ const pop = this.$refs.columnConfigPopover;
888
+ if (pop && typeof pop.doShow === "function") pop.doShow();
889
+ },
890
+ onColumnConfigShow() {
891
+ this.columnConfigKeyword = "";
892
+ this.dragFromIndex = -1;
893
+ this.dragOverIndex = -1;
894
+ this.configColumns = this.cloneConfigColumnsFrom(this.appliedColumns);
895
+ },
896
+ cancelColumnConfig() {
897
+ this.configColumns = this.cloneConfigColumnsFrom(this.appliedColumns);
898
+ this.closeColumnConfigPopover();
687
899
  },
688
900
  confirmColumnConfig() {
901
+ this.applyColumnConfig();
902
+ this.closeColumnConfigPopover();
903
+ },
904
+ canDragConfigIndex(idx) {
905
+ const col = this.configColumns[idx];
906
+ return col && !this.columnConfigSearchActive && !this.isFixedColumn(col) && col._visible !== false;
907
+ },
908
+ setColumnVisible(idx, visible) {
909
+ const col = this.configColumns[idx];
910
+ if (!col || this.isFixedColumn(col)) return;
911
+ this.$set(this.configColumns[idx], "_visible", visible === true);
912
+ },
913
+ removeColumnDragListeners() {
914
+ if (this._columnDragMoveHandler) {
915
+ document.removeEventListener("mousemove", this._columnDragMoveHandler);
916
+ this._columnDragMoveHandler = null;
917
+ }
918
+ if (this._columnDragUpHandler) {
919
+ document.removeEventListener("mouseup", this._columnDragUpHandler);
920
+ this._columnDragUpHandler = null;
921
+ }
922
+ },
923
+ onDragMouseDown(idx) {
924
+ if (!this.canDragConfigIndex(idx)) return;
925
+ this.dragFromIndex = idx;
926
+ this.dragOverIndex = idx;
927
+ this._columnDragMoveHandler = (e) => this.onDragMouseMove(e);
928
+ this._columnDragUpHandler = () => this.onDragMouseUp();
929
+ document.addEventListener("mousemove", this._columnDragMoveHandler);
930
+ document.addEventListener("mouseup", this._columnDragUpHandler);
931
+ },
932
+ onDragMouseMove(e) {
933
+ if (this.dragFromIndex < 0) return;
934
+ const target = document.elementFromPoint(e.clientX, e.clientY);
935
+ if (!target) return;
936
+ const row = target.closest("[data-config-idx]");
937
+ if (!row) return;
938
+ const overIdx = Number(row.getAttribute("data-config-idx"));
939
+ if (!Number.isFinite(overIdx) || !this.canDragConfigIndex(overIdx)) return;
940
+ this.dragOverIndex = overIdx;
941
+ },
942
+ onDragMouseUp() {
943
+ this.removeColumnDragListeners();
944
+ if (
945
+ this.dragFromIndex >= 0 &&
946
+ this.dragOverIndex >= 0 &&
947
+ this.dragFromIndex !== this.dragOverIndex
948
+ ) {
949
+ this.moveConfigColumnByIndex(this.dragFromIndex, this.dragOverIndex);
950
+ }
951
+ this.dragFromIndex = -1;
952
+ this.dragOverIndex = -1;
953
+ },
954
+ moveConfigColumnByIndex(from, to) {
955
+ const cols = this.configColumns.slice();
956
+ if (this.isFixedColumn(cols[from]) || this.isFixedColumn(cols[to])) return;
957
+ const moving = cols[from];
958
+ cols.splice(from, 1);
959
+ cols.splice(to, 0, moving);
960
+ this.configColumns = cols;
961
+ },
962
+ applyColumnConfig() {
689
963
  this.appliedColumns = this.configColumns.map((col) =>
690
964
  Object.assign({}, col, {
691
965
  hidden: col._visible === false
692
966
  })
693
967
  );
694
- this.columnConfigVisible = false;
695
968
  this.$emit("columns-change", this.appliedColumns);
696
969
  },
697
- onDragStart(index) {
698
- if (this.isFixedColumn(this.configColumns[index])) return;
699
- this.draggingIndex = index;
700
- },
701
- onDrop(targetIndex) {
702
- if (this.draggingIndex < 0 || this.draggingIndex === targetIndex) return;
703
- const target = this.configColumns[targetIndex];
704
- const moving = this.configColumns[this.draggingIndex];
705
- if (this.isFixedColumn(target) || this.isFixedColumn(moving)) {
706
- this.draggingIndex = -1;
707
- return;
708
- }
709
- const next = this.configColumns.slice();
710
- next[this.draggingIndex] = target;
711
- next[targetIndex] = moving;
712
- this.configColumns = next;
713
- this.draggingIndex = -1;
714
- },
715
970
  isFixedColumn(col) {
716
971
  return col && (col.fixed === "left" || col.fixed === "right" || col.configFixed === true);
717
972
  },
@@ -745,9 +1000,9 @@ export default {
745
1000
  align-items: center;
746
1001
  width: 100%;
747
1002
  box-sizing: border-box;
748
- margin-bottom: 14px;
1003
+ /*margin-bottom: 14px;*/
749
1004
  padding-bottom: 14px;
750
- border-bottom: 1px solid #f0f2f5;
1005
+ /*border-bottom: 1px solid #f0f2f5;*/
751
1006
  }
752
1007
 
753
1008
  /* 覆盖业务里 el-form-item.right 等 float,避免与右侧成组抢布局 */
@@ -772,8 +1027,7 @@ export default {
772
1027
  margin-right: 0 !important;
773
1028
  }
774
1029
 
775
- .common-list-page /deep/ .common-list-page__export-item,
776
- .common-list-page /deep/ .common-list-page__column-config-item {
1030
+ .common-list-page /deep/ .common-list-page__export-item {
777
1031
  margin-left: 0 !important;
778
1032
  margin-right: 0 !important;
779
1033
  flex-shrink: 0;
@@ -1103,32 +1357,198 @@ export default {
1103
1357
  line-height: 28px;
1104
1358
  }
1105
1359
 
1106
- .column-config-desc {
1107
- margin-bottom: 10px;
1360
+ .common-list-page /deep/ .common-list-page__config-col .cell {
1361
+ padding: 0 !important;
1362
+ overflow: visible;
1363
+ }
1364
+
1365
+ .common-list-page /deep/ .common-list-page__config-col th.el-table__cell {
1366
+ padding: 0 !important;
1367
+ overflow: visible;
1368
+ }
1369
+
1370
+ .column-config-trigger {
1371
+ display: inline-flex;
1372
+ align-items: center;
1373
+ justify-content: center;
1374
+ width: 28px;
1375
+ height: 28px;
1376
+ border-radius: 4px;
1377
+ color: #606266;
1378
+ cursor: pointer;
1379
+ transition: color 0.2s ease, background 0.2s ease;
1380
+ }
1381
+
1382
+ .column-config-trigger__inner {
1383
+ display: inline-flex;
1384
+ align-items: center;
1385
+ justify-content: center;
1386
+ width: 100%;
1387
+ height: 100%;
1388
+ }
1389
+
1390
+ .column-config-trigger:hover,
1391
+ .column-config-trigger.is-active {
1392
+ color: #409eff;
1393
+ background: #ecf5ff;
1394
+ }
1395
+
1396
+ .column-config-trigger .el-icon-setting {
1397
+ font-size: 16px;
1398
+ }
1399
+
1400
+ .column-config-panel {
1401
+ display: flex;
1402
+ flex-direction: column;
1403
+ box-sizing: border-box;
1404
+ max-height: min(70vh, calc(100vh - 120px));
1405
+ }
1406
+
1407
+ .column-config-panel__body {
1408
+ flex: 1;
1409
+ min-height: 0;
1410
+ overflow-y: auto;
1411
+ margin-right: -4px;
1412
+ padding-right: 4px;
1413
+ }
1414
+
1415
+ .column-config-panel__head {
1416
+ display: flex;
1417
+ align-items: center;
1418
+ justify-content: space-between;
1419
+ margin-bottom: 12px;
1420
+ }
1421
+
1422
+ .column-config-panel__title {
1423
+ font-size: 14px;
1424
+ font-weight: 600;
1425
+ color: #303133;
1426
+ }
1427
+
1428
+ .column-config-panel__close {
1429
+ font-size: 16px;
1108
1430
  color: #909399;
1431
+ cursor: pointer;
1109
1432
  }
1110
- .column-config-list {
1111
- max-height: 420px;
1112
- overflow: auto;
1113
- padding: 0;
1114
- margin: 0;
1115
- display: grid;
1116
- grid-template-columns: repeat(4, minmax(0, 1fr));
1117
- gap: 8px;
1433
+
1434
+ .column-config-panel__close:hover {
1435
+ color: #606266;
1118
1436
  }
1119
- .column-config-item {
1437
+
1438
+ .column-config-panel__search {
1439
+ margin-bottom: 12px;
1440
+ }
1441
+
1442
+ .column-config-panel__section + .column-config-panel__section {
1443
+ margin-top: 14px;
1444
+ padding-top: 14px;
1445
+ border-top: 1px solid #ebeef5;
1446
+ }
1447
+
1448
+ .column-config-panel__section-title {
1449
+ margin-bottom: 8px;
1450
+ font-size: 12px;
1451
+ color: #909399;
1452
+ }
1453
+
1454
+ .column-config-panel__list {
1455
+ margin: 0;
1456
+ padding: 0;
1120
1457
  list-style: none;
1458
+ }
1459
+
1460
+ .column-config-panel__item {
1121
1461
  display: flex;
1122
1462
  align-items: center;
1123
1463
  gap: 8px;
1124
- border: 1px solid #ebeef5;
1464
+ min-height: 36px;
1465
+ padding: 4px 2px;
1125
1466
  border-radius: 4px;
1126
- padding: 8px 10px;
1467
+ }
1468
+
1469
+ .column-config-panel__item--dragging {
1470
+ background: #ecf5ff;
1471
+ }
1472
+
1473
+ .column-config-panel__item--drag-over {
1474
+ background: #f5f7fa;
1475
+ }
1476
+
1477
+ .column-config-panel__item[draggable="true"] {
1127
1478
  cursor: move;
1479
+ }
1480
+
1481
+ .column-config-panel__item--fixed {
1482
+ cursor: default;
1483
+ }
1484
+
1485
+ .column-config-panel__drag {
1486
+ flex-shrink: 0;
1487
+ width: 16px;
1488
+ font-size: 14px;
1489
+ color: #c0c4cc;
1490
+ cursor: grab;
1491
+ touch-action: none;
1492
+ }
1493
+
1494
+ .column-config-panel__drag:active {
1495
+ cursor: grabbing;
1496
+ }
1497
+
1498
+ .column-config-panel__drag.is-disabled,
1499
+ .column-config-panel__drag--placeholder {
1500
+ visibility: hidden;
1501
+ }
1502
+
1503
+ .column-config-panel__label {
1504
+ flex: 1;
1128
1505
  min-width: 0;
1506
+ font-size: 13px;
1507
+ color: #303133;
1508
+ overflow: hidden;
1509
+ text-overflow: ellipsis;
1510
+ white-space: nowrap;
1129
1511
  }
1130
- .column-config-item--fixed {
1131
- background: #f5f7fa;
1132
- cursor: not-allowed;
1512
+
1513
+ .column-config-panel__empty {
1514
+ padding: 8px 0;
1515
+ font-size: 12px;
1516
+ color: #c0c4cc;
1517
+ text-align: center;
1518
+ }
1519
+
1520
+ .column-config-panel__footer {
1521
+ display: flex;
1522
+ flex-shrink: 0;
1523
+ justify-content: flex-end;
1524
+ gap: 8px;
1525
+ margin-top: 12px;
1526
+ padding-top: 12px;
1527
+ border-top: 1px solid #ebeef5;
1528
+ }
1529
+ </style>
1530
+
1531
+ <style>
1532
+ .common-list-page-column-config-popper {
1533
+ padding: 12px 14px !important;
1534
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) !important;
1535
+ z-index: 3000 !important;
1536
+ max-height: calc(100vh - 80px);
1537
+ overflow: visible;
1538
+ }
1539
+
1540
+ .common-list-page-column-config-popper .el-switch {
1541
+ pointer-events: auto !important;
1542
+ }
1543
+
1544
+ .column-config-panel__switch {
1545
+ flex-shrink: 0;
1546
+ position: relative;
1547
+ z-index: 2;
1548
+ pointer-events: auto;
1549
+ }
1550
+
1551
+ .common-list-page-column-config-popper .column-config-panel__switch .el-switch {
1552
+ pointer-events: auto;
1133
1553
  }
1134
1554
  </style>