wkjp-list-page 1.0.28 → 1.0.30

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.28",
3
+ "version": "1.0.30",
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)"
@@ -134,6 +147,7 @@
134
147
  :column="col"
135
148
  :resolve-cell-text="resolveCellText"
136
149
  :column-bind="tableColumnBind"
150
+ :parent-scoped-slots="$scopedSlots"
137
151
  />
138
152
  <el-table-column
139
153
  v-else-if="col.type !== 'slot' && isBuiltinCellColumn(col)"
@@ -309,6 +323,7 @@
309
323
 
310
324
  <script>
311
325
  import FloatingInput from "./FloatingInput.vue";
326
+ import FloatingSearchInput from "./FloatingSearchInput.vue";
312
327
  import FloatingSelect from "./FloatingSelect.vue";
313
328
  import FloatingDatePicker from "./FloatingDatePicker.vue";
314
329
  import FloatingCascader from "./FloatingCascader.vue";
@@ -319,6 +334,7 @@ export default {
319
334
  name: "CommonListPage",
320
335
  components: {
321
336
  FloatingInput,
337
+ FloatingSearchInput,
322
338
  FloatingSelect,
323
339
  FloatingDatePicker,
324
340
  FloatingCascader,
@@ -327,6 +343,12 @@ export default {
327
343
  },
328
344
  props: {
329
345
  filters: { type: Object, required: true },
346
+ /**
347
+ * 重置基准:首次传入的默认查询条件(深拷贝后冻结,后续查询怎么改都不影响「重置」)。
348
+ * 建议传入页面 `data` 里单独保存的一份默认值对象(勿与 `filters` 共用同一引用)。
349
+ * 未传时:在实例首次初始化时对当时的 `filters` 做一次快照(`v-if` 反复挂载时仍会按每次挂载时的 filters 快照,多 tab 请务必传本项)。
350
+ */
351
+ initialFilters: { type: Object, default: null },
330
352
  searchOption: { type: Array, default: () => [] },
331
353
  columns: { type: Array, default: () => [] },
332
354
  /**
@@ -386,7 +408,7 @@ export default {
386
408
  showQueryButton: { type: Boolean, default: true },
387
409
  /** 主查询按钮文案 */
388
410
  queryButtonText: { type: String, default: "查询" },
389
- /** 是否显示「重置」按钮(在查询按钮右侧):将 `filters` 恢复为进入页面时的快照 */
411
+ /** 是否显示「重置」按钮(在查询按钮右侧):将 `filters` 恢复为 `initialFilters`(或首次快照) */
390
412
  showClearQueryButton: { type: Boolean, default: true },
391
413
  clearQueryButtonText: { type: String, default: "重置" },
392
414
  /**
@@ -463,9 +485,9 @@ export default {
463
485
  columnConfigPanelMaxHeight: null,
464
486
  configColumns: [],
465
487
  appliedColumns: [],
466
- /** 进入页时 filters 的快照,用于「重置」 */
488
+ /** 重置基准快照(深拷贝后冻结,仅初始化时写入一次) */
467
489
  initialFiltersSnapshot: null,
468
- initialFiltersCaptured: false
490
+ resetBaselineLocked: false
469
491
  };
470
492
  },
471
493
  computed: {
@@ -661,10 +683,25 @@ export default {
661
683
  this.innerPagination = Object.assign({}, this.innerPagination, v);
662
684
  }
663
685
  }
686
+ },
687
+ initialFilters: {
688
+ immediate: true,
689
+ handler(val) {
690
+ if (this.resetBaselineLocked) return;
691
+ if (val && typeof val === "object") {
692
+ this.lockResetBaseline(val);
693
+ }
694
+ }
664
695
  }
665
696
  },
666
697
  created() {
667
- this.captureInitialFilters();
698
+ if (!this.resetBaselineLocked) {
699
+ const baseline =
700
+ this.initialFilters && typeof this.initialFilters === "object"
701
+ ? this.initialFilters
702
+ : this.filters;
703
+ this.lockResetBaseline(baseline);
704
+ }
668
705
  this.applyInnerPaginationConfig();
669
706
  if (this.fetchOnCreated) {
670
707
  this.fetchList();
@@ -775,25 +812,55 @@ export default {
775
812
  const id = item && (item.slot || item.prop || item.type || "");
776
813
  return "search-" + id + "-" + index;
777
814
  },
815
+ /** `type: 'inputSearch'` 或 `type: 'input'` 且 `appendSearch: true` */
816
+ isSearchInputWithAppend(item) {
817
+ if (!item) return false;
818
+ if (item.type === "inputSearch") return true;
819
+ return item.type === "input" && item.appendSearch === true;
820
+ },
778
821
  emitQuery() {
779
822
  this.$emit("query");
780
823
  },
781
824
  handleQueryClick() {
782
825
  this.fetchList({ resetPage: true });
783
826
  },
784
- captureInitialFilters() {
785
- if (this.initialFiltersCaptured || !this.filters) return;
827
+ /**
828
+ * baseline 深拷贝为重置基准,仅首次调用生效;之后 filters 怎么变都不更新快照。
829
+ * @param {Object} baseline 优先使用 `initialFilters`,否则为首次 `filters`
830
+ */
831
+ lockResetBaseline(baseline) {
832
+ if (this.resetBaselineLocked || !this.filters) return;
833
+ if (!baseline || typeof baseline !== "object") return;
786
834
  try {
787
835
  const snap = {};
788
- Object.keys(this.filters).forEach((key) => {
789
- snap[key] = this.cloneFilterValue(this.filters[key]);
836
+ const keys = {};
837
+ Object.keys(this.filters).forEach(function (k) {
838
+ keys[k] = true;
839
+ });
840
+ Object.keys(baseline).forEach(function (k) {
841
+ keys[k] = true;
842
+ });
843
+ Object.keys(keys).forEach((key) => {
844
+ const val = Object.prototype.hasOwnProperty.call(baseline, key)
845
+ ? baseline[key]
846
+ : this.filters[key];
847
+ snap[key] = this.cloneFilterValue(val);
790
848
  });
791
849
  this.initialFiltersSnapshot = snap;
792
- this.initialFiltersCaptured = true;
850
+ this.resetBaselineLocked = true;
793
851
  } catch (e) {
794
852
  this.initialFiltersSnapshot = null;
795
853
  }
796
854
  },
855
+ /** @deprecated 请使用 lockResetBaseline */
856
+ captureInitialFilters() {
857
+ if (this.resetBaselineLocked) return;
858
+ const baseline =
859
+ this.initialFilters && typeof this.initialFilters === "object"
860
+ ? this.initialFilters
861
+ : this.filters;
862
+ this.lockResetBaseline(baseline);
863
+ },
797
864
  cloneFilterValue(val) {
798
865
  if (val === null || val === undefined) return val;
799
866
  if (typeof val !== "object") return val;
@@ -1803,6 +1870,25 @@ export default {
1803
1870
  color: #409eff;
1804
1871
  }
1805
1872
 
1873
+ .common-list-page /deep/ .el-button--primary.is-disabled,
1874
+ .common-list-page /deep/ .el-button--primary.is-disabled:hover,
1875
+ .common-list-page /deep/ .el-button--primary.is-disabled:focus,
1876
+ .common-list-page /deep/ .el-button--primary:disabled {
1877
+ background-color: #a0cfff;
1878
+ border-color: #a0cfff;
1879
+ color: #fff;
1880
+ cursor: not-allowed;
1881
+ }
1882
+
1883
+ .common-list-page /deep/ .el-button--primary.is-plain.is-disabled,
1884
+ .common-list-page /deep/ .el-button--primary.is-plain.is-disabled:hover,
1885
+ .common-list-page /deep/ .el-button--primary.is-plain:disabled {
1886
+ background-color: #ecf5ff;
1887
+ border-color: #ebeef5;
1888
+ color: #a0cfff;
1889
+ cursor: not-allowed;
1890
+ }
1891
+
1806
1892
  /* ---------- 列表表格(极细外框兜底:空数据区不塌陷、无粗网格线) ---------- */
1807
1893
  .common-list-page /deep/ .el-table {
1808
1894
  overflow: hidden;
@@ -1911,6 +1997,28 @@ export default {
1911
1997
  color: #66b1ff;
1912
1998
  }
1913
1999
 
2000
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled,
2001
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled:hover,
2002
+ .common-list-page /deep/ .el-table .cell .el-button--text.is-disabled:focus,
2003
+ .common-list-page /deep/ .el-table .cell .el-button--text:disabled {
2004
+ color: #c0c4cc !important;
2005
+ cursor: not-allowed;
2006
+ }
2007
+
2008
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger.is-disabled,
2009
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger.is-disabled:hover,
2010
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--danger:disabled {
2011
+ color: #fab6b6 !important;
2012
+ cursor: not-allowed;
2013
+ }
2014
+
2015
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning.is-disabled,
2016
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning.is-disabled:hover,
2017
+ .common-list-page /deep/ .el-table .cell .el-button--text.el-button--warning:disabled {
2018
+ color: #f3d19e !important;
2019
+ cursor: not-allowed;
2020
+ }
2021
+
1914
2022
  .common-list-page /deep/ .el-pagination {
1915
2023
  margin-top: 14px;
1916
2024
  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;