This commit is contained in:
oneao committed 2025-09-03 17:31:30 +08:00
1 parent de2351e1b2
commit ab0489f692
22 files changed
+1100 -300

No files matched your search

@@ -4,35 +4,105 @@
$textarea-prefix: #{$prefix}-textarea;
.#{$textarea-prefix}-wrapper {
width: 100%;
border: 1px solid #ddd;
border-radius: 2px;
box-sizing: border-box;
position: relative;
margin: 0;
padding: 0;
&:hover {
border-color: $input-hover-color;
}
&:focus-within {
border-color: $input-focus-color;
box-shadow: $input-focus-shadow;
}
list-style: none;
position: relative;
width: 100%;
.#{$textarea-prefix}-inner {
width: 100%;
min-height: 80px; // 可根据需要
padding: 8px; // 内边距
border: none; // 去掉默认边框
display: flex;
padding: 5px 8px;
border: none;
outline: none;
resize: vertical;
box-sizing: border-box;
font-size: 13px;
color: #000;
transition:
all cubic-bezier(0.38, 0, 0.24, 1) 0.2s,
height 0s;
border: $input-border;
border-radius: $border-radius;
&::placeholder {
color: #999999;
color: $input-placeholder-color;
}
&:hover {
border-color: $input-hover-border-color;
}
&:focus-within {
border-color: $input-focus-border-color;
box-shadow: $input-focus-shadow;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: #e5e5e5;
border-radius: 0px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: #b2b2b2;
}
}
.#{$textarea-prefix}-disabled {
background-color: $input-disabled-bg-color;
cursor: not-allowed;
color: $input-disabled-font-color;
&:hover,
&:focus-within {
border-color: $input-border-color;
box-shadow: none;
}
}
.#{$textarea-prefix}-readonly {
background-color: $input-readonly-bg-color;
}
.#{$textarea-prefix}-clear {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
width: $input-icon-wrapper-size;
height: $input-icon-wrapper-size;
display: flex;
align-items: center;
justify-content: center;
color: $input-affix-color;
transition:
color 0.2s,
background-color 0.2s;
&:hover {
background-color: $input-icon-hover-bg-color;
border-radius: 50%;
}
}
.#{$textarea-prefix}-count {
position: absolute;
right: 8px;
bottom: 0px;
font-size: 12px;
color: $input-affix-color;
}
}
@@ -1,17 +1,129 @@
import type { PropType } from 'vue'
import { computed, defineComponent, ref } from 'vue'
import { computed, defineComponent, nextTick, onMounted, ref, watch } from 'vue'
import { getPrefixCls } from '../_utils/getPrefixCls'
import Icon from '../icon'
export default defineComponent({
name: 'Textarea',
props: {
modelValue: { type: String, default: '222' },
autosize: { type: [Boolean, Object] as PropType<boolean | { minRows?: number, maxRows?: number }>, default: false },
disabled: Boolean,
readonly: Boolean,
clearable: Boolean,
showCount: Boolean,
maxlength: Number,
autoFocus: Boolean,
},
emits: ['update:modelValue'],
setup() {
const prefixCls = getPrefixCls('input')
setup(props, { emit }) {
const prefixCls = getPrefixCls('textarea')
const textareaRef = ref<HTMLTextAreaElement | null>(null)
const inputValue = ref(props.modelValue || '')
/** textarea 高度自适应 */
const resizeTextarea = () => {
const el = textareaRef.value
if (!el || !props.autosize)
return
el.style.height = 'auto'
const style = getComputedStyle(el)
const border = Number.parseInt(style.borderTopWidth) + Number.parseInt(style.borderBottomWidth)
let height = el.scrollHeight + border
if (typeof props.autosize === 'object') {
const lineHeight = Number.parseInt(style.lineHeight || '20', 10)
const { minRows, maxRows } = props.autosize
if (minRows)
height = Math.max(height, lineHeight * minRows + border)
if (maxRows)
height = Math.min(height, lineHeight * maxRows + border)
}
el.style.height = `${height}px`
}
/** 同步父组件 modelValue */
watch(
() => props.modelValue,
(val) => {
inputValue.value = val || ''
nextTick(resizeTextarea)
},
)
/** 动态 autoFocus */
watch(
() => props.autoFocus,
(val) => {
if (val && textareaRef.value) {
nextTick(() => textareaRef.value!.focus())
}
},
{ immediate: true },
)
/** 更新值 */
const updateValue = (val: string) => {
if (props.maxlength)
val = val.slice(0, props.maxlength)
inputValue.value = val
emit('update:modelValue', val)
nextTick(resizeTextarea)
}
/** 清空 */
const clearValue = () => {
updateValue('')
if (textareaRef.value)
textareaRef.value.focus()
}
const countText = computed(() => {
const len = inputValue.value.length
return props.maxlength ? `${len} / ${props.maxlength}` : `${len}`
})
onMounted(() => nextTick(() => {
resizeTextarea()
if (props.autoFocus && textareaRef.value)
textareaRef.value.focus()
}))
return () => {
const value = inputValue.value
return (
<div class={`${prefixCls}-wrapper`}>
<textarea class={`${prefixCls}-inner`} />
<textarea
ref={textareaRef}
class={[
`${prefixCls}-inner`,
props.disabled && `${prefixCls}-disabled`,
props.readonly && `${prefixCls}-readonly`,
]}
value={value}
onInput={(e: Event) => updateValue((e.target as HTMLTextAreaElement).value)}
disabled={props.disabled}
readonly={props.readonly}
maxlength={props.maxlength}
/>
{/* Clear 按钮 */}
{props.clearable && value && !props.disabled && !props.readonly && (
<span class={`${prefixCls}-clear`} onClick={clearValue}>
<Icon icon="ic:round-clear" />
</span>
)}
{/* 字数统计 */}
{props.showCount && (
<span class={`${prefixCls}-count`}>
{countText.value}
</span>
)}
</div>
)
}