Files
test-subject/src/pages/post.vue
2025-12-04 15:32:55 +08:00

471 lines
11 KiB
Vue

<template>
<view v-if="finishloading" class="page">
<Head></Head>
<!-- 内容区 -->
<view class="content">
<!-- 动态区域 -->
<view class="moment">
<!-- 用户栏 -->
<view class="userbar">
<image v-if="post.user && post.user.userImg" :src="post.user.userImg" mode="aspectFill" class="userimg"
alt="用户头像" />
<image v-else src="/static/imgs/default-avatar.png" mode="aspectFill" class="userimg" alt="默认头像" />
<!-- 用户名翻译 -->
<text class="username">{{ post.translatedUserName || post.user.nickName }}</text>
<button class="follow" @tap="common.openapp">
<uni-icons v-if="post.isfollow" type="checkmarkempty" size="20" color="#333"></uni-icons>
<text v-else>フォロー</text>
</button>
</view>
<!-- 轮播图 -->
<swiper v-if="post.imgs && post.imgs.length > 0" :indicator-dots="false" :autoplay="false" :interval="3000"
:duration="1000" :circular="true" class="swiper-banner" @change="onChange">
<swiper-item v-for="(item, i) in post.imgs" :key="i">
<view class="swiper-center">
<image v-if="item.type === 'img'" :src="item.src" class="swiper-item" mode="aspectFit" alt="动态图片内容"
@load="onImageLoad(i)" @error="onImageError(i)" />
<video v-else-if="item.type === 'video'" id="videoid" :src="item.src" class="swiper-item"
:controls="false" @tap="pausevideo" object-fit="contain" alt="动态视频内容" />
<!-- 播放按钮 -->
<view v-if="!isPlaying" class="uni-video-cover" @tap="pausevideo">
<view class="uni-video-cover-play-button uni-video-icon"></view>
</view>
</view>
</swiper-item>
</swiper>
<!-- 外部指示点:2个及以上才显示 -->
<view v-if="post.imgs && post.imgs.length > 1" class="dots-bar">
<view v-for="(dot, i) in post.imgs" :key="i" class="dot" :class="{ active: i === current }" />
</view>
<!-- 底部文案 -->
<view v-if="(post.copywriting || post.translatedContent) && post.date" class="momentbottom">
<!-- 内容翻译 -->
<text class="copywriting">{{ post.translatedContent || post.copywriting }}</text>
<uni-dateformat :date="Date.parse(post.date.replace(/-/g, '/'))" :threshold="[0, 0]" format="yyyy-MM-dd"
class="date-text" />
</view>
</view>
</view>
<!-- 评论区域 + 互动区域 -->
<Comments :postid="post.id" />
<!-- Findmore -->
<Findmore />
</view>
<view v-else class="loading-container">
<text>ページ読み込み中...</text>
</view>
</template>
<script setup>
import { ref, getCurrentInstance } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useCommonStore } from '@/stores/common.js'
import Head from '@/pages/head/head.vue'
import Comments from '@/pages/comments/comments.vue'
import Findmore from '@/pages/findmore/findmore.vue'
import { getPostList, getPostLImage, getPostVideo, getUserImg } from '../api/api.js'
// 引入翻译工具
import { translateZhToJa } from '@/utils/translate.js';
const common = useCommonStore()
// 当前轮播图索引
const current = ref(0)
const onChange = e => current.value = e.detail.current
// 动态数据 - 初始化翻译相关字段
const post = ref({
translatedContent: '', // 内容翻译结果
translatedUserName: '' // 用户名翻译结果
});
// 播放状态
const isPlaying = ref(true)
// 页面加载状态
const finishloading = ref(false)
// 图片加载处理
const onImageLoad = (index) => {
if (post.value.imgs && post.value.imgs[index]) {
post.value.imgs[index].loading = false
post.value.imgs[index].error = false
}
}
const onImageError = (index) => {
if (post.value.imgs && post.value.imgs[index]) {
post.value.imgs[index].loading = false
post.value.imgs[index].error = true
console.warn(`图片加载失败: index ${index}`)
}
}
// 视频播放/暂停控制
const pausevideo = () => {
const videoCtx = uni.createVideoContext('videoid', getCurrentInstance());
if (videoCtx) {
if (isPlaying.value) {
videoCtx.pause()
isPlaying.value = false
} else {
videoCtx.play()
isPlaying.value = true
}
}
}
// 组件挂载时获取数据并翻译
onLoad(() => {
const params = ''
getPostList(params).then(async (res) => {
try {
const data = res.data.data
// 处理帖子相关字段
data.id = data.id || ''
data.title = data.newsTitle || data.title || ''
data.source = data.newsSource || data.source || ''
data.copywriting = data.textContent || data.newsContent || ''
data.date = data.time || data.date || ''
data.countLike = data.likeCount || data.countLike || 0
data.collectsum = data.collectsum || 0
// 仅翻译后端返回的特定内容
// 1. 翻译用户名
if (data.user && data.user.nickName) {
data.translatedUserName = await translateZhToJa(data.user.nickName);
}
// 2. 翻译内容
if (data.copywriting) {
data.translatedContent = await translateZhToJa(data.copywriting);
}
const mediaPromises = []
// 处理图片资源
if (data.imgs && Array.isArray(data.imgs)) {
data.imgs.forEach(image => {
mediaPromises.push(getPostLImage(image.original_url).then(imageRes => {
if (imageRes.statusCode === 200 && imageRes.data) {
const base64 = uni.arrayBufferToBase64(imageRes.data)
const imageUrl = 'data:image/webp;base64,' + base64
return {
type: 'img',
src: imageUrl,
original_url: image.original_url,
loading: true,
error: false
}
}
return null
}).catch(error => {
uni.showToast({
title: error.message,
icon: 'error'
})
return null
}))
})
}
// 处理视频资源
if (data.videos && Array.isArray(data.videos)) {
data.videos.forEach(video => {
mediaPromises.push(getPostVideo(video.original_url).then(videoRes => {
if (videoRes.statusCode === 200 && videoRes.data) {
const base64 = uni.arrayBufferToBase64(videoRes.data)
const videoUrl = 'data:video/mp4;base64,' + base64
return {
type: 'video',
src: videoUrl,
original_url: video.original_url,
loading: true,
error: false
}
}
return null
}).catch(error => {
uni.showToast({
title: error.message,
icon: 'error'
})
return null
}))
})
}
// 处理用户信息
if (data.user && data.user.constructor === Object) {
const avatarPath = data.user.avatar || data.userImg
if (avatarPath) {
mediaPromises.push(getUserImg(avatarPath).then(userImgRes => {
if (userImgRes.statusCode === 200 && userImgRes.data) {
const base64 = uni.arrayBufferToBase64(userImgRes.data)
const userImgUrl = 'data:image/webp;base64,' + base64
return {
type: 'userImg',
src: userImgUrl,
original_url: avatarPath
}
}
return null
}).catch(error => {
console.warn('用户头像加载失败:', error)
return null
}))
}
data.user = {
nickName: data.user.nickName || '',
userImg: avatarPath || ''
}
}
// 等待所有媒体资源加载完成
if (mediaPromises.length > 0) {
Promise.all(mediaPromises).then(mediaItems => {
const validMediaItems = mediaItems.filter(item => item !== null)
const userImgItem = validMediaItems.find(item => item.type === 'userImg')
const otherMediaItems = validMediaItems.filter(item => item.type !== 'userImg')
if (userImgItem) {
data.user.userImg = userImgItem.src
}
data.imgs = otherMediaItems
Object.assign(post.value, data)
finishloading.value = true
})
} else {
data.imgs = []
post.value = data
finishloading.value = true
}
} catch (error) {
handleError(error)
}
}).catch(error => {
handleError(error)
})
// 错误处理函数
const handleError = (error) => {
console.error('数据处理错误:', error)
post.value = {
user: { nickName: '不明ユーザー', userImg: '' },
translatedUserName: '不明ユーザー',
copywriting: 'コンテンツを読み込めませんでした',
translatedContent: 'コンテンツを読み込めませんでした',
date: new Date().toISOString(),
imgs: []
}
finishloading.value = true
}
})
</script>
<style scoped>
/* 保持原有样式不变 */
page,
.page {
width: 100%;
max-width: 430px;
height: 100vh;
display: flex;
flex-direction: column;
margin: 0 auto;
}
.content {
flex-shrink: 0;
display: flex;
flex-direction: column;
}
.moment {
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
.userbar {
width: 100%;
padding: 16px 16px 8px;
display: flex;
align-items: center;
box-sizing: border-box;
gap: 10px
}
.userimg {
width: 34px;
height: 34px;
border-radius: 50%;
}
.username {
flex: 1;
font-size: 17px;
font-weight: 600;
color: #333
}
.follow {
width: 84px;
height: 32px;
flex-shrink: 0;
font-size: 14px;
font-weight: 600;
line-height: 32px;
border-radius: 8px;
background-color: rgba(51, 51, 51, .05);
display: flex;
align-items: center;
justify-content: center;
}
.follow::after {
border: none;
}
.swiper-banner {
width: 100%;
height: 357px
}
swiper-item {
display: flex;
align-items: center;
justify-content: center
}
.swiper-center {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.swiper-item {
width: 100%;
height: 100%
}
::v-deep .uni-video-cover {
background-color: transparent;
}
::v-deep .uni-video-cover-play-button {
width: 100% !important;
height: 100% !important;
display: flex;
align-items: center;
justify-content: center;
}
.uni-video-cover {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1;
}
.uni-video-icon {
font-family: 'uni-video-icon';
text-align: center;
}
.uni-video-cover-play-button {
width: 100% !important;
height: 100% !important;
display: flex;
align-items: center;
justify-content: center;
line-height: 75px;
font-size: 56px;
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
}
.uni-video-cover-play-button::after {
content: '\ea24';
}
.dots-bar {
display: flex;
justify-content: center;
gap: 6px;
padding: 8px 0 0;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(0, 0, 0, .2);
transition: background .3s
}
.dot.active {
background: #333
}
.momentbottom {
padding: 16px 16px 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.copywriting {
font-size: 15px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
text-align: left;
color: #000;
}
.date-text {
display: flex;
align-items: center;
height: 17px;
line-height: 17px;
font-size: 12px;
color: #b1aeb2;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
</style>