@neatui/vue 2.17.2 → 2.17.5

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.
@@ -1,192 +1,272 @@
1
1
  <template>
2
2
  <div
3
- :ui-form="`${theme} sz:${sz} ${state.co} type:textarea ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''}`"
3
+ :ui-form="`${theme} sz:${sz} ${state.error.co} type:textarea ${block ? ' :block' : ''} ${disabled ? 'disabled' : ''} ${readonly ? 'readonly' : ''}`"
4
4
  @click="emits('click', $event)"
5
5
  >
6
- <div v-if="ready" :ui-form-tips="state.ontips">{{ state.tips }}</div>
6
+ <div :ui-form-tips="state.error.ontips">{{ state.error.tips }}</div>
7
+ <div ui-form-prefix="">
8
+ <template v-for="(item, idx) in prefix" :key="idx">
9
+ <div v-if="isObject(item)" v-bind="item.attrs" v-on="event(item, props.modelValue)">
10
+ <div v-html="isFunction(item.value) ? item.value(props.modelValue) : item.value"></div>
11
+ </div>
12
+ <div v-else v-html="isFunction(item) ? item(props.modelValue) : item"></div>
13
+ </template>
14
+ <slot name="prefix"></slot>
15
+ </div>
7
16
  <slot>
8
17
  <textarea
9
18
  ref="el"
10
- :rows="rows"
11
- v-model="mv"
12
- :type="type"
19
+ :title="props.modelValue"
20
+ :value="props.modelValue"
13
21
  :placeholder="placeholder"
14
- :readonly="readonly || disabled"
15
- @focus="emits('focus', $event)"
16
- @input="emits('input', $event)"
17
- @change="emits('change', $event)"
18
- @blur="emits('blur', $event)"
22
+ :disabled="disabled"
23
+ :readonly="readonly"
24
+ :rows="rows"
19
25
  ui-scroll=":y"
20
26
  dir="auto"
27
+ @focus="emits('focus', $event, state)"
28
+ @input="input($event)"
29
+ @change="emits('change', $event, state)"
30
+ @blur="blur($event)"
21
31
  ></textarea>
22
32
  </slot>
23
- <i v-if="ready && verify" :ui-form-verify="state.verify"></i>
33
+ <div ui-form-suffix="">
34
+ <i ui-form-clean="" v-if="clearable && props.modelValue && !readonly && !disabled" @click.stop="clear"></i>
35
+ <slot name="suffix"></slot>
36
+ <template v-for="(item, idx) in suffix" :key="idx">
37
+ <div v-if="isObject(item)" v-bind="item.attrs" v-on="event(item, props.modelValue)">
38
+ <div v-html="isFunction(item.value) ? item.value(props.modelValue) : item.value"></div>
39
+ </div>
40
+ <div v-else v-html="isFunction(item) ? item(props.modelValue) : item"></div>
41
+ </template>
42
+ </div>
43
+ <i v-if="verify && state.interacted" :ui-form-verify="state.error.verify"></i>
24
44
  </div>
25
45
  </template>
26
46
 
27
47
  <script setup lang="ts">
28
- import { ref, watch, computed, onMounted, PropType, nextTick } from 'vue';
48
+ import { reactive, watch, ref, nextTick, onMounted } from 'vue';
49
+ import { isObject, isFunction } from '@fekit/utils';
29
50
 
30
51
  const emits = defineEmits(['update:modelValue', 'click', 'blur', 'focus', 'change', 'input', 'clear']);
31
- const props = defineProps({
32
- theme: { type: String, default: '@a' },
33
- sz: { type: String, default: '' },
34
- co: { type: String, default: '' },
35
- block: { type: Boolean, default: true },
36
- // 值
37
- modelValue: {
38
- type: [String, Number],
39
- default: '',
40
- },
41
- // 类型
42
- type: {
43
- type: String,
44
- default: 'text',
45
- },
46
- // 提示文案
47
- tips: {
48
- type: String,
49
- default: '',
50
- },
51
- // 内容占位
52
- placeholder: {
53
- type: String,
54
- default: '',
55
- },
56
- // 最大长度
57
- maxLength: {
58
- type: Number,
59
- default: null,
60
- },
61
- // 最小长度
62
- minLength: {
63
- type: Number,
64
- default: null,
65
- },
66
- // 最小长度
67
- autoHeight: {
68
- type: Boolean,
69
- default: false,
70
- },
71
- // 是否禁用
72
- disabled: {
73
- type: Boolean,
74
- default: false,
75
- },
76
- // 是否中读
77
- readonly: {
78
- type: Boolean,
79
- default: false,
80
- },
81
- // 是否显示清除按钮
82
- clearable: {
83
- type: Boolean,
84
- default: true,
85
- },
86
- // 校验规则
87
- rules: {
88
- type: Array,
89
- default: () => [],
90
- },
91
- // 是否是示校验图标
92
- verify: {
93
- type: Boolean,
94
- default: false,
95
- },
96
- prefix: {
97
- type: Array as PropType<any[]>,
98
- default: () => [],
99
- },
100
- suffix: {
101
- type: Array as PropType<any[]>,
102
- default: () => [],
103
- },
104
- rows: {
105
- type: Number,
106
- default: 3,
107
- },
52
+
53
+ /**
54
+ * 入参说明
55
+ *
56
+ * modelValue 值
57
+ * theme 主题
58
+ * sz 尺寸
59
+ * co 颜色
60
+ * block 撑满
61
+ * ready 准备就绪
62
+ * tips 提示文案
63
+ * placeholder 内容占位
64
+ * maxlength 最大长度
65
+ * minlength 最小长度
66
+ * disabled 是否禁用
67
+ * readonly 是否只读
68
+ * clearable 是否显示清除按钮
69
+ * rules 校验规则
70
+ * verify 是否是示校验图标 2=tips 3=text 4=icon 5=tips+text 6=text+icon 7=text+icon
71
+ * prefix 前缀内容
72
+ * suffix 后缀内容
73
+ * interacted 用户是否交互
74
+ * autoHeight 是否自动调整高度
75
+ * rows 初始行数
76
+ */
77
+
78
+ // 类型
79
+ interface Props {
80
+ modelValue?: string | number;
81
+ placeholder?: string;
82
+ rules?: Array<object>;
83
+ theme?: string;
84
+ sz?: '' | 'xs' | 's' | 'm' | 'l' | 'xl';
85
+ co?: string;
86
+ block?: boolean;
87
+ ready?: boolean;
88
+ tips?: string;
89
+ maxlength?: null | number;
90
+ minlength?: null | number;
91
+ disabled?: boolean;
92
+ readonly?: boolean;
93
+ clearable?: boolean;
94
+ verify?: boolean;
95
+ prefix?: Array<any>;
96
+ suffix?: Array<any>;
97
+ interacted?: boolean;
98
+ autoHeight?: boolean;
99
+ rows?: number;
100
+ }
101
+ const props: any = withDefaults(defineProps<Props>(), {
102
+ modelValue: '',
103
+ placeholder: '',
104
+ rules: () => [],
105
+ theme: '@a',
106
+ sz: '',
107
+ co: '',
108
+ block: true,
109
+ ready: false,
110
+ tips: '',
111
+ maxlength: null,
112
+ minlength: null,
113
+ disabled: false,
114
+ readonly: false,
115
+ clearable: true,
116
+ verify: false,
117
+ prefix: () => [],
118
+ suffix: () => [],
119
+ interacted: false,
120
+ // textarea 特有属性默认值
121
+ autoHeight: false,
122
+ rows: 3,
108
123
  });
109
124
 
110
- const ready: any = ref(0);
111
- const state: any = computed(() => {
112
- if (ready.value) {
113
- // 提示文案
114
- let tips = '';
115
- // 错误类型
116
- let type = '';
117
- // 验证结果
118
- let verify = '';
119
- if (props.modelValue) {
120
- // 有值
121
- const res: any = props.rules?.some((rule: any) => {
122
- const yes = rule.validator && !rule.validator(props.modelValue);
123
- tips = rule.msg;
124
- type = rule.type;
125
- return yes;
126
- });
127
- verify = res ? 'no' : 'ok';
128
- } else {
129
- // 无值
130
- const res: any = props.rules?.some((rule: any) => {
131
- tips = rule.msg;
132
- type = rule.type;
133
- return rule.required;
134
- });
135
- verify = res ? 'no' : '';
136
- }
137
- if (verify !== 'no' && props.tips) {
138
- tips = props.tips;
139
- }
140
- // 是否显示提示
141
- const ontips = verify === 'no' && tips ? 'show' : '';
142
- // 状态颜色
143
- const co = verify === 'no' ? 'co:' + type : props.co ? 'co:' + props.co : '';
144
- return { tips, ontips, verify, co };
145
- } else {
146
- return {};
147
- }
125
+ const state: any = reactive({
126
+ // 用户是否交互
127
+ interacted: false,
128
+ // 值
129
+ value: props.modelValue,
130
+ // 错误
131
+ error: {},
148
132
  });
149
133
 
150
- // 自动高度
134
+ // textarea 元素引用
151
135
  const el: any = ref(null);
136
+
137
+ // 自动高度功能
152
138
  const fAutoHeight = () => {
153
139
  nextTick(() => {
154
- if (props.autoHeight) {
140
+ if (props.autoHeight && el.value) {
155
141
  el.value.style.height = 'auto';
156
142
  el.value.style.height = el.value.scrollHeight + 1 + 'px';
157
143
  }
158
144
  });
159
145
  };
160
146
 
161
- const mv: any = ref(props.modelValue);
147
+ // 外部传入交互标识,主要用于外部没有输入但点击了提交
162
148
  watch(
163
- () => props.modelValue,
164
- (v: any) => {
165
- mv.value = v;
149
+ () => props.interacted,
150
+ (interacted: any) => {
151
+ state.interacted = interacted;
166
152
  },
167
153
  { deep: true, immediate: true },
168
154
  );
169
155
 
156
+ // 校验
170
157
  watch(
171
- () => mv.value,
172
- (v: any) => {
173
- let value = v;
174
- if (typeof props.maxLength === 'number' && !isNaN(props.maxLength)) {
175
- value = value.slice(0, props.maxLength);
176
- mv.value = value;
177
- }
178
- emits('update:modelValue', value);
158
+ [() => state.interacted, () => props.modelValue],
159
+ async () => {
160
+ if (state.interacted) {
161
+ // 提示文案
162
+ let tips = '';
163
+ // 错误类型
164
+ let type = '';
165
+ // 验证结果
166
+ let verify = '';
179
167
 
180
- // 自动高度
181
- fAutoHeight();
168
+ if (props.modelValue) {
169
+ // 有值
170
+ let hasError = false;
171
+ // 遍历校验规则
172
+ for (const rule of props.rules) {
173
+ if (rule && rule.validator) {
174
+ const yes = !(await rule.validator(props.modelValue));
175
+ if (yes) {
176
+ tips = rule.msg;
177
+ type = rule.type;
178
+ hasError = true;
179
+ break;
180
+ }
181
+ }
182
+ }
183
+ verify = hasError ? 'no' : 'ok';
184
+ } else {
185
+ // 无值
186
+ let hasError = false;
187
+ // 遍历校验规则
188
+ for (const rule of props.rules) {
189
+ if (rule) {
190
+ tips = rule.msg;
191
+ type = rule.type;
192
+ if (rule.required) {
193
+ hasError = true;
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ verify = hasError ? 'no' : '';
199
+ }
200
+ if (verify !== 'no' && props.tips) {
201
+ tips = props.tips;
202
+ }
203
+ // 是否显示提示
204
+ const ontips = (verify === 'no' && tips) || props.tips ? 'show' : '';
205
+ // 状态颜色
206
+ const co = verify === 'no' ? 'co:' + type : props.co ? 'co:' + props.co : '';
207
+ state.error = { tips, ontips, verify, co };
208
+ } else {
209
+ state.error = { tips: props.tips, ontips: props.tips ? 'show' : '', verify: '', co: '' };
210
+ }
182
211
  },
183
- { deep: true },
212
+ { deep: true, immediate: true },
184
213
  );
185
214
 
215
+ // 清空
216
+ function clear() {
217
+ emits('update:modelValue', null);
218
+ emits('clear');
219
+ }
220
+ // 输入
221
+ const input = (e: any) => {
222
+ // 用户交互
223
+ if (!state.interacted) {
224
+ setTimeout(() => {
225
+ state.interacted = true;
226
+ }, 50);
227
+ }
228
+
229
+ // 长度限制
230
+ const value = e?.target?.value || '';
231
+ if (props.maxlength && value.length > props.maxlength) {
232
+ e.target.value = props.modelValue;
233
+ return false;
234
+ }
235
+ // 输入事件
236
+ emits('input', e, state);
237
+ emits('update:modelValue', value || null);
238
+
239
+ // 自动高度
240
+ fAutoHeight();
241
+ };
242
+ // 失焦
243
+ const blur = (e: any) => {
244
+ if (!state.interacted) {
245
+ setTimeout(() => {
246
+ state.interacted = true;
247
+ }, 50);
248
+ }
249
+ emits('blur', e, state);
250
+ };
251
+
252
+ // 事件处理
253
+ const event: any = (item: any, data: any): object => {
254
+ const _event: any = {};
255
+ if (isObject(item.event)) {
256
+ for (const key in item.event) {
257
+ const fun = item.event[key];
258
+ if (isFunction(fun)) {
259
+ _event[key] = (...args: any) => {
260
+ return fun({ data }, ...args);
261
+ };
262
+ }
263
+ }
264
+ }
265
+ return { ..._event };
266
+ };
267
+
268
+ // 组件挂载后初始化自动高度
186
269
  onMounted(() => {
187
- setTimeout(() => {
188
- ready.value = 1;
189
- }, 300);
190
270
  // 自动高度
191
271
  fAutoHeight();
192
272
  });
@@ -1,9 +1,11 @@
1
1
  import Input from './Input@v3.vue';
2
2
  import InputRange from './InputRange.vue';
3
3
  import Switch from './Switch.vue';
4
- import Textarea from './Textarea@v2.vue';
4
+ import Textarea from './Textarea.vue';
5
5
 
6
6
  import Checkbox from './Checkbox.vue';
7
+ import CheckboxItem from './CheckboxItem.vue';
8
+ import CheckboxGroup from './CheckboxGroup.vue';
7
9
  import Select from './Select@v3.vue';
8
10
  import SelectList from './SelectList.vue';
9
11
  import SelectTags from './SelectTags.vue';
@@ -17,8 +19,6 @@ import SearchMoreSelect from './SearchMoreSelect.vue';
17
19
  import PageSelect from './PageSelect.vue';
18
20
  import SelectTree from './SelectTree/SelectTree@v1.vue';
19
21
 
20
- // import DateView from './DateView.vue';
21
- // import DateView from './DateView@v2.vue';
22
22
  import DateView from './DateView@v3.vue';
23
23
  import DateRangeView from './DateRangeView@v3.vue';
24
24
  import DatePicker from './DatePicker.vue';
@@ -34,6 +34,8 @@ export {
34
34
  Switch,
35
35
  Textarea,
36
36
  Checkbox,
37
+ CheckboxItem,
38
+ CheckboxGroup,
37
39
  Select,
38
40
  SelectList,
39
41
  SelectTags,