|
|
@@ -1,65 +1,27 @@
|
|
|
<template>
|
|
|
<div>
|
|
|
<div class="header">
|
|
|
- <a-upload
|
|
|
- :accept="acceptType"
|
|
|
- multiple
|
|
|
- :beforeUpload="beforeUpload"
|
|
|
- :showUploadList="false"
|
|
|
- name="file"
|
|
|
- >
|
|
|
- <a-button
|
|
|
- v-if="butText"
|
|
|
- @click="addImage"
|
|
|
- :disabled="props.disabled"
|
|
|
- type="primary"
|
|
|
- size="small"
|
|
|
- class="mr-10"
|
|
|
- >{{ buttonText }}</a-button
|
|
|
- >
|
|
|
+ <a-upload :accept="acceptType" multiple :beforeUpload="beforeUpload" :showUploadList="false" name="file">
|
|
|
+ <a-button v-if="butText" @click="addImage" :disabled="props.disabled" type="primary" size="small"
|
|
|
+ class="mr-10">{{ buttonText }}</a-button>
|
|
|
</a-upload>
|
|
|
<div style="width: 300px" v-if="list.length">
|
|
|
- <a-slider
|
|
|
- :max="300"
|
|
|
- :min="60"
|
|
|
- id="test"
|
|
|
- v-model:value="baseSize"
|
|
|
- @change="changeZoom"
|
|
|
- />
|
|
|
+ <a-slider :max="300" :min="60" id="test" v-model:value="baseSize" @change="changeZoom" />
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div
|
|
|
- class="picture-wall-box"
|
|
|
- :style="{
|
|
|
- gridTemplateColumns: `repeat(auto-fill, ${baseSize}px)`,
|
|
|
- }"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="picture-wall-item"
|
|
|
- v-for="(item, index) in list"
|
|
|
- :key="item.attachmentid"
|
|
|
- :style="{
|
|
|
- width: baseSize + 'px',
|
|
|
- height: baseSize + 'px',
|
|
|
- }"
|
|
|
- :draggable="!props.disabled && pic && isImageFile(item)"
|
|
|
- @dragstart="dragStart(index)"
|
|
|
- @dragover="dragOver(index)"
|
|
|
- @dragend="dragEnd"
|
|
|
- >
|
|
|
+ <div class="picture-wall-box" :style="{
|
|
|
+ gridTemplateColumns: `repeat(auto-fill, ${baseSize}px)`,
|
|
|
+ }">
|
|
|
+ <div class="picture-wall-item" v-for="(item, index) in list" :key="item.attachmentid" :style="{
|
|
|
+ width: baseSize + 'px',
|
|
|
+ height: baseSize + 'px',
|
|
|
+ }" :draggable="!props.disabled && pic && isImageFile(item)" @dragstart="dragStart(index)"
|
|
|
+ @dragover="dragOver(index)" @dragend="dragEnd">
|
|
|
<div v-if="item.uid">
|
|
|
- <a-progress
|
|
|
- type="circle"
|
|
|
- :percent="item.progress"
|
|
|
- :status="item.exception"
|
|
|
- :size="[baseSize - 40, baseSize - 40]"
|
|
|
- />
|
|
|
+ <a-progress type="circle" :percent="item.progress" :status="item.exception"
|
|
|
+ :size="[baseSize - 40, baseSize - 40]" />
|
|
|
</div>
|
|
|
- <a-popover
|
|
|
- v-else
|
|
|
- :open="hovered == index"
|
|
|
- @openChange="handleHoverChange($event, index)"
|
|
|
- >
|
|
|
+ <a-popover v-else :open="hovered == index" @openChange="handleHoverChange($event, index)">
|
|
|
<template #content>
|
|
|
<div style="width: 200px; padding: 8px;">
|
|
|
<div style="font-weight: bold; margin-bottom: 4px;">{{ item.document || '文件' }}</div>
|
|
|
@@ -67,48 +29,59 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
<!-- 图片文件:使用图片预览 -->
|
|
|
- <div v-if="isImageFile(item)" style="width: 100%; height: 100%; position: relative;">
|
|
|
- <a-image
|
|
|
- :src="item.cover"
|
|
|
- :preview="{
|
|
|
- src: item.url,
|
|
|
- }"
|
|
|
- :style="{
|
|
|
+ <div v-if="isImageFile(item)" style="width: 100%; height: 100%; position: relative;"
|
|
|
+ @click="openImagePreview(index)">
|
|
|
+ <a-image :src="item.cover" :style="{
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ objectFit: 'cover',
|
|
|
+ }" />
|
|
|
+ <!-- 删除按钮 -->
|
|
|
+ <div v-if="!props.disabled && isDeletable" style="position: absolute; top: 4px; right: 4px;" @click.stop>
|
|
|
+ <a-popconfirm :open="linksid == item.linksid" title="确定删除当前资源吗?" @confirm="confirmDeleteImage"
|
|
|
+ @cancel="linksid = null">
|
|
|
+ <DeleteOutlined class="previewMaskIcon" :style="{ fontSize: '18px', color: '#ff4d4f' }"
|
|
|
+ @click.stop="linksid = item.linksid" />
|
|
|
+ </a-popconfirm>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 非图片文件:显示文件图标和点击下载 -->
|
|
|
+ <div v-else
|
|
|
+ style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; position: relative; cursor: pointer;"
|
|
|
+ @click.stop="handleFileClick(item)">
|
|
|
+ <!-- 视频且有封面图:显示封面图 -->
|
|
|
+ <template v-if="item.fileType && item.fileType.toLowerCase() === 'video' && hasCoverImage(item)">
|
|
|
+ <a-image v-if="getCoverImage(item).url" :preview="false" :src="getCoverImage(item).url" :style="{
|
|
|
width: '100%',
|
|
|
height: '100%',
|
|
|
objectFit: 'cover',
|
|
|
- }"
|
|
|
- >
|
|
|
- <template #previewMask>
|
|
|
+ }" />
|
|
|
+ <!-- 播放图标 -->
|
|
|
+ <div
|
|
|
+ style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; pointer-events: none;">
|
|
|
<div
|
|
|
- v-if="!props.disabled && isDeletable"
|
|
|
- style="display: flex; gap: 8px; background: rgba(0,0,0,0.5); height: 100%; align-items: center; justify-content: center;"
|
|
|
- >
|
|
|
- <a-popconfirm
|
|
|
- :open="linksid == item.linksid"
|
|
|
- title="确定删除当前资源吗?"
|
|
|
- @confirm="confirmDeleteImage"
|
|
|
- @cancel="linksid = null"
|
|
|
- >
|
|
|
- <DeleteOutlined
|
|
|
- class="previewMaskIcon"
|
|
|
- :style="{ fontSize: (baseSize > 200 ? '24px' : '20px'), color: '#fff' }"
|
|
|
- @click.stop="linksid = item.linksid"
|
|
|
- />
|
|
|
- </a-popconfirm>
|
|
|
+ style="width: 100%; height: 100%; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
|
|
+ <component :is="PlayCircleOutlined" :style="{ fontSize: '36px', color: '#fff' }" />
|
|
|
</div>
|
|
|
- </template>
|
|
|
- </a-image>
|
|
|
- </div>
|
|
|
- <!-- 非图片文件:显示文件图标和点击下载 -->
|
|
|
- <div
|
|
|
- v-else
|
|
|
- style="display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; cursor: pointer;"
|
|
|
- @click="downloadFile(item)"
|
|
|
- >
|
|
|
- <component :is="getFileIcon(item)" :style="{ fontSize: (baseSize > 100 ? '48px' : '36px'), color: '#1890ff' }" />
|
|
|
- <div style="font-size: 12px; color: #666; margin-top: 8px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 90%;">
|
|
|
- {{ item.document || '文件' }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 没有封面图:显示文件图标 -->
|
|
|
+ <template v-else>
|
|
|
+ <component :is="getFileIcon(item)"
|
|
|
+ :style="{ fontSize: (baseSize > 100 ? '48px' : '36px'), color: '#1890ff' }" />
|
|
|
+ <div
|
|
|
+ style="font-size: 12px; color: #666; margin-top: 8px; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: 90%;">
|
|
|
+ {{ item.document || '文件' }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <!-- 删除按钮 -->
|
|
|
+ <div v-if="!props.disabled && isDeletable" style="position: absolute; top: 4px; right: 4px; z-index: 10;"
|
|
|
+ @click.stop>
|
|
|
+ <a-popconfirm :open="linksid == item.linksid" title="确定删除当前资源吗?" @confirm="confirmDeleteImage"
|
|
|
+ @cancel="linksid = null">
|
|
|
+ <DeleteOutlined class="previewMaskIcon" :style="{ fontSize: '18px', color: '#ff4d4f' }"
|
|
|
+ @click.stop="linksid = item.linksid" />
|
|
|
+ </a-popconfirm>
|
|
|
</div>
|
|
|
</div>
|
|
|
</a-popover>
|
|
|
@@ -117,6 +90,44 @@
|
|
|
<div class="empty" v-if="!list.length">
|
|
|
<a-empty :image="simpleImage" />
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- 视频播放对话框 -->
|
|
|
+ <a-modal v-model:open="videoVisible" :title="videoTitle" :footer="null" width="800px" centered>
|
|
|
+ <div style="padding: 20px 0 20px 0;">
|
|
|
+ <div style="display: flex; justify-content: center; background: #000; padding: 20px; border-radius: 8px;">
|
|
|
+ <video :src="videoUrl" controls autoplay style="max-width: 100%; max-height: 60vh;" @ended="onVideoEnded" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
|
|
+ <a-button @click="switchVideo(-1)" :disabled="currentVideoIndex <= 0">
|
|
|
+ <template #icon><left-outlined /></template>
|
|
|
+ 上一个
|
|
|
+ </a-button>
|
|
|
+ <span style="font-size: 14px; color: #666;">{{ currentVideoIndex + 1 }} / {{ videoList.length }}</span>
|
|
|
+ <a-button @click="switchVideo(1)" :disabled="currentVideoIndex >= videoList.length - 1">
|
|
|
+ 下一个
|
|
|
+ <template #icon><right-outlined /></template>
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ </a-modal>
|
|
|
+
|
|
|
+ <!-- 图片预览对话框 -->
|
|
|
+ <a-modal v-model:open="imagePreviewVisible" :footer="null" width="90%" centered :closable="true">
|
|
|
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
|
|
|
+ <a-button @click="switchImage(-1)" :disabled="currentImageIndex <= 0">
|
|
|
+ <template #icon><left-outlined /></template>
|
|
|
+ 上一个
|
|
|
+ </a-button>
|
|
|
+ <span style="font-size: 14px; color: #666;">{{ currentImageIndex + 1 }} / {{ imageList.length }}</span>
|
|
|
+ <a-button @click="switchImage(1)" :disabled="currentImageIndex >= imageList.length - 1">
|
|
|
+ 下一个
|
|
|
+ <template #icon><right-outlined /></template>
|
|
|
+ </a-button>
|
|
|
+ </div>
|
|
|
+ <div style="display: flex; justify-content: center;">
|
|
|
+ <a-image :src="currentImageUrl" style="max-width: 100%; max-height: 80vh;" />
|
|
|
+ </div>
|
|
|
+ </a-modal>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -125,7 +136,7 @@ import { ref, reactive, defineProps, watch, computed } from "vue";
|
|
|
import Api from "@/api/api";
|
|
|
import Up from "@/api/upload";
|
|
|
import utils from "@/utils/utils";
|
|
|
-import { DeleteOutlined, FileOutlined, FileTextOutlined, PlayCircleOutlined } from "@ant-design/icons-vue";
|
|
|
+import { DeleteOutlined, FileOutlined, FileTextOutlined, PlayCircleOutlined, LeftOutlined, RightOutlined } from "@ant-design/icons-vue";
|
|
|
import { Empty } from "ant-design-vue";
|
|
|
|
|
|
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
|
|
@@ -237,6 +248,20 @@ function getFileIcon(item) {
|
|
|
return FileTextOutlined;
|
|
|
}
|
|
|
|
|
|
+// 判断是否有封面图
|
|
|
+function hasCoverImage(item) {
|
|
|
+ if (!item || !item.subfiles || !item.subfiles.length) return false;
|
|
|
+ const hasCover = item.subfiles.some(s => s.type === 'cover');
|
|
|
+ return hasCover;
|
|
|
+}
|
|
|
+
|
|
|
+// 获取封面图
|
|
|
+function getCoverImage(item) {
|
|
|
+ if (!item || !item.subfiles || !item.subfiles.length) return null;
|
|
|
+ const cover = item.subfiles.find(s => s.type === 'cover');
|
|
|
+ return cover;
|
|
|
+}
|
|
|
+
|
|
|
// 格式化文件大小
|
|
|
function getFileSize(size) {
|
|
|
if (!size) return '未知大小';
|
|
|
@@ -250,6 +275,16 @@ function getFileSize(size) {
|
|
|
return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
|
}
|
|
|
|
|
|
+// 处理文件点击:视频播放,其他下载
|
|
|
+function handleFileClick(item) {
|
|
|
+ const type = (item.fileType || '').toLowerCase();
|
|
|
+ if (type === 'video') {
|
|
|
+ playVideo(item);
|
|
|
+ } else {
|
|
|
+ downloadFile(item);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 下载文件
|
|
|
function downloadFile(item) {
|
|
|
const link = document.createElement('a');
|
|
|
@@ -261,6 +296,59 @@ function downloadFile(item) {
|
|
|
document.body.removeChild(link);
|
|
|
}
|
|
|
|
|
|
+// 视频播放相关
|
|
|
+let videoVisible = ref(false);
|
|
|
+let videoUrl = ref('');
|
|
|
+let videoTitle = ref('');
|
|
|
+let currentVideoIndex = ref(0);
|
|
|
+let videoList = ref([]);
|
|
|
+
|
|
|
+function playVideo(item) {
|
|
|
+ // 获取所有视频列表
|
|
|
+ videoList.value = list.value.filter(v => (v.fileType || '').toLowerCase() === 'video');
|
|
|
+ currentVideoIndex.value = videoList.value.findIndex(v => v.linksid === item.linksid);
|
|
|
+ videoUrl.value = item.url;
|
|
|
+ videoTitle.value = item.document;
|
|
|
+ videoVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function switchVideo(direction) {
|
|
|
+ const newIndex = currentVideoIndex.value + direction;
|
|
|
+ console.log("videoList.value[newIndex]", videoList.value[newIndex])
|
|
|
+ if (newIndex >= 0 && newIndex < videoList.value.length) {
|
|
|
+ currentVideoIndex.value = newIndex;
|
|
|
+ videoUrl.value = videoList.value[newIndex].url;
|
|
|
+ videoTitle.value = videoList.value[newIndex].document || '视频';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onVideoEnded() {
|
|
|
+ // 播放结束自动切换到下一个
|
|
|
+ switchVideo(1);
|
|
|
+}
|
|
|
+
|
|
|
+// 图片预览相关
|
|
|
+let imagePreviewVisible = ref(false);
|
|
|
+let currentImageUrl = ref('');
|
|
|
+let currentImageIndex = ref(0);
|
|
|
+let imageList = ref([]);
|
|
|
+
|
|
|
+function openImagePreview(index) {
|
|
|
+ // 获取所有图片列表
|
|
|
+ imageList.value = list.value.filter(v => isImageFile(v));
|
|
|
+ currentImageIndex.value = index;
|
|
|
+ currentImageUrl.value = imageList.value[index].url;
|
|
|
+ imagePreviewVisible.value = true;
|
|
|
+}
|
|
|
+
|
|
|
+function switchImage(direction) {
|
|
|
+ const newIndex = currentImageIndex.value + direction;
|
|
|
+ if (newIndex >= 0 && newIndex < imageList.value.length) {
|
|
|
+ currentImageIndex.value = newIndex;
|
|
|
+ currentImageUrl.value = imageList.value[newIndex].url;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function beforeUpload(file) {
|
|
|
file.document = file.name;
|
|
|
const filetype = file.document.substr(file.document.lastIndexOf(".") + 1);
|
|
|
@@ -359,7 +447,7 @@ watch(
|
|
|
}
|
|
|
);
|
|
|
function init(attinfos, pic = props.pic) {
|
|
|
- if(!attinfos.length) return;
|
|
|
+ if (!attinfos.length) return;
|
|
|
const fileType = Number(props.fileType);
|
|
|
if (pic) {
|
|
|
list.value = attinfos;
|
|
|
@@ -382,7 +470,7 @@ function init(attinfos, pic = props.pic) {
|
|
|
if (
|
|
|
pic &&
|
|
|
list.value[list.value.length - 1].sequence !=
|
|
|
- props.startSequence + list.value.length
|
|
|
+ props.startSequence + list.value.length
|
|
|
) {
|
|
|
setSequenceAll();
|
|
|
}
|
|
|
@@ -503,6 +591,10 @@ function handleHoverChange(visible, index) {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
+.picture-wall-box .picture-wall-item :deep(.ant-popconfirm) {
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
.empty {
|
|
|
width: 100%;
|
|
|
padding: 50px 0;
|