Skip to content

Commit c152dbc

Browse files
committed
feat: 新增文本省略组件
1 parent b56ae7c commit c152dbc

File tree

3 files changed

+196
-1
lines changed

3 files changed

+196
-1
lines changed

vue/README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1-
# Vue实用代码片段
1+
# Vue
2+
- Vue组件
3+
- [文本省略](./TextEllipsis/)
4+
- [瀑布流](./Waterfall/)
5+
- [无限滚动](./InfiniteScrollList/)
6+
- [权限控制](./Access/)
27
- [hook](./hook)

vue/TextEllipsis/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# 文本省略
2+
> 特点:支持展开折叠,支持自定义省略文本

vue/TextEllipsis/index.vue

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<template>
2+
<div ref="root" class="text-ellipsis-wrapper">
3+
{{ expanded ? props.content : text }}
4+
<span class="text-ellipsis-action" v-if="hasAction" @click="onClickAction">
5+
{{ actionText }}
6+
</span>
7+
</div>
8+
</template>
9+
10+
<script setup lang="ts">
11+
import {
12+
defineProps,
13+
withDefaults,
14+
defineExpose,
15+
defineEmits,
16+
onMounted,
17+
computed,
18+
ref,
19+
} from "vue";
20+
21+
export type TextEllipsisProps = {
22+
/**
23+
* @description 展示的文本
24+
*/
25+
content: string;
26+
/**
27+
* @description 文本行高,单位px,默认为 24px(16px * 1.5)
28+
*/
29+
lineHeight?: number;
30+
/**
31+
* @description 展示的行数,默认为1
32+
*/
33+
rows?: number;
34+
/**
35+
* @description 展开操作的文案,默认为"展开"
36+
*/
37+
expandText?: string;
38+
/**
39+
* @description 收起操作的文案,默认为"收起"
40+
*/
41+
collapseText?: string;
42+
/**
43+
* @description 省略号的文本内容,默认为"..."
44+
*/
45+
dots?: string;
46+
};
47+
48+
export type TextEllipsisExpose = {
49+
toogle: (collapse?: boolean) => void;
50+
};
51+
52+
const props = withDefaults(defineProps<TextEllipsisProps>(), {
53+
content: "",
54+
rows: 1,
55+
expandText: "展开",
56+
collapseText: "收起",
57+
dots: "...",
58+
lineHeight: 24,
59+
});
60+
61+
const text = ref<string>("");
62+
63+
const expanded = ref<boolean>(false);
64+
65+
const hasAction = ref<boolean>(false);
66+
67+
const root = ref<HTMLElement>();
68+
69+
// 操作按钮文本展示
70+
const actionText = computed(() =>
71+
expanded.value ? props.collapseText : props.expandText,
72+
);
73+
74+
// px转换为数字 例如:'12px' => 12
75+
const pxToNum = (value: string | null) => {
76+
if (!value) return 0;
77+
const match = value.match(/^\d*(\.\d*)?/);
78+
return match ? Number(match[0]) : 0;
79+
};
80+
81+
// 计算文本省略
82+
const calcEllipsised = () => {
83+
// 克隆一份容器,并离屏渲染到页面中
84+
const cloneContainer = () => {
85+
if (!root.value) return;
86+
87+
const originStyle = window.getComputedStyle(root.value);
88+
const container = document.createElement("div");
89+
const styleNames: string[] = Array.prototype.slice.apply(originStyle);
90+
styleNames.forEach((name) => {
91+
container.style.setProperty(name, originStyle.getPropertyValue(name));
92+
});
93+
// 离屏渲染
94+
container.style.position = "fixed";
95+
container.style.zIndex = "-9999";
96+
container.style.top = "-9999px";
97+
container.style.height = "auto";
98+
container.style.minHeight = "auto";
99+
container.style.maxHeight = "auto";
100+
// 该功能必须要求有全局行高(外层组件有设置过lineHeight),否则无法计算,以下是兼容处理
101+
container.style.lineHeight = props.lineHeight + "px";
102+
103+
container.innerText = props.content;
104+
document.body.appendChild(container);
105+
return container;
106+
};
107+
108+
const calcEllipsisText = (container: HTMLDivElement, maxHeight: number) => {
109+
const { content, dots } = props;
110+
const end = content.length;
111+
112+
const calcEllipse = () => {
113+
// 计算前后内容
114+
const tail = (left: number, right: number): string => {
115+
if (right - left <= 1) {
116+
return content.slice(0, left) + dots;
117+
}
118+
119+
const middle = Math.round((left + right) / 2);
120+
121+
// 设置拦截位置
122+
container.innerText =
123+
content.slice(0, middle) + dots + actionText.value;
124+
// 拦截后的高度仍然与要求的高度不匹配
125+
if (container.offsetHeight > maxHeight) {
126+
return tail(left, middle);
127+
}
128+
129+
return tail(middle, right);
130+
};
131+
132+
container.innerText = tail(0, end);
133+
};
134+
calcEllipse();
135+
return container.innerText;
136+
};
137+
138+
const container = cloneContainer();
139+
if (!container) return;
140+
const { paddingBottom, paddingTop, lineHeight } = container.style;
141+
const maxHeight = Math.ceil(
142+
(Number(props.rows) + 0.5) * pxToNum(lineHeight) +
143+
pxToNum(paddingTop) +
144+
pxToNum(paddingBottom),
145+
);
146+
147+
if (maxHeight < container.offsetHeight) {
148+
hasAction.value = true;
149+
150+
text.value = calcEllipsisText(container, maxHeight);
151+
} else {
152+
hasAction.value = false;
153+
text.value = props.content;
154+
}
155+
156+
document.body.removeChild(container);
157+
};
158+
159+
const toogle = (collapse?: boolean) => {
160+
if (collapse === undefined) {
161+
expanded.value = !expanded.value;
162+
return;
163+
}
164+
expanded.value = collapse;
165+
};
166+
167+
const emit = defineEmits<{
168+
(event: "onClickAction", e: Event): void;
169+
}>();
170+
171+
const onClickAction = (e: Event) => {
172+
emit("onClickAction", e);
173+
toogle();
174+
};
175+
176+
onMounted(calcEllipsised);
177+
178+
defineExpose<TextEllipsisExpose>({
179+
toogle,
180+
});
181+
</script>
182+
183+
<style scoped>
184+
.text-ellipsis-action {
185+
color: #1890ff;
186+
cursor: pointer;
187+
}
188+
</style>

0 commit comments

Comments
 (0)