Files
taimed-international-app/uni_modules/wot-design-uni/components/wd-input/wd-input.vue
2025-11-04 12:37:04 +08:00

301 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view :class="rootClass" :style="customStyle" @click="handleClick">
<view v-if="label || $slots.label" :class="labelClass" :style="labelStyle">
<text v-if="isRequired && markerSide === 'before'" class="wd-input__required wd-input__required--left">*</text>
<view v-if="prefixIcon || $slots.prefix" class="wd-input__prefix">
<wd-icon v-if="prefixIcon && !$slots.prefix" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
<slot v-else name="prefix"></slot>
</view>
<view class="wd-input__label-inner">
<text v-if="label && !$slots.label">{{ label }}</text>
<slot v-else-if="$slots.label" name="label"></slot>
</view>
<text v-if="isRequired && markerSide === 'after'" class="wd-input__required">*</text>
</view>
<view class="wd-input__body">
<view class="wd-input__value">
<view v-if="(prefixIcon || $slots.prefix) && !label" class="wd-input__prefix">
<wd-icon v-if="prefixIcon && !$slots.prefix" custom-class="wd-input__icon" :name="prefixIcon" @click="onClickPrefixIcon" />
<slot v-else name="prefix"></slot>
</view>
<input
:class="[
'wd-input__inner',
prefixIcon ? 'wd-input__inner--prefix' : '',
showWordCount ? 'wd-input__inner--count' : '',
alignRight ? 'is-align-right' : '',
customInputClass
]"
:type="type"
:password="showPassword && !isPwdVisible"
v-model="inputValue"
:placeholder="placeholderValue"
:disabled="disabled || readonly"
:maxlength="maxlength"
:focus="focused"
:confirm-type="confirmType"
:confirm-hold="confirmHold"
:cursor="cursor"
:cursor-spacing="cursorSpacing"
:placeholder-style="placeholderStyle"
:selection-start="selectionStart"
:selection-end="selectionEnd"
:adjust-position="adjustPosition"
:hold-keyboard="holdKeyboard"
:always-embed="alwaysEmbed"
:placeholder-class="inputPlaceholderClass"
:ignoreCompositionEvent="ignoreCompositionEvent"
:inputmode="inputmode"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@confirm="handleConfirm"
@keyboardheightchange="handleKeyboardheightchange"
/>
<view v-if="props.readonly" class="wd-input__readonly-mask" />
<view v-if="showClear || showPassword || suffixIcon || showWordCount || $slots.suffix" class="wd-input__suffix">
<wd-icon v-if="showClear" custom-class="wd-input__clear" name="error-fill" @click="handleClear" />
<wd-icon v-if="showPassword" custom-class="wd-input__icon" :name="isPwdVisible ? 'view' : 'eye-close'" @click="togglePwdVisible" />
<view v-if="showWordCount" class="wd-input__count">
<text
:class="[
inputValue && String(inputValue).length > 0 ? 'wd-input__count-current' : '',
String(inputValue).length > maxlength! ? 'is-error' : ''
]"
>
{{ String(inputValue).length }}
</text>
/{{ maxlength }}
</view>
<wd-icon v-if="suffixIcon && !$slots.suffix" custom-class="wd-input__icon" :name="suffixIcon" @click="onClickSuffixIcon" />
<slot v-else name="suffix"></slot>
</view>
</view>
<view v-if="errorMessage" class="wd-input__error-message">{{ errorMessage }}</view>
</view>
</view>
</template>
<script lang="ts">
export default {
name: 'wd-input',
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared'
}
}
</script>
<script lang="ts" setup>
import { computed, ref, watch, useSlots, type Slots } from 'vue'
import wdIcon from '../wd-icon/wd-icon.vue'
import { isDef, objToStyle, pause, isEqual } from '../common/util'
import { useCell } from '../composables/useCell'
import { FORM_KEY, type FormItemRule } from '../wd-form/types'
import { useParent } from '../composables/useParent'
import { useTranslate } from '../composables/useTranslate'
import { inputProps } from './types'
interface InputSlots extends Slots {
prefix?: () => any
suffix?: () => any
label?: () => any
}
const props = defineProps(inputProps)
const emit = defineEmits([
'update:modelValue',
'clear',
'blur',
'focus',
'input',
'keyboardheightchange',
'confirm',
'clicksuffixicon',
'clickprefixicon',
'click'
])
const slots = useSlots() as InputSlots
const { translate } = useTranslate('input')
const isPwdVisible = ref<boolean>(false)
const clearing = ref<boolean>(false) // 是否正在清空操作,避免重复触发失焦
const focused = ref<boolean>(false) // 控制聚焦
const focusing = ref<boolean>(false) // 当前是否激活状态
const inputValue = ref<string | number>(getInitValue()) // 输入框的值
const cell = useCell()
watch(
() => props.focus,
(newValue) => {
focused.value = newValue
},
{ immediate: true, deep: true }
)
watch(
() => props.modelValue,
(newValue) => {
inputValue.value = isDef(newValue) ? String(newValue) : ''
}
)
const { parent: form } = useParent(FORM_KEY)
const placeholderValue = computed(() => {
return isDef(props.placeholder) ? props.placeholder : translate('placeholder')
})
/**
* 展示清空按钮
*/
const showClear = computed(() => {
const { disabled, readonly, clearable, clearTrigger } = props
if (clearable && !readonly && !disabled && inputValue.value && (clearTrigger === 'always' || (props.clearTrigger === 'focus' && focusing.value))) {
return true
} else {
return false
}
})
/**
* 展示字数统计
*/
const showWordCount = computed(() => {
const { disabled, readonly, maxlength, showWordLimit } = props
return Boolean(!disabled && !readonly && isDef(maxlength) && maxlength > -1 && showWordLimit)
})
/**
* 表单错误提示信息
*/
const errorMessage = computed(() => {
if (form && props.prop && form.errorMessages && form.errorMessages[props.prop]) {
return form.errorMessages[props.prop]
} else {
return ''
}
})
// 是否展示必填
const isRequired = computed(() => {
let formRequired = false
if (form && form.props.rules) {
const rules = form.props.rules
for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key) && key === props.prop && Array.isArray(rules[key])) {
formRequired = rules[key].some((rule: FormItemRule) => rule.required)
}
}
}
return props.required || props.rules.some((rule) => rule.required) || formRequired
})
const rootClass = computed(() => {
return `wd-input ${props.label || slots.label ? 'is-cell' : ''} ${props.center ? 'is-center' : ''} ${cell.border.value ? 'is-border' : ''} ${
props.size ? 'is-' + props.size : ''
} ${props.error ? 'is-error' : ''} ${props.disabled ? 'is-disabled' : ''} ${
inputValue.value && String(inputValue.value).length > 0 ? 'is-not-empty' : ''
} ${props.noBorder ? 'is-no-border' : ''} ${props.customClass}`
})
const labelClass = computed(() => {
return `wd-input__label ${props.customLabelClass}`
})
const inputPlaceholderClass = computed(() => {
return `wd-input__placeholder ${props.placeholderClass}`
})
const labelStyle = computed(() => {
return props.labelWidth
? objToStyle({
'min-width': props.labelWidth,
'max-width': props.labelWidth
})
: ''
})
// 状态初始化
function getInitValue() {
const formatted = formatValue(props.modelValue)
if (!isValueEqual(formatted, props.modelValue)) {
emit('update:modelValue', formatted)
}
return formatted
}
function formatValue(value: string | number) {
const { maxlength } = props
if (isDef(maxlength) && maxlength !== -1 && String(value).length > maxlength) {
return value.toString().slice(0, maxlength)
}
return value
}
function togglePwdVisible() {
isPwdVisible.value = !isPwdVisible.value
}
async function handleClear() {
focusing.value = false
inputValue.value = ''
if (props.focusWhenClear) {
clearing.value = true
focused.value = false
}
await pause()
if (props.focusWhenClear) {
focused.value = true
focusing.value = true
}
emit('update:modelValue', inputValue.value)
emit('clear')
}
async function handleBlur() {
// 等待150毫秒clear执行完毕
await pause(150)
if (clearing.value) {
clearing.value = false
return
}
focusing.value = false
emit('blur', {
value: inputValue.value
})
}
function handleFocus({ detail }: any) {
focusing.value = true
emit('focus', detail)
}
function handleInput({ detail }: any) {
emit('update:modelValue', inputValue.value)
emit('input', detail)
}
function handleKeyboardheightchange({ detail }: any) {
emit('keyboardheightchange', detail)
}
function handleConfirm({ detail }: any) {
emit('confirm', detail)
}
function onClickSuffixIcon() {
emit('clicksuffixicon')
}
function onClickPrefixIcon() {
emit('clickprefixicon')
}
function handleClick(event: MouseEvent) {
emit('click', event)
}
function isValueEqual(value1: number | string, value2: number | string) {
return isEqual(String(value1), String(value2))
}
</script>
<style lang="scss" scoped>
@import './index.scss';
</style>
<style lang="scss">
@import './placeholder.scss';
</style>