vxe-pc-ui
Version:
A vue based PC component library
606 lines (605 loc) • 22.9 kB
JavaScript
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;
}
});