Repository: Tencent/tdesign-mobile-vue (ts)

Issue:
[组件名称] 描述问题的标题
### tdesign-mobile-vue 版本

0.26.0

### 重现链接

https://tdesign.tencent.com/mobile-vue/components/form

### 重现步骤

个人简介的t-textarea组件v-model绑定了值，输入文字对应的计数器的值不正确，初始有值计数器值仍然为0

### 期望结果

计数器值和输入框内v-model绑定的文字长度一致

### 实际结果

计数器值和输入框内v-model绑定的文字长度不一致

### 框架版本

_No response_

### 浏览器版本

_No response_

### 系统版本

_No response_

### Node版本

15

### 补充说明

_No response_
[Form] t-textarea组件的计数不正确
### tdesign-mobile-vue 版本

0.26.0

### 重现链接

https://stackblitz.com/edit/zlysxy?file=src%2Fdemo.vue

### 重现步骤

Form组件中的textarea组件计数问题

1.  初始化的值不正确
<img width="542" alt="image" src="https://github.com/Tencent/tdesign-mobile-vue/assets/13745660/566a55f4-f1a7-463e-86ee-a42bcb972fe9">


2.  输入了值，数据差1
<img width="550" alt="image" src="https://github.com/Tencent/tdesign-mobile-vue/assets/13745660/ee07c941-80ae-4169-a3da-a186fc25e1e8">

<img width="533" alt="image" src="https://github.com/Tencent/tdesign-mobile-vue/assets/13745660/f59beeed-b0b7-4a7b-8a27-447ecf6886fb">


### 期望结果

正确的计数结果

### 实际结果

_No response_

### 框架版本

_No response_

### 浏览器版本

_No response_

### 系统版本

_No response_

### Node版本

_No response_

### 补充说明

_No response_

Pull request description:
fix(Textarea): resolved indicator error
<!--
首先，感谢你的贡献！😄
请阅读并遵循 [TDesign 贡献指南](https://github.com/Tencent/tdesign/blob/main/docs/contributing.md)，填写以下 pull request 的信息。
PR 在维护者审核通过后会合并，谢谢！
-->

### 🤔 这个 PR 的性质是？

- [x] 日常 bug 修复
- [ ] 新特性提交
- [ ] 文档改进
- [ ] 演示代码改进
- [ ] 组件样式/交互改进
- [ ] CI/CD 改进
- [ ] 重构
- [ ] 代码风格优化
- [ ] 测试用例
- [ ] 分支合并
- [ ] 其他

### 🔗 相关 Issue
fix #900 
fix #901 
<!--
1. 描述相关需求的来源，如相关的 issue 讨论链接。
-->

### 💡 需求背景和解决方案

<!--
1. 要解决的具体问题。
2. 列出最终的 API 实现和用法。
3. 涉及UI/交互变动需要有截图或 GIF。
-->

### 📝 更新日志

<!--
从用户角度描述具体变化，以及可能的 breaking change 和其他风险。
-->

- fix(Textarea): 修复计数器错误

- [ ] 本条 PR 不需要纳入 Changelog

### ☑️ 请求合并前的自查清单

⚠️ 请自检并全部**勾选全部选项**。⚠️

- [ ] 文档已补充或无须补充
- [ ] 代码演示已提供或无须提供
- [ ] TypeScript 定义已补充或无须补充
- [ ] Changelog 已提供或无须提供


Patch:
diff --git a/src/link/link.vue b/src/link/link.vue
index f527949f..be8ce71b 100644
--- a/src/link/link.vue
+++ b/src/link/link.vue
@@ -6,13 +6,13 @@
     :aria-disabled="disabled"
     @click="handleClick"
   >
-    <span v-if="prefixContent" :class="`${baseClass}__prefix-icon ${prefix}-class-prefix-icon`">
+    <span v-if="prefixContent" :class="`${name}__prefix-icon ${prefix}-class-prefix-icon`">
       <t-node :content="prefixContent"></t-node>
     </span>
-    <span :class="`${baseClass}__content`">
+    <span :class="`${name}__content`">
       <t-node :content="linkContent"></t-node>
     </span>
-    <span v-if="suffixContent" :class="`${baseClass}__suffix-icon ${prefix}-class-suffix-icon`">
+    <span v-if="suffixContent" :class="`${name}__suffix-icon ${prefix}-class-suffix-icon`">
       <t-node :content="suffixContent"></t-node>
     </span>
   </a>
@@ -34,7 +34,6 @@ export default defineComponent({
   },
   props: LinkProps,
   setup(props, context) {
-    const baseClass = name;
     const emitEvent = useEmitEvent(props, context.emit);
     const _this = getCurrentInstance();
     const linkContent = computed(() => renderContent(_this, 'default', 'content'));
@@ -46,21 +45,21 @@ export default defineComponent({
       emitEvent('click', e);
     };
 
-    const linkClass = [
-      baseClass,
-      `${baseClass}--${props.theme}`,
-      `${name}--${props.size}`,
+    const linkClass = computed(() => [
+      name,
+      `${name}--${props.theme || 'default'}`,
+      `${name}--${props.size || 'medium'}`,
       {
         [`${name}--disabled`]: props.disabled,
         [`${name}--underline`]: props.underline,
         [`${name}--hover`]: props.hover && !props.disabled,
       },
-    ];
+    ]);
 
     return {
       prefix,
       linkClass,
-      baseClass,
+      name,
       handleClick,
       linkContent,
       prefixContent,
diff --git a/src/result/result.vue b/src/result/result.vue
index ac41274c..c5864864 100644
--- a/src/result/result.vue
+++ b/src/result/result.vue
@@ -46,7 +46,7 @@ export default defineComponent({
     const titleContent = computed(() => renderTNode(internalInstance, 'title'));
     const descriptionContent = computed(() => renderTNode(internalInstance, 'description'));
 
-    const classes = computed(() => [`${name}`, `${name}--theme-${props.theme}`]);
+    const classes = computed(() => [`${name}`, `${name}--theme-${props.theme || 'default'}`]);
 
     let iconContent: any;
     if (props?.theme) {
diff --git a/src/textarea/demos/maxcharacter.vue b/src/textarea/demos/maxcharacter.vue
index 94491045..d0d49c48 100644
--- a/src/textarea/demos/maxcharacter.vue
+++ b/src/textarea/demos/maxcharacter.vue
@@ -1,11 +1,8 @@
 <template>
-  <t-textarea
-    name="标签文字"
-    label="标签文字"
-    placeholder="请输入文字"
-    :maxcharacter="500"
-    indicator
-    :allow-input-over-max="true"
-  />
+  <t-textarea v-model="value" name="标签文字" label="标签文字" placeholder="请输入文字" :maxcharacter="500" indicator />
 </template>
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+const value = ref('');
+</script>
diff --git a/src/textarea/demos/maxlength.vue b/src/textarea/demos/maxlength.vue
index 94ec5f0c..7b610c57 100644
--- a/src/textarea/demos/maxlength.vue
+++ b/src/textarea/demos/maxlength.vue
@@ -1,11 +1,8 @@
 <template>
-  <t-textarea
-    name="标签文字"
-    label="标签文字"
-    placeholder="请输入文字"
-    :maxlength="500"
-    indicator
-    :allow-input-over-max="true"
-  />
+  <t-textarea v-model="value" name="标签文字" label="标签文字" placeholder="请输入文字" :maxlength="500" indicator />
 </template>
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+const value = ref('');
+</script>
diff --git a/src/textarea/textarea.vue b/src/textarea/textarea.vue
index 044205d2..07ada656 100644
--- a/src/textarea/textarea.vue
+++ b/src/textarea/textarea.vue
@@ -94,34 +94,33 @@ export default defineComponent({
 
     const handleInput = (e: any) => {
       if (e.isComposing || e.inputType === 'insertCompositionText') return;
-      textareaValueChangeHandle(e);
+      textareaValueChangeHandle();
     };
 
-    const textareaValueChangeHandle = (e: InputEvent) => {
-      const { target } = e;
-      const { value } = target as HTMLInputElement;
+    const textareaValueChangeHandle = () => {
+      const textarea = textareaRef.value as HTMLInputElement;
       if (
         !props.allowInputOverMax &&
         props.maxcharacter &&
         props.maxcharacter > 0 &&
         !Number.isNaN(props.maxcharacter)
       ) {
-        const { length = 0, characters = '' } = getCharacterLength(value, props.maxcharacter) as {
+        const { length = 0, characters = '' } = getCharacterLength(textarea.value, props.maxcharacter) as {
           length: number;
           characters: string;
         };
         setInnerValue(characters);
         textareaLength.value = length;
       } else {
-        setInnerValue(value);
-        textareaLength.value = String(innerValue.value).length;
+        setInnerValue(textarea.value);
+        textareaLength.value = String(textarea.value).length;
       }
       nextTick(() => setInputValue(innerValue.value));
       adjustTextareaHeight();
     };
 
     const handleCompositionend = (e: InputEvent | CompositionEvent) => {
-      textareaValueChangeHandle(e as InputEvent);
+      textareaValueChangeHandle();
     };
 
     const handleFocus = (e: FocusEvent) => {
@@ -135,6 +134,7 @@ export default defineComponent({
       if (props.autofocus) {
         textareaRef.value?.focus();
       }
+      textareaValueChangeHandle();
       adjustTextareaHeight();
     });
 


Test patch:
diff --git a/src/avatar/__test__/index.test.jsx b/src/avatar/__test__/index.test.jsx
index c7a1b57e..a10f4f8e 100644
--- a/src/avatar/__test__/index.test.jsx
+++ b/src/avatar/__test__/index.test.jsx
@@ -110,7 +110,7 @@ describe('avatar-group', async () => {
   it(': cascading', async () => {
     sizeList.forEach((s) => {
       const wrapper = mount(() => (
-        <AvatarGroup cascading="" size={s}>
+        <AvatarGroup cascading={''} size={s}>
           <Avatar image={IMAGE}></Avatar>
           <Avatar image={IMAGE}></Avatar>
         </AvatarGroup>
diff --git a/src/link/__test__/index.test.jsx b/src/link/__test__/index.test.jsx
index 7670afcc..e32bf68e 100644
--- a/src/link/__test__/index.test.jsx
+++ b/src/link/__test__/index.test.jsx
@@ -18,10 +18,10 @@ describe('Link', () => {
       expect(wrapper.find('.t-link--large').exists()).toBeTruthy();
       const wrapperDefault = mount({
         render() {
-          return <Link size=''>text</Link>;
+          return <Link size={''}>text</Link>;
         },
       });
-      expect(wrapperDefault.find('.t-link--').exists()).toBeTruthy();
+      expect(wrapperDefault.find('.t-link--medium').exists()).toBeTruthy();
     });
     it(':theme', () => {
       const wrapper = mount({
@@ -32,10 +32,10 @@ describe('Link', () => {
       expect(wrapper.find('.t-link--primary').exists()).toBeTruthy();
       const wrapperDefault = mount({
         render() {
-          return <Link theme=''>text</Link>;
+          return <Link theme={''}>text</Link>;
         },
       });
-      expect(wrapperDefault.find('.t-link--').exists()).toBeTruthy();
+      expect(wrapperDefault.find('.t-link--default').exists()).toBeTruthy();
     });
     it(':hover', () => {
       const wrapper = mount({
diff --git a/src/result/__test__/index.test.jsx b/src/result/__test__/index.test.jsx
index cac18d75..80bb5837 100644
--- a/src/result/__test__/index.test.jsx
+++ b/src/result/__test__/index.test.jsx
@@ -49,7 +49,8 @@ describe('Result.vue', () => {
     const wrapper1 = mount(() => <Result theme="success"></Result>);
     const wrapper2 = mount(() => <Result theme="warning"></Result>);
     const wrapper3 = mount(() => <Result theme="error"></Result>);
-    const wrapper4 = mount(() => <Result theme=""></Result>);
+    const wrapper4 = mount(() => <Result theme={''}></Result>);
+
 
     expect(wrapper1.classes()).toContain('t-result--theme-success');
     expect(wrapper1.findComponent(CheckCircleIcon).exists()).toBeTruthy();
@@ -58,6 +59,6 @@ describe('Result.vue', () => {
     expect(wrapper3.classes()).toContain('t-result--theme-error');
     expect(wrapper3.findComponent(CloseCircleIcon).exists()).toBeTruthy();
 
-    expect(wrapper4.classes()).toContain('t-result--theme-');
+    expect(wrapper4.classes()).toContain('t-result--theme-default');
   });
 });
diff --git a/src/textarea/__test__/index.test.jsx b/src/textarea/__test__/index.test.jsx
index fbc2d6ca..841b8617 100644
--- a/src/textarea/__test__/index.test.jsx
+++ b/src/textarea/__test__/index.test.jsx
@@ -25,9 +25,10 @@ describe('Textarea.vue', () => {
     const wrapper = mount(
       <Textarea label="标题" v-model={value.value} maxcharacter={maxcharacter} onChange={onChange} />,
     );
+    expect(onChange).toBeCalledTimes(1);
     const el = wrapper.find('textarea').element;
     await simulateEvent(el, '一个汉字等于两个字符，超出会被剪切', 'input');
-    expect(onChange).toBeCalledTimes(1);
+    expect(onChange).toBeCalledTimes(2);
     expect(onChange).toHaveBeenCalledWith('一');
   });
 
@@ -35,9 +36,10 @@ describe('Textarea.vue', () => {
     const value = ref('');
     const onChange = vi.fn();
     const wrapper = mount(<Textarea label="标题" v-model={value.value} autosize onChange={onChange} />);
+    expect(onChange).toBeCalledTimes(1);
     const el = wrapper.find('textarea').element;
     await simulateEvent(el, '这里是一段很长很长很长的长文本，支持自动换行', 'input');
-    expect(onChange).toBeCalledTimes(1);
+    expect(onChange).toBeCalledTimes(2);
   });
 
   it(': placeholder', async () => {