Procházet zdrojové kódy

添加 商品详情 模板页面

icssoa před 5 měsíci
rodič
revize
f651f8e8bd

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "cool-unix",
-	"version": "8.0.28",
+	"version": "8.0.29",
 	"license": "MIT",
 	"scripts": {
 		"build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js",

+ 6 - 0
pages.json

@@ -523,6 +523,12 @@
 					}
 				},
 				{
+					"path": "shop/goods-detail/index",
+					"style": {
+						"navigationStyle": "custom"
+					}
+				},
+				{
 					"path": "shop/shopping-cart",
 					"style": {
 						"navigationStyle": "custom"

+ 2 - 1
pages/index/template.uvue

@@ -58,7 +58,8 @@ const list = computed<Item[]>(() => [
 				path: "/pages/template/shop/goods-category"
 			},
 			{
-				label: t("商品详情")
+				label: t("商品详情"),
+				path: "/pages/template/shop/goods-detail/index"
 			},
 			{
 				label: t("商品列表、筛选")

+ 81 - 0
pages/template/shop/goods-detail/comment.uvue

@@ -0,0 +1,81 @@
+<template>
+	<view class="flex">
+		<view class="flex flex-row items-center mb-3">
+			<cl-text>买家评论 78</cl-text>
+
+			<cl-icon name="arrow-right-s-line" :pt="{ className: 'ml-auto' }"></cl-icon>
+		</view>
+
+		<view class="flex flex-col">
+			<view class="flex flex-row my-3" v-for="item in list" :key="item.id">
+				<cl-avatar :size="72" rounded :src="item.avatar"></cl-avatar>
+
+				<view class="flex-1 ml-4">
+					<view class="flex flex-row items-center justify-between">
+						<cl-text>{{ item.name }}</cl-text>
+						<cl-text color="info" :pt="{ className: 'text-xs' }">{{
+							item.time
+						}}</cl-text>
+					</view>
+
+					<cl-text ellipsis :lines="2" :pt="{ className: 'mt-1 text-sm' }">{{
+						item.content
+					}}</cl-text>
+				</view>
+
+				<view class="ml-3 relative">
+					<cl-image
+						:height="100"
+						:width="100"
+						:pt="{
+							inner: {
+								className: '!rounded-lg'
+							}
+						}"
+						src="https://unix.cool-js.com/images/demo/bg1.png"
+					/>
+
+					<view class="bg-black/60 rounded-full px-1 absolute top-9 right-1">
+						<text class="text-xs text-white">+3</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+
+type Comment = {
+	id: number;
+	avatar: string;
+	name: string;
+	content: string;
+	time: string;
+};
+
+const list = ref<Comment[]>([
+	{
+		id: 1,
+		avatar: "https://unix.cool-js.com/images/demo/avatar-1.jpg",
+		name: "李明",
+		content: "导游讲解很专业,风景绝美,是一次难忘的旅行体验!",
+		time: "几分钟前"
+	},
+	{
+		id: 2,
+		avatar: "https://unix.cool-js.com/images/demo/avatar-2.jpg",
+		name: "小芳",
+		content: "酒店干净卫生,位置也很方便,强烈推荐给大家!",
+		time: "1小时前"
+	},
+	{
+		id: 3,
+		avatar: "https://unix.cool-js.com/images/demo/avatar-3.jpg",
+		name: "王科",
+		content: "行程安排合理,吃住都很满意,导游态度超级好。",
+		time: "5天前"
+	}
+]);
+</script>

+ 15 - 0
pages/template/shop/goods-detail/desc.uvue

@@ -0,0 +1,15 @@
+<template>
+	<view class="flex">
+		<image v-for="item in list" :key="item" :src="item" mode="widthFix" class="w-full" />
+	</view>
+</template>
+
+<script setup lang="ts">
+import { ref } from "vue";
+
+const list = ref<string[]>([
+	"https://unix.cool-js.com/images/demo/goods/desc-1.jpg",
+	"https://unix.cool-js.com/images/demo/goods/desc-2.jpg",
+	"https://unix.cool-js.com/images/demo/goods/desc-3.jpg"
+]);
+</script>

+ 116 - 0
pages/template/shop/goods-detail/index.uvue

@@ -0,0 +1,116 @@
+<template>
+	<cl-page>
+		<Topbar />
+
+		<cl-banner
+			:list="bannerList"
+			:height="800"
+			:pt="{
+				className: '!rounded-none',
+				image: {
+					className: '!rounded-none'
+				}
+			}"
+		/>
+
+		<view class="card dark:!bg-surface-700" id="info">
+			<Info />
+		</view>
+
+		<view class="card !py-1 dark:!bg-surface-700">
+			<view class="row is-border dark:!border-surface-600">
+				<cl-icon name="apps-line"></cl-icon>
+
+				<view class="flex-1 ml-3">
+					<cl-text>已选:黑色 128GB + 碎屏险</cl-text>
+				</view>
+
+				<cl-icon name="arrow-right-s-line"></cl-icon>
+			</view>
+
+			<view class="row is-border !items-start dark:!border-surface-600">
+				<cl-icon name="truck-line" :pt="{ className: '!mt-1' }"></cl-icon>
+
+				<view class="flex-1 ml-3">
+					<cl-text color="info">预计11月1日 周三 送达</cl-text>
+					<cl-text color="info" :pt="{ className: '!mt-1' }">深圳益田假日广场</cl-text>
+				</view>
+			</view>
+
+			<view class="row">
+				<cl-icon name="shield-check-line"></cl-icon>
+
+				<view class="flex-1 ml-3">
+					<cl-text color="info">7天无理由退货 · 正品保证 · 极速发货 · 无忧售后</cl-text>
+				</view>
+			</view>
+		</view>
+
+		<view class="card dark:!bg-surface-700" id="comment">
+			<Comment />
+		</view>
+
+		<view class="card !p-0 dark:!bg-surface-700" id="desc">
+			<Desc />
+		</view>
+
+		<cl-footer>
+			<view class="flex flex-row overflow-visible">
+				<view class="flex flex-row mr-auto overflow-visible">
+					<view class="flex justify-center items-center px-4">
+						<cl-icon name="heart-line" :size="42"></cl-icon>
+						<cl-text :pt="{ className: 'text-xs mt-1' }">收藏</cl-text>
+					</view>
+
+					<view
+						class="flex justify-center items-center px-4 overflow-visible"
+						@tap="router.to('/pages/template/shop/shopping-cart')"
+					>
+						<cl-icon name="shopping-cart-line" :size="42"></cl-icon>
+						<cl-text :pt="{ className: 'text-xs mt-1' }">购物车</cl-text>
+
+						<cl-badge
+							type="error"
+							:value="3"
+							position
+							:pt="{ className: '!right-[24rpx] !top-[-10rpx] !scale-80' }"
+						></cl-badge>
+					</view>
+				</view>
+
+				<cl-button text border :pt="{ className: '!w-[220rpx]' }">加入购物车</cl-button>
+				<cl-button :pt="{ className: '!w-[220rpx]' }">立即购买</cl-button>
+			</view>
+		</cl-footer>
+	</cl-page>
+</template>
+
+<script setup lang="ts">
+import { router } from "@/cool";
+import { ref } from "vue";
+import Comment from "./comment.uvue";
+import Info from "./info.uvue";
+import Desc from "./desc.uvue";
+import Topbar from "./topbar.uvue";
+
+const bannerList = ref<string[]>([
+	"https://unix.cool-js.com/images/demo/goods/banner-1.jpg",
+	"https://unix.cool-js.com/images/demo/goods/banner-2.jpg",
+	"https://unix.cool-js.com/images/demo/goods/banner-3.jpg",
+	"https://unix.cool-js.com/images/demo/goods/banner-4.jpg"
+]);
+</script>
+
+<style lang="scss" scoped>
+.card {
+	@apply p-4 bg-white mb-3;
+
+	.row {
+		@apply flex flex-row items-center py-4;
+
+		&.is-border {
+			@apply border-b border-t-0 border-l-0 border-r-0 border-solid border-surface-100;
+		}
+	}
+}
+</style>

+ 35 - 0
pages/template/shop/goods-detail/info.uvue

@@ -0,0 +1,35 @@
+<template>
+	<view class="flex">
+		<cl-text :pt="{ className: 'text-lg font-bold mb-3' }"
+			>Apple/苹果 iPhone 15 (A3092) 128GB 黑色 支持移动联通电信5G 双卡双待手机</cl-text
+		>
+
+		<view class="flex flex-row items-center mb-3 overflow-visible">
+			<view class="flex flex-row items-end overflow-visible">
+				<cl-text color="error" :pt="{ className: 'font-bold text-sm' }">¥</cl-text>
+				<cl-text
+					type="amount"
+					:value="8499"
+					color="error"
+					:size="44"
+					currency=""
+					:pt="{
+						className: 'font-bold ml-1'
+					}"
+				>
+				</cl-text>
+			</view>
+
+			<cl-text color="info" :pt="{ className: 'text-sm ml-auto' }" rounded
+				>已售 100 件</cl-text
+			>
+		</view>
+
+		<view class="flex flex-row mb-3">
+			<cl-tag type="error" plain>满199减50</cl-tag>
+			<cl-tag type="error" plain>满299减100</cl-tag>
+		</view>
+	</view>
+</template>
+
+<script setup lang="ts"></script>

+ 124 - 0
pages/template/shop/goods-detail/topbar.uvue

@@ -0,0 +1,124 @@
+<template>
+	<cl-sticky>
+		<cl-topbar fixed safe-area-top>
+			<cl-tabs
+				:list="list"
+				v-model="active"
+				height="36px"
+				:gutter="20"
+				@change="onTabChange"
+			></cl-tabs>
+
+			<template #append>
+				<view class="h-[44px] w-[30px] flex items-center justify-center mr-1">
+					<cl-icon name="search-line"></cl-icon>
+				</view>
+			</template>
+		</cl-topbar>
+	</cl-sticky>
+</template>
+
+<script setup lang="ts">
+import { debounce, getSafeAreaHeight, isNull } from "@/cool";
+import { usePage, type ClTabsItem } from "@/uni_modules/cool-ui";
+import { getCurrentInstance, onMounted, onUnmounted, ref } from "vue";
+
+const { proxy } = getCurrentInstance()!;
+const page = usePage();
+
+// 当前激活tab
+const active = ref("info");
+
+// 滚动时激活tab
+const scrollActive = ref("");
+
+// 卡片距离顶部偏移量
+const tops = ref<number[]>([]);
+
+// tab项列表
+const list = ref<ClTabsItem[]>([
+	{ label: "商品", value: "info" },
+	{ label: "评价", value: "comment" },
+	{ label: "详情", value: "desc" }
+]);
+
+/**
+ * 获取所有.card顶部坐标
+ */
+async function getTops(): Promise<void> {
+	return new Promise((resolve) => {
+		uni.createSelectorQuery()
+			.in(proxy?.$root)
+			.selectAll(".card")
+			.boundingClientRect((res) => {
+				const top = page.getScrollTop() - 44 - getSafeAreaHeight("top"); // 去头部高度
+
+				// 只计算有id的card
+				tops.value = (res as NodeInfo[])
+					.filter((e) => e.id != "" && !isNull(e.id))
+					.map((e) => (e.top ?? 0) + top);
+
+				resolve();
+			})
+			.exec();
+	});
+}
+
+/**
+ * tab切换
+ */
+async function onTabChange(value: string) {
+	// 设置滚动时激活tab
+	scrollActive.value = value;
+
+	// 重新获取卡片位置
+	await getTops();
+
+	// 查找符合当前位置的tab索引
+	const index = list.value.findIndex((e) => e.value == value);
+	if (index < 0) return;
+
+	// 滚动到对应卡片位置
+	page.scrollTo(tops.value[index] + 1);
+}
+
+/**
+ * 同步当前tab
+ */
+const setActive = debounce(() => {
+	active.value = scrollActive.value;
+}, 100);
+
+/**
+ * 滚动时激活tab
+ */
+function onScroll(top: number) {
+	let index = -1;
+
+	// 查找符合当前位置的tab索引
+	for (let i = 0; i < tops.value.length; i++) {
+		if (top >= tops.value[i]) {
+			index = i;
+		}
+	}
+
+	// 设置激活tab
+	if (index >= 0 && index < list.value.length) {
+		scrollActive.value = list.value[index].value as string;
+		setActive();
+	}
+}
+
+onMounted(() => {
+	// 获取卡片位置
+	getTops();
+
+	// 监听页面滚动
+	page.onScroll(onScroll);
+});
+
+onUnmounted(() => {
+	// 移除监听
+	page.offScroll(onScroll);
+});
+</script>

+ 1 - 0
uni_modules/cool-ui/components/cl-banner/props.ts

@@ -21,4 +21,5 @@ export type ClBannerProps = {
 	showDots?: boolean;
 	disableTouch?: boolean;
 	height?: any;
+	imageMode?: string;
 };