feat: 更新预约功能与礼品卡支持

- 新增礼品卡页面,支持礼品卡的选择与使用。
- 调整预约下单流程,增强预约信息展示与用户交互。
- 更新支付成功页面的导航逻辑,确保用户体验流畅。
- 优化搜索页面的历史关键词加载逻辑,提升性能。
This commit is contained in:
pikachu1995@126.com
2026-09-28 14:37:26 +08:00
parent 2107e3f03a
commit 0bffe81c6b
25 changed files with 2272 additions and 1038 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div class="main-page">
<div class="main-page" :style="themeStyle">
<!-- #ifdef APP-PLUS -->
<view class="status_bar"></view>
<!-- #endif -->
@@ -13,8 +13,6 @@
routerVal.goodsId
" :thumbnail="goodsDetail.thumbnail" :goodsName="goodsDetail.goodsName" type="goods"
@close="enableShare = false" />
<popups v-model="popupsSwitch" @tapPopup="handleNavbarList" :popData="navbarListData" :x="navbarListX"
:y="navbarListY" placement="top-start" />
<view class="index">
<!-- topBar -->
<u-navbar
@@ -25,17 +23,8 @@
:bg-color="navbar.background"
:auto-back="false"
:class="headerFlag ? 'header' : 'header bg-none scroll-hide'"
@leftClick="back"
>
<template #left>
<view class="backs" @tap.stop>
<view class="nav-action" @tap.stop="back()">
<u-icon name="arrow-left" class="icon-back"></u-icon>
</view>
<view class="nav-action" @tap.stop="popupsSwitch = !popupsSwitch">
<u-icon name="list" class="icon-list"></u-icon>
</view>
</view>
</template>
<template #center>
<view class="headerList" :class="headerFlag ? 'tab-bar' : 'tab-bar scroll-hide'">
<view class="headerRow headerRow--tabs">
@@ -62,17 +51,8 @@
class="header-only-back"
:bg-color="navbarOnlyBack.background"
:auto-back="false"
@leftClick="back"
>
<template #left>
<view class="bg-back" @tap.stop>
<view class="nav-action" @tap.stop="back()">
<u-icon size="20" name="arrow-left" class="icon-back"></u-icon>
</view>
<view class="nav-action" @tap.stop="popupsSwitch = !popupsSwitch">
<u-icon size="20" name="list" class="icon-list"></u-icon>
</view>
</view>
</template>
</u-navbar>
</view>
@@ -262,7 +242,13 @@
<view class="detail-btn" v-if="!isGroup && !takeDownFromSale">
<view class="to-store-car to-store-btn" v-if="showAddToCart" @click="shutMask(4)">
加入购物车</view>
<view class="to-buy to-store-btn" @click="shutMask(4, 'buy')">立即购买</view>
<view
class="to-buy to-store-btn"
:class="{ 'to-buy-theme': isAppointmentGoodsType }"
@click="handleBuyClick"
>
{{ isAppointmentGoodsType ? '立即预约' : '立即购买' }}
</view>
<view class="to-store-car to-store-btn" v-if="startTimer">暂未开始</view>
</view>
<!-- 拼团结算 -->
@@ -325,13 +311,13 @@ import * as API_trade from '@/api/trade.js'
import * as API_Members from '@/api/members.js'
import * as API_store from '@/api/store.js'
import { getIMDetail } from '@/api/common'
import { modelNavigateTo } from '@/pages/tabbar/home/template/tpl.js'
/************请求存储***************/
import storage from '@/utils/storage.js'
/************工具函数***************/
import { useStore } from '@/store'
import { getThemeStyle } from '@/utils/theme'
import { unitPrice, goodsFormatPrice, isLogin, parseGoodsImageUrl, clearStrComma, talkIm } from '@/utils/filters.js'
import { isNonPhysicalGoods } from '@/utils/goodsType.js'
import { isNonPhysicalGoods, isAppointmentGoods } from '@/utils/goodsType.js'
/************组件***************/
import PromotionLayout from './product/promotion/-promotion'
@@ -347,36 +333,17 @@ import GoodsSwiper from './product/goods/-goods-swiper'
import popupGoods from '@/components/m-buy/goods'
import popupAddress from './product/popup/address'
import shares from '@/components/m-share/index'
import popups from '@/components/popups/popups'
import takeDownFormSaleGoods from '@/components/m-take-down-sale-goods/index'
import setup from './product/popup/popup'
const store = useStore()
const themeStyle = computed(() => getThemeStyle(store.state.theme))
const instance = getCurrentInstance()
const popupGoodsRef = ref()
const takeDownSaleRef = ref()
// 响应式数据
const promotionShow = ref(false)
let navbarListX: any
let navbarListY: any
// #ifdef H5
navbarListX = ref(110)
navbarListY = ref(80)
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
// #ifndef H5
navbarListX = ref(120)
navbarListY = ref(170)
// #endif
// #endif
const navbarListData = [
{ title: "首页", icon: "home-fill", ___type: "other" },
{ title: "购物车", icon: "bag-fill", ___type: "other" },
{ title: "搜索", icon: "search", ___type: "category" },
{ title: "个人中心", icon: "account-fill", ___type: "other" },
]
const popupsSwitch = ref(false)
const enableShare = ref(false)
const selectedGoods = ref<any>("")
const isGroup = ref(false)
@@ -437,6 +404,7 @@ const isNonPhysicalGoodsType = computed(() => isNonPhysicalGoods(goodsDetail.val
const showAddToCart = computed(() => {
return !isNonPhysicalGoodsType.value
})
const isAppointmentGoodsType = computed(() => isAppointmentGoods(goodsDetail.value?.goodsType))
// watch
watch(isGroup, (val) => {
@@ -494,10 +462,6 @@ function share() {
return `/pages/product/goods?id=${routerVal.value.id}&goodsId=${routerVal.value.goodsId}`
}
function handleNavbarList(val: any) {
modelNavigateTo({ url: val })
}
function emptyPromotion(): boolean {
if (PromotionList.value == "" || PromotionList.value == null || PromotionList.value == []) {
return true
@@ -637,13 +601,11 @@ function cartCount() {
}
function back() {
if (getCurrentPages().length == 1) {
uni.switchTab({
url: "/pages/tabbar/home/index",
})
} else {
uni.navigateBack()
}
uni.navigateBack({
fail() {
uni.switchTab({ url: '/pages/tabbar/home/index' })
},
})
}
function getStoreBaseInfoFun(id: any) {
@@ -724,6 +686,26 @@ function getCoupon(item: any) {
receiveCouponsFun(item.id)
}
function goAppointmentCheckout(num = 1) {
const sku = goodsDetail.value?.id
const gid = goodsDetail.value?.goodsId || routerVal.value.goodsId
if (!sku || !gid) {
uni.showToast({ title: '商品信息异常', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages/order/appointment/appointmentCheckout?skuId=${sku}&goodsId=${gid}&num=${num}`,
})
}
function handleBuyClick() {
if (isAppointmentGoodsType.value) {
goAppointmentCheckout()
return
}
shutMask(4, 'buy')
}
function shutMask(flag: any, buyFlag?: any, type?: any) {
promotionShow.value = false
buyMask.value = false

View File

@@ -46,6 +46,10 @@ page {
> .to-buy {
background-image: linear-gradient(135deg, #ffba0d, #ffc30d 69%, #ffcf0d);
}
> .to-buy-theme {
background-image: none;
background: var(--theme-primary, #ff3c2a);
}
> .to-store-car {
background-image: linear-gradient(135deg, #f2140c, #f2270c 70%, #f24d0c);
}
@@ -201,7 +205,7 @@ page {
.header-only-back {
background: transparent;
z-index: 9;
z-index: 12 !important;
}
.header,
.header-only-back {
@@ -332,26 +336,10 @@ page {
padding: 0;
}
.nav-action + .nav-action {
border-left: 2rpx solid rgb(194, 194, 194);
}
.backs,
.bg-back {
width: 150rpx;
border: 2rpx solid #e8e8e8;
border-radius: 100px;
display: flex;
align-items: center;
justify-content: space-around;
padding: 8rpx 0;
box-sizing: border-box;
}
.bg-back {
background: rgba($color: #fff, $alpha: 0.8);
}
.backs {
text-align: center;
font-size: 42rpx;
}
.headerImg {

View File

@@ -234,7 +234,7 @@ import {
getStoreIsCollect,
} from '@/api/members.js'
import config from '@/config/config'
import { getGoodsList } from '@/api/goods.js'
import { getGoodsList, getMpScene } from '@/api/goods.js'
import { getAllCoupons } from '@/api/promotions.js'
import { getFloorStoreData } from '@/api/home'
import {
@@ -349,6 +349,33 @@ function fallbackToBasePage() {
getCategoryData()
}
function applyStoreId(id) {
storeId.value = id || ''
goodsParams.storeId = storeId.value
couponParams.storeId = storeId.value
}
function parseStoreIdFromParams(params) {
const text = String(params || '').trim()
if (!text) return ''
if (text.indexOf('id=') === 0) return text.slice(3)
const match = /(?:^|&)id=([^&]+)/.exec(text)
return match ? decodeURIComponent(match[1]) : ''
}
async function resolveStoreIdFromScene(scene) {
const raw = decodeURIComponent(String(scene || '')).trim()
if (!raw) return ''
const direct = parseStoreIdFromParams(raw)
if (direct) return direct
try {
const res = await getMpScene(raw)
return parseStoreIdFromParams(res?.data?.result)
} catch {
return ''
}
}
function getStoreLicencePhoto() {
uni.navigateTo({
url: `/pages/product/licencePhoto?id=${storeId.value}`,
@@ -505,11 +532,15 @@ function getCoupon(item: any) {
}
onLoad((options: any) => {
storeId.value = options.id
console.log(storeId.value, 'this.storeId')
goodsParams.storeId = options.id
couponParams.storeId = options.id
applyStoreId(options?.id)
if (!storeId.value && options?.scene) {
resolveStoreIdFromScene(options.scene).then((id) => {
if (!storeId.value && id) {
applyStoreId(id)
init()
}
})
}
})
onPageScroll((e) => {
@@ -531,7 +562,7 @@ onMounted(() => {
withShareTicket: true,
})
// #endif
init()
if (storeId.value) init()
})
</script>