商店购物车页面

This commit is contained in:
ctw committed 2025-01-07 13:07:46 +08:00
1 parent 0088fe3509
commit 6f9d49f088
13 files changed
+478 -720

No files matched your search

+3 -3
View File
@@ -275,9 +275,9 @@
},
{
"pagePath": "pages/tabbar/shop",
"text": "商店",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-select.png"
"text": "超市",
"iconPath": "static/tabbar/shop.png",
"selectedIconPath": "static/tabbar/shop-select.png"
},
{
"pagePath": "pages/tabbar/order",
+1 -1
View File
@@ -17,7 +17,7 @@
<view class="h-20rpx" />
<view class="flex-col card p-x-20rpx p-y-30rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="v.id">
<view class="flex items-center justify-between second-text-26">
<img class="w-100rpx h-100rpx rounded-50rpx" :src="v.faceImg" />
<image class="w-100rpx h-100rpx rounded-50rpx" :src="v.faceImg" />
<text class="font-600">{{ v.username }}</text>
<text class="botder_btn" @click="doEdit(v.id)">修改</text>
<tm-modal title="确定删除吗?" height="auto" @confirm="doDel(v.id)">
-225
View File
@@ -1,225 +0,0 @@
<template>
<view class="uni-numbox">
<!-- <view @click="_calcValue('minus')" class="uni-numbox__minus uni-numbox-btns" :style="{ background }"> -->
<!-- <text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }"
:style="{ color }">
-
</text> -->
<text
class="cicon-move-round"
:class="{
'uni-numbox--disabled': inputValue <= min || disabled,
'groupon-btn': activity === 'groupon',
'seckill-btn': activity === 'seckill',
}"
@click="_calcValue('minus')"
></text>
<!-- </view> -->
<input
:disabled="disabled"
@focus="_onFocus"
@blur="_onBlur"
class="uni-numbox__value"
type="number"
v-model="inputValue"
:style="{ color }"
/>
<!-- <view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns">
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
</view> -->
<text
class="cicon-add-round"
:class="{
'uni-numbox--disabled': inputValue >= max || disabled,
'groupon-btn': activity === 'groupon',
'seckill-btn': activity === 'seckill',
}"
@click="_calcValue('plus')"
></text>
</view>
</template>
<script>
/**
* NumberBox 数字输入框
* @description 带加减按钮的数字输入框
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
* @property {Number} value 输入框当前值
* @property {Number} min 最小值
* @property {Number} max 最大值
* @property {Number} step 每次点击改变的间隔大小
* @property {String} background 背景色
* @property {String} color 字体颜色(前景色)
* @property {Boolean} disabled = [true|false] 是否为禁用状态
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
* @event {Function} focus 输入框聚焦时触发的事件,参数为 event 对象
* @event {Function} blur 输入框失焦时触发的事件,参数为 event 对象
*/
export default {
name: "UniNumberBox",
emits: ["change", "input", "update:modelValue", "blur", "focus"],
props: {
value: {
type: [Number, String],
default: 1,
},
modelValue: {
type: [Number, String],
default: 1,
},
min: {
type: Number,
default: 0,
},
max: {
type: Number,
default: 100,
},
step: {
type: Number,
default: 1,
},
background: {
type: String,
default: "#f5f5f5",
},
color: {
type: String,
default: "#333",
},
disabled: {
type: Boolean,
default: false,
},
activity: {
type: String,
default: "none",
},
},
data() {
return {
inputValue: 0,
};
},
watch: {
value(val) {
this.inputValue = +val;
},
modelValue(val) {
this.inputValue = +val;
},
},
created() {
if (this.value === 1) {
this.inputValue = +this.modelValue;
}
if (this.modelValue === 1) {
this.inputValue = +this.value;
}
},
methods: {
_calcValue(type) {
if (this.disabled) {
return;
}
const scale = this._getDecimalScale();
let value = this.inputValue * scale;
let step = this.step * scale;
if (type === "minus") {
value -= step;
if (value < this.min * scale) {
return;
}
if (value > this.max * scale) {
value = this.max * scale;
}
}
if (type === "plus") {
value += step;
if (value > this.max * scale) {
return;
}
if (value < this.min * scale) {
value = this.min * scale;
}
}
this.inputValue = (value / scale).toFixed(String(scale).length - 1);
this.$emit("change", +this.inputValue);
// TODO vue2 兼容
this.$emit("input", +this.inputValue);
// TODO vue3 兼容
this.$emit("update:modelValue", +this.inputValue);
},
_getDecimalScale() {
let scale = 1;
// 浮点型
if (~~this.step !== this.step) {
scale = Math.pow(10, String(this.step).split(".")[1].length);
}
return scale;
},
_onBlur(event) {
this.$emit("blur", event);
let value = event.detail.value;
if (!value) {
// this.inputValue = 0;
return;
}
value = +value;
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min;
}
const scale = this._getDecimalScale();
this.inputValue = value.toFixed(String(scale).length - 1);
this.$emit("change", +this.inputValue);
this.$emit("input", +this.inputValue);
},
_onFocus(event) {
this.$emit("focus", event);
},
},
};
</script>
<style lang="scss" scoped>
.uni-numbox .uni-numbox--disabled {
color: #c0c0c0 !important;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
.uni-numbox {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
}
.uni-numbox__value {
width: 74rpx;
text-align: center;
font-size: 30rpx;
}
.cicon-move-round {
font-size: 44rpx;
color: var(--ui-BG-Main);
}
.cicon-add-round {
font-size: 44rpx;
color: var(--ui-BG-Main);
}
.groupon-btn {
color: #ff6000;
}
.seckill-btn {
color: #ff5854;
}
</style>
+34 -54
View File
@@ -1,51 +1,3 @@
<script setup lang="ts">
import { ref, reactive, withDefaults, defineProps, defineEmits } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { hooks, router } from "@@/index";
import { merchantApi } from "@@/api";
import ProductRow from "./product-row.vue";
import ProductTotal from "./product-total.vue";
const emit = defineEmits(["update:modelValue", "submit", "doContainer", "add", "remove"]);
export type Props = {
modelValue: any;
carList: any;
totPrice: number;
totCount: number;
};
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
carList: [],
totPrice: 0,
totCount: 0,
});
const state = reactive<any>({});
onLoad(() => {});
onShow(() => {});
const doClose = () => {
emit("update:modelValue");
};
const doPay = () => {
emit("submit");
};
const doContainer = () => {
emit("doContainer");
};
const getHeight = () => {
const { bottom } = uni.getSystemInfoSync().safeAreaInsets!;
return `-${bottom + 16}px`;
};
</script>
<template>
<tm-drawer z-index="100" v-model:show="props.modelValue" showFooter title="购物车" position="bottom" size="60%" @close="doClose">
<product-row
@@ -63,16 +15,44 @@ const getHeight = () => {
@add="() => emit('add', v)"
@remove="emit('remove', v)"
/>
<template #footer>
<view class="car z-200 p-x-30rpx p-y-30rpx bg-white" :style="{ marginBottom: getHeight() }">
<product-total :totPrice="props.totPrice" :totCount="props.totCount" @submit="doPay" @doContainer="doContainer" />
<product-total :totPrice="props.totPrice" :totCount="props.totCount" @doPay="doPay" />
</view>
</template>
</tm-drawer>
</template>
<style lang="scss" scoped>
.car {
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
</style>
<script setup lang="ts">
import ProductRow from "./product-row.vue";
import ProductTotal from "./product-total.vue";
const emit = defineEmits(["update:modelValue", "doPay", "add", "remove"]);
export type Props = {
modelValue: any;
carList: any;
totPrice: number;
totCount: number;
};
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
carList: [],
totPrice: 0,
totCount: 0,
});
const doClose = () => {
emit("update:modelValue", false);
};
const doPay = () => {
emit("doPay");
};
const getHeight = () => {
const { bottom } = uni.getSystemInfoSync().safeAreaInsets!;
return `-${bottom + 16}px`;
};
</script>
+7 -13
View File
@@ -17,7 +17,7 @@
</view>
<view
class="second-text-24 w-200rpx py-1 border-1px border-solid border-#0094ff bg-#0094ff c-white! border-rd-8 text-center mt-1 float-right"
@tap.stop="handleClickSelectSku(props.productId)"
@tap.stop="handleClickAddToCar({ ...props })"
>
加入购物车
</view>
@@ -26,25 +26,24 @@
</template>
<script setup lang="ts">
import { onLoad, onShow } from "@dcloudio/uni-app";
import { hooks, router } from "@@/index";
const emit = defineEmits(["update:modelValue", "selectSku"]);
const emit = defineEmits(["onClickAddToCar"]);
export type Props = {
modelValue: number;
productId: number | null;
productCount: number;
pic: string;
name: string;
price: number;
marketPrice: number;
salesCount: number;
stock: number;
selectSku: () => void;
onClickAddToCar?: () => void;
};
const props = withDefaults(defineProps<Props>(), {
modelValue: 0,
productId: null,
productCount: 0,
pic: "",
name: "",
price: 0,
@@ -53,12 +52,7 @@ const props = withDefaults(defineProps<Props>(), {
stock: 0,
});
const handleClickSelectSku = (productId: number | null) => {
if (productId) {
emit("selectSku", productId);
}
const handleClickAddToCar = (productData: any) => {
emit("onClickAddToCar", productData);
};
onLoad(() => {});
onShow(() => {});
</script>
+186
View File
@@ -0,0 +1,186 @@
<template>
<tm-drawer z-index="100" v-model:show="props.modelValue" showFooter title="加入购物车" position="bottom" size="60%" @close="doClose">
<view class="px-20rpx">
<scroll-view scroll-y="true" class="max-h-7xl" @touchmove.stop>
<!-- 商品信息 -->
<view class="m-3 flex-row">
<image class="w-120rpx h-120rpx" :src="productData.pic" mode="aspectFill" />
<view class="w-100% ml-3 flex flex-col justify-between">
<view class="sub-text-30 font-600! ws-nowrap text-ellipsis overflow-hidden">{{ productData.name }}</view>
<view class="flex justify-between items-center">
<view class="c-red text-34">¥{{ productData.price }}</view>
<view class="sub-text-28">库存{{ productData.salesCount }}</view>
</view>
</view>
</view>
<!-- 规格 -->
<view v-for="property in propertyList" :key="property.id">
<view class="sub-text-32 font-600! my-2">{{ property.name }}</view>
<view class="flex flex-wrap">
<button
class="text-center bg-#F4F4F4 border-rd-8 c-black ml-0 mr-2 mb-2 border-none outline-none sub-text-34"
v-for="value in property.values"
:key="value.id"
:disabled="value.disabled === true"
:style="{ 'background-color': state.currentPropertyArray[property.id] === value.id ? '#0094ff' : '#F4F4F4' }"
@click="onSelectSku(property.id, value.id)"
>
{{ value.name }}
</button>
</view>
</view>
<!-- 数量 -->
<view class="sub-text-32 font-600! my-2">购买数量</view>
<view class="flex-row items-center justify-end m-t-20rpx">
<text
class="size-40rpx rounded-full flex justify-center items-center font-600! bg-#0094ff"
:style="{ backgroundColor: state.count <= 1 ? '#999999' : '#0094ff' }"
@click="handleNumRemove()"
>
-
</text>
<text class="sub-text-28 m-x-20rpx">{{ String(state.count).padStart(2, "0") }}</text>
<text
class="size-40rpx rounded-full flex justify-center items-center font-600! bg-#0094ff"
:style="{ backgroundColor: state.count >= productData.salesCount ? '#999999' : '#0094ff' }"
@click="handleNumAdd()"
>
+
</text>
</view>
</scroll-view>
</view>
<template #footer>
<button class="mx-10 border-rd-8 bg-#0094ff" @tap="handleClickAddToCar">加入购物车</button>
</template>
</tm-drawer>
</template>
<script setup lang="ts">
import { computed, reactive } from "vue";
const emit = defineEmits(["update:modelValue", "onAddToCar"]);
const propertyList = reactive([
{
id: "1",
name: "颜色",
values: [
{ id: "1", name: "红色", disabled: false },
{ id: "2", name: "蓝色", disabled: false },
],
},
{ id: "2", name: "版本", values: [{ id: "3", name: "黑色", disabled: false }] },
]);
export type Props = {
modelValue: boolean;
productData: any;
onAddToCar?: () => void;
};
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
productData: {},
});
const state = reactive<any>({
count: 1,
selectedSku: {}, // 选中的 SKU
currentPropertyArray: [], // 当前选中的属性,实际是个 Map。key 是 property 编号,value 是 value 编号
});
// SKU 列表
// const skuList = computed(() => {
// let skuPrices = props.goodsInfo.skus;
// for (let price of skuPrices) {
// price.value_id_array = price.properties.map((item) => item.valueId);
// }
// return skuPrices;
// });
// 当前所选属性下,获取所有有库存的 SKU 们
// function getCanUseSkuList() {
// let newSkus = [];
// for (let sku of skuList.value) {
// if (sku.stock <= 0) {
// continue;
// }
// let isOk = true;
// state.currentPropertyArray.forEach((propertyId) => {
// // propertyId 不为空,并且,这个 条 sku 没有被选中,则排除
// if (propertyId.toString() !== "" && sku.value_id_array.indexOf(propertyId) < 0) {
// isOk = false;
// }
// });
// if (isOk) {
// newSkus.push(sku);
// }
// }
// return newSkus;
// }
// 选择规格
function onSelectSku(propertyId: string | number, valueId: string | number) {
// 清空已选择
let isChecked = true; // 选中 or 取消选中
if (state.currentPropertyArray[propertyId] !== undefined && state.currentPropertyArray[propertyId] === valueId) {
// 点击已被选中的,删除并填充 ''
isChecked = false;
state.currentPropertyArray.splice(propertyId, 1, "");
} else {
// 选中
state.currentPropertyArray[propertyId] = valueId;
}
// 选中的 property 大类
let choosePropertyId: any = [];
state.currentPropertyArray.forEach((currentPropertyId: string) => {
if (currentPropertyId !== "") {
// currentPropertyId 为空是反选 填充的
choosePropertyId.push(currentPropertyId);
}
});
// 当前所选 property 下,所有可以选择的 SKU 们
// let newSkuList = getCanUseSkuList();
// 判断所有 property 大类是否选择完成
// if (choosePropertyId.length === propertyList.length && newSkuList.length) {
// newSkuList[0].name = state.selectedSku.goods_num || 1;
// state.selectedSku = newSkuList[0];
// } else {
// state.selectedSku = {};
// }
if (choosePropertyId.length === propertyList.length) {
state.selectedSku = state.currentPropertyArray;
}
// 改变 property 禁用状态
// changeDisabled(isChecked, propertyId, valueId);
}
const handleNumAdd = () => {
if (state.count >= props.productData.salesCount) {
return;
}
state.count += 1;
};
const handleNumRemove = () => {
if (state.count <= 1) {
return;
}
state.count -= 1;
};
// 点击加入购物车
const handleClickAddToCar = () => {
emit("onAddToCar", { productData: props.productData, state });
};
const doClose = () => {
emit("update:modelValue", false);
};
</script>
-127
View File
@@ -1,127 +0,0 @@
<script setup lang="ts">
import { ref, reactive, withDefaults, defineProps, defineEmits } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import ProductRow from "./product-row.vue";
import ProductTotal from "./product-total.vue";
import NumberBox from "./number-box.vue";
const emit = defineEmits(["update:modelValue", "submit", "doContainer", "add", "remove"]);
export type Props = {
modelValue: any;
carList: any;
totPrice: number;
totCount: number;
};
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
carList: [],
totPrice: 0,
totCount: 0,
});
const state = reactive<any>({
selectedSku: {
goods_num: 10,
stock: 0,
},
});
onLoad(() => {});
onShow(() => {});
const doClose = () => {
emit("update:modelValue");
};
const onNumberChange = (e: any) => {
console.log("11");
};
const onSelectSku = (propertyId, valueId) => {
console.log(propertyId, valueId);
};
const onAddCart = () => {
// emit("submit", state.selectedSku);
console.log(222);
};
</script>
<template>
<tm-drawer z-index="100" v-model:show="props.modelValue" showFooter title="加入购物车" position="bottom" size="60%" @close="doClose">
<!-- <product-row
v-for="(v, i) in props.carList"
mode="car"
:key="v.id"
v-model="v.productCount"
:pic="v.productPic"
:count="v.productCount"
:name="v.name"
:price="v.productPrice"
:marketPrice="v.marketPrice"
:salesCount="v.salesCount"
:stock="v.stock"
@add="() => emit('add', v)"
@remove="emit('remove', v)"
/> -->
<!-- 属性选择 -->
<view class="px-20rpx">
<scroll-view scroll-y="true" class="max-h-7xl" @touchmove.stop>
<view
v-for="property in [
{
id: '1',
name: '颜色',
values: [
{ id: '2', name: '22', disabled: false },
{ id: '2', name: '22', disabled: false },
],
},
{ id: '1', name: '版本', values: [{ id: '3', name: '33', disabled: false }] },
]"
:key="property.id"
>
<view>{{ property.name }}</view>
<view class="flex flex-wrap">
<button
class="lh-relaxed w-100rpx text-center bg-#999 border-rd-8 c-white ml-0 mr-2 mb-2"
v-for="value in property.values"
:key="value.id"
:disabled="value.disabled === true"
@tap="onSelectSku(property.id, value.id)"
>
{{ value.name }}
</button>
</view>
</view>
<view>购买数量</view>
<view class="flex-row items-center justify-end m-t-20rpx">
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff" @tap.stop="remove">
<view class="iconfont icon-a--" />
</view>
<view class="sub-text-28 m-x-20rpx">{{ props.modelValue }}</view>
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="add">
<view class="font-size-40rpx color-white iconfont icon-a-" />
</view>
</view>
<!-- <number-box
:min="1"
:max="state.selectedSku.stock"
:step="1"
v-model="state.selectedSku.goods_num"
@change="onNumberChange($event)"
/> -->
</scroll-view>
</view>
<template #footer>
<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart">加入购物车</button>
</template>
</tm-drawer>
</template>
<style lang="scss" scoped>
.car {
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
}
</style>
+33 -55
View File
@@ -1,59 +1,37 @@
<script setup lang="ts">
import { ref, reactive, withDefaults, defineProps, defineEmits } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { hooks, router } from "@@/index"
import { merchantApi } from "@@/api"
const emit = defineEmits(["submit", "doContainer"])
export type Props = {
totPrice: number,
totCount: number,
}
const props = withDefaults(defineProps<Props>(), {
totPrice: 0,
totCount: 0,
});
const state = reactive<any>({
})
onLoad(() => {
});
onShow(() => {
})
const doPay = () => {
emit("submit")
}
const doContainer = () => {
emit("doContainer")
}
</script>
<template>
<view class="flex-row items-center justify-between" @tap="doContainer">
<view class="flex-row-center">
<view class="flex-row-center size-80rpx rounded-full bg-#0094ff">
<view class="color-white! iconfont icon-biaoqianlanicon_gouwuche-xuanzhong" />
</view>
<view class="flex-col m-l-30rpx">
<view class="second-text-36 color-#f63a36!">¥ {{ hooks.useMoney(props.totPrice) }}</view>
<view class="sub-text-28">共{{ props.totCount }}件</view>
</view>
</view>
<view class="bg-#0094ff p-x-30rpx p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!"
@tap.stop="doPay">
购买
</view>
<view class="flex-row items-center justify-between" @tap="doContainer">
<view class="flex-row-center">
<view class="flex-row-center size-80rpx rounded-full bg-#0094ff">
<view class="color-white! iconfont icon-biaoqianlanicon_gouwuche-xuanzhong" />
</view>
<view class="flex-col m-l-30rpx">
<view class="second-text-36 color-#f63a36!">¥ {{ hooks.useMoney(props.totPrice) }}</view>
<view class="sub-text-28">共{{ props.totCount }}件</view>
</view>
</view>
<view class="bg-#0094ff p-x-30rpx p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!" @tap.stop="doPay">提交订单</view>
</view>
</template>
<style lang="scss" scoped></style>
<script setup lang="ts">
import { hooks } from "@@/index";
const emit = defineEmits(["doPay", "doContainer"]);
export type Props = {
totPrice: number;
totCount: number;
};
const props = withDefaults(defineProps<Props>(), {
totPrice: 0,
totCount: 0,
});
const doPay = () => {
emit("doPay");
};
const doContainer = () => {
emit("doContainer");
};
</script>
+1
View File
@@ -70,6 +70,7 @@
<!-- 操作区 -->
<view class="modal-footer border-top">
<view class="buy-box ss-flex ss-col-center ss-flex ss-col-center ss-row-center">
<button class="ss-reset-button add-btn ui-Shadow-Main" @tap="onAddCart">加入购物车</button>
<button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="onBuy">立即购买</button>
</view>
</view>
+213 -241
View File
@@ -1,204 +1,3 @@
<script setup lang="ts">
import { ref, reactive, nextTick } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { isEmpty } from "radash";
import { hooks, router } from "@@/index";
import { merchantApi, foodApi, payApi } from "@@/api";
import zSwiper from "@zebra-ui/swiper/components/z-swiper/z-swiper.vue";
import zSwiperItem from "@zebra-ui/swiper/components/z-swiper-item/z-swiper-item.vue";
import ProductRow from "./components/product-row.vue";
import ProductCar from "./components/product-car.vue";
import ProductTotal from "./components/product-total.vue";
import ProductSpec from "./components/product-spec.vue";
const state = reactive<any>({
detailId: null,
systemType: 3,
totPrice: 0,
totCount: 0,
currentClassifyIndex: 0,
showCarDialog: false,
showSpecDialog: false,
detail: {},
carList: [],
classifyList: [],
shopList: [],
});
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id;
}
});
onShow(() => {
getDetail();
getShopCar();
getClassify();
});
const getDetail = async () => {
const _pa = { id: state.detailId };
const { data } = await merchantApi.detail(_pa);
state.detail = data;
};
const getShopCar = async () => {
const _pa = { merchantId: state.detailId };
const { data } = await foodApi.getShopCar(_pa);
state.carList = data ?? [];
state.totPrice = state.carList.map((v) => v.totalPrice).reduce((pre, next) => pre + next, 0);
state.totCount = state.carList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0);
};
const getClassify = async () => {
const _pa = { systemType: state.systemType, merchantId: state.detailId };
const { data } = await foodApi.classify(_pa);
state.classifyList = data;
if (!isEmpty(state.classifyList)) {
getPorduct();
}
};
const updateShopCar = async () => {
if (isEmpty(state.carList)) return;
const list = state.carList
.filter((v) => v.productCount && v.productCount > 0)
.map((v) => {
return {
productCount: v.productCount,
productId: v.productId,
};
});
if (isEmpty(list)) return;
const _pa = { merchantId: state.detailId, list: list };
const { data, code }: any = await foodApi.updateShopCar(_pa);
if (code == 0) await getShopCar();
};
const getPorduct = async () => {
const target = state.classifyList[state.currentClassifyIndex];
if (isEmpty(target)) return;
const _pa = {
systemType: state.systemType,
merchantId: state.detailId,
categoryId: target.id,
};
const { data } = await foodApi.product(_pa);
state.shopList = data;
state.shopList.forEach((i) => {
if (i && i.foodSPURespVOList && i.foodSPURespVOList.length) {
i.foodSPURespVOList.forEach((j) => {
j.productCount = 0;
j.productId = j.id;
});
}
});
const shopList = [...state.shopList];
if (state.carList && state.carList.length) {
for (const i of shopList) {
if (i && i.foodSPURespVOList && i.foodSPURespVOList.length) {
for (const j of i.foodSPURespVOList) {
for (const v of state.carList) {
if (j.id == v.productId) {
j.productCount = v.productCount;
}
}
}
}
}
}
state.shopList = shopList;
};
const doAdd = async (item: any) => {
chentProductCount(item);
};
const remove = async (item: any) => {
chentProductCount(item);
};
const chentProductCount = async (item: any) => {
const target = state.carList.find((v) => v.productId == item.productId);
const targetIndex = state.carList.findIndex((v) => v.productId == item.productId);
if (target) {
state.carList[targetIndex].productCount = item.productCount;
} else {
state.carList.push({ ...item, productId: item.id });
}
nextTick(() => updateShopCar());
};
const doClassify = (i: any) => {
state.currentClassifyIndex = i;
getClassify();
};
const doVisibleCar = async () => {
state.showCarDialog = true;
};
const submitPay = async () => {
try {
if (isEmpty(state.carList)) {
uni.showToast({ title: "请至少购买一件商品", icon: "none" });
return;
}
uni.showLoading({ title: "支付中...", mask: false });
const _createtPa = { carParentId: state.carList[0].parentCarId };
const { data: createData, code: createCode }: any = await payApi.createHardwareOrder(_createtPa);
if (createCode != 0) throw new Error();
const _submitPa = { orderId: createData };
const { data: submitData, code: submitCode }: any = await payApi.submitHardwareOrder(_submitPa);
if (submitCode != 0) throw new Error();
uni.hideLoading();
uni.showToast({ title: "支付成功", icon: "none" });
router.jump("/pages/tabbar/order");
} catch (error) {
console.error(error);
uni.hideLoading();
}
};
const doPay = async () => {
uni.showModal({
title: "提示",
content: "是否确定购买",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return;
submitPay();
},
});
};
const getHeight = () => {
const rpx = 480;
return `calc(100vh - ${uni.upx2px(rpx)}px)`;
};
const doPhone = () => {
if (isEmpty(state.detail.phone)) {
uni.showToast({ title: "该店铺没有预留电话", icon: "none" });
return;
}
hooks.useMakePhoneCall(state.detail.phone);
};
const doFood = () => {
router.jump("/pages/merchant/food", { merchantId: state.detailId });
};
const selectSku = (productId: number | null) => {
state.showSpecDialog = true;
console.log(productId);
};
</script>
<template>
<crud-layout mainIsScroll tabBarType="just-flot-back" bg="/static/page/blue-bg.png">
<tm-sticky>
@@ -212,8 +11,8 @@ const selectSku = (productId: number | null) => {
<tm-sticky-header>
<view class="m-x-30rpx">
<!-- start 简介 start -->
<view class="h-15rpx" />
<!-- start 简介 start -->
<view flex-row card p-20rpx>
<image class="w-160rpx h-160rpx rounded-12rpx" :src="state.detail.logo" mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
@@ -245,32 +44,7 @@ const selectSku = (productId: number | null) => {
</view>
</view>
<!-- end 简介 end -->
<!-- start 功能 start -->
<view class="h-15rpx" />
<!-- <view class="flex-row items-center justify-evenly card p-y-20rpx">
<view class="flex-row-center" @tap="doPhone">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-dianhua" />
</view>
<text class="m-l-20rpx sub-text-30">联系食堂</text>
</view>
<view class="w-1px h-30rpx bg-#c4c4c4" />
<view class="flex-row-center" @tap.stop="router.jump('/pages/boxes/list', { id: state.detailId })">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-yuding" />
</view>
<text class="m-l-20rpx sub-text-28">包厢预定</text>
</view>
<view class="w-1px h-30rpx bg-#c4c4c4" />
<view class="flex-row-center" @tap="doFood">
<view class="flex-row-center w-50rpx h-50rpx bg-#167aff rounded-full">
<text class="font-size-28rpx! color-white iconfont icon-caipu" />
</view>
<text class="m-l-20rpx sub-text-28">每日菜谱</text>
</view>
</view> -->
<!-- end 功能 end -->
</view>
</tm-sticky-header>
@@ -305,15 +79,15 @@ const selectSku = (productId: number | null) => {
<product-row
v-for="pV in cV.foodSPURespVOList"
:key="pV.id"
v-model="pV.productCount"
:productId="pV.productId"
:productCount="pV.productCount"
:pic="pV.picUrl"
:name="pV.name"
:price="pV.price"
:marketPrice="pV.marketPrice"
:salesCount="pV.salesCount"
:stock="pV.stock"
@select-sku="selectSku"
@onClickAddToCar="onClickAddToCar"
/>
</view>
</view>
@@ -324,32 +98,230 @@ const selectSku = (productId: number | null) => {
<template #main-bottom>
<view class="car z-200 p-x-30rpx p-y-30rpx bg-white">
<product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCar" @submit="doPay" />
<product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCar" @doPay="doPay" />
</view>
</template>
</crud-layout>
<product-car
v-model="state.showCarDialog"
v-model="showCarDialog"
:carList="state.carList"
:totPrice="state.totPrice"
:totCount="state.totCount"
@add="doAdd"
@remove="remove"
@submit="doPay"
@doPay="doPay"
/>
<!-- 规格与数量弹框 -->
<product-spec
v-model="state.showSpecDialog"
:carList="state.carList"
:totPrice="state.totPrice"
:totCount="state.totCount"
@add="doAdd"
@remove="remove"
@submit="doPay"
/>
<product-sku v-model="showSkuDialog" :productData="curProductData" @onAddToCar="onAddToCar" />
</template>
<script setup lang="ts">
import { reactive, nextTick, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { isEmpty } from "radash";
import { hooks, router } from "@@/index";
import { merchantApi, foodApi, payApi } from "@@/api";
import zSwiper from "@zebra-ui/swiper/components/z-swiper/z-swiper.vue";
import zSwiperItem from "@zebra-ui/swiper/components/z-swiper-item/z-swiper-item.vue";
import ProductRow from "./components/product-row.vue";
import ProductCar from "./components/product-car.vue";
import ProductTotal from "./components/product-total.vue";
import ProductSku from "./components/product-sku.vue";
// 商品详情相关数据
const state = reactive<any>({
detailId: null,
detail: {},
carList: [],
totPrice: 0,
totCount: 0,
systemType: 3,
classifyList: [],
shopList: [],
currentClassifyIndex: 0,
});
// 当前点击加入购物车的商品信息
const curProductData = ref<any>({});
// sku 弹框开关
const showSkuDialog = ref<boolean>(false);
// 购物车弹框开关
const showCarDialog = ref<boolean>(false);
onLoad((options: any) => {
if (!isEmpty(options.id)) {
state.detailId = options.id;
}
});
onShow(() => {
getDetail();
getShopCar();
getClassify();
});
const getDetail = async () => {
const { data } = await merchantApi.detail({ id: state.detailId });
state.detail = data;
};
const getShopCar = async () => {
const { data } = await foodApi.getShopCar({ merchantId: state.detailId });
state.carList = data ?? [];
state.totPrice = state.carList.map((v) => v.totalPrice).reduce((pre, next) => pre + next, 0);
state.totCount = state.carList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0);
};
const getClassify = async () => {
const { data } = await foodApi.classify({ systemType: state.systemType, merchantId: state.detailId });
state.classifyList = data;
if (!isEmpty(state.classifyList)) {
getPorduct();
}
};
const updateShopCar = async () => {
if (isEmpty(state.carList)) return;
const list = state.carList
.filter((v) => v.productCount && v.productCount > 0)
.map((v) => {
return {
productCount: v.productCount,
productId: v.productId,
};
});
if (isEmpty(list)) return;
const { data, code }: any = await foodApi.updateShopCar({ merchantId: state.detailId, list: list });
if (code == 0) await getShopCar();
};
const getPorduct = async () => {
const target = state.classifyList[state.currentClassifyIndex];
if (isEmpty(target)) return;
const { data } = await foodApi.product({
systemType: state.systemType,
merchantId: state.detailId,
categoryId: target.id,
});
state.shopList = data;
state.shopList.forEach((i) => {
if (i && i.foodSPURespVOList && i.foodSPURespVOList.length) {
i.foodSPURespVOList.forEach((j) => {
j.productCount = 0;
j.productId = j.id;
});
}
});
const shopList = [...state.shopList];
if (state.carList && state.carList.length) {
for (const i of shopList) {
if (i && i.foodSPURespVOList && i.foodSPURespVOList.length) {
for (const j of i.foodSPURespVOList) {
for (const v of state.carList) {
if (j.id == v.productId) {
j.productCount = v.productCount;
}
}
}
}
}
}
state.shopList = shopList;
};
const doClassify = (i: any) => {
state.currentClassifyIndex = i;
getClassify();
};
const getHeight = () => {
const rpx = 480;
return `calc(100vh - ${uni.upx2px(rpx)}px)`;
};
// 点击添加到购物车
const onClickAddToCar = (productData: any) => {
showSkuDialog.value = true;
curProductData.value = productData;
};
// 加入到购物车时
const onAddToCar = (cartGoodsInfo: any) => {
showSkuDialog.value = false;
console.log(cartGoodsInfo);
};
// 显示购物车
const doVisibleCar = async () => {
showCarDialog.value = true;
};
// 点击提交订单
const doPay = async () => {
uni.showModal({
title: "提示",
content: "是否确定购买",
confirmColor: "#0094ff",
success: async (res) => {
if (!res.confirm) return;
submitPay(); // 提交订单
},
});
};
// 提交订单
const submitPay = async () => {
// try {
// if (isEmpty(state.carList)) {
// uni.showToast({ title: "请至少购买一件商品", icon: "none" });
// return;
// }
// uni.showLoading({ title: "支付中...", mask: false });
// const _createtPa = { carParentId: state.carList[0].parentCarId };
// const { data: createData, code: createCode }: any = await payApi.createHardwareOrder(_createtPa);
// if (createCode != 0) throw new Error();
// const _submitPa = { orderId: createData };
// const { data: submitData, code: submitCode }: any = await payApi.submitHardwareOrder(_submitPa);
// if (submitCode != 0) throw new Error();
// uni.hideLoading();
// uni.showToast({ title: "支付成功", icon: "none" });
// router.jump("/pages/tabbar/order");
// } catch (error) {
// console.error(error);
// uni.hideLoading();
// }
};
// ======================================================
const skuState = reactive<any>({
productId: null,
showSpecDialog: false,
skuData: [],
totCount: 0,
});
const chentProductCount = async (item: any) => {
const target = state.carList.find((v) => v.productId == item.productId);
const targetIndex = state.carList.findIndex((v) => v.productId == item.productId);
if (target) {
state.carList[targetIndex].productCount = item.productCount;
} else {
state.carList.push({ ...item, productId: item.id });
}
nextTick(() => updateShopCar());
};
const doPhone = () => {
if (isEmpty(state.detail.phone)) {
uni.showToast({ title: "该店铺没有预留电话", icon: "none" });
return;
}
hooks.useMakePhoneCall(state.detail.phone);
};
const doFood = () => {
router.jump("/pages/merchant/food", { merchantId: state.detailId });
};
</script>
<style lang="scss" scoped>
:deep() {
.layout_main > scroll-view {
-1
View File
@@ -62,7 +62,6 @@ const getDetail = async () => {
<view class="m-t-15rpx" v-html="state.detail.description"></view>
</view>
</view>
<view class="w-500rpx h-60rpx bg-#0094ff flex justify-center border-rd-8">加入购物车</view>
</crud-layout>
</template>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB