wkjp-list-page 1.0.7 → 1.0.9

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
@@ -55,8 +55,8 @@ Vue.use(CommonListPage);
55
55
  // data 示例(命名仅作示例,按你页面改)
56
56
  deviceListFilters: { keyword: "", status: "" },
57
57
  deviceListSearchOption: [
58
- { type: "input", prop: "keyword", label: "关键词", autoQuery: false },
59
- { type: "select", prop: "status", label: "状态", options: [{ label: "全部", value: "" }], autoQuery: false }
58
+ { type: "input", prop: "keyword", label: "关键词" },
59
+ { type: "select", prop: "status", label: "状态", options: [{ label: "全部", value: "" }], autoQuery: true }
60
60
  ],
61
61
  deviceListColumns: [
62
62
  { type: "index", label: "#", width: 55 },
@@ -275,6 +275,7 @@ deviceListTransformList(list) {
275
275
  | `exportMaxLimit` | `null` | 若设置:当前列表 `total` 超过该值则提示不导出。 |
276
276
  | `tableAttrs` | `{}` | 透传 `el-table`。 |
277
277
  | `tableHeaderBg` | `#e5e9f2` | 表头背景色。 |
278
+ | `cellEmptyText` | `--` | 非 slot 列空值占位;列上可用 `emptyText` / `defaultText` 单独覆盖。 |
278
279
  | `showQueryButton` | `true` | 是否显示查询按钮。 |
279
280
  | `queryButtonText` | `查询` | 查询按钮文案。 |
280
281
  | `showClearQueryButton` | `true` | 是否显示清除查询。 |
@@ -294,12 +295,12 @@ deviceListTransformList(list) {
294
295
  | `prop` | 对应 `filters[prop]`(`slot` 可省略)。 |
295
296
  | `label` / `placeholder` | 浮动标签,优先 `label`。 |
296
297
  | `width` | 宽度(数字,内部会加 `px`)。 |
297
- | `autoQuery` | 默认 `true`;`false` 时变更后不自动查(需点查询或回车)。 |
298
+ | `autoQuery` | 默认 `false`;为 `true` 时选择/日期等变更后自动 `fetchList`(重置第 1 页)。 |
298
299
  | `onChange` | `(val) => void`。 |
299
- | `input` | `maxlength`、`height`。回车触发 `query` 事件(不自动 `fetchList`,需自行处理或依赖其它筛选项的 `autoQuery`)。 |
300
+ | `input` | **`clearable` 默认 `true`**(有内容时出现清除);`false` 关闭。另有 `maxlength`、`height`;回车触发 `query` 事件(不自动 `fetchList`);其它类型若设 `autoQuery: true` 则变更后自动查。 |
300
301
  | `select` | `options`(平铺选项)、`multiple`、`clearable`(默认 true)、`filterable`、`disabled`。 |
301
302
  | `cascader` | **`options`** 为级联树;**`props` 或 `cascaderProps`** 为 `el-cascader` 的字段映射;`filters[prop]` 为 **路径数组**;可选 `filterable`、`collapseTags`、`showAllLevels`、`separator`、`debounce`、`height`。 |
302
- | 日期范围 | `valueFormat`(默认 `yyyy-MM-dd`)、`startPlaceholder`、`endPlaceholder`;可选 **`gmtBeginKey` / `gmtEndKey`** 同步写带时间的起止字段。 |
303
+ | 日期范围 | **`daterange` 未传 `valueFormat` 时默认 `yyyy-MM-dd`**(`date` / `month` / `year` / `datetime` 等同理,与 Element 约定不符时请自行传 `valueFormat`)。**`startPlaceholder` / `endPlaceholder` 未传时默认「开始日期」「结束日期」**(与上浮文案一致)。**`unlinkPanels`** 默认 `true`(对应 Element `unlink-panels`,左右面板月份独立);设为 `false` 则两面板联动。另有 `rangeSeparator`(默认「至」);可选 **`gmtBeginKey` / `gmtEndKey`** 同步写带时间的起止字段。 |
303
304
 
304
305
  ---
305
306
 
@@ -308,11 +309,15 @@ deviceListTransformList(list) {
308
309
  非 `slot` 列会 **透传** 到 `el-table-column`(`prop`、`label`、`width`、`fixed` 等)。
309
310
  内置列类型 **`selection` / `index` / `expand`** 不做自定义单元格包装。
310
311
 
312
+ 最简写法:**`{ prop: 'name', label: '姓名' }`**,值为 `null` / `undefined` / `''`(以及 `textFormatter` 得到空串)时,默认显示 **`--`**;表格上 **`cell-empty-text`** 可改全局占位;列上 **`emptyText`**(优先)或 **`defaultText`** 可单独改该列。
313
+
311
314
  | 字段 | 说明 |
312
315
  |------|------|
313
316
  | `type: 'slot'` + `slot` | 自定义列,父组件具名插槽。 |
314
317
  | `hidden` | 隐藏。 |
315
- | `valueGetter` / `textFormatter` / `defaultText` | 展示用(非 slot 列)。 |
318
+ | `valueGetter` | `(row, index) => any`,取展示用原始值(不填则用 `row[prop]`)。 |
319
+ | `textFormatter` | `(value, row, index) => any`,格式化后若为空仍走空值占位。 |
320
+ | `emptyText` / `defaultText` | 空值时显示文案;**`emptyText` 优先**;都不设则用 **`cell-empty-text`**(默认 `--`)。 |
316
321
 
317
322
  ---
318
323
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wkjp-list-page",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Vue2 + ElementUI CommonListPage component",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -40,7 +40,5 @@
40
40
  "webpack-cli": "^3.3.12",
41
41
  "webpack-dev-server": "^3.11.3"
42
42
  },
43
- "dependencies": {
44
- "qs": "6.11.2"
45
- }
43
+ "dependencies": {}
46
44
  }
@@ -13,6 +13,7 @@
13
13
  :style="{ width: `${item.width || 150}px` }"
14
14
  :maxlength="item.maxlength != null ? item.maxlength : ''"
15
15
  :height="item.height || '32px'"
16
+ :clearable="item.clearable !== false"
16
17
  @input="(v) => handleSearchInput(item, v)"
17
18
  @enter="emitQuery"
18
19
  />
@@ -57,14 +58,14 @@
57
58
  v-else-if="isDatePicker(item.type)"
58
59
  :value="resolveValue(item)"
59
60
  :type="item.type || 'date'"
60
- :unlink-panels="item.unlinkPanels !== false"
61
+ :unlink-panels="resolveSearchUnlinkPanels(item)"
61
62
  :editable="item.editable === true"
62
63
  :range-separator="item.rangeSeparator || '至'"
63
64
  :style="{ width: `${item.width || (item.type === 'daterange' || item.type === 'datetimerange' ? 300 : 150)}px` }"
64
65
  :label="floatingLabelFor(item)"
65
66
  :start-text="dateRangeStartText(item)"
66
67
  :end-text="dateRangeEndText(item)"
67
- :value-format="item.valueFormat || 'yyyy-MM-dd'"
68
+ :value-format="resolveSearchDateValueFormat(item)"
68
69
  :height="item.height || '32px'"
69
70
  @input="(val) => handleFieldChange(item, val)"
70
71
  />
@@ -220,6 +221,11 @@ export default {
220
221
  enableColumnConfig: { type: Boolean, default: false },
221
222
  /** 表头背景色,对应 Element `header-cell-style` 的 backgroundColor,可被 tableAttrs.header-cell-style 覆盖 */
222
223
  tableHeaderBg: { type: String, default: "#e5e9f2" },
224
+ /**
225
+ * 非 slot 列:值为 `null` / `undefined` / `''`,或 `textFormatter` 得到上述空值时的占位。
226
+ * 列上可用 **`emptyText`**(优先)或 **`defaultText`** 覆盖;仅写 `{ prop, label }` 时默认显示 `--`。
227
+ */
228
+ cellEmptyText: { type: String, default: "--" },
223
229
  /** 是否显示搜索区「查询」按钮;点击会 `fetchList({ resetPage: true })` */
224
230
  showQueryButton: { type: Boolean, default: true },
225
231
  /** 主查询按钮文案 */
@@ -402,7 +408,7 @@ export default {
402
408
  return col && ["selection", "index", "expand"].indexOf(col.type) !== -1;
403
409
  },
404
410
  tableColumnBind(col) {
405
- var omit = ["valueGetter", "textFormatter", "defaultText", "hidden", "_visible", "_configKey"];
411
+ var omit = ["valueGetter", "textFormatter", "defaultText", "emptyText", "hidden", "_visible", "_configKey"];
406
412
  var rest = {};
407
413
  Object.keys(col || {}).forEach(function (k) {
408
414
  if (omit.indexOf(k) === -1) rest[k] = col[k];
@@ -494,23 +500,54 @@ export default {
494
500
  }
495
501
  }
496
502
  if (typeof item.onChange === "function") item.onChange(value);
497
- if (item.autoQuery !== false) {
503
+ if (item.autoQuery === true) {
498
504
  this.fetchList({ resetPage: true });
499
505
  }
500
506
  },
501
507
  isDatePicker(type) {
502
508
  return ["date", "daterange", "datetime", "datetimerange", "month", "year"].includes(type);
503
509
  },
510
+ /**
511
+ * 有 `valueFormat`(非空字符串)则用之;否则 **`daterange` 与 `date` 等** 默认 **`yyyy-MM-dd`**(与历史 `item.valueFormat || 'yyyy-MM-dd'` 一致)。
512
+ */
513
+ resolveSearchDateValueFormat(item) {
514
+ if (!item) return "yyyy-MM-dd";
515
+ const vf = item.valueFormat;
516
+ if (vf != null && String(vf).trim() !== "") return vf;
517
+ return "yyyy-MM-dd";
518
+ },
519
+ /**
520
+ * 对应 Element `unlink-panels`:**默认 `true`**(左右面板月份不联动);仅当 **`unlinkPanels: false`** 时为 `false`。
521
+ */
522
+ resolveSearchUnlinkPanels(item) {
523
+ return item.unlinkPanels !== false;
524
+ },
525
+ isCellDisplayEmpty(display) {
526
+ return display === null || display === undefined || display === "";
527
+ },
504
528
  resolveCellText(col, row, index) {
505
- const value = typeof col.valueGetter === "function" ? col.valueGetter(row, index) : row[col.prop];
506
- if (typeof col.textFormatter === "function") return col.textFormatter(value, row, index);
507
- if (value === null || value === undefined || value === "") return col.defaultText !== undefined ? col.defaultText : "--";
508
- return value;
529
+ const raw = typeof col.valueGetter === "function" ? col.valueGetter(row, index) : row[col.prop];
530
+ let display;
531
+ if (typeof col.textFormatter === "function") {
532
+ display = col.textFormatter(raw, row, index);
533
+ } else {
534
+ display = raw;
535
+ }
536
+ if (this.isCellDisplayEmpty(display)) {
537
+ if (col.emptyText !== undefined && col.emptyText !== null) return col.emptyText;
538
+ if (col.defaultText !== undefined && col.defaultText !== null) return col.defaultText;
539
+ return this.cellEmptyText;
540
+ }
541
+ return display;
509
542
  },
510
543
  omitSlotType(col) {
511
544
  const rest = Object.assign({}, col);
512
545
  delete rest.type;
513
546
  delete rest.slot;
547
+ delete rest.valueGetter;
548
+ delete rest.textFormatter;
549
+ delete rest.defaultText;
550
+ delete rest.emptyText;
514
551
  return rest;
515
552
  },
516
553
  handleSizeChange(size) {
@@ -18,7 +18,7 @@
18
18
  </template>
19
19
 
20
20
  <script>
21
- import qs from "qs";
21
+ import { stringifyExportQuerys } from "../utils/stringifyExportQuerys";
22
22
  import { createDownLoadLink } from "../utils/createDownLoadLink";
23
23
 
24
24
  export default {
@@ -163,8 +163,7 @@ export default {
163
163
  /* ignore */
164
164
  }
165
165
  _export.link =
166
- `${_pathPrefix}?${qs.stringify(_parameters, {
167
- indices: false,
166
+ `${_pathPrefix}?${stringifyExportQuerys(_parameters, {
168
167
  skipNulls: true,
169
168
  })}` + (auth ? `&authorization=${auth}` : "");
170
169
  }
@@ -16,8 +16,8 @@
16
16
  :type="type"
17
17
  :value-format="valueFormat"
18
18
  :placeholder="isRange ? undefined : singlePlaceholder"
19
- :start-placeholder="isRange ? rangePh : undefined"
20
- :end-placeholder="isRange ? rangePh : undefined"
19
+ :start-placeholder="nativeStartPlaceholder"
20
+ :end-placeholder="nativeEndPlaceholder"
21
21
  class="floating-date-picker-inner"
22
22
  :style="editorStyle"
23
23
  @input="onInput"
@@ -70,8 +70,25 @@ export default {
70
70
  isRange() {
71
71
  return RANGE_TYPES.indexOf(this.type) !== -1;
72
72
  },
73
- rangePh() {
74
- return " ";
73
+ /** 与 Element `start-placeholder` 对齐;未单独传 attrs 时用 `startText`(默认「开始日期」) */
74
+ nativeStartPlaceholder() {
75
+ if (!this.isRange) return undefined;
76
+ const fromAttr =
77
+ this.$attrs.startPlaceholder !== undefined
78
+ ? this.$attrs.startPlaceholder
79
+ : this.$attrs["start-placeholder"];
80
+ if (fromAttr != null && String(fromAttr).trim() !== "") return fromAttr;
81
+ const t = (this.startText || "").trim();
82
+ return t || "开始日期";
83
+ },
84
+ /** 与 Element `end-placeholder` 对齐;未单独传 attrs 时用 `endText`(默认「结束日期」) */
85
+ nativeEndPlaceholder() {
86
+ if (!this.isRange) return undefined;
87
+ const fromAttr =
88
+ this.$attrs.endPlaceholder !== undefined ? this.$attrs.endPlaceholder : this.$attrs["end-placeholder"];
89
+ if (fromAttr != null && String(fromAttr).trim() !== "") return fromAttr;
90
+ const t = (this.endText || "").trim();
91
+ return t || "结束日期";
75
92
  },
76
93
  singlePlaceholder() {
77
94
  return " ";
@@ -1,7 +1,11 @@
1
1
  <template>
2
2
  <div
3
3
  class="floating-label-wrap"
4
- :class="{ 'is-focused': isFocused, 'has-value': hasValue }"
4
+ :class="{
5
+ 'is-focused': isFocused,
6
+ 'has-value': hasValue,
7
+ 'has-clear-btn': clearable && hasValue
8
+ }"
5
9
  >
6
10
  <input
7
11
  ref="input"
@@ -16,6 +20,17 @@
16
20
  @blur="handleBlur"
17
21
  @keyup.enter="$emit('enter')"
18
22
  />
23
+ <button
24
+ v-if="clearable && hasValue"
25
+ type="button"
26
+ class="floating-input__clear"
27
+ tabindex="-1"
28
+ aria-label="清除"
29
+ @mousedown.prevent
30
+ @click="clearInput"
31
+ >
32
+ <i class="el-icon-circle-close"></i>
33
+ </button>
19
34
  <label class="floating-label">{{ label }}</label>
20
35
  </div>
21
36
  </template>
@@ -44,6 +59,11 @@ export default {
44
59
  type: [String, Number],
45
60
  default: "32px",
46
61
  },
62
+ /** 为 true 且有内容时显示清除按钮;与 `searchOption` 里 `input` 默认 `clearable: true` 对齐 */
63
+ clearable: {
64
+ type: Boolean,
65
+ default: true,
66
+ },
47
67
  },
48
68
  data() {
49
69
  return {
@@ -73,6 +93,13 @@ export default {
73
93
  handleBlur() {
74
94
  this.isFocused = false;
75
95
  },
96
+ clearInput() {
97
+ this.hasValue = false;
98
+ this.$emit("input", "");
99
+ this.$nextTick(() => {
100
+ if (this.$refs.input) this.$refs.input.focus();
101
+ });
102
+ },
76
103
  checkValue() {
77
104
  this.hasValue = !!this.value;
78
105
  },
@@ -142,6 +169,39 @@ export default {
142
169
  font-family: inherit;
143
170
  }
144
171
 
172
+ .floating-label-wrap.has-clear-btn .text-box {
173
+ padding-right: 30px;
174
+ }
175
+
176
+ .floating-input__clear {
177
+ position: absolute;
178
+ right: 8px;
179
+ top: 50%;
180
+ transform: translateY(-50%);
181
+ z-index: 3;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ width: 22px;
186
+ height: 22px;
187
+ padding: 0;
188
+ margin: 0;
189
+ border: none;
190
+ background: transparent;
191
+ color: #c0c4cc;
192
+ cursor: pointer;
193
+ border-radius: 50%;
194
+ line-height: 1;
195
+ }
196
+
197
+ .floating-input__clear:hover {
198
+ color: #909399;
199
+ }
200
+
201
+ .floating-input__clear i {
202
+ font-size: 14px;
203
+ }
204
+
145
205
  .floating-label-wrap .text-box:hover {
146
206
  border-color: #c0c4cc;
147
207
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 导出链接查询串(替代 `qs.stringify`,避免旧构建链解析 `node_modules` 里 async generator 等新语法)。
3
+ * 行为对齐原用法:`indices: false`(数组同一 key 多次)、`skipNulls: true`。
4
+ *
5
+ * @param {Record<string, unknown>} obj
6
+ * @param {{ skipNulls?: boolean }} [options] 仅 `skipNulls`;数组为同一 key 多次 append(对齐 `qs` 的 `indices: false`)。
7
+ * @returns {string}
8
+ */
9
+ export function stringifyExportQuerys(obj, options) {
10
+ var opts = options || {};
11
+ var skipNulls = opts.skipNulls === true;
12
+ var sp = new URLSearchParams();
13
+ if (!obj || typeof obj !== "object") return "";
14
+ Object.keys(obj).forEach(function (key) {
15
+ var val = obj[key];
16
+ if (skipNulls && (val === null || val === undefined)) return;
17
+ if (Array.isArray(val)) {
18
+ val.forEach(function (item) {
19
+ if (skipNulls && (item === null || item === undefined)) return;
20
+ sp.append(key, item == null ? "" : String(item));
21
+ });
22
+ } else {
23
+ sp.append(key, val == null ? "" : String(val));
24
+ }
25
+ });
26
+ return sp.toString();
27
+ }