1. 加工费模块重构
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
2. 快递费模块重构 3. 订单导出功能优化 4. 商品订单的折扣功能 5. 修复了一些已知BUG
This commit is contained in:
133
apps/web-antd/src/components/video-player/XgVideoPlayer.vue
Normal file
133
apps/web-antd/src/components/video-player/XgVideoPlayer.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
|
||||
|
||||
import Player from 'xgplayer';
|
||||
|
||||
import 'xgplayer/dist/index.min.css';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
autoplay?: boolean;
|
||||
controls?: boolean;
|
||||
thumbnail?: boolean;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
poster?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
autoplay: false,
|
||||
controls: true,
|
||||
thumbnail: false,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
poster: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
ready: [player: Player];
|
||||
error: [error: any];
|
||||
ended: [];
|
||||
}>();
|
||||
|
||||
const containerRef = ref<HTMLDivElement>();
|
||||
const player = shallowRef<Player | null>(null);
|
||||
|
||||
function createPlayer() {
|
||||
if (!containerRef.value || !props.src) return;
|
||||
|
||||
player.value?.destroy();
|
||||
player.value = null;
|
||||
|
||||
const ignores = props.thumbnail
|
||||
? [
|
||||
'cssfullscreen',
|
||||
'screenshot',
|
||||
'pip',
|
||||
'miniscreen',
|
||||
'keyboard',
|
||||
'download',
|
||||
'playbackrate',
|
||||
'time',
|
||||
'definition',
|
||||
'fullscreen',
|
||||
'loading',
|
||||
'mobile',
|
||||
'pc',
|
||||
'play',
|
||||
'poster',
|
||||
'progress',
|
||||
'replay',
|
||||
'start',
|
||||
'volume',
|
||||
]
|
||||
: [];
|
||||
|
||||
const p = new Player({
|
||||
el: containerRef.value,
|
||||
url: props.src,
|
||||
autoplay: props.autoplay,
|
||||
controls: props.thumbnail ? false : props.controls,
|
||||
closeVideoClick: props.thumbnail,
|
||||
closeVideoDblclick: props.thumbnail,
|
||||
ignores,
|
||||
fluid: true,
|
||||
fitVideoSize: 'fixWidth',
|
||||
videoInit: true,
|
||||
poster: props.poster,
|
||||
playsinline: true,
|
||||
enableContextmenu: !props.thumbnail,
|
||||
});
|
||||
|
||||
p.on('ready', () => {
|
||||
emit('ready', p);
|
||||
});
|
||||
|
||||
p.on('error', (e: any) => {
|
||||
emit('error', e);
|
||||
});
|
||||
|
||||
p.on('ended', () => {
|
||||
emit('ended');
|
||||
});
|
||||
|
||||
player.value = p;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
createPlayer();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
player.value?.destroy();
|
||||
player.value = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
createPlayer();
|
||||
},
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
getPlayer: () => player.value,
|
||||
play: () => player.value?.play(),
|
||||
pause: () => player.value?.pause(),
|
||||
destroy: () => {
|
||||
player.value?.destroy();
|
||||
player.value = null;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="containerRef" class="xg-video-player"></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.xg-video-player {
|
||||
width: v-bind(width);
|
||||
height: v-bind(height);
|
||||
}
|
||||
</style>
|
||||
1
apps/web-antd/src/components/video-player/index.ts
Normal file
1
apps/web-antd/src/components/video-player/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as XgVideoPlayer } from './XgVideoPlayer.vue';
|
||||
@@ -11,11 +11,11 @@
|
||||
alt="预览"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<video
|
||||
<XgVideoPlayer
|
||||
v-else-if="uploadPreview.type === 'video'"
|
||||
:src="uploadPreview.url"
|
||||
class="w-full h-full object-cover"
|
||||
preload="metadata"
|
||||
thumbnail
|
||||
/>
|
||||
<FileOutlined v-else class="text-2xl text-gray-400"/>
|
||||
</div>
|
||||
@@ -61,6 +61,7 @@
|
||||
<script setup>
|
||||
import {inject} from 'vue';
|
||||
import {FileOutlined, SendOutlined, CloseOutlined} from '@ant-design/icons-vue';
|
||||
import { XgVideoPlayer } from '#/components/video-player';
|
||||
|
||||
const uploadPreview = inject('uploadPreview');
|
||||
const sendFile = inject('sendFile');
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { computed, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
import { XgVideoPlayer } from '#/components/video-player';
|
||||
import { useMediaPreview } from '../composables/useMediaPreview.ts';
|
||||
// import VueEasyLightbox from 'vue-easy-lightbox';
|
||||
import { useThemeStore } from '../stores/theme.ts';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { visible, mediaData, hidePreview, previewType } = useMediaPreview();
|
||||
|
||||
// 视频相关状态
|
||||
const videoPlayer = ref(null);
|
||||
const isPlaying = ref(false);
|
||||
const isMuted = ref(false);
|
||||
const volume = ref(1);
|
||||
const currentTime = ref(0);
|
||||
const duration = ref(0);
|
||||
const showControls = ref(true);
|
||||
const isPortrait = ref(false);
|
||||
const controlsTimeout = ref(null);
|
||||
|
||||
// 计算属性
|
||||
const showImagePreview = computed(
|
||||
() => visible.value && previewType.value === 'image',
|
||||
);
|
||||
@@ -29,341 +17,47 @@ const showVideoPreview = computed(
|
||||
() => visible.value && previewType.value === 'video',
|
||||
);
|
||||
|
||||
const progressPercent = computed(() => {
|
||||
if (duration.value === 0) return 0;
|
||||
return (currentTime.value / duration.value) * 100;
|
||||
});
|
||||
|
||||
// 视频加载完成
|
||||
const handleVideoLoad = () => {
|
||||
if (videoPlayer.value) {
|
||||
duration.value = videoPlayer.value.duration;
|
||||
|
||||
// 检测视频方向
|
||||
const video = videoPlayer.value;
|
||||
isPortrait.value = video.videoHeight > video.videoWidth;
|
||||
|
||||
// 监听时间更新
|
||||
video.addEventListener('timeupdate', updateTime);
|
||||
}
|
||||
};
|
||||
|
||||
// 图片加载完成
|
||||
const handleImageLoad = () => {
|
||||
// 可以在这里添加图片加载完成的逻辑
|
||||
};
|
||||
|
||||
// 更新播放时间
|
||||
const updateTime = () => {
|
||||
if (videoPlayer.value) {
|
||||
currentTime.value = videoPlayer.value.currentTime;
|
||||
}
|
||||
};
|
||||
|
||||
// 视频播放/暂停事件
|
||||
const handleVideoPlay = () => {
|
||||
isPlaying.value = true;
|
||||
hideControlsAfterDelay();
|
||||
};
|
||||
|
||||
const handleVideoPause = () => {
|
||||
isPlaying.value = false;
|
||||
showControls.value = true;
|
||||
clearTimeout(controlsTimeout.value);
|
||||
};
|
||||
|
||||
// 控制功能
|
||||
const togglePlay = () => {
|
||||
if (videoPlayer.value) {
|
||||
if (isPlaying.value) {
|
||||
videoPlayer.value.pause();
|
||||
} else {
|
||||
videoPlayer.value.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
if (videoPlayer.value) {
|
||||
videoPlayer.value.muted = !videoPlayer.value.muted;
|
||||
isMuted.value = videoPlayer.value.muted;
|
||||
}
|
||||
};
|
||||
|
||||
const updateVolume = () => {
|
||||
if (videoPlayer.value) {
|
||||
videoPlayer.value.volume = volume.value;
|
||||
isMuted.value = volume.value === 0;
|
||||
}
|
||||
};
|
||||
|
||||
const seekTo = (event) => {
|
||||
if (videoPlayer.value) {
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const percent = (event.clientX - rect.left) / rect.width;
|
||||
videoPlayer.value.currentTime = percent * duration.value;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (videoPlayer.value) {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
videoPlayer.value.requestFullscreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const togglePictureInPicture = async () => {
|
||||
if (videoPlayer.value) {
|
||||
try {
|
||||
await (document.pictureInPictureElement
|
||||
? document.exitPictureInPicture()
|
||||
: videoPlayer.value.requestPictureInPicture());
|
||||
} catch (error) {
|
||||
console.error('画中画功能不支持:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 隐藏控制栏
|
||||
const hideControlsAfterDelay = () => {
|
||||
clearTimeout(controlsTimeout.value);
|
||||
controlsTimeout.value = setTimeout(() => {
|
||||
if (isPlaying.value) {
|
||||
showControls.value = false;
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// 显示控制栏
|
||||
const showControlsTemporarily = () => {
|
||||
showControls.value = true;
|
||||
if (isPlaying.value) {
|
||||
hideControlsAfterDelay();
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间
|
||||
const formatDuration = (seconds) => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 下载媒体
|
||||
const downloadMedia = () => {
|
||||
if (mediaData.value?.url) {
|
||||
const link = document.createElement('a');
|
||||
link.href = mediaData.value.url;
|
||||
link.download = `media_${Date.now()}`;
|
||||
link.click();
|
||||
}
|
||||
};
|
||||
|
||||
// 分享媒体
|
||||
const shareMedia = async () => {
|
||||
if (navigator.share && mediaData.value?.url) {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: '分享媒体',
|
||||
url: mediaData.value.url,
|
||||
});
|
||||
} catch {
|
||||
console.log('分享取消或失败');
|
||||
}
|
||||
} else {
|
||||
// 复制链接到剪贴板
|
||||
navigator.clipboard.writeText(mediaData.value?.url || '');
|
||||
alert('链接已复制到剪贴板');
|
||||
}
|
||||
};
|
||||
|
||||
// 键盘事件处理
|
||||
const handleKeydown = (event) => {
|
||||
if (!visible.value) return;
|
||||
|
||||
switch (event.code) {
|
||||
case 'ArrowLeft': {
|
||||
if (showVideoPreview.value && videoPlayer.value) {
|
||||
event.preventDefault();
|
||||
videoPlayer.value.currentTime -= 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
if (showVideoPreview.value && videoPlayer.value) {
|
||||
event.preventDefault();
|
||||
videoPlayer.value.currentTime += 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Escape': {
|
||||
if (event.code === 'Escape') {
|
||||
hidePreview();
|
||||
break;
|
||||
}
|
||||
case 'Space': {
|
||||
if (showVideoPreview.value) {
|
||||
event.preventDefault();
|
||||
togglePlay();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听媒体数据变化
|
||||
watch(mediaData, () => {
|
||||
if (previewType.value === 'video') {
|
||||
isPlaying.value = false;
|
||||
currentTime.value = 0;
|
||||
duration.value = 0;
|
||||
showControls.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown);
|
||||
clearTimeout(controlsTimeout.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 图片预览组件 -->
|
||||
<!-- <div
|
||||
v-if="showImagePreview"
|
||||
class="media-preview-overlay"
|
||||
:class="{ 'dark': themeStore.isDarkMode }"
|
||||
@click.self="hidePreview"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<button class="preview-close-btn" @click="hidePreview">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<div class="image-preview-content">
|
||||
<img
|
||||
:src="mediaData"
|
||||
:alt="'图片预览'"
|
||||
class="preview-image"
|
||||
@load="handleImageLoad"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="preview-actions">
|
||||
<button class="action-btn" @click="downloadMedia" title="下载">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button class="action-btn" @click="shareMedia" title="分享">
|
||||
<i class="fas fa-share"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<Image
|
||||
:imgs="[{ src: mediaData }]"
|
||||
:visible="showImagePreview"
|
||||
@hide="hidePreview"
|
||||
/>
|
||||
|
||||
<!-- 视频预览组件 -->
|
||||
<div
|
||||
v-if="showVideoPreview"
|
||||
:class="{ dark: themeStore.isDarkMode }"
|
||||
class="media-preview-overlay video-preview"
|
||||
class="media-preview-overlay"
|
||||
@click.self="hidePreview"
|
||||
>
|
||||
<div :class="{ portrait: isPortrait }" class="video-preview-container">
|
||||
<div class="video-preview-container">
|
||||
<button class="preview-close-btn" @click="hidePreview">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<div class="video-content">
|
||||
<video
|
||||
ref="videoPlayer"
|
||||
<XgVideoPlayer
|
||||
:src="mediaData"
|
||||
autoplay
|
||||
class="preview-video"
|
||||
controls
|
||||
@loadedmetadata="handleVideoLoad"
|
||||
@pause="handleVideoPause"
|
||||
@play="handleVideoPlay"
|
||||
></video>
|
||||
|
||||
<!-- 自定义视频控制栏 -->
|
||||
<div v-if="showControls" class="video-controls">
|
||||
<div class="controls-top">
|
||||
<div class="video-title">视频预览</div>
|
||||
<div class="video-info">
|
||||
{{ formatDuration(currentTime) }} / {{ formatDuration(duration) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls-bottom">
|
||||
<button class="control-btn" @click="togglePlay">
|
||||
<i :class="isPlaying ? 'fa-pause' : 'fa-play'" class="fas"></i>
|
||||
</button>
|
||||
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" @click="seekTo">
|
||||
<div
|
||||
:style="{ width: `${progressPercent}%` }"
|
||||
class="progress-fill"
|
||||
></div>
|
||||
<div
|
||||
:style="{ left: `${progressPercent}%` }"
|
||||
class="progress-thumb"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="control-btn" @click="toggleMute">
|
||||
<i
|
||||
:class="isMuted ? 'fa-volume-mute' : 'fa-volume-up'"
|
||||
class="fas"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<div class="volume-container">
|
||||
<input
|
||||
v-model="volume"
|
||||
class="volume-slider"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.1"
|
||||
type="range"
|
||||
@input="updateVolume"
|
||||
autoplay
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="control-btn" @click="toggleFullscreen">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-actions">
|
||||
<button class="action-btn" title="下载" @click="downloadMedia">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button class="action-btn" title="分享" @click="shareMedia">
|
||||
<i class="fas fa-share"></i>
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
title="画中画"
|
||||
@click="togglePictureInPicture"
|
||||
>
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -396,15 +90,6 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
position: relative;
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-preview-container {
|
||||
position: relative;
|
||||
max-width: 90vw;
|
||||
@@ -417,10 +102,6 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.video-preview-container.portrait {
|
||||
max-width: 60vw;
|
||||
}
|
||||
|
||||
.preview-close-btn {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
@@ -445,178 +126,17 @@ onUnmounted(() => {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.image-preview-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview-image {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.video-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.preview-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
width: 90vw;
|
||||
max-height: 85vh;
|
||||
}
|
||||
|
||||
.video-controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
|
||||
color: white;
|
||||
padding: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.controls-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.video-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.video-info {
|
||||
font-size: 14px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.controls-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
flex: 1;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
height: 6px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #4361ee;
|
||||
border-radius: 3px;
|
||||
transition: width 0.1s ease;
|
||||
}
|
||||
|
||||
.progress-thumb {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #4361ee;
|
||||
border-radius: 50%;
|
||||
transform: translateX(-50%);
|
||||
transition: left 0.1s ease;
|
||||
}
|
||||
|
||||
.volume-container {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.volume-slider {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.volume-slider::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #4361ee;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.preview-actions,
|
||||
.video-actions {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 60px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.preview-container,
|
||||
.video-preview-container {
|
||||
max-width: 95vw;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.video-preview-container.portrait {
|
||||
max-width: 95vw;
|
||||
}
|
||||
|
||||
.preview-close-btn {
|
||||
top: -40px;
|
||||
width: 36px;
|
||||
@@ -624,35 +144,9 @@ onUnmounted(() => {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.video-controls {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.controls-bottom {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.volume-container {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.preview-actions,
|
||||
.video-actions {
|
||||
top: 16px;
|
||||
right: 50px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
.preview-video {
|
||||
width: 95vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,6 +54,7 @@ import previewMedia from '../composables/useMediaPreview.ts';
|
||||
import { useThemeStore } from '../stores/theme.ts';
|
||||
import { useUserStore as chatUseUserStore } from '../stores/user';
|
||||
import AudioMessage from './AudioMessage.vue';
|
||||
import { XgVideoPlayer } from '#/components/video-player';
|
||||
import PatientExperienceCard from '#/views/doctor/online-consultation/components/PatientExperienceCard.vue';
|
||||
import FollowUpDrugAssistantCard from './FollowUpDrugAssistantCard.vue';
|
||||
import ProductCard from '#/views/doctor/online-consultation/components/ProductCard.vue';
|
||||
@@ -472,12 +473,11 @@ const getSexText = (sex) => {
|
||||
class="video-message relative cursor-pointer"
|
||||
@click="handlePreviewMedia(parsedContent, 'video')"
|
||||
>
|
||||
<video
|
||||
<XgVideoPlayer
|
||||
:src="parsedContent"
|
||||
class="video-thumbnail max-w-full rounded-lg"
|
||||
preload="metadata"
|
||||
@error="handleVideoError"
|
||||
></video>
|
||||
thumbnail
|
||||
/>
|
||||
<div class="video-overlay">
|
||||
<div class="play-button">
|
||||
<i class="fas fa-play"></i>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { Button, Descriptions, Image, Spin } from 'ant-design-vue';
|
||||
|
||||
import MIcon from '#/components/icon/icon.vue';
|
||||
import { XgVideoPlayer } from '#/components/video-player';
|
||||
import type { FileGalleryDetail } from '#/api/core/file-gallery';
|
||||
import { getFileGalleryDetail } from '#/api/core/file-gallery';
|
||||
|
||||
@@ -70,11 +71,10 @@ defineExpose({
|
||||
<template v-if="detail">
|
||||
<div class="preview-wrap">
|
||||
<Image v-if="detail.type === 0" :src="detail.url" :width="220" />
|
||||
<video
|
||||
<XgVideoPlayer
|
||||
v-else-if="detail.type === 1"
|
||||
class="media-player"
|
||||
controls
|
||||
:src="detail.url"
|
||||
class="media-player"
|
||||
/>
|
||||
<audio
|
||||
v-else-if="detail.type === 2"
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"js-base64": "^3.7.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-vue-next": "^0.487.0",
|
||||
"md-editor-v3": "^5.4.5"
|
||||
"md-editor-v3": "^5.4.5",
|
||||
"xgplayer": "^3.0.24"
|
||||
}
|
||||
}
|
||||
|
||||
117
pnpm-lock.yaml
generated
117
pnpm-lock.yaml
generated
@@ -493,6 +493,9 @@ importers:
|
||||
md-editor-v3:
|
||||
specifier: ^5.4.5
|
||||
version: 5.4.5(vue@3.5.13(typescript@5.6.3))
|
||||
xgplayer:
|
||||
specifier: ^3.0.24
|
||||
version: 3.0.24(core-js@3.39.0)
|
||||
devDependencies:
|
||||
'@changesets/changelog-github':
|
||||
specifier: 'catalog:'
|
||||
@@ -5233,6 +5236,13 @@ packages:
|
||||
engines: {node: '>=v12.20.0'}
|
||||
hasBin: true
|
||||
|
||||
d@1.0.2:
|
||||
resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
danmu.js@1.2.1:
|
||||
resolution: {integrity: sha512-evDEImUBo94c846fC92K//Dzll8jXnZ3zKmYlQHwMzmvw6IW2IyjWL3Ew2SqEAzuqauFnDkwJEgZauu3uW/p1Q==}
|
||||
|
||||
dargs@8.1.0:
|
||||
resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5360,6 +5370,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
delegate@3.2.0:
|
||||
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==}
|
||||
|
||||
denque@2.1.0:
|
||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
@@ -5601,6 +5614,17 @@ packages:
|
||||
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es5-ext@0.10.64:
|
||||
resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
es6-iterator@2.0.3:
|
||||
resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
|
||||
|
||||
es6-symbol@3.1.4:
|
||||
resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
esbuild@0.24.0:
|
||||
resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -5814,6 +5838,10 @@ packages:
|
||||
jiti:
|
||||
optional: true
|
||||
|
||||
esniff@2.0.1:
|
||||
resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
espree@10.3.0:
|
||||
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -5856,6 +5884,9 @@ packages:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
event-emitter@0.3.5:
|
||||
resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
|
||||
|
||||
event-target-shim@5.0.1:
|
||||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -5863,6 +5894,9 @@ packages:
|
||||
eventemitter3@2.0.3:
|
||||
resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==}
|
||||
|
||||
eventemitter3@4.0.7:
|
||||
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
@@ -5890,6 +5924,9 @@ packages:
|
||||
resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
ext@1.7.0:
|
||||
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
||||
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
@@ -7352,6 +7389,9 @@ packages:
|
||||
engines: {node: '>= 4.4.x'}
|
||||
hasBin: true
|
||||
|
||||
next-tick@1.1.0:
|
||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||
|
||||
nitropack@2.10.4:
|
||||
resolution: {integrity: sha512-sJiG/MIQlZCVSw2cQrFG1H6mLeSqHlYfFerRjLKz69vUfdu0EL2l0WdOxlQbzJr3mMv/l4cOlCCLzVRzjzzF/g==}
|
||||
engines: {node: ^16.11.0 || >=17.0.0}
|
||||
@@ -9281,6 +9321,9 @@ packages:
|
||||
resolution: {integrity: sha512-UJShLPYi1aWqCdq9HycOL/gwsuqda1OISdBO3t8RlXQC4QvtuIz4b5FCfe2dQIWEpmlRExKmcTBfP1r9bhY7ig==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
type@2.7.3:
|
||||
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -9914,6 +9957,16 @@ packages:
|
||||
xe-utils@3.5.32:
|
||||
resolution: {integrity: sha512-R8ZT2lRnRBQO3pchM1za/Aru+/29DVDWD/OmOFODWWGkiQYz0iVIr8Bq8uKXS6zMhEsSqVCrn46bXzfe/Agjcw==}
|
||||
|
||||
xgplayer-subtitles@3.0.24:
|
||||
resolution: {integrity: sha512-yrHROepD9kTGWiTSxBjh+TGaK9orQOHQ1GUxWFOkQzO8BLl/pcIAqqJkHiAoP0j+eU8VdIhpb55oJRIkTTWPsA==}
|
||||
peerDependencies:
|
||||
core-js: '>=3.12.1'
|
||||
|
||||
xgplayer@3.0.24:
|
||||
resolution: {integrity: sha512-rK3hoJ+1Pbl9PUCHgeJv1GnOQF28BKLQFTBxdvi0pHsibJeXaDUf51SLFcgNVAPN15aDYcjJgaSf7LQ0RfEFPQ==}
|
||||
peerDependencies:
|
||||
core-js: '>=3.12.1'
|
||||
|
||||
xlsx@0.18.5:
|
||||
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -14376,6 +14429,15 @@ snapshots:
|
||||
|
||||
czg@1.11.0: {}
|
||||
|
||||
d@1.0.2:
|
||||
dependencies:
|
||||
es5-ext: 0.10.64
|
||||
type: 2.7.3
|
||||
|
||||
danmu.js@1.2.1:
|
||||
dependencies:
|
||||
event-emitter: 0.3.5
|
||||
|
||||
dargs@8.1.0: {}
|
||||
|
||||
data-view-buffer@1.0.2:
|
||||
@@ -14466,6 +14528,8 @@ snapshots:
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
delegate@3.2.0: {}
|
||||
|
||||
denque@2.1.0: {}
|
||||
|
||||
depcheck@1.4.7:
|
||||
@@ -14751,6 +14815,24 @@ snapshots:
|
||||
is-date-object: 1.1.0
|
||||
is-symbol: 1.1.1
|
||||
|
||||
es5-ext@0.10.64:
|
||||
dependencies:
|
||||
es6-iterator: 2.0.3
|
||||
es6-symbol: 3.1.4
|
||||
esniff: 2.0.1
|
||||
next-tick: 1.1.0
|
||||
|
||||
es6-iterator@2.0.3:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
es6-symbol: 3.1.4
|
||||
|
||||
es6-symbol@3.1.4:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
ext: 1.7.0
|
||||
|
||||
esbuild@0.24.0:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.24.0
|
||||
@@ -15054,6 +15136,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
esniff@2.0.1:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
event-emitter: 0.3.5
|
||||
type: 2.7.3
|
||||
|
||||
espree@10.3.0:
|
||||
dependencies:
|
||||
acorn: 8.14.0
|
||||
@@ -15090,10 +15179,17 @@ snapshots:
|
||||
|
||||
etag@1.8.1: {}
|
||||
|
||||
event-emitter@0.3.5:
|
||||
dependencies:
|
||||
d: 1.0.2
|
||||
es5-ext: 0.10.64
|
||||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@2.0.3: {}
|
||||
|
||||
eventemitter3@4.0.7: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
@@ -15143,6 +15239,10 @@ snapshots:
|
||||
|
||||
expect-type@1.1.0: {}
|
||||
|
||||
ext@1.7.0:
|
||||
dependencies:
|
||||
type: 2.7.3
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
extendable-error@0.1.7: {}
|
||||
@@ -16577,6 +16677,8 @@ snapshots:
|
||||
sax: 1.4.1
|
||||
optional: true
|
||||
|
||||
next-tick@1.1.0: {}
|
||||
|
||||
nitropack@2.10.4(encoding@0.1.13)(typescript@5.7.2):
|
||||
dependencies:
|
||||
'@cloudflare/kv-asset-handler': 0.3.4
|
||||
@@ -18667,6 +18769,8 @@ snapshots:
|
||||
|
||||
type-fest@4.30.2: {}
|
||||
|
||||
type@2.7.3: {}
|
||||
|
||||
typed-array-buffer@1.0.3:
|
||||
dependencies:
|
||||
call-bound: 1.0.3
|
||||
@@ -19461,6 +19565,19 @@ snapshots:
|
||||
|
||||
xe-utils@3.5.32: {}
|
||||
|
||||
xgplayer-subtitles@3.0.24(core-js@3.39.0):
|
||||
dependencies:
|
||||
core-js: 3.39.0
|
||||
eventemitter3: 4.0.7
|
||||
|
||||
xgplayer@3.0.24(core-js@3.39.0):
|
||||
dependencies:
|
||||
core-js: 3.39.0
|
||||
danmu.js: 1.2.1
|
||||
delegate: 3.2.0
|
||||
eventemitter3: 4.0.7
|
||||
xgplayer-subtitles: 3.0.24(core-js@3.39.0)
|
||||
|
||||
xlsx@0.18.5:
|
||||
dependencies:
|
||||
adler-32: 1.3.1
|
||||
|
||||
Reference in New Issue
Block a user