|
@@ -1,319 +0,0 @@
|
|
|
-<!-- <template>
|
|
|
- <div ref="textOverflow" class="text-overflow" :style="boxStyle">
|
|
|
- <span ref="overEllipsis" class="content">{{ realText }}</span>
|
|
|
- <span class="slot-box" ref="slotRef" v-if="showSlotNode">
|
|
|
- <slot :click-toggle="toggle" :expanded="expanded">
|
|
|
- <el-button @click="toggle" link type="primary" class="ml5 tan">
|
|
|
- {{ expanded ? stowText : expandText }}
|
|
|
- <SvgIcon name="ele-ArrowUp" class="ml2" v-show="expanded" />
|
|
|
- <SvgIcon name="ele-ArrowRight" class="ml2" v-show="!expanded" />
|
|
|
- </el-button>
|
|
|
- </slot>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
-
|
|
|
-</template>
|
|
|
-
|
|
|
-<script lang="ts" setup name="CommonContent">
|
|
|
-import { computed, onMounted, ref, nextTick } from 'vue'
|
|
|
- //用法
|
|
|
- //<ExpandText :text="content" :maxLines="3">
|
|
|
- //</ExpandText>
|
|
|
-const props = defineProps({
|
|
|
- text: {
|
|
|
- type: String,
|
|
|
- required: true
|
|
|
- },
|
|
|
- maxLines: {
|
|
|
- type: Number,
|
|
|
- default: 3,
|
|
|
- },
|
|
|
- width: {
|
|
|
- type: Number,
|
|
|
- default: 0,
|
|
|
- },
|
|
|
- expandText: {
|
|
|
- type: String,
|
|
|
- default: "展开",
|
|
|
- },
|
|
|
- stowText: {
|
|
|
- type: String,
|
|
|
- default: "收起",
|
|
|
- }
|
|
|
-})
|
|
|
-const expanded = ref(false)
|
|
|
-const slotBoxWidth = ref(0)
|
|
|
-const showSlotNode = ref(false)
|
|
|
-const textOverflow = ref(null as any)
|
|
|
-const overEllipsis = ref(null as any)
|
|
|
-const slotRef = ref(null as any)
|
|
|
-const offset = ref(props.text.length)
|
|
|
-const textBoxWidth = ref(props.width)
|
|
|
-
|
|
|
-const boxStyle = computed(() => {
|
|
|
- if (props.width) {
|
|
|
- return {
|
|
|
- width: props.width + "%",
|
|
|
- };
|
|
|
- } else {
|
|
|
- return ''
|
|
|
- }
|
|
|
-})
|
|
|
-const realText = computed(() => {
|
|
|
- // 是否被截取
|
|
|
- const isCutOut = offset.value !== props.text.length;
|
|
|
- let realText = props.text;
|
|
|
- if (isCutOut && !expanded.value) {
|
|
|
- realText = props.text.slice(0, offset.value) + "...";
|
|
|
- }
|
|
|
- return realText;
|
|
|
-})
|
|
|
-
|
|
|
-const calculateOffset = (from: any, to: any) => {
|
|
|
- nextTick(() => {
|
|
|
- if (Math.abs(from - to) <= 1) return;
|
|
|
- if (isOverflow()) {
|
|
|
- to = offset.value;
|
|
|
- } else {
|
|
|
- from = offset.value;
|
|
|
- }
|
|
|
- offset.value = Math.floor((from + to) / 2);
|
|
|
- calculateOffset(from, to);
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const isOverflow = () => {
|
|
|
- const { len, lastWidth } = getLines();
|
|
|
-
|
|
|
- if (len < props.maxLines) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (props.maxLines) {
|
|
|
- // 超出部分 行数 > 最大行数 或则 已经是最大行数但最后一行宽度 + 后面内容超出正常宽度
|
|
|
- const lastLineOver = !!(
|
|
|
- len === props.maxLines &&
|
|
|
- lastWidth + slotBoxWidth.value > textBoxWidth.value
|
|
|
- );
|
|
|
- if (len > props.maxLines || lastLineOver) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
-}
|
|
|
-const getLines = () => {
|
|
|
- const clientRects = overEllipsis.value.getClientRects();
|
|
|
- return {
|
|
|
- len: clientRects.length,
|
|
|
- lastWidth: clientRects[clientRects.length - 1].width,
|
|
|
- };
|
|
|
-}
|
|
|
-
|
|
|
-const toggle = () => {
|
|
|
- expanded.value = !expanded.value;
|
|
|
-}
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- const { len } = getLines()
|
|
|
- if (len > props.maxLines) {
|
|
|
- showSlotNode.value = true
|
|
|
- nextTick(() => {
|
|
|
- slotBoxWidth.value = slotRef.value.clientWidth;
|
|
|
- textBoxWidth.value = textOverflow.value.clientWidth;
|
|
|
- calculateOffset(0, props.text.length);
|
|
|
- })
|
|
|
- }
|
|
|
-})
|
|
|
-</script>
|
|
|
-<style scoped lang="scss">
|
|
|
-.text-overflow {
|
|
|
- .slot-box {
|
|
|
- display: inline-block;
|
|
|
- }
|
|
|
-}
|
|
|
-</style> -->
|
|
|
-、
|
|
|
-<!--展开/收起 组件-->
|
|
|
-<template>
|
|
|
- <div
|
|
|
- class="collapse w100 pd15"
|
|
|
- ref="collapse"
|
|
|
- :style="{
|
|
|
- height: state.isShow ? state.containerH + 'px' : state.containerH + 'px',
|
|
|
- backgroundColor: backgroundColor,
|
|
|
- }"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="collapse-content"
|
|
|
- ref="content"
|
|
|
- :style="{ position: state.isShow ? 'unset' : 'absolute', top: 0, lineHeight: state.isShow ? '' : props.defaultHeight + 'px' }"
|
|
|
- >
|
|
|
- <slot></slot>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- v-if="!hideControl"
|
|
|
- v-show="state.controlVisible"
|
|
|
- class="collapse-control"
|
|
|
- :style="{
|
|
|
- backgroundColor: backgroundColor,
|
|
|
- position: 'absolute',
|
|
|
- bottom: '0',
|
|
|
- right: '0',
|
|
|
- height: props.defaultHeight + 'px',
|
|
|
- lineHeight: props.defaultHeight + 'px',
|
|
|
- }"
|
|
|
- >
|
|
|
- <el-button link type="primary" @click="handleControl">
|
|
|
- {{ state.isShow ? foldText : unfoldText }}
|
|
|
- <template v-if="!hideControlIcon">
|
|
|
- <SvgIcon name="ele-ArrowUp" class="ml4" :style="{ transform: state.isShow ? 'none' : 'rotateZ(90deg)' }" />
|
|
|
- </template>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script lang="ts" setup name="collapse">
|
|
|
-import { onMounted, reactive, watch, ref, onUnmounted, nextTick } from 'vue';
|
|
|
-// 定义父组件传过来的值
|
|
|
-const props = defineProps({
|
|
|
- visible: Boolean,
|
|
|
-
|
|
|
- // 默认显示的内容高度
|
|
|
- defaultHeight: {
|
|
|
- type: Number,
|
|
|
- default: 30,
|
|
|
- },
|
|
|
-
|
|
|
- // 是否隐藏内置控制按钮
|
|
|
- hideControl: Boolean,
|
|
|
-
|
|
|
- // 是否隐藏内置控制按钮icon
|
|
|
- hideControlIcon: Boolean,
|
|
|
-
|
|
|
- foldText: {
|
|
|
- type: String,
|
|
|
- default: '收起',
|
|
|
- },
|
|
|
-
|
|
|
- unfoldText: {
|
|
|
- type: String,
|
|
|
- default: '详情',
|
|
|
- },
|
|
|
- backgroundColor: {
|
|
|
- type: String,
|
|
|
- default: 'var(--hotline-bg-main-color)',
|
|
|
- },
|
|
|
-});
|
|
|
-
|
|
|
-// 定义子组件向父组件传值/事件
|
|
|
-const emit = defineEmits(['change']);
|
|
|
-const content = ref<RefType>();
|
|
|
-const state = reactive({
|
|
|
- isShow: false,
|
|
|
- controlVisible: true,
|
|
|
-
|
|
|
- containerH: props.defaultHeight,
|
|
|
- contentH: 0,
|
|
|
- DOMWatcher: null as any,
|
|
|
- padding: 14,
|
|
|
-});
|
|
|
-
|
|
|
-// 展开收起
|
|
|
-const handleControl = () => {
|
|
|
- if (state.isShow) handleFold();
|
|
|
- else unfold();
|
|
|
-};
|
|
|
-
|
|
|
-const init = () => {
|
|
|
- state.contentH = content.value.offsetHeight + 30 + 20;
|
|
|
- /**
|
|
|
- * 当内容变化的同时也是展开状态的话,那么需要更新容器高度
|
|
|
- * 1. 当最新的内容高度大于默认高度时,则把容器高度更新最新内容高度一致即可
|
|
|
- * 2. 反之,把容器高度设到最小(即默认高度),同时设为收起状态
|
|
|
- */
|
|
|
- if (state.isShow) {
|
|
|
- if (state.contentH > props.defaultHeight) {
|
|
|
- state.containerH = state.contentH;
|
|
|
- } else {
|
|
|
- state.containerH = props.defaultHeight;
|
|
|
- state.isShow = false;
|
|
|
- }
|
|
|
- }
|
|
|
- state.controlVisible = content.value.offsetHeight > props.defaultHeight; // 控制按钮的显示隐藏
|
|
|
-};
|
|
|
-
|
|
|
-const watcher = () => {
|
|
|
- state.DOMWatcher = new MutationObserver(() => {
|
|
|
- // 监测到DOM变化,重新计算高度
|
|
|
- refresh();
|
|
|
- });
|
|
|
- state.DOMWatcher.observe(content.value, {
|
|
|
- childList: true,
|
|
|
- attributes: true,
|
|
|
- characterData: true,
|
|
|
- subtree: true,
|
|
|
- });
|
|
|
-};
|
|
|
-const refresh = () => {
|
|
|
- nextTick(() => {
|
|
|
- init();
|
|
|
- });
|
|
|
-};
|
|
|
-// 收起
|
|
|
-const handleFold = () => {
|
|
|
- state.containerH = props.defaultHeight;
|
|
|
- nextTick(() => {
|
|
|
- state.isShow = false;
|
|
|
- });
|
|
|
-};
|
|
|
-// 展开
|
|
|
-const unfold = () => {
|
|
|
- const contentH = content.value.offsetHeight;
|
|
|
- if (contentH > props.defaultHeight) {
|
|
|
- state.containerH = contentH;
|
|
|
- }
|
|
|
- nextTick(() => {
|
|
|
- state.isShow = true;
|
|
|
- });
|
|
|
-};
|
|
|
-watch(
|
|
|
- () => props.visible,
|
|
|
- (v: any) => {
|
|
|
- if (v) {
|
|
|
- unfold();
|
|
|
- } else {
|
|
|
- handleFold();
|
|
|
- }
|
|
|
- }
|
|
|
-);
|
|
|
-watch(
|
|
|
- () => state.isShow,
|
|
|
- (v: any) => {
|
|
|
- emit('change', v);
|
|
|
- }
|
|
|
-);
|
|
|
-
|
|
|
-onMounted(() => {
|
|
|
- init();
|
|
|
- watcher();
|
|
|
- if (props.visible) {
|
|
|
- unfold();
|
|
|
- }
|
|
|
-});
|
|
|
-onUnmounted(() => {
|
|
|
- state.DOMWatcher.disconnect();
|
|
|
-});
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped lang="scss">
|
|
|
-.collapse {
|
|
|
- position: relative;
|
|
|
- overflow: hidden;
|
|
|
- border-radius: 3px;
|
|
|
- transition: height 0.28s ease-in-out;
|
|
|
- .collapse-control {
|
|
|
- z-index: 2;
|
|
|
- padding: 0 10px;
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|