jianghu-ui 1.0.7 → 1.0.8

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.
@@ -5,61 +5,431 @@
5
5
  :class="formClasses"
6
6
  v-bind="mergedFormProps"
7
7
  v-on="formListeners"
8
+ @submit.prevent
8
9
  >
9
- <jh-form-fields
10
- v-model="formData"
11
- :fields="normalizedFields"
12
- :layout="internalLayout"
13
- :show-labels="showLabels"
14
- :label-width="labelWidth"
15
- :label-align="labelAlign"
16
- :show-required-mark="showRequiredMark"
17
- :readonly="readonly"
18
- :disabled="disabled"
19
- :default-dense="defaultDense"
20
- :default-filled="defaultFilled"
21
- :default-outlined="defaultOutlined"
22
- :default-single-line="defaultSingleLine"
23
- :default-cols-md="defaultColsMd"
24
- :hide-details="hideDetails"
25
- :label-class="labelClass"
26
- :input-class="inputClass"
27
- :row-class="rowClass"
28
- :validation-rules="validationRules"
29
- :row-props="gridRowProps"
30
- @field-input="handleFieldInput"
31
- @field-change="handleFieldChange"
32
- @field-blur="handleFieldBlur"
33
- >
34
- <template v-for="field in slotFields" v-slot:[`field-${field.key}`]="slotProps">
35
- <slot :name="`field-${field.key}`" v-bind="slotProps"></slot>
10
+ <!-- 标题区域 -->
11
+ <div v-if="title || $slots.title" class="jh-form-header">
12
+ <slot name="title">
13
+ <div class="jh-form-title">
14
+ {{ title }}
15
+ <v-tooltip v-if="tooltip" bottom>
16
+ <template v-slot:activator="{ on, attrs }">
17
+ <v-icon small class="ml-1" v-bind="attrs" v-on="on">mdi-help-circle-outline</v-icon>
18
+ </template>
19
+ <span>{{ tooltip }}</span>
20
+ </v-tooltip>
21
+ </div>
22
+ </slot>
23
+ <div v-if="description" class="jh-form-description">{{ description }}</div>
24
+ </div>
25
+
26
+ <!-- 字段内容区域 -->
27
+ <v-row :class="rowClass" :dense="dense" v-bind="gridRowProps">
28
+ <template v-for="(field, index) in visibleFields">
29
+ <!-- 分组标题 -->
30
+ <v-col v-if="field.type === 'group'" :key="`group-${index}`" cols="12">
31
+ <div class="jh-form-group-title" :class="field.titleClass">
32
+ <v-divider v-if="field.divider && index > 0" class="mb-4"></v-divider>
33
+ <h3 v-if="field.title" class="text-h6 mb-2">{{ field.title }}</h3>
34
+ <p v-if="field.description" class="text-caption grey--text mb-3">{{ field.description }}</p>
35
+ </div>
36
+ </v-col>
37
+
38
+ <!-- 普通字段 -->
39
+ <v-col
40
+ v-else
41
+ :key="field.key"
42
+ :cols="getFieldCols(field)"
43
+ :sm="getFieldSm(field)"
44
+ :md="getFieldMd(field)"
45
+ :lg="getFieldLg(field)"
46
+ :xl="getFieldXl(field)"
47
+ :class="getFieldColClass(field)"
48
+ >
49
+ <!-- 字段包装器 -->
50
+ <div :class="getFieldWrapperClass(field)">
51
+ <!-- 字段标签 (水平布局) -->
52
+ <div
53
+ v-if="field.label && showLabels && (internalLayout === 'horizontal' || field.layout === 'horizontal')"
54
+ :class="getHorizontalLabelClass(field)"
55
+ :style="getHorizontalLabelStyle(field)"
56
+ >
57
+ <span v-if="(field.required && showRequiredMark)" class="error--text mr-1">*</span>
58
+ {{ field.label }}
59
+ <v-tooltip v-if="field.tooltip" bottom>
60
+ <template v-slot:activator="{ on, attrs }">
61
+ <v-icon small class="ml-1" v-bind="attrs" v-on="on">mdi-help-circle-outline</v-icon>
62
+ </template>
63
+ <span>{{ field.tooltip }}</span>
64
+ </v-tooltip>
65
+ </div>
66
+
67
+ <!-- 字段输入区域 -->
68
+ <div :class="getFieldInputClass(field)">
69
+ <!-- 字段标签 (垂直/行内布局) -->
70
+ <div
71
+ v-if="field.label && showLabels && internalLayout !== 'horizontal' && field.layout !== 'horizontal'"
72
+ :class="getVerticalLabelClass(field)"
73
+ >
74
+ <span v-if="field.required && showRequiredMark" class="error--text">*</span>
75
+ {{ field.label }}
76
+ <v-tooltip v-if="field.tooltip" bottom>
77
+ <template v-slot:activator="{ on, attrs }">
78
+ <v-icon small class="ml-1" v-bind="attrs" v-on="on">mdi-help-circle-outline</v-icon>
79
+ </template>
80
+ <span>{{ field.tooltip }}</span>
81
+ </v-tooltip>
82
+ </div>
83
+
84
+ <!-- 只读模式展示 -->
85
+ <div v-if="isFieldReadonly(field)" class="jh-form-readonly-text">
86
+ {{ getReadonlyValue(field) }}
87
+ </div>
88
+
89
+ <!-- 表单字段 -->
90
+ <template v-else>
91
+ <!-- 文本输入框 -->
92
+ <v-text-field
93
+ v-if="field.type === 'text' || !field.type"
94
+ :class="inputClass"
95
+ :dense="getDense(field)"
96
+ :single-line="getSingleLine(field)"
97
+ :filled="getFilled(field)"
98
+ :outlined="getOutlined(field)"
99
+ :value="getFieldValue(field.key)"
100
+ @input="handleInput(field.key, $event)"
101
+ :rules="getRules(field)"
102
+ :disabled="getFieldDisabled(field)"
103
+ :readonly="field.readonly"
104
+ :placeholder="field.placeholder"
105
+ :prefix="field.prefix"
106
+ :suffix="field.suffix"
107
+ :hide-details="field.hideDetails || hideDetails"
108
+ v-bind="field.props"
109
+ @change="handleChange(field.key, $event)"
110
+ @blur="handleBlur(field.key, $event)"
111
+ ></v-text-field>
112
+
113
+ <!-- 文本域 -->
114
+ <v-textarea
115
+ v-else-if="field.type === 'textarea'"
116
+ :class="inputClass"
117
+ :dense="getDense(field)"
118
+ :filled="getFilled(field)"
119
+ :outlined="getOutlined(field)"
120
+ :value="getFieldValue(field.key)"
121
+ @input="handleInput(field.key, $event)"
122
+ :rules="getRules(field)"
123
+ :disabled="getFieldDisabled(field)"
124
+ :readonly="field.readonly"
125
+ :placeholder="field.placeholder"
126
+ :rows="field.rows || 3"
127
+ :hide-details="field.hideDetails || hideDetails"
128
+ v-bind="field.props"
129
+ @change="handleChange(field.key, $event)"
130
+ @blur="handleBlur(field.key, $event)"
131
+ ></v-textarea>
132
+
133
+ <!-- 数字输入框 -->
134
+ <v-text-field
135
+ v-else-if="field.type === 'number'"
136
+ :class="inputClass"
137
+ type="number"
138
+ :dense="getDense(field)"
139
+ :single-line="getSingleLine(field)"
140
+ :filled="getFilled(field)"
141
+ :outlined="getOutlined(field)"
142
+ :value="getFieldValue(field.key)"
143
+ @input="handleInput(field.key, $event)"
144
+ :rules="getRules(field)"
145
+ :disabled="getFieldDisabled(field)"
146
+ :readonly="field.readonly"
147
+ :placeholder="field.placeholder"
148
+ :hide-details="field.hideDetails || hideDetails"
149
+ v-bind="field.props"
150
+ @change="handleChange(field.key, $event)"
151
+ @blur="handleBlur(field.key, $event)"
152
+ ></v-text-field>
153
+
154
+ <!-- 下拉选择框 -->
155
+ <v-select
156
+ v-else-if="field.type === 'select'"
157
+ :class="inputClass"
158
+ :dense="getDense(field)"
159
+ :filled="getFilled(field)"
160
+ :outlined="getOutlined(field)"
161
+ :value="getFieldValue(field.key)"
162
+ @input="handleInput(field.key, $event)"
163
+ :items="getFieldOptions(field)"
164
+ :rules="getRules(field)"
165
+ :disabled="getFieldDisabled(field)"
166
+ :readonly="field.readonly"
167
+ :placeholder="field.placeholder"
168
+ :item-text="field.itemText || 'text'"
169
+ :item-value="field.itemValue || 'value'"
170
+ :multiple="field.multiple"
171
+ :chips="field.chips"
172
+ :hide-details="field.hideDetails || hideDetails"
173
+ small-chips
174
+ v-bind="field.props"
175
+ @change="handleChange(field.key, $event)"
176
+ @blur="handleBlur(field.key, $event)"
177
+ ></v-select>
178
+
179
+ <!-- 自动完成 -->
180
+ <v-autocomplete
181
+ v-else-if="field.type === 'autocomplete'"
182
+ :class="inputClass"
183
+ :dense="getDense(field)"
184
+ :filled="getFilled(field)"
185
+ :outlined="getOutlined(field)"
186
+ :value="getFieldValue(field.key)"
187
+ @input="handleInput(field.key, $event)"
188
+ :items="getFieldOptions(field)"
189
+ :rules="getRules(field)"
190
+ :disabled="getFieldDisabled(field)"
191
+ :readonly="field.readonly"
192
+ :placeholder="field.placeholder"
193
+ :item-text="field.itemText || 'text'"
194
+ :item-value="field.itemValue || 'value'"
195
+ :hide-details="field.hideDetails || hideDetails"
196
+ v-bind="field.props"
197
+ @change="handleChange(field.key, $event)"
198
+ @blur="handleBlur(field.key, $event)"
199
+ ></v-autocomplete>
200
+
201
+ <!-- 日期选择器 -->
202
+ <v-menu
203
+ v-else-if="field.type === 'date'"
204
+ v-model="dateMenus[field.key]"
205
+ :close-on-content-click="false"
206
+ transition="scale-transition"
207
+ offset-y
208
+ min-width="290px"
209
+ >
210
+ <template v-slot:activator="{ on, attrs }">
211
+ <v-text-field
212
+ :class="inputClass"
213
+ :dense="getDense(field)"
214
+ :filled="getFilled(field)"
215
+ :outlined="getOutlined(field)"
216
+ :value="getFieldValue(field.key)"
217
+ :rules="getRules(field)"
218
+ :disabled="getFieldDisabled(field)"
219
+ :placeholder="field.placeholder"
220
+ :hide-details="field.hideDetails || hideDetails"
221
+ readonly
222
+ v-bind="attrs"
223
+ v-on="on"
224
+ ></v-text-field>
225
+ </template>
226
+ <v-date-picker
227
+ :value="getFieldValue(field.key)"
228
+ @input="dateMenus[field.key] = false; handleInput(field.key, $event)"
229
+ :locale="field.locale || 'zh-cn'"
230
+ v-bind="field.pickerProps"
231
+ ></v-date-picker>
232
+ </v-menu>
233
+
234
+ <!-- 时间选择器 -->
235
+ <v-menu
236
+ v-else-if="field.type === 'time'"
237
+ v-model="timeMenus[field.key]"
238
+ :close-on-content-click="false"
239
+ transition="scale-transition"
240
+ offset-y
241
+ min-width="290px"
242
+ >
243
+ <template v-slot:activator="{ on, attrs }">
244
+ <v-text-field
245
+ :class="inputClass"
246
+ :dense="getDense(field)"
247
+ :filled="getFilled(field)"
248
+ :outlined="getOutlined(field)"
249
+ :value="getFieldValue(field.key)"
250
+ :rules="getRules(field)"
251
+ :disabled="getFieldDisabled(field)"
252
+ :placeholder="field.placeholder"
253
+ :hide-details="field.hideDetails || hideDetails"
254
+ readonly
255
+ v-bind="attrs"
256
+ v-on="on"
257
+ ></v-text-field>
258
+ </template>
259
+ <v-time-picker
260
+ :value="getFieldValue(field.key)"
261
+ @input="timeMenus[field.key] = false; handleInput(field.key, $event)"
262
+ format="24hr"
263
+ v-bind="field.pickerProps"
264
+ ></v-time-picker>
265
+ </v-menu>
266
+
267
+ <!-- 颜色选择器 -->
268
+ <v-menu
269
+ v-else-if="field.type === 'color'"
270
+ v-model="colorMenus[field.key]"
271
+ :close-on-content-click="false"
272
+ transition="scale-transition"
273
+ offset-y
274
+ min-width="320px"
275
+ >
276
+ <template v-slot:activator="{ on, attrs }">
277
+ <v-text-field
278
+ :class="inputClass"
279
+ :dense="getDense(field)"
280
+ :filled="getFilled(field)"
281
+ :outlined="getOutlined(field)"
282
+ :value="getFieldValue(field.key)"
283
+ :rules="getRules(field)"
284
+ :disabled="getFieldDisabled(field)"
285
+ :placeholder="field.placeholder"
286
+ :hide-details="field.hideDetails || hideDetails"
287
+ readonly
288
+ v-on="on"
289
+ v-bind="field.props"
290
+ >
291
+ <template v-slot:append>
292
+ <span
293
+ class="jh-color-preview"
294
+ :style="{ backgroundColor: getFieldValue(field.key) || field.defaultValue || '#ffffff' }"
295
+ ></span>
296
+ </template>
297
+ </v-text-field>
298
+ </template>
299
+ <v-color-picker
300
+ :value="getFieldValue(field.key) || field.defaultValue || '#000000'"
301
+ flat
302
+ :hide-mode-switch="field.hideModeSwitch !== false"
303
+ @input="handleColorInput(field, $event)"
304
+ v-bind="field.pickerProps"
305
+ ></v-color-picker>
306
+ </v-menu>
307
+
308
+ <!-- 滑块 -->
309
+ <v-slider
310
+ v-else-if="field.type === 'slider'"
311
+ :class="inputClass"
312
+ :value="getFieldValue(field.key)"
313
+ @input="handleInput(field.key, $event)"
314
+ :rules="getRules(field)"
315
+ :disabled="getFieldDisabled(field)"
316
+ :readonly="field.readonly"
317
+ :min="field.min !== undefined ? field.min : 0"
318
+ :max="field.max !== undefined ? field.max : 100"
319
+ :step="field.step !== undefined ? field.step : 1"
320
+ :thumb-label="field.thumbLabel"
321
+ :ticks="field.ticks"
322
+ :tick-size="field.tickSize"
323
+ :color="field.color || 'primary'"
324
+ :hide-details="field.hideDetails || hideDetails"
325
+ v-bind="field.props"
326
+ ></v-slider>
327
+
328
+ <!-- 区间滑块 -->
329
+ <v-range-slider
330
+ v-else-if="field.type === 'range-slider'"
331
+ :class="inputClass"
332
+ :value="getFieldValue(field.key)"
333
+ @input="handleInput(field.key, $event)"
334
+ :rules="getRules(field)"
335
+ :disabled="getFieldDisabled(field)"
336
+ :readonly="field.readonly"
337
+ :min="field.min !== undefined ? field.min : 0"
338
+ :max="field.max !== undefined ? field.max : 100"
339
+ :step="field.step !== undefined ? field.step : 1"
340
+ :thumb-label="field.thumbLabel"
341
+ :ticks="field.ticks"
342
+ :tick-size="field.tickSize"
343
+ :color="field.color || 'primary'"
344
+ :hide-details="field.hideDetails || hideDetails"
345
+ v-bind="field.props"
346
+ ></v-range-slider>
347
+
348
+ <!-- 开关 -->
349
+ <v-switch
350
+ v-else-if="field.type === 'switch'"
351
+ :input-value="getFieldValue(field.key)"
352
+ @change="handleChange(field.key, $event)"
353
+ :label="field.switchLabel"
354
+ :disabled="getFieldDisabled(field)"
355
+ :readonly="field.readonly"
356
+ :color="field.color || 'success'"
357
+ :hide-details="field.hideDetails || hideDetails"
358
+ v-bind="field.props"
359
+ ></v-switch>
360
+
361
+ <!-- 复选框 -->
362
+ <v-checkbox
363
+ v-else-if="field.type === 'checkbox'"
364
+ :input-value="getFieldValue(field.key)"
365
+ @change="handleChange(field.key, $event)"
366
+ :label="field.checkboxLabel"
367
+ :disabled="getFieldDisabled(field)"
368
+ :readonly="field.readonly"
369
+ :color="field.color || 'success'"
370
+ :hide-details="field.hideDetails || hideDetails"
371
+ v-bind="field.props"
372
+ ></v-checkbox>
373
+
374
+ <!-- 单选按钮组 -->
375
+ <v-radio-group
376
+ v-else-if="field.type === 'radio'"
377
+ :value="getFieldValue(field.key)"
378
+ @change="handleChange(field.key, $event)"
379
+ :rules="getRules(field)"
380
+ :disabled="getFieldDisabled(field)"
381
+ :readonly="field.readonly"
382
+ :row="field.row !== false"
383
+ :hide-details="field.hideDetails || hideDetails"
384
+ v-bind="field.props"
385
+ >
386
+ <v-radio
387
+ v-for="option in getFieldOptions(field)"
388
+ :key="option.value"
389
+ :label="option.text"
390
+ :value="option.value"
391
+ :color="field.color || 'success'"
392
+ ></v-radio>
393
+ </v-radio-group>
394
+
395
+ <!-- 自定义字段插槽 -->
396
+ <slot
397
+ v-else-if="field.type === 'slot'"
398
+ :name="`field-${field.key}`"
399
+ :field="field"
400
+ :value="getFieldValue(field.key)"
401
+ :formData="formData"
402
+ :updateField="updateField"
403
+ ></slot>
404
+ </template>
405
+ </div>
406
+ </div>
407
+ </v-col>
36
408
  </template>
37
- </jh-form-fields>
38
409
 
39
- <template v-if="isGridLayout">
40
- <v-row class="jh-form-grid-actions-row" v-bind="rowProps">
410
+ <!-- 操作按钮区域 (Grid布局时) -->
411
+ <template v-if="isGridLayout">
41
412
  <v-col cols="12" class="jh-form-actions-col">
42
- <slot name="actions" :formData="formData" :validate="validate" :resetForm="resetForm"></slot>
413
+ <slot name="actions" :formData="formData" :validate="validate" :resetForm="resetForm" :reset="reset"></slot>
43
414
  </v-col>
44
- </v-row>
45
- </template>
46
- <template v-else>
47
- <slot name="actions" :formData="formData" :validate="validate" :resetForm="resetForm"></slot>
48
- </template>
415
+ </template>
416
+ </v-row>
417
+
418
+ <!-- 操作按钮区域 (非Grid布局时) -->
419
+ <div v-if="!isGridLayout" class="jh-form-actions-wrapper">
420
+ <slot name="actions" :formData="formData" :validate="validate" :resetForm="resetForm" :reset="reset"></slot>
421
+ </div>
422
+
423
+ <!-- 底部插槽 -->
424
+ <slot name="footer" :formData="formData"></slot>
49
425
  </v-form>
50
426
  </template>
51
427
 
52
428
  <script>
53
- import JhFormFields from '../JhFormFields/JhFormFields.vue';
54
-
55
429
  export default {
56
430
  name: 'JhForm',
57
431
  inheritAttrs: false,
58
432
 
59
- components: {
60
- JhFormFields,
61
- },
62
-
63
433
  props: {
64
434
  // 表单字段配置
65
435
  fields: {
@@ -67,7 +437,13 @@ export default {
67
437
  default: () => [],
68
438
  },
69
439
 
70
- // 初始表单数据
440
+ // v-model 绑定的值
441
+ value: {
442
+ type: Object,
443
+ default: () => ({}),
444
+ },
445
+
446
+ // 初始表单数据 (当不使用 v-model 时使用)
71
447
  initialData: {
72
448
  type: Object,
73
449
  default: () => ({}),
@@ -76,7 +452,7 @@ export default {
76
452
  // 表单引用名称
77
453
  formRef: {
78
454
  type: String,
79
- default: 'jhForm',
455
+ default: 'form',
80
456
  },
81
457
 
82
458
  // 懒加载验证
@@ -98,13 +474,6 @@ export default {
98
474
  default: true,
99
475
  },
100
476
 
101
- // 标签位置 ('top' | 'left') - 已废弃,使用 layout 替代
102
- labelPosition: {
103
- type: String,
104
- default: 'top',
105
- validator: (v) => ['top', 'left'].includes(v),
106
- },
107
-
108
477
  // 标签宽度 (horizontal 布局时生效)
109
478
  labelWidth: {
110
479
  type: [Number, String],
@@ -166,7 +535,7 @@ export default {
166
535
  // 隐藏详情信息
167
536
  hideDetails: {
168
537
  type: [Boolean, String],
169
- default: true,
538
+ default: false,
170
539
  },
171
540
 
172
541
  // 自定义标签样式类
@@ -246,6 +615,37 @@ export default {
246
615
  type: Object,
247
616
  default: () => ({}),
248
617
  },
618
+
619
+ // 分组标题
620
+ title: {
621
+ type: String,
622
+ default: ''
623
+ },
624
+ // 分组描述
625
+ description: {
626
+ type: String,
627
+ default: ''
628
+ },
629
+ // 标题旁的 tooltip
630
+ tooltip: {
631
+ type: String,
632
+ default: ''
633
+ },
634
+ // 是否显示边框
635
+ bordered: {
636
+ type: Boolean,
637
+ default: false
638
+ },
639
+ // 压缩外边距/间距
640
+ dense: {
641
+ type: Boolean,
642
+ default: false
643
+ },
644
+ // 字段依赖配置
645
+ dependencies: {
646
+ type: Array,
647
+ default: () => []
648
+ },
249
649
  },
250
650
 
251
651
  data() {
@@ -254,6 +654,12 @@ export default {
254
654
  // 表单提交状态
255
655
  submitLoading: false,
256
656
  submitError: null,
657
+
658
+ // 字段状态
659
+ dateMenus: {},
660
+ timeMenus: {},
661
+ colorMenus: {},
662
+ dependencyWatchers: [],
257
663
  };
258
664
  },
259
665
 
@@ -262,6 +668,7 @@ export default {
262
668
  formClasses() {
263
669
  return {
264
670
  'jh-form': true,
671
+ 'jh-form--bordered': this.bordered,
265
672
  [`jh-form--${this.layout}`]: true,
266
673
  'jh-form--readonly': this.readonly,
267
674
  'jh-form--disabled': this.disabled,
@@ -272,18 +679,40 @@ export default {
272
679
  isGridLayout() {
273
680
  return this.grid || this.layout === 'grid';
274
681
  },
682
+
683
+ // 内部布局模式
684
+ internalLayout() {
685
+ return this.isGridLayout ? 'vertical' : this.layout;
686
+ },
275
687
 
276
- // 需要转交给 JhFormFields 的字段配置
277
- normalizedFields() {
278
- if (!this.isGridLayout) {
279
- return this.fields;
280
- }
688
+ // 需要显示的字段
689
+ visibleFields() {
690
+ // 先生成 normalizedFields (处理 col 配置)
691
+ const fields = this.normalizedFields;
692
+
693
+ return fields.filter(field => {
694
+ if (typeof field.visible === 'function') {
695
+ return field.visible(this.formData);
696
+ }
697
+ if (field.visible !== undefined) {
698
+ return field.visible;
699
+ }
700
+ return true;
701
+ });
702
+ },
281
703
 
704
+ // 标准化字段配置 (处理 Grid 列配置)
705
+ normalizedFields() {
282
706
  return this.fields.map(field => {
283
707
  if (field.type === 'group') {
284
708
  return { ...field };
285
709
  }
286
710
 
711
+ // 如果不是 grid 布局,且没有显式指定 cols,则保持原样 (render 时会默认占满或 auto)
712
+ if (!this.isGridLayout && !field.cols) {
713
+ return { ...field };
714
+ }
715
+
287
716
  const bindings = this.getColBindings(field);
288
717
  const colConfig = {};
289
718
 
@@ -292,47 +721,40 @@ export default {
292
721
  if (bindings.md !== undefined) colConfig.md = bindings.md;
293
722
  if (bindings.lg !== undefined) colConfig.lg = bindings.lg;
294
723
  if (bindings.xl !== undefined) colConfig.xl = bindings.xl;
295
-
296
- if (!Object.keys(colConfig).length) {
297
- return { ...field };
724
+
725
+ // 如果计算出了 col 配置,合并到 field.cols
726
+ if (Object.keys(colConfig).length) {
727
+ const existing = field.cols && typeof field.cols === 'object' ? field.cols : (field.cols ? { xs: field.cols } : {});
728
+ return {
729
+ ...field,
730
+ cols: {
731
+ ...existing,
732
+ ...colConfig,
733
+ },
734
+ };
298
735
  }
299
-
300
- const existing = field.cols && typeof field.cols === 'object' ? field.cols : {};
301
- return {
302
- ...field,
303
- cols: {
304
- ...existing,
305
- ...colConfig,
306
- },
307
- };
736
+
737
+ return { ...field };
308
738
  });
309
739
  },
310
740
 
311
- internalLayout() {
312
- return this.isGridLayout ? 'vertical' : this.layout;
313
- },
314
-
315
- slotFields() {
316
- return this.fields.filter(field => field.type === 'slot');
317
- },
318
-
319
741
  gridRowProps() {
320
- return this.isGridLayout ? (this.rowProps || {}) : {};
742
+ return this.rowProps || {};
321
743
  },
322
- // 合并透传属性,只排除组件内部明确处理的属性
744
+
745
+ // 合并透传属性
323
746
  mergedFormProps() {
324
747
  // 只排除组件内部明确处理的属性,其他所有属性都透传给 v-form
325
748
  const excludedAttrs = [
326
749
  'class', 'style',
327
- // 这些属性在组件内部有特殊处理
328
750
  'lazy-validation', 'lazyValidation',
329
- // JhForm 特有的 props(不在 v-form 中)
330
751
  'fields', 'initialData', 'formRef', 'layout', 'showLabels',
331
752
  'labelPosition', 'labelWidth', 'labelAlign', 'showRequiredMark',
332
753
  'readonly', 'disabled', 'defaultDense', 'defaultFilled', 'defaultOutlined',
333
754
  'defaultSingleLine', 'defaultColsMd', 'hideDetails', 'labelClass',
334
755
  'inputClass', 'rowClass', 'validationRules', 'submitter', 'onFinish',
335
- 'onFinishFailed', 'dateFormatter', 'omitNil', 'grid', 'colProps', 'rowProps'
756
+ 'onFinishFailed', 'dateFormatter', 'omitNil', 'grid', 'colProps', 'rowProps',
757
+ 'title', 'description', 'tooltip', 'bordered', 'dense', 'dependencies'
336
758
  ];
337
759
 
338
760
  const { class: cls, style, ...rest } = this.$attrs || {};
@@ -340,7 +762,6 @@ export default {
340
762
 
341
763
  Object.keys(rest).forEach(key => {
342
764
  const keyKebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();
343
- // 只排除明确处理的属性,其他都透传
344
765
  if (!excludedAttrs.includes(key) && !excludedAttrs.includes(keyKebab)) {
345
766
  filteredAttrs[key] = rest[key];
346
767
  }
@@ -348,19 +769,14 @@ export default {
348
769
 
349
770
  return filteredAttrs;
350
771
  },
351
- // 透传所有事件,只排除组件内部明确处理的事件
772
+
773
+ // 透传所有事件
352
774
  formListeners() {
353
- // 只排除组件内部明确处理的事件,其他所有事件都透传给 v-form
354
- const excludedEvents = [
355
- // JhForm 特有的事件(不在 v-form 中)
356
- 'field-change'
357
- ];
358
-
775
+ const excludedEvents = ['field-change', 'submit'];
359
776
  const listeners = { ...this.$listeners || {} };
360
777
  const filteredListeners = {};
361
778
 
362
779
  Object.keys(listeners).forEach(key => {
363
- // 只排除明确处理的事件,其他都透传
364
780
  if (!excludedEvents.includes(key)) {
365
781
  filteredListeners[key] = listeners[key];
366
782
  }
@@ -371,24 +787,50 @@ export default {
371
787
  },
372
788
 
373
789
  watch: {
374
- initialData: {
790
+ value: {
375
791
  handler(val) {
376
- this.formData = { ...val };
792
+ if (JSON.stringify(val) !== JSON.stringify(this.formData)) {
793
+ this.initFormData();
794
+ }
795
+ },
796
+ deep: true,
797
+ },
798
+ initialData: {
799
+ handler() {
800
+ this.initFormData();
377
801
  },
378
802
  immediate: true,
379
803
  deep: true,
380
804
  },
805
+ fields: {
806
+ handler() {
807
+ this.initFormData();
808
+ },
809
+ deep: true,
810
+ },
811
+ dependencies: {
812
+ handler(newDeps) {
813
+ this.setupDependencyWatchers(newDeps);
814
+ },
815
+ immediate: true,
816
+ },
381
817
  },
382
818
 
383
819
  mounted() {
384
- // 初始化表单数据
385
820
  this.initFormData();
821
+ this.setupFieldDependencies();
822
+ },
823
+
824
+ beforeDestroy() {
825
+ this.dependencyWatchers.forEach(unwatch => unwatch());
826
+ this.dependencyWatchers = [];
386
827
  },
387
828
 
388
829
  methods: {
389
830
  // 初始化表单数据
390
831
  initFormData() {
391
- const data = { ...this.initialData };
832
+ const sourceData = { ...this.initialData, ...this.value };
833
+ const data = { ...sourceData };
392
834
 
393
835
  // 从 fields 中提取默认值
394
836
  this.fields.forEach(field => {
@@ -402,9 +844,9 @@ export default {
402
844
 
403
845
  // Grid 模式下合并列配置
404
846
  getColBindings(field) {
847
+ // 只有在 grid 模式下或者显式传递了 colProps 时才生效
405
848
  const bindings = { ...(this.colProps || {}), ...(field.colProps || {}) };
406
849
 
407
- // 兼容字段 cols 写法
408
850
  if (field.cols) {
409
851
  if (typeof field.cols === 'object') {
410
852
  Object.assign(bindings, field.cols);
@@ -414,16 +856,17 @@ export default {
414
856
  }
415
857
 
416
858
  const span = field.colSpan !== undefined ? field.colSpan : bindings.span;
417
- const mappedCols = span !== undefined ? this.mapGridSpan(span) : null;
418
-
419
- if (mappedCols !== null && !('cols' in bindings || 'sm' in bindings || 'md' in bindings || 'lg' in bindings || 'xl' in bindings)) {
420
- bindings.md = mappedCols;
421
- }
422
-
423
- delete bindings.span;
424
-
859
+ // 默认在 grid 模式下,如果没有指定 cols,默认使用 6 (两列) 或者 defaultColsMd
860
+ // 原 JhForm 逻辑:mappedCols 默认为 8 (span=8 -> md=4?) Wait, mapGridSpan(8) = (8/24)*12 = 4.
861
+ // 这里我们尽量保持简单:如果没指定,用 defaultColsMd
862
+
863
+ // 如果没有指定任何 col 配置
425
864
  if (!('cols' in bindings || 'sm' in bindings || 'md' in bindings || 'lg' in bindings || 'xl' in bindings)) {
426
- bindings.md = this.mapGridSpan(this.colProps.span || 8);
865
+ if (span !== undefined) {
866
+ bindings.md = this.mapGridSpan(span);
867
+ } else if (this.isGridLayout) {
868
+ bindings.md = this.defaultColsMd;
869
+ }
427
870
  }
428
871
 
429
872
  return bindings;
@@ -435,93 +878,300 @@ export default {
435
878
  return Math.max(1, Math.min(12, mapped || 1));
436
879
  },
437
880
 
438
- handleFieldInput({ key, value }) {
439
- this.$emit('input', key, value, this.formData);
440
- this.$emit('field-change', { key, value, formData: this.formData });
881
+ // --- 字段渲染辅助方法 ---
882
+
883
+ getFieldValue(key) {
884
+ return this.formData[key];
885
+ },
886
+
887
+ getFieldOptions(field) {
888
+ if (typeof field.options === 'function') {
889
+ return field.options(this.formData);
890
+ }
891
+ return field.options || [];
892
+ },
893
+
894
+ getFieldCols(field) {
895
+ if (this.layout === 'inline') return 'auto';
896
+ if (field.cols) {
897
+ return typeof field.cols === 'object' ? (field.cols.xs || field.cols) : field.cols;
898
+ }
899
+ // 如果没有 cols 属性,但在 grid 模式下,normalizedFields 应该已经注入了 cols。
900
+ // 如果还是没有,且不是 grid 模式,默认 12
901
+ return this.defaultColsMd || 12;
902
+ },
903
+ getFieldSm(field) {
904
+ if (this.layout === 'inline') return 'auto';
905
+ return field.cols && typeof field.cols === 'object' ? field.cols.sm : undefined;
906
+ },
907
+ getFieldMd(field) {
908
+ if (this.layout === 'inline') return 'auto';
909
+ return field.cols && typeof field.cols === 'object' ? field.cols.md : undefined;
910
+ },
911
+ getFieldLg(field) {
912
+ if (this.layout === 'inline') return 'auto';
913
+ return field.cols && typeof field.cols === 'object' ? field.cols.lg : undefined;
914
+ },
915
+ getFieldXl(field) {
916
+ if (this.layout === 'inline') return 'auto';
917
+ return field.cols && typeof field.cols === 'object' ? field.cols.xl : undefined;
918
+ },
919
+
920
+ getFieldColClass(field) {
921
+ return field.colClass || '';
922
+ },
923
+
924
+ getFieldWrapperClass(field) {
925
+ const fieldLayout = field.layout || this.internalLayout;
926
+ const layoutClass = fieldLayout === 'horizontal' ? 'd-flex align-center' : '';
927
+ return `jh-field-wrapper ${layoutClass}`;
928
+ },
929
+
930
+ getHorizontalLabelClass(field) {
931
+ const align = field.labelAlign || this.labelAlign;
932
+ return `jh-field-label jh-input-label jh-field-label--horizontal text-${align} flex-shrink-0`;
933
+ },
934
+
935
+ getHorizontalLabelStyle(field) {
936
+ const width = field.labelWidth || this.labelWidth;
937
+ return {
938
+ width: typeof width === 'number' ? `${width}px` : width,
939
+ minWidth: typeof width === 'number' ? `${width}px` : width,
940
+ };
941
+ },
942
+
943
+ getVerticalLabelClass(field) {
944
+ return `jh-field-label jh-input-label jh-field-label--vertical mb-1`;
945
+ },
946
+
947
+ getFieldInputClass(field) {
948
+ const fieldLayout = field.layout || this.internalLayout;
949
+ return fieldLayout === 'horizontal' ? 'jh-field-input flex-grow-1' : 'jh-field-input';
950
+ },
951
+
952
+ getFieldDisabled(field) {
953
+ if (typeof field.disabled === 'function') {
954
+ return field.disabled(this.formData);
955
+ }
956
+ if (field.disabled !== undefined) {
957
+ return field.disabled;
958
+ }
959
+ return this.disabled;
960
+ },
961
+
962
+ isFieldReadonly(field) {
963
+ if (typeof field.readonly === 'function') {
964
+ return field.readonly(this.formData);
965
+ }
966
+ if (field.readonly !== undefined) {
967
+ return field.readonly;
968
+ }
969
+ return this.readonly;
970
+ },
971
+
972
+ getReadonlyValue(field) {
973
+ const value = this.formData[field.key];
974
+
975
+ if (typeof field.readonlyRender === 'function') {
976
+ return field.readonlyRender(value, this.formData);
977
+ }
978
+
979
+ if ((field.type === 'select' || field.type === 'radio') && field.options) {
980
+ const options = this.getFieldOptions(field);
981
+ if (field.multiple && Array.isArray(value)) {
982
+ return value.map(v => {
983
+ const option = options.find(opt => opt.value === v);
984
+ return option ? option.text : v;
985
+ }).join(', ');
986
+ } else {
987
+ const option = options.find(opt => opt.value === value);
988
+ return option ? option.text : value;
989
+ }
990
+ }
991
+
992
+ if (field.type === 'switch' || field.type === 'checkbox') {
993
+ return value ? '是' : '否';
994
+ }
995
+
996
+ if (field.type === 'range-slider' && Array.isArray(value)) {
997
+ return value.join(' ~ ');
998
+ }
999
+
1000
+ return value || '-';
1001
+ },
1002
+
1003
+ getDense(field) {
1004
+ return field.dense !== undefined ? field.dense : this.defaultDense;
1005
+ },
1006
+ getFilled(field) {
1007
+ return field.filled !== undefined ? field.filled : this.defaultFilled;
1008
+ },
1009
+ getOutlined(field) {
1010
+ return field.outlined !== undefined ? field.outlined : this.defaultOutlined;
1011
+ },
1012
+ getSingleLine(field) {
1013
+ return field.singleLine !== undefined ? field.singleLine : this.defaultSingleLine;
1014
+ },
1015
+
1016
+ getRules(field) {
1017
+ const rules = [];
1018
+ if (field.required) {
1019
+ rules.push(v => !!v || `${field.label || '此字段'}为必填项`);
1020
+ }
1021
+ if (field.rules) {
1022
+ if (Array.isArray(field.rules)) {
1023
+ rules.push(...field.rules);
1024
+ } else if (typeof field.rules === 'string') {
1025
+ const ruleNames = field.rules.split('|');
1026
+ ruleNames.forEach(name => {
1027
+ const trimmedName = name.trim();
1028
+ if (this.validationRules[trimmedName]) {
1029
+ rules.push(...this.validationRules[trimmedName]);
1030
+ }
1031
+ });
1032
+ }
1033
+ }
1034
+ return rules;
1035
+ },
1036
+
1037
+ // --- 事件处理 ---
1038
+
1039
+ handleInput(key, value) {
1040
+ this.$set(this.formData, key, value);
1041
+ this.$emit('input', this.formData);
1042
+ this.$emit('field-input', { key, value, formData: this.formData });
441
1043
  },
442
1044
 
443
- handleFieldChange({ key, value }) {
444
- this.$emit('change', key, value, this.formData);
1045
+ handleChange(key, value) {
1046
+ this.$set(this.formData, key, value);
1047
+ this.$emit('input', this.formData);
1048
+ this.$emit('change', this.formData);
445
1049
  this.$emit('field-change', { key, value, formData: this.formData });
446
1050
  },
1051
+
1052
+ handleColorInput(field, value) {
1053
+ this.handleInput(field.key, value);
1054
+ if (field.closeOnSelect !== false) {
1055
+ this.$set(this.colorMenus, field.key, false);
1056
+ }
1057
+ },
447
1058
 
448
- handleFieldBlur({ key, value }) {
1059
+ handleBlur(key, value) {
449
1060
  this.$emit('blur', key, value, this.formData);
1061
+ this.$emit('field-blur', { key, value, formData: this.formData });
450
1062
  },
451
1063
 
452
- // 更新字段值(供插槽使用)
453
1064
  updateField(key, value) {
454
1065
  this.$set(this.formData, key, value);
455
- this.handleFieldChange({ key, value });
1066
+ this.handleChange(key, value);
456
1067
  },
457
1068
 
458
- // 获取表单引用(供父组件调用) - 与 v-form 保持一致
1069
+ // --- 依赖处理 ---
1070
+
1071
+ setupFieldDependencies() {
1072
+ this.fields.forEach(field => {
1073
+ if (field.dependencies && Array.isArray(field.dependencies)) {
1074
+ field.dependencies.forEach(depKey => {
1075
+ const unwatch = this.$watch(
1076
+ () => this.formData[depKey],
1077
+ (newVal, oldVal) => {
1078
+ if (newVal !== oldVal) {
1079
+ this.handleDependencyChange(field, depKey, newVal, oldVal);
1080
+ }
1081
+ }
1082
+ );
1083
+ this.dependencyWatchers.push(unwatch);
1084
+ });
1085
+ }
1086
+ });
1087
+ },
1088
+
1089
+ setupDependencyWatchers(deps) {
1090
+ // 这里的 dependencies prop 是全局依赖监听,不同于 field.dependencies
1091
+ // 为了保持兼容性,我们保留这个功能
1092
+ // 实际上之前的 JhFormFields 实现是先清除再添加,这里照做
1093
+ // 但注意不要清除了 field dependencies,所以这里应该分开管理?
1094
+ // 之前的代码中 dependencyWatchers 混用了。
1095
+ // 我们这里简单处理:全部 push 到 dependencyWatchers,销毁时一起销毁。
1096
+ // 但是如果 deps 变化了,我们需要只清除这部分的 watcher。
1097
+ // 鉴于复杂性,且通常 dependencies prop 不会动态变化,我们暂且简化。
1098
+ // 如果必须支持动态,需要更复杂的管理。这里假设 dependencies prop 主要用于监听某些字段变化并发射事件。
1099
+
1100
+ deps.forEach(depKey => {
1101
+ const unwatch = this.$watch(
1102
+ () => this.formData[depKey],
1103
+ (newVal, oldVal) => {
1104
+ if (newVal !== oldVal) {
1105
+ this.$emit('dependency-change', { key: depKey, value: newVal, oldValue: oldVal, formData: this.formData });
1106
+ }
1107
+ }
1108
+ );
1109
+ this.dependencyWatchers.push(unwatch);
1110
+ });
1111
+ },
1112
+
1113
+ handleDependencyChange(field, depKey, newVal, oldVal) {
1114
+ if (typeof field.onDependencyChange === 'function') {
1115
+ field.onDependencyChange(depKey, newVal, oldVal, this.formData);
1116
+ }
1117
+ this.$emit('field-dependency-change', {
1118
+ field: field.key,
1119
+ dependency: depKey,
1120
+ value: newVal,
1121
+ oldValue: oldVal,
1122
+ formData: this.formData,
1123
+ });
1124
+ },
1125
+
1126
+ // --- 表单操作 API ---
1127
+
459
1128
  getForm() {
460
- return this.$refs[this.formRef];
1129
+ return this.$refs.form;
461
1130
  },
462
1131
 
463
- // 转发 v-form 的所有方法
1132
+ // 转发 v-form 的方法
464
1133
  ...(() => {
465
1134
  const methods = {};
466
- // 定义需要转发的 v-form 方法
467
1135
  const formMethods = ['validate', 'reset', 'resetValidation'];
468
-
469
1136
  formMethods.forEach(methodName => {
470
1137
  methods[methodName] = function(...args) {
471
- const form = this.$refs[this.formRef];
1138
+ const form = this.$refs.form;
472
1139
  if (form && typeof form[methodName] === 'function') {
473
1140
  return form[methodName](...args);
474
1141
  }
475
- // 如果 v-form 不存在或方法不存在,返回默认值
476
1142
  if (methodName === 'validate') return true;
477
1143
  return undefined;
478
1144
  };
479
1145
  });
480
-
481
1146
  return methods;
482
1147
  })(),
483
1148
 
484
- // 获取表单数据
485
1149
  getFormData() {
486
1150
  return { ...this.formData };
487
1151
  },
488
1152
 
489
- // 设置表单数据
490
1153
  setFieldsValue(values) {
491
1154
  this.formData = { ...this.formData, ...values };
492
1155
  },
493
1156
 
494
- // 设置单个字段值
495
1157
  setFieldValue(key, value) {
496
1158
  this.$set(this.formData, key, value);
497
1159
  },
498
1160
 
499
- // 重置表单 - 与 v-form 的 reset() 方法保持一致
1161
+ // 覆盖 v-form 的 reset,同时重置数据
500
1162
  reset() {
501
- const form = this.$refs[this.formRef];
502
- if (form) {
503
- form.reset();
504
- }
1163
+ const form = this.$refs.form;
1164
+ if (form) form.reset();
505
1165
  this.initFormData();
506
1166
  this.$emit('reset', this.formData);
507
1167
  },
508
1168
 
509
- // 重置表单(别名,保持向后兼容)
510
1169
  resetForm() {
511
1170
  this.reset();
512
1171
  },
513
1172
 
514
- // 重置表单验证 - 与 v-form 的 resetValidation() 方法保持一致
515
- resetValidation() {
516
- const form = this.$refs[this.formRef];
517
- if (form) {
518
- form.resetValidation();
519
- }
520
- },
521
-
522
- // 验证表单 - 与 v-form 的 validate() 方法保持一致
523
1173
  async validate() {
524
- const form = this.$refs[this.formRef];
1174
+ const form = this.$refs.form;
525
1175
  if (form) {
526
1176
  const isValid = await form.validate();
527
1177
  this.$emit('validate', isValid, this.formData);
@@ -530,19 +1180,7 @@ export default {
530
1180
  return true;
531
1181
  },
532
1182
 
533
- // 验证单个字段 - 与 v-form 的 validate() 方法保持一致
534
- async validateField(fieldName) {
535
- const form = this.$refs[this.formRef];
536
- if (form && form.$refs && form.$refs[fieldName]) {
537
- const field = form.$refs[fieldName];
538
- if (field && typeof field.validate === 'function') {
539
- return await field.validate();
540
- }
541
- }
542
- return true;
543
- },
544
-
545
- // 提交表单(增强版,支持加载状态和错误处理)
1183
+ // 提交表单
546
1184
  async submit(options = {}) {
547
1185
  const {
548
1186
  validate = true,
@@ -551,36 +1189,21 @@ export default {
551
1189
  resetError = true
552
1190
  } = options;
553
1191
 
554
- // 重置错误
555
- if (resetError) {
556
- this.submitError = null;
557
- }
558
-
559
- // 显示加载状态
560
- if (showLoading) {
561
- this.submitLoading = true;
562
- }
1192
+ if (resetError) this.submitError = null;
1193
+ if (showLoading) this.submitLoading = true;
563
1194
 
564
1195
  try {
565
- // 验证表单
566
1196
  if (validate) {
567
1197
  const isValid = await this.validate();
568
1198
  if (!isValid) {
569
- // 调用 onFinishFailed 回调
570
- if (this.onFinishFailed) {
571
- this.onFinishFailed(this.formData);
572
- }
1199
+ if (this.onFinishFailed) this.onFinishFailed(this.formData);
573
1200
  return false;
574
1201
  }
575
1202
  }
576
1203
 
577
- // 转换数据
578
1204
  const transformedData = transform ? this.getTransformedData() : { ...this.formData };
579
-
580
- // 触发 submit 事件
581
1205
  this.$emit('submit', transformedData);
582
1206
 
583
- // 调用 onFinish 回调
584
1207
  if (this.onFinish) {
585
1208
  await this.onFinish(transformedData);
586
1209
  }
@@ -592,61 +1215,18 @@ export default {
592
1215
  this.$emit('submit-error', error);
593
1216
  return false;
594
1217
  } finally {
595
- // 隐藏加载状态
596
- if (showLoading) {
597
- this.submitLoading = false;
598
- }
1218
+ if (showLoading) this.submitLoading = false;
599
1219
  }
600
1220
  },
601
-
602
- // 验证单个字段(增强版)
603
- async validateField(fieldName) {
604
- // 首先尝试通过 v-form 验证
605
- const form = this.$refs[this.formRef];
606
- if (form && form.validate) {
607
- // v-form 的 validate 方法会验证所有字段
608
- // 这里我们返回整体验证结果
609
- return await this.validate();
610
- }
611
-
612
- // 如果 v-form 不存在,返回 true
613
- return true;
614
- },
615
-
616
- // 获取表单状态
617
- getFormState() {
618
- return {
619
- submitLoading: this.submitLoading,
620
- submitError: this.submitError,
621
- formData: { ...this.formData }
622
- };
623
- },
624
-
625
- // 清除提交错误
626
- clearSubmitError() {
627
- this.submitError = null;
628
- },
629
-
630
- // 批量更新字段值
631
- batchUpdateFields(values) {
632
- Object.keys(values).forEach(key => {
633
- this.$set(this.formData, key, values[key]);
634
- this.handleFieldChange({ key, value: values[key] });
635
- });
636
- },
637
1221
 
638
- // 获取转换后的数据
639
1222
  getTransformedData() {
640
1223
  const data = { ...this.formData };
641
-
642
- // 应用字段级别的 transform
643
1224
  this.fields.forEach(field => {
644
1225
  if (field.key && field.transform && typeof field.transform === 'function') {
645
1226
  data[field.key] = field.transform(data[field.key], data);
646
1227
  }
647
1228
  });
648
1229
 
649
- // 忽略 null/undefined 值
650
1230
  if (this.omitNil) {
651
1231
  Object.keys(data).forEach(key => {
652
1232
  if (data[key] === null || data[key] === undefined || data[key] === '') {
@@ -657,56 +1237,59 @@ export default {
657
1237
 
658
1238
  return data;
659
1239
  },
660
-
661
- // 获取字段的依赖值
662
- getDependenciesValues(dependencies) {
663
- if (!dependencies || !Array.isArray(dependencies)) return [];
664
- return dependencies.map(key => this.formData[key]);
665
- },
666
-
667
- // 检查依赖是否变化
668
- checkDependenciesChanged(field, oldValues, newValues) {
669
- if (!field.dependencies) return false;
670
-
671
- for (let i = 0; i < field.dependencies.length; i++) {
672
- if (oldValues[i] !== newValues[i]) {
673
- return true;
674
- }
675
- }
676
- return false;
677
- },
678
1240
  },
679
1241
  };
680
1242
  </script>
681
1243
 
682
1244
  <style scoped>
683
- /* 表单布局样式 */
684
1245
  .jh-form {
685
1246
  width: 100%;
686
1247
  }
687
1248
 
688
- /* 水平布局 */
1249
+ .jh-form--bordered {
1250
+ border: 1px solid rgba(0, 0, 0, 0.12);
1251
+ border-radius: 4px;
1252
+ padding: 15px 16px 30px;
1253
+ }
1254
+
1255
+ .jh-form-header {
1256
+ margin-bottom: 16px;
1257
+ }
1258
+
1259
+ .jh-form-title {
1260
+ font-size: 16px;
1261
+ font-weight: 500;
1262
+ color: rgba(0, 0, 0, 0.85);
1263
+ display: flex;
1264
+ align-items: center;
1265
+ }
1266
+
1267
+ .jh-form-description {
1268
+ font-size: 14px;
1269
+ color: rgba(0, 0, 0, 0.45);
1270
+ margin-top: 4px;
1271
+ line-height: 1.5;
1272
+ }
1273
+
1274
+ /* Horizontal Layout */
689
1275
  .jh-form--horizontal .jh-field-label--horizontal {
690
1276
  padding-top: 0;
691
1277
  line-height: 1.5;
692
1278
  }
693
1279
 
694
- /* 行内布局 */
1280
+ /* Inline Layout */
695
1281
  .jh-form--inline .v-input {
696
1282
  margin-bottom: 0 !important;
697
1283
  }
698
1284
 
699
- /* 只读模式 */
1285
+ /* Readonly Mode */
700
1286
  .jh-form--readonly {
701
- /* 只读模式整体样式 */
702
1287
  .jh-field-label {
703
- /* 只读模式下的标签样式 */
704
1288
  color: rgba(0, 0, 0, 0.65);
705
1289
  font-weight: 500;
706
1290
  }
707
1291
 
708
1292
  .jh-form-readonly-text {
709
- /* 只读模式下的文本样式 */
710
1293
  padding: 6px 12px;
711
1294
  color: rgba(0, 0, 0, 0.87);
712
1295
  background-color: rgba(0, 0, 0, 0.04);
@@ -715,33 +1298,12 @@ export default {
715
1298
  transition: all 0.2s ease;
716
1299
  }
717
1300
 
718
- /* 水平布局下的调整 */
719
- &.jh-form--horizontal {
720
- .jh-field-wrapper {
721
- margin-bottom: 16px;
722
- }
723
- }
724
-
725
- /* 垂直布局下的调整 */
726
- &.jh-form--vertical {
727
- .jh-field-wrapper {
728
- margin-bottom: 16px;
729
- }
730
- }
731
-
732
- /* 网格布局下的调整 */
733
- &.jh-form--grid {
734
- .jh-field-wrapper {
735
- margin-bottom: 16px;
736
- }
1301
+ &.jh-form--horizontal .jh-field-wrapper,
1302
+ &.jh-form--vertical .jh-field-wrapper {
1303
+ margin-bottom: 16px;
737
1304
  }
738
1305
  }
739
1306
 
740
- /* 字段标签 */
741
- .jh-field-label {
742
-
743
- }
744
-
745
1307
  .jh-field-label--horizontal {
746
1308
  padding-right: 12px;
747
1309
  }
@@ -750,7 +1312,6 @@ export default {
750
1312
  display: block;
751
1313
  }
752
1314
 
753
- /* 表单分组标题 */
754
1315
  .jh-form-group-title {
755
1316
  margin-top: 8px;
756
1317
  margin-bottom: 16px;
@@ -761,24 +1322,27 @@ export default {
761
1322
  font-weight: 500;
762
1323
  }
763
1324
 
764
- /* 字段包装器 */
765
1325
  .jh-field-wrapper {
766
1326
  width: 100%;
767
1327
  }
768
1328
 
769
- /* 字段输入区域 */
770
1329
  .jh-field-input {
771
1330
  width: 100%;
772
1331
  }
773
1332
 
774
- /* 只读文本 */
775
1333
  .jh-form-readonly-text {
776
1334
  word-break: break-word;
777
1335
  }
778
1336
 
779
- /* 额外提示信息 */
780
- .jh-field-extra {
781
- margin-top: -8px;
782
- line-height: 1.5;
1337
+ .jh-color-preview {
1338
+ display: inline-block;
1339
+ width: 20px;
1340
+ height: 20px;
1341
+ border-radius: 4px;
1342
+ border: 1px solid rgba(0, 0, 0, 0.2);
1343
+ }
1344
+
1345
+ .jh-form-actions-wrapper {
1346
+ margin-top: 16px;
783
1347
  }
784
- </style>
1348
+ </style>