YingXingAI/components/ContentCard.vue

723 lines
18 KiB
Vue
Raw Permalink Normal View History

2025-06-30 14:43:02 +08:00
<template>
<!-- 视频图片 动态卡片 -->
<view class="content-card">
<!-- 用户信息 -->
<view class="user-info">
<image
class="avatar"
:src="cardData.user.avatar"
mode="aspectFill"
></image>
<view class="user-detail">
<text class="username">{{ cardData.user.name }}</text>
<text class="user-meta">{{ cardData.user.meta }}</text>
</view>
<view class="follow-btn" @click="onFollow">
<u-icon name="plus" color="#666" size="14"></u-icon>
<text>关注</text>
</view>
</view>
<!-- 内容文本 -->
<view class="content-text" @click="goContentDetail">
{{ cardData.content }}
</view>
<!-- 媒体内容 - 内部处理交互 -->
<view class="media-content">
<!-- 视频内容 -->
<view v-if="isVideo" class="video-container" @click="handleVideoPlay">
<image
class="video-cover"
:src="cardData.cover"
mode="aspectFill"
></image>
<view class="video-overlay">
<u-icon name="play-right" color="#fff" size="40"></u-icon>
<text v-if="cardData.duration" class="video-duration">{{
formatDuration(cardData.duration)
}}</text>
</view>
</view>
<!-- 图片内容 -->
<view v-else class="image-grid" :class="getImageLayoutClass()">
<image
v-for="(img, index) in cardData.images"
:key="index"
class="grid-image"
:src="img"
mode="aspectFill"
@click="handleImagePreview(index)"
></image>
</view>
</view>
<!-- 互动栏 - 内部处理交互 -->
<view class="interaction-bar">
<view
class="action-item"
v-for="(item, index) in actionButtons"
:key="index"
@click="handleAction(item.action)"
>
<image
:src="item.icon"
mode="aspectFill"
style="width: 40rpx; height: 40rpx"
></image>
<text>{{ cardData.stats[item.stat] }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "ContentCard",
props: {
cardData: {
type: Object,
required: true,
default: () => ({
user: {
avatar: "",
name: "",
meta: "",
},
content: "",
// 可以是视频或图片
type: "image", // 'image' 或 'video'
images: [], // 图片数组
cover: "", // 视频封面
videoUrl: "", // 视频地址
duration: 0, // 视频时长(秒)
stats: {
forward: 0,
comment: 0,
like: 0,
favorite: 0,
},
}),
},
},
data() {
return {
// 添加内部状态跟踪互动状态
isLiked: false,
isFavorited: false,
isForwarded: false,
videoContext: null,
// 添加操作按钮配置
actionButtons: [
{
icon: "/static/common/img/homepage/reply.png",
action: "forward",
stat: "forward"
},
{
icon: "/static/common/img/homepage/comment.png",
action: "comment",
stat: "comment"
},
{
icon: "/static/common/img/homepage/like.png",
action: "like",
stat: "like"
},
{
icon: "/static/common/img/homepage/star.png",
action: "favorite",
stat: "favorite"
}
]
};
},
computed: {
isVideo() {
return this.cardData.type === "video";
},
// 根据状态动态改变图标和颜色
likeIcon() {
return this.isLiked ? "heart-fill" : "heart";
},
likeColor() {
return this.isLiked ? "#ff5252" : "#999";
},
favoriteIcon() {
return this.isFavorited ? "star-fill" : "star";
},
favoriteColor() {
return this.isFavorited ? "#ffb700" : "#999";
},
forwardIcon() {
return this.isForwarded ? "arrow-right-fill" : "arrow-right";
},
forwardColor() {
return this.isForwarded ? "#2979ff" : "#999";
},
commentColor() {
return "#999"; // 评论按钮颜色无变化
},
},
methods: {
// 处理关注点击
onFollow() {
this.$emit("follow", this.cardData.user);
},
// 跳转详情页
goContentDetail() {
console.log("this.cardData", this.cardData);
this.$u.route({
url: "/pages/home/home/components/contentDetail/index",
// params: {
// card,
// },
});
},
// 格式化视频时长
formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes.toString().padStart(2, "0")}:${remainingSeconds
.toString()
.padStart(2, "0")}`;
},
// 处理视频播放
handleVideoPlay() {
if (this.cardData.videoUrl) {
// 记录播放历史
this.savePlayHistory();
// 使用内置视频播放器播放
try {
// 先尝试使用内部视频播放器
if (this.useInternalPlayer) {
this.showVideoPlayer = true;
this.$nextTick(() => {
this.videoContext = uni.createVideoContext("video-player", this);
this.videoContext.requestFullScreen();
this.videoContext.play();
});
} else {
// 否则跳转到专门的播放页面
uni.navigateTo({
url: `/pages/video/player?videoUrl=${encodeURIComponent(
this.cardData.videoUrl
)}&title=${encodeURIComponent(this.cardData.content)}`,
});
}
// 触发播放事件,让父组件可以进行额外处理
this.$emit("video-play", {
videoUrl: this.cardData.videoUrl,
title: this.cardData.content,
cover: this.cardData.cover,
cardId: this.cardData.id,
});
// 更新播放统计
this.updateVideoStats();
} catch (error) {
console.error("视频播放失败:", error);
uni.showToast({
title: "视频播放失败,请稍后再试",
icon: "none",
});
}
}
},
// 处理图片预览
handleImagePreview(index) {
try {
// 使用uni-app的图片预览API
uni.previewImage({
current: index,
urls: this.cardData.images,
longPressActions: {
itemList: ["保存图片", "分享图片"],
success: (res) => {
const { tapIndex } = res;
if (tapIndex === 0) {
// 保存图片
this.saveImage(this.cardData.images[index]);
} else if (tapIndex === 1) {
// 分享图片
this.shareImage(this.cardData.images[index]);
}
},
},
});
// 触发图片预览事件
this.$emit("image-preview", {
index,
images: this.cardData.images,
cardId: this.cardData.id,
});
} catch (error) {
console.error("图片预览失败:", error);
}
},
// 保存图片
saveImage(url) {
uni.showLoading({ title: "正在保存..." });
uni.downloadFile({
url,
success: (res) => {
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.showToast({ title: "保存成功" });
},
fail: (err) => {
console.error("保存失败:", err);
uni.showToast({ title: "保存失败", icon: "none" });
},
});
}
},
fail: () => {
uni.showToast({ title: "下载图片失败", icon: "none" });
},
complete: () => {
uni.hideLoading();
},
});
},
// 分享图片
shareImage(url) {
// 根据平台实现不同的分享逻辑
// #ifdef APP-PLUS
plus.share.sendWithSystem({
type: "image",
pictures: [url],
});
// #endif
// #ifdef H5
// H5环境可能需要调用其他API
// #endif
// #ifdef MP
// 小程序环境使用小程序分享API
uni.showToast({ title: "请使用右上角分享", icon: "none" });
// #endif
},
// 根据图片数量确定布局样式
getImageLayoutClass() {
const count = this.cardData.images.length;
if (count === 1) return "single-image";
if (count === 2) return "double-image";
if (count === 3) return "triple-image";
if (count === 4) return "four-image";
if (count > 4) return "multi-image";
return "";
},
// 处理转发
handleForward() {
this.isForwarded = !this.isForwarded;
// 显示分享菜单
uni.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"],
});
// 触发转发事件
this.$emit("forward", {
cardData: this.cardData,
isForwarded: this.isForwarded,
});
// 如果在APP中可以调用原生分享
// #ifdef APP-PLUS
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 0,
title: this.cardData.content,
summary: this.cardData.content,
imageUrl: this.isVideo ? this.cardData.cover : this.cardData.images[0],
success: function (res) {
console.log("分享成功:" + JSON.stringify(res));
},
fail: function (err) {
console.log("分享失败:" + JSON.stringify(err));
},
});
// #endif
},
// 处理评论
handleComment() {
// 跳转到评论页面或打开评论弹窗
this.$emit("comment", {
cardData: this.cardData,
});
// 这里可以实现自定义的评论交互
uni.navigateTo({
url: `/pages/comment/index?id=${this.cardData.id}&type=${this.cardData.type}`,
});
},
// 处理点赞
handleLike() {
// 切换点赞状态
this.isLiked = !this.isLiked;
// 更新点赞数据
const newStats = { ...this.cardData.stats };
if (this.isLiked) {
newStats.like += 1;
} else {
newStats.like = Math.max(0, newStats.like - 1);
}
// 更新数据并触发事件
this.$emit("like", {
cardData: this.cardData,
isLiked: this.isLiked,
newStats,
});
// 发送点赞请求到服务器
this.updateLikeStatus();
},
// 处理收藏
handleFavorite() {
// 切换收藏状态
this.isFavorited = !this.isFavorited;
// 更新收藏数据
const newStats = { ...this.cardData.stats };
if (this.isFavorited) {
newStats.favorite += 1;
} else {
newStats.favorite = Math.max(0, newStats.favorite - 1);
}
// 更新数据并触发事件
this.$emit("favorite", {
cardData: this.cardData,
isFavorited: this.isFavorited,
newStats,
});
// 发送收藏请求到服务器
this.updateFavoriteStatus();
},
// 更新点赞状态到服务器
updateLikeStatus() {
// 调用API更新点赞状态
// 示例代码根据实际API调整
const url = this.isLiked ? "/api/like/add" : "/api/like/cancel";
uni.request({
url,
method: "POST",
data: {
contentId: this.cardData.id,
contentType: this.cardData.type,
},
success: (res) => {
console.log("点赞状态更新成功", res);
},
fail: (err) => {
console.error("点赞状态更新失败", err);
// 恢复原状态
this.isLiked = !this.isLiked;
},
});
},
// 更新收藏状态到服务器
updateFavoriteStatus() {
// 调用API更新收藏状态
// 示例代码根据实际API调整
const url = this.isFavorited
? "/api/favorite/add"
: "/api/favorite/cancel";
uni.request({
url,
method: "POST",
data: {
contentId: this.cardData.id,
contentType: this.cardData.type,
},
success: (res) => {
console.log("收藏状态更新成功", res);
},
fail: (err) => {
console.error("收藏状态更新失败", err);
// 恢复原状态
this.isFavorited = !this.isFavorited;
},
});
},
// 记录视频播放历史
savePlayHistory() {
// 保存播放历史到本地存储或服务器
try {
const historyList = uni.getStorageSync("videoPlayHistory") || [];
const now = new Date().getTime();
// 查找是否已有该视频记录
const index = historyList.findIndex(
(item) => item.id === this.cardData.id
);
if (index > -1) {
// 更新已有记录
historyList[index].lastPlayTime = now;
historyList[index].playCount += 1;
} else {
// 添加新记录
historyList.push({
id: this.cardData.id,
title: this.cardData.content,
cover: this.cardData.cover,
lastPlayTime: now,
playCount: 1,
});
}
// 保存回本地存储
uni.setStorageSync("videoPlayHistory", historyList);
} catch (e) {
console.error("保存播放历史失败", e);
}
},
// 更新视频播放统计
updateVideoStats() {
// 调用API更新视频播放次数
uni.request({
url: "/api/video/view",
method: "POST",
data: {
videoId: this.cardData.id,
},
success: (res) => {
console.log("视频播放统计更新成功", res);
},
fail: (err) => {
console.error("视频播放统计更新失败", err);
},
});
},
// 添加统一的操作处理方法
handleAction(action) {
switch(action) {
case "forward":
this.handleForward();
break;
case "comment":
this.handleComment();
break;
case "like":
this.handleLike();
break;
case "favorite":
this.handleFavorite();
break;
}
}
},
};
</script>
<style scoped lang="scss">
.content-card {
background-color: #fff;
border-radius: 24rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
.user-info {
display: flex;
align-items: center;
margin-bottom: 24rpx;
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 16rpx;
}
.user-detail {
flex: 1;
.username {
font-size: 30rpx;
font-weight: 500;
color: #333;
display: block;
line-height: 1.2;
}
.user-meta {
font-size: 24rpx;
color: #999;
display: block;
line-height: 1.2;
margin-top: 6rpx;
}
}
.follow-btn {
display: flex;
align-items: center;
justify-content: center;
background-color: #f5f5f5;
border-radius: 30rpx;
padding: 8rpx 16rpx;
text {
font-size: 24rpx;
color: #666;
margin-left: 4rpx;
}
}
}
.content-text {
font-size: 28rpx;
color: #333;
line-height: 1.5;
margin-bottom: 24rpx;
}
.media-content {
margin-bottom: 24rpx;
width: 100%;
// 视频容器样式
.video-container {
position: relative;
width: 100%;
border-radius: 8rpx;
overflow: hidden;
.video-cover {
width: 100%;
height: 400rpx;
display: block;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.2);
.video-duration {
position: absolute;
right: 16rpx;
bottom: 16rpx;
color: #fff;
font-size: 24rpx;
background-color: rgba(0, 0, 0, 0.5);
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
}
}
// 图片网格样式
.image-grid {
display: flex;
flex-wrap: wrap;
.grid-image {
border-radius: 8rpx;
background-color: #f5f5f5;
}
&.single-image {
.grid-image {
width: 100%;
max-height: 400rpx;
}
}
&.double-image {
justify-content: space-between;
.grid-image {
width: 49%;
height: 240rpx;
}
}
&.triple-image {
justify-content: space-between;
.grid-image {
width: 31%;
height: 180rpx;
}
}
&.four-image {
justify-content: space-between;
.grid-image {
width: 49%;
height: 180rpx;
margin-bottom: 10rpx;
}
}
&.multi-image {
justify-content: space-between;
.grid-image {
width: 31%;
height: 180rpx;
margin-bottom: 10rpx;
}
}
}
}
.interaction-bar {
display: flex;
justify-content: space-between;
margin-top: 16rpx;
padding: 0 20rpx;
.action-item {
display: flex;
align-items: center;
text {
font-size: 24rpx;
color: #999;
margin-left: 8rpx;
}
}
}
}
</style>