UNPKG

vxe-pc-ui

Version:
250 lines (249 loc) 9.19 kB
import { defineComponent, ref, h, watch, computed, reactive, resolveComponent, onMounted, onBeforeUnmount } from 'vue'; import XEUtils from 'xe-utils'; import { getConfig, createEvent, usePermission, useSize, renderEmptyElement } from '../../ui'; export default defineComponent({ name: 'VxeTextEllipsis', props: { href: String, target: String, content: [String, Number], lineClamp: [String, Number], status: String, title: [String, Number], loading: Boolean, offsetLength: [String, Number], routerLink: Object, underline: { type: Boolean, default: () => getConfig().textEllipsis.underline }, /** * 权限码 */ permissionCode: [String, Number], size: { type: String, default: () => getConfig().textEllipsis.size || getConfig().size } }, emits: [ 'click' ], setup(props, context) { const { emit } = context; const xID = XEUtils.uniqueId(); const { computeSize } = useSize(props); const { computePermissionInfo } = usePermission(props); const refElem = ref(); const realityElem = ref(); const reactData = reactive({ resizeObserver: null, visibleLen: 0 }); const refMaps = { refElem }; const computeTextLineClamp = computed(() => { return XEUtils.toNumber(props.lineClamp); }); const computeTextContent = computed(() => { return XEUtils.toValueString(props.content); }); const computeTextOffsetLength = computed(() => { return props.offsetLength ? XEUtils.toNumber(props.offsetLength) : 0; }); const computeVisibleContent = computed(() => { const { visibleLen } = reactData; const textLineClamp = computeTextLineClamp.value; const textContent = computeTextContent.value; const textOffsetLength = computeTextOffsetLength.value; if (textLineClamp > 1) { if (textContent.length > visibleLen) { return `${textContent.slice(0, Math.max(1, visibleLen - 3 + textOffsetLength))}...`; } return textContent; } return textContent; }); const computeMaps = {}; const $xeTextEllipsis = { xID, props, context, reactData, getRefMaps: () => refMaps, getComputeMaps: () => computeMaps }; const dispatchEvent = (type, params, evnt) => { emit(type, createEvent(evnt, { $textEllipsis: $xeTextEllipsis }, params)); }; const calculateFont = (targetWidth) => { const el = refElem.value; const ryEl = realityElem.value; if (el && ryEl) { let fontSize = 12; try { fontSize = Math.max(10, XEUtils.toNumber(getComputedStyle(ryEl).fontSize)); } catch (e) { } const textContent = computeTextContent.value; let currIndex = Math.floor((targetWidth) / fontSize); let currStr = textContent.slice(0, currIndex); ryEl.textContent = currStr; reactData.visibleLen = currStr.length; let maxCount = 0; while (targetWidth > ryEl.clientWidth && maxCount < 30) { maxCount++; const offsetIndex = Math.floor((targetWidth - ryEl.clientWidth) / fontSize); if (offsetIndex) { currIndex += offsetIndex; currStr = textContent.slice(0, currIndex); ryEl.textContent = currStr; reactData.visibleLen = currStr.length; } else { break; } } ryEl.textContent = ''; ryEl.style.display = ''; ryEl.style.position = ''; ryEl.style.top = ''; ryEl.style.left = ''; } }; const updateStyle = () => { const el = refElem.value; const ryEl = realityElem.value; const textContent = computeTextContent.value; const textLineClamp = computeTextLineClamp.value; if (el && ryEl) { const cWidth = el.clientWidth; ryEl.style.display = 'block'; ryEl.style.position = 'absolute'; ryEl.style.top = '-3000px'; ryEl.style.left = '-3000px'; ryEl.textContent = textContent; const sWidth = ryEl.offsetWidth; const targetWidth = Math.floor(cWidth * textLineClamp); if (targetWidth > sWidth) { reactData.visibleLen = textContent.length; } else { calculateFont(targetWidth); } } else { reactData.visibleLen = textContent.length; } }; const textEllipsisMethods = { dispatchEvent }; const clickEvent = () => { emit('click', {}); }; const initObserver = () => { const { resizeObserver } = reactData; const textLineClamp = computeTextLineClamp.value; if (!resizeObserver) { const el = refElem.value; if (el && textLineClamp > 1) { if (window.ResizeObserver) { const observerObj = new window.ResizeObserver(XEUtils.throttle(() => { updateStyle(); }, 300, { leading: true, trailing: true })); observerObj.observe(el); reactData.resizeObserver = observerObj; } } } }; const textEllipsisPrivateMethods = {}; Object.assign($xeTextEllipsis, textEllipsisMethods, textEllipsisPrivateMethods); const renderContent = () => { const { routerLink, href, target, title } = props; const visibleContent = computeVisibleContent.value; if (routerLink) { return h(resolveComponent('router-link'), { class: 'vxe-text-ellipsis--link', title, target, to: routerLink }, { default() { return renderContent(); } }); } if (href) { return h('a', { class: 'vxe-text-ellipsis--link', href, target, title }, visibleContent); } return h('span', { class: 'vxe-text-ellipsis--content' }, visibleContent); }; const renderVN = () => { const { loading, status, title, underline } = props; const permissionInfo = computePermissionInfo.value; const vSize = computeSize.value; const textLineClamp = computeTextLineClamp.value; if (!permissionInfo.visible) { return renderEmptyElement($xeTextEllipsis); } return h('div', { ref: refElem, class: ['vxe-text-ellipsis', textLineClamp > 1 ? 'is--multi' : 'is--single', { [`size--${vSize}`]: vSize, [`theme--${status}`]: status, 'is--underline': underline, 'is--loading': loading }], title, onClick: clickEvent }, [ h('span', { ref: realityElem, class: 'vxe-text-ellipsis--reality' }), renderContent() ]); }; watch(() => props.content, () => { updateStyle(); }); watch(() => props.lineClamp, () => { initObserver(); updateStyle(); }); onMounted(() => { initObserver(); updateStyle(); }); onBeforeUnmount(() => { const { resizeObserver } = reactData; const el = refElem.value; const ryEl = realityElem.value; if (ryEl) { ryEl.textContent = ''; } if (resizeObserver) { if (el) { resizeObserver.unobserve(el); } resizeObserver.disconnect(); reactData.resizeObserver = null; } }); $xeTextEllipsis.renderVN = renderVN; return $xeTextEllipsis; }, render() { return this.renderVN(); } });