Files
Docker/WebVue/src/components/basic/OperatorStation.vue
T
2026-09-23 16:32:57 +08:00

330 lines
10 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-row :wrap="false">
<a-col flex="auto">
<a-row>
<a-col flex="400px">
<a-row justify="start" class="ops-row">
<a-col flex="32px" style="margin-left: 8px">
<a-tooltip title="新建文件夹">
<a-button v-if="canCreate" class="nav-btn" :icon="h(FolderAddOutlined)"
@click="$emit('create-folder')"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px">
<a-tooltip title="上传文件">
<a-button v-if="canCreate" :icon="h(UploadOutlined)" type="primary" class="nav-btn"
@click="triggerUpload">
</a-button> </a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && (isSingleItem || isSameMediaTypeSelected)">
<a-tooltip title="共享">
<a-button class="nav-btn" :icon="h(ShareAltOutlined)" @click="onShare"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && (isSingleItem || isSameMediaTypeSelected)">
<a-tooltip :title="publishTooltip">
<a-button class="nav-btn" :icon="h(NotificationOutlined)" @click="onPublish"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && isSingleItem">
<a-tooltip title="下载">
<a-button class="nav-btn" :icon="h(DownloadOutlined)" :disabled="!canAmend"
@click="onDownload"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="isSingleItem">
<a-tooltip title="重命名">
<a-button class="nav-btn" :icon="h(EditOutlined)" :disabled="!canAmend"
@click="$emit('rename', selectedItems)"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="isSingleItem">
<a-tooltip title="移动">
<a-button class="nav-btn" :icon="h(RotateRightOutlined)" :disabled="!canAmend"
@click="$emit('move', selectedItems)"></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="hasSelection">
<a-tooltip title="删除">
<a-popconfirm title="确定是否删除?" ok-text="是" cancel-text="否" @confirm="$emit('delete', selectedItems)">
<a-button class="nav-btn" :icon="h(DeleteOutlined, { style: { color: 'red' } })"
:disabled="!canDelete"></a-button>
</a-popconfirm>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && isSingleItem">
<a-tooltip :title="previewTooltip">
<a-button class="nav-btn" @click="onViewPreview"><cust-icon-font size="16"
type="icon-qingbaojiankong" /></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && isSingleItem && isSingleVideo">
<a-tooltip>
<a-button class="nav-btn" @click="onPlayVideo"><cust-icon-font size="16"
type="icon-foldervideo" /></a-button>
</a-tooltip>
</a-col>
<a-col flex="32px" v-if="!isDirectorySelected && isSingleItem && isSingleAudio">
<a-tooltip v-if="selectedItem.IsPlay != 'Y'">
<a-button class="nav-btn" :icon="h(PlayCircleOutlined, { style: { color: 'red' } })"
@click="onPlayAudio"></a-button>
</a-tooltip>
<a-tooltip v-else-if="selectedItem.IsPause != 'Y'">
<a-button class="nav-btn" :icon="h(PauseCircleOutlined)" @click="onPauseAudio"></a-button>
</a-tooltip>
<a-tooltip v-else>
<a-button class="nav-btn" :icon="h(PlayCircleOutlined, { style: { color: 'red' } })"
@click="onContinueAudio"></a-button>
</a-tooltip>
</a-col>
</a-row>
</a-col>
</a-row>
</a-col>
<a-col :span="8" flex="none">
</a-col>
</a-row>
<FileUpload v-if="canCreate" ref="fileUploadRef" :action="uploadUrl" :headers="headers"
:resolve-file-path="resolveFilePath" :multiple="multiple" @start="onUploadStart" @progress="onUploadProgress"
@success="onUploadSuccess" @complete="onUploadComplete" @interrupted="onUploadInterrupted" @error="onUploadError" />
</template>
<script setup lang="ts">
import { computed, ref, h } from "vue";
import {
UploadOutlined,
FolderAddOutlined,
DownloadOutlined,
ShareAltOutlined,
NotificationOutlined,
UnorderedListOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
EyeOutlined,
EditOutlined,
ScissorOutlined,
DeleteOutlined,
RotateRightOutlined,
DownOutlined,
} from "@ant-design/icons-vue";
import { DmsFileUploadViewDisplayData } from "@/data/auto/dmsFileUploadViewDisplayData";
import FileUpload from "@/components/basic/FileUpload.vue";
import fileTypePolicy from "@/policy/custom/fileTypePolicy";
const props = defineProps<{
selectedItems: DmsFileUploadViewDisplayData[];
uploadUrl: string;
headers: any;
resolveFilePath?: () => string | Promise<string>;
/** 上传时是否允许一次选多个文件,默认只允许选一个 */
multiple?: boolean;
canCreate: boolean;
canAmend: boolean;
canDelete: boolean;
canView: boolean;
}>();
const emit = defineEmits([
"create-folder",
"upload-start",
"upload-progress",
"upload-success",
"upload-complete",
"upload-interrupted",
"upload-error",
"download",
"share",
"publish",
"view-preview",
"play-video",
"play-audio",
"pause-audio",
"continue-audio",
"preview",
"rename",
"move",
"delete",
]);
const fileUploadRef = ref<any>(null);
const hasSelection = computed(() => props.selectedItems.length > 0);
const isDirectorySelected = computed(() =>
props.selectedItems.some((s) => s.IsDirectoryIndc == "Y"),
);
const isSingleItem = computed(() => props.selectedItems.length === 1);
const selectedItem = computed(() => {
return props.selectedItems[0] || ({} as DmsFileUploadViewDisplayData);
});
const selectedOrderKey = computed(() => {
return selectedItem.value?.OrderKey || selectedItem.value?.OrgFileName || "";
});
const isImage = (key: string) => {
if (!key) return false;
return /\.(png|jpe?g|gif|bmp|webp)$/i.test(key);
};
const isVideo = (key: string) => {
if (!key) return false;
return /\.(mp4|m3u8|rm|mpeg|mov|wmv|avi)$/i.test(key);
};
const isAudio = (key: string) => {
if (!key) return false;
return /\.(mp3|wav|flac|aac|ogg|wma)$/i.test(key);
};
const isSingleVideo = computed(() => {
return isSingleItem.value && isVideo(selectedOrderKey.value);
});
const isSingleAudio = computed(() => {
return isSingleItem.value && isAudio(selectedOrderKey.value);
});
/**
* 识别媒体类型,兼顾本地扩展名判断与全局 fileTypePolicy,
* 保证与发布时的类型判断一致。
*/
const mediaKind = (key: string): "image" | "video" | "audio" | "" => {
if (isImage(key) || fileTypePolicy.isImage(key)) return "image";
if (isVideo(key) || fileTypePolicy.isVideo(key)) return "video";
if (isAudio(key) || fileTypePolicy.isAudio(key)) return "audio";
return "";
};
/**
* 多选时选中项是否全部属于同一媒体类型(图片/视频/音频)。
* 满足时允许批量共享与发布。
*/
const isSameMediaTypeSelected = computed(() => {
const items = props.selectedItems;
if (items.length <= 1) return false;
const kinds = items.map((x) => mediaKind(x.OrderKey || x.OrgFileName || ""));
const kind = kinds[0];
return kind !== "" && kinds.every((k) => k === kind);
});
const publishTooltip = computed(() => {
if (isImage(selectedOrderKey.value)) return "发布相册";
if (isVideo(selectedOrderKey.value)) return "发布影视专辑";
if (isAudio(selectedOrderKey.value)) return "发布音乐专辑";
return "发布";
});
const previewTooltip = computed(() => {
if (isImage(selectedOrderKey.value)) return "相册展览";
if (isVideo(selectedOrderKey.value)) return "影视厅播放";
if (isAudio(selectedOrderKey.value)) return "音乐厅播放";
return "预览";
});
const triggerUpload = () => {
fileUploadRef.value?.trigger();
};
const onUploadStart = (file: File) => {
emit("upload-start", file);
};
const onUploadProgress = (payload: any) => {
emit("upload-progress", payload);
};
const onUploadSuccess = (payload: any) => {
emit("upload-success", payload);
};
/** 一批文件全部传完(多选时用它统一刷新一次,避免每个文件都刷一遍列表) */
const onUploadComplete = (payload: any) => {
emit("upload-complete", payload);
};
/** 上传中断:断点还在,交给页面去问用户要不要续传 */
const onUploadInterrupted = (payload: any) => {
emit("upload-interrupted", payload);
};
/** 从断点继续上传 */
const resumeUpload = () => {
fileUploadRef.value?.resume();
};
/** 放弃这次上传,清掉断点 */
const discardUpload = () => {
fileUploadRef.value?.discard();
};
defineExpose({ resumeUpload, discardUpload });
const onUploadError = (error: unknown) => {
emit("upload-error", error);
};
const onDownload = () => {
emit("download", props.selectedItems);
};
const onShare = () => {
emit("share", props.selectedItems);
};
const onPublish = () => {
emit("publish", props.selectedItems);
};
const onViewPreview = () => {
emit("view-preview", props.selectedItems);
};
const onPlayVideo = () => {
emit("play-video", selectedItem.value);
};
const onPlayAudio = () => {
emit("play-audio", selectedItem.value);
};
const onPauseAudio = () => {
emit("pause-audio", selectedItem.value);
};
const onContinueAudio = () => {
emit("continue-audio", selectedItem.value);
};
</script>
<style scoped>
/* 按钮之间统一 8px 间距。
原来是靠 24 栅格 span=2 撑开的,间距会随容器宽度变化(400px 时只有 1px 左右),
现在按钮列固定 32px(与 .nav-btn 同宽),真正的间距交给 gap:8px */
.ops-row {
gap: 4px;
margin-top: 4px;
flex-wrap: nowrap;
}
.nav-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.nav-btn:hover:not(:disabled) {
background: #e6f4ff;
color: #1677ff;
}
.nav-btn:disabled {
color: #bfbfbf;
}
</style>