238 lines
5.5 KiB
Vue
238 lines
5.5 KiB
Vue
<template>
|
|
<teleport to="body">
|
|
<t-image-viewer
|
|
v-model:visible="viewerVisible"
|
|
:images="images"
|
|
show-index
|
|
@close="handleClose"
|
|
@index-change="handleIndexChange"
|
|
>
|
|
<template #image="params">
|
|
<div
|
|
v-if="isVideoViewerItem(params.index)"
|
|
:class="[params.className, 'viewer-video-wrap']"
|
|
:style="params.style"
|
|
@click.stop
|
|
@pointerdown.stop
|
|
@pointermove.stop
|
|
@pointerup.stop
|
|
@touchstart.stop
|
|
@touchmove.stop
|
|
@touchend.stop
|
|
>
|
|
<video
|
|
:src="getViewerMediaURL(params.index)"
|
|
class="viewer-video"
|
|
controls
|
|
playsinline
|
|
preload="metadata"
|
|
@loadedmetadata="handleViewerVideoLoaded($event, params)"
|
|
@transitionstart="params.onTransitionstart"
|
|
@transitionend="params.onTransitionend"
|
|
/>
|
|
</div>
|
|
<img
|
|
v-else
|
|
:src="params.src"
|
|
:class="params.className"
|
|
:style="params.style"
|
|
:alt="getViewerAlt(params.index)"
|
|
@load="params.onLoad"
|
|
@transitionstart="params.onTransitionstart"
|
|
@transitionend="params.onTransitionend"
|
|
/>
|
|
</template>
|
|
<template #delete-btn>
|
|
<div v-if="showActions" class="viewer-action" @click.stop>
|
|
<t-popover
|
|
v-model:visible="photoActionVisible"
|
|
placement="bottom"
|
|
theme="light"
|
|
:show-arrow="false"
|
|
>
|
|
<t-button class="white" variant="text">
|
|
<t-icon name="ellipsis" />
|
|
</t-button>
|
|
<template #content>
|
|
<div class="actions" @click.stop>
|
|
<div class="title">修改类型</div>
|
|
<div class="type">
|
|
<t-button
|
|
v-for="item in attachTypeOptions"
|
|
:key="item.value"
|
|
:theme="item.value === currentPhotoItem?.attachType ? 'primary' : 'default'"
|
|
variant="text"
|
|
size="extra-small"
|
|
:disabled="submitting"
|
|
@click="changePhotoType(item.value)"
|
|
>
|
|
<span v-text="item.text" />
|
|
</t-button>
|
|
</div>
|
|
<t-divider />
|
|
<t-button
|
|
class="button delete-action"
|
|
theme="danger"
|
|
size="extra-small"
|
|
variant="text"
|
|
:disabled="submitting"
|
|
@click="removeCurrentPhoto"
|
|
>
|
|
<template #icon>
|
|
<t-icon name="delete" />
|
|
</template>
|
|
删除
|
|
</t-button>
|
|
</div>
|
|
</template>
|
|
</t-popover>
|
|
</div>
|
|
</template>
|
|
</t-image-viewer>
|
|
</teleport>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ImageSlotParams } from "tdesign-mobile-vue";
|
|
import type { MediaSelectorViewerItem } from "./types";
|
|
import { getAttachTypeText, getMediaPreviewURL, isVideoItem } from "./utils";
|
|
|
|
interface Props {
|
|
images: string[];
|
|
items: readonly MediaSelectorViewerItem[];
|
|
showActions: boolean;
|
|
submitting: boolean;
|
|
visible: boolean;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
const emit = defineEmits<{
|
|
(event: "update:visible", value: boolean): void;
|
|
(event: "change-type", sourceIndex: number, attachType: string): void;
|
|
(event: "remove", sourceIndex: number): void;
|
|
}>();
|
|
|
|
const viewerVisible = computed({
|
|
get: () => props.visible,
|
|
set: value => emit("update:visible", value)
|
|
});
|
|
const viewerIndex = ref(0);
|
|
const photoActionVisible = ref(false);
|
|
const attachTypeOptions = [
|
|
{ text: "照片", value: "PHOTO" },
|
|
{ text: "积分卡", value: "POINT_CARD" },
|
|
{ text: "其他文件", value: "OTHER" }
|
|
] as const;
|
|
const currentPhotoItem = computed(() => props.items[viewerIndex.value]);
|
|
|
|
watch(() => props.visible, value => {
|
|
if (value) {
|
|
viewerIndex.value = 0;
|
|
photoActionVisible.value = false;
|
|
}
|
|
});
|
|
|
|
function handleClose(): void {
|
|
photoActionVisible.value = false;
|
|
emit("update:visible", false);
|
|
}
|
|
|
|
function handleIndexChange(index: number): void {
|
|
viewerIndex.value = index;
|
|
photoActionVisible.value = false;
|
|
}
|
|
|
|
function isVideoViewerItem(index?: number): boolean {
|
|
const item = getViewerItem(index);
|
|
return !!item && isVideoItem(item);
|
|
}
|
|
|
|
function getViewerItem(index?: number): MediaSelectorViewerItem | undefined {
|
|
return index === undefined ? undefined : props.items[index];
|
|
}
|
|
|
|
function getViewerMediaURL(index?: number): string {
|
|
const item = getViewerItem(index);
|
|
return item ? getMediaPreviewURL(item) : "";
|
|
}
|
|
|
|
function getViewerAlt(index?: number): string {
|
|
return getAttachTypeText(getViewerItem(index)?.attachType);
|
|
}
|
|
|
|
function handleViewerVideoLoaded(event: Event, params: ImageSlotParams): void {
|
|
const video = event.currentTarget as HTMLVideoElement;
|
|
if (video.videoHeight > 0) {
|
|
video.height = video.videoHeight;
|
|
}
|
|
params.onLoad?.(event);
|
|
}
|
|
|
|
function changePhotoType(attachType: string): void {
|
|
const currentItem = currentPhotoItem.value;
|
|
photoActionVisible.value = false;
|
|
if (!currentItem || currentItem.attachType === attachType) {
|
|
return;
|
|
}
|
|
emit("change-type", currentItem.sourceIndex, attachType);
|
|
}
|
|
|
|
function removeCurrentPhoto(): void {
|
|
const currentItem = currentPhotoItem.value;
|
|
photoActionVisible.value = false;
|
|
if (currentItem) {
|
|
emit("remove", currentItem.sourceIndex);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.viewer-action {
|
|
color: #fff;
|
|
margin-left: -1rem;
|
|
|
|
.actions {
|
|
display: flex;
|
|
min-width: 8rem;
|
|
flex-direction: column;
|
|
|
|
.title {
|
|
padding: 0 .75rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.type {
|
|
display: flex;
|
|
justify-content: start;
|
|
flex-direction: column;
|
|
|
|
.button {
|
|
text-indent: 1.5em;
|
|
}
|
|
}
|
|
|
|
.button {
|
|
justify-content: start;
|
|
}
|
|
}
|
|
}
|
|
|
|
.viewer-video-wrap {
|
|
width: 100%;
|
|
height: 100%;
|
|
max-height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.viewer-video {
|
|
width: 100%;
|
|
height: auto;
|
|
max-height: 100%;
|
|
background: #0f172a;
|
|
object-fit: contain;
|
|
}
|
|
</style>
|