Files
WAI_Project_UNIX/uni_modules/cool-ui/components/cl-input/cl-input.uvue

448 lines
8.0 KiB
Plaintext
Raw Normal View History

2025-07-21 16:47:04 +08:00
<template>
<view
class="cl-input"
:class="[
pt.className,
{
'is-dark': isDark,
'cl-input--border': border,
'cl-input--focus': isFocus,
'cl-input--disabled': isDisabled,
'cl-input--error': isError,
2025-07-21 16:47:04 +08:00
}
]"
@tap="onTap"
>
<slot name="prepend"></slot>
<view class="cl-input__icon !pl-0 pr-[12rpx]" v-if="prefixIcon">
<cl-icon
:name="prefixIcon"
:size="pt.prefixIcon?.size ?? 32"
:pt="{ className: parseClass([pt.prefixIcon?.className]) }"
></cl-icon>
</view>
<input
class="cl-input__inner"
:class="[
{
2025-08-06 10:18:17 +08:00
'is-disabled': isDisabled,
'is-dark': isDark,
'is-exceed': isExceed
2025-07-21 16:47:04 +08:00
},
ptClassName
2025-07-21 16:47:04 +08:00
]"
:style="inputStyle"
2025-07-21 16:47:04 +08:00
:value="value"
2025-08-06 10:18:17 +08:00
:disabled="readonly ?? isDisabled"
2025-07-21 16:47:04 +08:00
:type="type"
:password="isPassword"
:focus="isFocus"
:placeholder="placeholder"
:placeholder-class="`text-surface-400 ${placeholderClass}`"
:maxlength="maxlength"
:cursor-spacing="cursorSpacing"
:confirm-type="confirmType"
:confirm-hold="confirmHold"
:adjust-position="adjustPosition"
:hold-keyboard="holdKeyboard"
@input="onInput"
@focus="onFocus"
@blur="onBlur"
@confirm="onConfirm"
@keyboardheightchange="onKeyboardheightchange"
/>
<view class="cl-input__icon" v-if="suffixIcon">
<cl-icon
:name="suffixIcon"
:size="pt.suffixIcon?.size ?? 32"
:pt="{ className: parseClass([pt.prefixIcon?.className]) }"
></cl-icon>
</view>
<view class="cl-input__icon" @tap="clear" v-if="showClear">
<cl-icon
name="close-circle-fill"
:size="32"
:pt="{ className: '!text-surface-400' }"
></cl-icon>
</view>
<view class="cl-input__icon" @tap="showPassword" v-if="password">
<cl-icon
:name="isPassword ? 'eye-line' : 'eye-off-line'"
:size="32"
:pt="{ className: '!text-surface-300' }"
></cl-icon>
</view>
<slot name="append"></slot>
</view>
</template>
<script setup lang="ts">
2025-08-12 14:30:20 +08:00
import { computed, nextTick, ref, watch, type PropType } from "vue";
2025-07-21 16:47:04 +08:00
import type { ClInputType, PassThroughProps } from "../../types";
import { isDark, parseClass, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import { t } from "@/locale";
import { useForm, useFormItem, useSize } from "../../hooks";
2025-07-21 16:47:04 +08:00
defineOptions({
name: "cl-input"
});
// 组件属性定义
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 绑定值
modelValue: {
type: String,
default: ""
},
// 输入框类型
type: {
type: String as PropType<ClInputType>,
default: "text"
},
// 前缀图标
prefixIcon: {
type: String,
default: ""
},
// 后缀图标
suffixIcon: {
type: String,
default: ""
},
// 是否密码框
password: {
type: Boolean,
default: false
},
// 是否自动聚焦
autofocus: {
type: Boolean,
default: false
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否只读
readonly: {
type: Boolean,
default: null
},
// 占位符
placeholder: {
type: String,
default: () => t("请输入")
2025-07-21 16:47:04 +08:00
},
// 占位符样式类
placeholderClass: {
type: String,
default: ""
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否可清除
clearable: {
type: Boolean,
default: false
},
// 光标与键盘的距离
cursorSpacing: {
type: Number,
default: 5
},
// 点击键盘确认按钮时是否保持键盘不收起
confirmHold: {
type: Boolean,
default: false
},
// 设置键盘右下角按钮的文字
confirmType: {
type: String as PropType<"done" | "go" | "next" | "search" | "send">,
default: "done"
},
// 键盘弹起时,是否自动上推页面
adjustPosition: {
type: Boolean,
default: true
},
// 最大输入长度
maxlength: {
type: Number,
default: 140
},
// 是否保持键盘不收起
holdKeyboard: {
type: Boolean,
default: false
},
// 保留精度
precision: {
type: Number,
default: 0
2025-07-21 16:47:04 +08:00
}
});
// 事件定义
const emit = defineEmits([
"update:modelValue",
"input",
"change",
"focus",
"blur",
"confirm",
"clear",
"keyboardheightchange"
]);
2025-08-06 10:18:17 +08:00
// cl-form 上下文
const { disabled } = useForm();
// cl-form-item 上下文
const { isError } = useFormItem();
2025-08-06 10:18:17 +08:00
// 是否禁用
const isDisabled = computed(() => {
return disabled.value || props.disabled;
});
2025-07-21 16:47:04 +08:00
// 透传样式类型定义
type PassThrough = {
className?: string;
inner?: PassThroughProps;
prefixIcon?: ClIconProps;
suffixIcon?: ClIconProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 输入框样式
const inputStyle = computed(() => {
const style = {};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
2025-07-21 16:47:04 +08:00
// 绑定值
const value = ref<string>("");
// 是否聚焦
const isFocus = ref<boolean>(props.autofocus);
// 是否显示清除按钮
const showClear = computed(() => {
return isFocus.value && props.clearable && value.value != "";
});
// 是否显示密码
const isPassword = ref(props.password);
// 是否超出限制
const isExceed = computed(() => {
// 检查数字精度是否超出限制
if (props.type == "number" && props.precision >= 0 && value.value != "") {
const parts = value.value.split(".");
return parts.length > 1 && parts[1].length > props.precision;
} else {
return false;
}
});
2025-07-21 16:47:04 +08:00
// 切换密码显示状态
function showPassword() {
isPassword.value = !isPassword.value;
}
// 获取焦点事件
2025-08-20 17:05:17 +08:00
function onFocus(e: UniInputFocusEvent) {
2025-07-21 16:47:04 +08:00
isFocus.value = true;
2025-08-20 17:05:17 +08:00
emit("focus", e);
2025-07-21 16:47:04 +08:00
}
// 失去焦点事件
2025-08-20 17:05:17 +08:00
function onBlur(e: UniInputBlurEvent) {
emit("blur", e);
2025-07-21 16:47:04 +08:00
// 处理数字精度
if (props.type == "number" && props.precision > 0 && value.value != "") {
const numValue = parseFloat(value.value);
if (!isNaN(numValue)) {
const formattedValue = numValue.toFixed(props.precision);
value.value = formattedValue;
emit("update:modelValue", formattedValue);
emit("change", formattedValue);
}
}
2025-07-21 16:47:04 +08:00
setTimeout(() => {
isFocus.value = false;
}, 0);
}
// 输入事件
function onInput(e: UniInputEvent) {
2025-08-20 17:05:17 +08:00
const v1 = e.detail.value;
const v2 = value.value;
2025-07-21 16:47:04 +08:00
2025-08-20 17:05:17 +08:00
value.value = v1;
2025-07-21 16:47:04 +08:00
2025-08-20 17:05:17 +08:00
emit("update:modelValue", v1);
emit("input", e);
if (v1 != v2) {
emit("change", v1);
2025-07-21 16:47:04 +08:00
}
}
// 点击确认按钮事件
function onConfirm(e: UniInputConfirmEvent) {
emit("confirm", e);
}
// 键盘高度变化事件
function onKeyboardheightchange(e: UniInputKeyboardHeightChangeEvent) {
emit("keyboardheightchange", e);
}
// 点击事件
function onTap() {
2025-08-06 10:18:17 +08:00
if (isDisabled.value) {
2025-07-21 16:47:04 +08:00
return;
}
isFocus.value = true;
}
// 聚焦方法
function focus() {
setTimeout(() => {
isFocus.value = false;
nextTick(() => {
isFocus.value = true;
});
}, 0);
}
// 清除方法
function clear() {
value.value = "";
emit("update:modelValue", "");
emit("change", "");
emit("clear");
// #ifdef H5
focus();
// #endif
}
watch(
computed(() => props.modelValue),
(val: string) => {
value.value = val;
},
{
immediate: true
}
);
defineExpose({
isFocus,
focus,
clear
});
</script>
<style lang="scss" scoped>
.cl-input {
@apply flex flex-row items-center bg-white duration-200;
@apply rounded-lg;
height: 66rpx;
padding: 0 20rpx;
transition-property: background-color, border-color;
&__inner {
@apply h-full text-surface-700;
flex: 1;
font-size: 28rpx;
&.is-dark {
@apply text-white;
}
&.is-exceed {
@apply text-red-500;
}
&.is-exceed.is-dark {
@apply text-red-400;
}
2025-07-21 16:47:04 +08:00
}
&__icon {
@apply flex items-center justify-center h-full;
padding-left: 20rpx;
}
&--border {
@apply border border-solid border-surface-200;
}
&--disabled {
@apply bg-surface-100 opacity-70;
}
&--focus {
&.cl-input--border {
@apply border-primary-500;
}
}
&--error {
@apply border-red-500;
}
2025-07-21 16:47:04 +08:00
&.is-dark {
@apply bg-surface-800;
&.cl-input--border {
2025-08-25 17:48:44 +08:00
@apply border-surface-600;
2025-07-21 16:47:04 +08:00
&.cl-input--focus {
@apply border-primary-500;
}
}
&.cl-input--disabled {
@apply bg-surface-700;
}
}
}
</style>