19 changed files with 460 additions and 474 deletions
@ -0,0 +1,436 @@ |
|||||
|
<script lang="tsx"> |
||||
|
import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue'; |
||||
|
import { Props } from './typing'; |
||||
|
import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; |
||||
|
import resumeSvg from '/@/assets/svg/preview/resume.svg'; |
||||
|
import rotateSvg from '/@/assets/svg/preview/p-rotate.svg'; |
||||
|
import scaleSvg from '/@/assets/svg/preview/scale.svg'; |
||||
|
import unScaleSvg from '/@/assets/svg/preview/unscale.svg'; |
||||
|
import unRotateSvg from '/@/assets/svg/preview/unrotate.svg'; |
||||
|
|
||||
|
enum StatueEnum { |
||||
|
LOADING, |
||||
|
DONE, |
||||
|
FAIL, |
||||
|
} |
||||
|
interface ImgState { |
||||
|
currentUrl: string; |
||||
|
imgScale: number; |
||||
|
imgRotate: number; |
||||
|
imgTop: number; |
||||
|
imgLeft: number; |
||||
|
currentIndex: number; |
||||
|
status: StatueEnum; |
||||
|
moveX: number; |
||||
|
moveY: number; |
||||
|
show: boolean; |
||||
|
} |
||||
|
const props = { |
||||
|
show: { |
||||
|
type: Boolean as PropType<boolean>, |
||||
|
default: false, |
||||
|
}, |
||||
|
imageList: { |
||||
|
type: [Array] as PropType<string[]>, |
||||
|
default: null, |
||||
|
}, |
||||
|
index: { |
||||
|
type: Number as PropType<number>, |
||||
|
default: 0, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
const prefixCls = 'img-preview'; |
||||
|
export default defineComponent({ |
||||
|
name: 'ImagePreview', |
||||
|
props, |
||||
|
setup(props: Props) { |
||||
|
const imgState = reactive<ImgState>({ |
||||
|
currentUrl: '', |
||||
|
imgScale: 1, |
||||
|
imgRotate: 0, |
||||
|
imgTop: 0, |
||||
|
imgLeft: 0, |
||||
|
status: StatueEnum.LOADING, |
||||
|
currentIndex: 0, |
||||
|
moveX: 0, |
||||
|
moveY: 0, |
||||
|
show: props.show, |
||||
|
}); |
||||
|
|
||||
|
const wrapElRef = ref<HTMLDivElement | null>(null); |
||||
|
const imgElRef = ref<HTMLImageElement | null>(null); |
||||
|
|
||||
|
// 初始化 |
||||
|
function init() { |
||||
|
initMouseWheel(); |
||||
|
const { index, imageList } = props; |
||||
|
|
||||
|
if (!imageList || !imageList.length) { |
||||
|
throw new Error('imageList is undefined'); |
||||
|
} |
||||
|
imgState.currentIndex = index; |
||||
|
handleIChangeImage(imageList[index]); |
||||
|
} |
||||
|
|
||||
|
// 重置 |
||||
|
function initState() { |
||||
|
imgState.imgScale = 1; |
||||
|
imgState.imgRotate = 0; |
||||
|
imgState.imgTop = 0; |
||||
|
imgState.imgLeft = 0; |
||||
|
} |
||||
|
|
||||
|
// 初始化鼠标滚轮事件 |
||||
|
function initMouseWheel() { |
||||
|
const wrapEl = unref(wrapElRef); |
||||
|
if (!wrapEl) { |
||||
|
return; |
||||
|
} |
||||
|
(wrapEl as any).onmousewheel = scrollFunc; |
||||
|
// 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替 |
||||
|
document.body.addEventListener('DOMMouseScroll', scrollFunc); |
||||
|
// 禁止火狐浏览器下拖拽图片的默认事件 |
||||
|
document.ondragstart = function () { |
||||
|
return false; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 监听鼠标滚轮 |
||||
|
function scrollFunc(e: any) { |
||||
|
e = e || window.event; |
||||
|
e.delta = e.wheelDelta || -e.detail; |
||||
|
|
||||
|
e.preventDefault(); |
||||
|
if (e.delta > 0) { |
||||
|
// 滑轮向上滚动 |
||||
|
scaleFunc(0.015); |
||||
|
} |
||||
|
if (e.delta < 0) { |
||||
|
// 滑轮向下滚动 |
||||
|
scaleFunc(-0.015); |
||||
|
} |
||||
|
} |
||||
|
// 缩放函数 |
||||
|
function scaleFunc(num: number) { |
||||
|
if (imgState.imgScale <= 0.2 && num < 0) return; |
||||
|
imgState.imgScale += num; |
||||
|
} |
||||
|
|
||||
|
// 旋转图片 |
||||
|
function rotateFunc(deg: number) { |
||||
|
imgState.imgRotate += deg; |
||||
|
} |
||||
|
|
||||
|
// 鼠标事件 |
||||
|
function handleMouseUp() { |
||||
|
const imgEl = unref(imgElRef); |
||||
|
if (!imgEl) return; |
||||
|
imgEl.onmousemove = null; |
||||
|
} |
||||
|
|
||||
|
// 更换图片 |
||||
|
function handleIChangeImage(url: string) { |
||||
|
imgState.status = StatueEnum.LOADING; |
||||
|
const img = new Image(); |
||||
|
img.src = url; |
||||
|
img.onload = () => { |
||||
|
imgState.currentUrl = url; |
||||
|
imgState.status = StatueEnum.DONE; |
||||
|
}; |
||||
|
img.onerror = () => { |
||||
|
imgState.status = StatueEnum.FAIL; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
// 关闭 |
||||
|
function handleClose(e: MouseEvent) { |
||||
|
e && e.stopPropagation(); |
||||
|
imgState.show = false; |
||||
|
// 移除火狐浏览器下的鼠标滚动事件 |
||||
|
document.body.removeEventListener('DOMMouseScroll', scrollFunc); |
||||
|
// 恢复火狐及Safari浏览器下的图片拖拽 |
||||
|
document.ondragstart = null; |
||||
|
} |
||||
|
|
||||
|
// 图片复原 |
||||
|
function resume() { |
||||
|
initState(); |
||||
|
} |
||||
|
|
||||
|
// 上一页下一页 |
||||
|
function handleChange(direction: 'left' | 'right') { |
||||
|
const { currentIndex } = imgState; |
||||
|
const { imageList } = props; |
||||
|
if (direction === 'left') { |
||||
|
imgState.currentIndex--; |
||||
|
if (currentIndex <= 0) { |
||||
|
imgState.currentIndex = imageList.length - 1; |
||||
|
} |
||||
|
} |
||||
|
if (direction === 'right') { |
||||
|
imgState.currentIndex++; |
||||
|
if (currentIndex >= imageList.length - 1) { |
||||
|
imgState.currentIndex = 0; |
||||
|
} |
||||
|
} |
||||
|
handleIChangeImage(imageList[imgState.currentIndex]); |
||||
|
} |
||||
|
|
||||
|
function handleAddMoveListener(e: MouseEvent) { |
||||
|
e = e || window.event; |
||||
|
imgState.moveX = e.clientX; |
||||
|
imgState.moveY = e.clientY; |
||||
|
const imgEl = unref(imgElRef); |
||||
|
if (imgEl) { |
||||
|
imgEl.onmousemove = moveFunc; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function moveFunc(e: MouseEvent) { |
||||
|
e = e || window.event; |
||||
|
e.preventDefault(); |
||||
|
const movementX = e.clientX - imgState.moveX; |
||||
|
const movementY = e.clientY - imgState.moveY; |
||||
|
imgState.imgLeft += movementX; |
||||
|
imgState.imgTop += movementY; |
||||
|
imgState.moveX = e.clientX; |
||||
|
imgState.moveY = e.clientY; |
||||
|
} |
||||
|
|
||||
|
// 获取图片样式 |
||||
|
const getImageStyle = computed(() => { |
||||
|
const { imgScale, imgRotate, imgTop, imgLeft } = imgState; |
||||
|
return { |
||||
|
transform: `scale(${imgScale}) rotate(${imgRotate}deg)`, |
||||
|
marginTop: `${imgTop}px`, |
||||
|
marginLeft: `${imgLeft}px`, |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const getIsMultipleImage = computed(() => { |
||||
|
const { imageList } = props; |
||||
|
return imageList.length > 1; |
||||
|
}); |
||||
|
|
||||
|
watchEffect(() => { |
||||
|
if (props.show) { |
||||
|
init(); |
||||
|
} |
||||
|
if (props.imageList) { |
||||
|
initState(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
const renderClose = () => { |
||||
|
return ( |
||||
|
<div class={`${prefixCls}__close`} onClick={handleClose}> |
||||
|
<CloseOutlined class={`${prefixCls}__close-icon`} /> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderIndex = () => { |
||||
|
if (!unref(getIsMultipleImage)) { |
||||
|
return null; |
||||
|
} |
||||
|
const { currentIndex } = imgState; |
||||
|
const { imageList } = props; |
||||
|
return ( |
||||
|
<div class={`${prefixCls}__index`}> |
||||
|
{currentIndex + 1} / {imageList.length} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderController = () => { |
||||
|
return ( |
||||
|
<div class={`${prefixCls}__controller`}> |
||||
|
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}> |
||||
|
<img src={unScaleSvg} /> |
||||
|
</div> |
||||
|
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}> |
||||
|
<img src={scaleSvg} /> |
||||
|
</div> |
||||
|
<div class={`${prefixCls}__controller-item`} onClick={resume}> |
||||
|
<img src={resumeSvg} /> |
||||
|
</div> |
||||
|
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}> |
||||
|
<img src={unRotateSvg} /> |
||||
|
</div> |
||||
|
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}> |
||||
|
<img src={rotateSvg} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderArrow = (direction: 'left' | 'right') => { |
||||
|
if (!unref(getIsMultipleImage)) { |
||||
|
return null; |
||||
|
} |
||||
|
return ( |
||||
|
<div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}> |
||||
|
{direction === 'left' ? <LeftOutlined /> : <RightOutlined />} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
return () => { |
||||
|
return ( |
||||
|
imgState.show && ( |
||||
|
<div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}> |
||||
|
<div class={`${prefixCls}-content`}> |
||||
|
{/*<Spin*/} |
||||
|
{/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/} |
||||
|
{/* spinning={true}*/} |
||||
|
{/* class={[*/} |
||||
|
{/* `${prefixCls}-image`,*/} |
||||
|
{/* {*/} |
||||
|
{/* hidden: imgState.status !== StatueEnum.LOADING,*/} |
||||
|
{/* },*/} |
||||
|
{/* ]}*/} |
||||
|
{/*/>*/} |
||||
|
<img |
||||
|
style={unref(getImageStyle)} |
||||
|
class={[ |
||||
|
`${prefixCls}-image`, |
||||
|
imgState.status === StatueEnum.DONE ? '' : 'hidden', |
||||
|
]} |
||||
|
ref={imgElRef} |
||||
|
src={imgState.currentUrl} |
||||
|
onMousedown={handleAddMoveListener} |
||||
|
/> |
||||
|
{renderClose()} |
||||
|
{renderIndex()} |
||||
|
{renderController()} |
||||
|
{renderArrow('left')} |
||||
|
{renderArrow('right')} |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
); |
||||
|
}; |
||||
|
}, |
||||
|
}); |
||||
|
</script> |
||||
|
<style lang="less"> |
||||
|
.img-preview { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
z-index: @preview-comp-z-index; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
user-select: none; |
||||
|
|
||||
|
&-content { |
||||
|
display: flex; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
color: @white; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
&-image { |
||||
|
cursor: pointer; |
||||
|
transition: transform 0.3s; |
||||
|
} |
||||
|
|
||||
|
&__close { |
||||
|
position: absolute; |
||||
|
top: -40px; |
||||
|
right: -40px; |
||||
|
width: 80px; |
||||
|
height: 80px; |
||||
|
overflow: hidden; |
||||
|
color: @white; |
||||
|
cursor: pointer; |
||||
|
background-color: rgba(0, 0, 0, 0.5); |
||||
|
border-radius: 50%; |
||||
|
transition: all 0.2s; |
||||
|
|
||||
|
&-icon { |
||||
|
position: absolute; |
||||
|
top: 46px; |
||||
|
left: 16px; |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: rgba(0, 0, 0, 0.8); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__index { |
||||
|
position: absolute; |
||||
|
bottom: 5%; |
||||
|
left: 50%; |
||||
|
padding: 0 22px; |
||||
|
font-size: 16px; |
||||
|
background: rgba(109, 109, 109, 0.6); |
||||
|
border-radius: 15px; |
||||
|
transform: translateX(-50%); |
||||
|
} |
||||
|
|
||||
|
&__controller { |
||||
|
position: absolute; |
||||
|
bottom: 10%; |
||||
|
left: 50%; |
||||
|
display: flex; |
||||
|
width: 260px; |
||||
|
height: 44px; |
||||
|
padding: 0 22px; |
||||
|
margin-left: -139px; |
||||
|
background: rgba(109, 109, 109, 0.6); |
||||
|
border-radius: 22px; |
||||
|
justify-content: center; |
||||
|
|
||||
|
&-item { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
padding: 0 9px; |
||||
|
font-size: 24px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.2s; |
||||
|
|
||||
|
&:hover { |
||||
|
transform: scale(1.2); |
||||
|
} |
||||
|
|
||||
|
img { |
||||
|
width: 1em; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__arrow { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
font-size: 28px; |
||||
|
cursor: pointer; |
||||
|
background-color: rgba(0, 0, 0, 0.5); |
||||
|
border-radius: 50%; |
||||
|
transition: all 0.2s; |
||||
|
|
||||
|
&:hover { |
||||
|
background-color: rgba(0, 0, 0, 0.8); |
||||
|
} |
||||
|
|
||||
|
&.left { |
||||
|
left: 50px; |
||||
|
} |
||||
|
|
||||
|
&.right { |
||||
|
right: 50px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -1,118 +0,0 @@ |
|||||
.img-preview { |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
z-index: @preview-comp-z-index; |
|
||||
background: rgba(0, 0, 0, 0.5); |
|
||||
user-select: none; |
|
||||
|
|
||||
&-content { |
|
||||
display: flex; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
color: @white; |
|
||||
justify-content: center; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
&-image { |
|
||||
cursor: pointer; |
|
||||
transition: transform 0.3s; |
|
||||
} |
|
||||
|
|
||||
&__close { |
|
||||
position: absolute; |
|
||||
top: -40px; |
|
||||
right: -40px; |
|
||||
width: 80px; |
|
||||
height: 80px; |
|
||||
overflow: hidden; |
|
||||
color: @white; |
|
||||
cursor: pointer; |
|
||||
background-color: rgba(0, 0, 0, 0.5); |
|
||||
border-radius: 50%; |
|
||||
transition: all 0.2s; |
|
||||
|
|
||||
&-icon { |
|
||||
position: absolute; |
|
||||
top: 46px; |
|
||||
left: 16px; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
|
|
||||
&:hover { |
|
||||
background-color: rgba(0, 0, 0, 0.8); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&__index { |
|
||||
position: absolute; |
|
||||
bottom: 5%; |
|
||||
left: 50%; |
|
||||
padding: 0 22px; |
|
||||
font-size: 16px; |
|
||||
background: rgba(109, 109, 109, 0.6); |
|
||||
border-radius: 15px; |
|
||||
transform: translateX(-50%); |
|
||||
} |
|
||||
|
|
||||
&__controller { |
|
||||
position: absolute; |
|
||||
bottom: 10%; |
|
||||
left: 50%; |
|
||||
display: flex; |
|
||||
width: 260px; |
|
||||
height: 44px; |
|
||||
padding: 0 22px; |
|
||||
margin-left: -139px; |
|
||||
background: rgba(109, 109, 109, 0.6); |
|
||||
border-radius: 22px; |
|
||||
justify-content: center; |
|
||||
|
|
||||
&-item { |
|
||||
display: flex; |
|
||||
height: 100%; |
|
||||
padding: 0 9px; |
|
||||
font-size: 24px; |
|
||||
cursor: pointer; |
|
||||
transition: all 0.2s; |
|
||||
|
|
||||
&:hover { |
|
||||
transform: scale(1.2); |
|
||||
} |
|
||||
|
|
||||
img { |
|
||||
width: 1em; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
&__arrow { |
|
||||
position: absolute; |
|
||||
top: 50%; |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
width: 50px; |
|
||||
height: 50px; |
|
||||
font-size: 28px; |
|
||||
cursor: pointer; |
|
||||
background-color: rgba(0, 0, 0, 0.5); |
|
||||
border-radius: 50%; |
|
||||
transition: all 0.2s; |
|
||||
|
|
||||
&:hover { |
|
||||
background-color: rgba(0, 0, 0, 0.8); |
|
||||
} |
|
||||
|
|
||||
&.left { |
|
||||
left: 50px; |
|
||||
} |
|
||||
|
|
||||
&.right { |
|
||||
right: 50px; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,305 +0,0 @@ |
|||||
import './index.less'; |
|
||||
|
|
||||
import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue'; |
|
||||
|
|
||||
// @ts-ignore
|
|
||||
import { basicProps } from './props'; |
|
||||
// @ts-ignore
|
|
||||
import { Props } from './types'; |
|
||||
|
|
||||
import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; |
|
||||
// import { Spin } from 'ant-design-vue';
|
|
||||
|
|
||||
import resumeSvg from '/@/assets/svg/preview/resume.svg'; |
|
||||
import rotateSvg from '/@/assets/svg/preview/p-rotate.svg'; |
|
||||
import scaleSvg from '/@/assets/svg/preview/scale.svg'; |
|
||||
import unScaleSvg from '/@/assets/svg/preview/unscale.svg'; |
|
||||
import unRotateSvg from '/@/assets/svg/preview/unrotate.svg'; |
|
||||
enum StatueEnum { |
|
||||
LOADING, |
|
||||
DONE, |
|
||||
FAIL, |
|
||||
} |
|
||||
interface ImgState { |
|
||||
currentUrl: string; |
|
||||
imgScale: number; |
|
||||
imgRotate: number; |
|
||||
imgTop: number; |
|
||||
imgLeft: number; |
|
||||
currentIndex: number; |
|
||||
status: StatueEnum; |
|
||||
moveX: number; |
|
||||
moveY: number; |
|
||||
show: boolean; |
|
||||
} |
|
||||
|
|
||||
const prefixCls = 'img-preview'; |
|
||||
export default defineComponent({ |
|
||||
name: 'ImagePreview', |
|
||||
props: basicProps, |
|
||||
setup(props: Props) { |
|
||||
const imgState = reactive<ImgState>({ |
|
||||
currentUrl: '', |
|
||||
imgScale: 1, |
|
||||
imgRotate: 0, |
|
||||
imgTop: 0, |
|
||||
imgLeft: 0, |
|
||||
status: StatueEnum.LOADING, |
|
||||
currentIndex: 0, |
|
||||
moveX: 0, |
|
||||
moveY: 0, |
|
||||
show: props.show, |
|
||||
}); |
|
||||
|
|
||||
const wrapElRef = ref<HTMLDivElement | null>(null); |
|
||||
const imgElRef = ref<HTMLImageElement | null>(null); |
|
||||
|
|
||||
// 初始化
|
|
||||
function init() { |
|
||||
initMouseWheel(); |
|
||||
const { index, imageList } = props; |
|
||||
|
|
||||
if (!imageList || !imageList.length) { |
|
||||
throw new Error('imageList is undefined'); |
|
||||
} |
|
||||
imgState.currentIndex = index; |
|
||||
handleIChangeImage(imageList[index]); |
|
||||
} |
|
||||
|
|
||||
// 重置
|
|
||||
function initState() { |
|
||||
imgState.imgScale = 1; |
|
||||
imgState.imgRotate = 0; |
|
||||
imgState.imgTop = 0; |
|
||||
imgState.imgLeft = 0; |
|
||||
} |
|
||||
|
|
||||
// 初始化鼠标滚轮事件
|
|
||||
function initMouseWheel() { |
|
||||
const wrapEl = unref(wrapElRef); |
|
||||
if (!wrapEl) { |
|
||||
return; |
|
||||
} |
|
||||
(wrapEl as any).onmousewheel = scrollFunc; |
|
||||
// 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
|
|
||||
document.body.addEventListener('DOMMouseScroll', scrollFunc); |
|
||||
// 禁止火狐浏览器下拖拽图片的默认事件
|
|
||||
document.ondragstart = function () { |
|
||||
return false; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
// 监听鼠标滚轮
|
|
||||
function scrollFunc(e: any) { |
|
||||
e = e || window.event; |
|
||||
e.delta = e.wheelDelta || -e.detail; |
|
||||
|
|
||||
e.preventDefault(); |
|
||||
if (e.delta > 0) { |
|
||||
// 滑轮向上滚动
|
|
||||
scaleFunc(0.015); |
|
||||
} |
|
||||
if (e.delta < 0) { |
|
||||
// 滑轮向下滚动
|
|
||||
scaleFunc(-0.015); |
|
||||
} |
|
||||
} |
|
||||
// 缩放函数
|
|
||||
function scaleFunc(num: number) { |
|
||||
if (imgState.imgScale <= 0.2 && num < 0) return; |
|
||||
imgState.imgScale += num; |
|
||||
} |
|
||||
|
|
||||
// 旋转图片
|
|
||||
function rotateFunc(deg: number) { |
|
||||
imgState.imgRotate += deg; |
|
||||
} |
|
||||
|
|
||||
// 鼠标事件
|
|
||||
function handleMouseUp() { |
|
||||
const imgEl = unref(imgElRef); |
|
||||
if (!imgEl) return; |
|
||||
imgEl.onmousemove = null; |
|
||||
} |
|
||||
|
|
||||
// 更换图片
|
|
||||
function handleIChangeImage(url: string) { |
|
||||
imgState.status = StatueEnum.LOADING; |
|
||||
const img = new Image(); |
|
||||
img.src = url; |
|
||||
img.onload = () => { |
|
||||
imgState.currentUrl = url; |
|
||||
imgState.status = StatueEnum.DONE; |
|
||||
}; |
|
||||
img.onerror = () => { |
|
||||
imgState.status = StatueEnum.FAIL; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
// 关闭
|
|
||||
function handleClose(e: MouseEvent) { |
|
||||
e && e.stopPropagation(); |
|
||||
imgState.show = false; |
|
||||
// 移除火狐浏览器下的鼠标滚动事件
|
|
||||
document.body.removeEventListener('DOMMouseScroll', scrollFunc); |
|
||||
// 恢复火狐及Safari浏览器下的图片拖拽
|
|
||||
document.ondragstart = null; |
|
||||
} |
|
||||
|
|
||||
// 图片复原
|
|
||||
function resume() { |
|
||||
initState(); |
|
||||
} |
|
||||
|
|
||||
// 上一页下一页
|
|
||||
function handleChange(direction: 'left' | 'right') { |
|
||||
const { currentIndex } = imgState; |
|
||||
const { imageList } = props; |
|
||||
if (direction === 'left') { |
|
||||
imgState.currentIndex--; |
|
||||
if (currentIndex <= 0) { |
|
||||
imgState.currentIndex = imageList.length - 1; |
|
||||
} |
|
||||
} |
|
||||
if (direction === 'right') { |
|
||||
imgState.currentIndex++; |
|
||||
if (currentIndex >= imageList.length - 1) { |
|
||||
imgState.currentIndex = 0; |
|
||||
} |
|
||||
} |
|
||||
handleIChangeImage(imageList[imgState.currentIndex]); |
|
||||
} |
|
||||
|
|
||||
function handleAddMoveListener(e: MouseEvent) { |
|
||||
e = e || window.event; |
|
||||
imgState.moveX = e.clientX; |
|
||||
imgState.moveY = e.clientY; |
|
||||
const imgEl = unref(imgElRef); |
|
||||
if (imgEl) { |
|
||||
imgEl.onmousemove = moveFunc; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function moveFunc(e: MouseEvent) { |
|
||||
e = e || window.event; |
|
||||
e.preventDefault(); |
|
||||
const movementX = e.clientX - imgState.moveX; |
|
||||
const movementY = e.clientY - imgState.moveY; |
|
||||
imgState.imgLeft += movementX; |
|
||||
imgState.imgTop += movementY; |
|
||||
imgState.moveX = e.clientX; |
|
||||
imgState.moveY = e.clientY; |
|
||||
} |
|
||||
|
|
||||
// 获取图片样式
|
|
||||
const getImageStyle = computed(() => { |
|
||||
const { imgScale, imgRotate, imgTop, imgLeft } = imgState; |
|
||||
return { |
|
||||
transform: `scale(${imgScale}) rotate(${imgRotate}deg)`, |
|
||||
marginTop: `${imgTop}px`, |
|
||||
marginLeft: `${imgLeft}px`, |
|
||||
}; |
|
||||
}); |
|
||||
|
|
||||
const getIsMultipleImage = computed(() => { |
|
||||
const { imageList } = props; |
|
||||
return imageList.length > 1; |
|
||||
}); |
|
||||
|
|
||||
watchEffect(() => { |
|
||||
if (props.show) { |
|
||||
init(); |
|
||||
} |
|
||||
if (props.imageList) { |
|
||||
initState(); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
const renderClose = () => { |
|
||||
return ( |
|
||||
<div class={`${prefixCls}__close`} onClick={handleClose}> |
|
||||
<CloseOutlined class={`${prefixCls}__close-icon`} /> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderIndex = () => { |
|
||||
if (!unref(getIsMultipleImage)) { |
|
||||
return null; |
|
||||
} |
|
||||
const { currentIndex } = imgState; |
|
||||
const { imageList } = props; |
|
||||
return ( |
|
||||
<div class={`${prefixCls}__index`}> |
|
||||
{currentIndex + 1} / {imageList.length} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderController = () => { |
|
||||
return ( |
|
||||
<div class={`${prefixCls}__controller`}> |
|
||||
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}> |
|
||||
<img src={unScaleSvg} /> |
|
||||
</div> |
|
||||
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}> |
|
||||
<img src={scaleSvg} /> |
|
||||
</div> |
|
||||
<div class={`${prefixCls}__controller-item`} onClick={resume}> |
|
||||
<img src={resumeSvg} /> |
|
||||
</div> |
|
||||
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}> |
|
||||
<img src={unRotateSvg} /> |
|
||||
</div> |
|
||||
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}> |
|
||||
<img src={rotateSvg} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const renderArrow = (direction: 'left' | 'right') => { |
|
||||
if (!unref(getIsMultipleImage)) { |
|
||||
return null; |
|
||||
} |
|
||||
return ( |
|
||||
<div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}> |
|
||||
{direction === 'left' ? <LeftOutlined /> : <RightOutlined />} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
return () => { |
|
||||
return ( |
|
||||
imgState.show && ( |
|
||||
<div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}> |
|
||||
<div class={`${prefixCls}-content`}> |
|
||||
{/*<Spin*/} |
|
||||
{/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/} |
|
||||
{/* spinning={true}*/} |
|
||||
{/* class={[*/} |
|
||||
{/* `${prefixCls}-image`,*/} |
|
||||
{/* {*/} |
|
||||
{/* hidden: imgState.status !== StatueEnum.LOADING,*/} |
|
||||
{/* },*/} |
|
||||
{/* ]}*/} |
|
||||
{/*/>*/} |
|
||||
<img |
|
||||
style={unref(getImageStyle)} |
|
||||
class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']} |
|
||||
ref={imgElRef} |
|
||||
src={imgState.currentUrl} |
|
||||
onMousedown={handleAddMoveListener} |
|
||||
/> |
|
||||
{renderClose()} |
|
||||
{renderIndex()} |
|
||||
{renderController()} |
|
||||
{renderArrow('left')} |
|
||||
{renderArrow('right')} |
|
||||
</div> |
|
||||
</div> |
|
||||
) |
|
||||
); |
|
||||
}; |
|
||||
}, |
|
||||
}); |
|
||||
@ -1,15 +0,0 @@ |
|||||
import { PropType } from 'vue'; |
|
||||
export const basicProps = { |
|
||||
show: { |
|
||||
type: Boolean as PropType<boolean>, |
|
||||
default: false, |
|
||||
}, |
|
||||
imageList: { |
|
||||
type: [Array] as PropType<string[]>, |
|
||||
default: null, |
|
||||
}, |
|
||||
index: { |
|
||||
type: Number as PropType<number>, |
|
||||
default: 0, |
|
||||
}, |
|
||||
}; |
|
||||
Loading…
Reference in new issue