vue 实现滚动tab 支持手势滑动

45 阅读2分钟
<template>
  <view class="container">
    <!-- 顶部固定导航栏 -->
    <view class="nav-bar">
      <view class="nav-content">
        <view v-for="(item, index) in navItems" :key="index" class="nav-item"
          :class="{ 'nav-item-active': currentIndex === index }" @click="changeNav(index)">
          {{ item }}
        </view>
      </view>
    </view>
    <!-- 下方可滑动内容区域 -->
    <swiper class="content-swiper" :current="currentIndex" @change="handleSwiperChange"
      :style="{ height: contentHeight + 'px' }">
      <swiper-item v-for="(contentList, contentIndex) in contentLists" :key="contentIndex">
        <scroll-view class="content-scroll" scroll-y="true">
          <view v-for="(item, index) in contentList" :key="index" class="content-item">
            <view class="item-header">
              <text class="item-title">{{ item.title }}</text>
              <text class="item-status" :class="item.status">{{ item.statusText }}</text>
            </view>
            <view class="item-content">
              <text class="item-desc">{{ item.description }}</text>
              <view class="item-info">
                <text class="item-time">{{ item.time }}</text>
                <text class="item-location">{{ item.location }}</text>
              </view>
            </view>
          </view>
        </scroll-view>
      </swiper-item>
    </swiper>
  </view>
</template>

<script>
export default {
  data() {
    return {
      navItems: ['进行中', '已结束', '待定'],
      currentIndex: 0,
      contentLists: [
        // 进行中的数据
        Array.from({ length: 20 }, (_, i) => ({
          title: `进行中的任务 ${i + 1}`,
          description: '这是一个进行中的任务描述,包含详细的任务信息和要求。',
          status: 'ongoing',
          statusText: '进行中',
          time: '2024-03-20 14:30',
          location: '北京市朝阳区'
        })),
        // 已结束的数据
        Array.from({ length: 10 }, (_, i) => ({
          title: `已结束的任务 ${i + 1}`,
          description: '这是一个已完成的任务描述,包含任务完成情况和总结。',
          status: 'completed',
          statusText: '已结束',
          time: '2024-03-19 16:00',
          location: '上海市浦东新区'
        })),
        // 待定的数据
        Array.from({ length: 5 }, (_, i) => ({
          title: `待定任务 ${i + 1}`,
          description: '这是一个待定的任务描述,等待进一步确认和安排。',
          status: 'pending',
          statusText: '待定',
          time: '待定',
          location: '待定'
        }))
      ],
      contentHeight: 0
    };
  },
  mounted() {
    this.calculateContentHeight();
  },
  methods: {
    changeNav(index) {
      this.currentIndex = index;
    },
    handleSwiperChange(e) {
      this.currentIndex = e.detail.current;
    },
    calculateContentHeight() {
      const query = uni.createSelectorQuery();
      query.select('.nav-bar').boundingClientRect();
      query.exec((res) => {
        const navBarHeight = res[0].height;
        const systemInfo = uni.getSystemInfoSync();
        const statusBarHeight = systemInfo.statusBarHeight;
        this.contentHeight = systemInfo.windowHeight - navBarHeight - statusBarHeight;
      });
    }
  }
};
</script>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  background-color: #f5f5f5;
}

.nav-bar {
  background-color: #ffffff;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  border-bottom: 1px solid #eee;
}

.nav-content {
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 44px;
}

.nav-item {
  flex: 1;
  text-align: center;
  font-size: 15px;
  color: #333;
  position: relative;
  padding: 0 10px;
}

.nav-item-active {
  color: #007aff;
  font-weight: 500;
}

.nav-item-active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 20px;
  height: 2px;
  background-color: #007aff;
}

.content-swiper {
  flex: 1;
  width: 100%;
  margin-top: 44px;
}

.content-scroll {
  height: 100%;
  padding: 15px;
  box-sizing: border-box;
}

.content-item {
  padding: 15px;
  background-color: #fff;
  border-radius: 8px;
  margin-bottom: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.item-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.item-title {
  font-size: 16px;
  font-weight: 500;
  color: #333;
}

.item-status {
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 4px;
}

.item-status.ongoing {
  color: #007aff;
  background-color: rgba(0, 122, 255, 0.1);
}

.item-status.completed {
  color: #34c759;
  background-color: rgba(52, 199, 89, 0.1);
}

.item-status.pending {
  color: #ff9500;
  background-color: rgba(255, 149, 0, 0.1);
}

.item-content {
  font-size: 14px;
  color: #666;
}

.item-desc {
  margin-bottom: 8px;
  line-height: 1.5;
}

.item-info {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: #999;
}

.item-time,
.item-location {
  display: flex;
  align-items: center;
}
</style>