hy-app 0.2.18 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ import type { FormColumnsType } from "../../typing";
2
+ import type HyInputProps from "../hy-input/typing";
3
+ import type HyTextareaProps from "../hy-textarea/typing";
4
+ import type HySwitchProps from "../hy-switch/typing";
5
+ import type HyPickerProps from "../hy-picker/typing";
6
+ import type HyRadioProps from "../hy-radio/typing";
7
+ import type HyCheckButtonProps from "../hy-check-button/typing";
8
+
9
+ export default interface HyFormProps {
10
+ /**
11
+ * @description 表单域提示文字的位置
12
+ * left - 左侧
13
+ * top - 上方
14
+ * */
15
+ labelPosition?: "left" | "top";
16
+ /**
17
+ * @description label宽度
18
+ * 数字 - 固定值
19
+ * auto - 自适应
20
+ * */
21
+ labelWidth?: string | number;
22
+ /**
23
+ * @description 是否右对齐
24
+ * */
25
+ right?: boolean;
26
+ /**
27
+ * @description label字体的对齐方式
28
+ * left - 左对齐
29
+ * center - 中间对齐
30
+ * right - 右对齐
31
+ * */
32
+ labelAlign?: HyApp.RowCenterType;
33
+ /**
34
+ * @description 显示冒号符号
35
+ * */
36
+ symbol?: boolean;
37
+ /**
38
+ * @description 显示底部下划线
39
+ * */
40
+ borderBottom?: boolean;
41
+ /**
42
+ * @description 当行内容高度
43
+ * */
44
+ itemHeight?: number | string;
45
+ /**
46
+ * @description 输入框属性api集合
47
+ * */
48
+ input?: Partial<HyInputProps>;
49
+ /**
50
+ * @description 文本域属性api集合
51
+ * */
52
+ textarea?: Partial<HyTextareaProps>;
53
+ /**
54
+ * @description 选择器属性api集合
55
+ * */
56
+ picker?: Partial<HyPickerProps>;
57
+ /**
58
+ * @description 开关属性api集合
59
+ * */
60
+ switchItem?: Partial<HySwitchProps>;
61
+ /**
62
+ * @description 单选属性api集合
63
+ * */
64
+ radio?: Partial<HyRadioProps>;
65
+ /**
66
+ * @description 选择按钮属性api集合
67
+ * */
68
+ checkButton?: Partial<HyCheckButtonProps>;
69
+ /**
70
+ * @description 表单配置
71
+ * */
72
+ columns: FormColumnsType[];
73
+ /**
74
+ * @description 表单值
75
+ * */
76
+ formData: Record<string, any>;
77
+ }
@@ -0,0 +1,199 @@
1
+ <template>
2
+ <view class="hy-form-item" :class="[`hy-form-item--${labelPosition}`]">
3
+ <view v-if="label" class="hy-form-item__label" :style="labelStyle">
4
+ <text v-if="isRequired" class="hy-form-item__label--required">*</text>
5
+ {{ label }}
6
+ </view>
7
+ <view class="hy-form-item__content">
8
+ <slot></slot>
9
+ <view v-if="errorMessage" class="hy-form-item__error">
10
+ {{ errorMessage }}
11
+ </view>
12
+ </view>
13
+ </view>
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ export default {
18
+ name: 'hy-form-item',
19
+ options: {
20
+ addGlobalClass: true,
21
+ virtualHost: true,
22
+ styleIsolation: 'shared',
23
+ },
24
+ }
25
+ </script>
26
+
27
+ <script setup lang="ts">
28
+ import { computed, inject, onMounted, onUnmounted, type PropType, provide, ref } from 'vue'
29
+ import type { FormContext } from './typing'
30
+
31
+ /**
32
+ * 表单组件子组件,需要搭配hy-form
33
+ * @displayName hy-form-item
34
+ */
35
+ defineOptions({})
36
+
37
+ const props = defineProps({
38
+ /**
39
+ * 标签文本
40
+ */
41
+ label: String,
42
+ /**
43
+ * 表单字段名
44
+ */
45
+ prop: String,
46
+ /**
47
+ * 是否必填
48
+ */
49
+ required: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ /**
54
+ * 验证规则
55
+ */
56
+ rules: Object as PropType<any>,
57
+ })
58
+
59
+ const emit = defineEmits<{
60
+ change: [value: any]
61
+ blur: [value: any]
62
+ }>()
63
+
64
+ // 注入表单上下文
65
+ const formContext = inject<FormContext>('formContext')
66
+ const formItem = {
67
+ // 处理子组件事件
68
+ handleChange(value: any) {
69
+ if (props.prop && formContext) {
70
+ formContext.setFieldValue(props.prop, value)
71
+ validate('change')
72
+ }
73
+ emit('change', value)
74
+ },
75
+ handleBlur(value: any) {
76
+ if (props.prop && formContext) {
77
+ validate('blur')
78
+ }
79
+ emit('blur', value)
80
+ },
81
+ }
82
+ provide('formItem', formItem)
83
+
84
+ // 当前组件的引用
85
+ const formItemRef = ref()
86
+
87
+ // 错误信息
88
+ const errorMessage = computed(() => {
89
+ if (!formContext || !props.prop) return ''
90
+ return formContext.errors[props.prop] || ''
91
+ })
92
+
93
+ // 是否必填
94
+ const isRequired = computed(() => {
95
+ if (props.required) return true
96
+ if (!formContext || !props.prop) return false
97
+
98
+ const fieldRules = formContext.rules.value?.[props.prop]
99
+ if (!fieldRules) return false
100
+
101
+ const rules = Array.isArray(fieldRules) ? fieldRules : [fieldRules]
102
+ return rules.some((rule) => rule.required)
103
+ })
104
+
105
+ // 标签样式
106
+ const labelStyle = computed(() => {
107
+ if (!formContext) return {}
108
+
109
+ const style: Record<string, any> = {}
110
+
111
+ if (formContext.labelWidth.value !== 'auto') {
112
+ style.width =
113
+ typeof formContext.labelWidth.value === 'number'
114
+ ? `${formContext.labelWidth.value}px`
115
+ : formContext.labelWidth.value
116
+ }
117
+
118
+ if (formContext.labelAlign.value) {
119
+ style.textAlign = formContext.labelAlign.value
120
+ }
121
+
122
+ return style
123
+ })
124
+
125
+ // 标签位置
126
+ const labelPosition = computed(() => {
127
+ return formContext?.labelPosition?.value || 'left'
128
+ })
129
+
130
+ // 监听表单数据变化
131
+ // watch(
132
+ // () => formContext?.formData[props.prop],
133
+ // (newVal) => {
134
+ // if (props.prop && formContext) {
135
+ // formContext.setFieldValue(props.prop, newVal)
136
+ // validate('change')
137
+ // }
138
+ // },
139
+ // { immediate: true },
140
+ // )
141
+
142
+ // 验证字段
143
+ const validate = (trigger?: 'blur' | 'change') => {
144
+ if (!formContext || !props.prop) return true
145
+
146
+ const value = formContext.getFieldValue(props.prop)
147
+ return formContext.validateField(props.prop, value, trigger)
148
+ }
149
+
150
+ // 重置字段
151
+ const resetField = () => {
152
+ if (!formContext || !props.prop) return
153
+
154
+ formContext.setFieldValue(props.prop, undefined)
155
+ formContext.validateField(props.prop, undefined)
156
+ }
157
+
158
+ // 清除验证
159
+ const clearValidate = () => {
160
+ if (!formContext || !props.prop) return
161
+
162
+ delete formContext.errors[props.prop]
163
+ }
164
+
165
+ // 组件挂载时注册到表单
166
+ onMounted(() => {
167
+ if (formContext) {
168
+ formContext.addFormItem({
169
+ props: props,
170
+ validate,
171
+ resetField,
172
+ clearValidate,
173
+ })
174
+ }
175
+ })
176
+
177
+ // 组件卸载时从表单中移除
178
+ onUnmounted(() => {
179
+ if (formContext) {
180
+ formContext.removeFormItem({
181
+ props: props,
182
+ validate,
183
+ resetField,
184
+ clearValidate,
185
+ })
186
+ }
187
+ })
188
+
189
+ // 暴露方法给父组件
190
+ defineExpose({
191
+ validate,
192
+ resetField,
193
+ clearValidate,
194
+ })
195
+ </script>
196
+
197
+ <style lang="scss" scoped>
198
+ @import './index.scss';
199
+ </style>
@@ -0,0 +1,41 @@
1
+ @use "../../theme.scss" as *;
2
+ @use "../../libs/css/mixin.scss" as *;
3
+
4
+ @include b(form-item) {
5
+ display: flex;
6
+ margin-bottom: 16px;
7
+
8
+ @include m(left) {
9
+ flex-direction: row;
10
+ align-items: flex-start;
11
+ }
12
+
13
+ @include m(top) {
14
+ flex-direction: column;
15
+ }
16
+
17
+ @include e(label) {
18
+ position: relative;
19
+ padding-right: 8px;
20
+ font-size: 14px;
21
+ color: #333;
22
+ line-height: 1.5;
23
+
24
+ @include m(required) {
25
+ color: #ff4d4f;
26
+ margin-right: 2px;
27
+ }
28
+ }
29
+
30
+ @include e(content) {
31
+ flex: 1;
32
+ min-width: 0;
33
+ }
34
+
35
+ @include e(error) {
36
+ margin-top: 4px;
37
+ font-size: 12px;
38
+ color: #ff4d4f;
39
+ line-height: 1.5;
40
+ }
41
+ }
@@ -0,0 +1,43 @@
1
+ export interface FormContext {
2
+ formData: Record<string, any>
3
+ errors: Record<string, string>
4
+ rules: any
5
+ labelWidth: any
6
+ labelPosition: any
7
+ labelAlign: any
8
+ addFormItem: (item: any) => void
9
+ removeFormItem: (item: any) => void
10
+ validateField: (field: string, value: any, trigger?: 'blur' | 'change') => boolean
11
+ setFieldValue: (field: string, value: any) => void
12
+ getFieldValue: (field: string) => any
13
+ }
14
+
15
+ export interface FormItemContext {
16
+ /**
17
+ * 失去焦点触发表单校验
18
+ * */
19
+ handleBlur: (value: string | number) => void
20
+ /**
21
+ * 值改变触发表单校验
22
+ * */
23
+ handleChange: (value: string | number) => void
24
+ }
25
+
26
+ export default interface HyFormItemProps {
27
+ /**
28
+ * 标签文本
29
+ */
30
+ label?: string
31
+ /**
32
+ * 表单字段名
33
+ */
34
+ prop?: string
35
+ /**
36
+ * 是否必填
37
+ */
38
+ required?: boolean
39
+ /**
40
+ * 验证规则
41
+ */
42
+ rules?: any
43
+ }
@@ -103,13 +103,15 @@ export default {
103
103
  </script>
104
104
 
105
105
  <script setup lang="ts">
106
- import { computed, nextTick, ref, toRefs, watch, getCurrentInstance, PropType } from 'vue'
107
- import type { CSSProperties } from 'vue'
106
+ import { computed, nextTick, ref, toRefs, watch, getCurrentInstance, inject } from 'vue'
107
+ import type { CSSProperties, PropType } from 'vue'
108
108
  import HyIcon from '../hy-icon/hy-icon.vue'
109
109
  import { addUnit, formatObject } from '../../utils'
110
110
  import { IconConfig } from '../../config'
111
111
  import type { IInputEmits } from './typing'
112
- import HyIconProps from '@/package/components/hy-icon/typing'
112
+ import type HyIconProps from '../hy-icon/typing'
113
+ import { FormItemContext } from '@/package/components/hy-form-item/typing'
114
+ import { TextAlign } from 'csstype'
113
115
 
114
116
  /**
115
117
  * 为一个输入框,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
@@ -236,7 +238,7 @@ const props = defineProps({
236
238
  * @values left,center,right
237
239
  * */
238
240
  inputAlign: {
239
- type: String,
241
+ type: String as PropType<TextAlign>,
240
242
  default: 'left',
241
243
  },
242
244
  /** 输入框字体的大小 */
@@ -303,6 +305,7 @@ const {
303
305
  placeholderStyle,
304
306
  } = toRefs(props)
305
307
  const emit = defineEmits<IInputEmits>()
308
+ const formItem = inject<FormItemContext>('formItem')
306
309
 
307
310
  const instance = getCurrentInstance()
308
311
  // 清除操作
@@ -424,8 +427,9 @@ const onInput = (e: any) => {
424
427
  /**
425
428
  * @description 输入框失去焦点时触发
426
429
  * */
427
- const onBlur = (event: Event) => {
430
+ const onBlur = (event: any) => {
428
431
  emit('blur', event.detail.value)
432
+ formItem.handleBlur(event.detail.value)
429
433
  // H5端的blur会先于点击清除控件的点击click事件触发,导致focused
430
434
  // 瞬间为false,从而隐藏了清除控件而无法被点击到
431
435
  setTimeout(() => {
@@ -466,6 +470,7 @@ const valueChange = (value: string | number, isOut = false) => {
466
470
  // 标识value值的变化是由内部引起的
467
471
  changeFromInner.value = true
468
472
  emit('change', value)
473
+ formItem.handleChange(value)
469
474
 
470
475
  emit('update:modelValue', value)
471
476
  }