723 lines
18 KiB
Vue
723 lines
18 KiB
Vue
<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>
|