u
This commit is contained in:
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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user