| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- <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>
|