ow
This commit is contained in:
1 parent
4adae1bd7d
commit
adcf94df4a
17 files changed
+274
-97
No files matched your search
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div class="psp">
|
||||
<!-- 图片舞台 -->
|
||||
<div class="psp-stage" ref="stageEl" :class="{ 'is-paused': !isPlaying && !!current }"
|
||||
<div class="psp-stage" ref="stageEl"
|
||||
:class="{ 'is-paused': !isPlaying && !!current, 'is-immersive': !controlsVisible }"
|
||||
:style="{ '--psp-kb': interval + 0.8 + 's', '--psp-out-span': outPlan.span + 's' }"
|
||||
:title="isPlaying ? '单击暂停' : '单击看大图'" @click="onStageClick">
|
||||
:title="stageTitle" @click="onStageClick">
|
||||
<!-- 组合类:多图同台 + 相框 / 气泡 / 花瓣 / 彩纸 / 聚光 / 灯柱 / 舞台 / 荧光棒(演唱会等 5 套) -->
|
||||
<!-- out-in:上一屏彻底走完才让下一屏进场,两屏绝不重叠(逐张离场也等最后一张飞完) -->
|
||||
<transition v-if="isShowcase" :name="'psp-fx-' + fxName" :duration="fxDuration" mode="out-in">
|
||||
@@ -71,12 +72,25 @@
|
||||
<PictureOutlined />
|
||||
<p>暂无图片</p>
|
||||
</div>
|
||||
|
||||
<!-- 控制区收起后画面上留一个恢复入口:整页用法没有弹窗右上角按钮,
|
||||
否则收起后就再也点不回来了 -->
|
||||
<button v-if="!controlsVisible" class="psp-restore" type="button" title="显示控制区"
|
||||
@click.stop="toggleControls">
|
||||
<EyeOutlined />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 控制条 -->
|
||||
<footer class="psp-bar">
|
||||
<!-- 控制条(收起后舞台吃掉这部分空间,图片最大化) -->
|
||||
<footer v-if="controlsVisible" class="psp-bar">
|
||||
<!-- 功能图标行 -->
|
||||
<div class="psp-actions">
|
||||
<!-- 隐藏/显示控制区:整页用法时父组件会顺带藏掉 layout 顶栏,做到整页全屏 -->
|
||||
<button class="psp-act" type="button" :class="{ 'is-on': !controlsVisible }"
|
||||
:title="controlsVisible ? '隐藏控制区' : '显示控制区'" @click.stop="toggleControls">
|
||||
<EyeInvisibleOutlined v-if="controlsVisible" />
|
||||
<EyeOutlined v-else />
|
||||
</button>
|
||||
<button class="psp-act" type="button" title="下载当前图片" @click.stop="onDownload">
|
||||
<DownloadOutlined />
|
||||
<span class="psp-badge" :class="{ 'is-on': downloaded }">{{ downloaded ? "" : "" }}</span>
|
||||
@@ -177,8 +191,9 @@
|
||||
</transition>
|
||||
|
||||
<!-- 精看:暂停时单击图片放大看。复用 ant-design 图片预览(自带缩放 / 旋转 / 翻转),
|
||||
图片本体藏起来,预览层由组件自己渲染,不受舞台裁切影响 -->
|
||||
<AImage :src="current?.url || ''" :style="{ display: 'none' }"
|
||||
图片本体藏起来,预览层由组件自己渲染,不受舞台裁切影响。
|
||||
这里用原图(full),舞台轮播放的是缩略图,精看要看清楚 -->
|
||||
<AImage :src="currentFull" :style="{ display: 'none' }"
|
||||
:preview="{ visible: picVisible, onVisibleChange: setPicVisible }" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -187,20 +202,24 @@
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { Image as AImage, message } from "ant-design-vue";
|
||||
import { request } from "@/utils/request";
|
||||
import { useVModel } from "@vueuse/core";
|
||||
import {
|
||||
DownOutlined, DownloadOutlined, CloseOutlined, PictureOutlined, EllipsisOutlined,
|
||||
ThunderboltOutlined, RetweetOutlined, SwapOutlined, StepBackwardFilled,
|
||||
StepForwardFilled, CaretRightFilled, PauseOutlined, UnorderedListOutlined,
|
||||
EyeOutlined, EyeInvisibleOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
|
||||
/** 一张图片 */
|
||||
export interface PhotoSlideItem {
|
||||
/** 原图地址 */
|
||||
/** 展示地址(舞台幻灯片 + 列表缩略图):一般给缩略图,轮播加载更快 */
|
||||
url: string;
|
||||
/** 缩略图地址(列表用,缺省用原图) */
|
||||
/** 缩略图地址(列表用,缺省用 url) */
|
||||
thumb?: string;
|
||||
/** 名称(下载文件名) */
|
||||
name?: string;
|
||||
/** 原图地址:精看与下载用它,缺省退回 url */
|
||||
full?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -208,10 +227,15 @@ const props = withDefaults(
|
||||
items?: PhotoSlideItem[];
|
||||
/** 每张停留秒数 */
|
||||
interval?: number;
|
||||
/** 底部控制区是否显示(页面可用它做「整页全屏」) */
|
||||
controls?: boolean;
|
||||
}>(),
|
||||
{ items: () => [], interval: 5 }
|
||||
{ items: () => [], interval: 5, controls: true }
|
||||
);
|
||||
const emit = defineEmits<{ (e: "close"): void }>();
|
||||
const emit = defineEmits<{
|
||||
(e: "close"): void;
|
||||
(e: "update:controls", visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* ---------------- 基础状态 ---------------- */
|
||||
const items = computed(() => props.items || []);
|
||||
@@ -225,12 +249,30 @@ const showList = ref(false);
|
||||
const menu = ref<"" | "effect" | "more">("");
|
||||
const downloaded = ref(false);
|
||||
|
||||
/**
|
||||
* 底部控制区是否显示:由功能图标行的眼睛图标切换。
|
||||
* 收起后舞台吃掉这部分空间,图片最大化;父组件可用 v-model:controls 拿到这个状态
|
||||
* 去做整页全屏(例如 Preview.vue 藏掉 layout 顶部的两条栏)。
|
||||
*
|
||||
* passive: true 是关键:不传的话 useVModel 直接读写 props,
|
||||
* 父组件没绑 v-model:controls 时内部改了值只会 emit 出去、props 不变,
|
||||
* 按钮就"点了没反应"(与 VideoPlayer 一致)。
|
||||
*/
|
||||
const controlsVisible = useVModel(props, "controls", emit, { passive: true });
|
||||
|
||||
const toggleControls = () => {
|
||||
menu.value = ""; // 顺手收起可能打开的弹层(特效 / 更多)
|
||||
controlsVisible.value = !controlsVisible.value;
|
||||
};
|
||||
|
||||
/** 舞台元素与像素尺寸:圆形格子的直径按舞台短边换算,避免非正方舞台被压成椭圆 */
|
||||
const stageEl = ref<HTMLElement | null>(null);
|
||||
const stageSize = ref({ w: 0, h: 0 });
|
||||
let stageRo: ResizeObserver | null = null;
|
||||
|
||||
const current = computed<PhotoSlideItem | null>(() => items.value[index.value] ?? null);
|
||||
/** 精看与下载用原图(full),没单独给就退回展示地址 */
|
||||
const currentFull = computed(() => current.value?.full || current.value?.url || "");
|
||||
const modeTitle = computed(
|
||||
() => ({ list: "顺序循环", random: "随机播放", once: "播完停止" }[mode.value])
|
||||
);
|
||||
@@ -789,15 +831,30 @@ const picVisible = ref(false);
|
||||
const setPicVisible = (v: boolean) => {
|
||||
picVisible.value = v;
|
||||
};
|
||||
/** 单击舞台:播放中先暂停(原来的行为);已经暂停则打开精看,效果同 DmsFileEnquiry 的单图精看 */
|
||||
/**
|
||||
* 单击舞台:播放中先暂停(原来的行为);已经暂停则打开精看,效果同 DmsFileEnquiry 的单图精看。
|
||||
*
|
||||
* 页内全屏(控制区已收起)时只切暂停/播放:那会儿没有控制条,
|
||||
* 再叠一层大图预览会盖住整屏、也没有恢复入口,容易把人"困住"。
|
||||
*/
|
||||
const onStageClick = () => {
|
||||
if (!items.value.length) return;
|
||||
if (!controlsVisible.value) {
|
||||
toggle();
|
||||
return;
|
||||
}
|
||||
if (isPlaying.value) {
|
||||
toggle();
|
||||
return;
|
||||
}
|
||||
setPicVisible(true);
|
||||
};
|
||||
|
||||
/** 舞台提示:页内全屏时没有"看大图",文案跟着变,免得点了跟说好的不一样 */
|
||||
const stageTitle = computed(() => {
|
||||
if (!controlsVisible.value) return isPlaying.value ? "单击暂停" : "单击播放";
|
||||
return isPlaying.value ? "单击暂停" : "单击看大图";
|
||||
});
|
||||
const playAt = (i: number) => {
|
||||
index.value = i;
|
||||
elapsed.value = 0;
|
||||
@@ -848,13 +905,15 @@ const onDownload = async () => {
|
||||
if (!it || downloading.value) return;
|
||||
downloading.value = true;
|
||||
try {
|
||||
// 下载要原图,不是舞台上那张缩略图
|
||||
const url = currentFull.value;
|
||||
const res = await request<ArrayBuffer>({
|
||||
url: it.url,
|
||||
url,
|
||||
method: "get",
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
const blobUrl = window.URL.createObjectURL(new Blob([res]));
|
||||
const ext = (it.url.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1] || "jpg";
|
||||
const ext = (url.match(/\.(\w{2,5})(?:\?|#|$)/) || [])[1] || "jpg";
|
||||
const a = document.createElement("a");
|
||||
a.href = blobUrl;
|
||||
a.download = `${it.name || "image"}.${ext}`;
|
||||
@@ -963,11 +1022,37 @@ onBeforeUnmount(() => {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 暂停后单击就是精看,光标跟着变 */
|
||||
.psp-stage.is-paused {
|
||||
/* 暂停后单击就是精看,光标跟着变;
|
||||
页内全屏(.is-immersive)时单击是继续播放,不给放大镜光标,免得误导 */
|
||||
.psp-stage.is-paused:not(.is-immersive) {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
/* 控制区收起后的恢复入口(整页用法没有弹窗右上角按钮) */
|
||||
.psp-restore {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, .38);
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
opacity: .55;
|
||||
transition: opacity .2s;
|
||||
}
|
||||
|
||||
.psp-restore:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.psp-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
||||
Reference in new issue
Block a user