IndexCheckInPage.vue 11.7 KB
<!--
 * @Date: 2025-05-29 15:34:17
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2025-05-30 15:11:36
 * @FilePath: /mlaj/src/views/checkin/IndexCheckInPage.vue
 * @Description: 文件描述
-->
<template>
  <AppLayout :hasTitle="false">
    <van-config-provider :theme-vars="themeVars">
      <van-calendar title="每日打卡" :poppable="false" :show-confirm="false" :style="{ height: '24rem' }"
        switch-mode="year-month" color="#4caf50" :formatter="formatter" row-height="42" :show-mark="false"
        @click-subtitle="onClickSubtitle">
      </van-calendar>

      <div class="text-wrapper">
        <div class="text-header">目标进度</div>
        <div class="grade-percentage-main">
          <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;">
            <van-col span="12">
              <span>年级目标</span>
            </van-col>
            <van-col span="12" style="text-align: right;">
              <span style="font-weight: bold;">{{ progress1 }}%</span>
            </van-col>
          </van-row>
          <van-progress :percentage="progress1" color="#4caf50" :show-pivot="false" />
        </div>
        <div class="class-percentage-main">
          <van-row justify="space-between" style="margin: 0.5rem 0; font-size: 0.9rem;">
            <van-col span="12">
              <span>班级目标</span>
            </van-col>
            <van-col span="12" style="text-align: right;">
              <span style="font-weight: bold;">{{ progress2 }}%</span>
            </van-col>
          </van-row>
          <van-progress :percentage="progress2" color="#4caf50" :show-pivot="false" />
        </div>
        <div style="padding: 0.75rem 1rem;">
          <van-image round width="2.8rem" height="2.8rem" src="https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg"
            v-for="(item, index) in teamAvatars" :key="index"
            :style="{ marginLeft: index > 0 ? '-0.5rem' : '', border: '2px solid #FFF' }" />
        </div>
      </div>

      <div class="text-wrapper">
        <div class="text-header">上传附件</div>
        <div style="display: flex; margin: 1rem 0; gap: 1rem;">
          <div style="text-align: center; border: 1px solid #a2d8a3; border-radius: 5px; padding: 1rem 0; flex: 1;">
            <div><van-icon name="photo" size="2.5rem" /></div>
            <div style="font-size: 0.85rem;">图文上传</div>
          </div>
          <div style="text-align: center; border: 1px solid #a2d8a3; border-radius: 5px; padding: 1rem 0; flex: 1;">
            <div><van-icon name="video" size="2.5rem" /></div>
            <div style="font-size: 0.85rem;">视频/语音</div>
          </div>
        </div>
      </div>

      <div class="text-wrapper">
        <div class="text-header">团队动态</div>
        <div class="post-card" v-for="post in mockPosts" :key="post.id">
          <div class="post-header">
            <van-row>
              <van-col span="3">
                <van-image round width="2.5rem" height="2.5rem" :src="post.user.avatar" />
              </van-col>
              <van-col span="20">
                <div class="user-info">
                  <div class="username">{{ post.user.name }}</div>
                  <div class="post-time">{{ post.user.time }}</div>
                </div>
              </van-col>
            </van-row>
          </div>
          <div class="post-content">
            <div class="post-text">{{ post.content }}</div>
            <div class="post-media">
              <div v-if="post.images.length" class="post-images">
                <van-image width="100" height="100" v-for="(image, index) in post.images" :key="index" :src="image"
                  @click="openImagePreview(index, post)" />
              </div>
              <van-image-preview v-if="currentPost" v-model:show="showImagePreview" :images="currentPost.images"
                :start-position="startPosition" :show-index="true" @change="onChange" />
              <!-- 视频封面和播放按钮 -->
              <div v-if="post.video && !post.isPlaying" class="relative w-full rounded-lg overflow-hidden"
                style="aspect-ratio: 16/9;">
                <img :src="post.videoCover || 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'"
                  :alt="post.content" class="w-full h-full object-cover" />
                <div class="absolute inset-0 flex items-center justify-center cursor-pointer bg-black/20"
                  @click="startPlay(post)">
                  <div
                    class="w-16 h-16 rounded-full bg-black/50 flex items-center justify-center hover:bg-black/70 transition-colors">
                    <van-icon name="play-circle-o" class="text-white" size="30" />
                  </div>
                </div>
              </div>
              <!-- 视频播放器 -->
              <VideoPlayer v-if="post.video && post.isPlaying" :video-url="post.video"
                class="post-video rounded-lg overflow-hidden" ref="(el) => { if(el) videoPlayers.value.push(el) }"
                @onPlay="(player) => handleVideoPlay(player, post)" @onPause="() => handleVideoPause(post)" />
              <AudioPlayer v-if="post.audio.length" :songs="post.audio" class="post-audio" />
            </div>
          </div>
          <div class="post-footer">
            <van-icon name="like" class="like-icon" />
            <span class="like-count">{{ post.likes }}</span>
          </div>
        </div>
      </div>

      <div style="height: 5rem;"></div>
    </van-config-provider>
  </AppLayout>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLayout from "@/components/layout/AppLayout.vue";
import FrostedGlass from "@/components/ui/FrostedGlass.vue";
import VideoPlayer from "@/components/ui/VideoPlayer.vue";
import AudioPlayer from "@/components/ui/AudioPlayer.vue";

// 存储所有视频播放器的引用
const videoPlayers = ref([]);

/**
 * 开始播放指定帖子的视频
 * @param {Object} post - 要播放视频的帖子对象
 */
const startPlay = (post) => {
  // 先暂停所有其他视频
  mockPosts.value.forEach(p => {
    if (p.id !== post.id) {
      p.isPlaying = false;
    }
  });

  // 设置当前视频为播放状态
  post.isPlaying = true;
};

/**
 * 处理视频播放事件
 * @param {Object} player - 视频播放器实例
 * @param {Object} post - 包含视频的帖子对象
 */
const handleVideoPlay = (player, post) => {
  // 停止其他视频播放
  stopOtherVideos(player, post);
};

/**
 * 处理视频暂停事件
 * @param {Object} post - 包含视频的帖子对象
 */
const handleVideoPause = (post) => {
  // 视频暂停时不改变isPlaying状态,保持播放器可见
  // 这样用户可以继续从暂停处播放
};

/**
 * 停止除当前播放器外的所有其他视频
 * @param {Object} currentPlayer - 当前播放的视频播放器实例
 * @param {Object} currentPost - 当前播放的帖子对象
 */
const stopOtherVideos = (currentPlayer, currentPost) => {
  // 暂停其他视频播放器
  videoPlayers.value.forEach(player => {
    if (player !== currentPlayer && player.pause) {
      player.pause();
    }
  });

  // 更新其他帖子的播放状态
  mockPosts.value.forEach(post => {
    if (post.id !== currentPost.id) {
      post.isPlaying = false;
    }
  });
};

// Mock数据
const mockPosts = ref([
  {
    id: 1,
    user: {
      name: '小林',
      avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
      time: '2小时前'
    },
    content: '今天完成了React基础课程的学习,收获满满!',
    images: [
      'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
      'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
      'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
      'https://cdn.ipadbiz.cn/space/816560/大国少年_FhnF8lsFMPnTDNTBlM6hYa-UFBlW.jpg',
    ],
    video: '',
    videoCover: '',
    isPlaying: false,
    audio: [],
    likes: 12
  },
  {
    id: 2,
    user: {
      name: '小林',
      avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
      time: '2小时前'
    },
    content: '今天完成了React基础课程的学习,收获满满!',
    images: [],
    video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4',
    videoCover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
    isPlaying: false,
    audio: [],
    likes: 12
  },
  {
    id: 3,
    user: {
      name: '小林',
      avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
      time: '2小时前'
    },
    content: '今天完成了React基础课程的学习,收获满满!',
    images: [],
    video: '',
    videoCover: '',
    isPlaying: false,
    audio: [
      {
        title: '学习心得分享',
        artist: '小林',
        url: 'https://example.com/audio.mp3',
        cover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'
      }
    ],
    likes: 12
  },
  {
    id: 4,
    user: {
      name: '小林',
      avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
      time: '2小时前'
    },
    content: '今天完成了React基础课程的学习,收获满满!',
    images: [],
    video: 'https://cdn.ipadbiz.cn/space/lk3DmvLO02dUC2zPiFwiClDe3nKL.mp4',
    videoCover: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
    isPlaying: false,
    audio: [],
    likes: 12
  },
]);

const themeVars = {
  calendarSelectedDayBackground: '#4caf50'
}

const progress1 = ref(50);
const progress2 = ref(76);

const teamAvatars = ref([
  'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
  'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
  'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
  'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg'
])

// 图片预览相关
const showImagePreview = ref(false);
const startPosition = ref(0);
const currentPost = ref(null);

// 打开图片预览
const openImagePreview = (index, post) => {
  currentPost.value = post;
  startPosition.value = index;
  showImagePreview.value = true;
}

// 图片切换事件处理
const onChange = (index) => {
  startPosition.value = index;
}
const formatter = (day) => {
  const month = day.date.getMonth() + 1;
  const date = day.date.getDate();

  let checkin_days = [1, 3, 5, 7];

  if (month === 5) {
    if (checkin_days.includes(date)) {
      day.className = 'calendar-checkin';
      day.type = 'selected';
    }
  }

  return day;
}

const onClickSubtitle = (evt) => {
  console.warn('点击了日期标题');
}
</script>

<style lang="less">
.calendar-checkin {
  .van-calendar__selected-day {
    background: #a2d8a3 !important;
  }
}

.text-wrapper {
  padding: 1rem;
  color: #4caf50;

  .text-header {
    font-size: 1.15rem;
  }

  .grade-percentage-main {
    padding: 0.75rem 1rem;
  }

  .class-percentage-main {
    padding: 0.75rem 1rem;
  }
}

.post-card {
  margin: 1rem 0;
  padding: 1rem;
  background-color: #FFF;
  border-radius: 5px;

  .post-header {
    margin-bottom: 1rem;
  }

  .user-info {
    margin-left: 0.5rem;

    .username {
      font-weight: 500;
    }

    .post-time {
      color: gray;
      font-size: 0.8rem;
    }
  }

  .post-content {
    .post-text {
      margin-bottom: 1rem;
    }

    .post-media {
      .post-images {
        display: flex;
        flex-wrap: wrap;
        gap: 0.5rem;
      }

      .post-video {
        margin: 1rem 0;
        width: 100%;
        border-radius: 8px;
        overflow: hidden;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      }

      .post-audio {
        margin: 1rem 0;
      }
    }
  }

  .post-footer {
    margin-top: 1rem;
    color: #666;

    .like-icon {
      margin-right: 0.25rem;
    }

    .like-count {
      font-size: 0.9rem;
    }
  }
}
</style>