ai-clone/frontend-ai/pages/revival/revival-history.vue

1165 lines
28 KiB
Vue
Raw Normal View History

2026-03-05 14:29:21 +08:00
<template>
<view class="history-container">
<!-- 头部 -->
<view class="header">
<text class="title">我的视频</text>
<text class="subtitle">{{ videos.length }} 个作品</text>
</view>
<!-- 视频列表 -->
<scroll-view scroll-y class="video-list">
<view v-if="loading" class="loading-box">
<text class="loading-text"> 加载中...</text>
</view>
<view v-else-if="videos.length === 0" class="empty-box">
<text class="empty-icon">📹</text>
<text class="empty-text">暂无复活视频</text>
<text class="empty-hint">快去复活你的第一张照片吧</text>
</view>
<view v-else class="video-grid">
<view
v-for="video in videos"
:key="video.id"
class="video-card"
@click="viewVideo(video)"
>
<!-- 封面 -->
<view class="video-cover">
<image
v-if="video.photo_url"
:src="video.photo_url"
class="cover-image"
mode="aspectFill"
></image>
<view v-else class="cover-placeholder">
<text class="placeholder-icon">📹</text>
</view>
<view class="play-overlay">
<text class="play-icon"></text>
</view>
<!-- 缓存状态标识 -->
<view v-if="video.isCached" class="cache-badge">
<text class="cache-text">已缓存</text>
</view>
<!-- AI生成提示标签 -->
<view class="ai-tag">
<text class="ai-tag-text">AI生成</text>
</view>
2026-03-05 14:29:21 +08:00
</view>
<!-- 信息 -->
<view class="video-info">
<text class="video-title">{{ video.name || '复活视频' }}</text>
<text class="video-text" v-if="video.text">{{ video.text }}</text>
<view class="video-meta">
<text class="video-time">{{ formatTime(video.create_time) }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="video-actions">
<view class="action-btn" @click.stop="viewVideo(video)">
<text class="action-icon"></text>
<text class="action-text">播放</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<view class="action-btn" @click.stop="saveVideoToAlbum(video)">
<text class="action-icon">💾</text>
<text class="action-text">保存</text>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button class="action-btn share" open-type="share" @click.stop="prepareShare(video)">
<text class="action-icon">🔗</text>
<text class="action-text">分享</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="action-btn share" @click.stop="shareVideo(video)">
<text class="action-icon">🔗</text>
<text class="action-text">分享</text>
</view>
<!-- #endif -->
<view v-if="isVideoPublishable(video)" class="action-btn publish" @click.stop="togglePublish(video)">
<text class="action-icon">{{ video.published ? '↩️' : '📢' }}</text>
<text class="action-text">{{ video.published ? '下架' : '发布' }}</text>
</view>
<view class="action-btn delete" @click.stop="deleteVideo(video)">
<text class="action-icon">🗑</text>
<text class="action-text">删除</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view v-if="publishDialogVisible" class="publish-mask" @click="closePublishDialog">
<view class="publish-dialog" @click.stop>
<view class="publish-title">发布作品</view>
<input
class="publish-input"
v-model="publishTitle"
placeholder="请输入标题最多20字"
maxlength="20"
/>
<view class="publish-actions">
<view class="publish-btn cancel" @click="closePublishDialog">取消</view>
<view class="publish-btn confirm" @click="confirmPublish">发布</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { API_BASE, API_ENDPOINTS, buildURL } from '@/config/api.js';
export default {
data() {
return {
API_BASE,
videos: [],
loading: false,
enablePrecache: false,
precacheQueue: [],
precaching: false,
precacheInFlight: {},
publishDialogVisible: false,
publishTitle: '',
publishTargetVideo: null,
shareTargetVideo: null
};
},
onLoad() {
this.loadVideos();
},
onShareAppMessage() {
const v = this.shareTargetVideo || {};
const title = (v && (v.name || '').trim()) ? v.name : '我的视频';
const imageUrl = v && v.photo_url ? v.photo_url : undefined;
const videoUrl = v && (v.edited_video_url || v.local_video_path || v.video_url) ? String(v.edited_video_url || v.local_video_path || v.video_url) : '';
const path = '/pages/video-player/video-player?audioInVideo=1&url=' + encodeURIComponent(videoUrl) + '&title=' + encodeURIComponent(title);
return {
title,
path,
imageUrl
};
},
onShareTimeline() {
const v = this.shareTargetVideo || {};
const title = (v && (v.name || '').trim()) ? v.name : '我的视频';
const imageUrl = v && v.photo_url ? v.photo_url : undefined;
const videoUrl = v && (v.edited_video_url || v.local_video_path || v.video_url) ? String(v.edited_video_url || v.local_video_path || v.video_url) : '';
const query = 'url=' + encodeURIComponent(videoUrl) + '&title=' + encodeURIComponent(title);
return {
title,
query,
imageUrl
};
},
methods: {
normalizeMediaUrl(url) {
if (!url) return url;
if (url.startsWith('/')) {
return `${this.API_BASE}${url}`;
}
if (url.startsWith('http://115.190.167.176:20002')) {
return url.replace('http://115.190.167.176:20002', this.API_BASE);
}
return url;
},
ensureLoginOrRedirect() {
const token = uni.getStorageSync('token') || '';
const userId = uni.getStorageSync('userId') || '';
if (token && userId) {
return true;
}
uni.showToast({
title: '请先登录',
icon: 'none'
});
setTimeout(() => {
uni.navigateTo({
url: '/pages/login/login'
});
}, 800);
return false;
},
isVideoPublishable(video) {
if (!video) return false;
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url || video.videoUrl || video.localVideoPath;
return !!videoUrl;
},
openPublishDialog(video) {
this.publishTargetVideo = video;
this.publishTitle = (video && (video.name || '') ? video.name : '复活视频');
this.publishDialogVisible = true;
},
closePublishDialog() {
this.publishDialogVisible = false;
this.publishTitle = '';
this.publishTargetVideo = null;
},
confirmPublish() {
const video = this.publishTargetVideo;
if (!video) {
this.closePublishDialog();
return;
}
const title = (this.publishTitle || '').trim();
if (!title) {
uni.showToast({ title: '请输入标题', icon: 'none' });
return;
}
if (title.length > 20) {
uni.showToast({ title: '标题不能超过20字', icon: 'none' });
return;
}
this.closePublishDialog();
this.publishVideo(video, title);
},
publishVideo(video, title) {
const token = uni.getStorageSync('token') || '';
const userId = uni.getStorageSync('userId') || '';
if (!token || !userId) {
uni.showToast({ title: '请先登录', icon: 'none' });
return;
}
uni.showLoading({ title: '发布中...' });
uni.request({
url: `${this.API_BASE}/api/works/publish/revival-video`,
method: 'POST',
header: {
'Content-Type': 'application/json',
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
data: {
userId: Number(userId),
revivalVideoId: video.id,
title
},
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.success) {
video.published = true;
video.workId = res.data.data && res.data.data.id;
uni.showToast({ title: '发布成功', icon: 'success' });
} else {
uni.showToast({ title: res.data?.message || '发布失败', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '发布失败', icon: 'none' });
},
complete: () => {
uni.hideLoading();
}
});
},
loadVideos() {
if (!this.ensureLoginOrRedirect()) {
this.loading = false;
this.videos = [];
return;
}
this.loading = true;
// 获取用户ID和Token
const userId = uni.getStorageSync('userId') || '';
const token = uni.getStorageSync('token') || '';
uni.request({
url: `${this.API_BASE}/api/photo-revival/videos`,
method: 'GET',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
console.log('[RevivalHistory] API响应:', res.data);
console.log('[RevivalHistory] 用户ID:', userId);
if (res.data.success) {
this.videos = (res.data.videos || []).map(v => ({
...v,
photo_url: this.normalizeMediaUrl(v && v.photo_url ? String(v.photo_url) : ''),
video_url: this.normalizeMediaUrl(v && v.video_url ? String(v.video_url) : ''),
edited_video_url: this.normalizeMediaUrl(v && v.edited_video_url ? String(v.edited_video_url) : ''),
local_video_path: this.normalizeMediaUrl(v && v.local_video_path ? String(v.local_video_path) : ''),
audio_url: this.normalizeMediaUrl(v && v.audio_url ? String(v.audio_url) : ''),
published: false,
workId: null
}));
console.log('[RevivalHistory] 加载视频数量:', this.videos.length);
// 检查每个视频的缓存状态
this.checkVideosCache();
// 查询发布状态
this.loadPublishStatus();
}
},
fail: (err) => {
console.error('[RevivalHistory] 加载失败:', err);
uni.showToast({
title: '加载失败',
icon: 'none'
});
},
complete: () => {
this.loading = false;
}
});
},
loadPublishStatus() {
const token = uni.getStorageSync('token') || '';
const userId = uni.getStorageSync('userId') || '';
this.videos.forEach(video => {
uni.request({
url: `${this.API_BASE}/api/works/source?sourceType=REVIVAL_VIDEO&sourceId=${video.id}`,
method: 'GET',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
if (res.statusCode === 200 && res.data) {
video.published = res.data.published === 1;
video.workId = res.data.id;
}
}
});
});
},
togglePublish(video) {
const token = uni.getStorageSync('token') || '';
const userId = uni.getStorageSync('userId') || '';
if (!token || !userId) {
uni.showToast({ title: '请先登录', icon: 'none' });
return;
}
if (!video.published) {
this.openPublishDialog(video);
return;
}
// 已发布:取消发布
uni.showModal({
title: '取消发布',
content: '确定要从作品广场下架该作品吗?',
success: (m) => {
if (!m.confirm) return;
uni.showLoading({ title: '处理中...' });
uni.request({
url: `${this.API_BASE}/api/works/unpublish?workId=${video.workId}&userId=${userId}`,
method: 'POST',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.success) {
video.published = false;
uni.showToast({ title: '已取消发布', icon: 'success' });
} else {
uni.showToast({ title: res.data?.message || '操作失败', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '操作失败', icon: 'none' });
},
complete: () => {
uni.hideLoading();
}
});
}
});
},
checkVideosCache() {
let scheduled = 0;
this.videos.forEach(video => {
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
if (videoUrl) {
const normalizedUrl = this.normalizeMediaUrl(videoUrl);
const cacheKey = this.generateCacheKey(normalizedUrl);
const cachedPath = uni.getStorageSync(cacheKey);
video.isCached = !!cachedPath;
if (this.enablePrecache && !video.isCached && typeof plus !== 'undefined' && scheduled < 3) {
scheduled++;
this.enqueuePrecache(normalizedUrl);
}
}
});
},
enqueuePrecache(videoUrl) {
if (!videoUrl) return;
if (this.precacheInFlight[videoUrl]) return;
if (this.precacheQueue.indexOf(videoUrl) !== -1) return;
this.precacheQueue.push(videoUrl);
this.processPrecacheQueue();
},
async processPrecacheQueue() {
if (this.precaching) return;
if (!this.precacheQueue.length) return;
this.precaching = true;
try {
while (this.precacheQueue.length) {
const url = this.precacheQueue.shift();
if (!url) continue;
if (this.precacheInFlight[url]) continue;
this.precacheInFlight[url] = true;
try {
await this.precacheVideoApp(url);
} catch (e) {
} finally {
delete this.precacheInFlight[url];
}
}
} finally {
this.precaching = false;
}
},
precacheVideoApp(videoUrl) {
return new Promise((resolve, reject) => {
if (typeof plus === 'undefined') {
resolve();
return;
}
const cacheKey = this.generateCacheKey(videoUrl);
const cachedPath = uni.getStorageSync(cacheKey);
if (cachedPath) {
resolve();
return;
}
uni.downloadFile({
url: videoUrl,
success: (res) => {
if (res.statusCode !== 200) {
reject(new Error('download failed'));
return;
}
uni.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) => {
const savedPath = saveRes.savedFilePath;
uni.setStorageSync(cacheKey, savedPath);
const cacheInfo = uni.getStorageSync('video_cache_info') || {};
cacheInfo[cacheKey] = { path: savedPath, url: videoUrl, time: Date.now() };
uni.setStorageSync('video_cache_info', cacheInfo);
resolve(savedPath);
},
fail: (err) => {
reject(err);
}
});
},
fail: (err) => {
reject(err);
}
});
});
},
generateCacheKey(url) {
let hash = 0;
for (let i = 0; i < url.length; i++) {
const char = url.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return 'video_' + Math.abs(hash);
},
viewVideo(video) {
console.log('[RevivalHistory] 点击视频:', video);
// 使用本地保存的视频路径如果没有则使用远程URL
const localPathStr = video && video.local_video_path ? String(video.local_video_path) : '';
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
const videoUrlStr = videoUrl ? String(videoUrl) : '';
const usedEdited = !!video.edited_video_url
|| (localPathStr.indexOf('/static/videos/revival_') !== -1)
|| (videoUrlStr.indexOf('/static/videos/revival_') !== -1);
console.log('[RevivalHistory] 视频URL:', videoUrl);
console.log('[RevivalHistory] usedEdited判定:', usedEdited, 'localPathStr=', localPathStr, 'videoUrlStr=', videoUrlStr);
if (!videoUrl) {
uni.showToast({
title: '视频地址不存在',
icon: 'none'
});
return;
}
const normalizedUrl = this.normalizeMediaUrl(videoUrl);
if (this.enablePrecache && typeof plus !== 'undefined') {
this.enqueuePrecache(normalizedUrl);
}
let targetUrl = `/pages/video-player/video-player?audioInVideo=1&id=${encodeURIComponent(video.id)}&url=${encodeURIComponent(videoUrl)}&title=${encodeURIComponent(video.name || '复活视频')}`;
// 不传入外部音频参数,播放器只播放视频本身,不做额外音频处理
console.log('[RevivalHistory] 跳转URL:', targetUrl);
// 跳转到视频播放页面
uni.navigateTo({
url: targetUrl,
success: () => {
console.log('[RevivalHistory] 跳转成功');
},
fail: (err) => {
console.error('[RevivalHistory] 跳转失败:', err);
uni.showToast({
title: '跳转失败: ' + err.errMsg,
icon: 'none'
});
}
});
},
playVideo(video) {
const localPathStr = video && video.local_video_path ? String(video.local_video_path) : '';
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
const videoUrlStr = videoUrl ? String(videoUrl) : '';
const usedEdited = !!video.edited_video_url
|| (localPathStr.indexOf('/static/videos/revival_') !== -1)
|| (videoUrlStr.indexOf('/static/videos/revival_') !== -1);
if (!videoUrl) {
uni.showToast({
title: '视频地址不存在',
icon: 'none'
});
return;
}
let targetUrl = `/pages/video-player/video-player?audioInVideo=1&url=${encodeURIComponent(videoUrl)}&title=${encodeURIComponent(video.name || '复活视频')}`;
// 不传入外部音频参数,播放器只播放视频本身,不做额外音频处理
uni.navigateTo({
url: targetUrl,
fail: (err) => {
console.error('[RevivalHistory] 跳转失败:', err);
uni.showToast({
title: '播放失败,请稍后重试',
icon: 'none'
});
}
});
},
prepareShare(video) {
this.shareTargetVideo = video || null;
},
shareVideo(video) {
console.log('[RevivalHistory] 分享视频:', video);
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
if (!videoUrl) {
uni.showToast({
title: '视频地址不存在',
icon: 'none'
});
return;
}
uni.showActionSheet({
itemList: ['保存到相册', '复制链接'],
success: (res) => {
if (res.tapIndex === 0) {
// 保存到相册 - 传入完整video对象以获取音频URL
this.saveVideoToAlbum(video);
} else if (res.tapIndex === 1) {
// 复制链接 - 如果有音频,先合成再复制
this.copyVideoLink(video);
}
}
});
},
// 复制视频链接(如果有音频则先合成)
async copyVideoLink(video) {
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
if (!videoUrl) {
uni.showToast({ title: '视频地址不存在', icon: 'none' });
return;
}
uni.setClipboardData({
data: videoUrl,
success: () => {
uni.showToast({
title: '链接已复制',
icon: 'success'
});
}
});
},
async saveVideoToAlbum(video) {
const videoUrl = video.edited_video_url || video.local_video_path || video.video_url;
console.log('[RevivalHistory] 保存视频:', videoUrl);
this.saveVideoOnly(videoUrl);
},
// 仅保存视频(不含音频)
saveVideoOnly(videoUrl) {
uni.showLoading({
title: '保存中...'
});
// 如果是本地路径,直接保存
if (videoUrl.startsWith('file://') || videoUrl.startsWith('wxfile://')) {
uni.saveVideoToPhotosAlbum({
filePath: videoUrl,
success: () => {
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
uni.hideLoading();
console.error('[RevivalHistory] 保存失败:', err);
if (err.errMsg.includes('auth')) {
uni.showModal({
title: '需要授权',
content: '请授权保存到相册',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
}
});
} else {
// 远程URL先下载再保存
uni.downloadFile({
url: videoUrl,
success: (res) => {
if (res.statusCode === 200) {
uni.saveVideoToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
uni.hideLoading();
uni.showToast({
title: '保存成功',
icon: 'success'
});
},
fail: (err) => {
uni.hideLoading();
console.error('[RevivalHistory] 保存失败:', err);
if (err.errMsg.includes('auth')) {
uni.showModal({
title: '需要授权',
content: '请授权保存到相册',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
}
});
} else {
uni.hideLoading();
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
},
fail: (err) => {
uni.hideLoading();
console.error('[RevivalHistory] 下载失败:', err);
uni.showToast({
title: '下载失败',
icon: 'none'
});
}
});
}
},
async deleteVideo(video) {
uni.showModal({
title: '确认删除',
content: `确定要删除视频"${video.name || '复活视频'}"吗?`,
success: (res) => {
if (res.confirm) {
const userId = uni.getStorageSync('userId') || '';
const token = uni.getStorageSync('token') || '';
uni.request({
url: `${API_BASE}/api/photo-revival/videos/${video.id}`,
method: 'DELETE',
header: {
'X-User-Id': userId,
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.success) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
// 刷新列表
this.loadVideos();
} else {
uni.showToast({
title: res.data?.message || '删除失败',
icon: 'none'
});
}
},
fail: (err) => {
uni.showToast({
title: '删除失败,请重试',
icon: 'none'
});
}
});
}
}
});
},
formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
},
formatDuration(seconds) {
if (!seconds) return '';
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${String(secs).padStart(2, '0')}`;
}
}
};
</script>
<style lang="scss" scoped>
.history-container {
min-height: 100vh;
background: linear-gradient(180deg, #FDF8F2 0%, #F5EDE3 100%);
display: flex;
flex-direction: column;
}
// 头部
.header {
padding: 48upx 32upx 32upx 40upx;
background: transparent;
.title {
font-size: 52upx;
font-weight: 700;
color: #8B7355;
display: block;
margin-bottom: 12upx;
letter-spacing: 3upx;
text-shadow: 0 2upx 4upx rgba(139, 115, 85, 0.1);
}
.subtitle {
font-size: 26upx;
color: #B8A898;
display: inline-block;
background: rgba(139, 115, 85, 0.08);
padding: 6upx 16upx;
border-radius: 20upx;
}
}
.video-list {
flex: 1;
padding: 0 24upx;
box-sizing: border-box;
}
.loading-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 160rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #8B7355;
margin-top: 16rpx;
}
.empty-box {
display: flex;
flex-direction: column;
align-items: center;
padding: 180upx 40upx;
}
.empty-icon {
font-size: 140upx;
margin-bottom: 40upx;
filter: grayscale(20%);
}
.empty-text {
font-size: 34upx;
color: #8B7355;
margin-bottom: 16upx;
font-weight: 600;
}
.empty-hint {
font-size: 26upx;
color: #B8A898;
text-align: center;
line-height: 1.6;
background: rgba(139, 115, 85, 0.06);
padding: 16upx 32upx;
border-radius: 24upx;
}
.video-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20upx;
padding: 0 0 40upx 0;
box-sizing: border-box;
}
.video-card {
background: white;
border-radius: 28upx;
overflow: hidden;
box-shadow: 0 8upx 32upx rgba(139, 115, 85, 0.12);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
width: 100%;
box-sizing: border-box;
border: 1upx solid rgba(139, 115, 85, 0.08);
&:active {
transform: translateY(-6upx) scale(1.01);
box-shadow: 0 16upx 40upx rgba(139, 115, 85, 0.2);
}
}
.video-cover {
position: relative;
width: 100%;
padding-top: 150%;
overflow: hidden;
background: linear-gradient(135deg, #F8F4F0 0%, #EDE6DD 100%);
}
.cover-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.video-card:active .cover-image {
transform: scale(1.02);
}
.play-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 88upx;
height: 88upx;
background: rgba(255, 255, 255, 0.95);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8upx 24upx rgba(0, 0, 0, 0.2);
}
.play-icon {
font-size: 36upx;
color: #8B7355;
padding-left: 6upx;
}
.cover-placeholder {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.placeholder-icon {
font-size: 60upx;
color: #ccc;
}
.video-duration {
position: absolute;
bottom: 8upx;
right: 8upx;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 4upx 10upx;
border-radius: 6upx;
font-size: 20upx;
}
.cache-badge {
position: absolute;
top: 12upx;
right: 12upx;
background: linear-gradient(135deg, #8B7355 0%, #A68B5B 100%);
padding: 6upx 12upx;
border-radius: 20upx;
box-shadow: 0 2upx 8upx rgba(139, 115, 85, 0.4);
}
.cache-text {
color: white;
font-size: 18upx;
font-weight: 500;
}
/* AI生成提示标签 */
.ai-tag {
position: absolute;
top: 12upx;
left: 12upx;
padding: 6upx 16upx;
background: rgba(255, 165, 0, 0.85);
color: white;
font-size: 20upx;
border-radius: 20upx;
font-weight: 500;
z-index: 10;
}
.ai-tag-text {
font-size: 20upx;
font-weight: 500;
}
2026-03-05 14:29:21 +08:00
.video-info {
padding: 20upx 16upx 16upx;
background: linear-gradient(180deg, #FFFFFF 0%, #FDFBF9 100%);
}
.video-title {
font-size: 28upx;
font-weight: 600;
color: #5A4A3A;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 8upx;
line-height: 1.4;
}
.video-text {
font-size: 22upx;
color: #999;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 8upx;
}
.video-meta {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8upx;
}
.video-time {
font-size: 20upx;
color: #B8A898;
background: rgba(139, 115, 85, 0.06);
padding: 4upx 10upx;
border-radius: 8upx;
}
.video-status {
font-size: 20upx;
color: #8B7355;
background: rgba(139, 115, 85, 0.1);
padding: 2upx 8upx;
border-radius: 6upx;
}
.video-actions {
display: flex;
border-top: 1upx solid rgba(139, 115, 85, 0.1);
background: #FDFBF9;
}
.action-btn {
flex: 1;
padding: 14upx 4upx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6upx;
border-right: 1upx solid rgba(139, 115, 85, 0.1);
transition: all 0.2s ease;
background: transparent;
border: none;
border-radius: 0;
line-height: normal;
&::after {
border: none;
}
&:last-child {
border-right: none;
}
&:active {
background: rgba(139, 115, 85, 0.08);
transform: scale(0.96);
}
&.delete:active {
background: rgba(220, 80, 80, 0.08);
}
&.share:active {
background: rgba(139, 115, 85, 0.1);
}
&.publish:active {
background: rgba(100, 180, 100, 0.1);
}
}
.action-icon {
font-size: 26upx;
}
.action-text {
font-size: 18upx;
color: #8B7355;
font-weight: 500;
}
.publish-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;
justify-content: center;
padding: 60upx;
z-index: 9999;
}
.publish-dialog {
width: 100%;
max-width: 650upx;
background: #fff;
border-radius: 28upx;
padding: 36upx;
box-shadow: 0 20upx 60upx rgba(0, 0, 0, 0.2);
}
.publish-title {
font-size: 32upx;
font-weight: 700;
color: #333;
margin-bottom: 24upx;
}
.publish-input {
width: 100%;
background: #f7f2ea;
border-radius: 20upx;
min-height: 96upx;
padding: 28upx 28upx;
font-size: 30upx;
color: #333;
box-sizing: border-box;
}
.publish-actions {
display: flex;
gap: 20upx;
margin-top: 28upx;
}
.publish-btn {
flex: 1;
text-align: center;
padding: 22upx 0;
border-radius: 22upx;
font-size: 28upx;
font-weight: 600;
}
.publish-btn.cancel {
background: #f0f0f0;
color: #333;
}
.publish-btn.confirm {
background: linear-gradient(135deg, #8B7355 0%, #6D8B8B 100%);
color: #fff;
}
</style>