422 lines
12 KiB
Vue
422 lines
12 KiB
Vue
<template>
|
|
<crud-layout mainIsScroll tabBarType="just-flot-back" :bg="filePath(SERVICE_IMAGE.blueBg)">
|
|
<tm-sticky>
|
|
<swiper class="w-100vw h-30vh" circular :indicator-dots="true" indicator-active-color="#0779FD"
|
|
indicator-color="#ffffff" :autoplay="true">
|
|
<swiper-item v-for="(v, i) in state.detail?.swiperPic?.split(',')" :key="i">
|
|
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
|
|
</swiper-item>
|
|
</swiper>
|
|
|
|
<tm-sticky-header>
|
|
<view class="m-x-30rpx">
|
|
<view class="h-15rpx" />
|
|
<!-- start 简介 start -->
|
|
<view class="flex-row card p-20rpx m-b-20rpx">
|
|
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(state.detail.logoPic)"
|
|
mode="aspectFill" />
|
|
<view class="flex-1 flex-col m-l-20rpx">
|
|
<view class="sub-text-34">{{ state.detail.name }}</view>
|
|
<view class="m-t-10rpx">
|
|
<view class="flex justify-between items-center">
|
|
<view class="flex-1">
|
|
<view v-if="state.detail.phone"
|
|
v-for="(item, index) in state.detail.phone.split('/')" :key="index"
|
|
class="flex items-center">
|
|
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
|
|
<view class="second-text-28 ml-8rpx">{{ item.trim() }}</view>
|
|
<view
|
|
class="second-text-20 bg-#f8f8f8 ml-8rpx p-2.4rpx flex items-center rounded-1 border border-solid border-b-coolGray"
|
|
@tap="doPhone(item)">
|
|
联系商家
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<tm-tag class="max-w-160rpx" size="xs">
|
|
{{ state.detail.deptName?.length > 5 ? state.detail.deptName.substring(0, 5) +
|
|
"..." : state.detail.deptName }}
|
|
</tm-tag>
|
|
</view>
|
|
<view class="second-text-24">
|
|
<view class="second-text-28">{{ state.detail.jobTime == ",," ? "" : "营业时间:" }}
|
|
</view>
|
|
<view>{{ state.detail.jobTime?.split(",")[0] ? "早:" +
|
|
state.detail.jobTime?.split(",")[0] : "" }}</view>
|
|
<view>{{ state.detail.jobTime?.split(",")[1] ? "中:" +
|
|
state.detail.jobTime?.split(",")[1] : "" }}</view>
|
|
<view>{{ state.detail.jobTime?.split(",")[2] ? "晚:" +
|
|
state.detail.jobTime?.split(",")[2] : "" }}</view>
|
|
</view>
|
|
<view v-if="state.detail.addressDes" class="second-text-28">
|
|
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
|
|
<text class="ml-8rpx">地址:</text>
|
|
<text class="m-l-10rpx">{{ state.detail.addressDes }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- end 简介 end -->
|
|
</view>
|
|
</tm-sticky-header>
|
|
<tm-alert v-if="state.detail?.discountsPrice" color="#0779FD" icon="notification-line"
|
|
closeIcon="close-line" :border="['2']" :borderColor="['#89c0ff']">
|
|
<marquee class="flex items-center">{{ state.detail.discountsPrice }}</marquee>
|
|
</tm-alert>
|
|
|
|
<!-- start 商品 start -->
|
|
<view v-if="false" class="flex-row">
|
|
<scroll-view v-if="state.classifyList.length" scroll-y :style="{ height: getHeight() }"
|
|
class="menu w-200rpx h-full p-l-12rpx p-b-20rpx bg-#f6f6f6">
|
|
<view class="menu_item flex-row-center" v-for="(v, i) in state.classifyList" :key="v.id"
|
|
:class="[{ menu_item_active: i == state.currentClassifyIndex }]" @tap="doClassify(i)">
|
|
<view class="menu_item_title show-line">{{ v.name }}</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<scroll-view v-if="state.goodsList.length" scroll-y :style="{ height: getHeight() }"
|
|
class="flex-1 p-b-20rpx p-x-15rpx">
|
|
<!-- <product-row
|
|
v-for="goodsItem in state.goodsList"
|
|
:key="goodsItem.id"
|
|
:productId="goodsItem.id"
|
|
:merchantId="goodsItem.merchantId"
|
|
:picUrl="filePath(goodsItem.picUrl)"
|
|
:productName="goodsItem.productName"
|
|
:salesCount="goodsItem.salesCount"
|
|
:stock="goodsItem.stock"
|
|
:unit="goodsItem.unit"
|
|
:retailPrice="goodsItem.retailPrice"
|
|
:promotionPrice="goodsItem.promotionPrice"
|
|
@onClickAddToCart="onClickAddToCart(goodsItem)"
|
|
/> -->
|
|
</scroll-view>
|
|
</view>
|
|
</tm-sticky>
|
|
<!-- end 商品 end -->
|
|
|
|
<template #main-bottom>
|
|
<view v-if="false && state.classifyList.length" class="car z-200 p-x-30rpx p-y-30rpx bg-white">
|
|
<!-- <product-total :totPrice="state.totPrice" :totCount="state.totCount" @doContainer="doVisibleCart" @submit="doBuy" /> -->
|
|
</view>
|
|
</template>
|
|
</crud-layout>
|
|
|
|
<!-- 购物车弹框 -->
|
|
<!-- <product-cart
|
|
v-model="state.showCartDialog"
|
|
:cartList="state.cartList"
|
|
:totPrice="state.totPrice"
|
|
:totCount="state.totCount"
|
|
@add="doAdd"
|
|
@remove="doRemove"
|
|
@submit="doBuy"
|
|
/> -->
|
|
<!-- 规格与数量弹框 -->
|
|
<!-- <product-sku v-model="state.showSkuDialog" :productData="state.curProductData" @onSkuAddToCart="onSkuAddToCart" /> -->
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { reactive, nextTick } from "vue"
|
|
import { onLoad, onShow } from "@dcloudio/uni-app"
|
|
import { isEmpty } from "radash"
|
|
import { hooks, router } from "@@/index"
|
|
import { foodApi, merchantApi, orderApi, payApi, shopApi } from "@@/api"
|
|
import { filePath } from "@@/utils/file"
|
|
import { SERVICE_IMAGE } from "@@/constant/index"
|
|
// import ProductRow from "./components/product-row.vue"
|
|
// import ProductCart from "./components/product-cart.vue"
|
|
// import ProductTotal from "./components/product-total.vue"
|
|
// import ProductSku from "./components/product-sku.vue"
|
|
import { getAppInfo } from "@/sheep/utils/app"
|
|
import { APPENV } from "@/sheep/constant"
|
|
|
|
// 商品详情相关数据
|
|
const state = reactive<any>({
|
|
merchantId: null,
|
|
detail: {},
|
|
|
|
goodsList: [],
|
|
|
|
cartList: [],
|
|
totPrice: 0,
|
|
totCount: 0,
|
|
|
|
classifyList: [],
|
|
currentClassifyIndex: 0,
|
|
|
|
showCartDialog: false,
|
|
showSkuDialog: false,
|
|
|
|
curProductData: {}, // 当前点击加入到购物车的商品信息
|
|
})
|
|
|
|
onLoad((options: any) => {
|
|
if (!isEmpty(options.id)) {
|
|
state.merchantId = options.id
|
|
}
|
|
})
|
|
onShow(async () => {
|
|
uni.showLoading({ title: "加载中...", mask: true })
|
|
try {
|
|
await getDetail()
|
|
// await getShopCart()
|
|
// await getClassify()
|
|
} catch (error: any) {
|
|
uni.showToast({ title: error, icon: "error" })
|
|
} finally {
|
|
uni.hideLoading()
|
|
}
|
|
})
|
|
|
|
const getHeight = () => {
|
|
const rpx = 480
|
|
return `calc(100vh - ${uni.upx2px(rpx)}px)`
|
|
}
|
|
|
|
const getDetail = async () => {
|
|
const { data } = await merchantApi.detail({ id: state.merchantId })
|
|
state.detail = data
|
|
}
|
|
|
|
const getShopCart = async () => {
|
|
const { data } = await orderApi.getShopCar({ merchantId: state.merchantId, hardType: "SUPERMARKET" })
|
|
state.cartList = data ?? []
|
|
state.totPrice = state.cartList.map((v) => Number(v.totalPrice)).reduce((pre, next) => pre + next, 0)
|
|
state.totCount = state.cartList.map((v) => v.productCount).reduce((pre, next) => pre + next, 0)
|
|
}
|
|
|
|
const getClassify = async () => {
|
|
if (state.merchantId) {
|
|
const { data } = await shopApi.getSupermarketSpuCategory({ merchantId: state.merchantId })
|
|
state.classifyList = data ?? []
|
|
if (!isEmpty(state.classifyList)) {
|
|
getPorductList()
|
|
}
|
|
}
|
|
}
|
|
|
|
const getPorductList = async () => {
|
|
const target = state.classifyList[state.currentClassifyIndex]
|
|
if (isEmpty(target)) return
|
|
try {
|
|
uni.showLoading({ title: "加载中...", mask: true })
|
|
const { code, data }: any = await shopApi.getSupermarketSpuList({
|
|
merchantId: state.merchantId,
|
|
categoryId: target.id,
|
|
current: 1,
|
|
size: 99999,
|
|
})
|
|
if (code == 0) {
|
|
state.goodsList = data.records
|
|
return
|
|
}
|
|
state.goodsList = []
|
|
} finally {
|
|
uni.hideLoading()
|
|
}
|
|
}
|
|
|
|
const doClassify = async (i: any) => {
|
|
state.currentClassifyIndex = i
|
|
await getPorductList()
|
|
}
|
|
|
|
// 点击添加到购物车
|
|
const onClickAddToCart = (goodsItem: any) => {
|
|
state.curProductData = goodsItem
|
|
state.showSkuDialog = true
|
|
}
|
|
|
|
// 选择sku加入到购物车
|
|
const onSkuAddToCart = (skuGoodsInfo: any) => {
|
|
chentProductCount(skuGoodsInfo) // 同步购物车中的数据
|
|
state.showSkuDialog = false
|
|
}
|
|
|
|
// 同步购物车中的数据
|
|
const chentProductCount = async (item: any, type?: "add" | "remove") => {
|
|
try {
|
|
uni.showLoading({ title: "加载中", mask: true })
|
|
|
|
// 在购物车中增减商品数量
|
|
let count = item.productCount
|
|
if (type === "add") {
|
|
count += 1
|
|
const { code }: any = await shopApi.checkSpuStock({ spuId: item.spuId, incrCount: count })
|
|
if (code != 0) throw new Error()
|
|
} else if (type === "remove") {
|
|
count -= 1
|
|
}
|
|
|
|
const targetIndex = state.cartList.findIndex((v: any) => v.productId == item.productId)
|
|
if (targetIndex !== -1) {
|
|
state.cartList[targetIndex].productCount = count
|
|
state.cartList[targetIndex].productId = item.productId
|
|
state.cartList[targetIndex].spuId = item.spuId
|
|
} else {
|
|
state.cartList.push(item)
|
|
}
|
|
|
|
nextTick(async () => await updateShopCart()) // 保存购物车数据
|
|
} catch (e) {
|
|
console.error(e)
|
|
} finally {
|
|
uni.hideLoading()
|
|
}
|
|
}
|
|
|
|
// 保存购物车数据
|
|
const updateShopCart = async () => {
|
|
if (isEmpty(state.cartList)) return
|
|
const list = state.cartList
|
|
.filter((v) => v.productCount && v.productCount > 0)
|
|
.map((v) => {
|
|
return {
|
|
productCount: v.productCount,
|
|
productId: v.productId,
|
|
}
|
|
})
|
|
const { code }: any = await orderApi.updateShopCar({ merchantId: state.merchantId, list, hardType: "SUPERMARKET" })
|
|
if (code == 0) await getShopCart()
|
|
}
|
|
|
|
// 打开购物车
|
|
const doVisibleCart = () => (state.showCartDialog = true)
|
|
|
|
const doAdd = async (item: any) => {
|
|
chentProductCount(item, "add")
|
|
}
|
|
const doRemove = async (item: any) => {
|
|
chentProductCount(item, "remove")
|
|
}
|
|
|
|
// 点击提交订单
|
|
const doBuy = async () => {
|
|
uni.showModal({
|
|
title: "提示",
|
|
content: "是否确定购买",
|
|
confirmColor: "#0094ff",
|
|
success: async (res) => {
|
|
if (!res.confirm) return
|
|
submitPay() // 提交订单
|
|
},
|
|
})
|
|
}
|
|
|
|
// 提交订单
|
|
const submitPay = async () => {
|
|
try {
|
|
if (isEmpty(state.cartList)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
|
|
|
|
uni.showLoading({ title: "支付中...", mask: false })
|
|
const { data, code }: any = await payApi.createHardwareOrder({ carParentId: state.cartList[0].parentCarId, hardType: "SUPERMARKET" })
|
|
if (code != 0) throw new Error()
|
|
uni.showToast({ title: "支付成功", icon: "none" })
|
|
router.jump("/pages/tabbar/order")
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
uni.hideLoading()
|
|
}
|
|
}
|
|
|
|
// 联系商家
|
|
const doPhone = async (item: string) => {
|
|
if (isEmpty(item)) return uni.showToast({ title: "该商户没有预留电话", icon: "none" })
|
|
hooks.useMakePhoneCall(item.trim())
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
:deep() {
|
|
.layout_main>scroll-view {
|
|
padding: 0 !important;
|
|
}
|
|
}
|
|
|
|
.menu {
|
|
&_item {
|
|
width: 100%;
|
|
height: 88rpx;
|
|
position: relative;
|
|
transition: all linear 0.2s;
|
|
|
|
&_title {
|
|
width: 100%;
|
|
line-height: 32rpx;
|
|
font-size: 30rpx;
|
|
font-weight: 400;
|
|
color: #333;
|
|
margin-left: 28rpx;
|
|
position: relative;
|
|
z-index: 0;
|
|
|
|
&::before {
|
|
content: "";
|
|
width: 64rpx;
|
|
height: 12rpx;
|
|
background: linear-gradient(90deg, #0094ff, #e9f5fe) !important;
|
|
position: absolute;
|
|
left: -64rpx;
|
|
bottom: 0;
|
|
z-index: -1;
|
|
transition: all linear 0.2s;
|
|
}
|
|
}
|
|
|
|
&.menu_item_active {
|
|
background-color: #fff;
|
|
border-radius: 20rpx 0 0 20rpx;
|
|
|
|
&::before {
|
|
content: "";
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: -20rpx;
|
|
width: 20rpx;
|
|
height: 20rpx;
|
|
background: radial-gradient(circle at 0 100%, transparent 20rpx, #fff 0);
|
|
}
|
|
|
|
&::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: -20rpx;
|
|
right: 0;
|
|
width: 20rpx;
|
|
height: 20rpx;
|
|
background: radial-gradient(circle at 0% 0%, transparent 20rpx, #fff 0);
|
|
}
|
|
|
|
.menu_item_title {
|
|
font-weight: 600;
|
|
|
|
&::before {
|
|
left: 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.car {
|
|
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.scroll-text {
|
|
display: inline-block;
|
|
white-space: nowrap;
|
|
animation: scroll-left 10s linear infinite;
|
|
}
|
|
|
|
@keyframes scroll-left {
|
|
0% {
|
|
transform: translateX(100%);
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(-100%);
|
|
}
|
|
}
|
|
</style>
|