郭鹏飞 7 сар өмнө
parent
commit
c40c80236e

+ 265 - 0
src/pages/moments/waterfall.vue

@@ -0,0 +1,265 @@
+<template>
+    <view class="page-container">
+        <view class="waterfall-box">
+            <view class="column">
+                <view class="card-item" v-for="(item, index) in leftList" :key="item.id" @click="goDetail(item)">
+                    <CardContent :item="item" />
+                </view>
+            </view>
+
+            <view class="column">
+                <view class="card-item" v-for="(item, index) in rightList" :key="item.id" @click="goDetail(item)">
+                    <CardContent :item="item" />
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+// 为了代码整洁,我们将卡片内部逻辑抽离成局部组件,或者直接写在上面
+// 这里为了演示方便,我直接在 methods 里处理数据分发
+
+export default {
+    components: {
+        // 内部组件:卡片内容
+        CardContent: {
+            props: ['item'],
+            template: `
+            <view class="card-inner">
+            <view class="media-cover">
+                <u-image 
+                v-if="item.type === 'image' && item.urls.length > 0" 
+                :src="item.urls[0]" 
+                width="100%" 
+                mode="widthFix" 
+                radius="8px 8px 0 0"
+                ></u-image>
+                
+                <u-image 
+                v-else-if="item.type === 'video'" 
+                :src="item.videoPoster" 
+                width="100%" 
+                mode="widthFix" 
+                radius="8px 8px 0 0"
+                ></u-image>
+                
+                <view v-else class="text-cover" :style="{background: item.bgColor || '#f0f2f5'}">
+                <text class="only-text">{{ item.content }}</text>
+                </view>
+
+                <view class="icon-layer">
+                <u-icon v-if="item.type === 'video'" name="play-circle-fill" color="#fff" size="28"></u-icon>
+                <view v-if="item.type === 'image' && item.urls.length > 1" class="img-count">
+                    <u-icon name="photo" color="#fff" size="12"></u-icon>
+                    <text class="count-num">{{ item.urls.length }}</text>
+                </view>
+                </view>
+            </view>
+
+            <view class="info-box">
+                <view class="title u-line-2" v-if="item.type !== 'text'">
+                {{ item.content || '分享生活' }}
+                </view>
+                
+                <view class="user-row">
+                <view class="left-user">
+                    <u-avatar :src="item.avatar" size="20"></u-avatar>
+                    <text class="name u-line-1">{{ item.nickname }}</text>
+                </view>
+                <view class="right-like">
+                    <u-icon :name="item.isLiked ? 'heart-fill' : 'heart'" :color="item.isLiked ? '#ff2442' : '#999'" size="16"></u-icon>
+                    <text class="num">{{ item.likeCount }}</text>
+                </view>
+                </view>
+            </view>
+            </view>
+        `
+        }
+    },
+    data() {
+        return {
+            allList: [],   // 原始数据
+            leftList: [],  // 左列数据
+            rightList: []  // 右列数据
+        };
+    },
+    onLoad() {
+        this.loadData();
+    },
+    methods: {
+        // 模拟获取数据
+        loadData() {
+            // 模拟API返回的数据
+            const apiData = [
+                {
+                    id: 1, type: 'image', content: '周末去露营啦,风景真的超级好!推荐大家来这里打卡。',
+                    urls: ['https://cdn.uviewui.com/uview/album/1.jpg', 'https://cdn.uviewui.com/uview/album/2.jpg'],
+                    avatar: 'https://cdn.uviewui.com/uview/album/1.jpg', nickname: '露营达人', likeCount: 120, isLiked: true
+                },
+                {
+                    id: 2, type: 'video', content: '这是我新拍的Vlog,剪辑了好久,希望大家喜欢!',
+                    videoPoster: 'https://cdn.uviewui.com/uview/album/2.jpg', videoUrl: '...',
+                    avatar: 'https://cdn.uviewui.com/uview/album/2.jpg', nickname: 'Vlog博主', likeCount: 45, isLiked: false
+                },
+                {
+                    id: 3, type: 'image', content: '今天吃的好丰盛',
+                    urls: ['https://cdn.uviewui.com/uview/album/3.jpg'], // 单图
+                    avatar: 'https://cdn.uviewui.com/uview/album/3.jpg', nickname: '吃货小美', likeCount: 8, isLiked: false
+                },
+                {
+                    id: 4, type: 'text', content: '今天心情稍微有点低落,但是看到彩虹了!',
+                    bgColor: '#ff9a9e', // 纯文字给个背景色
+                    avatar: 'https://cdn.uviewui.com/uview/album/4.jpg', nickname: '心情日记', likeCount: 2, isLiked: false
+                },
+                {
+                    id: 5, type: 'image', content: '高纵横比的图片测试,瀑布流效果。',
+                    urls: ['https://cdn.uviewui.com/uview/swiper/1.jpg'], // 这是一个长图
+                    avatar: 'https://cdn.uviewui.com/uview/album/5.jpg', nickname: '摄影师', likeCount: 300, isLiked: true
+                }
+            ];
+
+            this.distributeData(apiData);
+        },
+
+        // 关键:左右分列逻辑
+        distributeData(list) {
+            list.forEach((item, index) => {
+                // 简单方案:根据索引奇偶数分发
+                // 进阶方案:应该计算左右两列当前的累积高度,哪边矮由于哪边(需要异步获取图片高度,较复杂)
+                if (index % 2 === 0) {
+                    this.leftList.push(item);
+                } else {
+                    this.rightList.push(item);
+                }
+            });
+        },
+
+        goDetail(item) {
+            console.log('点击了卡片', item.id);
+            // 跳转到详情页,详情页通常是全屏轮播图或者类似抖音的上下滑动
+            // uni.navigateTo({ url: `/pages/moments/detail?id=${item.id}` });
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+    background-color: #f7f7f7; // 浅灰底色
+    min-height: 100vh;
+    padding: 20rpx;
+}
+
+.waterfall-box {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+}
+
+.column {
+    width: 48%; // 左右各占约一半
+    display: flex;
+    flex-direction: column;
+}
+
+.card-item {
+    margin-bottom: 20rpx;
+    background-color: #fff;
+    border-radius: 12rpx;
+    overflow: hidden; // 圆角遮罩
+    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+// 以下样式会被 CardContent 组件继承(如果是分开的文件需注意scoped)
+.card-inner {
+    width: 100%;
+}
+
+.media-cover {
+    position: relative;
+    width: 100%;
+    min-height: 200rpx; // 防止加载时塌陷
+
+    // 纯文字模式样式
+    .text-cover {
+        height: 300rpx;
+        padding: 30rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        .only-text {
+            color: #fff;
+            font-weight: bold;
+            font-size: 30rpx;
+            text-align: center;
+            line-height: 1.5;
+        }
+    }
+
+    .icon-layer {
+        position: absolute;
+        top: 10rpx;
+        right: 10rpx;
+        z-index: 2;
+
+        .img-count {
+            background: rgba(0, 0, 0, 0.5);
+            border-radius: 20rpx;
+            padding: 4rpx 12rpx;
+            display: flex;
+            align-items: center;
+
+            .count-num {
+                color: #fff;
+                font-size: 20rpx;
+                margin-left: 6rpx;
+            }
+        }
+    }
+}
+
+.info-box {
+    padding: 16rpx;
+
+    .title {
+        font-size: 28rpx;
+        color: #333;
+        line-height: 1.4;
+        font-weight: 500;
+        margin-bottom: 16rpx;
+        // u-line-2 是 uView 内置的多行省略样式,如果未生效请手动添加css
+    }
+
+    .user-row {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .left-user {
+            display: flex;
+            align-items: center;
+            flex: 1;
+            overflow: hidden;
+
+            .name {
+                font-size: 22rpx;
+                color: #666;
+                margin-left: 10rpx;
+            }
+        }
+
+        .right-like {
+            display: flex;
+            align-items: center;
+
+            .num {
+                font-size: 22rpx;
+                color: #666;
+                margin-left: 6rpx;
+            }
+        }
+    }
+}
+</style>