Files
pj_mini_app_cloud/src/pages/merchant/detail.vue
T
2026-03-26 11:03:45 +08:00

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>