index.vue 5.45 KB
<template>
  <view class="min-h-screen bg-gray-50">
    <!-- Header -->
    <!-- <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
      <view class="flex items-center" @click="goBack">
        <Left size="20" class="text-gray-600" />
        <text class="ml-2 text-lg font-medium">家庭相册</text>
      </view>
    </view> -->

    <!-- Album Grid -->
    <view class="p-4">
      <view class="grid grid-cols-2 gap-3">
        <view
          v-for="(item, index) in albumList"
          :key="index"
          class="relative aspect-square rounded-lg overflow-hidden bg-white shadow-sm"
          @click="handleItemClick(item, index)"
        >
          <!-- 图片封面 -->
          <image
            :src="item.type === 'video' ? item.thumbnail : item.url"
            class="w-full h-full object-cover"
            mode="aspectFill"
          />

          <!-- 视频播放标识 -->
          <view
            v-if="item.type === 'video'"
            class="absolute top-2 left-2 px-2 py-1 bg-black bg-opacity-70 rounded text-white text-xs"
          >
            视频
          </view>

          <!-- 视频时长 -->
          <!-- <view
            v-if="item.type === 'video'"
            class="absolute bottom-2 right-2 bg-black bg-opacity-60 text-white text-xs px-2 py-1 rounded"
          >
            {{ formatDuration(item.duration) }}
          </view> -->
        </view>
      </view>

      <!-- 空状态 -->
      <view v-if="albumList.length === 0" class="flex flex-col items-center justify-center py-20">
        <Photograph size="48" class="text-gray-300 mb-4" />
        <text class="text-gray-400">暂无相册内容</text>
      </view>
    </view>

    <!-- 图片预览 -->
    <nut-image-preview
      v-model:show="previewVisible"
      :images="previewImages"
      :init-no="previewIndex"
      :show-index="false"
      @close="closePreview"
    />

    <!-- 视频播放器 -->
    <view
      v-if="videoVisible"
      class="fixed inset-0 bg-black"
      style="z-index: 9999;"
      @click="closeVideo"
    >
      <!-- 关闭按钮 -->
      <view
        @click.stop="closeVideo"
        class="absolute top-4 right-4 w-10 h-10 bg-black bg-opacity-50 rounded-full flex items-center justify-center"
        style="z-index: 10000;"
      >
        <Close size="24" class="text-white" />
      </view>

      <!-- 视频播放器 -->
      <video
        v-if="currentVideo"
        :id="'album-video-' + videoId"
        :src="currentVideo.url"
        :poster="currentVideo.thumbnail"
        :controls="true"
        :autoplay="false"
        :show-center-play-btn="true"
        :show-play-btn="true"
        :object-fit="'contain'"
        :show-fullscreen-btn="true"
        style="width: 100vw; height: 50vh; position: absolute; top: 20vh; left: 0;"
        @click.stop
        @play="handleVideoPlay"
        @pause="handleVideoPause"
        @error="handleVideoError"
        @fullscreenchange="handleFullscreenChange"
      />
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import Taro from '@tarojs/taro';
import { Left, Service, Photograph, Close } from '@nutui/icons-vue-taro';
import { useMediaPreview } from '@/composables/useMediaPreview';

// 响应式数据
const albumList = ref([]);

// 使用媒体预览 composable
const {
  previewVisible,
  previewImages,
  previewIndex,
  videoVisible,
  currentVideo,
  videoId,
  handleMediaClick,
  closePreview,
  closeVideo,
  handleVideoPlay,
  handleVideoPause,
  handleFullscreenChange,
  handleVideoError,
} = useMediaPreview();

/**
 * 页面加载时设置标题和初始化数据
 */
onMounted(() => {
  Taro.setNavigationBarTitle({ title: '家庭相册' });
  initMockData();
});

/**
 * 初始化模拟数据
 */
const initMockData = () => {
  albumList.value = [
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/cat.jpeg',
      createTime: '2024-01-15 10:30'
    },
    {
      type: 'video',
      url: 'https://vjs.zencdn.net/v/oceans.mp4',
      thumbnail: 'https://img.yzcdn.cn/vant/apple-1.jpg',
      // duration: 125, // 秒
      createTime: '2024-01-14 16:20'
    },
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/apple-2.jpg',
      createTime: '2024-01-13 14:15'
    },
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/apple-3.jpg',
      createTime: '2024-01-12 09:45'
    },
    {
      type: 'video',
      url: 'https://vjs.zencdn.net/v/oceans.mp4',
      thumbnail: 'https://img.yzcdn.cn/vant/apple-4.jpg',
      createTime: '2024-01-11 18:30'
    },
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/tree.jpg',
      createTime: '2024-01-10 12:00'
    },
    {
      type: 'video',
      url: 'https://vjs.zencdn.net/v/oceans.mp4',
      thumbnail: 'https://img.yzcdn.cn/vant/leaf.jpg',
      createTime: '2024-01-09 15:20'
    },
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/sand.jpg',
      createTime: '2024-01-08 11:10'
    },
    {
      type: 'image',
      url: 'https://img.yzcdn.cn/vant/sand.jpg',
      createTime: '2024-01-08 11:10'
    },
  ];
};

/**
 * 处理项目点击事件
 * @param {Object} item - 相册项目
 * @param {number} index - 项目索引
 */
const handleItemClick = (item, index) => {
  handleMediaClick(item, albumList.value);
};
</script>

<style scoped>
.grid {
  display: grid;
}

.grid-cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.gap-3 {
  gap: 0.75rem;
}

.aspect-square {
  aspect-ratio: 1 / 1;
}
</style>