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

Issue:
[Indexes] demo返回方式
### tdesign-mobile-vue 版本

最新版

### 重现链接

_No response_

### 重现步骤

这里可以不用增加按钮用于返回，直接用导航左上角的返回就好
<img width="479" alt="image" src="https://github.com/Tencent/tdesign-mobile-vue/assets/94840745/d710954e-c992-42b9-86c0-f74184516512">


### 期望结果

_No response_

### 实际结果

_No response_

### 框架版本

_No response_

### 浏览器版本

_No response_

### 系统版本

_No response_

### Node版本

_No response_

### 补充说明

_No response_

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


Patch:
diff --git a/.github/workflows/issue-assignees.temp.yml b/.github/workflows/issue-assignees.temp.yml
index f52a0c94..29b803a0 100644
--- a/.github/workflows/issue-assignees.temp.yml
+++ b/.github/workflows/issue-assignees.temp.yml
@@ -15,38 +15,3 @@ jobs:
           issuesOpened: |
             👋 @{{ author }}，感谢给 TDesign 提出了 issue。
             请根据 issue 模版确保背景信息的完善，我们将调查并尽快回复你。
-
-      # https://docs.github.com/cn/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#issues
-      - uses: 94dreamer/issue-assignees@main
-        id: assignees
-        with:
-          project_name: ${{github.event.repository.name}}
-          issue_title: ${{github.event.issue.title}}
-
-      - run: echo ${{ steps.assignees.outputs.contributors }}
-      - name: Add assigness
-        if: steps.assignees.outputs.contributors != ''
-        uses: actions-cool/issues-helper@v3
-        with:
-          actions: 'add-assignees'
-          token: ${{ secrets.GITHUB_TOKEN }}
-          issue-number: ${{ github.event.issue.number }}
-          assignees: ${{ steps.assignees.outputs.contributors }}
-
-      - run: |
-          contributors=${{ steps.assignees.outputs.contributors }}
-          contributorstring=${contributors//,/ @}
-          echo "::set-output name=string::@$contributorstring"
-        id: contributors
-
-      - name: 通知贡献者
-        if: steps.assignees.outputs.contributors != ''
-        uses: actions-cool/maintain-one-comment@v2.0.0
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          body: |
-            ♥️ 有劳 ${{ steps.contributors.outputs.string }} 尽快确认问题。
-            确认有效后将下一步计划和可能需要的时间回复给 @${{ github.event.issue.user.login }} 。
-            <!-- AUTO_ASSIGENEES_NOTIFY_HOOK -->
-          number: ${{ github.event.issue.number }}
-          body-include: '<!-- AUTO_ASSIGENEES_NOTIFY_HOOK -->'
diff --git a/site/mobile/components/header.vue b/site/mobile/components/header.vue
index 1767ac7f..96dcfef7 100644
--- a/site/mobile/components/header.vue
+++ b/site/mobile/components/header.vue
@@ -1,25 +1,18 @@
 <template>
   <div v-show="title" class="tdesign-demo-topnav">
     <div class="tdesign-demo-topnav-title">{{ title }}</div>
-    <chevron-left-icon class="tdesign-demo-topnav__back" name="chevron-left" @click="onBack" />
+    <chevron-left-icon class="tdesign-demo-topnav__back" @click="onBack" />
   </div>
 </template>
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
 import { useRouter } from 'vue-router';
 import { ChevronLeftIcon } from 'tdesign-icons-vue-next';
-export default defineComponent({
-  components: { ChevronLeftIcon },
-  setup() {
-    const router = useRouter();
-    const title = computed(() => router.currentRoute.value.meta.title);
 
-    const onBack = () => {
-      if (history.length > 1) {
-        history.back();
-      }
-    };
-    return { onBack, title };
-  },
-});
+const router = useRouter();
+const title = computed(() => router.currentRoute.value.meta.title);
+
+const onBack = () => {
+  router.go(-1);
+};
 </script>
diff --git a/site/mobile/components/home.vue b/site/mobile/components/home.vue
index 4fb462ac..ca1c8445 100644
--- a/site/mobile/components/home.vue
+++ b/site/mobile/components/home.vue
@@ -2,7 +2,7 @@
   <div class="tdesign-mobile-home">
     <div class="tdesign-mobile-logo">
       <img src="https://tdesign.gtimg.com/site/TDesign.png" alt="logo" />
-      <p>TDesign Mobile Vue是由前端通用 UI 组件库 Oteam 发起，协同公司内有组件库开发经验的同学一起建设的组件库</p>
+      <p>TDesign 适配 Vue 的移动端组件库</p>
     </div>
     <t-collapse v-for="doc in docs.filter((x) => x.type === 'component')" :key="doc.title">
       <t-collapse-panel :name="doc.title" :title="doc.title">
diff --git a/site/mobile/router.ts b/site/mobile/router.ts
index 812bef91..d61dcab4 100644
--- a/site/mobile/router.ts
+++ b/site/mobile/router.ts
@@ -6,7 +6,7 @@ const { docs } = siteConfig;
 
 // 引入所有views下.vue文件
 // @ts-ignore
-const demoModules = import.meta.glob('../../src/**/demos/mobile.vue');
+const demoModules = import.meta.glob('../../src/**/demos/*.vue');
 
 function getDocsRoutes(docs: any[]): RouteRecordRaw[] {
   let docsRoutes: Array<RouteRecordRaw> = [];
@@ -32,15 +32,32 @@ function getDocsRoutes(docs: any[]): RouteRecordRaw[] {
       });
     }
   });
+
   return docsRoutes;
 }
 
+const componentInnerRoutes: RouteRecordRaw[] = [];
+const demoRouteGenerator = (compName, demos, title) => {
+  const ans = demos.map((demo) => ({
+    path: `/${compName}/${demo}`,
+    meta: { title },
+    component: demoModules[`../../src/${compName}/demos/${demo}.vue`],
+  }));
+
+  componentInnerRoutes.push(...ans);
+};
+
+// 组件需要多页面展示时，需要多个路由
+demoRouteGenerator('side-bar', ['base', 'switch', 'with-icon', 'custom'], 'SideBar 侧边栏');
+demoRouteGenerator('indexes', ['base', 'custom'], 'Indexes 索引');
+
 const routes: Array<RouteRecordRaw> = [
   {
     path: '/',
     component: Home,
   },
   ...getDocsRoutes(docs),
+  ...componentInnerRoutes,
 ];
 const router = createRouter({
   history: createWebHashHistory('/mobile.html'),
diff --git a/src/indexes/demos/mobile.vue b/src/indexes/demos/mobile.vue
index 06122ee3..66ea9389 100644
--- a/src/indexes/demos/mobile.vue
+++ b/src/indexes/demos/mobile.vue
@@ -1,5 +1,5 @@
 <template>
-  <div v-if="!showIndexes" class="tdesign-mobile-demo">
+  <div class="tdesign-mobile-demo">
     <h1 class="title">Indexes 索引</h1>
     <p class="summary">用于页面中信息快速检索，可以根据目录中的页码快速找到所需的内容。</p>
     <tdesign-demo-block title="01 组件类型" summary="基础索引类型">
@@ -17,33 +17,15 @@
       </div>
     </tdesign-demo-block>
   </div>
-  <div v-else>
-    <baseDemo v-if="type === 'base'" />
-    <custom v-if="type === 'custom'" />
-    <t-fab
-      :icon="iconFunc"
-      text="返回"
-      :style="{ 'z-index': 100, right: '16px', bottom: '32px' }"
-      :button-props="{ variant: 'outline' }"
-      @click="goBack"
-    />
-  </div>
 </template>
-<script setup lang="ts">
-import { ref, h } from 'vue';
-import { ChevronLeftDoubleIcon } from 'tdesign-icons-vue-next';
-import baseDemo from './base.vue';
-import custom from './custom.vue';
 
-const showIndexes = ref(false);
-const type = ref('letter');
-const iconFunc = () => h(ChevronLeftDoubleIcon);
+<script setup lang="ts">
+import { useRouter } from 'vue-router';
 
-const goBack = () => (showIndexes.value = false);
+const router = useRouter();
 
 const handleClick = (t = 'base') => {
-  showIndexes.value = true;
-  type.value = t;
+  router.push(`/indexes/${t}`);
 };
 </script>
 <style lang="less">
diff --git a/src/side-bar/demos/mobile.vue b/src/side-bar/demos/mobile.vue
index ec7be6b6..a50e037e 100644
--- a/src/side-bar/demos/mobile.vue
+++ b/src/side-bar/demos/mobile.vue
@@ -1,5 +1,5 @@
 <template>
-  <div v-if="!showSidebar" class="tdesign-mobile-demo">
+  <div class="tdesign-mobile-demo">
     <h1 class="title">SideBar 侧边栏</h1>
     <p class="summary">用于内容分类后的展示切换。</p>
     <tdesign-demo-block title="01 组件类型" summary="侧边导航用法" :padding="true">
@@ -21,27 +21,14 @@
       </t-button>
     </tdesign-demo-block>
   </div>
-
-  <div v-else>
-    <BaseDemo v-if="type === 'base'" />
-    <WithIconDemo v-if="type === 'with-icon'" />
-    <SwitchDemo v-if="type === 'switch'" />
-    <CustomDemo v-if="type === 'custom'" />
-  </div>
 </template>
 <script lang="ts" setup>
-import { ref } from 'vue';
-import BaseDemo from './base.vue';
-import SwitchDemo from './switch.vue';
-import WithIconDemo from './with-icon.vue';
-import CustomDemo from './custom.vue';
+import { useRouter } from 'vue-router';
 
-const showSidebar = ref(false);
-const type = ref('');
+const router = useRouter();
 
 const handleClick = (t = 'base') => {
-  showSidebar.value = true;
-  type.value = t;
+  router.push(`/side-bar/${t}`);
 };
 </script>
 
@@ -49,4 +36,8 @@ const handleClick = (t = 'base') => {
 .t-button {
   margin-bottom: 16px;
 }
+
+.tdesign-mobile-demo {
+  background-color: #fff;
+}
 </style>
diff --git a/src/side-bar/side-bar-item.vue b/src/side-bar/side-bar-item.vue
index 8777fa8b..2bb9d464 100644
--- a/src/side-bar/side-bar-item.vue
+++ b/src/side-bar/side-bar-item.vue
@@ -27,14 +27,13 @@ export default defineComponent({
   name,
   components: { TNode },
   props: SideBarItemProps,
-  setup(props, context) {
+  setup(props) {
     const internalInstance = getCurrentInstance();
     const { proxy } = internalInstance as ComponentInternalInstance;
     const sideBarProvide: any = inject('sideBarProvide', undefined);
     sideBarProvide.relation(proxy);
 
-    const index = computed(() => sideBarProvide.state.children.indexOf(proxy));
-
+    const index = computed(() => sideBarProvide.children.value.indexOf(proxy));
     const iconNode = computed(() => renderTNode(internalInstance, 'icon'));
     const isActive = computed(() => index.value === sideBarProvide.currentValue.value);
 
diff --git a/src/side-bar/side-bar.vue b/src/side-bar/side-bar.vue
index 7cf6b0e1..c3e7b19f 100644
--- a/src/side-bar/side-bar.vue
+++ b/src/side-bar/side-bar.vue
@@ -6,7 +6,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive, ComponentInternalInstance, provide, toRefs } from 'vue';
+import { defineComponent, ref, Ref, ComponentInternalInstance, provide, toRefs } from 'vue';
 import config from '../config';
 import SideBarProps from './props';
 import { TdSideBarProps } from './type';
@@ -28,16 +28,14 @@ export default defineComponent({
       'change',
     );
 
-    const state = reactive({
-      children: [] as ComponentInternalInstance[],
-    });
+    const children: Ref<ComponentInternalInstance[]> = ref([]);
 
     const relation = (child: ComponentInternalInstance) => {
-      child && state.children.push(child);
+      child && children.value.push(child);
     };
 
     const removeRelation = (child: ComponentInternalInstance) => {
-      state.children = state.children.filter((item) => item !== child);
+      children.value = children.value.filter((item) => item !== child);
     };
 
     const onClickItem = (cur: TdSideBarProps['value'], label: string) => {
@@ -47,7 +45,7 @@ export default defineComponent({
 
     provide('sideBarProvide', {
       ...props,
-      state,
+      children,
       currentValue,
       relation,
       removeRelation,
diff --git a/src/steps/steps.vue b/src/steps/steps.vue
index ef44c62a..63eae6d1 100644
--- a/src/steps/steps.vue
+++ b/src/steps/steps.vue
@@ -32,8 +32,12 @@ export default defineComponent({
       'change',
     );
 
-    const state = reactive({
-      children: [] as ComponentInternalInstance[],
+    interface TState {
+      children: ComponentInternalInstance[];
+    }
+
+    const state: TState = reactive({
+      children: [],
     });
 
     const relation = (child: ComponentInternalInstance) => {


Test patch:
diff --git a/src/indexes/__test__/index.test.jsx b/src/indexes/__test__/index.test.jsx
index caa6f869..4d9674d9 100644
--- a/src/indexes/__test__/index.test.jsx
+++ b/src/indexes/__test__/index.test.jsx
@@ -10,6 +10,12 @@ const { prefix } = config;
 const indexesClass = `${prefix}-indexes`;
 const indexesAnchorClass = `${prefix}-indexes-anchor`;
 
+const move = async (target, distance) => {
+  await trigger(target, 'touchstart', 0, 0);
+  await trigger(target, 'touchmove', 0, distance);
+  await trigger(target, 'touchend', 0, distance);
+};
+
 const children = new Array(5).fill('列表内容');
 const list = [
   {
@@ -70,6 +76,26 @@ describe('Indexes', () => {
       expect(wrapper.findAllComponents(TCell).length).toBe(
         list.reduce((count, item) => count + item.children.length, 0),
       );
+
+      const wrapper1 = mount({
+        setup() {
+          return () => (
+            <Indexes>
+              {{
+                default: list.map((item, index) => (
+                  <template>
+                    <IndexesAnchor index={item.index} key={index}></IndexesAnchor>
+                    {item.children.map((val) => (
+                      <TCell className="indexes-cell">{val}</TCell>
+                    ))}
+                  </template>
+                )),
+              }}
+            </Indexes>
+          );
+        },
+      });
+      expect(wrapper1.findAll(`.${componentName}__sidebar-item`).length).toBe(26);
     });
   });
   describe('event', () => {
@@ -220,3 +246,39 @@ describe('Indexes & IndexesAnchor', () => {
     expect(wrapper.element).toMatchSnapshot();
   });
 });
+
+describe('event', () => {
+  it('@touch', async () => {
+    const onSelect = vi.fn();
+    const onChange = vi.fn();
+    const wrapper = mount(Indexes, {
+      props: {
+        indexList,
+        onSelect,
+        onChange,
+      },
+    });
+
+    const sidebar = wrapper.find(`.${componentName}__sidebar`);
+
+    const $sideBarItems = wrapper.findAll(`.${componentName}__sidebar-item`);
+    // 模拟点击第一项 item, 此时第一项为激活态，两个事件均被触发
+    await $sideBarItems[0].trigger('click');
+    expect(onChange).toBeCalledTimes(1);
+    expect(onSelect).toBeCalledTimes(1);
+    expect($sideBarItems[0].classes()).toContain(`${componentName}__sidebar-item--active`);
+
+    // mock document.elementFromPoint
+    document.elementFromPoint = function () {
+      return sidebar.findAll(`.${componentName}__sidebar-item`)[2].element;
+    };
+
+    // 每一项的高度是20，这里以第0项为起始点，模拟滑动 40，此时第3项转为激活态，只会触发 change 事件
+    await move($sideBarItems[0], 40);
+    expect(wrapper.findAll(`.${componentName}__sidebar-item`)[2].classes()).toContain(
+      `${componentName}__sidebar-item--active`,
+    );
+    expect(onSelect).toBeCalledTimes(1);
+    expect(onChange).toBeCalledTimes(2);
+  });
+});