leisure-core 0.6.66 → 0.6.67

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/index.js CHANGED
@@ -46,6 +46,7 @@ import LeInputAdvanced from "./le-input-advanced/index.js";
46
46
  // import LeButtonAudit from "./le-button-audit/index.js";
47
47
  import LeInputValidate from "./le-input-validate/index.js";
48
48
  import LeDatePickerAuto from "./le-date-picker-auto/index.js";
49
+ import LeTableEdit from "./le-table-edit/index.js";
49
50
 
50
51
  const components = [
51
52
  LeArea,
@@ -90,6 +91,7 @@ const components = [
90
91
  // LeButtonAudit,
91
92
  LeInputValidate,
92
93
  LeDatePickerAuto,
94
+ LeTableEdit,
93
95
  ];
94
96
 
95
97
  const install = function (Vue) {
@@ -203,4 +205,5 @@ export default {
203
205
  leMixins,
204
206
  LeInputValidate,
205
207
  LeDatePickerAuto,
208
+ LeTableEdit,
206
209
  };
@@ -0,0 +1,6 @@
1
+ import LeTableEdit from "./src/main.vue";
2
+
3
+ LeTableEdit.install = function (Vue) {
4
+ Vue.component(LeTableEdit.name, LeTableEdit);
5
+ };
6
+ export default LeTableEdit;
@@ -0,0 +1,689 @@
1
+ <template>
2
+ <div class="editable-table">
3
+ <el-table
4
+ :data="internalTableData"
5
+ :border="border"
6
+ :stripe="stripe"
7
+ :size="size"
8
+ @cell-click="handleCellClick"
9
+ @cell-dblclick="handleCellDblClick"
10
+ style="width: 100%"
11
+ ref="tableRef"
12
+ >
13
+ <!-- 序号列 -->
14
+ <el-table-column
15
+ v-if="showIndex"
16
+ type="index"
17
+ :width="indexWidth"
18
+ :label="indexLabel"
19
+ align="center"
20
+ ></el-table-column>
21
+
22
+ <!-- 多选列 -->
23
+ <el-table-column
24
+ v-if="showSelection"
25
+ type="selection"
26
+ :width="selectionWidth"
27
+ align="center"
28
+ ></el-table-column>
29
+
30
+ <!-- 数据列 -->
31
+ <el-table-column
32
+ v-for="column in columns"
33
+ :key="column.prop"
34
+ :prop="column.prop"
35
+ :label="column.label"
36
+ :width="column.width"
37
+ :align="column.align || 'center'"
38
+ :min-width="column.minWidth"
39
+ :fixed="column.fixed"
40
+ >
41
+ <template slot-scope="scope">
42
+ <!-- 编辑状态 -->
43
+ <template v-if="isEditing(scope.$index, column.prop)">
44
+ <!-- 文本输入框 -->
45
+ <el-input
46
+ v-if="column.editType === 'input' || !column.editType"
47
+ v-model="scope.row[column.prop]"
48
+ :size="size"
49
+ @blur="handleInputBlur(scope.$index, column.prop)"
50
+ @keyup.enter.native="handleInputBlur(scope.$index, column.prop)"
51
+ ref="inputRef"
52
+ ></el-input>
53
+
54
+ <!-- 数字输入框 -->
55
+ <el-input-number
56
+ v-else-if="column.editType === 'number'"
57
+ v-model="scope.row[column.prop]"
58
+ :size="size"
59
+ :min="column.min || 0"
60
+ :max="column.max"
61
+ :step="column.step || 1"
62
+ @blur="handleInputBlur(scope.$index, column.prop)"
63
+ controls-position="right"
64
+ ></el-input-number>
65
+
66
+ <!-- 下拉选择器 -->
67
+ <el-select
68
+ v-else-if="column.editType === 'select'"
69
+ v-model="scope.row[column.prop]"
70
+ :size="size"
71
+ @change="handleInputBlur(scope.$index, column.prop)"
72
+ :placeholder="column.placeholder || '请选择'"
73
+ >
74
+ <el-option
75
+ v-for="item in column.options"
76
+ :key="item[column.valueKey]"
77
+ :label="item[column.labelKey]"
78
+ :value="item[column.valueKey]"
79
+ ></el-option>
80
+ </el-select>
81
+
82
+ <!-- 日期选择器 -->
83
+ <le-date-picker-auto
84
+ v-else-if="column.editType === 'date'"
85
+ v-model="scope.row[column.prop]"
86
+ :size="size"
87
+ :type="column.dateType || 'date'"
88
+ :format="'yyyy-MM-dd'"
89
+ :value-format="column.valueFormat || 'timestamp'"
90
+ @blur="handleInputBlur(scope.$index, column.prop)"
91
+ placeholder="选择日期"
92
+ ></le-date-picker-auto>
93
+
94
+ <!-- 开关 -->
95
+ <el-switch
96
+ v-else-if="column.editType === 'switch'"
97
+ v-model="scope.row[column.prop]"
98
+ @change="handleInputBlur(scope.$index, column.prop)"
99
+ :active-text="column.activeText"
100
+ :inactive-text="column.inactiveText"
101
+ ></el-switch>
102
+ </template>
103
+
104
+ <!-- 非编辑状态 -->
105
+ <template v-else>
106
+ <!-- 自定义渲染 -->
107
+ <template v-if="column.render">
108
+ <div v-html="column.render(scope.row, scope.$index)"></div>
109
+ </template>
110
+
111
+ <!-- 默认渲染 -->
112
+ <template v-else>
113
+ <span>{{ formatCellValue(scope.row[column.prop], column) }}</span>
114
+ </template>
115
+
116
+ <!-- 编辑图标 -->
117
+ <i
118
+ v-if="column.editable && editMode === 'icon'"
119
+ class="el-icon-edit edit-icon"
120
+ @click="startEdit(scope.$index, column.prop)"
121
+ ></i>
122
+ </template>
123
+ </template>
124
+ </el-table-column>
125
+
126
+ <!-- 操作列 -->
127
+ <el-table-column
128
+ v-if="showOperation"
129
+ :label="operationLabel"
130
+ :width="operationWidth"
131
+ :fixed="operationFixed"
132
+ align="center"
133
+ >
134
+ <template slot-scope="scope">
135
+ <!-- 编辑状态操作 -->
136
+ <template
137
+ v-if="editingRowIndex === scope.$index && editMode === 'button'"
138
+ >
139
+ <el-button
140
+ :size="size"
141
+ type="success"
142
+ @click="saveRow(scope.$index)"
143
+ >保存</el-button
144
+ >
145
+ <el-button :size="size" @click="cancelEdit(scope.$index)"
146
+ >取消</el-button
147
+ >
148
+ </template>
149
+
150
+ <!-- 非编辑状态操作 -->
151
+ <template v-else>
152
+ <el-button
153
+ v-if="showEditButton"
154
+ :size="size"
155
+ type="primary"
156
+ @click="startRowEdit(scope.$index)"
157
+ >编辑</el-button
158
+ >
159
+
160
+ <el-button
161
+ v-if="showDeleteButton"
162
+ :size="size"
163
+ type="danger"
164
+ @click="handleDelete(scope.$index, scope.row)"
165
+ >删除</el-button
166
+ >
167
+
168
+ <slot
169
+ name="operation"
170
+ :row="scope.row"
171
+ :index="scope.$index"
172
+ ></slot>
173
+ </template>
174
+ </template>
175
+ </el-table-column>
176
+ </el-table>
177
+
178
+ <!-- 分页 -->
179
+ <div class="pagination-wrapper" v-if="showPagination">
180
+ <el-pagination
181
+ @size-change="handleSizeChange"
182
+ @current-change="handleCurrentChange"
183
+ :current-page="currentPage"
184
+ :page-sizes="pageSizes"
185
+ :page-size="pageSize"
186
+ :layout="paginationLayout"
187
+ :total="total"
188
+ ></el-pagination>
189
+ </div>
190
+ </div>
191
+ </template>
192
+
193
+ <script>
194
+ export default {
195
+ name: "le-table-edit",
196
+ props: {
197
+ // 表格数据
198
+ data: {
199
+ type: Array,
200
+ default: () => [],
201
+ },
202
+ // 列配置
203
+ columns: {
204
+ type: Array,
205
+ default: () => [],
206
+ },
207
+ // 表格边框
208
+ border: {
209
+ type: Boolean,
210
+ default: true,
211
+ },
212
+ // 斑马纹
213
+ stripe: {
214
+ type: Boolean,
215
+ default: true,
216
+ },
217
+ // 尺寸
218
+ size: {
219
+ type: String,
220
+ default: "small",
221
+ },
222
+ // 显示序号列
223
+ showIndex: {
224
+ type: Boolean,
225
+ default: false,
226
+ },
227
+ // 序号列宽度
228
+ indexWidth: {
229
+ type: [Number, String],
230
+ default: 60,
231
+ },
232
+ // 序号列标题
233
+ indexLabel: {
234
+ type: String,
235
+ default: "序号",
236
+ },
237
+ // 显示多选列
238
+ showSelection: {
239
+ type: Boolean,
240
+ default: false,
241
+ },
242
+ // 多选列宽度
243
+ selectionWidth: {
244
+ type: [Number, String],
245
+ default: 60,
246
+ },
247
+ // 显示操作列
248
+ showOperation: {
249
+ type: Boolean,
250
+ default: true,
251
+ },
252
+ // 操作列标题
253
+ operationLabel: {
254
+ type: String,
255
+ default: "操作",
256
+ },
257
+ // 操作列宽度
258
+ operationWidth: {
259
+ type: [Number, String],
260
+ default: 180,
261
+ },
262
+ // 操作列固定
263
+ operationFixed: {
264
+ type: String,
265
+ default: "right",
266
+ },
267
+ // 显示编辑按钮
268
+ showEditButton: {
269
+ type: Boolean,
270
+ default: true,
271
+ },
272
+ // 显示删除按钮
273
+ showDeleteButton: {
274
+ type: Boolean,
275
+ default: true,
276
+ },
277
+ // 编辑模式: 'click' | 'dblclick' | 'icon' | 'button'
278
+ editMode: {
279
+ type: String,
280
+ default: "click",
281
+ },
282
+ // 是否同步数据到父组件
283
+ syncData: {
284
+ type: Boolean,
285
+ default: true,
286
+ },
287
+ // 分页配置
288
+ showPagination: {
289
+ type: Boolean,
290
+ default: false,
291
+ },
292
+ currentPage: {
293
+ type: Number,
294
+ default: 1,
295
+ },
296
+ pageSize: {
297
+ type: Number,
298
+ default: 10,
299
+ },
300
+ pageSizes: {
301
+ type: Array,
302
+ default: () => [10, 20, 50, 100],
303
+ },
304
+ total: {
305
+ type: Number,
306
+ default: 0,
307
+ },
308
+ paginationLayout: {
309
+ type: String,
310
+ default: "total, sizes, prev, pager, next, jumper",
311
+ },
312
+ },
313
+ data() {
314
+ return {
315
+ internalTableData: [],
316
+ editingCell: null, // 当前编辑的单元格 {rowIndex, prop}
317
+ editingRowIndex: -1, // 当前编辑的行索引(按钮模式)
318
+ originalData: {}, // 原始数据,用于取消编辑时恢复
319
+ };
320
+ },
321
+ watch: {
322
+ data: {
323
+ immediate: true,
324
+ deep: true,
325
+ handler(newVal) {
326
+ if (newVal && newVal.length > 0) {
327
+ this.internalTableData = JSON.parse(JSON.stringify(newVal));
328
+ } else {
329
+ this.internalTableData = [];
330
+ }
331
+ },
332
+ },
333
+ },
334
+ methods: {
335
+ // 判断单元格是否处于编辑状态
336
+ isEditing(rowIndex, prop) {
337
+ if (this.editMode === "button") {
338
+ return this.editingRowIndex === rowIndex;
339
+ } else {
340
+ return (
341
+ this.editingCell &&
342
+ this.editingCell.rowIndex === rowIndex &&
343
+ this.editingCell.prop === prop
344
+ );
345
+ }
346
+ },
347
+
348
+ // 开始编辑单元格
349
+ startEdit(rowIndex, prop) {
350
+ // 保存原始数据
351
+ this.originalData = JSON.parse(
352
+ JSON.stringify(this.internalTableData[rowIndex])
353
+ );
354
+
355
+ if (this.editMode === "button") {
356
+ this.editingRowIndex = rowIndex;
357
+ } else {
358
+ this.editingCell = { rowIndex, prop };
359
+
360
+ // 聚焦输入框
361
+ this.$nextTick(() => {
362
+ const input = this.$refs.inputRef;
363
+ if (input && input.focus) {
364
+ input.focus();
365
+ }
366
+ });
367
+ }
368
+ },
369
+
370
+ // 开始编辑整行(按钮模式)
371
+ startRowEdit(rowIndex) {
372
+ this.startEdit(rowIndex, null);
373
+ },
374
+
375
+ // 单元格点击事件
376
+ handleCellClick(row, column, cell, event) {
377
+ if (
378
+ this.editMode === "click" &&
379
+ column.property &&
380
+ this.isColumnEditable(column.property)
381
+ ) {
382
+ const rowIndex = this.internalTableData.indexOf(row);
383
+ this.startEdit(rowIndex, column.property);
384
+ }
385
+ },
386
+
387
+ // 单元格双击事件
388
+ handleCellDblClick(row, column, cell, event) {
389
+ if (
390
+ this.editMode === "dblclick" &&
391
+ column.property &&
392
+ this.isColumnEditable(column.property)
393
+ ) {
394
+ const rowIndex = this.internalTableData.indexOf(row);
395
+ this.startEdit(rowIndex, column.property);
396
+ }
397
+ },
398
+
399
+ // 判断列是否可编辑
400
+ isColumnEditable(prop) {
401
+ const column = this.columns.find((col) => col.prop === prop);
402
+ return column && column.editable !== false;
403
+ },
404
+
405
+ // 格式化单元格显示值
406
+ formatCellValue(value, column) {
407
+ if (column.formatter) {
408
+ return column.formatter(value);
409
+ }
410
+
411
+ // 处理日期类型 - 时间戳转 yyyy-MM-dd
412
+ if (column.editType === "date" && value) {
413
+ if (typeof value === "string" && value.includes(" ")) {
414
+ // 如果包含空格,说明有时间部分,只取日期部分
415
+ return value.split(" ")[0];
416
+ }
417
+ return this.formatDate(value);
418
+ }
419
+
420
+ // 处理下拉选择器的显示
421
+ if (column.editType === "select" && column.options) {
422
+ const valueKey = column.valueKey || "value"; // 使用配置的 valueKey,默认为 'value'
423
+ const labelKey = column.labelKey || "label"; // 使用配置的 labelKey,默认为 'label'
424
+ const option = column.options.find((opt) => {
425
+ const optValue = opt[valueKey];
426
+ // 处理数字和字符串的匹配
427
+ if (typeof optValue === "number" && typeof value === "string") {
428
+ return optValue.toString() === value;
429
+ } else if (
430
+ typeof optValue === "string" &&
431
+ typeof value === "number"
432
+ ) {
433
+ return optValue === value.toString();
434
+ }
435
+ return optValue === value;
436
+ });
437
+ return option ? option[labelKey] : value;
438
+ }
439
+
440
+ // 处理开关的显示
441
+ if (column.editType === "switch") {
442
+ return value ? column.activeText || "是" : column.inactiveText || "否";
443
+ }
444
+
445
+ return value;
446
+ },
447
+
448
+ // 输入框失去焦点
449
+ handleInputBlur(rowIndex, prop) {
450
+ if (this.editMode !== "button") {
451
+ this.saveCell(rowIndex, prop);
452
+ }
453
+ },
454
+
455
+ // 保存单元格
456
+ saveCell(rowIndex, prop) {
457
+ const newValue = this.internalTableData[rowIndex][prop];
458
+
459
+ // 验证
460
+ const column = this.columns.find((col) => col.prop === prop);
461
+ if (column && column.rules) {
462
+ const valid = this.validateCell(newValue, column.rules);
463
+ if (!valid) {
464
+ // 验证失败,恢复原始值
465
+ this.internalTableData[rowIndex][prop] = this.originalData[prop];
466
+ this.editingCell = null;
467
+ return;
468
+ }
469
+ }
470
+
471
+ // 同步数据到父组件
472
+ if (this.syncData) {
473
+ this.syncDataToParent();
474
+ }
475
+
476
+ // 触发保存事件
477
+ this.$emit("cell-save", {
478
+ rowIndex,
479
+ prop,
480
+ newValue,
481
+ oldValue: this.originalData[prop],
482
+ row: this.internalTableData[rowIndex],
483
+ });
484
+
485
+ this.editingCell = null;
486
+ },
487
+
488
+ // 保存整行(按钮模式)
489
+ saveRow(rowIndex) {
490
+ // 验证所有可编辑列
491
+ const editableColumns = this.columns.filter(
492
+ (col) => col.editable !== false
493
+ );
494
+ let isValid = true;
495
+
496
+ for (const column of editableColumns) {
497
+ if (column.rules) {
498
+ const value = this.internalTableData[rowIndex][column.prop];
499
+ if (!this.validateCell(value, column.rules)) {
500
+ isValid = false;
501
+ break;
502
+ }
503
+ }
504
+ }
505
+
506
+ if (!isValid) {
507
+ this.$message.error("数据验证失败,请检查输入");
508
+ return;
509
+ }
510
+
511
+ // 同步数据到父组件
512
+ if (this.syncData) {
513
+ this.syncDataToParent();
514
+ }
515
+
516
+ // 触发保存事件
517
+ this.$emit("row-save", {
518
+ rowIndex,
519
+ newData: this.internalTableData[rowIndex],
520
+ oldData: this.originalData,
521
+ });
522
+
523
+ this.editingRowIndex = -1;
524
+ },
525
+
526
+ // 同步数据到父组件
527
+ syncDataToParent() {
528
+ this.$emit(
529
+ "update:data",
530
+ JSON.parse(JSON.stringify(this.internalTableData))
531
+ );
532
+ this.$emit(
533
+ "data-change",
534
+ JSON.parse(JSON.stringify(this.internalTableData))
535
+ );
536
+ },
537
+
538
+ // 取消编辑
539
+ cancelEdit(rowIndex) {
540
+ // 恢复原始数据
541
+ if (this.editMode === "button") {
542
+ this.$set(
543
+ this.internalTableData,
544
+ rowIndex,
545
+ JSON.parse(JSON.stringify(this.originalData))
546
+ );
547
+ } else {
548
+ this.internalTableData[rowIndex] = JSON.parse(
549
+ JSON.stringify(this.originalData)
550
+ );
551
+ }
552
+
553
+ this.editingCell = null;
554
+ this.editingRowIndex = -1;
555
+
556
+ this.$emit("edit-cancel", {
557
+ rowIndex,
558
+ data: this.internalTableData[rowIndex],
559
+ });
560
+ },
561
+
562
+ // 验证单元格
563
+ validateCell(value, rules) {
564
+ for (const rule of rules) {
565
+ if (rule.required && !value && value !== 0) {
566
+ this.$message.error(rule.message || "该字段为必填项");
567
+ return false;
568
+ }
569
+
570
+ if (rule.pattern && !rule.pattern.test(value)) {
571
+ this.$message.error(rule.message || "格式不正确");
572
+ return false;
573
+ }
574
+
575
+ if (rule.validator && !rule.validator(value)) {
576
+ this.$message.error(rule.message || "验证失败");
577
+ return false;
578
+ }
579
+ }
580
+
581
+ return true;
582
+ },
583
+
584
+ // 删除行
585
+ handleDelete(rowIndex, row) {
586
+ this.$confirm("确定删除该行数据吗?", "提示", {
587
+ type: "warning",
588
+ })
589
+ .then(() => {
590
+ this.internalTableData.splice(rowIndex, 1);
591
+
592
+ // 同步数据到父组件
593
+ if (this.syncData) {
594
+ this.syncDataToParent();
595
+ }
596
+
597
+ this.$emit("row-delete", {
598
+ rowIndex,
599
+ row,
600
+ });
601
+ })
602
+ .catch(() => {});
603
+ },
604
+
605
+ // 分页大小改变
606
+ handleSizeChange(size) {
607
+ this.$emit("size-change", size);
608
+ },
609
+
610
+ // 当前页改变
611
+ handleCurrentChange(page) {
612
+ this.$emit("current-change", page);
613
+ },
614
+
615
+ // 获取表格数据
616
+ getTableData() {
617
+ return JSON.parse(JSON.stringify(this.internalTableData));
618
+ },
619
+
620
+ // 设置表格数据
621
+ setTableData(data) {
622
+ this.internalTableData = JSON.parse(JSON.stringify(data));
623
+ if (this.syncData) {
624
+ this.syncDataToParent();
625
+ }
626
+ },
627
+
628
+ // 获取选中的行(多选模式)
629
+ getSelection() {
630
+ return this.$refs.tableRef ? this.$refs.tableRef.selection : [];
631
+ },
632
+
633
+ // 添加新行
634
+ addRow(rowData = {}) {
635
+ const newRow = {};
636
+ this.columns.forEach((column) => {
637
+ if (column.prop) {
638
+ newRow[column.prop] =
639
+ rowData[column.prop] !== undefined
640
+ ? rowData[column.prop]
641
+ : column.defaultValue || "";
642
+ }
643
+ });
644
+ this.internalTableData.push(newRow);
645
+
646
+ // 同步数据到父组件
647
+ if (this.syncData) {
648
+ this.syncDataToParent();
649
+ }
650
+
651
+ return this.internalTableData.length - 1;
652
+ },
653
+
654
+ // 清除所有数据
655
+ clearData() {
656
+ this.internalTableData = [];
657
+ if (this.syncData) {
658
+ this.syncDataToParent();
659
+ }
660
+ },
661
+ formatDate(timestamp) {
662
+ if (!timestamp) return "";
663
+ return this.parseTime(timestamp, "{y}-{m}-{d}");
664
+ // return dayjs(Number(timestamp)).format("YYYY-MM-DD");
665
+ },
666
+ },
667
+ };
668
+ </script>
669
+
670
+ <style scoped>
671
+ .editable-table {
672
+ width: 100%;
673
+ }
674
+
675
+ .edit-icon {
676
+ margin-left: 8px;
677
+ color: #409eff;
678
+ cursor: pointer;
679
+ }
680
+
681
+ .edit-icon:hover {
682
+ color: #66b1ff;
683
+ }
684
+
685
+ .pagination-wrapper {
686
+ margin-top: 20px;
687
+ text-align: right;
688
+ }
689
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leisure-core",
3
- "version": "0.6.66",
3
+ "version": "0.6.67",
4
4
  "description": "leisure-core是京心数据基于vue2.x开发的一套后台管理系统桌面端组件库,封装了大量实用的UI控件模板,非常方便开发者快速搭建前端应用",
5
5
  "private": false,
6
6
  "author": "北方乐逍遥(zcx7878)",