ソースを参照

添加购物车模板

icssoa 7 ヶ月 前
コミット
96d44d1687
7 ファイル変更425 行追加2 行削除
  1. 0 0
      locale/en.ts
  2. 0 0
      locale/es.ts
  3. 0 0
      locale/zh-cn.ts
  4. 1 1
      package.json
  5. 6 0
      pages.json
  6. 2 1
      pages/index/template.uvue
  7. 416 0
      pages/template/shop/shopping-cart.uvue

ファイルの差分が大きいため隠しています
+ 0 - 0
locale/en.ts


ファイルの差分が大きいため隠しています
+ 0 - 0
locale/es.ts


ファイルの差分が大きいため隠しています
+ 0 - 0
locale/zh-cn.ts


+ 1 - 1
package.json

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

+ 6 - 0
pages.json

@@ -428,6 +428,12 @@
 					"style": {
 						"navigationBarTitleText": "%商品分类%"
 					}
+				},
+				{
+					"path": "shop/shopping-cart",
+					"style": {
+						"navigationStyle": "custom"
+					}
 				}
 			]
 		}

+ 2 - 1
pages/index/template.uvue

@@ -55,7 +55,8 @@ const list = computed<Item[]>(() => [
 				label: t("商品列表、筛选")
 			},
 			{
-				label: t("购物车")
+				label: t("购物车"),
+				path: "/pages/template/shop/shopping-cart"
 			},
 			{
 				label: t("订单列表、详情")

+ 416 - 0
pages/template/shop/shopping-cart.uvue

@@ -0,0 +1,416 @@
+<template>
+	<cl-page>
+		<cl-sticky>
+			<cl-topbar fixed safe-area-top :title="`${$t('购物车 ({num})', { num: list.length })}`">
+				<template #prepend>
+					<!-- #ifdef MP -->
+					<cl-text
+						:pt="{
+							className: 'ml-1'
+						}"
+						@tap="isDel = !isDel"
+					>
+						{{ isDel ? t("完成") : t("管理") }}
+					</cl-text>
+					<!-- #endif -->
+				</template>
+
+				<template #append>
+					<!-- #ifndef MP -->
+					<cl-text
+						:pt="{
+							className: 'mr-3'
+						}"
+						@tap="isDel = !isDel"
+					>
+						{{ isDel ? t("完成") : t("管理") }}
+					</cl-text>
+					<!-- #endif -->
+				</template>
+			</cl-topbar>
+		</cl-sticky>
+
+		<view class="p-3">
+			<view class="p-3 rounded-xl bg-white dark:!bg-surface-800 mb-3">
+				<cl-text
+					:pt="{
+						className: '!text-sm'
+					}"
+					>🔥 最新降价商品,限时优惠,抓紧抢购!</cl-text
+				>
+			</view>
+
+			<cl-list-item
+				v-for="(item, index) in list"
+				:key="item.id"
+				:pt="{
+					className: parseClass([
+						'rounded-2xl ',
+						[index == list.length - 1, 'mb-0', 'mb-3']
+					]),
+					inner: {
+						className: '!p-4'
+					}
+				}"
+				swipeable
+			>
+				<view class="flex flex-row flex-1">
+					<view class="flex flex-col mr-4 pt-[55rpx]" @tap="selectItem(item)">
+						<cl-icon
+							name="checkbox-circle-line"
+							color="primary"
+							:size="40"
+							v-if="item.checked"
+						></cl-icon>
+						<cl-icon name="checkbox-blank-circle-line" :size="40" v-else></cl-icon>
+					</view>
+
+					<cl-image :width="150" :height="150" :src="item.cover"></cl-image>
+
+					<view class="flex flex-col ml-3 flex-1">
+						<cl-text
+							:pt="{
+								className: 'mb-2 font-bold'
+							}"
+							>{{ item.name }}</cl-text
+						>
+
+						<view class="flex flex-row mb-2">
+							<view
+								class="bg-surface-100 dark:!bg-surface-700 rounded-md py-1 px-2 flex flex-row items-center"
+							>
+								<cl-text
+									:pt="{
+										className: '!text-xs'
+									}"
+									>{{ item.skuName }}</cl-text
+								>
+								<cl-icon name="arrow-down-s-line"></cl-icon>
+							</view>
+						</view>
+
+						<view class="flex flex-row items-center mb-2">
+							<cl-text
+								:pt="{
+									className: '!text-[22rpx] !text-red-500 mr-[1rpx]'
+								}"
+								>¥</cl-text
+							>
+							<cl-text
+								:pt="{
+									className: '!text-red-500 !text-[32rpx] mr-auto'
+								}"
+								>{{ item.price }}</cl-text
+							>
+
+							<view
+								v-if="isDel"
+								class="p-[8rpx] bg-red-500 rounded-lg"
+								@tap="delItem(index)"
+							>
+								<cl-icon name="delete-bin-line" color="white" :size="24"></cl-icon>
+							</view>
+
+							<view class="flex" v-else>
+								<cl-input-number
+									v-model="item.count"
+									:size="40"
+									:min="1"
+									:pt="{
+										op: {
+											plus: {
+												className: '!rounded-full'
+											},
+											minus: {
+												className: '!rounded-full'
+											},
+											icon: {
+												size: 28
+											}
+										},
+										value: {
+											className: '!w-[60rpx] rounded-full !px-0',
+											input: {
+												className: '!text-[24rpx]'
+											}
+										}
+									}"
+								></cl-input-number>
+							</view>
+						</view>
+
+						<cl-text :size="22" color="error">比加入时降¥100</cl-text>
+
+						<cl-text :size="22">满1件可换购0.5元商品</cl-text>
+					</view>
+				</view>
+
+				<template #swipe-right>
+					<cl-button
+						type="error"
+						:pt="{
+							className: '!rounded-none h-full w-[160rpx]'
+						}"
+						@tap="delItem(index)"
+						>{{ t("删除") }}</cl-button
+					>
+				</template>
+			</cl-list-item>
+
+			<cl-empty v-if="list.length == 0"></cl-empty>
+		</view>
+
+		<cl-footer>
+			<view class="flex flex-row items-center h-[70rpx]">
+				<cl-checkbox
+					active-icon="checkbox-circle-line"
+					inactive-icon="checkbox-blank-circle-line"
+					:pt="{
+						className: 'mr-auto'
+					}"
+					:size="28"
+					v-model="selectAll"
+					@change="onSelectAllChange"
+					>{{ t("全选") }}</cl-checkbox
+				>
+
+				<template v-if="isDel">
+					<cl-button
+						type="error"
+						:pt="{
+							className: '!px-5'
+						}"
+						@tap="delAll"
+					>
+						{{ t("删除") }}
+					</cl-button>
+				</template>
+
+				<template v-else>
+					<view class="flex flex-col mr-3 items-end pt-1">
+						<cl-text
+							color="info"
+							:pt="{
+								className: '!text-xs'
+							}"
+							>{{ t("合计") }}</cl-text
+						>
+						<cl-text color="error" :value="totalPrice" type="amount"></cl-text>
+					</view>
+
+					<cl-button
+						type="error"
+						:pt="{
+							className: '!px-8'
+						}"
+						@tap="toSettle"
+					>
+						{{ t("去结算") }}
+					</cl-button>
+				</template>
+			</view>
+		</cl-footer>
+	</cl-page>
+</template>
+
+<script lang="ts" setup>
+import { parseClass, isEmpty } from "@/cool";
+import { $t, t } from "@/locale";
+import { useUi } from "@/uni_modules/cool-ui";
+import { computed, ref } from "vue";
+
+const ui = useUi();
+
+// 商品类型
+type Goods = {
+	id: number;
+	name: string;
+	count: number;
+	price: number;
+	cover: string;
+	skuName: string;
+	checked: boolean;
+};
+
+// 商品列表
+const list = ref<Goods[]>([
+	{
+		id: 1,
+		name: "Apple iPad",
+		count: 1,
+		price: 450,
+		cover: "https://img.yzcdn.cn/vant/ipad.png",
+		skuName: "深空灰色 128GB WLAN版",
+		checked: true
+	},
+	{
+		id: 2,
+		name: "Samsung Galaxy S24",
+		count: 2,
+		price: 699,
+		cover: "https://img.yzcdn.cn/vant/samsung.png",
+		skuName: "曜石黑 12GB+256GB 官方标配",
+		checked: false
+	},
+	{
+		id: 3,
+		name: "Sony WH-1000XM5",
+		count: 1,
+		price: 299,
+		cover: "https://img.yzcdn.cn/vant/sony.png",
+		skuName: "黑色 无线蓝牙 官方标配",
+		checked: false
+	},
+	{
+		id: 4,
+		name: "小米手环8",
+		count: 3,
+		price: 49,
+		cover: "https://img.yzcdn.cn/vant/xiaomi.png",
+		skuName: "曜石黑 标准版 硅胶表带",
+		checked: false
+	},
+	{
+		id: 5,
+		name: "Kindle Paperwhite",
+		count: 1,
+		price: 120,
+		cover: "https://img.yzcdn.cn/vant/kindle.png",
+		skuName: "黑色 8GB 官方标配",
+		checked: false
+	}
+]);
+
+// 是否全选
+const selectAll = ref(false);
+
+/**
+ * 选择/取消选择单个商品
+ * @param item 需要操作的商品
+ */
+function selectItem(item: Goods) {
+	// 切换选中状态
+	item.checked = !item.checked;
+	// 判断是否所有商品都被选中,更新全选状态
+	selectAll.value = list.value.every((item) => item.checked);
+}
+
+/**
+ * 全选/取消全选
+ * @param val 是否全选
+ */
+function onSelectAllChange(val: boolean) {
+	list.value.forEach((item, index, arr) => {
+		// item.checked = val; // 这样写,在 android 上无效
+		arr[index].checked = val;
+	});
+}
+
+// 是否处于删除模式
+const isDel = ref(false);
+
+/**
+ * 删除单个商品
+ * @param index 商品索引
+ */
+function delItem(index: number) {
+	ui.showConfirm({
+		title: t("温馨提示"),
+		message: t("确定删除该商品吗?"),
+		callback(action) {
+			if (action === "confirm") {
+				// 删除指定索引的商品
+				list.value.splice(index, 1);
+
+				ui.showToast({
+					message: t("删除成功")
+				});
+			}
+		}
+	});
+}
+
+/**
+ * 删除所有已选中的商品
+ */
+function delAll() {
+	const checked = list.value.filter((item) => item.checked);
+
+	// 如果没有选中商品,提示用户
+	if (isEmpty(checked)) {
+		return ui.showToast({
+			message: t("请先选择商品")
+		});
+	}
+
+	ui.showConfirm({
+		title: t("温馨提示"),
+		message: t("确定删除选中的商品吗?"),
+		callback(action) {
+			if (action == "confirm") {
+				// 只保留未选中的商品
+				list.value = list.value.filter((item) => !item.checked);
+
+				// 如果之前是全选,删除后取消全选状态
+				if (selectAll.value) {
+					selectAll.value = false;
+				}
+
+				ui.showToast({
+					message: t("删除成功")
+				});
+			}
+		}
+	});
+}
+
+/**
+ * 清空购物车
+ */
+function clear() {
+	list.value = [];
+	selectAll.value = false;
+	isDel.value = false;
+}
+
+/**
+ * 计算已选中商品的总价
+ */
+const totalPrice = computed(() => {
+	return list.value
+		.filter((item) => item.checked)
+		.reduce((acc, item) => acc + item.price * item.count, 0);
+});
+
+/**
+ * 结算操作
+ */
+function toSettle() {
+	// 如果没有选中商品,提示用户
+	if (totalPrice.value <= 0) {
+		return ui.showToast({
+			message: t("请先选择商品")
+		});
+	}
+
+	ui.showConfirm({
+		title: t("温馨提示"),
+		message: $t("您需支付 {price} 元,请确认支付", { price: totalPrice.value }),
+		beforeClose(action, { showLoading, close }) {
+			if (action == "confirm") {
+				showLoading();
+
+				setTimeout(() => {
+					ui.showToast({
+						message: t("支付成功")
+					});
+
+					close();
+				}, 1000);
+			} else {
+				close();
+			}
+		}
+	});
+}
+</script>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません