Files
lucky_shop/components-diy/diy-channel-list.vue

575 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view :style="[componentStyle, { '--row-count': value.rowCount }]">
<!-- 轮播模式 -->
<swiper v-if="value.carousel && value.carousel.type != 'hide'" :indicator-dots="isIndicatorDots"
:autoplay="value.carousel.autoplay || false" :interval="value.carousel.interval || 3000"
:duration="value.carousel.duration || 500" :circular="value.carousel.circular || false"
:style="swiperHeight" class="channel-swiper">
<swiper-item v-for="(slide, slideIndex) in swiperSlides" :key="slideIndex">
<view :class="['swiper-slide-content', 'row1-of' + value.rowCount]">
<view v-for="(item, index) in slide" :key="index" :class="['channel-item', value.mode]">
<!-- 视频号视频卡片轮播模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:video-height="value.rowCount === 3 ? 180 : 240" :title-line-clamp="value.titleLineClamp"
:show-play-btn="value.showPlayBtn" :cover-style="value.coverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
</swiper-item>
</swiper>
<!-- 固定布局模式 -->
<view v-else-if="value.showStyle == 'fixed'" :class="['channel-list', 'row1-of' + value.rowCount]">
<view v-for="(item, index) in value.list" :key="index" class="channel-item">
<!-- 视频号视频卡片列表模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:video-height="value.rowCount === 3 ? 180 : 240" :title-line-clamp="value.titleLineClamp"
:show-play-btn="value.showPlayBtn" :cover-style="value.coverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
<!-- 其他布局模式如滚动布局 -->
<scroll-view v-else
:class="['channel-nav', value.showStyle == 'fixed' ? 'fixed-layout' : value.showStyle, 'row1-of' + value.rowCount]"
:scroll-x="value.showStyle == 'singleSlide'">
<view class="uni-scroll-view-content">
<view v-for="(item, index) in value.list" :key="index" :class="['channel-nav-item', value.mode]">
<!-- 视频号视频卡片滚动模式 -->
<diy-channel-video :value="item" @video-play="playVideo" :list-mode="true"
:video-height="value.rowCount === 3 ? 180 : 240" :title-line-clamp="value.titleLineClamp"
:show-play-btn="value.showPlayBtn" :cover-style="value.coverStyle"
:play-btn-style="value.playBtnStyle" :aspect-ratio="value.aspectRatio" />
</view>
</view>
</scroll-view>
</view>
</template>
<script>
/**
* 微信视频号列表组件
* 支持多种布局模式,包括固定布局和滚动布局
* 可配置列数、视频高度、标题行数等属性
* 用于在页面中展示多个微信视频号视频
*/
import DiyMinx from './minx.js'
import { wechatChannelUtil, wechatChannelConfig } from './js/wechat-channel.js'
export default {
name: 'diy-channel-list',
props: {
/**
* 组件配置数据
* @type {Object}
* @default () => ({})
* @property {string} showStyle - 显示样式可选值fixed, singleSlide
* @property {number} rowCount - 每行显示的视频数量
* @property {Array} list - 视频列表数据
* @property {string} componentBgColor - 组件背景色
* @property {string} componentAngle - 组件圆角类型可选值round
* @property {number} topAroundRadius - 顶部圆角半径
* @property {number} bottomAroundRadius - 底部圆角半径
* @property {Object} ornament - 装饰效果配置
* @property {number} titleLineClamp - 标题显示行数
* @property {string} aspectRatio - 视频比例可选值16:9, 3:4
* @property {boolean} showPlayBtn - 是否显示播放按钮
* @property {Object} coverStyle - 视频封面图样式
* @property {Object} playBtnStyle - 播放按钮样式
* @property {string} mode - 显示模式
* @property {number} imageSize - 图片尺寸(仅在特定模式下使用)
* @property {number} pageCount - 每页显示的视频数量
* @property {Object} carousel - 轮播配置
* @property {string} carousel.type - 轮播类型可选值default, hide
* @property {boolean} carousel.autoplay - 是否自动播放默认false
* @property {number} carousel.interval - 自动播放间隔默认3000ms
* @property {number} carousel.duration - 切换动画时长默认500ms
* @property {boolean} carousel.circular - 是否循环播放默认false
*/
value: {
type: Object,
default: () => ({})
}
},
mixins: [DiyMinx],
data() {
return {
pageWidth: '', // 页面宽度
indicatorDots: false, // 是否显示轮播指示器
swiperCurrent: 0 // 当前轮播索引
}
},
created() {
// 组件创建时的逻辑
// 可以在这里进行初始化操作,如获取页面宽度等
},
watch: {
/**
* 组件刷新监听
* 当组件需要刷新时触发
* @param {*} newValue - 新值
*/
componentRefresh(newValue) {
// 监听组件刷新
// 可以在这里处理组件刷新时的逻辑
}
},
computed: {
/**
* 组件样式
* 根据配置动态生成样式字符串,包括背景色、圆角、阴影和边框
* @returns {string} 样式字符串
*/
componentStyle() {
let style = '';
// 背景色
if (this.value?.componentBgColor) {
style += 'background-color:' + this.value?.componentBgColor + ';';
}
// 圆角样式
if (this.value?.componentAngle == 'round') {
style += 'border-top-left-radius:' + (2 * this.value?.topAroundRadius) + 'rpx;';
style += 'border-top-right-radius:' + (2 * this.value?.topAroundRadius) + 'rpx;';
style += 'border-bottom-left-radius:' + (2 * this.value?.bottomAroundRadius) + 'rpx;';
style += 'border-bottom-right-radius:' + (2 * this.value?.bottomAroundRadius) + 'rpx;';
}
// 装饰效果:阴影
style += 'box-shadow:' + (this.value?.ornament?.type == 'shadow' ? '0 0 10rpx ' + this.value?.ornament?.color : '') + ';';
// 装饰效果:边框
style += 'border:' + (this.value?.ornament?.type == 'stroke' ? '2rpx solid ' + this.value?.ornament?.color : '') + ';';
return style;
},
/**
* 轮播高度
* 根据模式和配置计算轮播高度
* @returns {string} 轮播高度样式
*/
swiperHeight() {
let height = 0;
// 根据不同模式计算高度
if (this.value?.mode == 'graphic') {
height = (49 + this.value?.imageSize) * this.value?.pageCount;
} else if (this.value?.mode == 'img') {
height = (22 + this.value?.imageSize) * this.value?.pageCount;
} else if (this.value?.mode == 'text') {
height = 43 * this.value?.pageCount;
}
return 'height:' + (2 * height) + 'rpx';
},
/**
* 是否显示指示器
* 根据轮播配置和列表长度判断是否显示指示器
* @returns {boolean} 是否显示指示器
*/
isIndicatorDots() {
// 当轮播类型不是隐藏,且视频数量超过一页时显示指示器
return this.value?.carousel?.type != 'hide' &&
1 != Math.ceil(this.value?.list?.length / (this.value?.pageCount * this.value?.rowCount));
},
/**
* 轮播幻灯片数据
* 将视频列表分割成轮播所需的幻灯片数据
* @returns {Array} 轮播幻灯片数据
*/
swiperSlides() {
const slides = [];
const list = this.value?.list || [];
const pageSize = (this.value?.pageCount || 1) * (this.value?.rowCount || 1);
// 将列表数据分割成每页显示的数量
for (let i = 0; i < list.length; i += pageSize) {
slides.push(list.slice(i, i + pageSize));
}
return slides;
}
},
methods: {
/**
* 播放视频
* 触发 video-play 事件,并在微信小程序中调用视频播放接口
* @param {Object} item - 视频数据对象
*/
async playVideo(item) {
await this.__$emitEvent({
eventName: 'video-play',
data: item,
promiseCallback: async (event, handler, awaitedResult) => {
if (!awaitedResult) return;
try {
// 发送视频被点击播放事件
this.$emit('channel-video-click-play', item);
console.log('播放视频:', item);
// #ifdef MP-WEIXIN
// 在微信小程序环境中调用视频播放接口
await wechatChannelUtil.playVideo(item);
// #endif
// #ifndef MP-WEIXIN
if (item?.channelType === 'wechat') {
uni.showToast({
title: '视频号仅支持在微信小程序环境中播放',
icon: 'none',
duration: 2000
});
} else {
// 在非微信小程序环境中,直接触发事件
this.$emit('video-play', item);
}
// #endif
} catch (err) {
console.error('打开视频号失败', err);
}
}
})
},
/**
* 图片加载错误处理
* 当图片加载失败时,设置默认图片
* @param {number} index - 图片索引
*/
imgError(index) {
// 图片加载失败的处理逻辑
console.log('图片加载失败:', index);
// 为失败的图片设置默认图片
const item = this.value.list[index];
if (item) {
// 使用默认图片替代加载失败的图片
// #ifdef MP-WEIXIN
item.coverUrl = wechatChannelConfig.video.defaultCoverUrl;
// #endif
}
}
}
}
</script>
<style lang="scss" scoped>
@import './css/common-channel.scss';
/**
* 列表布局样式
* 支持 2 列和 3 列布局
*/
.channel-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 16rpx;
box-sizing: border-box;
.channel-item {
// 默认 3 列布局
flex: 0 0 calc(33.3333333% - 10rpx);
box-sizing: border-box;
&:nth-child(3n) {
margin-right: 0;
}
}
// 2 列布局
&.row1-of2 {
.channel-item {
flex: 0 0 calc(50% - 8rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(2n) {
margin-right: 0;
}
}
}
// 4 列布局
&.row1-of4 {
.channel-item {
flex: 0 0 calc(25% - 12rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(4n) {
margin-right: 0;
}
}
}
// 1 列布局
&.row1-of1 {
.channel-item {
flex: 0 0 100%;
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(1n) {
margin-right: 0;
}
}
}
}
/**
* 导航布局样式
* 支持固定布局和滚动布局
*/
.channel-nav {
padding: 16rpx;
box-sizing: border-box;
.uni-scroll-view-content {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
// 单滑动模式
&.singleSlide {
.uni-scroll-view-content {
flex-wrap: nowrap;
}
.channel-nav-item {
flex-shrink: 0;
width: 280rpx;
}
}
.channel-nav-item {
display: flex;
flex-direction: column;
box-sizing: border-box;
// 默认 3 列布局
flex: 0 0 calc(33.3333333% - 10rpx);
&:nth-child(3n) {
margin-right: 0;
}
}
// 2 列布局
&.row1-of2 {
.channel-nav-item {
flex: 0 0 calc(50% - 8rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(2n) {
margin-right: 0;
}
}
}
// 4 列布局
&.row1-of4 {
.channel-nav-item {
flex: 0 0 calc(25% - 12rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(4n) {
margin-right: 0;
}
}
}
// 1 列布局
&.row1-of1 {
.channel-nav-item {
flex: 0 0 100%;
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(1n) {
margin-right: 0;
}
}
}
}
/**
* 确保所有视频卡片高度一致
*/
.channel-item,
.channel-nav-item {
display: flex;
flex-direction: column;
}
/**
* 轮播样式
* 支持轮播模式的布局和样式
*/
.channel-swiper {
width: 100%;
box-sizing: border-box;
.swiper-slide-content {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 16rpx;
box-sizing: border-box;
.channel-item {
// 默认 3 列布局
flex: 0 0 calc(33.3333333% - 10rpx);
box-sizing: border-box;
&:nth-child(3n) {
margin-right: 0;
}
}
// 轮播模式下的 2 列布局
&.row1-of2 {
.channel-item {
flex: 0 0 calc(50% - 8rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(2n) {
margin-right: 0;
}
}
}
// 轮播模式下的 4 列布局
&.row1-of4 {
.channel-item {
flex: 0 0 calc(25% - 12rpx);
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(4n) {
margin-right: 0;
}
}
}
// 轮播模式下的 1 列布局
&.row1-of1 {
.channel-item {
flex: 0 0 100%;
&:nth-child(3n) {
margin-right: 16rpx;
}
&:nth-child(1n) {
margin-right: 0;
}
}
}
}
}
/**
* 响应式调整
* 在小屏幕设备上调整布局和间距
*/
@media (max-width: 375px) {
.channel-list,
.channel-nav,
.channel-swiper .swiper-slide-content {
gap: 12rpx;
padding: 12rpx;
.channel-item,
.channel-nav-item {
// 默认 3 列布局
flex: 0 0 calc(33.3333333% - 8rpx);
&:nth-child(3n) {
margin-right: 0;
}
}
// 小屏幕上的 2 列布局
&.row1-of2 {
.channel-item,
.channel-nav-item {
flex: 0 0 calc(50% - 6rpx);
&:nth-child(3n) {
margin-right: 12rpx;
}
&:nth-child(2n) {
margin-right: 0;
}
}
}
// 小屏幕上的 4 列布局
&.row1-of4 {
.channel-item,
.channel-nav-item {
flex: 0 0 calc(25% - 9rpx);
&:nth-child(3n) {
margin-right: 12rpx;
}
&:nth-child(4n) {
margin-right: 0;
}
}
}
// 小屏幕上的 1 列布局
&.row1-of1 {
.channel-item,
.channel-nav-item {
flex: 0 0 100%;
&:nth-child(3n) {
margin-right: 12rpx;
}
&:nth-child(1n) {
margin-right: 0;
}
}
}
}
// 小屏幕上的单滑动模式
.channel-nav {
&.singleSlide {
.channel-nav-item {
width: 240rpx;
}
}
}
}
</style>