Field.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <template>
  2. <FlexView
  3. :touchable="touchable || childOnClickListener !== undefined"
  4. :pressedColor="themeContext.resolveThemeColor('FieldPressedColor', 'pressed.white')"
  5. :innerStyle="{
  6. ...themeStyles.field.value,
  7. ...(labelPosition === 'top' ? themeStyles.fieldVertical.value : {}),
  8. ...fieldStyle,
  9. ...(focused ? activeFieldStyle : {}),
  10. ...(error || finalErrorMessage ? errorFieldStyle : {})
  11. }"
  12. :direction="labelPosition === 'top' ? 'column' : 'row'"
  13. :center="labelPosition === 'top' ? false : true"
  14. @click="onClick"
  15. >
  16. <!-- 左边的标签区域 -->
  17. <FlexRow
  18. v-if="showLabel !== false && label"
  19. align="center"
  20. :flex="labelFlex"
  21. :innerStyle="{
  22. width: labelWidth,
  23. }"
  24. :justify="selectStyleType(labelAlign, 'left', {
  25. left: 'flex-start',
  26. center: 'center',
  27. right: 'flex-end',
  28. })"
  29. >
  30. <text v-if="requiredShow && showRequiredBadge" :style="themeStyles.requiredMark.value">*</text>
  31. <slot name="leftIcon" />
  32. <view v-if="!label" :style="labelStyle" />
  33. <text v-else :style="{
  34. ...themeStyles.labelText.value,
  35. ...labelStyle,
  36. color: themeContext.resolveThemeColor(disabled ? labelDisableColor : labelColor),
  37. }">
  38. {{ label + (colon ? ': ' : '') }}
  39. </text>
  40. </FlexRow>
  41. <!-- 输入框区域 -->
  42. <FlexCol :flex="inputFlex">
  43. <FlexRow
  44. justify="space-between"
  45. align="center"
  46. :innerStyle="themeStyles.inputWapper2.value"
  47. >
  48. <slot name="prefix" />
  49. <slot name="leftButton" />
  50. <slot name="control" />
  51. <slot>
  52. <textarea
  53. v-if="multiline"
  54. ref="inputRef"
  55. :style="{
  56. ...themeStyles.input.value,
  57. ...inputStyle,
  58. ...(focused ? activeInputStyle : {}),
  59. color: themeContext.resolveThemeColor(disabled ? inputDisableColor : (error ? errorTextColor : inputColor)),
  60. textAlign: inputAlign,
  61. }"
  62. :autoHeight="autoHeight"
  63. :value="inputValue"
  64. :password="type==='password'"
  65. :placeholder="placeholder"
  66. :placeholder-style="`color: ${themeContext.resolveThemeColor(error ? errorTextColor : placeholderTextColor)}`"
  67. confirm-type="done"
  68. :maxlength="maxLength"
  69. :disabled="disabled"
  70. :readonly="readonly"
  71. @input="onInput"
  72. @focus="onFocus"
  73. @blur="onBlur"
  74. @confirm="onBlur"
  75. @click="onClick"
  76. />
  77. <input
  78. v-else
  79. ref="inputRef"
  80. :style="{
  81. ...themeStyles.input.value,
  82. ...inputStyle,
  83. ...(focused ? activeInputStyle : {}),
  84. color: themeContext.resolveThemeColor(disabled ? inputDisableColor : (error ? errorTextColor : inputColor)),
  85. textAlign: inputAlign,
  86. }"
  87. :value="inputValue"
  88. :password="type==='password'"
  89. :placeholder="placeholder"
  90. :placeholder-style="`color: ${themeContext.resolveThemeColor(error ? errorTextColor : placeholderTextColor)}`"
  91. confirm-type="done"
  92. :type="selectStyleType(type, 'text', {
  93. text: 'text',
  94. password: 'safe-password',
  95. number: 'number',
  96. decimal: 'digit',
  97. tel: 'tel',
  98. email: 'text',
  99. }) || 'text'"
  100. :maxlength="maxLength"
  101. :disabled="disabled"
  102. :readonly="readonly"
  103. @input="onInput"
  104. @focus="onFocus"
  105. @blur="onBlur"
  106. @confirm="onBlur"
  107. @click="onClick"
  108. />
  109. </slot>
  110. <slot name="suffix" />
  111. <slot name="rightButton" />
  112. <Icon v-if="showRightArrow" icon="arrow-right" :size="themeContext.resolveThemeSize('FieldRightArrowSize', 30)" v-bind="rightArrowProps" />
  113. </FlexRow>
  114. <FlexRow
  115. v-if="finalErrorMessage"
  116. :gap="10"
  117. :innerStyle="themeStyles.errorMessage.value"
  118. align="center"
  119. >
  120. <Icon
  121. :icon="errorIcon"
  122. :size="themeContext.resolveThemeSize('FieldErrorIconSize', 40)"
  123. v-bind="errorIconProps"
  124. :color="themeContext.resolveThemeColor('FieldErrorMessageColor', 'danger')"
  125. />
  126. <text :style="themeStyles.errorMessageText.value">{{finalErrorMessage}}</text>
  127. </FlexRow>
  128. <text v-if="showWordLimit" :style="themeStyles.wordLimitText.value">{{wordLimitText}}</text>
  129. </FlexCol>
  130. <!-- 清除按钮 -->
  131. <IconButton
  132. v-if="(clearButton && (
  133. clearButtonMode === 'always'
  134. || (clearButtonMode === 'while-editing' && focused))
  135. || (clearButtonMode === 'unless-editing' && modelValue !== '')
  136. )"
  137. icon="delete-filling"
  138. :color="themeContext.resolveThemeColor('FieldClearIconColor', 'grey')"
  139. v-bind="clearButtonProps"
  140. @click="onClear"
  141. />
  142. </FlexView>
  143. </template>
  144. <script setup lang="ts">
  145. import { computed, inject, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
  146. import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
  147. import { FormItemContextContextKey, propGetFormContext, type FormContext, type FormItemContext, type FormItemInternalContext } from './FormContext';
  148. import { DynamicColor, DynamicSize, DynamicVar, selectStyleType } from '../theme/ThemeTools';
  149. import type { IconProps } from '../basic/Icon.vue';
  150. import Icon from '../basic/Icon.vue';
  151. import IconButton from '../basic/IconButton.vue';
  152. import FlexCol from '../layout/FlexCol.vue';
  153. import FlexRow from '../layout/FlexRow.vue';
  154. import FlexView from '../layout/FlexView.vue';
  155. export interface FieldInstance {
  156. /**
  157. * 获取输入框焦点
  158. */
  159. focus: () => void;
  160. /**
  161. * 取消输入框焦点
  162. */
  163. blur: () => void;
  164. /**
  165. * 清空输入框
  166. */
  167. clear: () => void;
  168. /**
  169. * 返回值表明当前输入框是否获得了焦点。
  170. */
  171. isFocused: () => boolean;
  172. /**
  173. * 清除当前条目的校验状态
  174. */
  175. clearValidate: () => void;
  176. }
  177. export interface FieldProps {
  178. modelValue?: any,
  179. /**
  180. * 输入框左侧文本
  181. */
  182. label?: string;
  183. /**
  184. * 名称,作为提交表单时的标识符
  185. */
  186. name?: string;
  187. /**
  188. * 是否可点击.可以作为一个纯点击条目
  189. */
  190. touchable?: boolean;
  191. /**
  192. * 输入框类型
  193. * @default 'text'
  194. */
  195. type?: 'text'|'tel'|'number'|'password'|'number'|'email'|'decimal';
  196. /**
  197. * 输入的最大字符数
  198. */
  199. maxLength?: number;
  200. /**
  201. * 输入框占位提示文字
  202. */
  203. placeholder?: string;
  204. /**
  205. * 是否禁用输入框
  206. * @default false
  207. */
  208. disabled?: boolean;
  209. /**
  210. * 是否只读
  211. * @default false
  212. */
  213. readonly?: boolean;
  214. /**
  215. * 是否内容垂直居中
  216. * @default false
  217. */
  218. center?: boolean;
  219. /**
  220. * 是否在 label 后面添加冒号
  221. * @default true
  222. */
  223. colon?: boolean;
  224. /**
  225. * 是否必填
  226. * @default false
  227. */
  228. required?: boolean;
  229. /**
  230. * 多行文字
  231. * @default false
  232. */
  233. multiline?: boolean;
  234. /**
  235. * 多行文字下是否自动调整高度
  236. * @default false
  237. */
  238. autoHeight?: boolean;
  239. /**
  240. * 是否显示表单必填星号
  241. * @default true
  242. */
  243. showRequiredBadge?: boolean;
  244. /**
  245. * 是否启用清除图标,点击清除图标后会清空输入框
  246. * @default false
  247. */
  248. clearButton?: boolean;
  249. /**
  250. * 清除图标的自定义属性
  251. */
  252. clearButtonProps?: IconProps;
  253. /**
  254. * 清除图标的显示模式
  255. */
  256. clearButtonMode?: 'always'|'while-editing'|'unless-editing';
  257. /**
  258. * 左侧文本的宽度
  259. */
  260. labelWidth?: string|number;
  261. /**
  262. * 左侧文本对齐
  263. * @default 'left'
  264. */
  265. labelAlign?: 'left'|'center'|'right';
  266. /**
  267. * 左侧文本的位置
  268. * @default 'left'
  269. */
  270. labelPosition?: 'top'|'left';
  271. /**
  272. * 左侧文本的flex占比
  273. * @default undefined
  274. */
  275. labelFlex?: number;
  276. /**
  277. * 输入框的flex占比
  278. * @default 5
  279. */
  280. inputFlex?: number;
  281. /**
  282. * 左侧文本的样式
  283. */
  284. labelStyle?: TextStyle;
  285. /**
  286. * 左侧文本的颜色
  287. */
  288. labelColor?: string;
  289. /**
  290. * 左侧文本的禁用颜色
  291. */
  292. labelDisableColor?: string;
  293. /**
  294. * 输入框样式
  295. */
  296. inputStyle?: TextStyle;
  297. /**
  298. * 激活时的外壳样式
  299. */
  300. activeInputStyle?: TextStyle;
  301. /**
  302. * 输入框颜色
  303. */
  304. inputColor?: string;
  305. /**
  306. * 输入框文本对齐。
  307. * @default 'left'
  308. */
  309. inputAlign?: 'left'|'center'|'right';
  310. /**
  311. * 输入框禁用颜色
  312. */
  313. inputDisableColor?: string;
  314. /**
  315. * 外壳样式
  316. */
  317. fieldStyle?: ViewStyle;
  318. /**
  319. * 激活时的外壳样式
  320. */
  321. activeFieldStyle?: ViewStyle;
  322. /**
  323. * 错误时的外壳样式
  324. */
  325. errorFieldStyle?: ViewStyle;
  326. /**
  327. * 错误时的文字颜色
  328. * @default Color.danger
  329. */
  330. errorTextColor?: string;
  331. /**
  332. * 文本框水印文字颜色
  333. * @default Color.grey
  334. */
  335. placeholderTextColor?: string;
  336. /**
  337. * 输入内容格式化函数
  338. */
  339. formatter?: (text: string) => string;
  340. /**
  341. * 格式化函数触发的时机,可选值为 blur
  342. * @default 'input'
  343. */
  344. formatTrigger?: 'blur'|'input';
  345. /**
  346. * 设置字段校验的时机
  347. * * blur 文本框失去焦点时校验
  348. * * change 数值更改时校验
  349. * * submit 提交时校验(默认)
  350. * @default 'submit'
  351. */
  352. validateTrigger?: 'blur'|'change'|'submit';
  353. /**
  354. * 是否将输入内容标红。
  355. */
  356. error?: boolean;
  357. /**
  358. * 错误提示的图标
  359. * @default 'prompt'
  360. */
  361. errorIcon?: string;
  362. /**
  363. * 错误提示图标的自定义属性
  364. */
  365. errorIconProps?: IconProps;
  366. /**
  367. * 底部错误提示文案,为空时不展示
  368. */
  369. errorMessage?: string;
  370. /**
  371. * 点击输入框是否重置错误状态
  372. * @default true
  373. */
  374. resetErrorOnClick?: boolean;
  375. /**
  376. * 是否显示字数统计
  377. * @default false
  378. */
  379. showWordLimit?: boolean;
  380. /**
  381. * 是否显左边标题
  382. * @default true
  383. */
  384. showLabel?: boolean;
  385. /**
  386. * 是否显右边箭头
  387. * @default false
  388. */
  389. showRightArrow?: boolean;
  390. /**
  391. * 右边箭头的自定义属性
  392. */
  393. rightArrowProps?: IconProps;
  394. }
  395. defineOptions({
  396. options: {
  397. styleIsolation: "shared",
  398. virtualHost: true,
  399. }
  400. })
  401. const emit = defineEmits([ 'update:modelValue', 'click', 'blur', 'focus', 'clear' ])
  402. const props = withDefaults(defineProps<FieldProps>(), {
  403. label: '',
  404. labelColor: () => propGetThemeVar('FieldLabelColor', propGetFormContext()?.fieldProps.value?.labelColor ?? 'text'),
  405. labelDisableColor: () => propGetThemeVar('FieldLabelDisableColor', propGetFormContext()?.fieldProps.value?.labelDisableColor ?? 'grey'),
  406. labelFlex: () => propGetThemeVar('FieldLabelFlex', propGetFormContext()?.labelFlex.value)!,
  407. inputDisableColor: () => propGetThemeVar('FieldInputDisableColor', propGetFormContext()?.fieldProps.value?.inputDisableColor ?? 'grey'),
  408. inputColor: () => propGetThemeVar('FieldInputColor', propGetFormContext()?.fieldProps.value?.inputColor ?? 'text'),
  409. inputFlex: () => propGetThemeVar('FieldInputFlex', propGetFormContext()?.inputFlex.value ?? 5),
  410. placeholderTextColor: () => propGetThemeVar('FieldPlaceholderTextColor', propGetFormContext()?.fieldProps.value?.placeholderTextColor ?? 'text.second'),
  411. errorTextColor: () => propGetThemeVar('FieldErrorTextColor', propGetFormContext()?.fieldProps.value?.errorTextColor ?? 'danger'),
  412. resetErrorOnClick: () => propGetThemeVar('FieldResetErrorOnClick', propGetFormContext()?.fieldProps.value?.resetErrorOnClick ?? true),
  413. colon: () => propGetFormContext()?.colon.value ?? true,
  414. fieldStyle: () => propGetFormContext()?.fieldProps.value?.fieldStyle ?? propGetThemeVar('FieldFieldStyle', {}),
  415. activeFieldStyle: () => propGetFormContext()?.fieldProps.value?.activeFieldStyle ?? propGetThemeVar('FieldActiveFieldStyle', {}),
  416. errorFieldStyle: () => propGetFormContext()?.fieldProps.value?.errorFieldStyle ?? propGetThemeVar('FieldErrorFieldStyle', {}),
  417. labelStyle: () => propGetThemeVar('FieldLabelStyle', propGetFormContext()?.fieldProps.value?.labelStyle ?? {}),
  418. inputStyle: () => propGetThemeVar('FieldInputStyle', propGetFormContext()?.fieldProps.value?.inputStyle ?? {}),
  419. activeInputStyle: () => propGetThemeVar('FieldActiveInputStyle', propGetFormContext()?.fieldProps.value?.activeInputStyle ?? {}),
  420. required: false,
  421. center: () => propGetFormContext()?.fieldProps.value?.center ?? true,
  422. showWordLimit: () => propGetFormContext()?.fieldProps.value?.showWordLimit ?? false,
  423. clearButton: () => propGetFormContext()?.fieldProps.value?.clearButton ?? false,
  424. clearButtonMode: () => propGetFormContext()?.fieldProps.value?.clearButtonMode ?? 'always',
  425. labelWidth: () => propGetFormContext()?.labelWidth.value ?? "left",
  426. labelAlign: () => propGetFormContext()?.labelAlign.value ?? "left",
  427. labelPosition: () => propGetFormContext()?.labelPosition.value ?? 'left',
  428. inputAlign: () => propGetFormContext()?.fieldProps.value?.inputAlign ?? "left",
  429. type: () => propGetFormContext()?.fieldProps.value?.type ?? "text",
  430. formatTrigger: () => propGetFormContext()?.fieldProps.value?.formatTrigger ?? 'input',
  431. showLabel: () => propGetFormContext()?.showLabel.value ?? true,
  432. showRequiredBadge: () => propGetFormContext()?.fieldProps.value?.showRequiredBadge ?? true,
  433. showRightArrow: () => propGetFormContext()?.fieldProps.value?.showRightArrow ?? false,
  434. disabled: false,
  435. readonly: false,
  436. autoHeight: false,
  437. maxLength: 100,
  438. modelValue: undefined,
  439. errorIcon: () => propGetThemeVar('FieldErrorIcon', 'prompt'),
  440. errorIconProps: () => propGetThemeVar('FieldErrorIconProps', propGetFormContext()?.fieldProps.value?.errorIconProps ?? {}),
  441. });
  442. //#region Context
  443. const formContextProps = inject<FormContext>('formContext', null as any);
  444. const error = ref<string|null>(null);
  445. let childRef : any = null;
  446. const childOnClickListener = ref<(() => void)|undefined>(undefined);
  447. //Context for parent
  448. const formItemInternalContext : FormItemInternalContext = {
  449. getValidateTrigger: () => props.validateTrigger || formContextProps?.validateTrigger.value || 'submit',
  450. getFieldName: () => props.name ?? '',
  451. setErrorState(errorMessage) { error.value = errorMessage; },
  452. getUniqueId() {
  453. return uniqueId;
  454. },
  455. setBlurState() {
  456. inputRef.value?.blur();
  457. childRef.value?.blur();
  458. },
  459. };
  460. //Context for custom children
  461. const formItemContext : FormItemContext = {
  462. getFieldName: (ref: any) => {
  463. if (ref)
  464. childRef = ref;
  465. return props.name ?? uniqueId;
  466. },
  467. onFieldFocus: () => {
  468. formContextProps?.onFieldFocus(formItemInternalContext);
  469. emit('focus');
  470. },
  471. onFieldBlur: () => {
  472. formContextProps?.onFieldBlur(formItemInternalContext);
  473. emit('blur');
  474. },
  475. getFormModelValue: () => formContextProps?.getItemValue(formItemInternalContext),
  476. onFieldChange: (newValue: unknown) => { formContextProps?.onFieldChange(formItemInternalContext, newValue); },
  477. clearValidate: () => { formContextProps?.clearValidate(formItemInternalContext); },
  478. setOnClickListener(listener: (() => void)|undefined) {
  479. childOnClickListener.value = listener;
  480. },
  481. }
  482. provide(FormItemContextContextKey, formItemContext);
  483. //Add ref in form
  484. const addNumber = formContextProps?.addFormItemField(formItemInternalContext);
  485. const uniqueId = (formContextProps?.name || 'form') + 'Item' + (props.name || `unknowProperity${addNumber}`);
  486. onBeforeUnmount(() => {
  487. formContextProps?.removeFormItemField(formItemInternalContext);
  488. })
  489. //#endregion
  490. const themeContext = useTheme();
  491. const themeStyles = themeContext.useThemeStyles({
  492. field: {
  493. backgroundColor: DynamicColor('FieldBackgroundColor', 'background.cell'),
  494. paddingVertical: DynamicSize('FieldPaddingVertical', 16),
  495. paddingHorizontal: DynamicSize('FieldPaddingHorizontal', 20),
  496. borderBottomWidth: DynamicSize('FieldBorderBottomWidth', '1px'),
  497. borderBottomColor: DynamicColor('FieldBorderBottomColor', 'border.cell'),
  498. borderBottomStyle: 'solid',
  499. },
  500. fieldVertical: {
  501. gap: DynamicSize('FieldVerticalGap', 20),
  502. },
  503. requiredMark: {
  504. fontSize: DynamicSize('FieldRequiredMark', 28),
  505. alignSelf: 'flex-start',
  506. color: DynamicColor('FieldRequiredMark', 'danger'),
  507. paddingVertical: DynamicSize('FieldRequiredMarkPaddingVertical', 8),
  508. marginHorizontal: DynamicSize('FieldRequiredMarkMarginHorizontal', 8),
  509. },
  510. labelText: {
  511. marginRight: DynamicSize('FieldLabelMarginRight', 20),
  512. fontSize: DynamicSize('FieldLabelFontSize', 28),
  513. alignSelf: 'flex-start',
  514. paddingVertical: DynamicSize('FieldLabelPaddingVertical', 8),
  515. },
  516. inputWapper2: {
  517. align: 'center',
  518. alignSelf: 'center',
  519. width: '100%',
  520. },
  521. input: {
  522. flex: 1,
  523. width: 'auto',
  524. minWidth: '100rpx',
  525. paddingVertical: DynamicSize('FieldInputPaddingVertical', 0),
  526. paddingHorizontal: DynamicSize('FieldInputPaddingHorizontal', 0),
  527. },
  528. errorMessageText: {
  529. fontSize: DynamicSize('FieldErrorMessageFontSize', 24),
  530. color: DynamicColor('FieldErrorMessageColor', 'danger'),
  531. },
  532. errorMessage: {
  533. marginTop: DynamicSize('FieldErrorMessageMarginTop', 12),
  534. },
  535. wordLimitText: {
  536. fontSize: DynamicSize('FieldWordLimitTextFontSize', 24),
  537. color: DynamicColor('FieldWordLimitTextColor', 'text.second'),
  538. width: DynamicSize('FieldWordLimitTextWidth', '100%'),
  539. textAlign: DynamicVar('FieldWordLimitTextTextAlign', 'right'),
  540. },
  541. clearIcon: {
  542. width: DynamicSize('FieldClearIconWidth', 60),
  543. justifyContent: 'center',
  544. alignItems: 'center',
  545. },
  546. });
  547. const requiredShow = computed(() => {
  548. return props.required == true || formContextProps?.getItemRequieed(formItemInternalContext);
  549. });
  550. const wordLimitText = computed(() => {
  551. let wordString = props.modelValue ? props.modelValue.length : '0';
  552. if (props.maxLength)
  553. wordString += `/${props.maxLength}`;
  554. else
  555. wordString += `字`;
  556. return wordString
  557. })
  558. const finalErrorMessage = computed(() => {
  559. return props.errorMessage || error.value || '';
  560. })
  561. const inputValue = ref();
  562. const focused = ref(false);
  563. const inputRef = ref();
  564. watch(() => props.modelValue, (newValue) => {
  565. inputValue.value = newValue;
  566. });
  567. onMounted(() => {
  568. inputValue.value = props.modelValue ?? formItemContext.getFormModelValue();
  569. });
  570. function emitChangeText(text: string) {
  571. emit('update:modelValue', text);
  572. inputValue.value = text;
  573. formItemContext.onFieldChange(text);
  574. }
  575. function doFormatter(text: string) {
  576. switch (props.type) {
  577. case 'decimal':
  578. text = text.replace(/[^\d.]/g, "");
  579. text = text.replace(/^\./g, ""); //必须保证第一个为数字而不是.
  580. text = text.replace(/\.{2,}/g, "."); //保证只有出现一个.而没有多个.
  581. text = text.replace(".","$#$").replace(/\./g, "").replace("$#$", ".");
  582. break;
  583. case 'number':
  584. text = text.replace(/[^\d]/g, '');
  585. break;
  586. case 'tel':
  587. text = text.replace(/[^(\d|\-|*|#)]/g, '');
  588. break;
  589. }
  590. if (props.formatter)
  591. text = props.formatter(text);
  592. return text;
  593. }
  594. function onInput(e: any) {
  595. focused.value = true;
  596. let text = e.detail.value;
  597. if (props.formatTrigger !== 'blur') //格式化字符串
  598. text = doFormatter(text);
  599. emitChangeText(text);
  600. }
  601. function onFocus() {
  602. focused.value = true;
  603. formItemContext.onFieldFocus();
  604. }
  605. function onBlur() {
  606. focused.value = false;
  607. if (props.formatTrigger === 'blur'){//格式化字符串
  608. const text = doFormatter(props.modelValue ?? '');
  609. emitChangeText(text);
  610. }
  611. formItemContext.onFieldBlur();
  612. }
  613. function onClear() {
  614. //清空按钮
  615. emitChangeText('');
  616. emit('clear');
  617. }
  618. function onClick() {
  619. childOnClickListener.value?.();
  620. emit('click');
  621. if (props.resetErrorOnClick)
  622. fieldInstance.clearValidate();
  623. }
  624. const fieldInstance : FieldInstance = {
  625. focus() {
  626. inputRef.value?.focus();
  627. childRef.value?.focus();
  628. },
  629. blur() {
  630. inputRef.value?.blur();
  631. childRef.value?.blur();
  632. },
  633. clear() {
  634. inputRef.value?.clear();
  635. childRef.value?.clear();
  636. },
  637. isFocused() :boolean {
  638. return focused.value;
  639. },
  640. clearValidate() {
  641. formContextProps?.clearValidate(formItemInternalContext);
  642. }
  643. }
  644. defineExpose<FieldInstance>(fieldInstance);
  645. </script>