| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- <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>
|