<!-- <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="!hideControll"
			v-show="state.controllVisible"
			class="collapse-controll"
			:style="{ backgroundColor: backgroundColor, position: 'absolute', bottom: '0', right: '0',height:props.defaultHeight+'px',lineHeight:props.defaultHeight+'px' }"
		>
			<el-button link type="primary" @click="handleControll">
				{{ state.isShow ? foldText : unfoldText }}
				<template v-if="!hideControllIcon">
					<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,
	},

	// 是否隐藏内置控制按钮
	hideControll: Boolean,

	// 是否隐藏内置控制按钮icon
	hideControllIcon: 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();
const state = reactive({
	isShow: false,
	controllVisible: true,

	containerH: props.defaultHeight,
	contentH: 0,
	DOMWatcher: null as any,
	padding: 14,
});

// 展开收起
const handleControll = () => {
	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.controllVisible = 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-controll {
		z-index: 2;
		padding: 0 10px;
	}
}
</style>
