添加查看更多组件

This commit is contained in:
icssoa
2025-09-13 20:48:11 +08:00
parent 742e67bd5f
commit b64b5c5b62
7 changed files with 353 additions and 1 deletions

View File

@@ -0,0 +1,230 @@
<template>
<view class="cl-read-more" :class="[pt.className]">
<!-- 内容区域 -->
<view class="cl-read-more__wrapper" :class="[pt.wrapper?.className]" :style="wrapperStyle">
<view class="cl-read-more__content" :class="[pt.content?.className]">
<slot></slot>
</view>
<view
class="cl-read-more__mask"
:class="[
{
'is-show': !isExpanded && showToggle,
'is-dark': isDark
},
pt.mask?.className
]"
></view>
</view>
<!-- 展开/收起按钮 -->
<slot name="toggle" :isExpanded="isExpanded">
<view
class="cl-read-more__toggle"
:class="[
{
'is-disabled': disabled
},
pt.toggle?.className
]"
@tap="toggle"
v-if="showToggle"
>
<cl-text
color="primary"
:pt="{
className: 'text-sm mr-1'
}"
>
{{ isExpanded ? collapseText : expandText }}
</cl-text>
<cl-icon :name="isExpanded ? collapseIcon : expandIcon" color="primary"></cl-icon>
</view>
</slot>
</view>
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, ref, onMounted, watch } from "vue";
import { getPx, isDark, parsePt } from "@/cool";
import type { PassThroughProps } from "../../types";
import { t } from "@/locale";
defineOptions({
name: "cl-read-more"
});
// 组件属性定义
const props = defineProps({
// 透传样式
pt: {
type: Object,
default: () => ({})
},
// 是否展开
modelValue: {
type: Boolean,
default: false
},
// 收起状态下的最大高度
height: {
type: [Number, String],
default: 80
},
// 展开文本
expandText: {
type: String,
default: () => t("展开")
},
// 收起文本
collapseText: {
type: String,
default: () => t("收起")
},
// 展开图标
expandIcon: {
type: String,
default: "arrow-down-s-line"
},
// 收起图标
collapseIcon: {
type: String,
default: "arrow-up-s-line"
},
// 是否禁用
disabled: {
type: Boolean,
default: false
}
});
// 事件定义
const emit = defineEmits(["update:modelValue", "change", "toggle"]);
// 插槽定义
defineSlots<{
toggle(props: { isExpanded: boolean }): any;
}>();
const { proxy } = getCurrentInstance()!;
// 透传样式类型
type PassThrough = {
className?: string;
wrapper?: PassThroughProps;
content?: PassThroughProps;
mask?: PassThroughProps;
toggle?: PassThroughProps;
};
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 展开状态
const isExpanded = ref(props.modelValue);
// 内容实际高度
const contentHeight = ref(0);
// 是否显示切换按钮
const showToggle = ref(true);
// 包裹器样式
const wrapperStyle = computed(() => {
const style = {};
if (showToggle.value) {
style["height"] = isExpanded.value ? `${contentHeight.value}px` : `${props.height}rpx`;
}
return style;
});
/**
* 切换展开/收起状态
*/
function toggle() {
if (props.disabled) {
emit("toggle", isExpanded.value);
return;
}
isExpanded.value = !isExpanded.value;
emit("update:modelValue", isExpanded.value);
emit("change", isExpanded.value);
}
/**
* 获取内容实际高度
*/
function getContentHeight() {
uni.createSelectorQuery()
.in(proxy)
.select(".cl-read-more__content")
.boundingClientRect((node) => {
// 获取内容高度
contentHeight.value = (node as NodeInfo).height ?? 0;
// 实际高度是否大于折叠高度
showToggle.value = contentHeight.value > getPx(props.height);
})
.exec();
}
// 监听modelValue变化
watch(
computed(() => props.modelValue),
(val: boolean) => {
isExpanded.value = val;
}
);
// 组件挂载后获取内容高度
onMounted(() => {
getContentHeight();
});
// 暴露方法
defineExpose({
toggle
});
</script>
<style lang="scss" scoped>
.cl-read-more {
@apply relative;
&__wrapper {
@apply relative duration-300;
transition-property: height;
}
&__mask {
@apply absolute bottom-0 left-0 w-full h-14 opacity-0 pointer-events-none;
// #ifndef APP-IOS
transition-duration: 200ms;
transition-property: opacity;
// #endif
background-image: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0));
&.is-dark {
background-image: linear-gradient(to top, rgba(30, 30, 30, 1), rgba(30, 30, 30, 0));
}
&.is-show {
@apply opacity-100;
}
}
&__toggle {
@apply flex flex-row items-center justify-center mt-2;
&.is-disabled {
@apply opacity-50;
}
}
}
</style>

View File

@@ -0,0 +1,20 @@
import type { PassThroughProps } from "../../types";
export type ClReadMorePassThrough = {
className?: string;
content?: PassThroughProps;
mask?: PassThroughProps;
toggle?: PassThroughProps;
};
export type ClReadMoreProps = {
className?: string;
pt?: ClReadMorePassThrough;
modelValue?: boolean;
height?: any;
expandText?: string;
collapseText?: string;
expandIcon?: string;
collapseIcon?: string;
disabled?: boolean;
};