| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- <template>
- <view class="l-input"
- :style="[styles, lStyle]"
- ref="rootRef"
- :class="[
- 'l-input--' + layout,
- classic ? 'l-input--classic-' + status : '',
- {'l-input--classic': classic},
- {'l-input--classic-disabled': classic && disabled},
- {'l-input--classic-focused': classic && !disabled && innerFocused},
- {'l-input--border': bordered && !classic}]">
- <view class="l-input__wrap--prefix" v-if="label != null || $slots['label'] != null || $slots['prefix-icon'] != null || prefixIcon != null">
- <!-- #ifndef UNI-APP-X && APP -->
- <view class="l-input__icon--prefix" v-if="$slots['prefix-icon'] != null || prefixIcon != null">
- <slot name="prefix-icon">
- <l-icon :name="prefixIcon" v-if="prefixIcon != null" :color="prefixIconColor" :size="prefixIconSize"></l-icon>
- </slot>
- </view>
- <!-- #endif -->
- <!-- #ifdef UNI-APP-X && APP -->
- <slot name="prefix-icon">
- <l-icon class="l-input__icon--prefix" :name="prefixIcon" v-if="prefixIcon != null" :color="prefixIconColor" :size="prefixIconSize"></l-icon>
- </slot>
- <!-- #endif -->
- <text class="l-input__label"
- :style="[labelStyle]"
- :class="{ 'l-input__label--gap': prefixIcon != null || $slots['prefix-icon'] != null}"
- v-if="label != null || $slots['label'] != null ">
- <slot name="label">{{label}}</slot>
- </text>
- </view>
- <view class="l-input__wrap">
- <view class="l-input__content">
- <input
- class="l-input__control"
- :style="[inputStyle]"
- :class="[
- 'l-input--' + align,
- {
- 'l-input__control--disabled': isDisabled,
- 'l-input__control--read-only': isReadonly,
- }
- ]"
- :maxlength="maxlength"
- :disabled="isDisabled || isReadonly"
- :placeholder="placeholder"
- :placeholder-style="innerPlaceholderStyle"
- :placeholder-class="innerPlaceholderStyle == '' ? (isDisabled || isReadonly ? 'l-input__placeholder--disabled' : 'l-input__placeholder') : ''"
- :value="innerValue"
- :type="type == 'password' ? 'text' : type"
- :password="type == 'password'"
- :focus="focus"
- :confirm-type="confirmType"
- :confirm-hold="confirmHold"
- :cursor="cursor"
- :cursor-color="cursorColor"
- :cursor-spacing="cursorSpacing"
- :adjust-position="adjustPosition"
- :auto-focus="autoFocus"
- :always-embed="alwaysEmbed"
- :selection-start="selectionStart"
- :selection-end="selectionEnd"
- :hold-keyboard="holdKeyboard"
- :safe-password-cert-path="safePasswordCertPath"
- :safe-password-length="safePasswordLength"
- :safe-password-time-stamp="safePasswordTimeStamp"
- :safe-password-nonce="safePasswordNonce"
- :safe-password-salt="safePasswordSalt"
- :safe-password-custom-hash="safePasswordCustomHash"
- :aria-label="label"
- :aria-roledescription="label"
- @input="onInput"
- @focus="onFocus"
- @blur="onBlur"
- @confirm="onConfirm"
- @keyboardheightchange="onKeyboardHeightChange"
- @nicknamereview="onNickNameReview" />
- <!-- #ifndef UNI-APP-X && APP -->
- <view class="l-input__wrap--clearable-icon" v-if="clearable" @click="clearInput" v-show="showClearIcon">
- <l-icon name="close-circle-filled" :size="clearIconSize"></l-icon>
- </view>
- <!-- #endif -->
- <!-- #ifdef UNI-APP-X && APP -->
- <view v-if="clearable" v-show="showClearIcon" @click="clearInput">
- <l-icon class="l-input__wrap--clearable-icon" name="close-circle-filled" :size="clearIconSize" ></l-icon>
- </view>
- <!-- <l-icon class="l-input__wrap--clearable-icon" name="close-circle-filled" size="44rpx" @click="clearInput" v-if="clearable" v-show="showClearIcon"></l-icon> -->
- <!-- #endif -->
- <view class="l-input__wrap--suffix" @click="onSuffixClick" v-if="suffix != null || $slots['suffix'] != null">
- <slot name="suffix">
- <text class="l-input__wrap--suffix-text">{{suffix}}</text>
- </slot>
- </view>
- <!-- #ifndef UNI-APP-X && APP -->
- <view class="l-input__wrap--suffix-icon" v-if="suffixIcon != null || $slots['suffix-icon'] != null">
- <slot name="suffix-icon">
- <l-icon :name="suffixIcon" @click="onSuffixIconClick" :size="suffixIconSize" :color="suffixIconColor" v-if="suffixIcon != null"></l-icon>
- </slot>
- </view>
- <!-- #endif -->
- <!-- #ifdef UNI-APP-X && APP -->
- <slot name="suffix-icon">
- <l-icon class="l-input__wrap--suffix-icon" :name="suffixIcon" :size="suffixIconSize" :color="suffixIconColor" v-if="suffixIcon != null"></l-icon>
- </slot>
- <!-- #endif -->
- </view>
- <text class="l-input__tips" :style="[tipsStyle]" :class="['l-input__tips--' + status]" v-if="tips != null && tips!.length > 0 || $slots['tips'] != null">
- <slot name="tips">{{tips}}</slot>
- </text>
- </view>
- <!-- #ifdef APP -->
- <view class="l-input__border" v-if="bordered && !classic"></view>
- <!-- #endif -->
- </view>
- </template>
- <script lang="uts" setup>
- /**
- * Input 输入框组件
- * @description 增强版输入组件,支持安全加密输入、多类型验证和智能交互
- * <br> 插件类型:LInputComponentPublicInstance
- * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-input
- *
- * @property {boolean} adjustPosition 键盘弹起时页面自动上推
- * @property {'left'|'center'|'right'} align 文本对齐方式
- * @value left
- * @value center
- * @value right
- * @property {boolean} alwaysEmbed iOS强制同层渲染(仅iOS生效)
- * @property {boolean} autoFocus 自动聚焦(即将废弃,建议使用focus)
- * @property {boolean} bordered 边框模式
- * @property {'always'|'focus'} clearTrigger 清空按钮显示规则
- * @value always
- * @value focus
- * @property {boolean} clearable 显示清空按钮
- * @property {boolean} confirmHold 点击确认按钮保持键盘
- * @property {'send'|'search'|'next'|'go'|'done'} confirmType 确认按钮类型
- * @value send 显示"发送"
- * @value search 显示"搜索"
- * @value next 显示"下一个"
- * @value go 显示"前往"
- * @value done 显示"完成"
- * @property {number} cursor 初始光标位置(需配合selectionStart/End)
- * @property {string} cursorColor 光标颜色
- * @property {number} cursorSpacing 光标与键盘间距(单位px)
- * @property {boolean} disabled 禁用状态
- * @property {boolean} focus 强制聚焦
- * @property {boolean} focused 是否显示获焦样式,用于结合自定义键盘使用时显示高亮效果
- * @property {boolean} classic 是否使用经典边框样式
- * @property {boolean} holdKeyboard 聚焦时保持键盘不收起
- * @property {string} label 左侧标签文本
- * @property {'horizontal'|'vertical'} layout 标签布局
- * @value horizontal
- * @value vertical
- * @property {number} maxcharacter 最大字符长度(中文算2字符)
- * @property {number} maxlength 最大输入长度(中文算1字符)
- * @property {string} placeholder 占位文本
- * @property {string} placeholderStyle 占位符内联样式
- * @property {string} placeholderClass 占位符CSS类名
- * @property {boolean} readonly 只读模式
- * @property {string} safePasswordCertPath 安全证书路径(仅App)
- * @property {string} safePasswordCustomHash 自定义哈希算法(仅App)
- * @property {number} safePasswordLength 安全密码长度(仅App)
- * @property {string} safePasswordNonce 加密随机数(仅App)
- * @property {string} safePasswordSalt 加密盐值(仅App)
- * @property {number} safePasswordTimeStamp 加密时间戳(仅App)
- * @property {'default'|'success'|'warning'|'error'} status 校验状态
- * @value default
- * @value success
- * @value warning
- * @value error
- * @property {string} prefixIcon 前缀图标(支持图片路径/字体图标)
- * @property {string} prefixIconSize 前缀图标尺寸(带单位)
- * @property {string} prefixIconColor 前缀图标颜色
- * @property {string} suffix 后缀文本内容
- * @property {string} suffixIcon 后缀图标(支持图片路径/字体图标)
- * @property {string} suffixIconSize 后缀图标尺寸(带单位)
- * @property {string} clearIconSize 删除图标尺寸
- * @property {string} suffixIconColor 后缀图标颜色
- * @property {string} tips 底部提示文本(根据status变色)
- * @property {'text'|'number'|'idcard'|'digit'|'safe-password'|'password'|'nickname'} type 输入类型
- * @value text 普通文本
- * @value number 数字键盘(非强制)
- * @value idcard 身份证键盘(带X)
- * @value digit 强制数字键盘
- * @value safe-password 安全加密输入
- * @value password 密码输入
- * @value nickname 昵称输入(过滤特殊字符)
- * @property {string} value 输入值(支持v-model)
- * @property {string} modelValue 输入值(支持v-model)
- * @property {string} lStyle 根节点自定义样式
- * @property {string} labelStyle 标签样式
- * @property {string} tipsStyle 提示文本样式
- * @property {string} inputStyle 输入域样式
- * @property {string} borderColor 边框颜色(bordered时生效)
- * @property {string} focusedBorderColor 聚焦时边框颜色(bordered时生效)
- * @event {Function} change 输入时触发
- * @event {Function} focus 聚焦时触发
- * @event {Function} blur 失焦时触发
- * @event {Function} confirm 点击完成按钮触发
- * @event {Function} clear 点击清空按钮触发
- * @event {Function} click-icon 点击触发
- */
- import { InputProps } from './type';
- import { characterLimit, type CharacterLengthResult } from '@/uni_modules/lime-shared/characterLimit';
- import { objToCss } from '@/uni_modules/lime-shared/objToCss';
- // #ifdef APP
- const themeVars = inject('limeConfigProviderThemeVars', computed(()=> ({})))
- // import { useDrawBorder, DrawBorderOptions } from '@/uni_modules/lime-style/hairline'
- // #endif
-
- defineOptions({
- behaviors: ['wx://form-field']
- })
- const emit = defineEmits(['change', 'update:modelValue' ,'focus', 'blur', 'confirm', 'clear', 'keyboardheightchange', 'nicknamereview' ,'click-icon'])
- const props = withDefaults(defineProps<InputProps>(),{
- adjustPosition: true,
- align: 'left',
- alwaysEmbed: false,
- autoFocus: false,
- bordered: true,
- clearTrigger: 'focus',
- clearable: false,
- confirmHold: false,
- confirmType: 'done',
- cursor: 0,
- cursorColor: '',
- cursorSpacing: 0,
- disabled: false,
- focus: false,
- holdKeyboard: false,
- layout: 'horizontal',
- maxlength: -1,
- placeholder: '',
- placeholderStyle: '',
- readonly: false,
- safePasswordCertPath: '',
- safePasswordCustomHash: '',
- // safePasswordLength: 0,
- safePasswordNonce: '',
- safePasswordSalt: '',
- // safePasswordTimeStamp: 0,
- selectionEnd: -1,
- selectionStart: -1,
- status: 'default',
- // suffixIcon: '',
- type: 'text',
- classic: false,
- focused: false
- })
-
- const formItemBlur = inject<(() => void)|null>('formItemBlur', null);
- const formDisabled = inject<Ref<boolean|null>|null>('formDisabled', null)
- const formReadonly = inject<Ref<boolean|null>|null>('formReadonly', null)
-
- const calculateValue = (value: string|number):CharacterLengthResult => {
- const { maxlength, maxcharacter } = props;
- if(maxcharacter != null && maxcharacter > 0) {
- return characterLimit('maxcharacter', `${value}`, maxcharacter)
- }
- return {
- characters: `${value}`,
- length: `${value}`.length
- } as CharacterLengthResult
- }
- let _innerValue = ref<string|number>('');
- const innerFocused = ref(props.focus || props.focused);
- const innerValue = computed({
- set(value: string|number) {
- _innerValue.value = value;
- emit('change', value)
- emit('update:modelValue', value)
- },
- get():string|number {
- const _value = props.value ?? props.modelValue
- if(_innerValue.value != _value && props.type != 'number') {
- const { characters } = calculateValue(`${_value ?? _innerValue.value}`);
- return characters
- }
- return _value ?? _innerValue.value
- }
- } as WritableComputedOptions<string>)
-
- const isDisabled = computed(():boolean => props.disabled || (formDisabled?.value ?? false))
- const isReadonly = computed(():boolean => props.readonly || (formReadonly?.value ?? false))
- const innerPlaceholderStyle = computed(():string=>{
- let style = ''
- // #ifdef APP
- // 直接使用固定值,不依赖 themeVars
- // 注意:Android 原生 input 的 placeholder-style 不支持 rpx,必须使用 px
- style += `color: ${themeVars.value['inputPlaceholderTextColor'] ?? '#999999'};`
- // #endif
-
- return style + (typeof props.placeholderStyle == 'string' ? `${props.placeholderStyle}` : objToCss(props.placeholderStyle as UTSJSONObject))
- })
- const styles = computed(():Map<string, any>=>{
- const style = new Map<string, any>()
- // #ifndef UNI-APP-X && APP
- if(props.borderColor != null) {
- style.set('--l-input-border-color', props.borderColor)
- }
- if(props.focusedBorderColor != null) {
- style.set('--l-input-focused-border-color', props.focusedBorderColor)
- }
- // #endif
- return style
- })
- const showClearIcon = computed(():boolean => {
- const { clearTrigger, disabled, readonly } = props;
- if(disabled || readonly) {
- return false
- }
- return `${innerValue.value}`.length > 0 || clearTrigger == 'always'
- })
- const onInput = (e: UniInputEvent) => {
- const { value, cursor, keyCode } = e.detail;
- if(props.type == 'number') {
- const _v:number = parseFloat(`${value}`)
- // @ts-ignore
- innerValue.value = isNaN(_v) ? '' : _v;
- } else {
- const { characters } = calculateValue(value);
- innerValue.value = characters;
- }
- }
- const onFocus = (event: UniInputFocusEvent) => {
- innerFocused.value = true;
- emit('focus', event)
- }
- const onBlur = (event: UniInputBlurEvent) => {
- innerFocused.value = false;
- emit('blur', event)
- formItemBlur?.()
- }
- const onConfirm = (event: UniInputConfirmEvent) => {
- emit('confirm', event)
- }
- const onKeyboardHeightChange = (event: UniInputKeyboardHeightChangeEvent) => {
- emit('keyboardheightchange', event)
- }
- const onNickNameReview = (event: any) => {
- emit('nicknamereview', event)
- }
- const clearInput = () => {
- innerValue.value = '';
- emit('clear')
- }
- const onSuffixClick = () => {
- emit('click-icon', { trigger: 'suffix' })
- }
- const onSuffixIconClick = () => {
- emit('click-icon', { trigger: 'suffix-icon' })
- }
- watchEffect(()=> {
- innerFocused.value = props.focus || props.focused;
- })
- // #ifdef APP-ANDROID || APP-IOS
- const rootRef = ref<UniElement|null>(null);
- onMounted(()=>{
- watchEffect(()=>{
- if(!props.classic) {
- if(props.borderColor != null && !innerFocused.value) {
- rootRef.value?.style.setProperty('border-color', props.borderColor)
- }
- if(props.focusedBorderColor != null && innerFocused.value) {
- rootRef.value?.style.setProperty('border-color', props.focusedBorderColor)
- }
- }
- })
- })
- // #endif
- </script>
- <style lang="scss">
- @import './index';
- </style>
|