wkjp-list-page 1.0.27 → 1.0.29

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": "wkjp-list-page",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Vue2 + ElementUI CommonListPage component",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -6,6 +6,19 @@
6
6
  :key="searchItemKey(item, index)"
7
7
  >
8
8
  <slot v-if="item.type === 'slot'" :name="item.slot" :item="item" :filters="filters"></slot>
9
+ <floating-search-input
10
+ v-else-if="isSearchInputWithAppend(item)"
11
+ :label="floatingLabelFor(item)"
12
+ :value="resolveInputString(item)"
13
+ :style="{ width: `${item.width || 150}px` }"
14
+ :maxlength="item.maxlength != null ? item.maxlength : ''"
15
+ :height="item.height || '32px'"
16
+ :clearable="item.clearable !== false"
17
+ :search-icon="item.searchIcon || 'el-icon-search'"
18
+ :search-button-title="item.searchButtonTitle || '查询'"
19
+ @input="(v) => handleSearchInput(item, v)"
20
+ @search="emitQuery"
21
+ />
9
22
  <floating-input
10
23
  v-else-if="item.type === 'input'"
11
24
  :label="floatingLabelFor(item)"
@@ -128,6 +141,14 @@
128
141
  <slot :name="col.slot" :row="scope.row" :scope="scope"></slot>
129
142
  </template>
130
143
  </el-table-column>
144
+ <nested-table-column
145
+ v-else-if="col.children && col.children.length"
146
+ :key="'col-nested-' + idx + '-' + (col.prop || col.label)"
147
+ :column="col"
148
+ :resolve-cell-text="resolveCellText"
149
+ :column-bind="tableColumnBind"
150
+ :parent-scoped-slots="$scopedSlots"
151
+ />
131
152
  <el-table-column
132
153
  v-else-if="col.type !== 'slot' && isBuiltinCellColumn(col)"
133
154
  :key="'col-builtin-' + idx + '-' + col.type"
@@ -302,19 +323,23 @@
302
323
 
303
324
  <script>
304
325
  import FloatingInput from "./FloatingInput.vue";
326
+ import FloatingSearchInput from "./FloatingSearchInput.vue";
305
327
  import FloatingSelect from "./FloatingSelect.vue";
306
328
  import FloatingDatePicker from "./FloatingDatePicker.vue";
307
329
  import FloatingCascader from "./FloatingCascader.vue";
308
330
  import ExportFile from "./ExportFile.vue";
331
+ import NestedTableColumn from "./NestedTableColumn.vue";
309
332
 
310
333
  export default {
311
334
  name: "CommonListPage",
312
335
  components: {
313
336
  FloatingInput,
337
+ FloatingSearchInput,
314
338
  FloatingSelect,
315
339
  FloatingDatePicker,
316
340
  FloatingCascader,
317
- ExportFile
341
+ ExportFile,
342
+ NestedTableColumn,
318
343
  },
319
344
  props: {
320
345
  filters: { type: Object, required: true },
@@ -689,6 +714,13 @@ export default {
689
714
  /** 表头 + 当前页数据中最长展示文本的字符数(slot 列仅表头) */
690
715
  getColumnMaxContentLength(col) {
691
716
  var labelLen = this.textDisplayLength((col && (col.label || col.prop || col.slot)) || "");
717
+ if (col && col.children && col.children.length) {
718
+ var nestedMax = labelLen;
719
+ for (var c = 0; c < col.children.length; c++) {
720
+ nestedMax = Math.max(nestedMax, this.getColumnMaxContentLength(col.children[c]));
721
+ }
722
+ return nestedMax;
723
+ }
692
724
  if (!col || col.type === "slot" || this.isBuiltinCellColumn(col)) {
693
725
  return labelLen;
694
726
  }
@@ -702,6 +734,12 @@ export default {
702
734
  },
703
735
  resolveAutoColumnLayout(col) {
704
736
  if (!col) return {};
737
+ if (col.children && col.children.length) {
738
+ var nestedLayout = {};
739
+ if (col.width != null && col.width !== "") nestedLayout.width = col.width;
740
+ if (col.minWidth != null && col.minWidth !== "") nestedLayout.minWidth = col.minWidth;
741
+ return nestedLayout;
742
+ }
705
743
  var hasWidth = col.width != null && col.width !== "";
706
744
  var hasMinWidth = col.minWidth != null && col.minWidth !== "";
707
745
  if (hasWidth || hasMinWidth) {
@@ -733,7 +771,16 @@ export default {
733
771
  return layout;
734
772
  },
735
773
  tableColumnBind(col) {
736
- var omit = ["valueGetter", "textFormatter", "defaultText", "emptyText", "hidden", "_visible", "_configKey"];
774
+ var omit = [
775
+ "valueGetter",
776
+ "textFormatter",
777
+ "defaultText",
778
+ "emptyText",
779
+ "hidden",
780
+ "_visible",
781
+ "_configKey",
782
+ "children",
783
+ ];
737
784
  var rest = {};
738
785
  Object.keys(col || {}).forEach(function (k) {
739
786
  if (omit.indexOf(k) === -1) rest[k] = col[k];
@@ -744,6 +791,12 @@ export default {
744
791
  const id = item && (item.slot || item.prop || item.type || "");
745
792
  return "search-" + id + "-" + index;
746
793
  },
794
+ /** `type: 'inputSearch'` 或 `type: 'input'` 且 `appendSearch: true` */
795
+ isSearchInputWithAppend(item) {
796
+ if (!item) return false;
797
+ if (item.type === "inputSearch") return true;
798
+ return item.type === "input" && item.appendSearch === true;
799
+ },
747
800
  emitQuery() {
748
801
  this.$emit("query");
749
802
  },
@@ -1772,6 +1825,25 @@ export default {
1772
1825
  color: #409eff;
1773
1826
  }
1774
1827
 
1828
+ .common-list-page /deep/ .el-button--primary.is-disabled,
1829
+ .common-list-page /deep/ .el-button--primary.is-disabled:hover,
1830
+ .common-list-page /deep/ .el-button--primary.is-disabled:focus,
1831
+ .common-list-page /deep/ .el-button--primary:disabled {
1832
+ background-color: #a0cfff;
1833
+ border-color: #a0cfff;
1834
+ color: #fff;
1835
+ cursor: not-allowed;
1836
+ }
1837
+
1838
+ .common-list-page /deep/ .el-button--primary.is-plain.is-disabled,
1839
+ .common-list-page /deep/ .el-button--primary.is-plain.is-disabled:hover,
1840
+ .common-list-page /deep/ .el-button--primary.is-plain:disabled {
1841
+ background-color: #ecf5ff;
1842
+ border-color: #ebeef5;
1843
+ color: #a0cfff;
1844
+ cursor: not-allowed;
1845
+ }
1846
+
1775
1847
  /* ---------- 列表表格(极细外框兜底:空数据区不塌陷、无粗网格线) ---------- */
1776
1848
  .common-list-page /deep/ .el-table {
1777
1849
  overflow: hidden;
@@ -1880,6 +1952,28 @@ export default {
1880
1952
  color: #66b1ff;
1881
1953
  }
1882
1954
 
1955
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled,
1956
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled:hover,
1957
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled:focus,
1958
+ .common-list-page /deep/ .el-table .cell .el-button--text:disabled {
1959
+ color: #c0c4cc !important;
1960
+ cursor: not-allowed;
1961
+ }
1962
+
1963
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger.is-disabled,
1964
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger.is-disabled:hover,
1965
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger:disabled {
1966
+ color: #fab6b6 !important;
1967
+ cursor: not-allowed;
1968
+ }
1969
+
1970
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning.is-disabled,
1971
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning.is-disabled:hover,
1972
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning:disabled {
1973
+ color: #f3d19e !important;
1974
+ cursor: not-allowed;
1975
+ }
1976
+
1883
1977
  .common-list-page /deep/ .el-pagination {
1884
1978
  margin-top: 14px;
1885
1979
  padding-right: 4px;
@@ -0,0 +1,303 @@
1
+ <template>
2
+ <div
3
+ class="floating-label-wrap floating-search-input"
4
+ :class="{
5
+ 'is-focused': isFocused,
6
+ 'has-value': hasValue,
7
+ 'has-clear-btn': clearable && hasValue,
8
+ }"
9
+ >
10
+ <div class="floating-search-input__group">
11
+ <div class="floating-search-input__field">
12
+ <input
13
+ ref="input"
14
+ :type="type"
15
+ placeholder=" "
16
+ :value="value"
17
+ class="text-box"
18
+ :style="inputStyle"
19
+ :maxlength="maxlength"
20
+ @input="handleInput"
21
+ @focus="handleFocus"
22
+ @blur="handleBlur"
23
+ @keyup.enter="handleEnter"
24
+ />
25
+ <button
26
+ v-if="clearable && hasValue"
27
+ type="button"
28
+ class="floating-input__clear"
29
+ tabindex="-1"
30
+ aria-label="清除"
31
+ @mousedown.prevent
32
+ @click="clearInput"
33
+ >
34
+ <i class="el-icon-circle-close"></i>
35
+ </button>
36
+ </div>
37
+ <button
38
+ type="button"
39
+ class="floating-search-input__append"
40
+ tabindex="-1"
41
+ :title="searchButtonTitle"
42
+ @click="handleSearch"
43
+ >
44
+ <i :class="searchIcon"></i>
45
+ </button>
46
+ </div>
47
+ <label class="floating-label">{{ label }}</label>
48
+ </div>
49
+ </template>
50
+
51
+ <script>
52
+ export default {
53
+ name: "FloatingSearchInput",
54
+ props: {
55
+ type: {
56
+ type: String,
57
+ default: "text",
58
+ },
59
+ label: {
60
+ type: String,
61
+ required: true,
62
+ },
63
+ value: {
64
+ type: String,
65
+ default: "",
66
+ },
67
+ maxlength: {
68
+ type: [String, Number],
69
+ default: "",
70
+ },
71
+ height: {
72
+ type: [String, Number],
73
+ default: "32px",
74
+ },
75
+ clearable: {
76
+ type: Boolean,
77
+ default: true,
78
+ },
79
+ searchIcon: {
80
+ type: String,
81
+ default: "el-icon-search",
82
+ },
83
+ searchButtonTitle: {
84
+ type: String,
85
+ default: "查询",
86
+ },
87
+ },
88
+ data() {
89
+ return {
90
+ isFocused: false,
91
+ hasValue: false,
92
+ };
93
+ },
94
+ computed: {
95
+ inputStyle() {
96
+ const height =
97
+ typeof this.height === "number" ? `${this.height}px` : this.height;
98
+ return {
99
+ height: height,
100
+ lineHeight: height,
101
+ };
102
+ },
103
+ },
104
+ methods: {
105
+ handleInput(event) {
106
+ const value = event.target.value;
107
+ this.hasValue = !!value;
108
+ this.$emit("input", value);
109
+ },
110
+ handleFocus() {
111
+ this.isFocused = true;
112
+ },
113
+ handleBlur() {
114
+ this.isFocused = false;
115
+ },
116
+ clearInput() {
117
+ this.hasValue = false;
118
+ this.$emit("input", "");
119
+ this.$nextTick(() => {
120
+ if (this.$refs.input) this.$refs.input.focus();
121
+ });
122
+ },
123
+ handleEnter() {
124
+ this.$emit("enter");
125
+ },
126
+ handleSearch() {
127
+ this.$emit("search");
128
+ },
129
+ checkValue() {
130
+ this.hasValue = !!this.value;
131
+ },
132
+ },
133
+ mounted() {
134
+ this.checkValue();
135
+ },
136
+ watch: {
137
+ value() {
138
+ this.checkValue();
139
+ },
140
+ },
141
+ };
142
+ </script>
143
+
144
+ <style scoped>
145
+ .floating-search-input {
146
+ width: 100%;
147
+ }
148
+
149
+ .floating-search-input__group {
150
+ display: flex;
151
+ align-items: stretch;
152
+ width: 100%;
153
+ border-radius: 4px;
154
+ border: 1px solid #dcdfe6;
155
+ background: #fff;
156
+ box-sizing: border-box;
157
+ overflow: hidden;
158
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
159
+ }
160
+
161
+ .floating-search-input__field {
162
+ position: relative;
163
+ flex: 1;
164
+ min-width: 0;
165
+ }
166
+
167
+ .floating-search-input .text-box {
168
+ width: 100%;
169
+ padding: 0 12px;
170
+ border: none;
171
+ border-radius: 0;
172
+ background: transparent;
173
+ box-sizing: border-box;
174
+ font-size: 13px;
175
+ color: #303133;
176
+ font-family: inherit;
177
+ }
178
+
179
+ .floating-search-input.has-clear-btn .text-box {
180
+ padding-right: 30px;
181
+ }
182
+
183
+ /* 与普通 FloatingInput 清除按钮一致 */
184
+ .floating-search-input .floating-input__clear {
185
+ position: absolute;
186
+ right: 8px;
187
+ top: 50%;
188
+ transform: translateY(-50%);
189
+ z-index: 3;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ width: 22px;
194
+ height: 22px;
195
+ padding: 0;
196
+ margin: 0;
197
+ border: none;
198
+ background: transparent;
199
+ color: #c0c4cc;
200
+ cursor: pointer;
201
+ border-radius: 50%;
202
+ line-height: 1;
203
+ }
204
+
205
+ .floating-search-input .floating-input__clear:hover {
206
+ color: #909399;
207
+ }
208
+
209
+ .floating-search-input .floating-input__clear i {
210
+ font-size: 14px;
211
+ }
212
+
213
+ .floating-search-input__append {
214
+ flex-shrink: 0;
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: center;
218
+ width: 36px;
219
+ margin: 0;
220
+ padding: 0;
221
+ border: none;
222
+ border-left: 1px solid #dcdfe6;
223
+ background: #f5f7fa;
224
+ color: #909399;
225
+ cursor: pointer;
226
+ line-height: 1;
227
+ }
228
+
229
+ .floating-search-input__append:hover {
230
+ color: #409eff;
231
+ background: #ecf5ff;
232
+ }
233
+
234
+ .floating-search-input__append i {
235
+ font-size: 14px;
236
+ }
237
+
238
+ .floating-search-input > .floating-label {
239
+ position: absolute;
240
+ left: 12px;
241
+ top: 50%;
242
+ transform: translateY(-50%);
243
+ font-size: 13px;
244
+ font-weight: 400;
245
+ color: #909399;
246
+ pointer-events: none;
247
+ transition: color 0.2s ease, transform 0.2s ease, top 0.2s ease,
248
+ left 0.2s ease, font-size 0.2s ease, background 0.2s ease,
249
+ padding 0.2s ease, box-shadow 0.2s ease;
250
+ background: transparent;
251
+ line-height: 1.2;
252
+ z-index: 4;
253
+ white-space: nowrap;
254
+ max-width: calc(100% - 52px);
255
+ overflow: hidden;
256
+ text-overflow: ellipsis;
257
+ }
258
+
259
+ .floating-search-input.is-focused > .floating-label,
260
+ .floating-search-input.has-value > .floating-label {
261
+ top: 0;
262
+ left: 10px;
263
+ transform: translateY(-50%);
264
+ font-size: 12px;
265
+ font-weight: 500;
266
+ color: #409eff;
267
+ padding: 0 6px;
268
+ background: #fff;
269
+ border-radius: 4px;
270
+ box-shadow: 0 0 0 1px #fff;
271
+ }
272
+
273
+ .floating-search-input__group:hover {
274
+ border-color: #c0c4cc;
275
+ }
276
+
277
+ .floating-search-input.is-focused .floating-search-input__group {
278
+ border-color: #409eff;
279
+ box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.18);
280
+ }
281
+
282
+ .floating-search-input .text-box:focus {
283
+ outline: none;
284
+ }
285
+ </style>
286
+
287
+ <style>
288
+ .el-form-item.is-error .floating-search-input.is-focused .floating-label,
289
+ .el-form-item.is-error .floating-search-input.has-value .floating-label {
290
+ color: #f56c6c !important;
291
+ box-shadow: 0 0 0 1px #fff;
292
+ }
293
+
294
+ .el-form-item.is-error .floating-search-input .floating-search-input__group {
295
+ border-color: #f56c6c !important;
296
+ box-shadow: none !important;
297
+ }
298
+
299
+ .el-form-item.is-error .floating-search-input.is-focused .floating-search-input__group {
300
+ border-color: #f56c6c !important;
301
+ box-shadow: 0 0 0 2px rgba(245, 108, 108, 0.2) !important;
302
+ }
303
+ </style>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="floating-label-wrap">
3
3
  <el-select
4
- :value="value"
4
+ :value="selectValue"
5
5
  v-bind="$attrs"
6
6
  :multiple="multiple"
7
7
  :clearable="clearable"
@@ -53,6 +53,19 @@ export default {
53
53
  };
54
54
  },
55
55
  computed: {
56
+ selectValue() {
57
+ if (Array.isArray(this.value)) {
58
+ return this.value.length ? this.value : undefined;
59
+ }
60
+ if (
61
+ this.value === "" ||
62
+ this.value === null ||
63
+ this.value === undefined
64
+ ) {
65
+ return undefined;
66
+ }
67
+ return this.value;
68
+ },
56
69
  hasValue() {
57
70
  if (Array.isArray(this.value)) {
58
71
  return this.value.length > 0;
@@ -1,34 +1,41 @@
1
1
  <script>
2
2
  /**
3
- * 递归渲染带 children 的列配置(多级表头)
4
- * 由 CommonListPage 在检测到 columns[].children 时使用
3
+ * 递归渲染 el-table-column,支持 columns[].children 多级表头。
5
4
  */
6
5
  export default {
7
6
  name: "NestedTableColumn",
8
7
  props: {
9
8
  column: { type: Object, required: true },
10
- /** (col, row, rowIndex) => 展示文案,与 CommonListPage.resolveCellText 一致 */
11
- formatCell: { type: Function, default: null },
12
- /** (col) => 传给 el-table-column 的绑定对象 */
9
+ resolveCellText: { type: Function, default: null },
13
10
  columnBind: { type: Function, default: null },
14
11
  },
15
12
  render(h) {
16
13
  const col = this.column;
17
14
  const slots = this.$scopedSlots;
18
- const bind = this.columnBind;
19
- const formatCell = this.formatCell;
15
+ const resolveCellText = this.resolveCellText;
16
+ const columnBind = this.columnBind;
20
17
 
21
18
  if (col.children && col.children.length) {
19
+ const parentBind =
20
+ typeof columnBind === "function" ? columnBind(col) || {} : {};
21
+ const parentProps = {
22
+ label: parentBind.label != null ? parentBind.label : col.label,
23
+ align: parentBind.align != null ? parentBind.align : col.align,
24
+ fixed: parentBind.fixed != null ? parentBind.fixed : col.fixed,
25
+ width: parentBind.width != null ? parentBind.width : col.width,
26
+ minWidth: parentBind.minWidth != null ? parentBind.minWidth : col.minWidth,
27
+ headerAlign: parentBind.headerAlign,
28
+ };
22
29
  return h(
23
30
  "el-table-column",
24
- { props: { label: col.label, align: col.align } },
31
+ { props: parentProps },
25
32
  col.children.map((child, idx) =>
26
33
  h("nested-table-column", {
27
34
  key: `${child.prop || child.label || "col"}-${idx}`,
28
35
  props: {
29
36
  column: child,
30
- formatCell,
31
- columnBind: bind,
37
+ resolveCellText,
38
+ columnBind,
32
39
  },
33
40
  scopedSlots: slots,
34
41
  }),
@@ -36,60 +43,31 @@ export default {
36
43
  );
37
44
  }
38
45
 
39
- if (col.type === "slot" && col.slot && slots[col.slot]) {
40
- const slotProps = bind ? bind(this.omitSlotMeta(col)) : this.omitSlotMeta(col);
46
+ const bind = typeof columnBind === "function" ? columnBind(col) || {} : {};
47
+ const columnProps = Object.assign({}, bind);
48
+ delete columnProps.children;
49
+
50
+ if (col.slot && slots[col.slot]) {
41
51
  return h("el-table-column", {
42
- props: slotProps,
52
+ props: columnProps,
43
53
  scopedSlots: {
44
54
  default: (scope) => slots[col.slot](scope),
45
55
  },
46
56
  });
47
57
  }
48
58
 
49
- if (col.type === "selection" || col.type === "index" || col.type === "expand") {
50
- return h("el-table-column", {
51
- props: bind ? bind(col) : col,
52
- });
53
- }
54
-
55
- const columnProps = bind ? bind(col) : this.leafColumnProps(col);
56
-
57
- if (typeof col.formatter === "function") {
58
- columnProps.formatter = col.formatter;
59
- return h("el-table-column", { props: columnProps });
60
- }
61
-
62
59
  return h("el-table-column", {
63
60
  props: columnProps,
64
61
  scopedSlots: {
65
62
  default: (scope) => {
66
63
  const text =
67
- typeof formatCell === "function"
68
- ? formatCell(col, scope.row, scope.$index)
64
+ typeof resolveCellText === "function"
65
+ ? resolveCellText(col, scope.row, scope.$index)
69
66
  : scope.row[col.prop];
70
67
  return h("span", String(text == null ? "" : text));
71
68
  },
72
69
  },
73
70
  });
74
71
  },
75
- methods: {
76
- omitSlotMeta(col) {
77
- const rest = Object.assign({}, col);
78
- delete rest.slot;
79
- delete rest.type;
80
- return rest;
81
- },
82
- leafColumnProps(col) {
83
- return {
84
- prop: col.prop,
85
- label: col.label,
86
- sortable: col.sortable,
87
- width: col.width,
88
- minWidth: col.minWidth,
89
- align: col.align,
90
- showOverflowTooltip: col.showOverflowTooltip,
91
- };
92
- },
93
- },
94
72
  };
95
73
  </script>