Initial Commit
This commit is contained in:
commit
1d71a02738
237 changed files with 64293 additions and 0 deletions
555
components/media-preview/media-preview.js
Normal file
555
components/media-preview/media-preview.js
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
// 🎨 媒体预览组件逻辑
|
||||
const mediaManager = require('../../utils/media-manager.js');
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
// 是否显示预览
|
||||
visible: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
|
||||
// 媒体列表
|
||||
mediaList: {
|
||||
type: Array,
|
||||
value: []
|
||||
},
|
||||
|
||||
// 当前索引
|
||||
currentIndex: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
|
||||
// 是否可以分享
|
||||
canShare: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
|
||||
// 是否可以编辑
|
||||
canEdit: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
|
||||
// 是否可以删除
|
||||
canDelete: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
|
||||
// 是否显示底部操作栏
|
||||
showFooter: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
|
||||
// 是否显示手势提示
|
||||
showGestureTips: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
}
|
||||
},
|
||||
|
||||
data: {
|
||||
// 当前媒体
|
||||
currentMedia: {},
|
||||
|
||||
// 音频播放状态
|
||||
audioPlaying: false,
|
||||
audioProgress: 0,
|
||||
audioCurrentTime: 0,
|
||||
|
||||
// 手势提示定时器
|
||||
gestureTimer: null
|
||||
},
|
||||
|
||||
observers: {
|
||||
'mediaList, currentIndex': function(mediaList, currentIndex) {
|
||||
if (mediaList && mediaList.length > 0 && currentIndex >= 0 && currentIndex < mediaList.length) {
|
||||
this.setData({
|
||||
currentMedia: mediaList[currentIndex]
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
lifetimes: {
|
||||
attached() {
|
||||
console.log('🎨 媒体预览组件已加载');
|
||||
},
|
||||
|
||||
detached() {
|
||||
console.log('🎨 媒体预览组件已卸载');
|
||||
this.cleanup();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 🎨 ===== 基础操作 =====
|
||||
|
||||
// 阻止事件冒泡
|
||||
stopPropagation() {
|
||||
// 阻止点击事件冒泡到遮罩层
|
||||
},
|
||||
|
||||
// 遮罩点击
|
||||
onMaskTap() {
|
||||
this.closePreview();
|
||||
},
|
||||
|
||||
// 关闭预览
|
||||
closePreview() {
|
||||
this.setData({
|
||||
visible: false
|
||||
});
|
||||
|
||||
this.triggerEvent('close');
|
||||
this.cleanup();
|
||||
},
|
||||
|
||||
// 清理资源
|
||||
cleanup() {
|
||||
// 停止音频播放
|
||||
if (this.data.audioPlaying) {
|
||||
this.stopAudio();
|
||||
}
|
||||
|
||||
// 清理定时器
|
||||
if (this.data.gestureTimer) {
|
||||
clearTimeout(this.data.gestureTimer);
|
||||
}
|
||||
},
|
||||
|
||||
// 🎨 ===== 图片操作 =====
|
||||
|
||||
// 轮播图切换
|
||||
onSwiperChange(e) {
|
||||
const currentIndex = e.detail.current;
|
||||
this.setData({
|
||||
currentIndex: currentIndex
|
||||
});
|
||||
|
||||
this.triggerEvent('indexchange', {
|
||||
currentIndex: currentIndex
|
||||
});
|
||||
},
|
||||
|
||||
// 图片加载完成
|
||||
onImageLoad(e) {
|
||||
console.log('🖼️ 图片加载完成');
|
||||
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const mediaList = this.data.mediaList;
|
||||
|
||||
if (mediaList[index]) {
|
||||
mediaList[index].loading = false;
|
||||
mediaList[index].error = false;
|
||||
|
||||
this.setData({
|
||||
mediaList: mediaList
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 图片加载失败
|
||||
onImageError(e) {
|
||||
console.error('❌ 图片加载失败');
|
||||
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const mediaList = this.data.mediaList;
|
||||
|
||||
if (mediaList[index]) {
|
||||
mediaList[index].loading = false;
|
||||
mediaList[index].error = true;
|
||||
|
||||
this.setData({
|
||||
mediaList: mediaList
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 图片点击
|
||||
onImageTap(e) {
|
||||
// 可以实现双击放大等功能
|
||||
console.log('🖼️ 图片点击');
|
||||
},
|
||||
|
||||
// 重试加载
|
||||
retryLoad(e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const mediaList = this.data.mediaList;
|
||||
|
||||
if (mediaList[index]) {
|
||||
mediaList[index].loading = true;
|
||||
mediaList[index].error = false;
|
||||
|
||||
this.setData({
|
||||
mediaList: mediaList
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 🎨 ===== 视频操作 =====
|
||||
|
||||
// 视频播放
|
||||
onVideoPlay() {
|
||||
console.log('🎬 视频开始播放');
|
||||
this.triggerEvent('videoplay');
|
||||
},
|
||||
|
||||
// 视频暂停
|
||||
onVideoPause() {
|
||||
console.log('🎬 视频暂停');
|
||||
this.triggerEvent('videopause');
|
||||
},
|
||||
|
||||
// 视频结束
|
||||
onVideoEnded() {
|
||||
console.log('🎬 视频播放结束');
|
||||
this.triggerEvent('videoended');
|
||||
},
|
||||
|
||||
// 视频错误
|
||||
onVideoError(e) {
|
||||
console.error('❌ 视频播放错误:', e.detail);
|
||||
wx.showToast({
|
||||
title: '视频播放失败',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// 视频时间更新
|
||||
onVideoTimeUpdate(e) {
|
||||
// 可以用于显示播放进度
|
||||
console.log('🎬 视频时间更新:', e.detail);
|
||||
},
|
||||
|
||||
// 🎨 ===== 音频操作 =====
|
||||
|
||||
// 切换音频播放
|
||||
toggleAudioPlay() {
|
||||
if (this.data.audioPlaying) {
|
||||
this.pauseAudio();
|
||||
} else {
|
||||
this.playAudio();
|
||||
}
|
||||
},
|
||||
|
||||
// 播放音频
|
||||
playAudio() {
|
||||
// 这里需要实现音频播放逻辑
|
||||
console.log('🎵 播放音频');
|
||||
|
||||
this.setData({
|
||||
audioPlaying: true
|
||||
});
|
||||
|
||||
// 模拟播放进度
|
||||
this.startAudioProgress();
|
||||
},
|
||||
|
||||
// 暂停音频
|
||||
pauseAudio() {
|
||||
console.log('🎵 暂停音频');
|
||||
|
||||
this.setData({
|
||||
audioPlaying: false
|
||||
});
|
||||
|
||||
this.stopAudioProgress();
|
||||
},
|
||||
|
||||
// 停止音频
|
||||
stopAudio() {
|
||||
console.log('🎵 停止音频');
|
||||
|
||||
this.setData({
|
||||
audioPlaying: false,
|
||||
audioProgress: 0,
|
||||
audioCurrentTime: 0
|
||||
});
|
||||
|
||||
this.stopAudioProgress();
|
||||
},
|
||||
|
||||
// 开始音频进度更新
|
||||
startAudioProgress() {
|
||||
this.audioProgressTimer = setInterval(() => {
|
||||
const currentTime = this.data.audioCurrentTime + 1;
|
||||
const duration = this.data.currentMedia.duration || 100;
|
||||
const progress = (currentTime / duration) * 100;
|
||||
|
||||
this.setData({
|
||||
audioCurrentTime: currentTime,
|
||||
audioProgress: Math.min(progress, 100)
|
||||
});
|
||||
|
||||
if (progress >= 100) {
|
||||
this.stopAudio();
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 停止音频进度更新
|
||||
stopAudioProgress() {
|
||||
if (this.audioProgressTimer) {
|
||||
clearInterval(this.audioProgressTimer);
|
||||
this.audioProgressTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 🎨 ===== 文件操作 =====
|
||||
|
||||
// 打开文件
|
||||
openFile() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
wx.openDocument({
|
||||
filePath: currentMedia.tempFilePath || currentMedia.url,
|
||||
fileType: currentMedia.extension,
|
||||
success: () => {
|
||||
console.log('📄 文件打开成功');
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('❌ 文件打开失败:', error);
|
||||
wx.showToast({
|
||||
title: '无法打开此文件',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 保存文件
|
||||
async saveFile() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
try {
|
||||
wx.showLoading({
|
||||
title: '保存中...'
|
||||
});
|
||||
|
||||
// 如果是网络文件,先下载
|
||||
let filePath = currentMedia.tempFilePath;
|
||||
if (!filePath && currentMedia.url) {
|
||||
const downloadResult = await mediaManager.downloadFile(currentMedia.url);
|
||||
if (downloadResult.success) {
|
||||
filePath = downloadResult.tempFilePath;
|
||||
} else {
|
||||
throw new Error('下载失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到本地
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
wx.saveFile({
|
||||
tempFilePath: filePath,
|
||||
success: resolve,
|
||||
fail: reject
|
||||
});
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
wx.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
console.log('📄 文件保存成功:', result.savedFilePath);
|
||||
|
||||
} catch (error) {
|
||||
wx.hideLoading();
|
||||
console.error('❌ 文件保存失败:', error);
|
||||
wx.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 🎨 ===== 操作按钮 =====
|
||||
|
||||
// 下载媒体
|
||||
async downloadMedia() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
if (!currentMedia.url) {
|
||||
wx.showToast({
|
||||
title: '无法下载',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
wx.showLoading({
|
||||
title: '下载中...'
|
||||
});
|
||||
|
||||
const result = await mediaManager.downloadFile(currentMedia.url, {
|
||||
fileName: currentMedia.name
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
|
||||
if (result.success) {
|
||||
wx.showToast({
|
||||
title: '下载完成',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
this.triggerEvent('download', {
|
||||
media: currentMedia,
|
||||
filePath: result.tempFilePath
|
||||
});
|
||||
} else {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
wx.hideLoading();
|
||||
console.error('❌ 下载失败:', error);
|
||||
wx.showToast({
|
||||
title: '下载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 分享媒体
|
||||
shareMedia() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
this.triggerEvent('share', {
|
||||
media: currentMedia
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑媒体
|
||||
editMedia() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
this.triggerEvent('edit', {
|
||||
media: currentMedia,
|
||||
index: this.data.currentIndex
|
||||
});
|
||||
},
|
||||
|
||||
// 删除媒体
|
||||
deleteMedia() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
|
||||
wx.showModal({
|
||||
title: '删除确认',
|
||||
content: '确定要删除这个文件吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.triggerEvent('delete', {
|
||||
media: currentMedia,
|
||||
index: this.data.currentIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 收藏媒体
|
||||
favoriteMedia() {
|
||||
const currentMedia = this.data.currentMedia;
|
||||
const favorited = !currentMedia.favorited;
|
||||
|
||||
// 更新收藏状态
|
||||
currentMedia.favorited = favorited;
|
||||
this.setData({
|
||||
currentMedia: currentMedia
|
||||
});
|
||||
|
||||
this.triggerEvent('favorite', {
|
||||
media: currentMedia,
|
||||
favorited: favorited
|
||||
});
|
||||
|
||||
wx.showToast({
|
||||
title: favorited ? '已收藏' : '已取消收藏',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
// 显示更多操作
|
||||
showMoreActions() {
|
||||
const actions = ['转发', '设为壁纸', '添加到相册', '举报'];
|
||||
|
||||
wx.showActionSheet({
|
||||
itemList: actions,
|
||||
success: (res) => {
|
||||
this.triggerEvent('moreaction', {
|
||||
action: actions[res.tapIndex],
|
||||
media: this.data.currentMedia
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 🎨 ===== 工具方法 =====
|
||||
|
||||
// 格式化文件大小
|
||||
formatFileSize(size) {
|
||||
if (!size) return '未知大小';
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let unitIndex = 0;
|
||||
let fileSize = size;
|
||||
|
||||
while (fileSize >= 1024 && unitIndex < units.length - 1) {
|
||||
fileSize /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${fileSize.toFixed(1)} ${units[unitIndex]}`;
|
||||
},
|
||||
|
||||
// 格式化时长
|
||||
formatDuration(duration) {
|
||||
if (!duration) return '00:00';
|
||||
|
||||
const minutes = Math.floor(duration / 60);
|
||||
const seconds = Math.floor(duration % 60);
|
||||
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(time) {
|
||||
if (!time) return '00:00';
|
||||
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60);
|
||||
|
||||
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
},
|
||||
|
||||
// 获取文件图标
|
||||
getFileIcon(extension) {
|
||||
const iconMap = {
|
||||
'pdf': '📄',
|
||||
'doc': '📝',
|
||||
'docx': '📝',
|
||||
'xls': '📊',
|
||||
'xlsx': '📊',
|
||||
'ppt': '📽️',
|
||||
'pptx': '📽️',
|
||||
'txt': '📃',
|
||||
'zip': '🗜️',
|
||||
'rar': '🗜️',
|
||||
'mp3': '🎵',
|
||||
'wav': '🎵',
|
||||
'mp4': '🎬',
|
||||
'avi': '🎬'
|
||||
};
|
||||
|
||||
return iconMap[extension] || '📄';
|
||||
}
|
||||
}
|
||||
});
|
||||
4
components/media-preview/media-preview.json
Normal file
4
components/media-preview/media-preview.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
192
components/media-preview/media-preview.wxml
Normal file
192
components/media-preview/media-preview.wxml
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
<!-- 🎨 媒体预览组件 -->
|
||||
<view class="media-preview-container" wx:if="{{visible}}" bindtap="onMaskTap">
|
||||
<!-- 背景遮罩 -->
|
||||
<view class="preview-mask"></view>
|
||||
|
||||
<!-- 预览内容 -->
|
||||
<view class="preview-content" catchtap="stopPropagation">
|
||||
<!-- 头部工具栏 -->
|
||||
<view class="preview-header">
|
||||
<view class="header-info">
|
||||
<text class="media-title">{{currentMedia.name || '媒体预览'}}</text>
|
||||
<text class="media-info" wx:if="{{currentMedia.size}}">
|
||||
{{formatFileSize(currentMedia.size)}}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="header-actions">
|
||||
<!-- 下载按钮 -->
|
||||
<view class="action-btn" bindtap="downloadMedia" wx:if="{{currentMedia.url}}">
|
||||
<text class="action-icon">📥</text>
|
||||
</view>
|
||||
|
||||
<!-- 分享按钮 -->
|
||||
<view class="action-btn" bindtap="shareMedia" wx:if="{{canShare}}">
|
||||
<text class="action-icon">📤</text>
|
||||
</view>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="action-btn close-btn" bindtap="closePreview">
|
||||
<text class="action-icon">✕</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 媒体内容区域 -->
|
||||
<view class="media-container">
|
||||
<!-- 图片预览 -->
|
||||
<view class="image-preview" wx:if="{{currentMedia.type === 'image'}}">
|
||||
<swiper class="image-swiper"
|
||||
current="{{currentIndex}}"
|
||||
bindchange="onSwiperChange"
|
||||
indicator-dots="{{mediaList.length > 1}}"
|
||||
indicator-color="rgba(255, 255, 255, 0.3)"
|
||||
indicator-active-color="rgba(255, 255, 255, 0.8)">
|
||||
<swiper-item wx:for="{{mediaList}}" wx:key="index" wx:if="{{item.type === 'image'}}">
|
||||
<view class="image-item">
|
||||
<image class="preview-image"
|
||||
src="{{item.url || item.tempFilePath}}"
|
||||
mode="aspectFit"
|
||||
bindload="onImageLoad"
|
||||
binderror="onImageError"
|
||||
bindtap="onImageTap"
|
||||
data-index="{{index}}" />
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-overlay" wx:if="{{item.loading}}">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<view class="error-overlay" wx:if="{{item.error}}">
|
||||
<text class="error-icon">❌</text>
|
||||
<text class="error-text">加载失败</text>
|
||||
<view class="retry-btn" bindtap="retryLoad" data-index="{{index}}">
|
||||
<text class="retry-text">重试</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 图片计数 -->
|
||||
<view class="image-counter" wx:if="{{mediaList.length > 1}}">
|
||||
<text class="counter-text">{{currentIndex + 1}} / {{mediaList.length}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<view class="video-preview" wx:if="{{currentMedia.type === 'video'}}">
|
||||
<video class="preview-video"
|
||||
src="{{currentMedia.url || currentMedia.tempFilePath}}"
|
||||
poster="{{currentMedia.thumbnailPath}}"
|
||||
controls="{{true}}"
|
||||
autoplay="{{false}}"
|
||||
loop="{{false}}"
|
||||
muted="{{false}}"
|
||||
show-center-play-btn="{{true}}"
|
||||
show-play-btn="{{true}}"
|
||||
show-fullscreen-btn="{{true}}"
|
||||
bindplay="onVideoPlay"
|
||||
bindpause="onVideoPause"
|
||||
bindended="onVideoEnded"
|
||||
binderror="onVideoError"
|
||||
bindtimeupdate="onVideoTimeUpdate">
|
||||
</video>
|
||||
|
||||
<!-- 视频信息 -->
|
||||
<view class="video-info">
|
||||
<text class="video-duration">{{formatDuration(currentMedia.duration)}}</text>
|
||||
<text class="video-size">{{currentMedia.width}}×{{currentMedia.height}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 文件预览 -->
|
||||
<view class="file-preview" wx:if="{{currentMedia.type === 'file'}}">
|
||||
<view class="file-icon-container">
|
||||
<text class="file-icon">{{getFileIcon(currentMedia.extension)}}</text>
|
||||
</view>
|
||||
|
||||
<view class="file-details">
|
||||
<text class="file-name">{{currentMedia.name}}</text>
|
||||
<text class="file-size">{{formatFileSize(currentMedia.size)}}</text>
|
||||
<text class="file-type">{{currentMedia.extension.toUpperCase()}} 文件</text>
|
||||
</view>
|
||||
|
||||
<view class="file-actions">
|
||||
<view class="file-action-btn" bindtap="openFile">
|
||||
<text class="action-text">打开文件</text>
|
||||
</view>
|
||||
<view class="file-action-btn" bindtap="saveFile">
|
||||
<text class="action-text">保存到本地</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 音频预览 -->
|
||||
<view class="audio-preview" wx:if="{{currentMedia.type === 'audio'}}">
|
||||
<view class="audio-player">
|
||||
<view class="audio-cover">
|
||||
<text class="audio-icon">🎵</text>
|
||||
</view>
|
||||
|
||||
<view class="audio-controls">
|
||||
<view class="play-btn {{audioPlaying ? 'playing' : ''}}" bindtap="toggleAudioPlay">
|
||||
<text class="play-icon">{{audioPlaying ? '⏸️' : '▶️'}}</text>
|
||||
</view>
|
||||
|
||||
<view class="audio-progress">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{audioProgress}}%"></view>
|
||||
</view>
|
||||
<view class="time-info">
|
||||
<text class="current-time">{{formatTime(audioCurrentTime)}}</text>
|
||||
<text class="total-time">{{formatTime(currentMedia.duration)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="audio-info">
|
||||
<text class="audio-name">{{currentMedia.name}}</text>
|
||||
<text class="audio-size">{{formatFileSize(currentMedia.size)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="preview-footer" wx:if="{{showFooter}}">
|
||||
<view class="footer-actions">
|
||||
<!-- 编辑按钮 -->
|
||||
<view class="footer-btn" bindtap="editMedia" wx:if="{{canEdit}}">
|
||||
<text class="footer-icon">✏️</text>
|
||||
<text class="footer-text">编辑</text>
|
||||
</view>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<view class="footer-btn" bindtap="deleteMedia" wx:if="{{canDelete}}">
|
||||
<text class="footer-icon">🗑️</text>
|
||||
<text class="footer-text">删除</text>
|
||||
</view>
|
||||
|
||||
<!-- 收藏按钮 -->
|
||||
<view class="footer-btn" bindtap="favoriteMedia">
|
||||
<text class="footer-icon">{{currentMedia.favorited ? '❤️' : '🤍'}}</text>
|
||||
<text class="footer-text">{{currentMedia.favorited ? '已收藏' : '收藏'}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 更多按钮 -->
|
||||
<view class="footer-btn" bindtap="showMoreActions">
|
||||
<text class="footer-icon">⋯</text>
|
||||
<text class="footer-text">更多</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手势操作提示 -->
|
||||
<view class="gesture-tips" wx:if="{{showGestureTips}}">
|
||||
<text class="tips-text">双击放大 · 滑动切换 · 点击关闭</text>
|
||||
</view>
|
||||
</view>
|
||||
586
components/media-preview/media-preview.wxss
Normal file
586
components/media-preview/media-preview.wxss
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
/* 🎨 媒体预览组件样式 */
|
||||
|
||||
/* CSS变量定义 */
|
||||
.media-preview-container {
|
||||
--preview-bg: rgba(0, 0, 0, 0.9);
|
||||
--header-bg: rgba(0, 0, 0, 0.7);
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||
--button-bg: rgba(255, 255, 255, 0.1);
|
||||
--button-active: rgba(255, 255, 255, 0.2);
|
||||
--border-color: rgba(255, 255, 255, 0.2);
|
||||
--shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* 🎨 预览容器 */
|
||||
.media-preview-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--preview-bg);
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 🎨 背景遮罩 */
|
||||
.preview-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 🎨 预览内容 */
|
||||
.preview-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 🎨 头部工具栏 */
|
||||
.preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
background: var(--header-bg);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-bottom: 1rpx solid var(--border-color);
|
||||
}
|
||||
|
||||
.header-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.media-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.media-info {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
background: var(--button-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
background: var(--button-active);
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.action-btn.close-btn {
|
||||
background: rgba(255, 59, 48, 0.8);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 32rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 🎨 媒体容器 */
|
||||
.media-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 🎨 图片预览 */
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.image-counter {
|
||||
position: absolute;
|
||||
bottom: 40rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 12rpx 24rpx;
|
||||
background: var(--header-bg);
|
||||
border-radius: 24rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
.counter-text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 🎨 视频预览 */
|
||||
.video-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
width: 100%;
|
||||
max-height: 80%;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.video-info {
|
||||
position: absolute;
|
||||
bottom: 40rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
background: var(--header-bg);
|
||||
border-radius: 24rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
.video-duration,
|
||||
.video-size {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 🎨 文件预览 */
|
||||
.file-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-icon-container {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 32rpx;
|
||||
background: var(--button-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
border: 2rpx solid var(--border-color);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 120rpx;
|
||||
}
|
||||
|
||||
.file-details {
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16rpx;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.file-size,
|
||||
.file-type {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.file-action-btn {
|
||||
padding: 24rpx 48rpx;
|
||||
background: var(--button-bg);
|
||||
border-radius: 32rpx;
|
||||
border: 1rpx solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(20rpx);
|
||||
}
|
||||
|
||||
.file-action-btn:active {
|
||||
background: var(--button-active);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 30rpx;
|
||||
color: var(--text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 🎨 音频预览 */
|
||||
.audio-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 40rpx;
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 40rpx;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.audio-cover {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 100rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.audio-icon {
|
||||
font-size: 80rpx;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.audio-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 48rpx;
|
||||
background: var(--button-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(20rpx);
|
||||
border: 2rpx solid var(--border-color);
|
||||
}
|
||||
|
||||
.play-btn:active {
|
||||
background: var(--button-active);
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.play-btn.playing {
|
||||
background: rgba(52, 199, 89, 0.8);
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
font-size: 40rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.audio-progress {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 8rpx;
|
||||
background: var(--button-bg);
|
||||
border-radius: 4rpx;
|
||||
margin-bottom: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 4rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.current-time,
|
||||
.total-time {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.audio-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-name {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12rpx;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.audio-size {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 🎨 加载和错误状态 */
|
||||
.loading-overlay,
|
||||
.error-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--preview-bg);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid var(--border-color);
|
||||
border-top: 4rpx solid var(--text-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text,
|
||||
.error-text {
|
||||
font-size: 28rpx;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
padding: 16rpx 32rpx;
|
||||
background: var(--button-bg);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.retry-btn:active {
|
||||
background: var(--button-active);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.retry-text {
|
||||
font-size: 26rpx;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 🎨 底部操作栏 */
|
||||
.preview-footer {
|
||||
padding: 32rpx;
|
||||
background: var(--header-bg);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-top: 1rpx solid var(--border-color);
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.footer-btn:active {
|
||||
background: var(--button-bg);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.footer-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 🎨 手势提示 */
|
||||
.gesture-tips {
|
||||
position: absolute;
|
||||
bottom: 160rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 16rpx 32rpx;
|
||||
background: var(--header-bg);
|
||||
border-radius: 32rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
animation: tipsFadeIn 0.5s ease-out 1s both;
|
||||
}
|
||||
|
||||
@keyframes tipsFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 24rpx;
|
||||
color: var(--text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 📱 响应式设计 */
|
||||
@media screen and (max-width: 375px) {
|
||||
.preview-header {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.media-title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.file-icon-container {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 96rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 414px) {
|
||||
.preview-header {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.media-title {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.file-icon-container {
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 140rpx;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue