refactor: clean up old Tabs component and update Card routing
- remove unused Tabs component docs, test, style and markdown files - refactor Tabs component to use inline utility classes and inline keyframes - update Card component's click handler to use named route "goods"
This commit is contained in:
@@ -1,250 +0,0 @@
|
|||||||
# Tab 切换组件
|
|
||||||
|
|
||||||
一个功能完整的 Tab 切换组件,支持动画过渡、自定义内容和响应式设计。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- ✅ **多标签切换**:支持任意数量的标签页切换
|
|
||||||
- ✅ **动画指示器**:选中状态下划线,支持平滑滑动动画
|
|
||||||
- ✅ **自定义内容**:支持插槽自定义标签内容
|
|
||||||
- ✅ **响应式设计**:适配不同屏幕尺寸
|
|
||||||
- ✅ **动态宽度**:下划线宽度根据文字宽度动态调整
|
|
||||||
- ✅ **事件支持**:完整的切换事件和双向绑定
|
|
||||||
- ✅ **主题定制**:支持自定义指示器颜色
|
|
||||||
- ✅ **uniapp 兼容**:使用 uniapp 内置组件开发
|
|
||||||
|
|
||||||
## 基础用法
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab :tabs="tabList" :defaultActive="0" @change="handleTabChange" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import Tab from "./components/Tab/index.vue";
|
|
||||||
|
|
||||||
const tabList = ref([
|
|
||||||
{ label: "全部订单", value: "all" },
|
|
||||||
{ label: "服务工单", value: "service" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleTabChange = ({ index, item }) => {
|
|
||||||
console.log("切换到:", item.label);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 自定义标签内容
|
|
||||||
|
|
||||||
使用 `tab-item` 插槽可以完全自定义标签的显示内容:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab :tabs="customTabs" @change="handleChange">
|
|
||||||
<template #tab-item="{ item, index, isActive }">
|
|
||||||
<div class="custom-tab">
|
|
||||||
<img v-if="item.icon" :src="item.icon" class="tab-icon" />
|
|
||||||
<text :class="{ 'active-text': isActive }">{{ item.label }}</text>
|
|
||||||
<div v-if="item.badge" class="badge">{{ item.badge }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Tab>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const customTabs = ref([
|
|
||||||
{
|
|
||||||
label: "待处理",
|
|
||||||
value: "pending",
|
|
||||||
icon: "/static/pending.png",
|
|
||||||
badge: "3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "已完成",
|
|
||||||
value: "completed",
|
|
||||||
icon: "/static/completed.png",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 双向绑定
|
|
||||||
|
|
||||||
支持 `v-model` 双向绑定当前选中的索引:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab v-model="activeIndex" :tabs="tabList" />
|
|
||||||
<text>当前选中索引:{{ activeIndex }}</text>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const activeIndex = ref(0);
|
|
||||||
const tabList = ref([
|
|
||||||
{ label: "标签1", value: "tab1" },
|
|
||||||
{ label: "标签2", value: "tab2" },
|
|
||||||
]);
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 动态标签
|
|
||||||
|
|
||||||
支持动态添加和删除标签:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab :tabs="dynamicTabs" @change="handleChange" />
|
|
||||||
<button @click="addTab">添加标签</button>
|
|
||||||
<button @click="removeTab">删除标签</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const dynamicTabs = ref([{ label: "标签1", value: "tab1" }]);
|
|
||||||
|
|
||||||
const addTab = () => {
|
|
||||||
const newIndex = dynamicTabs.value.length + 1;
|
|
||||||
dynamicTabs.value.push({
|
|
||||||
label: `标签${newIndex}`,
|
|
||||||
value: `tab${newIndex}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTab = () => {
|
|
||||||
if (dynamicTabs.value.length > 1) {
|
|
||||||
dynamicTabs.value.pop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Props
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
| -------------- | ------ | --------------------------------------------------------------------- | ---------------------------- |
|
|
||||||
| tabs | Array | `[{label:'全部订单',value:'all'},{label:'服务工单',value:'service'}]` | 标签数据数组 |
|
|
||||||
| defaultActive | Number | `0` | 默认选中的标签索引 |
|
|
||||||
| indicatorColor | String | `#007AFF` | 指示器颜色 |
|
|
||||||
| modelValue | Number | - | 当前选中索引(用于 v-model) |
|
|
||||||
|
|
||||||
### tabs 数组项结构
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface TabItem {
|
|
||||||
label: string; // 标签显示文本
|
|
||||||
value: string; // 标签值
|
|
||||||
[key: string]: any; // 其他自定义属性
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Events
|
|
||||||
|
|
||||||
| 事件名 | 说明 | 参数 |
|
|
||||||
| ----------------- | --------------------- | ---------------------------------- |
|
|
||||||
| change | 标签切换时触发 | `{ index: number, item: TabItem }` |
|
|
||||||
| update:modelValue | 用于 v-model 双向绑定 | `index: number` |
|
|
||||||
|
|
||||||
## Slots
|
|
||||||
|
|
||||||
| 插槽名 | 说明 | 作用域参数 |
|
|
||||||
| -------- | -------------- | ----------------------------------------------------- |
|
|
||||||
| tab-item | 自定义标签内容 | `{ item: TabItem, index: number, isActive: boolean }` |
|
|
||||||
|
|
||||||
## 方法
|
|
||||||
|
|
||||||
通过 `ref` 可以调用组件的方法:
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab ref="tabRef" :tabs="tabList" />
|
|
||||||
<button @click="switchToTab(1)">切换到第二个标签</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const tabRef = ref();
|
|
||||||
|
|
||||||
const switchToTab = (index) => {
|
|
||||||
tabRef.value.setActiveIndex(index);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
| 方法名 | 说明 | 参数 | 返回值 |
|
|
||||||
| -------------- | ---------------------- | --------------- | --------- |
|
|
||||||
| setActiveIndex | 设置当前选中的标签 | `index: number` | - |
|
|
||||||
| getActiveIndex | 获取当前选中的标签索引 | - | `number` |
|
|
||||||
| getActiveItem | 获取当前选中的标签项 | - | `TabItem` |
|
|
||||||
|
|
||||||
## 样式定制
|
|
||||||
|
|
||||||
### CSS 变量
|
|
||||||
|
|
||||||
组件支持通过 CSS 变量进行样式定制:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
.tab-container {
|
|
||||||
--tab-bg-color: #fff; // 背景色
|
|
||||||
--tab-text-color: #666; // 文字颜色
|
|
||||||
--tab-active-color: #333; // 选中文字颜色
|
|
||||||
--tab-indicator-color: #007aff; // 指示器颜色
|
|
||||||
--tab-border-color: #f0f0f0; // 边框颜色
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义主题
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<Tab :tabs="tabList" indicatorColor="#ff4d4f" class="custom-tab" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.custom-tab {
|
|
||||||
--tab-indicator-color: #ff4d4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-tab .tab-text-active {
|
|
||||||
color: #ff4d4f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 技术实现
|
|
||||||
|
|
||||||
- **框架**:Vue 3 组合式 API
|
|
||||||
- **平台**:uniapp 跨平台开发
|
|
||||||
- **动画**:CSS3 transition + transform
|
|
||||||
- **响应式**:CSS media queries
|
|
||||||
- **兼容性**:微信小程序、H5、App
|
|
||||||
|
|
||||||
## 设计规范
|
|
||||||
|
|
||||||
- 遵循微信小程序设计规范
|
|
||||||
- 支持无障碍访问
|
|
||||||
- 响应式设计,适配不同设备
|
|
||||||
- 流畅的动画过渡效果
|
|
||||||
- 一致的视觉风格
|
|
||||||
|
|
||||||
## 兼容性
|
|
||||||
|
|
||||||
| 平台 | 支持情况 |
|
|
||||||
| ------------ | ----------- |
|
|
||||||
| 微信小程序 | ✅ 完全支持 |
|
|
||||||
| H5 | ✅ 完全支持 |
|
|
||||||
| App | ✅ 完全支持 |
|
|
||||||
| 支付宝小程序 | ✅ 完全支持 |
|
|
||||||
| 百度小程序 | ✅ 完全支持 |
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0
|
|
||||||
|
|
||||||
- ✨ 初始版本发布
|
|
||||||
- ✨ 支持基础标签切换功能
|
|
||||||
- ✨ 支持动画指示器
|
|
||||||
- ✨ 支持自定义标签内容
|
|
||||||
- ✨ 支持响应式设计
|
|
||||||
- ✨ 支持事件和双向绑定
|
|
||||||
|
|
||||||
## 备注
|
|
||||||
|
|
||||||
仅供学习、交流使用,请勿用于商业用途。
|
|
||||||
@@ -1,21 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tab-container">
|
<div class="relative">
|
||||||
<div class="tab-wrapper">
|
<div class="flex items-center justify-center h-[30px]">
|
||||||
<div
|
<div v-for="(item, index) in tabList" :key="index"
|
||||||
v-for="(item, index) in tabList"
|
:class="['flex-1 flex items-center justify-center h-full relative transition-all duration-300 ease-out px-2', activeIndex === index && 'text-[#333] text-base font-semibold']"
|
||||||
:key="index"
|
@click="handleTabClick(index)">
|
||||||
:class="['tab-item', activeIndex === index && 'tab-item-active']"
|
<slot name="tab-item" :item="item" :index="index" :isActive="activeIndex === index">
|
||||||
@click="handleTabClick(index)"
|
<span :class="['text-[#333] font-semibold', activeIndex === index && 'text-[#333] text-base font-semibold']">
|
||||||
>
|
|
||||||
<slot
|
|
||||||
name="tab-item"
|
|
||||||
:item="item"
|
|
||||||
:index="index"
|
|
||||||
:isActive="activeIndex === index"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
:class="['tab-span', activeIndex === index && 'tab-span-active']"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
@@ -23,17 +13,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下划线指示器 -->
|
<!-- 下划线指示器 -->
|
||||||
<div
|
<div :class="[
|
||||||
:class="[
|
'absolute bottom-0 h-[3px] min-h-[3px] bg-[#007aff] rounded-[10px] [transition:left_0.3s_cubic-bezier(0.4,0,0.2,1),width_0.3s_cubic-bezier(0.4,0,0.2,1)] z-10 [transform:translateZ(0)] [will-change:left,width] opacity-0 w-[15px] left-0',
|
||||||
'tab-indicator',
|
indicatorAnimating && '[animation:tabSwitch_0.3s_ease-out]',
|
||||||
indicatorAnimating && 'animating',
|
indicatorInitialized && 'opacity-100',
|
||||||
indicatorInitialized && 'initialized',
|
]" :style="{
|
||||||
]"
|
|
||||||
:style="{
|
|
||||||
left: indicatorLeft + 'px',
|
left: indicatorLeft + 'px',
|
||||||
width: indicatorWidth + 'px',
|
width: indicatorWidth + 'px',
|
||||||
}"
|
}"></div>
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -271,5 +258,15 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@import "./styles/index.scss";
|
@keyframes tabSwitch {
|
||||||
|
0% {
|
||||||
|
transform: translateZ(0) scaleX(0.8);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateZ(0) scaleX(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
.tab-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
font-weight: 400;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-text-active {
|
|
||||||
color: #333;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item-active {
|
|
||||||
.tab-text {
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-indicator {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
height: 3px;
|
|
||||||
min-height: 3px; /* 确保最小高度 */
|
|
||||||
background-color: #007aff;
|
|
||||||
border-radius: 10px;
|
|
||||||
transition:
|
|
||||||
left 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
|
||||||
width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
z-index: 1;
|
|
||||||
transform: translateZ(0); /* 启用硬件加速 */
|
|
||||||
will-change: left, width; /* 优化动画性能 */
|
|
||||||
|
|
||||||
/* 初始状态:未初始化时隐藏 */
|
|
||||||
opacity: 0;
|
|
||||||
width: 15px; /* 默认宽度15px */
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 已初始化状态 */
|
|
||||||
.tab-indicator.initialized {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 点击效果 */
|
|
||||||
.tab-item:active {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义主题色支持 */
|
|
||||||
.tab-container[data-indicator-color="red"] .tab-indicator {
|
|
||||||
background-color: #ff4d4f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-container[data-indicator-color="green"] .tab-indicator {
|
|
||||||
background-color: #52c41a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-container[data-indicator-color="orange"] .tab-indicator {
|
|
||||||
background-color: #fa8c16;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动画增强 */
|
|
||||||
@keyframes tabSwitch {
|
|
||||||
0% {
|
|
||||||
transform: translateZ(0) scaleX(0.8);
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateZ(0) scaleX(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-indicator.animating {
|
|
||||||
animation: tabSwitch 0.3s ease-out;
|
|
||||||
}
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="test-page">
|
|
||||||
<div class="test-section">
|
|
||||||
<span class="test-title">Tab组件测试</span>
|
|
||||||
|
|
||||||
<!-- 基础测试 -->
|
|
||||||
<div class="test-item">
|
|
||||||
<span class="item-title">基础用法</span>
|
|
||||||
<Tab :tabs="basicTabs" :defaultActive="0" @change="handleTabChange" />
|
|
||||||
<div class="result">
|
|
||||||
<span>当前选中: {{ currentTab.label }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 多标签测试 -->
|
|
||||||
<div class="test-item">
|
|
||||||
<span class="item-title">多标签测试</span>
|
|
||||||
<Tab
|
|
||||||
:tabs="multiTabs"
|
|
||||||
:defaultActive="1"
|
|
||||||
@change="handleMultiTabChange"
|
|
||||||
/>
|
|
||||||
<div class="result">
|
|
||||||
<span>当前选中: {{ currentMultiTab.label }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 快速切换测试 -->
|
|
||||||
<div class="test-item">
|
|
||||||
<span class="item-title">快速切换测试</span>
|
|
||||||
<Tab
|
|
||||||
ref="fastTabRef"
|
|
||||||
:tabs="fastTabs"
|
|
||||||
:defaultActive="0"
|
|
||||||
@change="handleFastTabChange"
|
|
||||||
/>
|
|
||||||
<div class="result">
|
|
||||||
<span>当前选中: {{ currentFastTab.label }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="test-buttons">
|
|
||||||
<button
|
|
||||||
v-for="(tab, index) in fastTabs"
|
|
||||||
:key="index"
|
|
||||||
class="test-btn"
|
|
||||||
@click="switchToTab(index)"
|
|
||||||
>
|
|
||||||
切换到{{ tab.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 初始化测试 -->
|
|
||||||
<div class="test-item">
|
|
||||||
<span class="item-title">初始化测试</span>
|
|
||||||
<span class="test-desc"
|
|
||||||
>测试指示器的动态高度和宽度初始化及错误处理</span
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
v-if="showInitTest"
|
|
||||||
:tabs="initTestTabs"
|
|
||||||
:defaultActive="initActiveIndex"
|
|
||||||
@change="handleInitTestChange"
|
|
||||||
/>
|
|
||||||
<div class="result">
|
|
||||||
<span>当前选中: {{ currentInitTab.label }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="test-buttons">
|
|
||||||
<button class="test-btn" @click="toggleInitTest">
|
|
||||||
{{ showInitTest ? "隐藏" : "显示" }}组件
|
|
||||||
</button>
|
|
||||||
<button class="test-btn" @click="changeInitActive">
|
|
||||||
切换默认激活项 (当前: {{ initActiveIndex }})
|
|
||||||
</button>
|
|
||||||
<button class="test-btn" @click="addInitTab">添加Tab</button>
|
|
||||||
<button class="test-btn" @click="removeInitTab">移除Tab</button>
|
|
||||||
<button class="test-btn" @click="rapidToggle">快速切换测试</button>
|
|
||||||
</div>
|
|
||||||
<div class="test-info">
|
|
||||||
<span>错误处理测试:组件现在能够安全处理实例为null的情况</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from "vue";
|
|
||||||
import Tab from "./index.vue";
|
|
||||||
|
|
||||||
// 基础标签
|
|
||||||
const basicTabs = ref([
|
|
||||||
{ label: "全部订单", value: "all" },
|
|
||||||
{ label: "服务工单", value: "service" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 多标签
|
|
||||||
const multiTabs = ref([
|
|
||||||
{ label: "全部", value: "all" },
|
|
||||||
{ label: "待支付", value: "unpaid" },
|
|
||||||
{ label: "待确认", value: "unconfirmed" },
|
|
||||||
{ label: "进行中", value: "processing" },
|
|
||||||
{ label: "已完成", value: "completed" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 快速切换测试
|
|
||||||
const fastTabs = ref([
|
|
||||||
{ label: "标签A", value: "a" },
|
|
||||||
{ label: "标签B", value: "b" },
|
|
||||||
{ label: "标签C", value: "c" },
|
|
||||||
{ label: "标签D", value: "d" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 初始化测试
|
|
||||||
const initTestTabs = ref([
|
|
||||||
{ label: "初始1", value: "init1" },
|
|
||||||
{ label: "初始2", value: "init2" },
|
|
||||||
{ label: "初始3", value: "init3" },
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 当前选中索引
|
|
||||||
const currentTabIndex = ref(0);
|
|
||||||
const currentMultiTabIndex = ref(1);
|
|
||||||
const currentFastTabIndex = ref(0);
|
|
||||||
const currentInitTabIndex = ref(1);
|
|
||||||
|
|
||||||
// 初始化测试状态
|
|
||||||
const showInitTest = ref(true);
|
|
||||||
const initActiveIndex = ref(1);
|
|
||||||
|
|
||||||
// 计算当前选中项
|
|
||||||
const currentTab = computed(() => basicTabs.value[currentTabIndex.value] || {});
|
|
||||||
const currentMultiTab = computed(
|
|
||||||
() => multiTabs.value[currentMultiTabIndex.value] || {},
|
|
||||||
);
|
|
||||||
const currentFastTab = computed(
|
|
||||||
() => fastTabs.value[currentFastTabIndex.value] || {},
|
|
||||||
);
|
|
||||||
const currentInitTab = computed(
|
|
||||||
() => initTestTabs.value[currentInitTabIndex.value] || {},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Tab引用
|
|
||||||
const fastTabRef = ref(null);
|
|
||||||
|
|
||||||
// 事件处理
|
|
||||||
const handleTabChange = ({ index, item }) => {
|
|
||||||
currentTabIndex.value = index;
|
|
||||||
console.log("基础Tab切换:", item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMultiTabChange = ({ index, item }) => {
|
|
||||||
currentMultiTabIndex.value = index;
|
|
||||||
console.log("多标签Tab切换:", item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFastTabChange = ({ index, item }) => {
|
|
||||||
currentFastTabIndex.value = index;
|
|
||||||
console.log("快速Tab切换:", item);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInitTestChange = ({ index, item }) => {
|
|
||||||
currentInitTabIndex.value = index;
|
|
||||||
console.log("初始化Tab切换:", item);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 程序化切换
|
|
||||||
const switchToTab = (index) => {
|
|
||||||
currentFastTabIndex.value = index;
|
|
||||||
if (fastTabRef.value) {
|
|
||||||
fastTabRef.value.setActiveIndex(index);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化测试方法
|
|
||||||
const toggleInitTest = () => {
|
|
||||||
showInitTest.value = !showInitTest.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeInitActive = () => {
|
|
||||||
initActiveIndex.value =
|
|
||||||
(initActiveIndex.value + 1) % initTestTabs.value.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addInitTab = () => {
|
|
||||||
const newIndex = initTestTabs.value.length + 1;
|
|
||||||
initTestTabs.value.push({
|
|
||||||
label: `初始${newIndex}`,
|
|
||||||
value: `init${newIndex}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeInitTab = () => {
|
|
||||||
if (initTestTabs.value.length > 1) {
|
|
||||||
initTestTabs.value.pop();
|
|
||||||
if (initActiveIndex.value >= initTestTabs.value.length) {
|
|
||||||
initActiveIndex.value = initTestTabs.value.length - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const rapidToggle = () => {
|
|
||||||
// 快速切换测试,模拟可能导致实例为null的场景
|
|
||||||
let count = 0;
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
showInitTest.value = !showInitTest.value;
|
|
||||||
count++;
|
|
||||||
if (count >= 6) {
|
|
||||||
clearInterval(interval);
|
|
||||||
showInitTest.value = true;
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.test-page {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-section {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-item {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-info {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f0f9ff;
|
|
||||||
border-radius: 8px;
|
|
||||||
border-left: 4px solid #007aff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-info span {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result span {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-buttons {
|
|
||||||
margin-top: 15px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-btn {
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #007aff;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.test-btn:active {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -67,6 +67,7 @@ const navigateToPage = (commodityId, path) => {
|
|||||||
router.push({ path, query: { commodityId } })
|
router.push({ path, query: { commodityId } })
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = ({ commodityId }) =>
|
const handleClick = ({ commodityId }) => {
|
||||||
navigateToPage(commodityId, "/pages/goods/index");
|
router.push({ name: "goods", query: { commodityId } })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user