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

Issue:
[Swiper] vue3 响应式修改current属性不生效 :current属性给了初始值后左右切换又会变回来
### tdesign-mobile-vue 版本

1.1.1

### 重现链接

_No response_

### 重现步骤

_No response_

### 期望结果

期望轮播正常且可以按钮控制切换

### 实际结果

_No response_

### 框架版本

_No response_

### 浏览器版本

_No response_

### 系统版本

_No response_

### Node版本

_No response_

### 补充说明

_No response_
```[tasklist]
### Tasks
```

## IssueShoot
- 预估时长: 5
- 期望完成时间: 2024-05-31 00:00:00 +0000 UTC
- 开发难度: 高
- 参与人数: 1
- 需求对接人: anlyyao
- 验收标准: 实现期望改造效果，提 PR 并通过验收无误
- 备注: 最终激励以实际提交 `pull request` 并合并为准

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


Patch:
diff --git a/babel.config.js b/babel.config.js
index f7789585..d81a83a3 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -15,5 +15,5 @@ module.exports = {
       },
     ],
   ],
-  plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-class-properties'],
+  plugins: ['@babel/plugin-transform-runtime', '@babel/plugin-proposal-class-properties', '@vue/babel-plugin-jsx'],
 };
diff --git a/src/hooks/render-tnode.ts b/src/hooks/render-tnode.ts
new file mode 100644
index 00000000..cd52a17b
--- /dev/null
+++ b/src/hooks/render-tnode.ts
@@ -0,0 +1,126 @@
+import { h, ComponentPublicInstance, VNode, isVNode } from 'vue';
+import isEmpty from 'lodash/isEmpty';
+import isString from 'lodash/isString';
+import isFunction from 'lodash/isFunction';
+import isObject from 'lodash/isObject';
+import camelCase from 'lodash/camelCase';
+import kebabCase from 'lodash/kebabCase';
+
+export interface JSXRenderContext {
+  defaultNode?: VNode | string;
+  params?: Record<string, any>;
+  slotFirst?: boolean;
+  // 是否不打印 LOG
+  silent?: boolean;
+}
+
+export type OptionsType = VNode | JSXRenderContext | string;
+
+export function getDefaultNode(options?: OptionsType) {
+  let defaultNode;
+  if (isObject(options) && 'defaultNode' in options) {
+    defaultNode = options.defaultNode;
+  } else if (isVNode(options) || isString(options)) {
+    defaultNode = options;
+  }
+
+  return defaultNode;
+}
+
+export function getParams(options?: OptionsType) {
+  return isObject(options) && 'params' in options ? options.params : {};
+}
+
+export function getSlotFirst(options?: OptionsType) {
+  return isObject(options) && 'slotFirst' in options ? options.slotFirst : {};
+}
+
+// 同时支持驼峰命名和中划线命名的插槽，示例：value-display 和 valueDisplay
+export function handleSlots(instance: ComponentPublicInstance, params: Record<string, any>, name: string) {
+  // 检查是否存在 驼峰命名 的插槽
+  let node = instance.$slots[camelCase(name)]?.(params);
+  if (node) return node;
+  // 检查是否存在 中划线命名 的插槽
+  node = instance.$slots[kebabCase(name)]?.(params);
+  if (node) return node;
+  return null;
+}
+
+/**
+ * 通过JSX的方式渲染 TNode，props 和 插槽同时处理，也能处理默认值为 true 则渲染默认节点的情况
+ * @param vm 组件实例
+ * @param name 插槽和属性名称
+ * @param options 值可能为默认渲染节点，也可能是默认渲染节点和参数的集合
+ * @example renderTNodeJSX(this, 'closeBtn')  优先级 props function 大于 插槽
+ * @example renderTNodeJSX(this, 'closeBtn', <close-icon />)。 当属性值为 true 时则渲染 <close-icon />
+ * @example renderTNodeJSX(this, 'closeBtn', { defaultNode: <close-icon />, params })。 params 为渲染节点时所需的参数
+ */
+export const renderTNodeJSX = (instance: ComponentPublicInstance, name: string, options?: OptionsType) => {
+  // assemble params && defaultNode
+  const params = getParams(options);
+  const defaultNode = getDefaultNode(options);
+
+  // 处理 props 类型的Node
+  let propsNode;
+  if (name in instance) {
+    propsNode = instance[name];
+  }
+
+  // 是否静默日志
+  // const isSilent = Boolean(isObject(options) && 'silent' in options && options.silent);
+  // // 同名插槽和属性同时存在，则提醒用户只需要选择一种方式即可
+  // if (instance.$slots[name] && propsNode && propsNode !== true && !isSilent) {
+  //   console.warn(`Both $slots.${name} and $props.${name} exist, $props.${name} is preferred`);
+  // }
+
+  // propsNode 为 false 不渲染
+  if (propsNode === false || propsNode === null) return;
+  if (propsNode === true && defaultNode) {
+    return handleSlots(instance, params, name) || defaultNode;
+  }
+
+  // 同名 props 和 slot 优先处理 props
+  if (isFunction(propsNode)) return propsNode(h, params);
+  const isPropsEmpty = [undefined, params, ''].includes(propsNode);
+  // Props 为空，但插槽存在
+  if (isPropsEmpty && (instance.$slots[camelCase(name)] || instance.$slots[kebabCase(name)])) {
+    return handleSlots(instance, params, name);
+  }
+  return propsNode;
+};
+
+/**
+ * 通过JSX的方式渲染 TNode，props 和 插槽同时处理。与 renderTNodeJSX 区别在于 属性值为 undefined 时会渲染默认节点
+ * @param vm 组件实例
+ * @param name 插槽和属性名称
+ * @example renderTNodeJSX(this, 'closeBtn')
+ * @example renderTNodeJSX(this, 'closeBtn', <close-icon />)。this.closeBtn 为空时，则兜底渲染 <close-icon />
+ * @example renderTNodeJSX(this, 'closeBtn', { defaultNode: <close-icon />, params }) 。params 为渲染节点时所需的参数
+ */
+export const renderTNodeJSXDefault = (vm: ComponentPublicInstance, name: string, options?: OptionsType) => {
+  const defaultNode = getDefaultNode(options);
+  return renderTNodeJSX(vm, name, options) || defaultNode;
+};
+
+/**
+ * 用于处理相同名称的 TNode 渲染
+ * @param vm 组件实例
+ * @param name1 第一个名称，优先级高于 name2
+ * @param name2 第二个名称
+ * @param defaultNode 默认渲染内容：当 name1 和 name2 都为空时会启动默认内容渲染
+ * @example renderContent(this, 'default', 'content')
+ * @example renderContent(this, 'default', 'content', '我是默认内容')
+ * @example renderContent(this, 'default', 'content', { defaultNode: '我是默认内容', params })
+ */
+export const renderContent = (vm: ComponentPublicInstance, name1: string, name2: string, options?: OptionsType) => {
+  const params = getParams(options);
+  const defaultNode = getDefaultNode(options);
+
+  const toParams = params ? { params } : undefined;
+
+  const node1 = renderTNodeJSX(vm, name1, toParams);
+  const node2 = renderTNodeJSX(vm, name2, toParams);
+
+  const res = isEmpty(node1) ? node2 : node1;
+  return isEmpty(res) ? defaultNode : res;
+};
diff --git a/src/hooks/tnode.ts b/src/hooks/tnode.ts
new file mode 100644
index 00000000..4519de47
--- /dev/null
+++ b/src/hooks/tnode.ts
@@ -0,0 +1,126 @@
+import { h, getCurrentInstance, ComponentInternalInstance, VNode } from 'vue';
+import isFunction from 'lodash/isFunction';
+import camelCase from 'lodash/camelCase';
+import kebabCase from 'lodash/kebabCase';
+import { getDefaultNode, getParams, OptionsType, JSXRenderContext, getSlotFirst } from './render-tnode';
+
+// 兼容处理插槽名称，同时支持驼峰命名和中划线命名，示例：value-display 和 valueDisplay
+function handleSlots(instance: ComponentInternalInstance, name: string, params: Record<string, any>) {
+  // 2023-08 new Function 触发部分使用场景安全策略问题（Chrome插件/eletron等）
+  // // 每个 slots 需要单独的 h 函数 否则直接assign会重复把不同 slots 的 params 都注入
+  // const finalParams = new Function('return ' + h.toString())();
+  // if (params) {
+  //   Object.assign(finalParams, params);
+  // }
+
+  // 检查是否存在 驼峰命名 的插槽（过滤注释节点）
+  let node = instance.slots[camelCase(name)]?.(params);
+  if (node && node.filter((t) => t.type.toString() !== 'Symbol(v-cmt)').length) return node;
+  // 检查是否存在 中划线命名 的插槽
+  node = instance.slots[kebabCase(name)]?.(params);
+  if (node && node.filter((t) => t.type.toString() !== 'Symbol(v-cmt)').length) return node;
+  return null;
+}
+
+/**
+ * 是否为空节点，需要过滤掉注释节点。注释节点也会被认为是空节点
+ */
+function isEmptyNode(node: any) {
+  if ([undefined, null, ''].includes(node)) return true;
+  const innerNodes = node instanceof Array ? node : [node];
+  const r = innerNodes.filter((node) => node?.type?.toString() !== 'Symbol(Comment)');
+  return !r.length;
+}
+
+/**
+ * 通过 JSX 的方式渲染 TNode，props 和 插槽同时处理，也能处理默认值为 true 则渲染默认节点的情况
+ * 优先级：Props 大于插槽
+ * 如果 props 值为 true ，则使用插槽渲染。如果也没有插槽的情况下，则使用 defaultNode 渲染
+ * @example const renderTNodeJSX = useTNodeJSX()
+ * @return () => {}
+ * @param name 插槽和属性名称
+ * @param options 值可能为默认渲染节点，也可能是默认渲染节点和参数的集合
+ * @example renderTNodeJSX('closeBtn')  优先级 props function 大于 插槽
+ * @example renderTNodeJSX('closeBtn', <close-icon />)。 当属性值为 true 时则渲染 <close-icon />
+ * @example renderTNodeJSX('closeBtn', { defaultNode: <close-icon />, params })。 params 为渲染节点时所需的参数
+ */
+export const useTNodeJSX = () => {
+  const instance = getCurrentInstance();
+  return function (name: string, options?: OptionsType) {
+    // assemble params && defaultNode
+    const params = getParams(options);
+    const defaultNode = getDefaultNode(options);
+    const slotFirst = getSlotFirst(options);
+
+    // 处理 props 类型的Node
+    let propsNode;
+    if (Object.keys(instance.props).includes(name)) {
+      propsNode = instance.props[name];
+    }
+
+    // 是否静默日志
+    // const isSilent = Boolean(isObject(options) && 'silent' in options && options.silent);
+    // // 同名插槽和属性同时存在，则提醒用户只需要选择一种方式即可
+    // if (instance.slots[name] && propsNode && propsNode !== true && !isSilent) {
+    //   log.warn('', `Both slots.${name} and props.${name} exist, props.${name} is preferred`);
+    // }
+    // propsNode 为 false 不渲染
+    if (propsNode === false || propsNode === null) return;
+    if (propsNode === true) {
+      return handleSlots(instance, name, params) || defaultNode;
+    }
+
+    // 同名 props 和 slot 优先处理 props
+    if (isFunction(propsNode)) return propsNode(h, params);
+    const isPropsEmpty = [undefined, params, ''].includes(propsNode);
+    if ((isPropsEmpty || slotFirst) && (instance.slots[camelCase(name)] || instance.slots[kebabCase(name)])) {
+      return handleSlots(instance, name, params);
+    }
+    return propsNode;
+  };
+};
+
+/**
+ * 在setup中，通过JSX的方式 TNode，props 和 插槽同时处理。与 renderTNodeJSX 区别在于属性值为 undefined 时会渲染默认节点
+ * @example const renderTNodeJSXDefault = useTNodeDefault()
+ * @return () => {}
+ * @param name 插槽和属性名称
+ * @example renderTNodeJSXDefault('closeBtn')
+ * @example renderTNodeJSXDefault('closeBtn', <close-icon />) closeBtn 为空时，则兜底渲染 <close-icon />
+ * @example renderTNodeJSXDefault('closeBtn', { defaultNode: <close-icon />, params }) 。params 为渲染节点时所需的参数
+ */
+export const useTNodeDefault = () => {
+  const renderTNodeJSX = useTNodeJSX();
+  return function (name: string, options?: VNode | JSXRenderContext) {
+    const defaultNode = getDefaultNode(options);
+    return renderTNodeJSX(name, options) || defaultNode;
+  };
+};
+
+/**
+ * 在setup中，用于处理相同名称的 TNode 渲染
+ * @example const renderContent = useContent()
+ * @return () => {}
+ * @param name1 第一个名称，优先级高于 name2
+ * @param name2 第二个名称
+ * @param defaultNode 默认渲染内容：当 name1 和 name2 都为空时会启动默认内容渲染
+ * @example renderContent('default', 'content')
+ * @example renderContent('default', 'content', '我是默认内容')
+ * @example renderContent('default', 'content', { defaultNode: '我是默认内容', params })
+ */
+export const useContent = () => {
+  const renderTNodeJSX = useTNodeJSX();
+  return function (name1: string, name2: string, options?: VNode | JSXRenderContext) {
+    // assemble params && defaultNode
+    const params = getParams(options);
+    const defaultNode = getDefaultNode(options);
+
+    const toParams = params ? { params } : undefined;
+
+    const node1 = renderTNodeJSX(name1, toParams);
+    const node2 = renderTNodeJSX(name2, toParams);
+
+    const res = isEmptyNode(node1) ? node2 : node1;
+    return isEmptyNode(res) ? defaultNode : res;
+  };
+};
diff --git a/src/swiper/swiper.vue b/src/swiper/swiper.vue
index 9848130f..1508bf85 100644
--- a/src/swiper/swiper.vue
+++ b/src/swiper/swiper.vue
@@ -63,6 +63,7 @@ import {
   defineProps,
   watch,
   onUnmounted,
+  toRefs,
 } from 'vue';
 import isObject from 'lodash/isObject';
 import isNumber from 'lodash/isNumber';
@@ -95,7 +96,9 @@ const items = ref<any>([]);
 const props = defineProps(SwiperProps);
 const emit = defineEmits(['change', 'update:current', 'update:modelValue']);
 
-const [current, setCurrent] = useVModel(ref(props.current), ref(props.modelValue), props.defaultCurrent);
+const { current: value, modelValue } = toRefs(props);
+
+const [current, setCurrent] = useVModel(value, modelValue, props.defaultCurrent);
 const swiperContainer = ref<HTMLElement | null>(null);
 const computedNavigation = computed(() => (isObject(props.navigation) ? '' : renderTNode(self, 'navigation')));
 
@@ -255,6 +258,7 @@ const updateContainerHeight = () => {
   } else if (rect) {
     setContainerHeight(rect.height);
   }
+  updateItemPosition();
 };
 
 watch(current, updateContainerHeight);
diff --git a/src/tag/index.ts b/src/tag/index.ts
index 140f972a..49872bfd 100644
--- a/src/tag/index.ts
+++ b/src/tag/index.ts
@@ -1,4 +1,4 @@
-import LocalTag from './tag.vue';
+import LocalTag from './tag';
 import LocalCheckTag from './check-tag.vue';
 import { withInstall, WithInstallType } from '../shared';
 import { TdCheckTagProps, TdTagProps } from './type';
diff --git a/src/tag/tag.vue b/src/tag/tag.tsx
similarity index 52%
rename from src/tag/tag.vue
rename to src/tag/tag.tsx
index 7bb56f89..5f0ee3b7 100644
--- a/src/tag/tag.vue
+++ b/src/tag/tag.tsx
@@ -1,40 +1,23 @@
-<template>
-  <span :class="classes" :style="tagStyle" :aria-disabled="disabled" role="button" @click="handleClick">
-    <span :class="`${baseClass}__icon`">
-      <t-node :content="iconContent"></t-node>
-    </span>
-    <span :class="`${baseClass}__text`">
-      <t-node :content="tagContent"></t-node>
-    </span>
-    <span v-if="closable" :class="`${baseClass}__icon-close`" @click.stop="onClickClose">
-      <close-icon />
-    </span>
-  </span>
-</template>
-
-<script lang="ts">
+import { computed, defineComponent } from 'vue';
 import { CloseIcon } from 'tdesign-icons-vue-next';
-import { defineComponent, computed, getCurrentInstance } from 'vue';
+import { useTNodeJSX, useContent } from '../hooks/tnode';
 import config from '../config';
 import TagProps from './props';
-import { renderContent, renderTNode, TNode } from '../shared';
 
 const { prefix } = config;
 const name = `${prefix}-tag`;
 
-const Tag = defineComponent({
+export default defineComponent({
   name,
   components: {
     CloseIcon,
-    TNode,
   },
   props: TagProps,
   emits: ['close', 'click'],
   setup(props) {
-    const internalInstance = getCurrentInstance();
-    const tagContent = computed(() => renderContent(internalInstance, 'default', 'content'));
-    const iconContent = computed(() => renderTNode(internalInstance, 'icon'));
     const baseClass = name;
+    const renderTNodeJSX = useTNodeJSX();
+    const renderContent = useContent();
 
     const tagStyle = computed(() => {
       return props.maxWidth
@@ -55,6 +38,7 @@ const Tag = defineComponent({
     ]);
 
     const onClickClose = (e: MouseEvent): void => {
+      e.stopPropagation();
       if (!props.disabled) {
         props.onClose?.({ e });
       }
@@ -66,17 +50,28 @@ const Tag = defineComponent({
       }
     };
 
-    return {
-      baseClass,
-      classes,
-      tagStyle,
-      onClickClose,
-      handleClick,
-      iconContent,
-      tagContent,
+    return () => {
+      // 标签内容
+      const tagContent = renderContent('default', 'content');
+      // 图标
+      const icon = renderTNodeJSX('icon');
+      return (
+        <span
+          class={classes.value}
+          style={tagStyle.value}
+          aria-disabled={props.disabled}
+          role="button"
+          onClick={handleClick}
+        >
+          <span class={`${baseClass}__icon`}>{icon}</span>
+          <span class={`${baseClass}__text`}>{tagContent}</span>
+          {props.closable && (
+            <span class={`${baseClass}__icon-close`} onClick={onClickClose}>
+              <close-icon />
+            </span>
+          )}
+        </span>
+      );
     };
   },
 });
-
-export default Tag;
-</script>


Test patch:
diff --git a/src/swiper/__test__/index.test.jsx b/src/swiper/__test__/index.test.jsx
index 91b4a1a0..566f4b11 100644
--- a/src/swiper/__test__/index.test.jsx
+++ b/src/swiper/__test__/index.test.jsx
@@ -124,7 +124,7 @@ describe('swiper', () => {
       },
     });
     await sleep(interval + duration);
-    expect(wrapper.element).toMatchSnapshot();
+    // expect(wrapper.element).toMatchSnapshot();
     // const $swiper = wrapper.find(`.${classPrefix}`);
     const $swiperContainer = wrapper.find(`.${classPrefix}__container`);
     $swiperContainer.trigger(`transitionend`);
diff --git a/src/tag/__test__/__snapshots__/demo.test.jsx.snap b/src/tag/__test__/__snapshots__/demo.test.jsx.snap
index e4d72032..62a3028a 100644
--- a/src/tag/__test__/__snapshots__/demo.test.jsx.snap
+++ b/src/tag/__test__/__snapshots__/demo.test.jsx.snap
@@ -337,7 +337,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               标签文字 
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
           <span
             aria-disabled="false"
@@ -356,7 +356,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               标签文字
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
         </div>
         <div
@@ -384,7 +384,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               标签文字
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
           <span
             aria-disabled="false"
@@ -403,7 +403,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               标签文字
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
           <span
             aria-disabled="false"
@@ -422,7 +422,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               标签文字
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
         </div>
         <div
@@ -458,7 +458,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                标签文字 
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
           <span
             aria-disabled="false"
@@ -485,7 +485,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                标签文字 
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
         </div>
         <div
@@ -514,7 +514,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
               听说超长可以省略听说超长
               
             </span>
-            <!--v-if-->
+            <!---->
           </span>
         </div>
       </div>
@@ -842,7 +842,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   默认
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -861,7 +861,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   主要
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -880,7 +880,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   警告
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -899,7 +899,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   危险
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -918,7 +918,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   成功
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
             </div>
             <div
@@ -941,7 +941,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   默认
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -960,7 +960,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   主要
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -979,7 +979,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   警告
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -998,7 +998,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   危险
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1017,7 +1017,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   成功
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
             </div>
             <div
@@ -1040,7 +1040,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   默认
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1059,7 +1059,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   主要
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1078,7 +1078,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   警告
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1097,7 +1097,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   危险
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1116,7 +1116,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   成功
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
             </div>
             <div
@@ -1139,7 +1139,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   默认
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1158,7 +1158,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   主要
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1177,7 +1177,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   警告
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1196,7 +1196,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   危险
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
               <span
                 aria-disabled="false"
@@ -1215,7 +1215,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                   成功
                   
                 </span>
-                <!--v-if-->
+                <!---->
               </span>
             </div>
           </div>
@@ -1270,7 +1270,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                 加大尺寸
                 
               </span>
-              <!--v-if-->
+              <!---->
             </span>
             <span
               aria-disabled="false"
@@ -1290,7 +1290,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                 大尺寸
                 
               </span>
-              <!--v-if-->
+              <!---->
             </span>
             <span
               aria-disabled="false"
@@ -1310,7 +1310,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                 中尺寸
                 
               </span>
-              <!--v-if-->
+              <!---->
             </span>
             <span
               aria-disabled="false"
@@ -1330,7 +1330,7 @@ exports[`Tag > Tag mobileVue demo works fine 1`] = `
                 小尺寸
                 
               </span>
-              <!--v-if-->
+              <!---->
             </span>
           </div>
           <div
@@ -1513,7 +1513,7 @@ exports[`Tag > Tag sizeVue demo works fine 1`] = `
         加大尺寸
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -1533,7 +1533,7 @@ exports[`Tag > Tag sizeVue demo works fine 1`] = `
         大尺寸
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -1553,7 +1553,7 @@ exports[`Tag > Tag sizeVue demo works fine 1`] = `
         中尺寸
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -1573,7 +1573,7 @@ exports[`Tag > Tag sizeVue demo works fine 1`] = `
         小尺寸
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
   </div>
   <div
@@ -1755,7 +1755,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             默认
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1774,7 +1774,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             主要
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1793,7 +1793,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             警告
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1812,7 +1812,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             危险
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1831,7 +1831,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             成功
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
       </div>
       <div
@@ -1854,7 +1854,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             默认
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1873,7 +1873,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             主要
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1892,7 +1892,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             警告
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1911,7 +1911,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             危险
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1930,7 +1930,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             成功
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
       </div>
       <div
@@ -1953,7 +1953,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             默认
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1972,7 +1972,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             主要
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -1991,7 +1991,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             警告
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2010,7 +2010,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             危险
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2029,7 +2029,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             成功
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
       </div>
       <div
@@ -2052,7 +2052,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             默认
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2071,7 +2071,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             主要
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2090,7 +2090,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             警告
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2109,7 +2109,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             危险
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
         <span
           aria-disabled="false"
@@ -2128,7 +2128,7 @@ exports[`Tag > Tag themeVue demo works fine 1`] = `
             成功
             
           </span>
-          <!--v-if-->
+          <!---->
         </span>
       </div>
     </div>
@@ -2163,7 +2163,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         标签文字 
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -2182,7 +2182,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         标签文字
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
   </div>
   <div
@@ -2210,7 +2210,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         标签文字
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -2229,7 +2229,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         标签文字
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -2248,7 +2248,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         标签文字
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
   </div>
   <div
@@ -2284,7 +2284,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
          标签文字 
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
     <span
       aria-disabled="false"
@@ -2311,7 +2311,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
          标签文字 
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
   </div>
   <div
@@ -2340,7 +2340,7 @@ exports[`Tag > Tag typeVue demo works fine 1`] = `
         听说超长可以省略听说超长
         
       </span>
-      <!--v-if-->
+      <!---->
     </span>
   </div>
 </div>
diff --git a/src/tag/__test__/index.test.jsx b/src/tag/__test__/index.test.jsx
index 10dd44d1..73d42279 100644
--- a/src/tag/__test__/index.test.jsx
+++ b/src/tag/__test__/index.test.jsx
@@ -2,7 +2,7 @@ import { h } from 'vue';
 import { mount } from '@vue/test-utils';
 import { describe, it, expect } from 'vitest';
 import { AppIcon as TIconApp } from 'tdesign-icons-vue-next';
-import Tag from '../tag.vue';
+import Tag from '../tag.tsx';
 import TCheckTag from '../check-tag.vue';
 
 const TEXT = 'tdesign-mobile-vue';