Prechádzať zdrojové kódy

添加查看更多组件

icssoa 6 mesiacov pred
rodič
commit
b64b5c5b62

+ 6 - 0
pages.json

@@ -317,6 +317,12 @@
 					}
 				},
 				{
+					"path": "data/read-more",
+					"style": {
+						"navigationBarTitleText": "ReadMore 查看更多"
+					}
+				},
+				{
 					"path": "data/draggable",
 					"style": {
 						"navigationBarTitleText": "Draggable 拖拽"

+ 85 - 0
pages/demo/data/read-more.uvue

@@ -0,0 +1,85 @@
+<template>
+	<cl-page>
+		<view class="p-3">
+			<demo-item :label="t('基础用法')">
+				<cl-read-more>
+					<cl-text>
+						云想衣裳花想容,春风拂槛露华浓。若非群玉山头见,会向瑶台月下逢。
+						一枝红艳露凝香,云雨巫山枉断肠。借问汉宫谁得似?可怜飞燕倚新妆。
+						名花倾国两相欢,常得君王带笑看。解释春风无限恨,沉香亭北倚阑干。
+					</cl-text>
+				</cl-read-more>
+			</demo-item>
+
+			<demo-item :label="t('禁用切换按钮')">
+				<cl-read-more
+					v-model="visible"
+					:disabled="disabled"
+					:expand-text="disabled ? '付费解锁' : '展开'"
+					:expand-icon="disabled ? 'lock-line' : 'arrow-down-s-line'"
+					@toggle="toggle"
+				>
+					<cl-text>
+						云想衣裳花想容,春风拂槛露华浓。若非群玉山头见,会向瑶台月下逢。
+						一枝红艳露凝香,云雨巫山枉断肠。借问汉宫谁得似?可怜飞燕倚新妆。
+						名花倾国两相欢,常得君王带笑看。解释春风无限恨,沉香亭北倚阑干。
+					</cl-text>
+				</cl-read-more>
+			</demo-item>
+
+			<demo-item :label="t('自定义高度')">
+				<cl-read-more :height="300">
+					<cl-text>
+						云想衣裳花想容,春风拂槛露华浓。若非群玉山头见,会向瑶台月下逢。
+						一枝红艳露凝香,云雨巫山枉断肠。借问汉宫谁得似?可怜飞燕倚新妆。
+					</cl-text>
+
+					<cl-image
+						:height="300"
+						width="100%"
+						:pt="{
+							className: 'my-3'
+						}"
+						src="https://uni-docs.cool-js.com/demo/pages/demo/static/bg1.png"
+					></cl-image>
+
+					<cl-text>
+						名花倾国两相欢,常得君王带笑看。解释春风无限恨,沉香亭北倚阑干。
+					</cl-text>
+				</cl-read-more>
+			</demo-item>
+		</view>
+	</cl-page>
+</template>
+
+<script lang="ts" setup>
+import { router } from "@/cool";
+import { t } from "@/locale";
+import DemoItem from "../components/item.uvue";
+import { ref } from "vue";
+import { useUi } from "@/uni_modules/cool-ui";
+
+const ui = useUi();
+
+const visible = ref(false);
+const disabled = ref(true);
+
+function toggle(isExpanded: boolean) {
+	ui.showConfirm({
+		title: "提示",
+		message: "需支付100元才能解锁全部内容,是否继续?",
+		callback(action) {
+			if (action == "confirm") {
+				ui.showToast({
+					message: "支付成功"
+				});
+
+				disabled.value = false;
+				visible.value = true;
+			}
+		}
+	});
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 6 - 1
pages/index/home.uvue

@@ -48,7 +48,7 @@
 			<view class="group" v-for="item in data" :key="item.label">
 				<cl-text :pt="{ className: '!text-sm !text-surface-400 mb-2 ml-2' }">{{
 					item.label
-				}}</cl-text>
+				}}({{ item.children?.length ?? 0 }})</cl-text>
 
 				<view class="list">
 					<cl-row :gutter="10">
@@ -276,6 +276,11 @@ const data = computed<Item[]>(() => {
 					path: "/pages/demo/data/avatar"
 				},
 				{
+					label: t("查看更多"),
+					icon: "menu-add-line",
+					path: "/pages/demo/data/read-more"
+				},
+				{
 					label: t("列表"),
 					icon: "list-check",
 					path: "/pages/demo/data/list"

+ 230 - 0
uni_modules/cool-ui/components/cl-read-more/cl-read-more.uvue

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

+ 20 - 0
uni_modules/cool-ui/components/cl-read-more/props.ts

@@ -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;
+};

+ 2 - 0
uni_modules/cool-ui/index.d.ts

@@ -51,6 +51,7 @@ import type { ClProgressCircleProps, ClProgressCirclePassThrough } from "./compo
 import type { ClQrcodeProps } from "./components/cl-qrcode/props";
 import type { ClRadioProps, ClRadioPassThrough } from "./components/cl-radio/props";
 import type { ClRateProps, ClRatePassThrough } from "./components/cl-rate/props";
+import type { ClReadMoreProps, ClReadMorePassThrough } from "./components/cl-read-more/props";
 import type { ClRowProps, ClRowPassThrough } from "./components/cl-row/props";
 import type { ClSafeAreaProps, ClSafeAreaPassThrough } from "./components/cl-safe-area/props";
 import type { ClSelectProps, ClSelectPassThrough } from "./components/cl-select/props";
@@ -130,6 +131,7 @@ declare module "vue" {
 		"cl-qrcode": (typeof import('./components/cl-qrcode/cl-qrcode.uvue')['default']) & import('vue').DefineComponent<ClQrcodeProps>;
 		"cl-radio": (typeof import('./components/cl-radio/cl-radio.uvue')['default']) & import('vue').DefineComponent<ClRadioProps>;
 		"cl-rate": (typeof import('./components/cl-rate/cl-rate.uvue')['default']) & import('vue').DefineComponent<ClRateProps>;
+		"cl-read-more": (typeof import('./components/cl-read-more/cl-read-more.uvue')['default']) & import('vue').DefineComponent<ClReadMoreProps>;
 		"cl-row": (typeof import('./components/cl-row/cl-row.uvue')['default']) & import('vue').DefineComponent<ClRowProps>;
 		"cl-safe-area": (typeof import('./components/cl-safe-area/cl-safe-area.uvue')['default']) & import('vue').DefineComponent<ClSafeAreaProps>;
 		"cl-select": (typeof import('./components/cl-select/cl-select.uvue')['default']) & import('vue').DefineComponent<ClSelectProps>;

+ 4 - 0
uni_modules/cool-ui/types/component.d.ts

@@ -238,3 +238,7 @@ declare type ClMarqueeComponentPublicInstance = {
 	stop(): void;
 	reset(): void;
 };
+
+declare type ClReadMoreComponentPublicInstance = {
+	toggle(): void;
+};