@fesjs/fes-design
Version:
fes-design for PC
318 lines (312 loc) • 9.33 kB
JavaScript
import { defineComponent, ref, computed, reactive, inject, watch, onUnmounted, nextTick, resolveComponent, openBlock, createElementBlock, normalizeClass, normalizeStyle, renderSlot, createElementVNode, createVNode, mergeProps, createBlock, createCommentVNode } from 'vue';
import { useThrottleFn, useEventListener } from '@vueuse/core';
import { isString } from 'lodash-es';
import getPrefixCls from '../_util/getPrefixCls';
import { PictureOutlined, PictureFailOutlined } from '../icon';
import { isHtmlElement, getScrollContainer, isInContainer } from '../_util/dom';
import { pxfy, noop, noopInNoop } from '../_util/utils';
import { ERROR_EVENT, LOAD_EVENT, CLOSE_EVENT } from '../_util/constants';
import download from '../_util/download';
import { useTheme } from '../_theme/useTheme';
import { PREVIEW_PROVIDE_KEY } from './props';
import Preview from './preview.js';
const prefixCls = getPrefixCls('img');
let curIndex = 0;
let prevOverflow = '';
const imageProps = {
src: {
type: String,
default: ''
},
name: String,
preview: {
type: Boolean,
default: false
},
fit: {
type: String
},
lazy: {
type: Boolean,
default: false
},
hideOnClickModal: {
type: Boolean,
default: false
},
scrollContainer: [String, Object],
download: {
type: Boolean,
default: false
},
previewContainer: {
type: Function
},
height: [String, Number],
width: [String, Number]
};
var script = defineComponent({
name: 'FImage',
componentName: 'FImage',
components: {
Preview,
PictureOutlined,
PictureFailOutlined
},
props: imageProps,
emits: [ERROR_EVENT, LOAD_EVENT, CLOSE_EVENT],
setup(props, _ref) {
let {
attrs,
emit
} = _ref;
useTheme();
const loading = ref(true);
const isLoadError = ref(false);
const container = ref(null);
const isShowPreview = ref(false);
const currentId = ref(curIndex++);
const imgAttrs = computed(() => {
const {
crossorigin = undefined,
decoding = 'auto',
alt = undefined,
sizes = undefined,
srcset = undefined,
usemap = undefined
} = attrs;
return {
crossorigin,
decoding,
alt,
sizes,
srcset,
usemap
};
});
const style = computed(() => {
const {
width,
height
} = props;
return {
width: pxfy(width),
height: pxfy(height)
};
});
const imageSize = reactive({
height: 0,
width: 0
});
const {
isGroup,
setShowPreview,
setCurrent,
registerImage
} = inject(PREVIEW_PROVIDE_KEY, {
setShowPreview: noop,
isGroup: ref(false),
setCurrent: noop,
registerImage: noopInNoop
});
const canPreview = computed(() => props.preview && !isLoadError.value);
const canGroupPreview = computed(() => isGroup.value && !isLoadError.value);
const _scrollContainer = computed(() => {
let dom;
const _container = props.scrollContainer;
if (isString(_container) && _container !== '') {
dom = document.querySelector(_container);
}
if (isHtmlElement(_container)) {
dom = _container;
} else if (container.value) {
dom = getScrollContainer(container.value);
}
return dom;
});
const imageStyle = computed(() => {
const {
fit
} = props;
const styleObj = {
objectFit: 'fill',
cursor: ''
};
if (fit) {
styleObj.objectFit = fit;
}
if (props.download || canPreview.value || canGroupPreview.value) {
styleObj.cursor = 'pointer';
}
return styleObj;
});
const handleLoaded = (e, img) => {
imageSize.width = img.width;
imageSize.height = img.height;
loading.value = false;
isLoadError.value = false;
emit(LOAD_EVENT, e);
};
const handleError = e => {
loading.value = false;
isLoadError.value = true;
emit(ERROR_EVENT, e);
};
let currentImageId = 0;
const loadImage = () => {
// loading 为true 才会加载图片
if (!loading.value) {
return;
}
const img = new Image();
const imageId = ++currentImageId;
img.addEventListener('load', e => {
// 检查 imageId 是否与 currentImageId 相同
if (imageId !== currentImageId) {
return;
}
handleLoaded(e, img);
});
img.addEventListener('error', e => {
// 检查 imageId 是否与 currentImageId 相同
if (imageId !== currentImageId) {
return;
}
handleError(e);
});
// 赋值开始加载图片src
img.src = props.src;
};
const lazyLoadHandler = useThrottleFn(() => {
// load image until image enter the container
if (isInContainer(container.value, _scrollContainer.value)) {
loadImage();
}
}, 200);
let clearScrollListener = noop;
async function addLazyLoadListener() {
await nextTick();
if (clearScrollListener) {
clearScrollListener();
}
if (_scrollContainer.value) {
clearScrollListener = useEventListener(_scrollContainer, 'scroll', lazyLoadHandler);
}
lazyLoadHandler();
}
function clickHandler() {
if (canGroupPreview.value) {
setCurrent(currentId.value);
setShowPreview(true);
} else if (canPreview.value) {
// prevent body scroll
prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
isShowPreview.value = true;
} else if (props.download) {
// 下载
download({
href: props.src,
name: props.name
});
}
}
function closeViewer() {
document.body.style.overflow = prevOverflow;
isShowPreview.value = false;
emit(CLOSE_EVENT);
}
watch(() => props.src, _src => {
if (_src) {
// 将 loading 状态设置为 true
loading.value = true;
// 重置 isLoadError 状态
isLoadError.value = false;
if (props.lazy) {
addLazyLoadListener();
} else {
loadImage();
}
}
}, {
immediate: true
});
let unRegister = noop;
watch([() => props.src, () => props.name, () => props.download, canGroupPreview], () => {
unRegister();
if (canGroupPreview.value) {
unRegister = registerImage({
id: currentId.value,
url: props.src,
name: props.name,
size: imageSize,
download: props.download
});
}
}, {
immediate: true
});
onUnmounted(() => {
if (unRegister) {
unRegister();
}
if (clearScrollListener) {
clearScrollListener();
}
});
return {
imgAttrs,
imageStyle,
isShowPreview,
clickHandler,
closeViewer,
container,
prefixCls,
isLoadError,
loading,
imageSize,
style
};
}
});
const _hoisted_1 = ["src"];
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_PictureOutlined = resolveComponent("PictureOutlined");
const _component_PictureFailOutlined = resolveComponent("PictureFailOutlined");
const _component_Preview = resolveComponent("Preview");
return openBlock(), createElementBlock("div", {
ref: "container",
class: normalizeClass(_ctx.prefixCls),
style: normalizeStyle(_ctx.style)
}, [_ctx.loading ? renderSlot(_ctx.$slots, "placeholder", {
key: 0
}, () => [createElementVNode("div", {
class: normalizeClass(`${_ctx.prefixCls}__placeholder`)
}, [createVNode(_component_PictureOutlined), _cache[1] || (_cache[1] = createElementVNode("span", null, "加载中", -1 /* HOISTED */))], 2 /* CLASS */)]) : _ctx.isLoadError ? renderSlot(_ctx.$slots, "error", {
key: 1
}, () => [createElementVNode("div", {
class: normalizeClass(`${_ctx.prefixCls}__error`)
}, [createVNode(_component_PictureFailOutlined), _cache[2] || (_cache[2] = createElementVNode("span", null, "加载失败", -1 /* HOISTED */))], 2 /* CLASS */)]) : (openBlock(), createElementBlock("div", {
key: 2,
class: normalizeClass(`${_ctx.prefixCls}__inner`),
onClick: _cache[0] || (_cache[0] = function () {
return _ctx.clickHandler && _ctx.clickHandler(...arguments);
})
}, [renderSlot(_ctx.$slots, "default", {}, () => [createElementVNode("img", mergeProps({
src: _ctx.src,
class: `${_ctx.prefixCls}__inner-image`,
style: _ctx.imageStyle
}, _ctx.imgAttrs), null, 16 /* FULL_PROPS */, _hoisted_1)])], 2 /* CLASS */)), _ctx.isShowPreview ? (openBlock(), createBlock(_component_Preview, {
key: 3,
src: _ctx.src,
name: _ctx.name,
size: _ctx.imageSize,
download: _ctx.download,
"hide-on-click-modal": _ctx.hideOnClickModal,
getContainer: _ctx.previewContainer,
onClose: _ctx.closeViewer
}, null, 8 /* PROPS */, ["src", "name", "size", "download", "hide-on-click-modal", "getContainer", "onClose"])) : createCommentVNode("v-if", true)], 6 /* CLASS, STYLE */);
}
script.render = render;
script.__file = "components/image/image.vue";
export { script as default, imageProps };