|
@@ -0,0 +1,364 @@
|
|
|
+<template>
|
|
|
+ <div class="moment-header-container">
|
|
|
+ <img src="../../static/images/moment-bg.png" style="width: 100%;">
|
|
|
+ <CloseOutlined class="close-icon" @click="closeMoment"/>
|
|
|
+ <InstagramFilled class="pub-icon" @click="showPublishPage"/>
|
|
|
+ </div>
|
|
|
+ <div class="user-info-container">
|
|
|
+ <img :src="loginUser.photo" style="float: right; height: 60px; border-radius: 5px;">
|
|
|
+ <b style="float: right; margin-right: 10px; color: white;">
|
|
|
+ {{loginUser.userName}}
|
|
|
+ </b>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="moment-list-container">
|
|
|
+
|
|
|
+ <div class="feed-container" ref="feedContainer">
|
|
|
+ <a-list item-layout="vertical" class="moment-list" :data-source="feedList" :loading="loading">
|
|
|
+ <template #renderItem="{ item }">
|
|
|
+ <a-list-item :key="item.id"> <!-- 添加唯一key -->
|
|
|
+ <!-- 动态内容展示 -->
|
|
|
+ <div class="feed-item">
|
|
|
+ <div class="feed-header">
|
|
|
+ <!-- 头部:头像+用户信息 -->
|
|
|
+ <a-avatar
|
|
|
+ :src="item.authorInfo.photo"
|
|
|
+ class="user-avatar"
|
|
|
+ :size="32"
|
|
|
+ style="margin-left: -40px; float: left; border-radius: 3px;"
|
|
|
+ />
|
|
|
+ <div class="user-info" style="text-align: left; float: left;">
|
|
|
+ <p class="user-name" style="line-height: 22px; margin-bottom: 0px;">{{ item.authorInfo.userName }}</p>
|
|
|
+ <p class="user-signature" style="font-size: 10px; line-height: 10px;">{{ item.authorInfo.signature }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="feed-content">{{ item.content }}</div>
|
|
|
+ <div
|
|
|
+ class="feed-images"
|
|
|
+ :class="`count-${Math.min(item.images.length, 9)}`"
|
|
|
+ v-if="item.images"
|
|
|
+ >
|
|
|
+ <a-image
|
|
|
+ v-for="(image, index) in item.images"
|
|
|
+ :key="index"
|
|
|
+ :src="image.resource"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="item.video" style="height: auto;"
|
|
|
+ >
|
|
|
+ <video
|
|
|
+ autoplay
|
|
|
+ muted
|
|
|
+ style="max-height: 250px; float: left; max-width: 95%;"
|
|
|
+ controls
|
|
|
+ :poster="item.video.resource"
|
|
|
+ >
|
|
|
+ <source :src="item.video.resource" type="video/mp4">
|
|
|
+ </video>
|
|
|
+ <div style="clear: both;"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="moment-footer">
|
|
|
+ {{formatTime(item.publishTime)}}
|
|
|
+ <span style="margin-left: 20px;">{{item.ipAddress}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </a-list-item>
|
|
|
+ </template>
|
|
|
+ </a-list>
|
|
|
+ <div v-if="loading" class="loading-more">
|
|
|
+ <a-spin />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <a-modal
|
|
|
+ :visible="momentPublishVisible"
|
|
|
+ @cancel="momentPublishVisible = false"
|
|
|
+ :footer="null"
|
|
|
+ :closable="false"
|
|
|
+ :bodyStyle="{ padding: 0 }"
|
|
|
+ style="height: 500px; width: 300px; border-radius: 3px; padding: 0px; margin-top: 80px;"
|
|
|
+ >
|
|
|
+ <MomentPublish
|
|
|
+ @success="handlePublishSuccess"
|
|
|
+ @cancel="momentPublishVisible = false" >
|
|
|
+ </MomentPublish>
|
|
|
+ </a-modal>
|
|
|
+
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import axios from 'axios';
|
|
|
+import { inject, defineComponent, ref, onMounted, onUnmounted, watch } from 'vue';
|
|
|
+import { List as AList, Image as AImage, Spin as ASpin } from 'ant-design-vue';
|
|
|
+import MomentPublish from '../moment/publish.vue';
|
|
|
+export default defineComponent({
|
|
|
+ props: {
|
|
|
+
|
|
|
+ },
|
|
|
+ components: {
|
|
|
+ MomentPublish
|
|
|
+ },
|
|
|
+ setup(props, {emit}) {
|
|
|
+
|
|
|
+ const loginUser = ref(inject('loginUser'));
|
|
|
+ const feedList = ref([]);
|
|
|
+ const loading = ref(false);
|
|
|
+ const feedContainer = ref(null);
|
|
|
+ const momentPublishVisible = ref(false);
|
|
|
+ const lastMomentId = ref(99999999999);
|
|
|
+
|
|
|
+ // 模拟数据加载函数
|
|
|
+ const loadMoreFeeds = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ // 这里替换为实际的API请求
|
|
|
+ axios.get(`/client-service/moment/timeline?lastMomentId=` + lastMomentId.value, {
|
|
|
+ // 这里可以添加请求的配置,例如 headers 或 params
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ 'origin-url': window.location.href
|
|
|
+ }
|
|
|
+ }).then(function (response) {
|
|
|
+ loading.value = false;
|
|
|
+ if (response.data.code === 40001) {
|
|
|
+ // 未登录
|
|
|
+ window.location.href = response.data.redirect_url;
|
|
|
+ } else if (response.data.code === 200) {
|
|
|
+ const moments = response.data.data;
|
|
|
+ if(moments.length > 0) {
|
|
|
+ feedList.value = [...feedList.value, ...moments];
|
|
|
+ // 更新最后ID
|
|
|
+ lastMomentId.value = moments[moments.length-1].id;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }).catch(function (error) {
|
|
|
+ // 处理错误
|
|
|
+ console.error(error);
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载数据失败:', error);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 在 setup() 中替换这部分
|
|
|
+ const throttle = (func, wait) => {
|
|
|
+ let timeout;
|
|
|
+ return (...args) => {
|
|
|
+ if (!timeout) {
|
|
|
+ timeout = setTimeout(() => {
|
|
|
+ func.apply(this, args);
|
|
|
+ timeout = null;
|
|
|
+ }, wait);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
+ // 修改滚动处理函数
|
|
|
+ const handleScroll = throttle(() => {
|
|
|
+ const container = feedContainer.value
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ // 调试输出(保留用于问题诊断)
|
|
|
+ console.log(`滚动位置: ${container.scrollTop + container.clientHeight}/${container.scrollHeight}`)
|
|
|
+
|
|
|
+ // 改进的判断逻辑
|
|
|
+ const scrollThreshold = 50 // 缓冲像素
|
|
|
+ const isBottomReached = container.scrollTop + container.clientHeight + scrollThreshold >= container.scrollHeight
|
|
|
+
|
|
|
+ if (isBottomReached && !loading.value) {
|
|
|
+ loadMoreFeeds()
|
|
|
+ }
|
|
|
+ }, 200);
|
|
|
+
|
|
|
+ const showPublishPage = () => {
|
|
|
+ momentPublishVisible.value = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ const closeMoment = () => {
|
|
|
+ emit("closeMoment")
|
|
|
+ }
|
|
|
+
|
|
|
+ const handlePublishSuccess = () => {
|
|
|
+ momentPublishVisible.value = false;
|
|
|
+ setTimeout(() => {
|
|
|
+ lastMomentId.value = 99999999999;
|
|
|
+ feedList.value = [];
|
|
|
+ loadMoreFeeds();
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+
|
|
|
+ const formatTime = (timestamp) => {
|
|
|
+ const now = new Date()
|
|
|
+ const target = new Date(timestamp)
|
|
|
+ const diffMinutes = Math.round((now - target) / 60000)
|
|
|
+
|
|
|
+ if (diffMinutes < 60) {
|
|
|
+ return `${diffMinutes}分钟前`
|
|
|
+ } else if (target.toDateString() === now.toDateString()) {
|
|
|
+ return target.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
|
|
+ } else {
|
|
|
+ return `${target.getMonth() + 1}月${target.getDate()}日`
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ feedContainer.value?.removeEventListener('scroll', handleScroll);
|
|
|
+ });
|
|
|
+
|
|
|
+ onMounted(() => {
|
|
|
+ loadMoreFeeds();
|
|
|
+ feedContainer.value?.addEventListener('scroll', handleScroll);
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ return {
|
|
|
+ feedList,
|
|
|
+ loading,
|
|
|
+ loginUser,
|
|
|
+ momentPublishVisible,
|
|
|
+ feedContainer,
|
|
|
+ formatTime,
|
|
|
+ showPublishPage,
|
|
|
+ closeMoment,
|
|
|
+ handlePublishSuccess
|
|
|
+ };
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ .moment-header-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 180px;
|
|
|
+ background-color: black;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ .user-info-container {
|
|
|
+ position: relative;
|
|
|
+ width: 90%;
|
|
|
+ height: 60px;
|
|
|
+ margin-top: -30px;
|
|
|
+ margin-left: 5%;
|
|
|
+ }
|
|
|
+ .moment-list-container {
|
|
|
+ position: relative;
|
|
|
+ width: 90%;
|
|
|
+ height: 60px;
|
|
|
+ margin-top: 0px;
|
|
|
+ margin-left: 5%;
|
|
|
+ height: 400px
|
|
|
+ }
|
|
|
+ .pub-icon, .close-icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 27px;
|
|
|
+ top: 20px;
|
|
|
+ color: white;
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+ .close-icon {
|
|
|
+ left: 20px;
|
|
|
+ }
|
|
|
+ .feed-container {
|
|
|
+ height: 400px;
|
|
|
+ border: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 0px;
|
|
|
+ overflow: auto !important;
|
|
|
+ scrollbar-width: none !important; /* Firefox */
|
|
|
+ -ms-overflow-style: none !important; /* IE/Edge */
|
|
|
+ }
|
|
|
+
|
|
|
+.feed-item {
|
|
|
+padding: 0px 16px;
|
|
|
+border-bottom: none;
|
|
|
+}
|
|
|
+.feed-header {
|
|
|
+position: relative;
|
|
|
+width: 100%;
|
|
|
+height: 45px;
|
|
|
+text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.user-name {
|
|
|
+font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.feed-content {
|
|
|
+margin-bottom: 12px;
|
|
|
+line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.feed-images {
|
|
|
+width: 100%;
|
|
|
+height: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-more {
|
|
|
+display: flex;
|
|
|
+justify-content: center;
|
|
|
+padding: 16px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.parent-container {
|
|
|
+isolation: isolate; /* 阻止样式继承 */
|
|
|
+}
|
|
|
+
|
|
|
+.feed-images {
|
|
|
+display: flex;
|
|
|
+flex-wrap: wrap;
|
|
|
+gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 单张图片样式 */
|
|
|
+.count-1 {
|
|
|
+justify-content: center;
|
|
|
+
|
|
|
+::v-deep .ant-image {
|
|
|
+ max-width: 80%;
|
|
|
+ height: auto;
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+/* 两张图片样式 */
|
|
|
+.count-2 {
|
|
|
+flex-wrap: nowrap; /* 关键修复点 */
|
|
|
+::v-deep .ant-image {
|
|
|
+ flex: 0 0 100px; /* 严格固定尺寸 */
|
|
|
+ margin-right: 12px; /* 替代gap避免计算误差 */
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+/* 三张及以上图片 */
|
|
|
+.count-3 ::v-deep .ant-image,
|
|
|
+.count-4 ::v-deep .ant-image,
|
|
|
+.count-5 ::v-deep .ant-image,
|
|
|
+.count-6 ::v-deep .ant-image,
|
|
|
+.count-7 ::v-deep .ant-image,
|
|
|
+.count-8 ::v-deep .ant-image,
|
|
|
+.count-9 ::v-deep .ant-image {
|
|
|
+flex: 0 0 calc(33.333% - 8px);
|
|
|
+}
|
|
|
+
|
|
|
+/* 单张图片比例保持 */
|
|
|
+::v-deep .ant-image-img {
|
|
|
+object-fit: cover;
|
|
|
+width: 100%;
|
|
|
+height: 100%;
|
|
|
+}
|
|
|
+.moment-footer {
|
|
|
+line-height: 30px;
|
|
|
+font-size: 12px;
|
|
|
+text-align: left;
|
|
|
+color: gray;
|
|
|
+}
|
|
|
+</style>
|