UNPKG

vxe-pc-ui

Version:
606 lines (605 loc) 22.9 kB
import { defineComponent, h, provide, ref, reactive, computed, watch, onMounted, onUnmounted, createCommentVNode, onBeforeUnmount } from 'vue'; import { VxeUI, getConfig, createEvent, getIcon, globalEvents, GLOBAL_EVENT_KEYS, getI18n } from '../../ui'; import XEUtils from 'xe-utils'; import { getDomNode, addClass, removeClass } from '../..//ui/src/dom'; export default defineComponent({ name: 'VxeImagePreview', props: { modelValue: Number, urlList: Array, urlField: { type: String, default: () => getConfig().imagePreview.urlField }, maskClosable: { type: Boolean, default: () => getConfig().imagePreview.maskClosable }, marginSize: { type: String, default: () => getConfig().imagePreview.marginSize }, showPrintButton: { type: Boolean, default: () => getConfig().imagePreview.showPrintButton }, showDownloadButton: { type: Boolean, default: () => getConfig().imagePreview.showDownloadButton }, beforeDownloadMethod: Function, downloadMethod: Function }, emits: [ 'update:modelValue', 'change', 'download', 'download-fail', 'close' ], setup(props, context) { const { emit } = context; const xID = XEUtils.uniqueId(); const refElem = ref(); const refMaps = { refElem }; const reactData = reactive({ activeIndex: props.modelValue || 0, offsetPct11: false, offsetScale: 0, offsetRotate: 0, offsetLeft: 0, offsetTop: 0 }); const computeUrlProp = computed(() => { return props.urlField || 'url'; }); const computeMarginSize = computed(() => { return XEUtils.toNumber(props.marginSize || 0) || 16; }); const computeRotateText = computed(() => { const { offsetRotate } = reactData; if (offsetRotate) { return `${offsetRotate}°`; } return '0°'; }); const computeScaleText = computed(() => { const { offsetScale } = reactData; if (offsetScale) { return `${XEUtils.ceil((1 + offsetScale) * 100)}%`; } return '100%'; }); const computeImgList = computed(() => { const { urlList } = props; const urlProp = computeUrlProp.value; if (urlList && urlList.length) { return urlList.map(item => { if (XEUtils.isString(item)) { return item; } if (item[urlProp]) { return item[urlProp]; } return ''; }); } return []; }); const computeImgTransform = computed(() => { let { offsetScale, offsetRotate, offsetLeft, offsetTop } = reactData; const stys = []; let targetScale = 1; if (offsetScale) { targetScale = 1 + offsetScale; stys.push(`scale(${targetScale})`); } if (offsetRotate) { stys.push(`rotate(${offsetRotate}deg)`); } if (offsetLeft || offsetTop) { // 缩放与位移 offsetLeft /= targetScale; offsetTop /= targetScale; let targetOffsetLeft = offsetLeft; let targetOffsetTop = offsetTop; if (offsetRotate) { // 转向与位移 switch (offsetRotate % 360) { case 90: case -270: targetOffsetLeft = offsetTop; targetOffsetTop = -offsetLeft; break; case 180: case -180: targetOffsetLeft = -offsetLeft; targetOffsetTop = -offsetTop; break; case 270: case -90: targetOffsetLeft = -offsetTop; targetOffsetTop = offsetLeft; break; } } stys.push(`translate(${targetOffsetLeft}px, ${targetOffsetTop}px)`); } return stys.length ? stys.join(' ') : ''; }); const computeMaps = { computeImgList }; const $xeImagePreview = { xID, props, context, reactData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $imagePreview: $xeImagePreview }, params)); }; const imagePreviewMethods = { dispatchEvent }; const emitModel = (value) => { reactData.activeIndex = value; emit('update:modelValue', value); }; const handleCloseEvent = (evnt) => { dispatchEvent('close', {}, evnt); }; const imagePreviewPrivateMethods = {}; const resetStyle = () => { const elem = refElem.value; removeClass(elem, 'is--move'); Object.assign(reactData, { offsetPct11: false, offsetScale: 0, offsetRotate: 0, offsetLeft: 0, offsetTop: 0 }); }; const getOffsetZoomStep = () => { const { offsetScale } = reactData; let stepNum = 0.02; if (offsetScale >= -0.6) { stepNum = 0.04; if (offsetScale >= -0.4) { stepNum = 0.07; if (offsetScale >= 0) { stepNum = 0.1; if (offsetScale >= 3) { stepNum = 0.25; if (offsetScale >= 8) { stepNum = 0.4; if (offsetScale >= 16) { stepNum = 0.6; if (offsetScale >= 24) { stepNum = 0.9; if (offsetScale >= 32) { stepNum = 1.3; if (offsetScale >= 39) { stepNum = 1.9; if (offsetScale >= 45) { stepNum = 2.5; } } } } } } } } } } return stepNum; }; const handleZoom = (isAdd) => { const { offsetScale } = reactData; const stepNum = getOffsetZoomStep(); if (isAdd) { reactData.offsetScale = Number(Math.min(49, offsetScale + stepNum).toFixed(2)); } else { reactData.offsetScale = Number(Math.max(-0.9, offsetScale - stepNum).toFixed(2)); } }; const handleChange = (isNext) => { let activeIndex = reactData.activeIndex || 0; const imgList = computeImgList.value; if (isNext) { if (activeIndex >= imgList.length - 1) { activeIndex = 0; } else { activeIndex++; } } else { if (activeIndex <= 0) { activeIndex = imgList.length - 1; } else { activeIndex--; } } resetStyle(); reactData.activeIndex = activeIndex; emitModel(activeIndex); }; const handleRotateImg = (isRight) => { let offsetRotate = reactData.offsetRotate; if (isRight) { offsetRotate += 90; } else { offsetRotate -= 90; } reactData.offsetRotate = offsetRotate; }; const handlePct11 = () => { resetStyle(); reactData.offsetPct11 = true; }; const handlePrintImg = () => { const { activeIndex } = reactData; const imgList = computeImgList.value; const imgUrl = imgList[activeIndex || 0]; if (VxeUI.print) { VxeUI.print({ align: 'center', pageBreaks: [ { bodyHtml: `<img src="${imgUrl}" style="max-width:100%;max-height:100%;">` } ] }); } }; const handleDownloadEvent = (evnt, imgUrl) => { dispatchEvent('download', { url: imgUrl }, evnt); }; const handleDefaultDownload = (evnt, imgUrl) => { if (VxeUI.saveFile) { fetch(imgUrl).then(res => { return res.blob().then(blob => { VxeUI.saveFile({ filename: imgUrl, content: blob }); handleDownloadEvent(evnt, imgUrl); }); }).catch(() => { if (VxeUI.modal) { VxeUI.modal.message({ content: getI18n('vxe.error.downErr'), status: 'error' }); } }); } }; const handleDownloadImg = (evnt) => { const { activeIndex } = reactData; const imgList = computeImgList.value; const imgUrl = imgList[activeIndex || 0]; const beforeDownloadFn = props.beforeDownloadMethod || getConfig().imagePreview.beforeDownloadMethod; const downloadFn = props.downloadMethod || getConfig().imagePreview.downloadMethod; Promise.resolve(beforeDownloadFn ? beforeDownloadFn({ $imagePreview: $xeImagePreview, url: imgUrl, index: activeIndex || 0 }) : true).then(status => { if (status) { if (downloadFn) { Promise.resolve(downloadFn({ $imagePreview: $xeImagePreview, url: imgUrl, index: activeIndex || 0 })).then(() => { handleDownloadEvent(evnt, imgUrl); }).catch(e => e); } else { handleDefaultDownload(evnt, imgUrl); } } }); }; const handleOperationBtn = (evnt, code) => { const { activeIndex } = reactData; const imgList = computeImgList.value; const imgUrl = imgList[activeIndex || 0]; if (imgUrl) { switch (code) { case 'zoomOut': handleZoom(false); break; case 'zoomIn': handleZoom(true); break; case 'pctFull': resetStyle(); break; case 'pct11': handlePct11(); break; case 'rotateLeft': handleRotateImg(false); break; case 'rotateRight': handleRotateImg(true); break; case 'print': handlePrintImg(); break; case 'download': handleDownloadImg(evnt); break; } } }; const wheelEvent = (evnt) => { const delta = evnt.deltaY; if (delta > 0) { handleZoom(false); } else if (delta < 0) { handleZoom(true); } }; const moveEvent = (evnt) => { const { offsetTop, offsetLeft } = reactData; const elem = refElem.value; evnt.preventDefault(); const domMousemove = document.onmousemove; const domMouseup = document.onmouseup; const startX = evnt.pageX; const startY = evnt.pageY; const marginSize = computeMarginSize.value; document.onmousemove = et => { const { pageX, pageY } = et; const { visibleHeight, visibleWidth } = getDomNode(); et.preventDefault(); addClass(elem, 'is--move'); // 限制边界值 if (pageX > marginSize && pageY > marginSize && pageX < (visibleWidth - marginSize) && pageY < (visibleHeight - marginSize)) { reactData.offsetLeft = offsetLeft + pageX - startX; reactData.offsetTop = offsetTop + pageY - startY; } }; document.onmouseup = () => { document.onmousemove = domMousemove; document.onmouseup = domMouseup; removeClass(elem, 'is--move'); }; }; const handleGlobalKeydownEvent = (evnt) => { const hasCtrlKey = evnt.ctrlKey; const hasShiftKey = evnt.shiftKey; const isUpArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_UP); const isDownArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_DOWN); const isLeftArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_LEFT); const isRightArrow = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.ARROW_RIGHT); const isR = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.R); const isP = globalEvents.hasKey(evnt, GLOBAL_EVENT_KEYS.P); if (isUpArrow) { evnt.preventDefault(); if (hasShiftKey) { reactData.offsetTop -= 1; } else { handleZoom(true); } } else if (isDownArrow) { evnt.preventDefault(); if (hasShiftKey) { reactData.offsetTop += 1; } else { handleZoom(false); } } else if (isLeftArrow) { evnt.preventDefault(); if (hasShiftKey) { reactData.offsetLeft -= 1; } else { handleChange(false); } } else if (isRightArrow) { evnt.preventDefault(); if (hasShiftKey) { reactData.offsetLeft += 1; } else { handleChange(true); } } else if (isR && hasCtrlKey) { evnt.preventDefault(); if (hasShiftKey) { handleRotateImg(false); } else { handleRotateImg(true); } } else if (isP && hasCtrlKey) { evnt.preventDefault(); handlePrintImg(); } }; const handleClickMaskEvent = (evnt) => { if (props.maskClosable) { if (evnt.target === evnt.currentTarget) { dispatchEvent('close', {}, evnt); } } }; Object.assign($xeImagePreview, imagePreviewMethods, imagePreviewPrivateMethods); const renderImgWrapper = () => { const { activeIndex } = reactData; const imgList = computeImgList.value; const imgTransform = computeImgTransform.value; return h('div', { class: 'vxe-image-preview--img-list', onClick: handleClickMaskEvent }, imgList.map((url, index) => { const isActive = activeIndex === index; return h('img', { class: ['vxe-image-preview--img-item', { 'is--active': isActive }], src: url, style: isActive ? { transform: imgTransform } : null, onMousedown(evnt) { moveEvent(evnt); } }); })); }; const renderOperationBtn = (code, icon) => { return h('div', { class: 'vxe-image-preview--operation-btn', title: getI18n(`vxe.imagePreview.operBtn.${code}`), onClick(evnt) { handleOperationBtn(evnt, code); } }, [ h('i', { class: getIcon()[icon] }) ]); }; const renderBtnWrapper = () => { const { showPrintButton, showDownloadButton } = props; const { activeIndex } = reactData; const imgList = computeImgList.value; const rotateText = computeRotateText.value; const scaleText = computeScaleText.value; return h('div', { class: 'vxe-image-preview--btn-wrapper' }, [ h('div', { class: 'vxe-image-preview--close-wrapper' }, [ h('div', { class: 'vxe-image-preview--close-btn', onClick: handleCloseEvent }, [ h('i', { class: getIcon().IMAGE_PREVIEW_CLOSE }) ]), h('div', { class: 'vxe-image-preview--close-bg' }) ]), imgList.length > 1 ? h('div', { class: 'vxe-image-preview--previous-btn', onClick() { handleChange(false); } }, [ h('i', { class: getIcon().IMAGE_PREVIEW_PREVIOUS }) ]) : createCommentVNode(), imgList.length > 1 ? h('div', { class: 'vxe-image-preview--next-btn', onClick() { handleChange(true); } }, [ h('i', { class: getIcon().IMAGE_PREVIEW_NEXT }) ]) : createCommentVNode(), h('div', { class: 'vxe-image-preview--operation-info' }, [ h('div', { class: 'vxe-image-preview--operation-deg' }, rotateText), h('div', { class: 'vxe-image-preview--operation-pct' }, scaleText) ]), h('div', { class: 'vxe-image-preview--operation-wrapper' }, [ h('div', { class: 'vxe-image-preview--operation-active-count' }, [ h('span', { class: 'vxe-image-preview--operation-active-current' }, `${(activeIndex || 0) + 1}`), h('span', { class: 'vxe-image-preview--operation-active-total' }, `/${imgList.length}`) ]), renderOperationBtn('zoomOut', 'IMAGE_PREVIEW_ZOOM_OUT'), renderOperationBtn('zoomIn', 'IMAGE_PREVIEW_ZOOM_IN'), renderOperationBtn('pctFull', 'IMAGE_PREVIEW_PCT_FULL'), renderOperationBtn('pct11', 'IMAGE_PREVIEW_PCT_1_1'), renderOperationBtn('rotateLeft', 'IMAGE_PREVIEW_ROTATE_LEFT'), renderOperationBtn('rotateRight', 'IMAGE_PREVIEW_ROTATE_RIGHT'), showPrintButton ? renderOperationBtn('print', 'IMAGE_PREVIEW_PRINT') : createCommentVNode(), showDownloadButton ? renderOperationBtn('download', 'IMAGE_PREVIEW_DOWNLOAD') : createCommentVNode() ]) ]); }; const renderVN = () => { const { offsetPct11 } = reactData; return h('div', { ref: refElem, class: ['vxe-image-preview', { 'is--pct11': offsetPct11 }], onWheel: wheelEvent }, [ renderImgWrapper(), renderBtnWrapper() ]); }; watch(() => props.modelValue, val => { reactData.activeIndex = val; resetStyle(); }); onMounted(() => { globalEvents.on($xeImagePreview, 'keydown', handleGlobalKeydownEvent); }); onBeforeUnmount(() => { const elem = refElem.value; if (elem) { removeClass(elem, 'is--move'); } }); onUnmounted(() => { globalEvents.off($xeImagePreview, 'keydown'); }); provide('$xeImagePreview', $xeImagePreview); $xeImagePreview.renderVN = renderVN; return renderVN; } });