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,749 @@
1
+ <template>
2
+ <div :class="['jh-steps-form-container', { 'is-vertical': vertical }]" :style="containerStyle">
3
+ <template v-if="vertical">
4
+ <!-- 垂直布局:左侧步骤指示器 -->
5
+ <div class="jh-steps-form-stepper">
6
+ <div class="vertical-steps">
7
+ <div
8
+ v-for="(step, index) in steps"
9
+ :key="`step-${index}`"
10
+ :class="[
11
+ 'vertical-step-item',
12
+ {
13
+ 'active': currentStepIndex === index + 1,
14
+ 'completed': currentStepIndex > index + 1,
15
+ 'editable': editable && index < currentStepIndex - 1
16
+ }
17
+ ]"
18
+ @click="editable && index < currentStepIndex - 1 && handleStepClick(index)"
19
+ >
20
+ <div class="step-number">
21
+ <v-icon v-if="currentStepIndex > index + 1" small color="success">mdi-check</v-icon>
22
+ <span v-else>{{ index + 1 }}</span>
23
+ </div>
24
+ <div class="step-info">
25
+ <div class="step-title">{{ step.title }}</div>
26
+ <div v-if="step.subTitle" class="step-subtitle">{{ step.subTitle }}</div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- 垂直布局:右侧内容区域 -->
33
+ <div class="jh-steps-form-content">
34
+ <v-card flat>
35
+ <div class="step-content">
36
+ <template v-for="(step, index) in steps">
37
+ <div v-if="currentStepIndex === index + 1" :key="`content-${index}`">
38
+ <!-- 使用插槽渲染步骤内容 -->
39
+ <slot
40
+ v-if="step.slot"
41
+ :name="step.slot"
42
+ :step="step"
43
+ :index="index"
44
+ :formData="formData"
45
+ ></slot>
46
+
47
+ <!-- 使用 JhForm 渲染步骤表单 -->
48
+ <jh-form
49
+ v-else-if="step.fields"
50
+ :ref="`stepForm${index}`"
51
+ :fields="step.fields"
52
+ :initial-data="formData[`step${index}`] || {}"
53
+ :show-buttons="false"
54
+ :outlined="outlined"
55
+ :dense="dense"
56
+ v-bind="mergedFormProps"
57
+ @change="handleStepFormChange(index, $event)"
58
+ />
59
+
60
+ <!-- 默认内容 -->
61
+ <div v-else>
62
+ <slot :step="step" :index="index" :formData="formData"></slot>
63
+ </div>
64
+ </div>
65
+ </template>
66
+ </div>
67
+
68
+ <!-- 步骤操作按钮 -->
69
+ <div v-if="!submitter || submitter.render !== false" class="step-actions d-flex justify-space-between mt-6">
70
+ <!-- 自定义渲染提交器 -->
71
+ <template v-if="submitter && submitter.render">
72
+ <slot
73
+ name="submitter"
74
+ :step="steps[currentStepIndex - 1]"
75
+ :index="currentStepIndex - 1"
76
+ :onPrevious="handlePrevious"
77
+ :onNext="handleNext"
78
+ :onFinish="handleFinish"
79
+ :submitting="submitting"
80
+ :validating="validating"
81
+ >
82
+ <component
83
+ :is="submitter.render"
84
+ :step="steps[currentStepIndex - 1]"
85
+ :index="currentStepIndex - 1"
86
+ :onPrevious="handlePrevious"
87
+ :onNext="handleNext"
88
+ :onFinish="handleFinish"
89
+ :submitting="submitting"
90
+ :validating="validating"
91
+ />
92
+ </slot>
93
+ </template>
94
+
95
+ <!-- 默认按钮 -->
96
+ <template v-else>
97
+ <v-btn
98
+ v-if="currentStepIndex > 1 && (!submitter || submitter.showPrevious !== false)"
99
+ text
100
+ @click="handlePrevious"
101
+ :disabled="submitting"
102
+ v-bind="submitter && submitter.previousButtonProps"
103
+ >
104
+ {{ (submitter && submitter.previousText) || previousButtonText }}
105
+ </v-btn>
106
+ <v-spacer v-else></v-spacer>
107
+
108
+ <div>
109
+ <v-btn
110
+ v-if="currentStepIndex < steps.length"
111
+ color="primary"
112
+ @click="handleNext"
113
+ :loading="validating"
114
+ :disabled="submitting"
115
+ v-bind="submitter && submitter.nextButtonProps"
116
+ >
117
+ {{ (submitter && submitter.nextText) || nextButtonText }}
118
+ </v-btn>
119
+ <v-btn
120
+ v-else
121
+ color="primary"
122
+ @click="handleFinish"
123
+ :loading="submitting"
124
+ v-bind="submitter && submitter.submitButtonProps"
125
+ >
126
+ {{ (submitter && submitter.submitText) || finishButtonText }}
127
+ </v-btn>
128
+ </div>
129
+ </template>
130
+ </div>
131
+ </v-card>
132
+ </div>
133
+ </template>
134
+
135
+ <template v-else>
136
+ <!-- 水平布局:使用原有的 v-stepper 结构 -->
137
+ <v-stepper
138
+ v-model="currentStepIndex"
139
+ :alt-labels="altLabels"
140
+ v-bind="stepsProps"
141
+ class="w-full"
142
+ >
143
+ <v-stepper-header>
144
+ <template v-for="(step, index) in steps">
145
+ <v-stepper-step
146
+ :key="`step-${index}`"
147
+ :complete="currentStepIndex > index + 1"
148
+ :step="index + 1"
149
+ :editable="editable && index < currentStepIndex"
150
+ @click="editable && handleStepClick(index)"
151
+ >
152
+ {{ step.title }}
153
+ <small v-if="step.subTitle">{{ step.subTitle }}</small>
154
+ </v-stepper-step>
155
+ <v-divider v-if="index < steps.length - 1" :key="`divider-${index}`"></v-divider>
156
+ </template>
157
+ </v-stepper-header>
158
+
159
+ <v-stepper-items>
160
+ <v-stepper-content
161
+ v-for="(step, index) in steps"
162
+ :key="`content-${index}`"
163
+ :step="index + 1"
164
+ >
165
+ <div class="step-content">
166
+ <!-- 使用插槽渲染步骤内容 -->
167
+ <slot
168
+ v-if="step.slot"
169
+ :name="step.slot"
170
+ :step="step"
171
+ :index="index"
172
+ :formData="formData"
173
+ ></slot>
174
+
175
+ <!-- 使用 JhForm 渲染步骤表单 -->
176
+ <jh-form
177
+ v-else-if="step.fields"
178
+ :ref="`stepForm${index}`"
179
+ :fields="step.fields"
180
+ :initial-data="formData[`step${index}`] || {}"
181
+ :show-buttons="false"
182
+ :outlined="outlined"
183
+ :dense="dense"
184
+ v-bind="mergedFormProps"
185
+ @change="handleStepFormChange(index, $event)"
186
+ />
187
+
188
+ <!-- 默认内容 -->
189
+ <div v-else>
190
+ <slot :step="step" :index="index" :formData="formData"></slot>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- 步骤操作按钮 -->
195
+ <div v-if="!submitter || submitter.render !== false" class="step-actions d-flex justify-space-between mt-6">
196
+ <!-- 自定义渲染提交器 -->
197
+ <template v-if="submitter && submitter.render">
198
+ <slot
199
+ name="submitter"
200
+ :step="step"
201
+ :index="index"
202
+ :onPrevious="handlePrevious"
203
+ :onNext="handleNext"
204
+ :onFinish="handleFinish"
205
+ :submitting="submitting"
206
+ :validating="validating"
207
+ >
208
+ <component
209
+ :is="submitter.render"
210
+ :step="step"
211
+ :index="index"
212
+ :onPrevious="handlePrevious"
213
+ :onNext="handleNext"
214
+ :onFinish="handleFinish"
215
+ :submitting="submitting"
216
+ :validating="validating"
217
+ />
218
+ </slot>
219
+ </template>
220
+
221
+ <!-- 默认按钮 -->
222
+ <template v-else>
223
+ <v-btn
224
+ v-if="index > 0 && (!submitter || submitter.showPrevious !== false)"
225
+ text
226
+ @click="handlePrevious"
227
+ :disabled="submitting"
228
+ v-bind="submitter && submitter.previousButtonProps"
229
+ >
230
+ {{ (submitter && submitter.previousText) || previousButtonText }}
231
+ </v-btn>
232
+ <v-spacer v-else></v-spacer>
233
+
234
+ <div>
235
+ <v-btn
236
+ v-if="index < steps.length - 1"
237
+ color="primary"
238
+ @click="handleNext"
239
+ :loading="validating"
240
+ :disabled="submitting"
241
+ v-bind="submitter && submitter.nextButtonProps"
242
+ >
243
+ {{ (submitter && submitter.nextText) || nextButtonText }}
244
+ </v-btn>
245
+ <v-btn
246
+ v-else
247
+ color="primary"
248
+ @click="handleFinish"
249
+ :loading="submitting"
250
+ v-bind="submitter && submitter.submitButtonProps"
251
+ >
252
+ {{ (submitter && submitter.submitText) || finishButtonText }}
253
+ </v-btn>
254
+ </div>
255
+ </template>
256
+ </div>
257
+ </v-stepper-content>
258
+ </v-stepper-items>
259
+ </v-stepper>
260
+ </template>
261
+ </div>
262
+ </template>
263
+
264
+ <script>
265
+ import JhForm from '../JhForm/JhForm.vue';
266
+
267
+ export default {
268
+ name: 'JhStepsForm',
269
+ components: {
270
+ JhForm,
271
+ },
272
+ props: {
273
+ // 步骤配置
274
+ steps: {
275
+ type: Array,
276
+ required: true,
277
+ default: () => [],
278
+ // 每个步骤: { title, subTitle, slot, fields }
279
+ },
280
+ // 初始步骤索引 (从0开始)
281
+ initialStep: {
282
+ type: Number,
283
+ default: 0,
284
+ },
285
+ // 当前激活步骤 (支持 v-model)
286
+ value: {
287
+ type: Number,
288
+ default: null,
289
+ },
290
+ // 当前步骤 (支持 .sync)
291
+ current: {
292
+ type: Number,
293
+ default: null,
294
+ },
295
+ // 是否可编辑已完成的步骤
296
+ editable: {
297
+ type: Boolean,
298
+ default: false,
299
+ },
300
+ // 是否垂直布局
301
+ vertical: {
302
+ type: Boolean,
303
+ default: false,
304
+ },
305
+ // 是否替代标签模式(步骤标题在图标下方)
306
+ altLabels: {
307
+ type: Boolean,
308
+ default: false,
309
+ },
310
+ // 输入框样式
311
+ outlined: {
312
+ type: Boolean,
313
+ default: true,
314
+ },
315
+ // 紧凑模式
316
+ dense: {
317
+ type: Boolean,
318
+ default: false,
319
+ },
320
+ // 提交中状态
321
+ submitting: {
322
+ type: Boolean,
323
+ default: false,
324
+ },
325
+ // 按钮文本
326
+ previousButtonText: {
327
+ type: String,
328
+ default: '上一步',
329
+ },
330
+ nextButtonText: {
331
+ type: String,
332
+ default: '下一步',
333
+ },
334
+ finishButtonText: {
335
+ type: String,
336
+ default: '完成',
337
+ },
338
+ // 步骤验证函数
339
+ validateStep: {
340
+ type: Function,
341
+ default: null,
342
+ },
343
+ // 数据转换函数(最终提交前)
344
+ transformBeforeFinish: {
345
+ type: Function,
346
+ default: null,
347
+ },
348
+ // 步骤变化回调
349
+ onCurrentChange: {
350
+ type: Function,
351
+ default: null,
352
+ },
353
+ // 表单字段变化回调
354
+ onFormChange: {
355
+ type: Function,
356
+ default: null,
357
+ },
358
+ // 传递给 v-stepper 的额外属性
359
+ stepsProps: {
360
+ type: Object,
361
+ default: () => ({}),
362
+ },
363
+ // 传递给所有步骤表单的公共属性
364
+ formProps: {
365
+ type: Object,
366
+ default: () => ({}),
367
+ },
368
+ // 自定义步骤表单渲染
369
+ stepFormRender: {
370
+ type: Function,
371
+ default: null,
372
+ },
373
+ // 提交按钮配置
374
+ submitter: {
375
+ type: [Object, Boolean],
376
+ default: null,
377
+ // {
378
+ // render: false, // 隐藏按钮
379
+ // render: Function, // 自定义渲染
380
+ // showPrevious: true,
381
+ // previousText: '上一步',
382
+ // previousButtonProps: {},
383
+ // nextText: '下一步',
384
+ // nextButtonProps: {},
385
+ // submitText: '提交',
386
+ // submitButtonProps: {},
387
+ // }
388
+ },
389
+ // 容器样式
390
+ containerStyle: {
391
+ type: [Object, String],
392
+ default: null,
393
+ },
394
+ },
395
+ data() {
396
+ const initialIndex = this.current !== null ? this.current :
397
+ (this.value !== null ? this.value : this.initialStep);
398
+ return {
399
+ currentStepIndex: initialIndex + 1,
400
+ formData: {},
401
+ validating: false,
402
+ formMapRef: {}, // 存储所有步骤表单实例
403
+ };
404
+ },
405
+ computed: {
406
+ // 合并表单属性
407
+ mergedFormProps() {
408
+ return {
409
+ ...this.formProps,
410
+ outlined: this.outlined,
411
+ dense: this.dense,
412
+ };
413
+ },
414
+ },
415
+ watch: {
416
+ value(newVal) {
417
+ if (newVal !== null && newVal + 1 !== this.currentStepIndex) {
418
+ this.currentStepIndex = newVal + 1;
419
+ }
420
+ },
421
+ current(newVal) {
422
+ if (newVal !== null && newVal + 1 !== this.currentStepIndex) {
423
+ this.currentStepIndex = newVal + 1;
424
+ }
425
+ },
426
+ currentStepIndex(newVal, oldVal) {
427
+ const stepIndex = newVal - 1;
428
+
429
+ // 触发 v-model 更新
430
+ this.$emit('input', stepIndex);
431
+
432
+ // 触发 .sync 更新
433
+ this.$emit('update:current', stepIndex);
434
+
435
+ // 触发步骤变化事件
436
+ this.$emit('step-change', {
437
+ current: stepIndex,
438
+ step: this.steps[stepIndex],
439
+ });
440
+
441
+ // 触发 onCurrentChange 回调
442
+ if (this.onCurrentChange && oldVal !== undefined) {
443
+ this.onCurrentChange(stepIndex);
444
+ }
445
+ },
446
+ },
447
+ mounted() {
448
+ // 初始化表单实例映射
449
+ this.updateFormMapRef();
450
+ },
451
+ updated() {
452
+ // 更新表单实例映射
453
+ this.updateFormMapRef();
454
+ },
455
+ methods: {
456
+ // 处理步骤点击
457
+ handleStepClick(index) {
458
+ if (this.editable && index < this.currentStepIndex - 1) {
459
+ this.currentStepIndex = index + 1;
460
+ }
461
+ },
462
+
463
+ // 处理步骤表单数据变化
464
+ handleStepFormChange(index, values) {
465
+ this.$set(this.formData, `step${index}`, values);
466
+ this.$emit('change', this.formData);
467
+
468
+ // 触发 onFormChange 回调
469
+ if (this.onFormChange) {
470
+ this.onFormChange({
471
+ stepIndex: index,
472
+ values,
473
+ allValues: this.formData,
474
+ });
475
+ }
476
+ },
477
+
478
+ // 更新表单实例映射
479
+ updateFormMapRef() {
480
+ const newFormMapRef = {};
481
+ this.steps.forEach((step, index) => {
482
+ if (step.fields) {
483
+ const formRef = this.$refs[`stepForm${index}`];
484
+ if (formRef && formRef[0]) {
485
+ newFormMapRef[index] = formRef[0];
486
+ }
487
+ }
488
+ });
489
+ this.formMapRef = newFormMapRef;
490
+ },
491
+
492
+ // 上一步
493
+ handlePrevious() {
494
+ if (this.currentStepIndex > 1) {
495
+ this.currentStepIndex--;
496
+ }
497
+ },
498
+
499
+ // 下一步
500
+ async handleNext() {
501
+ const currentIndex = this.currentStepIndex - 1;
502
+ const isValid = await this.validateCurrentStep(currentIndex);
503
+
504
+ if (isValid) {
505
+ this.currentStepIndex++;
506
+ }
507
+ },
508
+
509
+ // 完成
510
+ async handleFinish() {
511
+ const currentIndex = this.currentStepIndex - 1;
512
+ const isValid = await this.validateCurrentStep(currentIndex);
513
+
514
+ if (!isValid) {
515
+ return;
516
+ }
517
+
518
+ // 收集所有步骤数据
519
+ let allData = { ...this.formData };
520
+
521
+ // 如果提供了转换函数,则转换数据
522
+ if (this.transformBeforeFinish) {
523
+ allData = this.transformBeforeFinish(allData);
524
+ }
525
+
526
+ this.$emit('finish', allData);
527
+ },
528
+
529
+ // 验证当前步骤
530
+ async validateCurrentStep(stepIndex) {
531
+ this.validating = true;
532
+
533
+ try {
534
+ const step = this.steps[stepIndex];
535
+
536
+ // 如果步骤有表单,验证表单
537
+ if (step.fields) {
538
+ const formRef = this.$refs[`stepForm${stepIndex}`];
539
+ if (formRef && formRef[0]) {
540
+ const isValid = await formRef[0].validate();
541
+ if (!isValid) {
542
+ return false;
543
+ }
544
+ }
545
+ }
546
+
547
+ // 如果提供了自定义验证函数
548
+ if (this.validateStep) {
549
+ const customValidation = await this.validateStep({
550
+ step,
551
+ stepIndex,
552
+ formData: this.formData,
553
+ });
554
+
555
+ if (customValidation === false) {
556
+ return false;
557
+ }
558
+ }
559
+
560
+ return true;
561
+ } catch (error) {
562
+ console.error('步骤验证失败:', error);
563
+ return false;
564
+ } finally {
565
+ this.validating = false;
566
+ }
567
+ },
568
+
569
+ // 跳转到指定步骤
570
+ goToStep(stepIndex) {
571
+ if (stepIndex >= 0 && stepIndex < this.steps.length) {
572
+ this.currentStepIndex = stepIndex + 1;
573
+ }
574
+ },
575
+
576
+ // 重置表单
577
+ reset() {
578
+ this.currentStepIndex = this.initialStep + 1;
579
+ this.formData = {};
580
+
581
+ // 重置所有步骤表单
582
+ this.steps.forEach((step, index) => {
583
+ if (step.fields) {
584
+ const formRef = this.$refs[`stepForm${index}`];
585
+ if (formRef && formRef[0]) {
586
+ formRef[0].resetForm();
587
+ }
588
+ }
589
+ });
590
+ },
591
+
592
+ // 获取所有表单数据
593
+ getFormData() {
594
+ return { ...this.formData };
595
+ },
596
+
597
+ // 设置表单数据
598
+ setFormData(data) {
599
+ this.formData = { ...data };
600
+ },
601
+
602
+ // 获取表单实例映射
603
+ getFormMapRef() {
604
+ return this.formMapRef;
605
+ },
606
+
607
+ // 获取指定步骤的表单实例
608
+ getStepFormRef(stepIndex) {
609
+ return this.formMapRef[stepIndex] || null;
610
+ },
611
+
612
+ // 验证所有步骤
613
+ async validateAll() {
614
+ const results = [];
615
+ for (let i = 0; i < this.steps.length; i++) {
616
+ const isValid = await this.validateCurrentStep(i);
617
+ results.push(isValid);
618
+ }
619
+ return results.every(r => r);
620
+ },
621
+ },
622
+ };
623
+ </script>
624
+
625
+ <style scoped>
626
+ .jh-steps-form-container {
627
+ display: flex;
628
+ width: 100%;
629
+ }
630
+
631
+ /* 垂直布局样式 */
632
+ .jh-steps-form-stepper {
633
+ width: 250px;
634
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
635
+ padding: 16px;
636
+ }
637
+
638
+ .jh-steps-form-content {
639
+ flex: 1;
640
+ padding: 16px 24px;
641
+ }
642
+
643
+ .vertical-steps {
644
+ display: flex;
645
+ flex-direction: column;
646
+ }
647
+
648
+ .vertical-step-item {
649
+ display: flex;
650
+ align-items: flex-start;
651
+ padding: 12px 0;
652
+ position: relative;
653
+ cursor: default;
654
+ }
655
+
656
+ .vertical-step-item.editable {
657
+ cursor: pointer;
658
+ }
659
+
660
+ .vertical-step-item:not(:last-child)::after {
661
+ content: '';
662
+ position: absolute;
663
+ left: 15px;
664
+ top: 40px;
665
+ width: 1px;
666
+ height: calc(100% - 16px);
667
+ background-color: rgba(0, 0, 0, 0.12);
668
+ }
669
+
670
+ .vertical-step-item.completed:not(:last-child)::after {
671
+ background-color: #4caf50;
672
+ }
673
+
674
+ .step-number {
675
+ width: 32px;
676
+ height: 32px;
677
+ border-radius: 50%;
678
+ display: flex;
679
+ align-items: center;
680
+ justify-content: center;
681
+ background-color: rgba(0, 0, 0, 0.12);
682
+ color: rgba(0, 0, 0, 0.6);
683
+ font-size: 14px;
684
+ font-weight: 500;
685
+ margin-right: 12px;
686
+ flex-shrink: 0;
687
+ }
688
+
689
+ .vertical-step-item.active .step-number {
690
+ background-color: #1976d2;
691
+ color: white;
692
+ }
693
+
694
+ .vertical-step-item.completed .step-number {
695
+ background-color: #4caf50;
696
+ color: white;
697
+ }
698
+
699
+ .step-info {
700
+ flex: 1;
701
+ }
702
+
703
+ .step-title {
704
+ font-size: 14px;
705
+ font-weight: 500;
706
+ line-height: 1.2;
707
+ margin-bottom: 4px;
708
+ }
709
+
710
+ .vertical-step-item.active .step-title {
711
+ color: #1976d2;
712
+ }
713
+
714
+ .step-subtitle {
715
+ font-size: 12px;
716
+ color: rgba(0, 0, 0, 0.6);
717
+ line-height: 1.2;
718
+ }
719
+
720
+ .jh-steps-form {
721
+ width: 100%;
722
+ }
723
+
724
+ /* 水平布局时隐藏垂直步骤指示器 */
725
+ .jh-steps-form-container:not(.is-vertical) .jh-steps-form-stepper {
726
+ display: none;
727
+ }
728
+
729
+ .jh-steps-form-container:not(.is-vertical) .jh-steps-form-content {
730
+ width: 100%;
731
+ padding: 0;
732
+ }
733
+
734
+ .step-content {
735
+ min-height: 200px;
736
+ }
737
+
738
+ .step-actions {
739
+ padding-top: 16px;
740
+ border-top: 1px solid rgba(0, 0, 0, 0.12);
741
+ }
742
+
743
+ /* 响应式调整 */
744
+ @media (max-width: 600px) {
745
+ .step-content {
746
+ min-height: 150px;
747
+ }
748
+ }
749
+ </style>