jianghu-ui 1.0.1

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.
Files changed (112) hide show
  1. package/README.md +376 -0
  2. package/dist/jianghu-ui.css +2318 -0
  3. package/dist/jianghu-ui.js +2 -0
  4. package/dist/jianghu-ui.js.LICENSE.txt +1 -0
  5. package/package.json +56 -0
  6. package/src/Design.stories.mdx +195 -0
  7. package/src/Introduction.stories.mdx +148 -0
  8. package/src/components/JhAddressSelect/JhAddressSelect.md +250 -0
  9. package/src/components/JhAddressSelect/JhAddressSelect.stories.js +282 -0
  10. package/src/components/JhAddressSelect/JhAddressSelect.vue +261 -0
  11. package/src/components/JhCard/JhCard.md +246 -0
  12. package/src/components/JhCard/JhCard.stories.js +688 -0
  13. package/src/components/JhCard/JhCard.vue +604 -0
  14. package/src/components/JhCheckCard/JhCheckCard.md +245 -0
  15. package/src/components/JhCheckCard/JhCheckCard.stories.js +750 -0
  16. package/src/components/JhCheckCard/JhCheckCard.vue +476 -0
  17. package/src/components/JhConfirmDialog/JhConfirmDialog.md +70 -0
  18. package/src/components/JhConfirmDialog/JhConfirmDialog.stories.js +550 -0
  19. package/src/components/JhConfirmDialog/JhConfirmDialog.vue +181 -0
  20. package/src/components/JhDateRangePicker/JhDateRangePicker.md +56 -0
  21. package/src/components/JhDateRangePicker/JhDateRangePicker.stories.js +320 -0
  22. package/src/components/JhDateRangePicker/JhDateRangePicker.vue +307 -0
  23. package/src/components/JhDescriptions/JhDescriptions.md +724 -0
  24. package/src/components/JhDescriptions/JhDescriptions.stories.js +858 -0
  25. package/src/components/JhDescriptions/JhDescriptions.vue +933 -0
  26. package/src/components/JhDraggable/JhDraggable.md +66 -0
  27. package/src/components/JhDraggable/JhDraggable.stories.js +161 -0
  28. package/src/components/JhDraggable/JhDraggable.vue +254 -0
  29. package/src/components/JhDrawer/JhDrawer.md +68 -0
  30. package/src/components/JhDrawer/JhDrawer.stories.js +478 -0
  31. package/src/components/JhDrawer/JhDrawer.vue +281 -0
  32. package/src/components/JhDrawerForm/JhDrawerForm.md +69 -0
  33. package/src/components/JhDrawerForm/JhDrawerForm.stories.js +492 -0
  34. package/src/components/JhDrawerForm/JhDrawerForm.vue +297 -0
  35. package/src/components/JhEditableTable/JhEditableTable.md +507 -0
  36. package/src/components/JhEditableTable/JhEditableTable.stories.js +615 -0
  37. package/src/components/JhEditableTable/JhEditableTable.vue +685 -0
  38. package/src/components/JhFileInput/JhFileInput.md +56 -0
  39. package/src/components/JhFileInput/JhFileInput.stories.js +103 -0
  40. package/src/components/JhFileInput/JhFileInput.vue +253 -0
  41. package/src/components/JhForm/JhForm.md +676 -0
  42. package/src/components/JhForm/JhForm.stories.js +1375 -0
  43. package/src/components/JhForm/JhForm.vue +657 -0
  44. package/src/components/JhFormField/JhFormField.stories.js +217 -0
  45. package/src/components/JhFormField/JhFormField.vue +439 -0
  46. package/src/components/JhFormFields/JhFormFields.md +647 -0
  47. package/src/components/JhFormFields/JhFormFields.stories.js +922 -0
  48. package/src/components/JhFormFields/JhFormFields.vue +998 -0
  49. package/src/components/JhFormList/JhFormList.md +303 -0
  50. package/src/components/JhFormList/JhFormList.stories.js +661 -0
  51. package/src/components/JhFormList/JhFormList.vue +1127 -0
  52. package/src/components/JhJsonEditor/JhJsonEditor.md +54 -0
  53. package/src/components/JhJsonEditor/JhJsonEditor.stories.js +157 -0
  54. package/src/components/JhJsonEditor/JhJsonEditor.vue +178 -0
  55. package/src/components/JhLayout/JhLayout.md +580 -0
  56. package/src/components/JhLayout/JhLayout.stories.js +414 -0
  57. package/src/components/JhLayout/JhLayout.vue +387 -0
  58. package/src/components/JhList/JhList.md +441 -0
  59. package/src/components/JhList/JhList.stories.js +524 -0
  60. package/src/components/JhList/JhList.vue +571 -0
  61. package/src/components/JhMarkdownEditor/JhMarkdownEditor.md +56 -0
  62. package/src/components/JhMarkdownEditor/JhMarkdownEditor.stories.js +191 -0
  63. package/src/components/JhMarkdownEditor/JhMarkdownEditor.vue +188 -0
  64. package/src/components/JhMask/JhMask.md +62 -0
  65. package/src/components/JhMask/JhMask.stories.js +270 -0
  66. package/src/components/JhMask/JhMask.vue +123 -0
  67. package/src/components/JhMenu/JhMenu.md +85 -0
  68. package/src/components/JhMenu/JhMenu.stories.js +384 -0
  69. package/src/components/JhMenu/JhMenu.vue +545 -0
  70. package/src/components/JhModal/JhModal.md +68 -0
  71. package/src/components/JhModal/JhModal.stories.js +562 -0
  72. package/src/components/JhModal/JhModal.vue +235 -0
  73. package/src/components/JhModalForm/JhModalForm.md +69 -0
  74. package/src/components/JhModalForm/JhModalForm.stories.js +592 -0
  75. package/src/components/JhModalForm/JhModalForm.vue +298 -0
  76. package/src/components/JhPageContainer/JhPageContainer.md +409 -0
  77. package/src/components/JhPageContainer/JhPageContainer.stories.js +209 -0
  78. package/src/components/JhPageContainer/JhPageContainer.vue +72 -0
  79. package/src/components/JhQueryFilter/JhQueryFilter.md +77 -0
  80. package/src/components/JhQueryFilter/JhQueryFilter.stories.js +684 -0
  81. package/src/components/JhQueryFilter/JhQueryFilter.vue +429 -0
  82. package/src/components/JhScene/JhScene.md +64 -0
  83. package/src/components/JhScene/JhScene.stories.js +317 -0
  84. package/src/components/JhScene/JhScene.vue +376 -0
  85. package/src/components/JhStatisticCard/JhStatisticCard.md +363 -0
  86. package/src/components/JhStatisticCard/JhStatisticCard.stories.js +847 -0
  87. package/src/components/JhStatisticCard/JhStatisticCard.vue +459 -0
  88. package/src/components/JhStepsForm/JhStepsForm.md +666 -0
  89. package/src/components/JhStepsForm/JhStepsForm.stories.js +1224 -0
  90. package/src/components/JhStepsForm/JhStepsForm.vue +749 -0
  91. package/src/components/JhTable/JhTable.md +730 -0
  92. package/src/components/JhTable/JhTable.stories.js +1444 -0
  93. package/src/components/JhTable/JhTable.vue +2298 -0
  94. package/src/components/JhTableAttachment/JhTableAttachment.md +70 -0
  95. package/src/components/JhTableAttachment/JhTableAttachment.stories.js +198 -0
  96. package/src/components/JhTableAttachment/JhTableAttachment.vue +264 -0
  97. package/src/components/JhToast/JhToast.md +67 -0
  98. package/src/components/JhToast/JhToast.stories.js +386 -0
  99. package/src/components/JhToast/JhToast.vue +239 -0
  100. package/src/components/JhTreeSelect/JhTreeSelect.md +82 -0
  101. package/src/components/JhTreeSelect/JhTreeSelect.stories.js +391 -0
  102. package/src/components/JhTreeSelect/JhTreeSelect.vue +727 -0
  103. package/src/components/JhWaterMark/JhWaterMark.md +190 -0
  104. package/src/components/JhWaterMark/JhWaterMark.stories.js +675 -0
  105. package/src/components/JhWaterMark/JhWaterMark.vue +351 -0
  106. package/src/components/README.md +52 -0
  107. package/src/index.js +135 -0
  108. package/src/style/globalCSSJHV4.css +348 -0
  109. package/src/style/globalCSSVuetifyV4.css +637 -0
  110. package/src/style/storybook.css +4 -0
  111. package/src/tailwind.css +3 -0
  112. package/src/utils/vuetify.js +31 -0
@@ -0,0 +1,685 @@
1
+ <template>
2
+ <div class="jh-editable-table">
3
+ <v-data-table
4
+ :headers="tableHeaders"
5
+ :items="internalData"
6
+ :items-per-page="itemsPerPage"
7
+ :hide-default-footer="hideFooter"
8
+ :dense="dense"
9
+ :disable-sort="!sortable"
10
+ class="elevation-1"
11
+ >
12
+ <!-- 自定义单元格渲染 -->
13
+ <template v-for="column in editableColumns" v-slot:[`item.${column.key}`]="{ item }">
14
+ <div :key="column.key" class="jh-editable-cell">
15
+ <!-- 编辑模式 -->
16
+ <template v-if="isEditing(item)">
17
+ <!-- 文本输入 -->
18
+ <v-text-field
19
+ v-if="!column.type || column.type === 'text'"
20
+ v-model="item[column.key]"
21
+ :label="column.label"
22
+ :rules="column.rules"
23
+ :placeholder="column.placeholder"
24
+ dense
25
+ hide-details="auto"
26
+ class="my-1"
27
+ ></v-text-field>
28
+
29
+ <!-- 数字输入 -->
30
+ <v-text-field
31
+ v-else-if="column.type === 'number'"
32
+ v-model.number="item[column.key]"
33
+ :label="column.label"
34
+ :rules="column.rules"
35
+ type="number"
36
+ dense
37
+ hide-details="auto"
38
+ class="my-1"
39
+ ></v-text-field>
40
+
41
+ <!-- 下拉选择 -->
42
+ <v-select
43
+ v-else-if="column.type === 'select'"
44
+ v-model="item[column.key]"
45
+ :items="column.options"
46
+ :label="column.label"
47
+ :rules="column.rules"
48
+ dense
49
+ hide-details="auto"
50
+ class="my-1"
51
+ ></v-select>
52
+
53
+ <!-- 日期选择 -->
54
+ <v-menu
55
+ v-else-if="column.type === 'date'"
56
+ v-model="dateMenus[item._uuid + column.key]"
57
+ :close-on-content-click="false"
58
+ transition="scale-transition"
59
+ offset-y
60
+ min-width="auto"
61
+ >
62
+ <template v-slot:activator="{ on, attrs }">
63
+ <v-text-field
64
+ v-model="item[column.key]"
65
+ :label="column.label"
66
+ prepend-icon="mdi-calendar"
67
+ readonly
68
+ dense
69
+ hide-details="auto"
70
+ v-bind="attrs"
71
+ v-on="on"
72
+ class="my-1"
73
+ ></v-text-field>
74
+ </template>
75
+ <v-date-picker
76
+ v-model="item[column.key]"
77
+ @input="dateMenus[item._uuid + column.key] = false"
78
+ ></v-date-picker>
79
+ </v-menu>
80
+
81
+ <!-- 开关 -->
82
+ <v-switch
83
+ v-else-if="column.type === 'switch'"
84
+ v-model="item[column.key]"
85
+ :label="column.label"
86
+ dense
87
+ hide-details="auto"
88
+ class="my-1"
89
+ ></v-switch>
90
+
91
+ <!-- 枚举选择 -->
92
+ <v-select
93
+ v-else-if="column.type === 'enum' && column.enum"
94
+ v-model="item[column.key]"
95
+ :items="getEnumOptions(column.enum)"
96
+ :label="column.label"
97
+ :rules="column.rules"
98
+ dense
99
+ hide-details="auto"
100
+ class="my-1"
101
+ ></v-select>
102
+ </template>
103
+
104
+ <!-- 显示模式 -->
105
+ <template v-else>
106
+ <!-- 自定义渲染 -->
107
+ <slot
108
+ v-if="$scopedSlots[`cell.${column.key}`]"
109
+ :name="`cell.${column.key}`"
110
+ :item="item"
111
+ :value="item[column.key]"
112
+ ></slot>
113
+
114
+ <!-- 默认显示 -->
115
+ <span v-else>
116
+ <!-- 枚举类型 -->
117
+ <v-chip
118
+ v-if="column.type === 'enum' && column.enum"
119
+ :color="column.enum[item[column.key]]?.color"
120
+ small
121
+ >
122
+ {{ column.enum[item[column.key]]?.text || item[column.key] }}
123
+ </v-chip>
124
+
125
+ <!-- 开关显示 -->
126
+ <v-icon v-else-if="column.type === 'switch'" :color="item[column.key] ? 'success' : 'grey'">
127
+ {{ item[column.key] ? 'mdi-check-circle' : 'mdi-circle-outline' }}
128
+ </v-icon>
129
+
130
+ <!-- 普通文本 -->
131
+ <span v-else>{{ item[column.key] }}</span>
132
+ </span>
133
+ </template>
134
+ </div>
135
+ </template>
136
+
137
+ <!-- 操作列 -->
138
+ <template v-slot:[`item.actions`]="{ item }">
139
+ <div class="jh-editable-actions">
140
+ <!-- 编辑状态按钮 -->
141
+ <template v-if="isEditing(item)">
142
+ <v-btn
143
+ icon
144
+ small
145
+ color="success"
146
+ @click="handleSave(item)"
147
+ title="保存"
148
+ >
149
+ <v-icon small>mdi-check</v-icon>
150
+ </v-btn>
151
+ <v-btn
152
+ icon
153
+ small
154
+ color="error"
155
+ @click="handleCancel(item)"
156
+ title="取消"
157
+ >
158
+ <v-icon small>mdi-close</v-icon>
159
+ </v-btn>
160
+ </template>
161
+
162
+ <!-- 显示状态按钮 -->
163
+ <template v-else>
164
+ <v-btn
165
+ v-if="editable"
166
+ icon
167
+ small
168
+ color="primary"
169
+ @click="handleEdit(item)"
170
+ title="编辑"
171
+ >
172
+ <v-icon small>mdi-pencil</v-icon>
173
+ </v-btn>
174
+ <v-btn
175
+ v-if="deletable"
176
+ icon
177
+ small
178
+ color="error"
179
+ @click="handleDelete(item)"
180
+ title="删除"
181
+ >
182
+ <v-icon small>mdi-delete</v-icon>
183
+ </v-btn>
184
+
185
+ <!-- 自定义操作按钮 -->
186
+ <slot name="actions" :item="item"></slot>
187
+ </template>
188
+ </div>
189
+ </template>
190
+
191
+ <!-- 底部插槽 - 新增按钮 -->
192
+ <template v-slot:footer v-if="recordCreator">
193
+ <div class="jh-editable-footer pa-3">
194
+ <v-btn
195
+ color="primary"
196
+ small
197
+ @click="handleAddRow"
198
+ :disabled="hasEditingRow"
199
+ >
200
+ <v-icon small left>mdi-plus</v-icon>
201
+ {{ recordCreatorProps.creatorButtonText || '添加一行数据' }}
202
+ </v-btn>
203
+ </div>
204
+ </template>
205
+ </v-data-table>
206
+ </div>
207
+ </template>
208
+
209
+ <script>
210
+ export default {
211
+ name: 'JhEditableTable',
212
+
213
+ props: {
214
+ // 表格数据 (v-model)
215
+ value: {
216
+ type: Array,
217
+ default: () => [],
218
+ },
219
+
220
+ // 列配置
221
+ columns: {
222
+ type: Array,
223
+ required: true,
224
+ // 列配置示例:
225
+ // [{
226
+ // key: 'name',
227
+ // label: '姓名',
228
+ // type: 'text', // text | number | select | date | switch | enum
229
+ // editable: true,
230
+ // rules: [(v) => !!v || '必填'],
231
+ // options: [], // select 类型的选项
232
+ // enum: {}, // enum 类型的映射
233
+ // placeholder: '',
234
+ // }]
235
+ },
236
+
237
+ // 是否可编辑
238
+ editable: {
239
+ type: Boolean,
240
+ default: true,
241
+ },
242
+
243
+ // 是否可删除
244
+ deletable: {
245
+ type: Boolean,
246
+ default: true,
247
+ },
248
+
249
+ // 是否显示新增按钮
250
+ recordCreator: {
251
+ type: Boolean,
252
+ default: true,
253
+ },
254
+
255
+ // 新增按钮配置
256
+ recordCreatorProps: {
257
+ type: Object,
258
+ default: () => ({
259
+ creatorButtonText: '添加一行数据',
260
+ record: {},
261
+ }),
262
+ },
263
+
264
+ // 每页显示数量
265
+ itemsPerPage: {
266
+ type: Number,
267
+ default: -1, // -1 表示显示全部
268
+ },
269
+
270
+ // 是否隐藏底部分页
271
+ hideFooter: {
272
+ type: Boolean,
273
+ default: true,
274
+ },
275
+
276
+ // 是否紧凑模式
277
+ dense: {
278
+ type: Boolean,
279
+ default: false,
280
+ },
281
+
282
+ // 是否可排序
283
+ sortable: {
284
+ type: Boolean,
285
+ default: false,
286
+ },
287
+
288
+ // 行唯一标识字段
289
+ rowKey: {
290
+ type: String,
291
+ default: 'id',
292
+ },
293
+
294
+ // 可编辑类型 (EditableProTable 风格)
295
+ editableType: {
296
+ type: String,
297
+ default: 'multiple', // single | multiple
298
+ validator: (v) => ['single', 'multiple'].includes(v),
299
+ },
300
+
301
+ // 最大行数
302
+ maxRows: {
303
+ type: Number,
304
+ default: Infinity,
305
+ },
306
+
307
+ // 值变化回调
308
+ onValuesChange: {
309
+ type: Function,
310
+ default: null,
311
+ },
312
+
313
+ // 添加行前守卫
314
+ beforeAddRow: {
315
+ type: Function,
316
+ default: null,
317
+ },
318
+
319
+ // 删除行前守卫
320
+ beforeRemoveRow: {
321
+ type: Function,
322
+ default: null,
323
+ },
324
+
325
+ // 自定义操作渲染
326
+ actionRender: {
327
+ type: Function,
328
+ default: null,
329
+ },
330
+
331
+ // 受控模式的编辑行keys
332
+ editableKeys: {
333
+ type: Array,
334
+ default: null,
335
+ },
336
+
337
+ // 保存数据时的回调
338
+ onSave: {
339
+ type: Function,
340
+ default: null,
341
+ },
342
+
343
+ // 删除数据时的回调
344
+ onDelete: {
345
+ type: Function,
346
+ default: null,
347
+ },
348
+ },
349
+
350
+ data() {
351
+ return {
352
+ internalData: [],
353
+ editingKeys: new Set(), // 正在编辑的行的 key 集合
354
+ originalData: new Map(), // 保存原始数据用于取消操作
355
+ dateMenus: {}, // 日期选择器菜单状态
356
+ };
357
+ },
358
+
359
+ computed: {
360
+ // 可编辑的列
361
+ editableColumns() {
362
+ return this.columns.filter(col => col.editable !== false);
363
+ },
364
+
365
+ // 表头配置
366
+ tableHeaders() {
367
+ const headers = this.columns.map(col => ({
368
+ text: col.label || col.key,
369
+ value: col.key,
370
+ sortable: col.sortable !== false && this.sortable,
371
+ width: col.width,
372
+ align: col.align || 'start',
373
+ }));
374
+
375
+ // 添加操作列
376
+ if (this.editable || this.deletable || this.$scopedSlots.actions) {
377
+ headers.push({
378
+ text: '操作',
379
+ value: 'actions',
380
+ sortable: false,
381
+ width: 120,
382
+ align: 'center',
383
+ });
384
+ }
385
+
386
+ return headers;
387
+ },
388
+
389
+ // 是否有正在编辑的行
390
+ hasEditingRow() {
391
+ return this.editingKeys.size > 0;
392
+ },
393
+ },
394
+
395
+ watch: {
396
+ value: {
397
+ handler(newVal) {
398
+ this.initInternalData(newVal);
399
+ },
400
+ immediate: true,
401
+ deep: true,
402
+ },
403
+ },
404
+
405
+ methods: {
406
+ // 初始化内部数据
407
+ initInternalData(data) {
408
+ this.internalData = data.map(item => {
409
+ // 为每行添加唯一标识
410
+ if (!item._uuid) {
411
+ item._uuid = this.generateUUID();
412
+ }
413
+ return { ...item };
414
+ });
415
+ },
416
+
417
+ // 生成 UUID
418
+ generateUUID() {
419
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
420
+ const r = Math.random() * 16 | 0;
421
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
422
+ return v.toString(16);
423
+ });
424
+ },
425
+
426
+ // 获取行的唯一标识
427
+ getRowKey(item) {
428
+ return item._uuid || item[this.rowKey];
429
+ },
430
+
431
+ // 判断行是否在编辑状态
432
+ isEditing(item) {
433
+ return this.editingKeys.has(this.getRowKey(item));
434
+ },
435
+
436
+ // 将 enum 对象转换为 v-select 选项
437
+ getEnumOptions(enumObj) {
438
+ return Object.keys(enumObj).map(key => ({
439
+ text: enumObj[key].text || key,
440
+ value: key,
441
+ }));
442
+ },
443
+
444
+ // 开始编辑行
445
+ async handleEdit(item) {
446
+ const key = this.getRowKey(item);
447
+
448
+ // single 模式下,关闭其他编辑行
449
+ if (this.editableType === 'single' && this.editingKeys.size > 0) {
450
+ const currentKey = Array.from(this.editingKeys)[0];
451
+ const currentItem = this.internalData.find(row => this.getRowKey(row) === currentKey);
452
+ if (currentItem) {
453
+ await this.handleCancel(currentItem);
454
+ }
455
+ }
456
+
457
+ // 保存原始数据
458
+ this.originalData.set(key, { ...item });
459
+
460
+ // 添加到编辑集合
461
+ this.editingKeys.add(key);
462
+
463
+ this.$emit('edit', item);
464
+ this.$emit('update:editableKeys', Array.from(this.editingKeys));
465
+ },
466
+
467
+ // 保存行
468
+ async handleSave(item) {
469
+ const key = this.getRowKey(item);
470
+
471
+ // 验证数据
472
+ if (!this.validateRow(item)) {
473
+ return;
474
+ }
475
+
476
+ // 执行保存回调
477
+ if (this.onSave) {
478
+ const result = await this.onSave(key, item, this.originalData.get(key));
479
+ if (result === false) return;
480
+ }
481
+
482
+ // 从编辑集合中移除
483
+ this.editingKeys.delete(key);
484
+ this.originalData.delete(key);
485
+
486
+ // 更新父组件数据
487
+ this.syncToParent();
488
+
489
+ this.$emit('save', item);
490
+ this.$emit('change', this.internalData);
491
+ this.$emit('update:editableKeys', Array.from(this.editingKeys));
492
+
493
+ // 触发值变化回调
494
+ if (this.onValuesChange) {
495
+ this.onValuesChange(item, this.internalData);
496
+ }
497
+ },
498
+
499
+ // 取消编辑
500
+ handleCancel(item) {
501
+ const key = this.getRowKey(item);
502
+
503
+ // 如果是新增的行,直接删除
504
+ if (item._isNew) {
505
+ const index = this.internalData.findIndex(row => this.getRowKey(row) === key);
506
+ if (index > -1) {
507
+ this.internalData.splice(index, 1);
508
+ }
509
+ } else {
510
+ // 恢复原始数据
511
+ const originalItem = this.originalData.get(key);
512
+ if (originalItem) {
513
+ const index = this.internalData.findIndex(row => this.getRowKey(row) === key);
514
+ if (index > -1) {
515
+ this.internalData.splice(index, 1, { ...originalItem });
516
+ }
517
+ }
518
+ }
519
+
520
+ // 从编辑集合中移除
521
+ this.editingKeys.delete(key);
522
+ this.originalData.delete(key);
523
+
524
+ // 更新父组件数据
525
+ this.syncToParent();
526
+
527
+ this.$emit('cancel', item);
528
+ },
529
+
530
+ // 删除行
531
+ async handleDelete(item) {
532
+ const key = this.getRowKey(item);
533
+ const index = this.internalData.findIndex(row => this.getRowKey(row) === key);
534
+
535
+ // 执行删除前守卫
536
+ if (this.beforeRemoveRow) {
537
+ const canDelete = await this.beforeRemoveRow(item, index);
538
+ if (canDelete === false) return;
539
+ }
540
+
541
+ // 执行删除回调
542
+ if (this.onDelete) {
543
+ const result = await this.onDelete(key, item);
544
+ if (result === false) return;
545
+ }
546
+
547
+ if (index > -1) {
548
+ this.internalData.splice(index, 1);
549
+
550
+ // 更新父组件数据
551
+ this.syncToParent();
552
+
553
+ this.$emit('delete', item, index);
554
+ this.$emit('change', this.internalData);
555
+ }
556
+ },
557
+
558
+ // 添加新行
559
+ async handleAddRow() {
560
+ // 检查最大行数限制
561
+ if (this.internalData.length >= this.maxRows) {
562
+ this.$emit('max-rows', this.maxRows);
563
+ return;
564
+ }
565
+
566
+ // 执行添加前守卫
567
+ if (this.beforeAddRow) {
568
+ const canAdd = await this.beforeAddRow();
569
+ if (canAdd === false) return;
570
+ }
571
+
572
+ const newRow = {
573
+ _uuid: this.generateUUID(),
574
+ _isNew: true,
575
+ ...this.recordCreatorProps.record,
576
+ };
577
+
578
+ // 初始化列的默认值
579
+ this.columns.forEach(col => {
580
+ if (col.key && newRow[col.key] === undefined) {
581
+ if (col.type === 'switch') {
582
+ newRow[col.key] = false;
583
+ } else if (col.type === 'number') {
584
+ newRow[col.key] = 0;
585
+ } else {
586
+ newRow[col.key] = '';
587
+ }
588
+ }
589
+ });
590
+
591
+ this.internalData.push(newRow);
592
+
593
+ // 立即进入编辑状态
594
+ this.$nextTick(() => {
595
+ this.handleEdit(newRow);
596
+ });
597
+
598
+ this.$emit('add', newRow);
599
+ },
600
+
601
+ // 验证行数据
602
+ validateRow(item) {
603
+ // 简单验证:检查必填字段
604
+ for (const col of this.columns) {
605
+ if (col.rules && col.editable !== false) {
606
+ for (const rule of col.rules) {
607
+ const result = rule(item[col.key]);
608
+ if (typeof result === 'string') {
609
+ // 验证失败
610
+ this.$emit('validation-error', { item, column: col, message: result });
611
+ return false;
612
+ }
613
+ }
614
+ }
615
+ }
616
+ return true;
617
+ },
618
+
619
+ // 同步到父组件
620
+ syncToParent() {
621
+ // 移除内部标识字段
622
+ const cleanData = this.internalData.map(item => {
623
+ const { _uuid, _isNew, ...rest } = item;
624
+ return rest;
625
+ });
626
+
627
+ this.$emit('input', cleanData);
628
+ },
629
+
630
+ // 公共方法:获取当前数据
631
+ getData() {
632
+ return this.internalData.map(item => {
633
+ const { _uuid, _isNew, ...rest } = item;
634
+ return rest;
635
+ });
636
+ },
637
+
638
+ // 公共方法:设置数据
639
+ setData(data) {
640
+ this.initInternalData(data);
641
+ this.syncToParent();
642
+ },
643
+
644
+ // 公共方法:清空数据
645
+ clearData() {
646
+ this.internalData = [];
647
+ this.editingKeys.clear();
648
+ this.originalData.clear();
649
+ this.syncToParent();
650
+ },
651
+ },
652
+ };
653
+ </script>
654
+
655
+ <style scoped>
656
+ .jh-editable-table {
657
+ width: 100%;
658
+ }
659
+
660
+ .jh-editable-cell {
661
+ min-height: 48px;
662
+ display: flex;
663
+ align-items: center;
664
+ }
665
+
666
+ .jh-editable-actions {
667
+ display: flex;
668
+ gap: 4px;
669
+ justify-content: center;
670
+ align-items: center;
671
+ }
672
+
673
+ .jh-editable-footer {
674
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
675
+ }
676
+
677
+ /* 编辑模式下的输入框样式优化 */
678
+ .jh-editable-table >>> .v-text-field.v-text-field--enclosed .v-text-field__details {
679
+ margin-bottom: 0;
680
+ }
681
+
682
+ .jh-editable-table >>> .v-input--dense > .v-input__control > .v-input__slot {
683
+ margin-bottom: 0;
684
+ }
685
+ </style>