272 lines
7.6 KiB
Vue
272 lines
7.6 KiB
Vue
<script setup lang="ts">
|
|
import type { ComponentInternalInstance } from 'vue'
|
|
import { computed, defineComponent, getCurrentInstance, onMounted, reactive, ref } from 'vue'
|
|
import { CLICK_EVENT, PREFIX } from '../_constants'
|
|
import { useRect, useSelectorQuery } from '../_hooks'
|
|
import { getRandomId } from '../_utils'
|
|
import type { EllipsisedValue } from './ellipsis'
|
|
import { ellipsisEmits, ellipsisProps } from './ellipsis'
|
|
|
|
const props = defineProps(ellipsisProps)
|
|
|
|
const emit = defineEmits(ellipsisEmits)
|
|
const instance = getCurrentInstance() as ComponentInternalInstance
|
|
const { query } = useSelectorQuery(instance)
|
|
const refRandomId = getRandomId()
|
|
const rootId = `root${refRandomId}`
|
|
const symbolContainId = `symbolContain${refRandomId}`
|
|
const rootContainId = `rootContain${refRandomId}`
|
|
const contantCopy = ref(props.content)
|
|
let maxHeight = 0 // 超出的最大高度
|
|
let lineHeight = 0 // 当行的最大高度
|
|
let originHeight = 0 // 原始高度
|
|
const ellipsis = reactive<EllipsisedValue>({})
|
|
const widthRef = ref('auto')
|
|
const state = reactive({
|
|
exceeded: false, // 是否超出
|
|
expanded: false, // 是否折叠
|
|
})
|
|
|
|
let widthBase = [14, 10, 7, 8.4, 10] // 中、英(大)、英(小)、数字、其他字符的基础宽度
|
|
let symbolTextWidth = widthBase[0] * 0.7921
|
|
const chineseReg = /^[\u4E00-\u9FA5]+$/ // 汉字
|
|
const digitReg = /^\d+$/ // 数字
|
|
const letterUpperReg = /^[A-Z]+$/ // 字母
|
|
const letterLowerReg = /^[a-z]+$/ // 字母
|
|
|
|
const classes = computed(() => {
|
|
const prefixCls = componentName
|
|
return {
|
|
ell: true,
|
|
[prefixCls]: true,
|
|
}
|
|
})
|
|
|
|
const symbolText = computed(() => {
|
|
if (props.direction === 'end' || props.direction === 'middle')
|
|
return `${props.symbol}${props.expandText}`
|
|
|
|
return `${props.symbol}${props.expandText}${props.symbol}`
|
|
})
|
|
|
|
onMounted(() => {
|
|
setTimeout(() => {
|
|
getSymbolInfo()
|
|
getReference()
|
|
}, 100)
|
|
})
|
|
// 获取省略号宽度
|
|
async function getSymbolInfo() {
|
|
const refe = await useRect(symbolContainId, instance)
|
|
symbolTextWidth = refe.width ? Math.ceil(refe.width) : Math.ceil(widthBase[0] * 0.7921)
|
|
}
|
|
|
|
async function getReference() {
|
|
query.select(rootId)
|
|
&& query
|
|
.select(`#${rootId}`)
|
|
.fields(
|
|
{
|
|
computedStyle: ['width', 'height', 'lineHeight', 'paddingTop', 'paddingBottom', 'fontSize'],
|
|
},
|
|
(res: any) => {
|
|
lineHeight = pxToNumber(res.lineHeight === 'normal' ? props.lineHeight : res.lineHeight)
|
|
maxHeight = Math.floor(
|
|
lineHeight * (Number(props.rows) + 0.5) + pxToNumber(res.paddingTop) + pxToNumber(res.paddingBottom),
|
|
)
|
|
|
|
originHeight = pxToNumber(res.height)
|
|
|
|
widthRef.value = res.width
|
|
|
|
// 设置基础字符
|
|
const bsize = pxToNumber(res.fontSize)
|
|
widthBase = [bsize, bsize * 0.72, bsize * 0.53, bsize * 0.4, bsize * 0.75]
|
|
|
|
calcEllipse()
|
|
},
|
|
)
|
|
.exec()
|
|
}
|
|
|
|
// 计算省略号的位置
|
|
async function calcEllipse() {
|
|
const refe = await useRect(rootContainId, instance)
|
|
|
|
if (refe.height! <= maxHeight) {
|
|
state.exceeded = false
|
|
}
|
|
else {
|
|
const rowNum = Math.floor(props.content.length / (originHeight / lineHeight - 1)) // 每行的字数
|
|
|
|
if (props.direction === 'middle') {
|
|
const end = props.content.length
|
|
ellipsis.leading = tailorContent(0, rowNum * (Number(props.rows) + 0.5), 'end')
|
|
ellipsis.tailing = tailorContent(props.content.length - rowNum * (Number(props.rows) + 0.5), end, 'start')
|
|
}
|
|
else if (props.direction === 'end') {
|
|
const end = rowNum * (Number(props.rows) + 0.5)
|
|
ellipsis.leading = tailorContent(0, end)
|
|
}
|
|
else {
|
|
const start = props.content.length - rowNum * (Number(props.rows) + 0.5) - 5
|
|
|
|
ellipsis.tailing = tailorContent(start, props.content.length)
|
|
}
|
|
|
|
// 进行兜底判断,是否符合要求
|
|
assignContent()
|
|
setTimeout(() => {
|
|
verifyEllipsis()
|
|
}, 100)
|
|
}
|
|
}
|
|
|
|
// 验证省略号
|
|
async function verifyEllipsis() {
|
|
const refe = await useRect(rootContainId, instance)
|
|
if (refe && refe.height && refe.height > maxHeight) {
|
|
if (props.direction === 'end')
|
|
ellipsis.leading = ellipsis.leading?.slice(0, ellipsis.leading.length - 1)
|
|
else
|
|
ellipsis.tailing = ellipsis.tailing?.slice(1, ellipsis.tailing.length)
|
|
|
|
assignContent()
|
|
setTimeout(() => {
|
|
verifyEllipsis()
|
|
}, 100)
|
|
}
|
|
}
|
|
|
|
function assignContent() {
|
|
contantCopy.value = `${ellipsis.leading || ''}${ellipsis.leading ? props.symbol : ''}${props.expandText || ''}${ellipsis.tailing ? props.symbol : ''}${ellipsis.tailing || ''}`
|
|
}
|
|
// 计算省略号
|
|
function tailorContent(left: number, right: number, type = '') {
|
|
const threeDotWidth = symbolTextWidth
|
|
|
|
const direc = props.direction === 'middle' && type ? type : props.direction
|
|
|
|
state.exceeded = true
|
|
|
|
let widthPart = -1
|
|
const start = left
|
|
const end = right
|
|
let cutoff = 0
|
|
let marking = 0
|
|
|
|
if (direc === 'end') {
|
|
marking = start
|
|
cutoff = end
|
|
}
|
|
else {
|
|
marking = end
|
|
cutoff = start
|
|
}
|
|
|
|
const contentWidth = pxToNumber(widthRef.value) * Number(props.rows) - threeDotWidth
|
|
const contentPartWidth = props.direction === 'middle' ? contentWidth / 2 : contentWidth
|
|
|
|
while (widthPart < contentPartWidth) {
|
|
const zi = props.content[marking]
|
|
if (chineseReg.test(zi))
|
|
widthPart = Number(widthPart + widthBase[0])
|
|
else if (letterUpperReg.test(zi))
|
|
widthPart = Number(widthPart + widthBase[1])
|
|
else if (letterLowerReg.test(zi))
|
|
widthPart = Number(widthPart + widthBase[2])
|
|
else if (digitReg.test(zi))
|
|
widthPart = Number(widthPart + widthBase[3])
|
|
else
|
|
widthPart = Number(widthPart + widthBase[4])
|
|
|
|
cutoff = marking
|
|
|
|
direc === 'end' ? marking++ : marking--
|
|
}
|
|
|
|
if (direc === 'end')
|
|
return props.content.slice(0, cutoff)
|
|
else
|
|
return props.content.slice(cutoff, end)
|
|
}
|
|
|
|
function pxToNumber(value: string | null) {
|
|
if (!value)
|
|
return 0
|
|
const match = value.match(/^\d*(\.\d*)?/)
|
|
return match ? Number(match[0]) : 0
|
|
}
|
|
|
|
// 展开收起
|
|
function clickHandle(type: number) {
|
|
if (type === 1) {
|
|
state.expanded = true
|
|
emit('change', 'expand')
|
|
}
|
|
else {
|
|
state.expanded = false
|
|
emit('change', 'collapse')
|
|
}
|
|
}
|
|
|
|
// 文本点击
|
|
function handleClick() {
|
|
emit(CLICK_EVENT)
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
const componentName = `${PREFIX}-ellipsis`
|
|
|
|
export default defineComponent({
|
|
name: componentName,
|
|
options: {
|
|
virtualHost: true,
|
|
addGlobalClass: true,
|
|
styleIsolation: 'shared',
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<view :class="customClass" :style="customStyle">
|
|
<view :id="rootId" :class="classes" @click="handleClick">
|
|
<view v-if="!state.exceeded" class="nut-ellipsis__wordbreak">
|
|
{{ content }}
|
|
</view>
|
|
|
|
<view v-if="state.exceeded && !state.expanded" class="nut-ellipsis__wordbreak">
|
|
{{ ellipsis.leading }}{{ ellipsis.leading && symbol
|
|
}}
|
|
<view v-if="expandText" class="nut-ellipsis__text" @click.stop="clickHandle(1)">
|
|
{{ expandText }}
|
|
</view>{{ ellipsis.tailing && symbol }}{{ ellipsis.tailing }}
|
|
</view>
|
|
<view v-if="state.exceeded && state.expanded">
|
|
{{ content }}
|
|
<view v-if="expandText" class="nut-ellipsis__text" @click.stop="clickHandle(2)">
|
|
{{ collapseText }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view :id="rootContainId" class="nut-ellipsis__copy" :style="{ width: widthRef }">
|
|
<view>{{ contantCopy }}</view>
|
|
</view>
|
|
|
|
<!-- 省略号 symbol -->
|
|
<view :id="symbolContainId" class="nut-ellipsis__copy" style="display: inline">
|
|
{{
|
|
symbolText
|
|
}}
|
|
</view>
|
|
|
|
<!-- 数字 9 英文 W -->
|
|
</view>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@import './index';
|
|
</style>
|