提交 e23bd269 编写于 作者: 无木

feat(preview): add more features

为Preview组件添加新的属性及事件
上级 98749ec6
### ✨ Features
- **Preview** 添加新的属性及事件
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- **ApiTreeSelect** 修复未能正确监听`params`变化的问题 - **ApiTreeSelect** 修复未能正确监听`params`变化的问题
......
...@@ -38,13 +38,33 @@ ...@@ -38,13 +38,33 @@
type: Number as PropType<number>, type: Number as PropType<number>,
default: 0, default: 0,
}, },
scaleStep: {
type: Number as PropType<number>,
},
defaultWidth: {
type: Number as PropType<number>,
},
maskClosable: {
type: Boolean as PropType<boolean>,
},
rememberState: {
type: Boolean as PropType<boolean>,
},
}; };
const prefixCls = 'img-preview'; const prefixCls = 'img-preview';
export default defineComponent({ export default defineComponent({
name: 'ImagePreview', name: 'ImagePreview',
props, props,
setup(props: Props) { emits: ['img-load', 'img-error'],
setup(props: Props, { expose, emit }) {
interface stateInfo {
scale: number;
rotate: number;
top: number;
left: number;
}
const stateMap = new Map<string, stateInfo>();
const imgState = reactive<ImgState>({ const imgState = reactive<ImgState>({
currentUrl: '', currentUrl: '',
imgScale: 1, imgScale: 1,
...@@ -96,6 +116,14 @@ ...@@ -96,6 +116,14 @@
}; };
} }
const getScaleStep = computed(() => {
if (props.scaleStep > 0 && props.scaleStep < 100) {
return props.scaleStep / 100;
} else {
return imgState.imgScale / 10;
}
});
// 监听鼠标滚轮 // 监听鼠标滚轮
function scrollFunc(e: any) { function scrollFunc(e: any) {
e = e || window.event; e = e || window.event;
...@@ -104,11 +132,11 @@ ...@@ -104,11 +132,11 @@
e.preventDefault(); e.preventDefault();
if (e.delta > 0) { if (e.delta > 0) {
// 滑轮向上滚动 // 滑轮向上滚动
scaleFunc(0.015); scaleFunc(getScaleStep.value);
} }
if (e.delta < 0) { if (e.delta < 0) {
// 滑轮向下滚动 // 滑轮向下滚动
scaleFunc(-0.015); scaleFunc(-getScaleStep.value);
} }
} }
// 缩放函数 // 缩放函数
...@@ -134,11 +162,54 @@ ...@@ -134,11 +162,54 @@
imgState.status = StatueEnum.LOADING; imgState.status = StatueEnum.LOADING;
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = () => { img.onload = (e: Event) => {
if (imgState.currentUrl !== url) {
const ele: HTMLElement[] = e.composedPath();
if (props.rememberState) {
// 保存当前图片的缩放信息
stateMap.set(imgState.currentUrl, {
scale: imgState.imgScale,
top: imgState.imgTop,
left: imgState.imgLeft,
rotate: imgState.imgRotate,
});
// 如果之前已存储缩放信息,就应用
const stateInfo = stateMap.get(url);
if (stateInfo) {
imgState.imgScale = stateInfo.scale;
imgState.imgTop = stateInfo.top;
imgState.imgRotate = stateInfo.rotate;
imgState.imgLeft = stateInfo.left;
} else {
initState();
if (props.defaultWidth) {
imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
}
}
} else {
if (props.defaultWidth) {
imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
}
}
ele &&
emit('img-load', {
index: imgState.currentIndex,
dom: ele[0] as HTMLImageElement,
url,
});
}
imgState.currentUrl = url; imgState.currentUrl = url;
imgState.status = StatueEnum.DONE; imgState.status = StatueEnum.DONE;
}; };
img.onerror = () => { img.onerror = (e: Event) => {
const ele: EventTarget[] = e.composedPath();
ele &&
emit('img-error', {
index: imgState.currentIndex,
dom: ele[0] as HTMLImageElement,
url,
});
imgState.status = StatueEnum.FAIL; imgState.status = StatueEnum.FAIL;
}; };
} }
...@@ -146,6 +217,10 @@ ...@@ -146,6 +217,10 @@
// 关闭 // 关闭
function handleClose(e: MouseEvent) { function handleClose(e: MouseEvent) {
e && e.stopPropagation(); e && e.stopPropagation();
close();
}
function close() {
imgState.show = false; imgState.show = false;
// 移除火狐浏览器下的鼠标滚动事件 // 移除火狐浏览器下的鼠标滚动事件
document.body.removeEventListener('DOMMouseScroll', scrollFunc); document.body.removeEventListener('DOMMouseScroll', scrollFunc);
...@@ -158,6 +233,19 @@ ...@@ -158,6 +233,19 @@
initState(); initState();
} }
expose({
resume,
close,
prev: handleChange.bind(null, 'left'),
next: handleChange.bind(null, 'right'),
setScale: (scale: number) => {
if (scale > 0 && scale <= 10) imgState.imgScale = scale;
},
setRotate: (rotate: number) => {
imgState.imgRotate = rotate;
},
} as PreviewActions);
// 上一页下一页 // 上一页下一页
function handleChange(direction: 'left' | 'right') { function handleChange(direction: 'left' | 'right') {
const { currentIndex } = imgState; const { currentIndex } = imgState;
...@@ -205,6 +293,7 @@ ...@@ -205,6 +293,7 @@
transform: `scale(${imgScale}) rotate(${imgRotate}deg)`, transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
marginTop: `${imgTop}px`, marginTop: `${imgTop}px`,
marginLeft: `${imgLeft}px`, marginLeft: `${imgLeft}px`,
maxWidth: props.defaultWidth ? 'unset' : '100%',
}; };
}); });
...@@ -222,6 +311,16 @@ ...@@ -222,6 +311,16 @@
} }
}); });
const handleMaskClick = (e: MouseEvent) => {
if (
props.maskClosable &&
e.target &&
(e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`)
) {
handleClose(e);
}
};
const renderClose = () => { const renderClose = () => {
return ( return (
<div class={`${prefixCls}__close`} onClick={handleClose}> <div class={`${prefixCls}__close`} onClick={handleClose}>
...@@ -246,10 +345,16 @@ ...@@ -246,10 +345,16 @@
const renderController = () => { const renderController = () => {
return ( return (
<div class={`${prefixCls}__controller`}> <div class={`${prefixCls}__controller`}>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}> <div
class={`${prefixCls}__controller-item`}
onClick={() => scaleFunc(-getScaleStep.value)}
>
<img src={unScaleSvg} /> <img src={unScaleSvg} />
</div> </div>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}> <div
class={`${prefixCls}__controller-item`}
onClick={() => scaleFunc(getScaleStep.value)}
>
<img src={scaleSvg} /> <img src={scaleSvg} />
</div> </div>
<div class={`${prefixCls}__controller-item`} onClick={resume}> <div class={`${prefixCls}__controller-item`} onClick={resume}>
...@@ -279,7 +384,12 @@ ...@@ -279,7 +384,12 @@
return () => { return () => {
return ( return (
imgState.show && ( imgState.show && (
<div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}> <div
class={prefixCls}
ref={wrapElRef}
onMouseup={handleMouseUp}
onClick={handleMaskClick}
>
<div class={`${prefixCls}-content`}> <div class={`${prefixCls}-content`}>
{/*<Spin*/} {/*<Spin*/}
{/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/} {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
......
...@@ -6,15 +6,12 @@ import { createVNode, render } from 'vue'; ...@@ -6,15 +6,12 @@ import { createVNode, render } from 'vue';
let instance: ReturnType<typeof createVNode> | null = null; let instance: ReturnType<typeof createVNode> | null = null;
export function createImgPreview(options: Options) { export function createImgPreview(options: Options) {
if (!isClient) return; if (!isClient) return;
const { imageList, show = true, index = 0 } = options;
const propsData: Partial<Props> = {}; const propsData: Partial<Props> = {};
const container = document.createElement('div'); const container = document.createElement('div');
propsData.imageList = imageList; Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
propsData.show = show;
propsData.index = index;
instance = createVNode(ImgPreview, propsData); instance = createVNode(ImgPreview, propsData);
render(instance, container); render(instance, container);
document.body.appendChild(container); document.body.appendChild(container);
return instance.component?.exposed;
} }
...@@ -2,6 +2,12 @@ export interface Options { ...@@ -2,6 +2,12 @@ export interface Options {
show?: boolean; show?: boolean;
imageList: string[]; imageList: string[];
index?: number; index?: number;
scaleStep?: number;
defaultWidth?: number;
maskClosable?: boolean;
rememberState?: boolean;
onImgLoad?: (img: HTMLImageElement) => void;
onImgError?: (img: HTMLImageElement) => void;
} }
export interface Props { export interface Props {
...@@ -9,6 +15,19 @@ export interface Props { ...@@ -9,6 +15,19 @@ export interface Props {
instance: Props; instance: Props;
imageList: string[]; imageList: string[];
index: number; index: number;
scaleStep: number;
defaultWidth: number;
maskClosable: boolean;
rememberState: boolean;
}
export interface PreviewActions {
resume: () => void;
close: () => void;
prev: () => void;
next: () => void;
setScale: (scale: number) => void;
setRotate: (rotate: number) => void;
} }
export interface ImageProps { export interface ImageProps {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { createImgPreview, ImagePreview } from '/@/components/Preview/index'; import { createImgPreview, ImagePreview } from '/@/components/Preview/index';
import { PageWrapper } from '/@/components/Page'; import { PageWrapper } from '/@/components/Page';
// import { PreviewActions } from '/@/components/Preview/src/typing';
const imgList: string[] = [ const imgList: string[] = [
'https://picsum.photos/id/66/346/216', 'https://picsum.photos/id/66/346/216',
...@@ -18,7 +19,11 @@ ...@@ -18,7 +19,11 @@
components: { PageWrapper, ImagePreview }, components: { PageWrapper, ImagePreview },
setup() { setup() {
function openImg() { function openImg() {
createImgPreview({ imageList: imgList }); const onImgLoad = ({ index, url, dom }) => {
console.log(`第${index + 1}张图片已加载,URL为:${url}`, dom);
};
// 可以使用createImgPreview返回的 PreviewActions 来控制预览逻辑,实现类似幻灯片、自动旋转之类的骚操作
createImgPreview({ imageList: imgList, defaultWidth: 700, rememberState: true, onImgLoad });
} }
return { imgList, openImg }; return { imgList, openImg };
}, },
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册