l-input.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <template>
  2. <view class="l-input"
  3. :style="[styles, lStyle]"
  4. :class="[
  5. 'l-input--' + layout,
  6. classic ? 'l-input--classic-' + status : '',
  7. {'l-input--classic': classic},
  8. {'l-input--classic-disabled': classic && disabled},
  9. {'l-input--classic-focused': classic && disabled},
  10. {'l-input--border': bordered && !classic}]">
  11. <view class="l-input__wrap--prefix" v-if="label || $slots['label'] || $slots['prefix-icon'] || prefixIcon">
  12. <view class="l-input__icon--prefix" v-if="$slots['prefix-icon'] || prefixIcon">
  13. <slot name="prefix-icon">
  14. <l-icon :name="prefixIcon" v-if="prefixIcon" :color="prefixIconColor" :size="prefixIconSize"></l-icon>
  15. </slot>
  16. </view>
  17. <text class="l-input__label"
  18. :style="[labelStyle]"
  19. :class="{ 'l-input__label--gap': prefixIcon || $slots['prefix-icon']}"
  20. v-if="label || $slots['label'] ">
  21. <slot name="label">{{label}}</slot>
  22. </text>
  23. </view>
  24. <view class="l-input__wrap">
  25. <view class="l-input__content">
  26. <input
  27. class="l-input__control"
  28. :style="[inputStyle]"
  29. :class="[
  30. 'l-input--' + align,
  31. {
  32. 'l-input__control--disabled': isDisabled,
  33. 'l-input__control--read-only': isReadonly,
  34. }
  35. ]"
  36. :maxlength="maxlength"
  37. :disabled="isDisabled || isReadonly"
  38. :placeholder="placeholder"
  39. :placeholder-style="innerPlaceholderStyle"
  40. :placeholder-class="!innerPlaceholderStyle ? (isDisabled || isReadonly ? 'l-input__placeholder--disabled' : 'l-input__placeholder') : ''"
  41. :value="innerValue"
  42. :password="type == 'password'"
  43. :type="type == 'password' ? 'text' : type"
  44. :focus="focus"
  45. :confirm-type="confirmType"
  46. :confirm-hold="confirmHold"
  47. :cursor="cursor"
  48. :cursor-color="cursorColor"
  49. :cursor-spacing="cursorSpacing"
  50. :adjust-position="adjustPosition"
  51. :auto-focus="autoFocus"
  52. :always-embed="alwaysEmbed"
  53. :selection-start="selectionStart"
  54. :selection-end="selectionEnd"
  55. :hold-keyboard="holdKeyboard"
  56. :safe-password-cert-path="safePasswordCertPath"
  57. :safe-password-length="safePasswordLength"
  58. :safe-password-time-stamp="safePasswordTimeStamp"
  59. :safe-password-nonce="safePasswordNonce"
  60. :safe-password-salt="safePasswordSalt"
  61. :safe-password-custom-hash="safePasswordCustomHash"
  62. :aria-label="label"
  63. :aria-roledescription="label"
  64. @input="onInput"
  65. @focus="onFocus"
  66. @blur="onBlur"
  67. @confirm="onConfirm"
  68. @keyboardheightchange="onKeyboardHeightChange"
  69. @nicknamereview="onNickNameReview" />
  70. <view class="l-input__wrap--clearable-icon" v-if="clearable" @click="clearInput" v-show="showClearIcon">
  71. <l-icon name="close-circle-filled" :size="clearIconSize"></l-icon>
  72. </view>
  73. <view class="l-input__wrap--suffix" @click="onSuffixClick" v-if="suffix || $slots['suffix']">
  74. <slot name="suffix">
  75. <text class="l-input__wrap--suffix-text">{{suffix}}</text>
  76. </slot>
  77. </view>
  78. <view class="l-input__wrap--suffix-icon" v-if="suffixIcon || $slots['suffix-icon']">
  79. <slot name="suffix-icon">
  80. <l-icon :name="suffixIcon" @click="onSuffixIconClick" :size="suffixIconSize" :color="suffixIconColor" v-if="suffixIcon"></l-icon>
  81. </slot>
  82. </view>
  83. </view>
  84. <text class="l-input__tips" :style="[tipsStyle]" :class="['l-input__tips--' + status]" v-if="tips && tips!.length > 0 || $slots['tips']">
  85. <slot name="tips">{{tips}}</slot>
  86. </text>
  87. </view>
  88. </view>
  89. </template>
  90. <script lang="ts">
  91. // @ts-nocheck
  92. /**
  93. * Input 输入框组件
  94. * @description 增强版输入组件,支持安全加密输入、多类型验证和智能交互
  95. * <br> 插件类型:LInputComponentPublicInstance
  96. * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-input
  97. *
  98. * @property {boolean} adjustPosition 键盘弹起时页面自动上推
  99. * @property {'left'|'center'|'right'} align 文本对齐方式
  100. * @value left
  101. * @value center
  102. * @value right
  103. * @property {boolean} alwaysEmbed iOS强制同层渲染(仅iOS生效)
  104. * @property {boolean} autoFocus 自动聚焦(即将废弃,建议使用focus)
  105. * @property {boolean} bordered 无边框模式
  106. * @property {'always'|'focus'} clearTrigger 清空按钮显示规则
  107. * @value always
  108. * @value focus
  109. * @property {boolean} clearable 显示清空按钮
  110. * @property {boolean} confirmHold 点击确认按钮保持键盘
  111. * @property {'send'|'search'|'next'|'go'|'done'} confirmType 确认按钮类型
  112. * @value send 显示"发送"
  113. * @value search 显示"搜索"
  114. * @value next 显示"下一个"
  115. * @value go 显示"前往"
  116. * @value done 显示"完成"
  117. * @property {number} cursor 初始光标位置(需配合selectionStart/End)
  118. * @property {string} cursorColor 光标颜色
  119. * @property {number} cursorSpacing 光标与键盘间距(单位px)
  120. * @property {boolean} disabled 禁用状态
  121. * @property {boolean} focus 强制聚焦
  122. * @property {boolean} focused 是否显示获焦样式,用于结合自定义键盘使用时显示高亮效果
  123. * @property {boolean} holdKeyboard 聚焦时保持键盘不收起
  124. * @property {string} label 左侧标签文本
  125. * @property {'horizontal'|'vertical'} layout 标签布局
  126. * @value horizontal
  127. * @value vertical
  128. * @property {number} maxcharacter 最大字符长度(中文算2字符)
  129. * @property {number} maxlength 最大输入长度(中文算1字符)
  130. * @property {string} placeholder 占位文本
  131. * @property {string} placeholderStyle 占位符内联样式
  132. * @property {string} placeholderClass 占位符CSS类名
  133. * @property {boolean} readonly 只读模式
  134. * @property {string} safePasswordCertPath 安全证书路径(仅App)
  135. * @property {string} safePasswordCustomHash 自定义哈希算法(仅App)
  136. * @property {number} safePasswordLength 安全密码长度(仅App)
  137. * @property {string} safePasswordNonce 加密随机数(仅App)
  138. * @property {string} safePasswordSalt 加密盐值(仅App)
  139. * @property {number} safePasswordTimeStamp 加密时间戳(仅App)
  140. * @property {'default'|'success'|'warning'|'error'} status 校验状态
  141. * @value default
  142. * @value success
  143. * @value warning
  144. * @value error
  145. * @property {string} prefixIcon 前缀图标(支持图片路径/字体图标)
  146. * @property {string} prefixIconSize 前缀图标尺寸(带单位)
  147. * @property {string} prefixIconColor 前缀图标颜色
  148. * @property {string} suffix 后缀文本内容
  149. * @property {string} suffixIcon 后缀图标(支持图片路径/字体图标)
  150. * @property {string} suffixIconSize 后缀图标尺寸(带单位)
  151. * @property {string} clearIconSize 删除图标尺寸
  152. * @property {string} suffixIconColor 后缀图标颜色
  153. * @property {string} tips 底部提示文本(根据status变色)
  154. * @property {'text'|'number'|'idcard'|'digit'|'safe-password'|'password'|'nickname'} type 输入类型
  155. * @value text 普通文本
  156. * @value number 数字键盘(非强制)
  157. * @value idcard 身份证键盘(带X)
  158. * @value digit 强制数字键盘
  159. * @value safe-password 安全加密输入
  160. * @value password 密码输入
  161. * @value nickname 昵称输入(过滤特殊字符)
  162. * @property {string} value 输入值(支持v-model)
  163. * @property {string} modelValue 输入值(支持v-model)
  164. * @property {string} lStyle 根节点自定义样式
  165. * @property {string} labelStyle 标签样式
  166. * @property {string} tipsStyle 提示文本样式
  167. * @property {string} inputStyle 输入域样式
  168. * @property {string} borderColor 边框颜色(bordered时生效)
  169. * @property {string} focusedBorderColor 聚焦时边框颜色(bordered时生效)
  170. * @event {Function} change 输入时触发
  171. * @event {Function} focus 聚焦时触发
  172. * @event {Function} blur 失焦时触发
  173. * @event {Function} confirm 点击完成按钮触发
  174. * @event {Function} clear 点击清空按钮触发
  175. * @event {Function} click-icon 点击触发
  176. */
  177. import { defineComponent, computed, watchEffect, inject, ref } from '@/uni_modules/lime-shared/vue';
  178. import inputProps from './props';
  179. import { objToCss } from '@/uni_modules/lime-shared/objToCss';
  180. export default defineComponent({
  181. name: 'l-input',
  182. props: inputProps,
  183. emits: ['change', 'focus', 'blur', 'confirm', 'clear', 'click', 'input', 'update:modelValue'],
  184. options: {
  185. behaviors: ['wx://form-field']
  186. },
  187. setup(props, {emit}) {
  188. const formItemBlur = inject<(() => void)|null>('formItemBlur', null);
  189. const formDisabled = inject<Ref<boolean|null>|null>('formDisabled', null)
  190. const formReadonly = inject<Ref<boolean|null>|null>('formReadonly', null)
  191. const isDisabled = computed(():boolean => props.disabled || formDisabled?.value)
  192. const isReadonly = computed(():boolean => props.readonly || formReadonly?.value)
  193. const calculateValue = (value: string):CharacterLengthResult => {
  194. const { maxlength, maxcharacter } = props;
  195. if(maxcharacter != null && maxcharacter > 0) {
  196. return characterLimit('maxcharacter', value, maxcharacter)
  197. }
  198. // else if(maxlength > 0) {
  199. // return characterLimit('maxlength', value, maxlength)
  200. // }
  201. return {
  202. characters: value,
  203. length: value.length
  204. } as CharacterLengthResult
  205. }
  206. let _innerValue = '';
  207. const innerFocused = ref(props.focus || props.focused);
  208. const innerValue = computed({
  209. set(value: string|number) {
  210. // modelValue.value = value;
  211. _innerValue = value;
  212. emit('update:modelValue', value)
  213. emit('change', value)
  214. // #ifdef VUE2
  215. emit('input', value)
  216. // #endif
  217. },
  218. get():string|number {
  219. const _value = props.value ?? props.modelValue
  220. if(_innerValue != _value) {
  221. const { characters } = calculateValue(props.value ?? props.modelValue ?? '');
  222. return props.type != 'number' ? characters : characters && Number(characters)
  223. }
  224. return props.type != 'number' ? _value : _value && Number(_value)
  225. }
  226. } as WritableComputedOptions<string>)
  227. const innerPlaceholderStyle = computed(():string=>{
  228. return typeof props.placeholderStyle == 'string' ? props.placeholderStyle : objToCss(props.placeholderStyle)
  229. })
  230. const styles = computed(() =>{
  231. const style:Record<string, any> = {}
  232. if(props.borderColor) {
  233. style['--l-input-border-color'] = props.borderColor
  234. }
  235. if(props.focusedBorderColor) {
  236. style['--l-input-focused-border-color'] = props.focusedBorderColor
  237. }
  238. return style
  239. })
  240. const showClearIcon = computed(():boolean => {
  241. const { clearTrigger, disabled, readonly } = props;
  242. if(disabled || readonly) {
  243. return false
  244. }
  245. return innerValue.value.length > 0 || clearTrigger == 'always'
  246. })
  247. const onInput = (e: UniInputEvent) => {
  248. const { value, cursor, keyCode } = e.detail;
  249. if(props.type == 'number') {
  250. const _v = (typeof value == 'string' ? parseFloat(value) : value) //as number
  251. innerValue.value = isNaN(_v) ? '' : _v;
  252. } else {
  253. const { characters } = calculateValue(value);
  254. innerValue.value = characters;
  255. }
  256. }
  257. const onFocus = (event: UniInputFocusEvent) => {
  258. innerFocused.value = true;
  259. emit('focus', event)
  260. }
  261. const onBlur = (event: UniInputBlurEvent) => {
  262. innerFocused.value = false;
  263. emit('blur', event)
  264. formItemBlur?.()
  265. }
  266. const onConfirm = (event: UniInputConfirmEvent) => {
  267. emit('confirm', event)
  268. }
  269. const onKeyboardHeightChange = (event: UniInputKeyboardHeightChangeEvent) => {
  270. emit('keyboardheightchange', event)
  271. }
  272. const onNickNameReview = (event: any) => {
  273. emit('nicknamereview', event)
  274. }
  275. const clearInput = () => {
  276. innerValue.value = '';
  277. emit('clear')
  278. }
  279. const onSuffixClick = () => {
  280. emit('click', { trigger: 'suffix' })
  281. }
  282. const onSuffixIconClick = () => {
  283. emit('click', { trigger: 'suffix-icon' })
  284. }
  285. watchEffect(()=> {
  286. innerFocused.value = props.focus || props.focused;
  287. })
  288. return {
  289. isDisabled,
  290. isReadonly,
  291. styles,
  292. showClearIcon,
  293. innerValue,
  294. innerFocused,
  295. onInput,
  296. onFocus,
  297. onBlur,
  298. onConfirm,
  299. onKeyboardHeightChange,
  300. onNickNameReview,
  301. clearInput,
  302. onSuffixClick,
  303. onSuffixIconClick,
  304. innerPlaceholderStyle
  305. }
  306. }
  307. })
  308. </script>
  309. <style lang="scss">
  310. @import './index';
  311. </style>