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

371 lines
6.5 KiB
Plaintext
Raw Normal View History

2025-07-21 16:47:04 +08:00
<template>
<view
class="cl-textarea"
:class="[
pt.className,
{
'is-dark': isDark,
'cl-textarea--border': border,
'cl-textarea--focus': isFocus,
'cl-textarea--disabled': disabled
}
]"
@tap="onTap"
>
<textarea
class="cl-textarea__inner"
:class="[
{
'is-disabled': disabled,
'is-dark': isDark
},
pt.inner?.className
]"
:style="{
height: parseRpx(height)
}"
:value="value"
:name="name"
:disabled="readonly ?? disabled"
:placeholder="placeholder"
:placeholder-class="`text-surface-400 ${placeholderClass}`"
:maxlength="maxlength"
:auto-focus="isFocus"
:cursor="cursor"
:cursor-spacing="cursorSpacing"
:cursor-color="cursorColor"
:show-confirm-bar="showConfirmBar"
:confirm-hold="confirmHold"
:auto-height="autoHeight"
:fixed="fixed"
:adjust-position="adjustPosition"
:hold-keyboard="holdKeyboard"
:inputmode="inputmode"
:disable-default-padding="disableDefaultPadding"
:adjust-keyboard-to="adjustKeyboardTo"
@confirm="onConfirm"
@input="onInput"
@linechange="onLineChange"
@blur="onBlur"
@keyboardheightchange="onKeyboardheightchange"
@focus="onFocus"
/>
<text class="cl-textarea__count" v-if="showWordLimit"
>{{ value.length }}/{{ maxlength }}</text
>
</view>
</template>
<script setup lang="ts">
import { computed, nextTick, ref, watch, type PropType } from "vue";
import { parsePt, parseRpx } from "@/cool";
import type { PassThroughProps } from "../../types";
import { isDark } from "@/cool";
import { t } from "@/locale";
defineOptions({
name: "cl-textarea"
});
// 组件属性定义
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 绑定值
modelValue: {
type: String,
default: ""
},
// 是否显示边框
border: {
type: Boolean,
default: true
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 是否只读
readonly: {
type: Boolean,
default: null
},
// 是否显示字数统计
showWordLimit: {
type: Boolean,
default: true
},
// 名称
name: {
type: String,
default: ""
},
// 占位符
placeholder: {
type: String,
default: t("请输入")
},
// 占位符样式类
placeholderClass: {
type: String,
default: ""
},
// 最大输入长度
maxlength: {
type: Number,
default: 140
},
// 是否自动聚焦
autofocus: {
type: Boolean,
default: false
},
// 设置键盘右下角按钮的文字
confirmType: {
type: String as PropType<"done" | "go" | "next" | "search" | "send">,
default: "done"
},
// 指定focus时的光标位置
cursor: {
type: Number,
default: 0
},
// 点击键盘确认按钮时是否保持键盘不收起
confirmHold: {
type: Boolean,
default: false
},
// 高度
height: {
type: [Number, String],
default: 120
},
// 是否自动增高
autoHeight: {
type: Boolean,
default: false
},
// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
fixed: {
type: Boolean,
default: false
},
// 光标与键盘的距离
cursorSpacing: {
type: Number,
default: 5
},
// 指定光标颜色
cursorColor: {
type: String,
default: ""
},
// 是否显示键盘上方带有”完成“按钮那一栏
showConfirmBar: {
type: Boolean,
default: true
},
// 光标起始位置
selectionStart: {
type: Number,
default: -1
},
// 光标结束位置
selectionEnd: {
type: Number,
default: -1
},
// 盘弹起时,是否自动上推页面
adjustPosition: {
type: Boolean,
default: true
},
// 它提供了用户在编辑元素或其内容时可能输入的数据类型的提示。
inputmode: {
type: String as PropType<
"none" | "text" | "decimal" | "numeric" | "tel" | "search" | "email" | "url"
>,
default: "text"
},
// focus时点击页面的时候不收起键盘
holdKeyboard: {
type: Boolean,
default: false
},
// 是否禁用默认内边距
disableDefaultPadding: {
type: Boolean,
default: true
},
// 键盘对齐位置
adjustKeyboardTo: {
type: String as PropType<"cursor" | "bottom">,
default: "cursor"
}
});
// 事件定义
const emit = defineEmits([
"update:modelValue",
"input",
"change",
"focus",
"blur",
"confirm",
"linechange",
"keyboardheightchange"
]);
// 透传样式类型定义
type PassThrough = {
className?: string;
inner?: PassThroughProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 绑定值
const value = ref(props.modelValue);
// 是否聚焦
const isFocus = ref<boolean>(props.autofocus);
// 获取焦点事件
function onFocus() {
isFocus.value = true;
emit("focus");
}
// 失去焦点事件
function onBlur() {
emit("blur");
setTimeout(() => {
isFocus.value = false;
}, 0);
}
// 输入事件
function onInput(e: UniInputEvent) {
const val = e.detail.value;
value.value = val;
emit("update:modelValue", val);
emit("change", val);
}
// 点击确认按钮事件
function onConfirm(e: UniInputConfirmEvent) {
emit("confirm", e);
}
// 键盘高度变化事件
function onKeyboardheightchange(e: UniInputKeyboardHeightChangeEvent) {
emit("keyboardheightchange", e);
}
// 行数变化事件
function onLineChange(e: UniTextareaLineChangeEvent) {
emit("linechange", e);
}
// 点击事件
function onTap() {
if (props.disabled) {
return;
}
isFocus.value = true;
}
// 聚焦方法
function focus() {
setTimeout(() => {
isFocus.value = false;
nextTick(() => {
isFocus.value = true;
});
}, 0);
}
watch(
computed(() => props.modelValue),
(val: string) => {
value.value = val;
}
);
defineExpose({
isFocus,
focus
});
</script>
<style lang="scss" scoped>
.cl-textarea {
@apply flex flex-row items-center bg-white;
@apply rounded-lg;
padding: 16rpx 20rpx;
transition-property: border-color, background-color;
transition-duration: 0.2s;
:deep(.uni-textarea-compute) {
opacity: 0;
}
&__inner {
@apply h-full text-surface-700;
flex: 1;
font-size: 28rpx;
&.is-dark {
@apply text-white;
}
}
&__icon {
@apply flex items-center justify-center h-full;
padding-left: 20rpx;
}
&__count {
@apply text-surface-400 text-sm absolute right-2 bottom-2;
}
&--border {
@apply border border-solid border-surface-200;
}
&--focus {
@apply border-primary-500;
}
&--disabled {
@apply bg-surface-100 opacity-70;
}
&.is-dark {
@apply bg-surface-800;
&.cl-textarea--border {
@apply border-surface-700;
&.cl-textarea--focus {
@apply border-primary-500;
}
}
&.cl-textarea--disabled {
@apply bg-surface-700;
}
}
}
</style>