hy-app 0.2.18 → 0.3.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.
@@ -0,0 +1,201 @@
1
+ <template>
2
+ <view
3
+ class="hy-form-item"
4
+ :class="[`hy-form-item--${labelPosition}`, formContext.border && 'hy-border__bottom']"
5
+ >
6
+ <view v-if="label" class="hy-form-item__label" :style="labelStyle">
7
+ <text v-if="isRequired" class="hy-form-item__label--required">*</text>
8
+ {{ label }}
9
+ </view>
10
+ <view class="hy-form-item__content">
11
+ <slot></slot>
12
+ <view v-if="errorMessage" class="hy-form-item__error">
13
+ {{ errorMessage }}
14
+ </view>
15
+ </view>
16
+ </view>
17
+ </template>
18
+
19
+ <script lang="ts">
20
+ export default {
21
+ name: 'hy-form-item',
22
+ options: {
23
+ addGlobalClass: true,
24
+ virtualHost: true,
25
+ styleIsolation: 'shared',
26
+ },
27
+ }
28
+ </script>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, inject, onMounted, onUnmounted, provide, ref } from 'vue'
32
+ import type { PropType } from 'vue'
33
+ import type { FormContext } from './typing'
34
+ import { addUnit } from '../../utils'
35
+
36
+ /**
37
+ * 表单组件子组件,需要搭配hy-form
38
+ * @displayName hy-form-item
39
+ */
40
+ defineOptions({})
41
+
42
+ const props = defineProps({
43
+ /**
44
+ * 标签文本
45
+ */
46
+ label: String,
47
+ /**
48
+ * 表单字段名
49
+ */
50
+ prop: String,
51
+ /**
52
+ * 是否必填
53
+ */
54
+ required: {
55
+ type: Boolean,
56
+ default: false,
57
+ },
58
+ /**
59
+ * 验证规则
60
+ */
61
+ rules: Object as PropType<any>,
62
+ })
63
+
64
+ const emit = defineEmits<{
65
+ change: [value: any]
66
+ blur: [value: any]
67
+ }>()
68
+
69
+ // 注入表单上下文
70
+ const formContext = inject<FormContext>('formContext')
71
+ const formItem = {
72
+ // 处理子组件事件
73
+ handleChange(value: any) {
74
+ if (props.prop && formContext) {
75
+ formContext.setFieldValue(props.prop, value)
76
+ validate('change')
77
+ }
78
+ emit('change', value)
79
+ },
80
+ handleBlur(value: any) {
81
+ if (props.prop && formContext) {
82
+ validate('blur')
83
+ }
84
+ emit('blur', value)
85
+ },
86
+ }
87
+ provide('formItem', formItem)
88
+
89
+ // 当前组件的引用
90
+ const formItemRef = ref()
91
+
92
+ // 错误信息
93
+ const errorMessage = computed(() => {
94
+ if (!formContext || !props.prop) return ''
95
+ return formContext.errors[props.prop] || ''
96
+ })
97
+
98
+ // 是否必填
99
+ const isRequired = computed(() => {
100
+ if (props.required) return true
101
+ if (!formContext || !props.prop) return false
102
+
103
+ const fieldRules = formContext.rules.value?.[props.prop]
104
+ if (!fieldRules) return false
105
+
106
+ const rules = Array.isArray(fieldRules) ? fieldRules : [fieldRules]
107
+ return rules.some((rule) => rule.required)
108
+ })
109
+
110
+ // 标签样式
111
+ const labelStyle = computed(() => {
112
+ if (!formContext) return {}
113
+
114
+ const style: Record<string, any> = {}
115
+
116
+ if (formContext.labelWidth !== 'auto') {
117
+ style.width = addUnit(formContext.labelWidth)
118
+ }
119
+
120
+ if (formContext.labelAlign) {
121
+ style.textAlign = formContext.labelAlign
122
+ }
123
+
124
+ return style
125
+ })
126
+
127
+ // 标签位置
128
+ const labelPosition = computed(() => {
129
+ return formContext?.labelPosition || 'left'
130
+ })
131
+
132
+ // 监听表单数据变化
133
+ // watch(
134
+ // () => formContext?.formData[props.prop],
135
+ // (newVal) => {
136
+ // if (props.prop && formContext) {
137
+ // formContext.setFieldValue(props.prop, newVal)
138
+ // validate('change')
139
+ // }
140
+ // },
141
+ // { immediate: true },
142
+ // )
143
+
144
+ // 验证字段
145
+ const validate = (trigger?: 'blur' | 'change') => {
146
+ if (!formContext || !props.prop) return true
147
+
148
+ const value = formContext.getFieldValue(props.prop)
149
+ return formContext.validateField(props.prop, value, trigger)
150
+ }
151
+
152
+ // 重置字段
153
+ const resetField = () => {
154
+ if (!formContext || !props.prop) return
155
+
156
+ formContext.setFieldValue(props.prop, undefined)
157
+ formContext.validateField(props.prop, undefined)
158
+ }
159
+
160
+ // 清除验证
161
+ const clearValidate = () => {
162
+ if (!formContext || !props.prop) return
163
+
164
+ delete formContext.errors[props.prop]
165
+ }
166
+
167
+ // 组件挂载时注册到表单
168
+ onMounted(() => {
169
+ if (formContext) {
170
+ formContext.addFormItem({
171
+ props: props,
172
+ validate,
173
+ resetField,
174
+ clearValidate,
175
+ })
176
+ }
177
+ })
178
+
179
+ // 组件卸载时从表单中移除
180
+ onUnmounted(() => {
181
+ if (formContext) {
182
+ formContext.removeFormItem({
183
+ props: props,
184
+ validate,
185
+ resetField,
186
+ clearValidate,
187
+ })
188
+ }
189
+ })
190
+
191
+ // 暴露方法给父组件
192
+ defineExpose({
193
+ validate,
194
+ resetField,
195
+ clearValidate,
196
+ })
197
+ </script>
198
+
199
+ <style lang="scss" scoped>
200
+ @import './index.scss';
201
+ </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: $hy-text-color;
22
+ line-height: 2.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,40 @@
1
+ import HyFormSimpleProps from '../hy-form/typing'
2
+
3
+ export interface FormContext extends HyFormSimpleProps {
4
+ errors: Record<string, string>
5
+ addFormItem: (item: any) => void
6
+ removeFormItem: (item: any) => void
7
+ validateField: (field: string, value: any, trigger?: 'blur' | 'change') => boolean
8
+ setFieldValue: (field: string, value: any) => void
9
+ getFieldValue: (field: string) => any
10
+ }
11
+
12
+ export interface FormItemContext {
13
+ /**
14
+ * 失去焦点触发表单校验
15
+ * */
16
+ handleBlur: (value: string | number) => void
17
+ /**
18
+ * 值改变触发表单校验
19
+ * */
20
+ handleChange: (value: string | number) => void
21
+ }
22
+
23
+ export default interface HyFormItemProps {
24
+ /**
25
+ * 标签文本
26
+ */
27
+ label?: string
28
+ /**
29
+ * 表单字段名
30
+ */
31
+ prop?: string
32
+ /**
33
+ * 是否必填
34
+ */
35
+ required?: boolean
36
+ /**
37
+ * 验证规则
38
+ */
39
+ rules?: any
40
+ }
@@ -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
  }
@@ -21,52 +21,52 @@
21
21
  </template>
22
22
 
23
23
  <script setup lang="ts">
24
- import { ref, reactive, onMounted, computed } from "vue";
25
- import { onHide } from "@dcloudio/uni-app";
26
- import { storeToRefs } from "pinia";
27
- import { useUserInfo } from "../../store";
28
- import { decryptData, encryptData } from "../../utils";
29
- import { FormTypeEnum } from "../../typing";
30
- import { IconConfig } from "../../config";
31
- import type { UserLoginInfoVo } from "./typing";
24
+ import { ref, reactive, onMounted, computed } from 'vue'
25
+ import { onHide } from '@dcloudio/uni-app'
26
+ import { storeToRefs } from 'pinia'
27
+ import { useUserInfo } from '../../store'
28
+ import { decryptData, encryptData } from '../../utils'
29
+ import { FormTypeEnum } from '../../typing'
30
+ import { IconConfig } from '../../config'
31
+ import type { UserLoginInfoVo } from './typing'
32
32
 
33
33
  // 组件
34
- import HyCheckbox from "../hy-checkbox/hy-checkbox.vue";
35
- import HyForm from "../hy-form/hy-form.vue";
34
+ import HyCheckbox from '../hy-checkbox/hy-checkbox.vue'
35
+ import HyForm from '@/package/components/hy-form-group/hy-form-group.vue'
36
36
 
37
37
  interface IProps {
38
- themeColor: string;
39
- prefix: string;
40
- isShowPwd: boolean;
41
- userPlaceholder: string;
42
- pwdPlaceholder: string;
43
- customUserValidator: Record<string, any>;
44
- customPwdValidator: Record<string, any>;
45
- userNumValidator: Record<string, any>;
46
- pwdNumValidator: Record<string, any>;
38
+ themeColor: string
39
+ prefix: string
40
+ isShowPwd: boolean
41
+ userPlaceholder: string
42
+ pwdPlaceholder: string
43
+ customUserValidator: Record<string, any>
44
+ customPwdValidator: Record<string, any>
45
+ userNumValidator: Record<string, any>
46
+ pwdNumValidator: Record<string, any>
47
47
  }
48
48
 
49
49
  const props = withDefaults(defineProps<IProps>(), {
50
- themeColor: "",
51
- prefix: "hy",
50
+ themeColor: '',
51
+ prefix: 'hy',
52
52
  isShowPwd: false,
53
- userPlaceholder: "",
54
- pwdPlaceholder: "",
53
+ userPlaceholder: '',
54
+ pwdPlaceholder: '',
55
55
  customUserValidator: () => ({}),
56
56
  customPwdValidator: () => ({}),
57
57
  userNumValidator: () => ({}),
58
58
  pwdNumValidator: () => ({}),
59
- });
60
- const emit = defineEmits(["handleHistory", "handleCheckbox"]);
61
- const userInfoStore = useUserInfo();
62
- const { userForm, choiceList, rememberPsw } = storeToRefs(userInfoStore);
59
+ })
60
+ const emit = defineEmits(['handleHistory', 'handleCheckbox'])
61
+ const userInfoStore = useUserInfo()
62
+ const { userForm, choiceList, rememberPsw } = storeToRefs(userInfoStore)
63
63
 
64
- const showChoice = ref<boolean>(false);
65
- const showPwd = ref<boolean>(false);
64
+ const showChoice = ref<boolean>(false)
65
+ const showPwd = ref<boolean>(false)
66
66
  const userColumns = computed(() => [
67
67
  {
68
- field: "userName",
69
- label: "",
68
+ field: 'userName',
69
+ label: '',
70
70
  type: FormTypeEnum.TEXT,
71
71
  input: {
72
72
  clearable: true,
@@ -76,20 +76,18 @@ const userColumns = computed(() => [
76
76
  color: props.themeColor,
77
77
  },
78
78
  suffixIcon: {
79
- name: showChoice.value
80
- ? IconConfig.ARROW_UP_FILL
81
- : IconConfig.ARROW_DOWN_FILL,
79
+ name: showChoice.value ? IconConfig.ARROW_UP_FILL : IconConfig.ARROW_DOWN_FILL,
82
80
  color: props.themeColor,
83
81
  },
84
82
  onSuffix: () => {
85
- showChoice.value = !showChoice.value;
83
+ showChoice.value = !showChoice.value
86
84
  },
87
85
  },
88
86
  rules: [props.customUserValidator, props.userNumValidator],
89
87
  },
90
88
  {
91
- field: "password",
92
- label: "",
89
+ field: 'password',
90
+ label: '',
93
91
  type: showPwd.value ? FormTypeEnum.TEXT : FormTypeEnum.PASSWORD,
94
92
  input: {
95
93
  clearable: true,
@@ -103,22 +101,22 @@ const userColumns = computed(() => [
103
101
  color: props.themeColor,
104
102
  },
105
103
  onSuffix: () => {
106
- showPwd.value = !showPwd.value;
104
+ showPwd.value = !showPwd.value
107
105
  },
108
106
  },
109
107
  rules: [props.customUserValidator, props.pwdNumValidator],
110
108
  },
111
- ]);
112
- const rememberList = reactive([{ label: "记住密码", value: 1 }]);
113
- const form_1Ref = ref<InstanceType<typeof HyForm>>();
109
+ ])
110
+ const rememberList = reactive([{ label: '记住密码', value: 1 }])
111
+ const form_1Ref = ref<InstanceType<typeof HyForm>>()
114
112
  // 效验用户名和密码
115
113
  const userRules = reactive({
116
114
  userName: [
117
115
  {
118
116
  required: true,
119
- message: "请先输入账号",
117
+ message: '请先输入账号',
120
118
  // 可以单个或者同时写两个触发验证方式
121
- trigger: ["blur", "change"],
119
+ trigger: ['blur', 'change'],
122
120
  },
123
121
  props.customUserValidator,
124
122
  props.userNumValidator,
@@ -126,44 +124,44 @@ const userRules = reactive({
126
124
  password: [
127
125
  {
128
126
  required: true,
129
- message: "请输入密码",
127
+ message: '请输入密码',
130
128
  // 可以单个或者同时写两个触发验证方式
131
- trigger: ["blur", "change"],
129
+ trigger: ['blur', 'change'],
132
130
  },
133
131
  props.pwdNumValidator,
134
132
  props.customPwdValidator,
135
133
  ],
136
- });
137
- const rememberPassword = ref(false);
138
- const account = uni.getStorageSync(`${props.prefix}_account`);
139
- const accountList = uni.getStorageSync(`${props.prefix}_choiceList`);
134
+ })
135
+ const rememberPassword = ref(false)
136
+ const account = uni.getStorageSync(`${props.prefix}_account`)
137
+ const accountList = uni.getStorageSync(`${props.prefix}_choiceList`)
140
138
 
141
139
  onMounted(() => {
142
- if (!account) return;
143
- const result = decryptData(account);
144
- console.log(result);
140
+ if (!account) return
141
+ const result = decryptData(account)
142
+ console.log(result)
145
143
  //有缓存就赋值给文本没有就清空
146
- rememberPsw.value = result?.rememberPsw;
144
+ rememberPsw.value = result?.rememberPsw
147
145
  //获取缓存的账号和密码
148
- userForm.value.userName = result?.userName;
149
- userForm.value.password = result?.password;
146
+ userForm.value.userName = result?.userName
147
+ userForm.value.password = result?.password
150
148
 
151
149
  if (accountList) {
152
- choiceList.value = decryptData(accountList) as UserLoginInfoVo[];
150
+ choiceList.value = decryptData(accountList) as UserLoginInfoVo[]
153
151
  }
154
- });
152
+ })
155
153
 
156
154
  onHide(() => {
157
155
  // if (!account) return;
158
156
  //获取缓存的账号和密码
159
- const { userName, password } = decryptData(account);
157
+ const { userName, password } = decryptData(account)
160
158
  if (choiceList.value.length) {
161
159
  // 过滤数判断是否有一样的账号
162
160
  const filterArr = choiceList.value.filter((item) => {
163
- return item.user === userName;
164
- });
161
+ return item.user === userName
162
+ })
165
163
  // 有一样的账号退出函数不执行下面的
166
- if (filterArr.length) return;
164
+ if (filterArr.length) return
167
165
  }
168
166
  // 判断是否有保存账号和密码
169
167
  if (userName && password) {
@@ -171,17 +169,14 @@ onHide(() => {
171
169
  choiceList.value.unshift({
172
170
  user: userName,
173
171
  pwd: password,
174
- });
172
+ })
175
173
  // 数组最多只放三个账号
176
174
  if (choiceList.value.length >= 5) {
177
- choiceList.value.splice(5, 1);
175
+ choiceList.value.splice(5, 1)
178
176
  }
179
- uni.setStorageSync(
180
- `${props.prefix}_choiceList`,
181
- encryptData(choiceList.value),
182
- );
177
+ uni.setStorageSync(`${props.prefix}_choiceList`, encryptData(choiceList.value))
183
178
  }
184
- });
179
+ })
185
180
 
186
181
  /**
187
182
  * 登录效验
@@ -191,30 +186,30 @@ const loginFn = () => {
191
186
  form_1Ref.value
192
187
  ?.handleSubmit()
193
188
  .then((res) => {
194
- resolve("success" + res);
189
+ resolve('success' + res)
195
190
  })
196
191
  .catch((err) => {
197
- reject("error" + err);
198
- });
199
- });
200
- };
192
+ reject('error' + err)
193
+ })
194
+ })
195
+ }
201
196
 
202
197
  /**
203
198
  * 勾选是否记住密码
204
199
  * */
205
200
  const checkboxChange = () => {
206
- emit("handleCheckbox", rememberPassword.value);
207
- };
201
+ emit('handleCheckbox', rememberPassword.value)
202
+ }
208
203
 
209
204
  /**
210
205
  * 选择历史账号
211
206
  * */
212
207
  const btnChoiceClick = (index: number) => {
213
- showChoice.value = false;
214
- userForm.value.name = choiceList.value[index].user;
215
- userForm.value.pwd = choiceList.value[index].pwd;
216
- emit("handleHistory");
217
- };
208
+ showChoice.value = false
209
+ userForm.value.name = choiceList.value[index].user
210
+ userForm.value.pwd = choiceList.value[index].pwd
211
+ emit('handleHistory')
212
+ }
218
213
 
219
214
  /**
220
215
  * 长按操作历史账户
@@ -224,21 +219,18 @@ const btnChoiceClick = (index: number) => {
224
219
  const extensionFun = (index: number, username: string) => {
225
220
  switch (index) {
226
221
  case 0:
227
- const i = choiceList.value.findIndex((item) => item.user === username);
228
- choiceList.value.splice(i, 1);
229
- uni.setStorageSync(
230
- `${props.prefix}_choiceList`,
231
- encryptData(choiceList.value),
232
- );
233
- break;
222
+ const i = choiceList.value.findIndex((item) => item.user === username)
223
+ choiceList.value.splice(i, 1)
224
+ uni.setStorageSync(`${props.prefix}_choiceList`, encryptData(choiceList.value))
225
+ break
234
226
  default:
235
- break;
227
+ break
236
228
  }
237
- };
229
+ }
238
230
 
239
231
  defineExpose({
240
232
  loginFn,
241
- });
233
+ })
242
234
  </script>
243
235
 
244
236
  <style lang="scss" scoped>
@@ -28,8 +28,8 @@
28
28
  <view v-if="!slots.left" class="hy-swipe-action--right__action">
29
29
  <view
30
30
  class="hy-swipe-action--right__action-btn"
31
- :style="item.style"
32
31
  v-for="(item, i) in options"
32
+ :style="item.style"
33
33
  @tap.stop="onActiveClick(item, i)"
34
34
  >
35
35
  {{ item.text }}
@@ -61,10 +61,15 @@ import {
61
61
  ref,
62
62
  watch,
63
63
  useSlots,
64
- PropType,
64
+ type PropType,
65
65
  } from 'vue'
66
- import type { ISwipeActionEmits } from './typing'
67
- import type { SwipeActionStatus, SwipeActionPosition, SwipeActionReason } from './typing'
66
+ import type {
67
+ ISwipeActionEmits,
68
+ SwipeActionStatus,
69
+ SwipeActionPosition,
70
+ SwipeActionReason,
71
+ SwipeActionOptionsVo,
72
+ } from './typing'
68
73
  import { useTouch } from '../../composables'
69
74
  import { closeOther, pushToQueue, removeFromQueue } from './index'
70
75
  import { getRect, guid } from '../../utils'
@@ -81,7 +86,7 @@ const props = defineProps({
81
86
  * 滑动按钮的状态,使用v-model进行双向绑定。
82
87
  * @values left,close,right
83
88
  * */
84
- modelValue: String,
89
+ modelValue: String as PropType<SwipeActionStatus>,
85
90
  /** 是否禁用滑动操作 */
86
91
  disabled: {
87
92
  type: Boolean,
@@ -94,7 +99,7 @@ const props = defineProps({
94
99
  },
95
100
  /** 右侧按钮内容 */
96
101
  options: {
97
- type: Array as unknown as PropType<SwipeActionStatus>,
102
+ type: Array as unknown as PropType<SwipeActionOptionsVo[]>,
98
103
  default: () => [
99
104
  {
100
105
  text: '收藏',
@@ -115,6 +120,8 @@ const props = defineProps({
115
120
  type: Number,
116
121
  default: 300,
117
122
  },
123
+ /** 关闭滑动按钮前的钩子函数 */
124
+ beforeClose: Function,
118
125
  })
119
126
  const emit = defineEmits<ISwipeActionEmits>()
120
127
  const leftClass = `hy-swipe-action--left--${guid()}`
@@ -1,6 +1,6 @@
1
1
  import type { CSSProperties, PropType } from 'vue'
2
2
 
3
- interface SwipeActionOptionsVo {
3
+ export interface SwipeActionOptionsVo {
4
4
  text: string
5
5
  style?: CSSProperties
6
6
  icon?: string