Files
Docker/WebVue/src/views/master/DmsFileEnquiry.vue
T
2026-09-23 16:56:36 +08:00

1381 lines
42 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<a-spin size="large" :spinning="loading" :tip="spinTip">
<div class="dms-file-enquiry">
<Toolbar :path-stack="pathStack" :root-path="rootPath" @go-top="onGoTop" @go-back="onGoBack"
@refresh="onReSearch" @search="onSearchByName" @breadcrumb-click="onBreadcrumbClick" />
<OperatorStation ref="operatorStationRef" :selected-items="keys" :upload-url="uploadUrl" :headers="headers"
:can-create="canCreate" :can-amend="canAmend" :can-delete="canDelete" :can-view="canView"
:resolve-file-path="resolveUploadFilePath" multiple @create-folder="onCreate" @upload-start="onUploadStart"
@upload-progress="onUploadProgress" @upload-success="onUploadSuccess" @upload-complete="onUploadComplete"
@upload-interrupted="onUploadInterrupted" @upload-error="onUploadError" @download="onDownloadSelected"
@share="onShareSelected" @publish="onPublishSelected" @view-preview="onViewPreviewSelected"
@play-video="onUrlVideoPlayItem" @play-audio="onPlayAudioItem" @pause-audio="onPauseAudio"
@continue-audio="onContinueAudio" @preview="onPreviewSelected" @rename="onAmendSelected" @move="onMoveSelected"
@delete="onDeleteSelected" />
<MyComputer :datas="datas" v-model:selected-keys="keys" @select-change="onSelectChange"
@open-folder="onOpenFolder" @open-file="onOpenFile" @preview-image="onPreviewOrgFile" />
</div>
<a-image :width="1024" :style="{ display: 'none' }" :preview="{
visible,
onVisibleChange: setPicVisible,
}" :src="imageSrc" />
<dmsFolder @on-submit="onReload" @submit-start="loading = true" @submit-end="loading = false"></dmsFolder>
<VideoPlayModal v-model:open="videoPlayModal" :videos="videoPlayList" :start-index="videoPlayIndex"
:title="videoPlayTitle" :fullscreen="width < 640" />
<dragModal v-model:open="showModalShare" width="60%" title="分享管理查询" :mask-closable="false" :fullscreen="width < 640"
@ok="handleSelectShare">
<DmsFileShareModal ref="shareEnquiry"></DmsFileShareModal>
</dragModal>
<dragModal v-model:open="showModalPublishPhoto" width="60%" title="相册专辑查询" :mask-closable="false"
:fullscreen="width < 640" @ok="handleSelectPublishPhoto">
<PublishPhotoEnquiry ref="publishPhotoEnquiry"></PublishPhotoEnquiry>
</dragModal>
<dragModal v-model:open="showModalPublishAudio" width="60%" title="音乐专辑查询" :mask-closable="false"
:fullscreen="width < 640" @ok="handleSelectPublishAudio">
<PublishAudioEnquiry ref="publishAudioEnquiry"></PublishAudioEnquiry>
</dragModal>
<dragModal v-model:open="showModalPublishVideo" width="60%" title="影视专辑查询" :mask-closable="false"
:fullscreen="width < 640" @ok="handleSelectPublishVideo">
<PublishVideoEnquiry ref="publishVideoEnquiry"></PublishVideoEnquiry>
</dragModal>
<!-- 上传中断:问用户要不要从断点续传 -->
<a-modal v-model:open="showResumeModal" title="上传中断" :mask-closable="false" :closable="false" ok-text="继续上传"
cancel-text="放弃" @ok="onResumeUpload" @cancel="onDiscardUpload">
<p>「{{ interruptedInfo.fileName }}」已上传 {{ interruptedInfo.uploaded }}/{{ interruptedInfo.total }} 片。</p>
<p>网络似乎中断了,是否从断点继续上传?</p>
<p style="color: #8a919f; font-size: 12px">
提示:刷新页面会丢失已上传的分片,那之后只能重新选择文件从头上传。
</p>
</a-modal>
</a-spin>
</template>
<script setup lang="ts">
import { reactive, onMounted, onUnmounted, ref, unref, h, provide } from "vue";
import { BindOption } from "@/data/custom/bindOption";
import { SelectOption } from "@/data/auto/selectOption";
import { DmsFileUploadViewDisplayData } from "@/data/auto/dmsFileUploadViewDisplayData";
import { SearchFilter } from "@/data/auto/searchFilter";
import dmsFileService from "@/service/master/dmsFileService";
import publishPhotoService from "@/service/master/publishPhotoService";
import publishAudioService from "@/service/master/publishAudioService";
import { DmsFileSearchCriteria } from "@/data/auto/dmsFileSearchCriteria";
import dragModal from "@/components/basic/DragModal.vue";
import DmsFileShareModal from "./DmsFileShareModal.vue";
import PublishPhotoEnquiry from "./PublishPhotoEnquiry.vue";
import PublishAudioEnquiry from "./PublishAudioEnquiry.vue";
import PublishVideoEnquiry from "./PublishVideoEnquiry.vue";
import { FamilyGroupFolderEntity } from "@/data/auto/familyGroupFolderEntity";
import {
PermissionContext,
usePermissionContext,
} from "@/hooks/PermissionContext";
import { useRoute, useRouter } from "vue-router";
import { useBindOption } from "@/hooks/useBindOption";
import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy";
import { SearchFilterTypePolicy } from "@/data/custom/searchFilterTypePolicy";
import { notification } from "ant-design-vue";
import { SearchCriteriaResultData } from "@/data/auto/searchCriteriaResultData";
import dmsFolder from "./DmsFolderModal.vue";
import { useLoginProfileStore } from "@/store/modules/loginProfile";
import { WebSitePolicy } from "@/policy/custom/webSitePolicy";
import { orderBy } from "lodash-es";
import { Howl } from "howler";
import { PublishVideoEntity } from "@/data/auto/publishVideoEntity";
import publishVideoService from "@/service/master/publishVideoService";
import { useWindowSize } from "@vueuse/core";
import { request } from "@/utils/request";
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
import { PublishAudioEntity } from "@/data/auto/publishAudioEntity";
import { FamilyFolderFavoriteViewEntity } from "@/data/auto/familyFolderFavoriteViewEntity";
import { DmsFileEntity } from "@/data/auto/dmsFileEntity";
import { PublishData } from "@/data/auto/publishData";
import { PublishPhotoEntity } from "@/data/auto/publishPhotoEntity";
import MyComputer from "@/components/basic/MyComputer.vue";
import Toolbar from "@/components/basic/Toolbar.vue";
import OperatorStation from "@/components/basic/OperatorStation.vue";
import VideoPlayModal from "@/components/basic/VideoPlayModal.vue";
import type { VideoItem } from "@/components/basic/VideoPlayer.vue";
import { PublishVideoDetailDisplayData } from "@/data/auto/publishVideoDetailDisplayData";
interface BreadcrumbItem {
value: string;
displayName: string;
}
const { width } = useWindowSize();
const showModalShare = ref<boolean>(false);
const sharedFileRecords = ref<DmsFileUploadViewDisplayData[]>([]);
const showModalPublishPhoto = ref<boolean>(false);
const showModalPublishAudio = ref<boolean>(false);
const showModalPublishVideo = ref<boolean>(false);
const spinTip = ref<string>("加载中...");
const loading = ref<boolean>(false);
const canView = ref<boolean>(false);
const canAmend = ref<boolean>(false);
const canDelete = ref<boolean>(false);
const canCreate = ref<boolean>(false);
const rootPath = ref<string>("");
const imageSrc = ref<string>("");
const route = useRoute();
const router = useRouter();
const previousFilePath = ref<string>("");
/**
* 上传地址:
* 开发环境指向本地服务,生产环境指向同源根路径。
* 注意 WebApiRoot 生产环境为 "/",必须先去掉结尾斜杠再拼接,
* 否则会得到 "//DmsFile/UploadDmsFile",被浏览器当作协议相对地址(host = dmsfile)而导致解析失败。
*/
const uploadUrl = ref<string>(
`${(process.env.NODE_ENV === "development"
? WebSitePolicy.WebApiRootDev
: WebSitePolicy.WebApiRoot
).replace(/\/$/, "")
}/DmsFile/UploadDmsFile`
);
const currFilePath = ref<string>("");
const headers = ref({
Authorization: useLoginProfileStore().getToken(),
"Content-Type": "multipart/form-data",
FilePath: currFilePath.value,
});
const bindOptions = ref<BindOption[]>([]);
const datas = ref<DmsFileUploadViewDisplayData[]>([]);
const pagingParams = ref<PagingParams>({ pageSize: 999999, page: 1 });
const result = ref<SearchCriteriaResultData>(new SearchCriteriaResultData());
const currentFilter = ref<SearchFilter[]>([]);
const isSearched = ref<boolean>(false);
const currentSearchCriteria = ref<DmsFileSearchCriteria>(
new DmsFileSearchCriteria(),
);
const keys = ref<DmsFileUploadViewDisplayData[]>([]);
const dmsFolderModal = ref<boolean>(false);
const dmsFolderTitle = ref<string>("新建文件夹");
const dmsFolderIsRename = ref<boolean>(false);
const dmsFolderIsMove = ref<boolean>(false);
const entity = ref<DmsFileEntity>(new DmsFileEntity());
const videoPlayModal = ref<boolean>(false);
const videoPlayList = ref<VideoItem[]>([]);
const videoPlayIndex = ref<number>(0);
const videoPlayTitle = ref<string>("");
const visible = ref<boolean>(false);
const pathStack = ref<BreadcrumbItem[]>([]);
interface PublishPhotoEnquiry {
keys: PublishPhotoEntity[];
}
const publishPhotoEnquiry = ref<PublishPhotoEnquiry | null>(null);
interface PublishAudioEnquiry {
keys: PublishAudioEntity[];
}
const publishAudioEnquiry = ref<PublishAudioEnquiry | null>(null);
interface PublishVideoEnquiry {
keys: PublishVideoEntity[];
}
const publishVideoEnquiry = ref<PublishVideoEnquiry | null>(null);
interface ShareEnquiry {
keys: FamilyFolderFavoriteViewEntity[];
}
const shareEnquiry = ref<ShareEnquiry | null>(null);
const sound = ref();
const toUploadView = (r: Record<string, any>): DmsFileUploadViewDisplayData =>
r as DmsFileUploadViewDisplayData;
onUnmounted(() => {
sound.value?.stop();
clearRefreshTimers();
stopTranscodeTip();
});
const play = (src: string) => {
sound.value?.stop();
if (!src || src == undefined || src == "") {
return;
}
sound.value = new Howl({
src: src,
volume: 0.5,
autoplay: true,
loop: false,
html5: true,
preload: true,
onend: () => {
datas.value.forEach((element) => {
element.IsPlay = "";
});
},
});
sound.value.play();
};
const onPauseAudio = (record: DmsFileUploadViewDisplayData) => {
record.IsPause = "Y";
sound.value?.pause();
};
const onContinueAudio = (record: DmsFileUploadViewDisplayData) => {
record.IsPause = "";
sound.value?.play();
};
const onPlayAudio = (record: DmsFileUploadViewDisplayData) => {
datas.value.forEach((element) => {
if (element.FileId == record.FileId && element.IsPlay == "Y") {
element.IsPlay = "";
} else if (
element.FileId == record.FileId &&
(element.IsPlay == "" ||
element.IsPlay == null ||
element.IsPlay == undefined)
) {
element.IsPlay = "Y";
} else {
element.IsPlay = "";
}
record.IsPause = "";
});
if (record.IsPlay == "") {
sound.value?.stop();
return;
}
play(record.Url);
};
//================provide================
provide("dmsFolderModal", dmsFolderModal);
provide("dmsFolderTitle", dmsFolderTitle);
provide("dmsFolderIsRename", dmsFolderIsRename);
provide("dmsFolderIsMove", dmsFolderIsMove);
provide("DmsFileEntity", entity);
provide("isModalPublishVideo", showModalPublishVideo);
provide("isModalPublishAudio", showModalPublishAudio);
provide("isModalPublishPhoto", showModalPublishPhoto);
//================provide================
defineExpose({
keys,
});
onMounted(async () => {
let pContext: PermissionContext = usePermissionContext();
canView.value = pContext.canView(route.meta.permission as string);
canAmend.value = pContext.canAmend(route.meta.permission as string);
canDelete.value = pContext.canDelete(route.meta.permission as string);
canCreate.value = pContext.canCreate(route.meta.permission as string);
await initBindOption();
pathStack.value = [{ value: rootPath.value, displayName: "我的云盘" }];
onSearchByFilePath(rootPath.value);
});
/** 解析上传目标文件夹(DMS 当前路径) */
const resolveUploadFilePath = () => {
const bindOption = bindOptions.value.filter(
(x) => x.BindValue == DmsFileUploadViewDisplayData.DB_FILE_PATH,
)[0];
return bindOption?.Value || currFilePath.value;
};
/** 本次上传的是否是视频:只有视频才需要转码/截图 */
const uploadingVideo = ref<boolean>(false);
/**
* "视频转码中 x%" 提示。
*
* 最后一片发出去后,服务端要先 ffmpeg 截封面(要解码一段视频,最多 25 秒)才返回,
* 这段时间前端进度停在 99%/100% 一动不动,看起来像卡死。
* 这里用一个按时间推进的估算进度(与后端 25 秒上限对齐,最高 99%)告诉用户还在处理。
*/
const TRANSCODE_MAX_MS = 25000;
let transcodeTimer = 0;
const startTranscodeTip = () => {
if (transcodeTimer) return;
const startAt = Date.now();
spinTip.value = "预览10秒视频转码中 0%";
transcodeTimer = window.setInterval(() => {
const ratio = Math.min(0.99, (Date.now() - startAt) / TRANSCODE_MAX_MS);
spinTip.value = `预览10秒视频转码中 ${Math.round(ratio * 100)}%`;
}, 300);
};
const stopTranscodeTip = () => {
if (!transcodeTimer) return;
clearInterval(transcodeTimer);
transcodeTimer = 0;
};
const onUploadStart = (file: File) => {
loading.value = true;
uploadingVideo.value = !!file && fileTypePolicy.isVideo(file?.name || "");
};
const onUploadProgress = (payload: { text?: string; percent?: number }) => {
// 99%/100% 之后就是服务端转码阶段,换成转码提示,别让用户以为卡住了
if (uploadingVideo.value && (payload?.percent ?? 0) >= 99) {
startTranscodeTip();
return;
}
// 进度回到低处说明开始传下一个文件了,转码提示到此为止
stopTranscodeTip();
spinTip.value = payload?.text || "上传中...";
};
/** 单个文件传完:这里不刷新,等一批都传完再统一刷,避免选了多个文件反复刷列表 */
const onUploadSuccess = () => {
// 字节已传完,服务端还在合并分片 + ffmpeg 截图/转码
if (uploadingVideo.value) {
startTranscodeTip();
return;
}
spinTip.value = "上传完成,等待服务端处理...";
};
/** 上传后延迟刷新的定时器,离开页面/重新开始时清掉,避免野刷新 */
let refreshTimers: number[] = [];
const clearRefreshTimers = () => {
refreshTimers.forEach((t) => clearTimeout(t));
refreshTimers = [];
};
/**
* 一批文件全部传完:给后端留点时间合并分片,文件越多等得越久。
* 视频封面(jpg)由 ffmpeg 生成、可能比合并更慢,所以首刷之后再补刷两次——
* 每次查询服务端都会重新拼 ?v=,src 变了 <img> 才会重新请求,封面就能补上来。
*/
const onUploadComplete = (payload: { count?: number }) => {
const count = payload?.count ?? 1;
const sec = 2500 + (count - 1) * 1000;
// 本次是不是视频:只有视频才需要补刷和转码提示
const isVideo = uploadingVideo.value;
// 视频场景由转码提示接管文案,等首刷完成后再切回"加载中..."
if (!isVideo) spinTip.value = "加载中...";
clearRefreshTimers();
refreshTimers.push(
window.setTimeout(() => {
// 列表已经刷出来了,转码提示到此为止
stopTranscodeTip();
uploadingVideo.value = false;
onReSearch();
loading.value = false;
}, sec),
);
// 只有视频才补刷:封面(jpg)是服务端 ffmpeg 异步生成的,首刷时可能还没落盘;
// 每次查询服务端都会重新拼 ?v=,src 变了 <img> 才会重新请求,封面就能补上来。
// 图片/音频/普通文件上传后文件已经就绪,一次刷新就够,多刷纯属浪费。
if (isVideo) {
refreshTimers.push(window.setTimeout(onReSearch, sec + 3000));
refreshTimers.push(window.setTimeout(onReSearch, sec + 6000));
}
};
/** 中断时的断点信息,弹窗里展示用 */
const interruptedInfo = ref<{ fileName: string; uploaded: number; total: number }>({
fileName: "",
uploaded: 0,
total: 0,
});
const showResumeModal = ref<boolean>(false);
const operatorStationRef = ref<any>(null);
/** 上传中断:断点还在内存里,弹窗问用户要不要接着传 */
const onUploadInterrupted = (payload: {
fileName?: string;
uploaded?: number;
total?: number;
}) => {
interruptedInfo.value = {
fileName: payload?.fileName || "",
uploaded: payload?.uploaded ?? 0,
total: payload?.total ?? 0,
};
stopTranscodeTip();
loading.value = false;
spinTip.value = "加载中...";
showResumeModal.value = true;
};
/** 继续上传:从断点那一批接着发 */
const onResumeUpload = () => {
showResumeModal.value = false;
loading.value = true;
spinTip.value = "继续上传中...";
operatorStationRef.value?.resumeUpload();
};
/** 放弃:清掉断点,已传的分片留在服务器上不会被合并 */
const onDiscardUpload = () => {
showResumeModal.value = false;
operatorStationRef.value?.discardUpload();
stopTranscodeTip();
uploadingVideo.value = false;
loading.value = false;
};
const onUploadError = () => {
stopTranscodeTip();
uploadingVideo.value = false;
loading.value = false;
spinTip.value = "加载中...";
};
const onSelectChange = (selected: DmsFileUploadViewDisplayData[]) => {
keys.value = selected;
};
const onViewPreviewPhoto = async (filePath: string) => {
router.push({ name: "Preview", params: { id: filePath, mode: "list" } });
};
const onViewPreviewAudio = async (filePath: string) => {
router.push({
name: "PreviewAudio",
params: { id: filePath, mode: "list" },
});
};
const onViewPreviewVideo = async (filePath: string) => {
router.push({
name: "PreviewVideo",
params: { id: filePath, mode: "list" },
});
};
const onChangeFolder = async (fvalue: string) => {
onSearchByFilePath(fvalue);
};
/** DMS 记录 → 播放列表项:Url=原码、FormattedM3u8=转码(10秒),封面用同名 png 兜底 */
const toVideoItem = (record: DmsFileUploadViewDisplayData): VideoItem => {
const item = new PublishVideoDetailDisplayData();
item.Title = record.OrgFileName;
item.Url = record.Url;
item.FormattedM3u8 = record.FormattedM3u8;
item.FormattedUrl = record.FormattedM3u8 || record.Url;
item.FormattedPic = (record.FormattedPic || "").replace(/\.m3u8$/, ".png");
return item;
};
/** 当前目录里的视频,作为弹窗播放器的播放列表 */
const buildVideoList = (
records: DmsFileUploadViewDisplayData[],
): VideoItem[] =>
records
.filter(
(x) =>
x.IsDirectoryIndc != "Y" &&
fileTypePolicy.isVideo(x.OrderKey || x.OrgFileName) &&
!!(x.FormattedM3u8 || x.Url),
)
.map(toVideoItem);
/** 打开视频弹窗 */
const openVideoPlay = (list: VideoItem[], index: number, title: string) => {
sound.value?.stop();
datas.value.forEach((element) => {
element.IsPlay = "";
});
videoPlayList.value = list;
videoPlayIndex.value = index < 0 ? 0 : index;
videoPlayTitle.value = title || "视频播放";
videoPlayModal.value = true;
};
const onPreviewFile = async (usernameOrRecord: any, orderKey?: string) => {
let username: string;
let fname: string;
if (typeof usernameOrRecord === "object") {
username = usernameOrRecord.CreatedBy;
fname = usernameOrRecord.OrderKey;
} else {
username = usernameOrRecord;
fname = orderKey!;
}
loading.value = true;
getPreviewFile(username, fname);
};
const onPreviewOrgFile = async (usernameOrRecord: any, orderKey?: string) => {
let fileId: number;
let orgFileName: string;
if (typeof usernameOrRecord === "object") {
fileId = usernameOrRecord.FileId;
orgFileName = usernameOrRecord.OrgFileName;
} else {
fileId = usernameOrRecord;
orgFileName = orderKey!;
}
loading.value = true;
onViewPictureFile(fileId, orgFileName);
};
const getPreviewFile = (username: string, fname: string) => {
let dms: DmsFileEntity = new DmsFileEntity();
dms.CreatedBy = username;
dms.OrderKey = fname;
dmsFileService
.DownloadFilePreview(dms)
.then((res) => {
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
imageSrc.value = downloadUrl;
loading.value = false;
setPicVisible(true);
})
.catch(() => {
loading.value = false;
});
};
const DownloadFileCombine = (
fs: string[],
mergedData: Uint8Array,
index: number,
ttl: number,
total: number,
preview: boolean,
fname: string,
) => {
loading.value = true;
fs[fs.length - 2] = index.toString();
spinTip.value = "加载中(" + index + "/" + total + ")...";
dmsFileService
.DownloadFileCombine(fs.toString().replace(/,/g, "-"))
.then((res) => {
mergedData.set(new Uint8Array(res), ttl);
if (index < total) {
DownloadFileCombine(
fs,
mergedData,
index + 1,
ttl + res.byteLength,
total,
preview,
fname,
);
} else if (index == total) {
loading.value = false;
spinTip.value = "加载中...";
const downloadUrl = window.URL.createObjectURL(
new Blob([mergedData as unknown as BlobPart]),
);
imageSrc.value = downloadUrl;
if (!preview) {
const link = document.createElement("a");
link.href = downloadUrl;
link.setAttribute("download", fname);
document.body.appendChild(link);
link.click();
} else {
setPicVisible(true);
}
}
})
.catch(() => {
spinTip.value = "加载中...";
loading.value = false;
});
};
const onLastFolder = async () => {
if (!datas.value || datas.value.length == 0) {
onSearchByFilePath(previousFilePath.value);
} else {
onPreviousFolder();
}
};
const onPreviousFolder = async () => {
let filePath: string = "";
if (datas.value && datas.value.length > 0) filePath = datas.value[0].FilePath;
dmsFileService.FindFolderNameParentByFolderName(filePath).then((res) => {
if (res && res.length > 0) onSearchByFilePath(res);
});
};
const onPublishPhoto = async () => {
showModalPublishPhoto.value = true;
};
const onPublishAudio = async () => {
showModalPublishAudio.value = true;
};
const onPublishVideo = async () => {
showModalPublishVideo.value = true;
};
const onShareFamily = async (records: DmsFileUploadViewDisplayData[]) => {
sharedFileRecords.value = records.map((r) => toUploadView(r));
showModalShare.value = true;
};
/** 按 URL 直接下载(视频走这条:Url 就是原码文件) */
const downloadByUrl = async (url: string, fileName: string) => {
if (!url) {
notification.info({
message: "系统提示:",
description: "该文件没有可下载的地址",
placement: "topRight",
});
return;
}
try {
loading.value = true;
spinTip.value = "下载中...";
// 与 VideoPlayer 一致:先取回字节流,再用 Blob + <a download> 落盘
const res = await request<ArrayBuffer>({
url,
method: "get",
responseType: "arraybuffer",
});
const blobUrl = window.URL.createObjectURL(new Blob([res]));
const link = document.createElement("a");
link.href = blobUrl;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
link.remove();
// 立刻 revoke 部分浏览器会来不及触发下载,延后释放
setTimeout(() => window.URL.revokeObjectURL(blobUrl), 1000);
} catch (error) {
notification.info({
message: "系统提示:",
description: "下载失败",
placement: "topRight",
});
} finally {
loading.value = false;
spinTip.value = "加载中...";
}
};
/** 下载一条记录:视频下原码,其它文件走原来的分片合并下载 */
const onDownloadFile = async (record: DmsFileUploadViewDisplayData) => {
if (fileTypePolicy.isVideo(record.OrgFileName) || fileTypePolicy.isVideo(record.OrderKey)) {
await downloadByUrl(record.Url, record.OrgFileName);
return;
}
GetOrgPictureFile(record.FileId, record.OrgFileName, false);
};
const GetOrgPictureFile = async (
fid: number,
fname: string,
preview: boolean,
) => {
if (fileTypePolicy.isVideo(fname)) {
return;
}
loading.value = true;
dmsFileService
.FindByKey(fid)
.then((res) => {
let fs: string[] = res.OrderKey.split("-");
let index: string = fs[fs.length - 2];
let total: string = fs[fs.length - 1].split(".")[0];
var mergedData = new Uint8Array((res.FileSize + 1) * 1024);
let ttl: number = 0;
fs[fs.length - 2] = "1";
spinTip.value = "加载中(1/" + total + ")...";
DownloadFileCombine(
fs,
mergedData,
1,
ttl,
parseInt(total),
preview,
fname,
);
})
.catch(() => {
loading.value = false;
});
};
const onViewPictureFile = async (fid: number, fname: string) => {
GetOrgPictureFile(fid, fname, true);
};
const onSearchByFilePath = async (fvalue: string) => {
setBindOptionValue(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
fvalue,
"",
[fvalue],
false,
);
previousFilePath.value = unref(currFilePath.value);
currFilePath.value = fvalue;
headers.value.FilePath = fvalue;
isSearched.value = true;
if (currentSearchCriteria.value && fvalue && fvalue.length > 0) {
currentSearchCriteria.value.Filter = [];
let filter: SearchFilter;
filter = new SearchFilter();
filter.Column = DmsFileUploadViewDisplayData.DB_FILE_PATH;
filter.Type = "SingleSelection";
filter.Operator = "single";
filter.Value = fvalue;
currentSearchCriteria.value.Filter.push(filter);
} else {
currentSearchCriteria.value.Filter = [];
}
await findBySearchCriteria(unref(currentSearchCriteria));
};
const onViewPic = () => {
if (keys.value.length == 0) return;
if (keys.value[0].IsDirectoryIndc == "Y") {
onChangeFolder(keys.value[0].FileName);
} else if (fileTypePolicy.isImage(keys.value[0].OrgFileName)) {
onPreviewFile(keys.value[0].CreatedBy, keys.value[0].OrderKey);
} else if (fileTypePolicy.isVideo(keys.value[0].OrgFileName)) {
onUrlVideoPlayItem(keys.value[0]);
} else {
notification.info({
message: "系统提示:",
description: "未知格式无法预览",
placement: "topRight",
});
}
};
const setPicVisible = (value: boolean): void => {
visible.value = value;
};
const onCreate = () => {
dmsFolderTitle.value = "新增文件夹";
dmsFolderIsRename.value = false;
dmsFolderIsMove.value = false;
dmsFolderModal.value = true;
entity.value = new DmsFileEntity();
let bindOption: BindOption = bindOptions.value.filter(
(x) => x.BindValue == DmsFileUploadViewDisplayData.DB_FILE_PATH,
)[0];
entity.value.FilePath = bindOption.SelectOptions.filter(
(x) => x.BindValue == bindOption.Value,
)[0].DisplayName;
entity.value.OrgFileName = bindOption.Value;
currFilePath.value = bindOption.Value;
};
const onAmend = () => {
if (keys.value[0].IsDirectoryIndc == "Y") {
dmsFolderTitle.value = "重命名文件夹";
} else {
dmsFolderTitle.value = "重命名文件";
}
dmsFolderIsRename.value = true;
dmsFolderModal.value = true;
dmsFolderIsMove.value = false;
entity.value = new DmsFileEntity();
entity.value.FileId = keys.value[0].FileId;
entity.value.FilePath = keys.value[0].FolderLocalName;
if (keys.value[0].CreatedBy == keys.value[0].FolderLocalName) {
entity.value.FilePath = "";
}
entity.value.FileName = keys.value[0].OrgFileName;
};
const onMove = () => {
if (keys.value[0].IsDirectoryIndc == "Y") {
dmsFolderTitle.value = "移动文件夹";
} else {
dmsFolderTitle.value = "移动文件";
}
dmsFolderIsRename.value = false;
dmsFolderIsMove.value = true;
dmsFolderModal.value = true;
entity.value = new DmsFileEntity();
entity.value.FileId = keys.value[0].FileId;
entity.value.FilePath = keys.value[0].FolderLocalName;
if (keys.value[0].CreatedBy == keys.value[0].FolderLocalName) {
entity.value.FilePath = "";
}
entity.value.FileName = keys.value[0].FileName;
entity.value.OrgFileName = keys.value[0].OrgFileName;
};
const onDelete = async () => {
try {
loading.value = true;
for (let i = 0; i < keys.value.length; i++) {
await dmsFileService.Delete(keys.value[i].FileId);
}
notification.info({
message: "系统提示:",
description: "记录删除成功",
placement: "topRight",
});
} catch (error) {
notification.info({
message: "系统提示:",
description: "记录删除失败",
placement: "topRight",
});
}
finally {
loading.value = false;
onReload();
}
};
const setBindOptionValue = (
bindValue: string,
value: string,
value1: string,
values: string[],
disabled: boolean,
) => {
let bindOption: BindOption = bindOptions.value.filter(
(x) => x.BindValue == bindValue,
)[0];
if (!bindOption) return;
bindOption.Value = value;
bindOption.Value1 = value1;
bindOption.Values = values;
bindOption.disabled = disabled;
};
const initBindOption = async () => {
let ubo = useBindOption();
bindOptions.value = [];
bindOptions.value.splice(0, bindOptions.value.length);
let bindOption: BindOption = ubo.getBindOption(
DmsFileUploadViewDisplayData.DB_ORG_FILE_NAME,
"名称",
SearchFilterTypePolicy.TEXT,
SearchFilterOperatorPolicy.CONTAIN,
);
bindOptions.value.push(bindOption);
let rps: SelectOption[] = await dmsFileService.FindDmsFolders();
if (rps && rps.length > 1) {
rootPath.value = rps[1].BindValue;
if (!currFilePath.value) {
currFilePath.value = rps[1].BindValue;
}
}
bindOption = ubo.getBindSelectOption(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
"当前目录",
SearchFilterTypePolicy.SINGLE_SELECTION,
SearchFilterOperatorPolicy.SINGLE,
rps,
);
bindOptions.value.push(bindOption);
setBindOptionValue(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
currFilePath.value,
"",
[currFilePath.value],
false,
);
};
result.value.Page = pagingParams.value.page ?? 1;
result.value.Limit = pagingParams.value.pageSize;
const onSubmit = async (filters: SearchFilter[]) => {
loading.value = true;
let c: DmsFileSearchCriteria = new DmsFileSearchCriteria();
currentFilter.value = filters;
c.Filter = filters;
c.limit = pagingParams.value.pageSize;
c.page = pagingParams.value.page ?? 1;
c.GridColumns = [];
currentSearchCriteria.value = c;
await findBySearchCriteria(c);
};
const findBySearchCriteria = async (c: DmsFileSearchCriteria) => {
keys.value = [];
try {
if (c.limit == 0) c.limit = pagingParams.value.pageSize;
if (c.page == 0) c.page = pagingParams.value.page ?? 1;
currentFilter.value = c.Filter;
let data = await dmsFileService.FindBySearchCriteria(c);
isSearched.value = true;
result.value.Total = data.Total;
result.value.Page = data.Page;
result.value.Limit = data.Limit;
const rawList = data.Datas ?? [];
// 始终排序,空数组orderBy也安全
datas.value = orderBy(
rawList,
(x: DmsFileUploadViewDisplayData) => x.IsDirectoryIndc,
"desc",
);
loading.value = false;
if (
data.Datas &&
data.Datas.length > 0 &&
fileTypePolicy.isAudio(data.Datas[0].OrderKey)
) {
onPlayAudio(data.Datas[0]);
onPauseAudio(data.Datas[0]);
} else {
sound.value?.stop();
}
} catch (error) {
loading.value = false;
console.log(error);
}
};
const onReSearch = async () => {
setBindOptionValue(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
currFilePath.value,
"",
[currFilePath.value],
false,
);
await findBySearchCriteria(unref(currentSearchCriteria));
loading.value = false;
spinTip.value = "加载中...";
};
const onReload = async () => {
let cfp: string = currFilePath.value;
await initBindOption();
setBindOptionValue(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
cfp,
"",
[cfp],
false,
);
await findBySearchCriteria(unref(currentSearchCriteria));
};
const handleSelectShare = async () => {
// 没选目标共享目录 / 没有待共享文件时直接提示,不发请求
if (!shareEnquiry.value) return;
if (shareEnquiry.value.keys.length === 0 || sharedFileRecords.value.length === 0) {
notification.info({
message: "系统提示:",
description: "请选择一条记录",
placement: "topRight",
});
return;
}
try {
loading.value = true;
spinTip.value = "共享中...";
let fgps: FamilyGroupFolderEntity[] = [];
// 每个选中的目标共享目录 × 每个待共享的文件,各生成一条群组文件夹记录
shareEnquiry.value.keys.forEach((target) => {
sharedFileRecords.value.forEach((record) => {
let fgp: FamilyGroupFolderEntity = new FamilyGroupFolderEntity();
fgp.FamilyName = target.FamilyName;
fgp.TopFolderName = target.TopFolderName;
fgp.FolderName = record.FileName ?? "";
fgp.FolderLocalName = record.OrgFileName ?? "";
fgp.FilePath = target.FolderName;
fgps.push(fgp);
});
});
// 先关弹窗再发请求:遮罩盖在弹窗上会看不清,关掉后 loading 效果更明显。
// 注意 fgps 必须在关闭前组装好,弹窗销毁后就取不到选中的目标目录了
showModalShare.value = false;
await dmsFileService.CreateFamilyGroupFolder(fgps);
notification.info({
message: "系统提示:",
description: "共享成功",
placement: "topRight",
});
} catch (error) {
notification.info({
message: "系统提示:",
description: "共享失败",
placement: "topRight",
});
} finally {
loading.value = false;
spinTip.value = "加载中...";
}
};
const handleSelectPublishPhoto = async () => {
try {
loading.value = true;
if (publishPhotoEnquiry.value) {
if (publishPhotoEnquiry.value.keys.length > 0) {
// 先把选中的专辑名取出来:关掉弹窗后组件可能被销毁,就取不到了
const publishName = publishPhotoEnquiry.value.keys[0].PublishName;
// 先关弹窗再发请求,遮罩才看得清
showModalPublishPhoto.value = false;
let data: PublishData;
for (let i = 0; i < keys.value.length; i++) {
data = new PublishData();
data.FileId = keys.value[i].FileId;
data.PublishName = publishName;
await publishPhotoService.InsertPublishPhotoDetail(data);
}
notification.info({
message: "系统提示:",
description: "作品发布成功",
placement: "topRight",
});
} else {
notification.info({
message: "系统提示:",
description: "请选择一条记录",
placement: "topRight",
});
}
}
} catch (error) {
notification.info({
message: "系统提示:",
description: "作品发布失败",
placement: "topRight",
});
}
finally {
loading.value = false;
}
};
const handleSelectPublishAudio = async () => {
try {
loading.value = true;
if (publishAudioEnquiry.value) {
if (publishAudioEnquiry.value.keys.length > 0) {
// 先把选中的专辑名取出来:关掉弹窗后组件可能被销毁,就取不到了
const publishName = publishAudioEnquiry.value.keys[0].PublishName;
// 先关弹窗再发请求,遮罩才看得清
showModalPublishAudio.value = false;
let data: PublishData;
for (let i = 0; i < keys.value.length; i++) {
data = new PublishData();
data.FileId = keys.value[i].FileId;
data.PublishName = publishName;
await publishAudioService.InsertPublishAudioDetail(data);
}
notification.info({
message: "系统提示:",
description: "专辑发布成功",
placement: "topRight",
});
} else {
notification.info({
message: "系统提示:",
description: "请选择一条记录",
placement: "topRight",
});
}
}
} catch (error) {
notification.info({
message: "系统提示:",
description: "专辑发布失败",
placement: "topRight",
});
}
finally {
loading.value = false;
}
};
const handleSelectPublishVideo = async () => {
try {
loading.value = true;
if (publishVideoEnquiry.value) {
if (publishVideoEnquiry.value.keys.length > 0) {
// 先把选中的专辑名取出来:关掉弹窗后组件可能被销毁,就取不到了
const publishName = publishVideoEnquiry.value.keys[0].PublishName;
// 先关弹窗再发请求,遮罩才看得清
showModalPublishVideo.value = false;
let data: PublishData;
for (let i = 0; i < keys.value.length; i++) {
data = new PublishData();
data.FileId = keys.value[i].FileId;
data.PublishName = publishName;
await publishVideoService.InsertPublishVideoDetail(data);
}
notification.info({
message: "系统提示:",
description: "专辑发布成功",
placement: "topRight",
});
} else {
notification.info({
message: "系统提示:",
description: "请选择一条记录",
placement: "topRight",
});
}
}
} catch (error) {
notification.info({
message: "系统提示:",
description: "专辑发布失败",
placement: "topRight",
});
}
finally {
loading.value = false;
}
};
const onGoTop = () => {
pathStack.value = [{ value: rootPath.value, displayName: "我的云盘" }];
onSearchByFilePath(rootPath.value);
};
const onGoBack = () => {
if (pathStack.value.length > 1) {
pathStack.value.pop();
const prev = pathStack.value[pathStack.value.length - 1];
onSearchByFilePath(prev.value);
}
};
const onBreadcrumbClick = (item: BreadcrumbItem, index: number) => {
pathStack.value = pathStack.value.slice(0, index + 1);
onSearchByFilePath(item.value);
};
const onSearchByName = (keyword: string) => {
let c: DmsFileSearchCriteria = new DmsFileSearchCriteria();
let filters: SearchFilter[] = [];
let filePathFilter: SearchFilter = new SearchFilter();
filePathFilter.Column = DmsFileUploadViewDisplayData.DB_FILE_PATH;
filePathFilter.Type = "SingleSelection";
filePathFilter.Operator = "single";
filePathFilter.Value = currFilePath.value;
filters.push(filePathFilter);
if (keyword && keyword.trim()) {
let nameFilter: SearchFilter = new SearchFilter();
nameFilter.Column = DmsFileUploadViewDisplayData.DB_ORG_FILE_NAME;
nameFilter.Type = SearchFilterTypePolicy.TEXT;
nameFilter.Operator = SearchFilterOperatorPolicy.CONTAIN;
nameFilter.Value = keyword.trim();
filters.push(nameFilter);
}
c.Filter = filters;
c.limit = pagingParams.value.pageSize;
c.page = 1;
c.GridColumns = [];
currentSearchCriteria.value = c;
setBindOptionValue(
DmsFileUploadViewDisplayData.DB_FILE_PATH,
currFilePath.value,
"",
[currFilePath.value],
false,
);
pagingParams.value.page = 1;
findBySearchCriteria(c);
};
const onOpenFolder = (record: DmsFileUploadViewDisplayData) => {
if (record.IsDirectoryIndc == "Y") {
pathStack.value.push({
value: record.FileName,
displayName: record.OrgFileName,
});
onChangeFolder(record.FileName);
}
};
const onOpenFile = (record: DmsFileUploadViewDisplayData) => {
if (fileTypePolicy.isImage(record.OrgFileName)) {
onPreviewFile(record.CreatedBy, record.OrderKey);
} else if (fileTypePolicy.isVideo(record.OrgFileName)) {
onUrlVideoPlayItem(record);
} else if (fileTypePolicy.isAudio(record.OrgFileName)) {
onPlayAudio(toUploadView(record));
} else {
onDownloadFile(record);
}
};
const onDownloadSelected = async (items: DmsFileUploadViewDisplayData[]) => {
// 逐个串行下载:并发触发多个 <a download> 浏览器会拦截后面的
for (const item of items) {
if (item.IsDirectoryIndc != "Y") {
await onDownloadFile(item);
}
}
};
const onShareSelected = (items: DmsFileUploadViewDisplayData[]) => {
const files = items.filter((item) => item.IsDirectoryIndc != "Y");
// 只选中目录时原来什么都不发生,这里给个提示
if (files.length === 0) {
notification.info({
message: "系统提示:",
description: "请选择要共享的文件",
placement: "topRight",
});
return;
}
onShareFamily(files);
};
const onPublishSelected = async (items: DmsFileUploadViewDisplayData[]) => {
const files = items.filter((item) => item.IsDirectoryIndc != "Y");
if (files.length === 0) return;
try {
loading.value = true;
const first = files[0];
const mediaKey = (item: DmsFileUploadViewDisplayData) =>
item.OrderKey || item.OrgFileName || "";
if (fileTypePolicy.isImage(mediaKey(first))) {
keys.value = files.filter((item) => fileTypePolicy.isImage(mediaKey(item)));
await onPublishPhoto();
} else if (fileTypePolicy.isVideo(mediaKey(first))) {
keys.value = files.filter((item) => fileTypePolicy.isVideo(mediaKey(item)));
await onPublishVideo();
} else if (fileTypePolicy.isAudio(mediaKey(first))) {
keys.value = files.filter((item) => fileTypePolicy.isAudio(mediaKey(item)));
await onPublishAudio();
}
} catch (error) {
notification.info({
message: "系统提示:",
description: "打开发布窗口失败",
placement: "topRight",
});
} finally {
loading.value = false;
}
};
const onViewPreviewSelected = (items: DmsFileUploadViewDisplayData[]) => {
if (items.length === 0) return;
const first = items[0];
if (
fileTypePolicy.isImage(first.OrderKey) ||
fileTypePolicy.isImage(first.OrgFileName)
) {
onViewPreviewPhoto(first.FilePath);
} else if (
fileTypePolicy.isVideo(first.OrderKey) ||
fileTypePolicy.isVideo(first.OrgFileName)
) {
onViewPreviewVideo(first.FilePath);
} else if (
fileTypePolicy.isAudio(first.OrderKey) ||
fileTypePolicy.isAudio(first.OrgFileName)
) {
onViewPreviewAudio(first.FilePath);
}
};
/** 播放视频:把当前目录的视频串成播放列表,从点中的那一个开始 */
const onUrlVideoPlayItem = (record: DmsFileUploadViewDisplayData) => {
const list = buildVideoList(datas.value);
let index = list.findIndex((x) => x.Url === record.Url);
if (index < 0) {
list.unshift(toVideoItem(record));
index = 0;
}
openVideoPlay(list, index, record.OrgFileName);
};
const onPlayAudioItem = (record: DmsFileUploadViewDisplayData) => {
onPlayAudio(record);
};
const onPreviewSelected = (items: DmsFileUploadViewDisplayData[]) => {
if (items.length > 0) {
keys.value = items;
onViewPic();
}
};
const onAmendSelected = (items: DmsFileUploadViewDisplayData[]) => {
if (items.length === 1) {
keys.value = items;
onAmend();
}
};
const onMoveSelected = (items: DmsFileUploadViewDisplayData[]) => {
if (items.length === 1) {
keys.value = items;
onMove();
}
};
const onDeleteSelected = () => {
onDelete();
};
</script>
<style scoped>
.dms-file-enquiry {
display: flex;
flex-direction: column;
height: calc(100vh - 120px);
}
</style>