wkjp-list-page 1.0.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/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # wkjp-common-list-page
2
+
3
+ 基于 Vue2 + ElementUI 的通用列表组件,支持:
4
+ - 配置化搜索区
5
+ - 配置化表格列(含 slot)
6
+ - 托管请求模式(api/buildParams/apiAdapter)
7
+ - 列配置弹窗(显隐 + 拖拽排序)
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ npm i wkjp-common-list-page
13
+ ```
14
+
15
+ ## 使用
16
+
17
+ ```js
18
+ import Vue from "vue";
19
+ import ElementUI from "element-ui";
20
+ import "element-ui/lib/theme-chalk/index.css";
21
+ import CommonListPage from "wkjp-common-list-page";
22
+
23
+ Vue.use(ElementUI);
24
+ Vue.use(CommonListPage);
25
+ ```
26
+
27
+ 或按需引入:
28
+
29
+ ```js
30
+ import { CommonListPage } from "wkjp-common-list-page";
31
+
32
+ export default {
33
+ components: { CommonListPage }
34
+ };
35
+ ```
36
+
37
+ ## 基础示例
38
+
39
+ ```vue
40
+ <template>
41
+ <common-list-page
42
+ :filters="filters"
43
+ :search-option="searchOption"
44
+ :columns="columns"
45
+ :api="apiFn"
46
+ :enable-column-config="true"
47
+ />
48
+ </template>
49
+ ```
50
+
51
+ ## 发布
52
+
53
+ 1. 修改 `package.json` 中 `name` 和 `version`
54
+ 2. 登录 npm:`npm login`
55
+ 3. 发布:`npm publish`
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "wkjp-list-page",
3
+ "version": "1.0.0",
4
+ "description": "Vue2 + ElementUI CommonListPage component",
5
+ "main": "src/index.js",
6
+ "module": "src/index.js",
7
+ "files": [
8
+ "src"
9
+ ],
10
+ "keywords": [
11
+ "vue2",
12
+ "element-ui",
13
+ "table",
14
+ "list"
15
+ ],
16
+ "peerDependencies": {
17
+ "vue": "^2.6.14",
18
+ "element-ui": "^2.15.14"
19
+ },
20
+ "license": "MIT"
21
+ }
@@ -0,0 +1,365 @@
1
+ <template>
2
+ <section>
3
+ <el-form inline :model="filters">
4
+ <el-form-item v-for="(item, index) in searchOption" :key="`search-${index}`">
5
+ <slot v-if="item.type === 'slot'" :name="item.slot" :item="item" :filters="filters"></slot>
6
+ <el-input
7
+ v-else-if="item.type === 'input'"
8
+ v-model="filters[item.prop]"
9
+ :style="{ width: `${item.width || 150}px` }"
10
+ :placeholder="item.placeholder || ''"
11
+ :clearable="item.clearable"
12
+ @keyup.enter.native="emitQuery"
13
+ ></el-input>
14
+ <el-select
15
+ v-else-if="item.type === 'select'"
16
+ :value="resolveValue(item)"
17
+ :multiple="item.multiple === true"
18
+ :clearable="item.clearable !== false"
19
+ :style="{ width: `${item.width || 150}px` }"
20
+ :placeholder="item.placeholder || ''"
21
+ @change="(val) => handleFieldChange(item, val)"
22
+ >
23
+ <el-option
24
+ v-for="option in item.options || []"
25
+ :key="`${item.prop}-${option.value}`"
26
+ :label="option.label"
27
+ :value="option.value"
28
+ ></el-option>
29
+ </el-select>
30
+ <el-date-picker
31
+ v-else-if="isDatePicker(item.type)"
32
+ :value="resolveValue(item)"
33
+ :type="item.type || 'date'"
34
+ :unlink-panels="item.unlinkPanels !== false"
35
+ :editable="item.editable === true"
36
+ :range-separator="item.rangeSeparator || '至'"
37
+ :style="{ width: `${item.width || (item.type === 'daterange' ? 220 : 150)}px` }"
38
+ :placeholder="item.placeholder || ''"
39
+ :end-placeholder="item.endPlaceholder || ''"
40
+ :start-placeholder="item.startPlaceholder || ''"
41
+ :value-format="item.valueFormat || 'yyyy-MM-dd'"
42
+ @change="(val) => handleFieldChange(item, val)"
43
+ ></el-date-picker>
44
+ </el-form-item>
45
+ <slot name="search-extra"></slot>
46
+ <el-form-item v-if="enableColumnConfig" style="float: right; margin-left: 12px">
47
+ <el-button type="primary" plain @click="openColumnConfig">配置表单</el-button>
48
+ </el-form-item>
49
+ </el-form>
50
+
51
+ <el-table :data="resolvedTableData" v-loading="resolvedLoading" v-bind="tableAttrs">
52
+ <template v-for="(col, idx) in visibleColumns">
53
+ <el-table-column
54
+ v-if="col.type !== 'slot'"
55
+ :key="`col-${idx}-${col.prop || col.label}`"
56
+ v-bind="col"
57
+ >
58
+ <template slot-scope="scope">
59
+ <span>{{ resolveCellText(col, scope.row, scope.$index) }}</span>
60
+ </template>
61
+ </el-table-column>
62
+ <el-table-column
63
+ v-else
64
+ :key="`slot-col-${idx}-${col.slot}`"
65
+ v-bind="omitSlotType(col)"
66
+ >
67
+ <template slot-scope="scope">
68
+ <slot :name="col.slot" :row="scope.row" :scope="scope"></slot>
69
+ </template>
70
+ </el-table-column>
71
+ </template>
72
+ </el-table>
73
+
74
+ <el-pagination
75
+ v-if="resolvedPagination"
76
+ background
77
+ :total="resolvedPagination.total"
78
+ :current-page="resolvedPagination.page"
79
+ :page-size="resolvedPagination.pageSize"
80
+ :page-sizes="resolvedPagination.pSizes || [10, 50, 100, 200]"
81
+ @size-change="handleSizeChange"
82
+ @current-change="handlePageChange"
83
+ layout="total, sizes, prev, pager, next, jumper"
84
+ ></el-pagination>
85
+
86
+ <el-dialog
87
+ title="配置表单"
88
+ :visible.sync="columnConfigVisible"
89
+ width="900px"
90
+ :close-on-click-modal="false"
91
+ >
92
+ <div class="column-config-desc">拖动调整列顺序,勾选控制是否显示</div>
93
+ <ul class="column-config-list">
94
+ <li
95
+ v-for="(col, index) in configColumns"
96
+ :key="col._configKey"
97
+ :draggable="!isFixedColumn(col)"
98
+ class="column-config-item"
99
+ :class="{ 'column-config-item--fixed': isFixedColumn(col) }"
100
+ @dragstart="onDragStart(index)"
101
+ @dragover.prevent
102
+ @drop="onDrop(index)"
103
+ >
104
+ <i class="el-icon-rank"></i>
105
+ <el-checkbox v-model="col._visible">{{ col.label || col.prop || col.slot }}</el-checkbox>
106
+ <el-tag v-if="isFixedColumn(col)" type="info" size="mini">固定</el-tag>
107
+ </li>
108
+ </ul>
109
+ <span slot="footer" class="dialog-footer">
110
+ <el-button @click="columnConfigVisible = false">取 消</el-button>
111
+ <el-button type="primary" @click="confirmColumnConfig">确 定</el-button>
112
+ </span>
113
+ </el-dialog>
114
+ </section>
115
+ </template>
116
+
117
+ <script>
118
+ export default {
119
+ name: "CommonListPage",
120
+ props: {
121
+ filters: { type: Object, required: true },
122
+ searchOption: { type: Array, default: () => [] },
123
+ columns: { type: Array, default: () => [] },
124
+ tableData: { type: Array, default: () => [] },
125
+ loading: { type: Boolean, default: false },
126
+ pagination: { type: Object, default: null },
127
+ tableAttrs: { type: Object, default: () => ({}) },
128
+ api: { type: Function, default: null },
129
+ buildParams: { type: Function, default: null },
130
+ apiAdapter: { type: Function, default: null },
131
+ autoRequest: { type: Boolean, default: true },
132
+ enableColumnConfig: { type: Boolean, default: false }
133
+ },
134
+ data() {
135
+ return {
136
+ innerLoading: false,
137
+ innerTableData: [],
138
+ innerPagination: {
139
+ total: 0,
140
+ page: 1,
141
+ pageSize: 10,
142
+ pSizes: [10, 50, 100, 200]
143
+ },
144
+ columnConfigVisible: false,
145
+ draggingIndex: -1,
146
+ configColumns: [],
147
+ appliedColumns: []
148
+ };
149
+ },
150
+ computed: {
151
+ visibleColumns() {
152
+ const source = this.enableColumnConfig ? this.appliedColumns : this.columns;
153
+ return source.filter((item) => item && item.hidden !== true && item._visible !== false);
154
+ },
155
+ useInnerApi() {
156
+ return typeof this.api === "function";
157
+ },
158
+ resolvedTableData() {
159
+ return this.useInnerApi ? this.innerTableData : this.tableData;
160
+ },
161
+ resolvedLoading() {
162
+ return this.useInnerApi ? this.innerLoading : this.loading;
163
+ },
164
+ resolvedPagination() {
165
+ return this.useInnerApi ? this.innerPagination : this.pagination;
166
+ }
167
+ },
168
+ watch: {
169
+ columns: {
170
+ immediate: true,
171
+ deep: true,
172
+ handler(cols) {
173
+ this.initColumnConfig(cols || []);
174
+ }
175
+ },
176
+ pagination: {
177
+ deep: true,
178
+ immediate: true,
179
+ handler(v) {
180
+ if (!this.useInnerApi || !v) return;
181
+ this.innerPagination = { ...this.innerPagination, ...v };
182
+ }
183
+ }
184
+ },
185
+ created() {
186
+ if (this.useInnerApi && this.autoRequest) this.fetchList();
187
+ },
188
+ methods: {
189
+ emitQuery() {
190
+ this.$emit("query");
191
+ },
192
+ resolveValue(item) {
193
+ if (Object.prototype.hasOwnProperty.call(item, "value")) return item.value;
194
+ return this.filters[item.prop];
195
+ },
196
+ handleFieldChange(item, value) {
197
+ if (item.prop) this.$set(this.filters, item.prop, value);
198
+ if (typeof item.onChange === "function") item.onChange(value);
199
+ if (item.autoQuery !== false) {
200
+ if (this.useInnerApi) this.fetchList({ resetPage: true });
201
+ else this.emitQuery();
202
+ }
203
+ },
204
+ isDatePicker(type) {
205
+ return ["date", "daterange", "datetime", "datetimerange", "month", "year"].includes(type);
206
+ },
207
+ resolveCellText(col, row, index) {
208
+ const value = typeof col.valueGetter === "function" ? col.valueGetter(row, index) : row[col.prop];
209
+ if (typeof col.textFormatter === "function") return col.textFormatter(value, row, index);
210
+ if (value === null || value === undefined || value === "") return col.defaultText !== undefined ? col.defaultText : "--";
211
+ return value;
212
+ },
213
+ omitSlotType(col) {
214
+ const { type, slot, ...rest } = col;
215
+ return rest;
216
+ },
217
+ handleSizeChange(size) {
218
+ if (this.useInnerApi) {
219
+ this.innerPagination.pageSize = size;
220
+ this.innerPagination.page = 1;
221
+ this.fetchList();
222
+ }
223
+ this.$emit("size-change", size);
224
+ },
225
+ handlePageChange(page) {
226
+ if (this.useInnerApi) {
227
+ this.innerPagination.page = page;
228
+ this.fetchList();
229
+ }
230
+ this.$emit("page-change", page);
231
+ },
232
+ buildRequestParams() {
233
+ const page = this.innerPagination.page;
234
+ const pageSize = this.innerPagination.pageSize;
235
+ if (typeof this.buildParams === "function") return this.buildParams(this.filters, { page, pageSize });
236
+ return {
237
+ ...this.filters,
238
+ currentPage: page,
239
+ pageSize
240
+ };
241
+ },
242
+ normalizeApiResult(res) {
243
+ if (typeof this.apiAdapter === "function") return this.apiAdapter(res) || { list: [], total: 0 };
244
+ if (res && res.object) {
245
+ return {
246
+ list: res.object.list || [],
247
+ total: res.object.num || 0
248
+ };
249
+ }
250
+ return { list: [], total: 0 };
251
+ },
252
+ fetchList({ resetPage = false } = {}) {
253
+ if (!this.useInnerApi) return Promise.resolve();
254
+ if (resetPage) this.innerPagination.page = 1;
255
+ const params = this.buildRequestParams();
256
+ this.innerLoading = true;
257
+ return this.api(params)
258
+ .then((res) => {
259
+ const { list, total } = this.normalizeApiResult(res);
260
+ this.innerTableData = list;
261
+ this.innerPagination.total = total;
262
+ this.$emit("fetched", {
263
+ list,
264
+ total,
265
+ page: this.innerPagination.page,
266
+ pageSize: this.innerPagination.pageSize,
267
+ raw: res
268
+ });
269
+ })
270
+ .finally(() => {
271
+ this.innerLoading = false;
272
+ });
273
+ },
274
+ buildConfigKey(col, index) {
275
+ return `${col.prop || col.slot || col.label || "col"}-${index}`;
276
+ },
277
+ initColumnConfig(cols) {
278
+ const mapped = cols.map((col, index) => ({
279
+ ...col,
280
+ _configKey: this.buildConfigKey(col, index),
281
+ _visible: col.hidden !== true
282
+ }));
283
+ const sorted = this.sortConfigColumns(mapped);
284
+ this.appliedColumns = sorted.map((col) => ({ ...col }));
285
+ this.configColumns = sorted.map((col) => ({ ...col }));
286
+ },
287
+ openColumnConfig() {
288
+ this.configColumns = this.sortConfigColumns(this.appliedColumns.map((col) => ({ ...col })));
289
+ this.columnConfigVisible = true;
290
+ },
291
+ confirmColumnConfig() {
292
+ this.appliedColumns = this.configColumns.map((col) => ({
293
+ ...col,
294
+ hidden: col._visible === false
295
+ }));
296
+ this.columnConfigVisible = false;
297
+ this.$emit("columns-change", this.appliedColumns);
298
+ },
299
+ onDragStart(index) {
300
+ if (this.isFixedColumn(this.configColumns[index])) return;
301
+ this.draggingIndex = index;
302
+ },
303
+ onDrop(targetIndex) {
304
+ if (this.draggingIndex < 0 || this.draggingIndex === targetIndex) return;
305
+ const target = this.configColumns[targetIndex];
306
+ const moving = this.configColumns[this.draggingIndex];
307
+ if (this.isFixedColumn(target) || this.isFixedColumn(moving)) {
308
+ this.draggingIndex = -1;
309
+ return;
310
+ }
311
+ const next = [...this.configColumns];
312
+ next[this.draggingIndex] = target;
313
+ next[targetIndex] = moving;
314
+ this.configColumns = next;
315
+ this.draggingIndex = -1;
316
+ },
317
+ isFixedColumn(col) {
318
+ return col && (col.fixed === "left" || col.fixed === "right" || col.configFixed === true);
319
+ },
320
+ isRightFixedColumn(col) {
321
+ return col && col.fixed === "right";
322
+ },
323
+ isLeftFixedColumn(col) {
324
+ return col && (col.fixed === "left" || (col.configFixed === true && col.fixed !== "right"));
325
+ },
326
+ sortConfigColumns(cols) {
327
+ const leftFixedCols = cols.filter((item) => this.isLeftFixedColumn(item));
328
+ const normalCols = cols.filter((item) => !this.isFixedColumn(item));
329
+ const rightFixedCols = cols.filter((item) => this.isRightFixedColumn(item));
330
+ return [...leftFixedCols, ...normalCols, ...rightFixedCols];
331
+ }
332
+ }
333
+ };
334
+ </script>
335
+
336
+ <style scoped>
337
+ .column-config-desc {
338
+ margin-bottom: 10px;
339
+ color: #909399;
340
+ }
341
+ .column-config-list {
342
+ max-height: 420px;
343
+ overflow: auto;
344
+ padding: 0;
345
+ margin: 0;
346
+ display: grid;
347
+ grid-template-columns: repeat(4, minmax(0, 1fr));
348
+ gap: 8px;
349
+ }
350
+ .column-config-item {
351
+ list-style: none;
352
+ display: flex;
353
+ align-items: center;
354
+ gap: 8px;
355
+ border: 1px solid #ebeef5;
356
+ border-radius: 4px;
357
+ padding: 8px 10px;
358
+ cursor: move;
359
+ min-width: 0;
360
+ }
361
+ .column-config-item--fixed {
362
+ background: #f5f7fa;
363
+ cursor: not-allowed;
364
+ }
365
+ </style>
package/src/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import CommonListPage from "./components/CommonListPage.vue";
2
+
3
+ CommonListPage.install = function install(Vue) {
4
+ Vue.component(CommonListPage.name, CommonListPage);
5
+ };
6
+
7
+ export { CommonListPage };
8
+ export default CommonListPage;