493 lines
16 KiB
Vue
493 lines
16 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="zh-readMore">
|
|||
|
|
<view class="zh-readMore-wrapper" :style="[textStyle]">
|
|||
|
|
<view
|
|||
|
|
class="zh-text"
|
|||
|
|
:class="textClass"
|
|||
|
|
:style="[textStyleObject, { '--unfold_packup_line': `${unfold_packup_line}px` }, { '--duration': `${duration}s` }]"
|
|||
|
|
>
|
|||
|
|
<view class="zh-open" :style="[unfoldTextStyle]" v-if="!showall && is_open" @click.stop="open">
|
|||
|
|
<text class="zh-ellipsis">...</text>
|
|||
|
|
<text class="zh-open-text">{{ unfoldText }}</text>
|
|||
|
|
</view>
|
|||
|
|
<text class="" :style="[lineBreakStyles]">
|
|||
|
|
<text v-if="title" :style="[titleStyle]">{{ title }}</text>
|
|||
|
|
{{ text }}
|
|||
|
|
</text>
|
|||
|
|
<text class="zh-fewer" v-if="is_open && showall" @click.stop="open">
|
|||
|
|
<text class="zh-fewer-text" :style="[packupTextStyle]">{{ packupText }}</text>
|
|||
|
|
</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="zh-overflowcont" id="zh-overflowcont" :style="[textStyle, lineStyle]">
|
|||
|
|
<text class="zh-overflowcont-text" :style="[lineBreakStyle]">
|
|||
|
|
<text v-if="title" :style="[titleStyle]">{{ title }}</text>
|
|||
|
|
{{ text }}
|
|||
|
|
</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="zh-overflowcont" id="zh-overflowcontline" :style="[textStyle]">
|
|||
|
|
<text class="" :style="[lineBreakStyle]">{{ text }}</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="zh-overflowcont">
|
|||
|
|
<view class="" :style="[unfoldTextStyle]" id="zh-open">
|
|||
|
|
<text class="zh-ellipsis">...</text>
|
|||
|
|
<text class="zh-overflowcont-open">{{ unfoldText }}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<!-- #ifdef VUE3 -->
|
|||
|
|
<!-- vue3 -->
|
|||
|
|
<script lang="ts" setup>
|
|||
|
|
/*
|
|||
|
|
* @param {text,String} 需要展示的文本
|
|||
|
|
* @param {textStyle,Object} 需要展示的文本样式
|
|||
|
|
* @param {rows,Number} 文本容器中超出多行后显示省略号
|
|||
|
|
* @param {lineBreak,Boolean} 保留空白字符序列,同时允许正常换行
|
|||
|
|
* @param {packupLineBreak,Boolean} 只在收起时,忽略空白字符;展开时,保留空白字符序列,同时允许正常换行
|
|||
|
|
* @param {unfoldText,String} 展开操作的文案
|
|||
|
|
* @param {unfoldTextStyle,Object} 展开操作的样式
|
|||
|
|
* @param {packupText,String} 收起操作的文案
|
|||
|
|
* @param {packupTextStyle,Object} 收起操作的样式
|
|||
|
|
* @param {duration,Number || String} 展示动画过度时间(单位s)
|
|||
|
|
*/
|
|||
|
|
import { computed, nextTick, ref, watch, getCurrentInstance } from 'vue'
|
|||
|
|
const props = defineProps({
|
|||
|
|
title: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '',
|
|||
|
|
},
|
|||
|
|
titleStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
'font-weight': '600',
|
|||
|
|
'margin-right': '10rpx',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
text: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '',
|
|||
|
|
},
|
|||
|
|
textStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#000',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
rows: {
|
|||
|
|
type: Number,
|
|||
|
|
default: 3,
|
|||
|
|
},
|
|||
|
|
lineBreak: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true,
|
|||
|
|
},
|
|||
|
|
packupLineBreak: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false,
|
|||
|
|
},
|
|||
|
|
unfoldText: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '展开',
|
|||
|
|
},
|
|||
|
|
unfoldTextStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#206ef7',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
packupText: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '收起',
|
|||
|
|
},
|
|||
|
|
packupTextStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#206ef7',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
duration: {
|
|||
|
|
type: [Number, String],
|
|||
|
|
default: () => 0,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const box_h = ref(0) //展开后的高度
|
|||
|
|
const showall = ref(false) //是否展开
|
|||
|
|
const is_open = ref(false) //是否显示展开/收起按钮
|
|||
|
|
const unfold_packup_line = ref(22) //展开收起宽度
|
|||
|
|
const settime = ref(null) //定时器标识
|
|||
|
|
|
|||
|
|
const instance = getCurrentInstance()
|
|||
|
|
const selectQuery = uni.createSelectorQuery().in(instance.proxy)
|
|||
|
|
|
|||
|
|
//导出自定义事件
|
|||
|
|
const emit = defineEmits(['click'])
|
|||
|
|
|
|||
|
|
//事件
|
|||
|
|
//展开收起事件
|
|||
|
|
const open = () => {
|
|||
|
|
showall.value = !showall.value
|
|||
|
|
emit('click', showall.value) //展开true 关闭false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
//计算属性
|
|||
|
|
|
|||
|
|
const textStyleObject = computed(() => {
|
|||
|
|
const { rows } = props
|
|||
|
|
return {
|
|||
|
|
'max-height': showall.value ? `${1.5 * box_h.value}em` : `${1.5 * rows}em`,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const textClass = computed(() => {
|
|||
|
|
return showall.value ? 'showall' : ''
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const lineStyle = computed(() => {
|
|||
|
|
const { rows } = props
|
|||
|
|
return {
|
|||
|
|
display: '-webkit-box',
|
|||
|
|
'-webkit-box-orient': 'vertical',
|
|||
|
|
'-webkit-line-clamp': rows,
|
|||
|
|
overflow: 'hidden',
|
|||
|
|
'word-break': 'break-all',
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const lineBreakStyles = computed(() => {
|
|||
|
|
const { lineBreak, packupLineBreak } = props
|
|||
|
|
if (!lineBreak) {
|
|||
|
|
return {
|
|||
|
|
'white-space': 'normal',
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (packupLineBreak) {
|
|||
|
|
return {
|
|||
|
|
'white-space': showall.value ? 'pre-wrap' : 'normal',
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return {
|
|||
|
|
'white-space': 'pre-wrap',
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const lineBreakStyle = computed(() => {
|
|||
|
|
const { lineBreak } = props
|
|||
|
|
return {
|
|||
|
|
'white-space': lineBreak ? 'pre-wrap' : 'normal',
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 监听
|
|||
|
|
watch(
|
|||
|
|
() => props.text,
|
|||
|
|
(newValue, oldValue) => {
|
|||
|
|
const { rows } = props
|
|||
|
|
nextTick(() => {
|
|||
|
|
clearTimeout(settime.value)
|
|||
|
|
settime.value = setTimeout(() => {
|
|||
|
|
selectQuery.select(`#zh-overflowcont`).boundingClientRect()
|
|||
|
|
selectQuery.select('#zh-overflowcontline').boundingClientRect()
|
|||
|
|
selectQuery.select('#zh-open').boundingClientRect()
|
|||
|
|
selectQuery.exec(res => {
|
|||
|
|
if (res && res[0] && res[1]) {
|
|||
|
|
const h1 = res[0].height // 收起时高度
|
|||
|
|
const h2 = res[1].height // 展开时高度
|
|||
|
|
const lineCount = rows
|
|||
|
|
const lineHeight = h1 / lineCount
|
|||
|
|
|
|||
|
|
// 计算实际行数(考虑空行)
|
|||
|
|
const actualLines = Math.ceil(h2 / lineHeight)
|
|||
|
|
|
|||
|
|
// 如果实际行数超过限制行数,或者高度差超过0.1行,才显示展开/收起按钮
|
|||
|
|
if (actualLines > lineCount || h2 > h1 + lineHeight * 0.1) {
|
|||
|
|
is_open.value = true
|
|||
|
|
} else {
|
|||
|
|
is_open.value = false
|
|||
|
|
}
|
|||
|
|
box_h.value = Math.ceil(h2 / lineHeight) + 1
|
|||
|
|
showall.value = false
|
|||
|
|
}
|
|||
|
|
if (res && res[2]) {
|
|||
|
|
let unfold_packup_width = res[2].height - 1.5 || 22
|
|||
|
|
unfold_packup_line.value = unfold_packup_width
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}, 100)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
deep: true,
|
|||
|
|
immediate: true,
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<!-- #endif -->
|
|||
|
|
|
|||
|
|
<!-- #ifdef VUE2 -->
|
|||
|
|
<!-- vue2 -->
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
/*
|
|||
|
|
* @param {text,String} 需要展示的文本
|
|||
|
|
* @param {textStyle,Object} 需要展示的文本样式
|
|||
|
|
* @param {rows,Number} 文本容器中超出多行后显示省略号
|
|||
|
|
* @param {lineBreak,Boolean} 保留空白字符序列,同时允许正常换行
|
|||
|
|
* @param {packupLineBreak,Boolean} 只在收起时,忽略空白字符;展开时,保留空白字符序列,同时允许正常换行
|
|||
|
|
* @param {unfoldText,String} 展开操作的文案
|
|||
|
|
* @param {unfoldTextStyle,Object} 展开操作的样式
|
|||
|
|
* @param {packupText,String} 收起操作的文案
|
|||
|
|
* @param {packupTextStyle,Object} 收起操作的样式
|
|||
|
|
* @param {duration,Number || String} 展示动画过度时间(单位s)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'zh-readMore',
|
|||
|
|
props: {
|
|||
|
|
title: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '',
|
|||
|
|
},
|
|||
|
|
titleStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
'font-weight': '600',
|
|||
|
|
'margin-right': '10rpx',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
text: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '',
|
|||
|
|
},
|
|||
|
|
textStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#000',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
rows: {
|
|||
|
|
type: Number,
|
|||
|
|
default: 3,
|
|||
|
|
},
|
|||
|
|
lineBreak: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true,
|
|||
|
|
},
|
|||
|
|
packupLineBreak: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: false,
|
|||
|
|
},
|
|||
|
|
unfoldText: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '展开',
|
|||
|
|
},
|
|||
|
|
unfoldTextStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#206ef7',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
packupText: {
|
|||
|
|
type: String,
|
|||
|
|
default: () => '收起',
|
|||
|
|
},
|
|||
|
|
packupTextStyle: {
|
|||
|
|
type: Object,
|
|||
|
|
default: () => ({
|
|||
|
|
color: '#206ef7',
|
|||
|
|
'font-size': '28rpx',
|
|||
|
|
'font-weight': '400',
|
|||
|
|
}),
|
|||
|
|
},
|
|||
|
|
duration: {
|
|||
|
|
type: Number || String,
|
|||
|
|
default: () => 0,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
box_h: 0, //展开后的高度
|
|||
|
|
showall: false, //是否展开
|
|||
|
|
is_open: false, //是否显示展开/收起按钮
|
|||
|
|
unfold_packup_line: 22, //展开收起宽度
|
|||
|
|
settime: null, //定时器标识
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
//展开收起事件
|
|||
|
|
open() {
|
|||
|
|
this.showall = !this.showall
|
|||
|
|
this.$emit('click', this.showall) //展开true 关闭false
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
mounted() {},
|
|||
|
|
computed: {
|
|||
|
|
textStyleObject() {
|
|||
|
|
return {
|
|||
|
|
'max-height': this.showall ? `${1.5 * this.box_h}em` : `${1.5 * this.rows}em`,
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
textClass() {
|
|||
|
|
return this.showall ? 'showall' : ''
|
|||
|
|
},
|
|||
|
|
lineStyle() {
|
|||
|
|
return {
|
|||
|
|
display: '-webkit-box',
|
|||
|
|
'-webkit-box-orient': 'vertical',
|
|||
|
|
'-webkit-line-clamp': this.rows,
|
|||
|
|
overflow: 'hidden',
|
|||
|
|
'word-break': 'break-all',
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
lineBreakStyles() {
|
|||
|
|
if (!this.lineBreak) {
|
|||
|
|
return {
|
|||
|
|
'white-space': 'normal',
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if (this.packupLineBreak) {
|
|||
|
|
return {
|
|||
|
|
'white-space': this.showall ? 'pre-wrap' : 'normal',
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return {
|
|||
|
|
'white-space': 'pre-wrap',
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
lineBreakStyle() {
|
|||
|
|
return {
|
|||
|
|
'white-space': this.lineBreak ? 'pre-wrap' : 'normal',
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
watch: {
|
|||
|
|
text: {
|
|||
|
|
handler(newValue, oldValue) {
|
|||
|
|
this.$nextTick(() => {
|
|||
|
|
clearTimeout(this.settime)
|
|||
|
|
this.settime = setTimeout(() => {
|
|||
|
|
const selectQuery = uni.createSelectorQuery().in(this)
|
|||
|
|
selectQuery.select(`#zh-overflowcont`).boundingClientRect()
|
|||
|
|
selectQuery.select('#zh-overflowcontline').boundingClientRect()
|
|||
|
|
selectQuery.select('#zh-open').boundingClientRect()
|
|||
|
|
selectQuery.exec(res => {
|
|||
|
|
if (res && res[0] && res[1]) {
|
|||
|
|
const h1 = res[0].height // 收起时高度
|
|||
|
|
const h2 = res[1].height // 展开时高度
|
|||
|
|
const lineCount = this.rows
|
|||
|
|
const lineHeight = h1 / lineCount
|
|||
|
|
|
|||
|
|
// 计算实际行数(考虑空行)
|
|||
|
|
const actualLines = Math.ceil(h2 / lineHeight)
|
|||
|
|
|
|||
|
|
// 如果实际行数超过限制行数,或者高度差超过0.1行,才显示展开/收起按钮
|
|||
|
|
if (actualLines > lineCount || h2 > h1 + lineHeight * 0.1) {
|
|||
|
|
this.is_open = true
|
|||
|
|
} else {
|
|||
|
|
this.is_open = false
|
|||
|
|
}
|
|||
|
|
this.box_h = Math.ceil(h2 / lineHeight) + 1
|
|||
|
|
this.showall = false
|
|||
|
|
}
|
|||
|
|
if (res && res[2]) {
|
|||
|
|
let unfold_packup_width = res[2].height - 1.5 || 22
|
|||
|
|
this.unfold_packup_line = unfold_packup_width
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}, 100)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
deep: true,
|
|||
|
|
immediate: true,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<!-- #endif -->
|
|||
|
|
|
|||
|
|
<style lang="scss">
|
|||
|
|
.zh-readMore {
|
|||
|
|
position: relative;
|
|||
|
|
|
|||
|
|
.zh-readMore-wrapper {
|
|||
|
|
overflow: hidden;
|
|||
|
|
display: flex;
|
|||
|
|
|
|||
|
|
.zh-text {
|
|||
|
|
position: relative;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
text-align: justify;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
word-break: break-all;
|
|||
|
|
transition: var(--duration) max-height linear;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.zh-text::before {
|
|||
|
|
float: right;
|
|||
|
|
height: calc(100% - var(--unfold_packup_line));
|
|||
|
|
content: '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.zh-open {
|
|||
|
|
position: relative;
|
|||
|
|
float: right;
|
|||
|
|
clear: both;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
z-index: 2;
|
|||
|
|
|
|||
|
|
.zh-open-text {
|
|||
|
|
margin-left: 6rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.zh-fewer {
|
|||
|
|
display: inline-block;
|
|||
|
|
|
|||
|
|
.zh-fewer-text {
|
|||
|
|
margin-left: 6rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.zh-ellipsis {
|
|||
|
|
font-size: 30rpx;
|
|||
|
|
color: #434343;
|
|||
|
|
}
|
|||
|
|
.zh-overflowcont {
|
|||
|
|
position: absolute;
|
|||
|
|
top: -10000px;
|
|||
|
|
left: -10000px;
|
|||
|
|
width: 100%;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
text-align: justify;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
word-break: break-all;
|
|||
|
|
|
|||
|
|
.zh-overflowcont-text {
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.zh-overflowcont-open {
|
|||
|
|
margin-left: 6rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|