add new `/notice` application route and standalone notice detail page format vue-router import to follow project code style guidelines hide myOrder entries from ChatQuickAccess and MoreService component lists comment out unused book now button in goods page and location navigation card refactor home page drawer event listeners with proper types and optional chaining fix NoticeMessage component: remove redundant swiper indicators, adjust container padding, update navigation to use the new notice route comment out weather fetching logic in Welcome component to disable weather display
273 lines
8.8 KiB
Vue
273 lines
8.8 KiB
Vue
<template>
|
||
<div class="flex flex-col h-screen bg-white">
|
||
<TopNavBar fixed :title="navOpacity < 0.5 ? '' : t('goods.title.detail')"
|
||
:background="`rgba(255, 255, 255, ${navOpacity})`" :titleColor="navOpacity < 0.5 ? '#ffffff' : '#000000'"
|
||
:backIconColor="navOpacity < 0.5 ? '#ffffff' : '#000000'" />
|
||
<!-- 滚动区域 -->
|
||
<div class="flex-1 overflow-y-auto scrollbar-none [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
|
||
@scroll="handleScroll">
|
||
<ImageSwiper :border-radius="0" :height="300" :images="goodsData.commodityPhotoList" thumbnailBottom="42px" />
|
||
|
||
<div class="bg-white py-[20px] relative mt-[-30px] z-10 rounded-t-[28px]">
|
||
<!-- 商品信息组件 -->
|
||
<GoodInfo :goodsData="goodsData" />
|
||
|
||
<!-- 地址区域 -->
|
||
<LocationCard :orderData="goodsData" />
|
||
|
||
<!-- 日期选择区域 -->
|
||
<DateSelector v-if="goodsData.orderType == 0" @showCalendar="showCalendar" :checkInDate="selectedDate.startDate"
|
||
:checkOutDate="selectedDate.endDate" :checkInDay="''" :checkOutDay="''" :nights="selectedDate.totalDays" />
|
||
|
||
<!-- 商品套餐组件 -->
|
||
<GoodPackage v-if="
|
||
goodsData.orderType != 0 &&
|
||
goodsData.commodityPackageConfig &&
|
||
goodsData.commodityPackageConfig.length
|
||
" :goodsData="goodsData" />
|
||
|
||
<!-- 商品设施组件 -->
|
||
<GoodFacility v-if="
|
||
goodsData.commodityEquipment &&
|
||
goodsData.commodityEquipment.length
|
||
" :goodsData="goodsData" />
|
||
|
||
<!-- 商品详情组件 -->
|
||
<GoodDetail :goodsData="goodsData" />
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 立即抢购 -->
|
||
<div
|
||
class="sticky bottom-0 bg-white px-[12px] pt-[12px] pb-[24px] z-10 shrink-0 flex items-center border-t border-ink-200">
|
||
<div class="flex items-center mr-[8px] text-[#FF3D60]">
|
||
<span class="text-[16px] font-medium">¥</span>
|
||
<span class="text-[20px] font-bold leading-[28px]">
|
||
{{ calculatedTotalPrice }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- <div
|
||
class="flex items-center ml-auto pl-[8px] rounded-[10px] w-[120px] h-[48px] [background:linear-gradient(90deg,#ff3d60_57%,#ff990c_100%)]"
|
||
@click="navigateToPay(goodsData)">
|
||
<img class="w-[34px] h-[48px]" src="https://oss.nianxx.cn/mp/static/version_101/common/btn.png" />
|
||
<span class="text-[16px] font-medium text-white">{{ t("goods.actions.bookNow") }}</span>
|
||
</div> -->
|
||
</div>
|
||
|
||
<!-- 日历组件 -->
|
||
<Calender :visible="calendarVisible" mode="range" :range-require-price="true" :price-data="priceData"
|
||
@close="handleCalendarClose" @range-select="handleDateSelect" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { onMounted, ref } from "vue";
|
||
import { useI18n } from "vue-i18n";
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { showDialog } from "vant";
|
||
import { goodsDetail, commodityDailyPriceList } from "@/api/goods";
|
||
import { DateUtils } from "@/utils/dateUtils";
|
||
import TopNavBar from "@/components/TopNavBar/index.vue";
|
||
import ImageSwiper from "@/components/ImageSwiper/index.vue";
|
||
import GoodInfo from "./components/GoodInfo/index.vue";
|
||
import Calender from "@/components/Calender/index.vue";
|
||
import LocationCard from "./components/LocationCard/index.vue";
|
||
import DateSelector from "./components/DateSelector/index.vue";
|
||
import GoodDetail from "@/components/GoodDetail/index.vue";
|
||
import GoodFacility from "./components/GoodFacility/index.vue";
|
||
import GoodPackage from "./components/GoodPackage/index.vue";
|
||
import { useSelectedDateStore } from "@/store";
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const { t } = useI18n();
|
||
|
||
// 导航栏透明度 - 默认透明,随滚动变为不透明
|
||
const navOpacity = ref(0);
|
||
const calendarVisible = ref(false);
|
||
const goodsData = ref({});
|
||
|
||
// 处理滚动事件
|
||
const handleScroll = (e) => {
|
||
const scrollTop = Number(e?.target?.scrollTop ?? e?.currentTarget?.scrollTop ?? e?.detail?.scrollTop ?? 0);
|
||
// 设置一个阈值,当滚动超过200px时,导航栏完全不透明
|
||
const threshold = 200;
|
||
navOpacity.value = Math.min(scrollTop / threshold, 1);
|
||
};
|
||
|
||
const selectedDate = ref({
|
||
startDate: DateUtils.formatDate(), // 当天日期
|
||
endDate: DateUtils.formatDate(new Date(Date.now() + 24 * 60 * 60 * 1000)), // 第二天日期
|
||
totalDays: 1,
|
||
});
|
||
|
||
const priceData = ref([]);
|
||
|
||
// 计算的总价格
|
||
const calculatedTotalPrice = ref(0);
|
||
|
||
// 获取商品详情数据
|
||
const goodsInfo = async (params) => {
|
||
const res = await goodsDetail(params);
|
||
|
||
goodsData.value = res.data;
|
||
|
||
// 初始化计算价格
|
||
calculatedTotalPrice.value = goodsData.value.specificationPrice || 0;
|
||
|
||
// 判断是酒店类型订单再获取获取商品日价格及库存
|
||
if (goodsData.value.orderType == 0) {
|
||
configGoodsData();
|
||
getGoodsDailyPrice({
|
||
commodityId: goodsData.value.commodityId,
|
||
});
|
||
}
|
||
|
||
if (goodsData.value.commodityStatus !== "1") {
|
||
showDialog({
|
||
title: t("goods.dialog.unavailableTitle"),
|
||
message: t("goods.dialog.unavailableMessage"),
|
||
|
||
}).then(() => {
|
||
router.back()
|
||
}).catch(() => {
|
||
|
||
});
|
||
return;
|
||
}
|
||
};
|
||
|
||
const configGoodsData = () => {
|
||
goodsData.value.startDate = selectedDate.value.startDate;
|
||
goodsData.value.endDate = selectedDate.value.endDate;
|
||
goodsData.value.totalDays = selectedDate.value.totalDays;
|
||
goodsData.value.calculatedTotalPrice = calculatedTotalPrice.value;
|
||
};
|
||
|
||
// 获取商品日价格及库存
|
||
const getGoodsDailyPrice = async (params) => {
|
||
const res = await commodityDailyPriceList(params);
|
||
|
||
priceData.value = res.data;
|
||
};
|
||
|
||
// TODO
|
||
const selectedDateStore = useSelectedDateStore();
|
||
const getDefaultSelectedDate = () => ({
|
||
startDate: DateUtils.formatDate(),
|
||
endDate: DateUtils.formatDate(new Date(Date.now() + 24 * 60 * 60 * 1000)),
|
||
totalDays: 1,
|
||
});
|
||
|
||
const normalizeSelectedDate = (data) => {
|
||
if (data?.startDate && data?.endDate && Number(data.totalDays) > 0) {
|
||
return {
|
||
startDate: data.startDate,
|
||
endDate: data.endDate,
|
||
totalDays: Number(data.totalDays),
|
||
};
|
||
}
|
||
|
||
return getDefaultSelectedDate();
|
||
};
|
||
|
||
onMounted(() => {
|
||
// 从路由参数中获取商品ID
|
||
const commodityId = route.query.commodityId || "1950766939442774018";
|
||
// 从store中获取选中的日期
|
||
selectedDate.value = normalizeSelectedDate(selectedDateStore.selectedDate);
|
||
selectedDateStore.setData({ ...selectedDate.value });
|
||
|
||
goodsInfo({ commodityId });
|
||
});
|
||
|
||
// 显示日历弹窗
|
||
const showCalendar = () => (calendarVisible.value = true);
|
||
|
||
const handleCalendarClose = () => (calendarVisible.value = false);
|
||
|
||
const handleDateSelect = (data) => {
|
||
console.log("选择的日期:", data);
|
||
|
||
// 保存选择的日期范围
|
||
selectedDate.value = {
|
||
startDate: data.startDate,
|
||
endDate: data.endDate,
|
||
totalDays: data.totalDays,
|
||
};
|
||
|
||
selectedDateStore.setData(selectedDate.value);
|
||
|
||
// 根据商品类型计算价格
|
||
if (goodsData.value.orderType == 0) {
|
||
// 酒店类型:计算dateRange总价格,排除最后一天(同一天除外)
|
||
if (data.dateRange && Array.isArray(data.dateRange)) {
|
||
// 获取默认价格作为回退值
|
||
const defaultPrice = Number(goodsData.value.specificationPrice) || 0;
|
||
|
||
// 检查价格是否有效的函数
|
||
const isValidPrice = (price) => {
|
||
return (
|
||
price !== null &&
|
||
price !== undefined &&
|
||
price !== "" &&
|
||
price !== "-" &&
|
||
!isNaN(Number(price)) &&
|
||
Number(price) > 0
|
||
);
|
||
};
|
||
|
||
// 如果是同一天(数组长度为1),使用该天的价格
|
||
if (data.dateRange.length === 1) {
|
||
const dayPrice = data.dateRange[0].price;
|
||
calculatedTotalPrice.value = isValidPrice(dayPrice)
|
||
? Number(dayPrice)
|
||
: defaultPrice;
|
||
console.log(
|
||
"同一天选择,价格:",
|
||
calculatedTotalPrice.value,
|
||
"(原价格:",
|
||
dayPrice,
|
||
")",
|
||
);
|
||
} else {
|
||
// 多天选择,排除最后一天
|
||
const dateRangeExcludingLast = data.dateRange.slice(0, -1);
|
||
calculatedTotalPrice.value = dateRangeExcludingLast.reduce(
|
||
(total, dateItem) => {
|
||
const dayPrice = dateItem.price;
|
||
const priceToAdd = isValidPrice(dayPrice)
|
||
? Number(dayPrice)
|
||
: defaultPrice;
|
||
return total + priceToAdd;
|
||
},
|
||
0,
|
||
);
|
||
console.log(
|
||
"酒店类型计算总价格(排除最后一天):",
|
||
calculatedTotalPrice.value,
|
||
);
|
||
}
|
||
configGoodsData();
|
||
}
|
||
} else {
|
||
// 非酒店类型:如果没有dateRange,使用默认价格
|
||
calculatedTotalPrice.value = goodsData.value.specificationPrice || 0;
|
||
}
|
||
|
||
// 日历组件会自动关闭,无需手动设置
|
||
calendarVisible.value = false;
|
||
};
|
||
|
||
// 跳转订购
|
||
const navigateToPay = ({ commodityId }) => {
|
||
router.push({
|
||
name: "booking",
|
||
query: {
|
||
commodityId,
|
||
},
|
||
})
|
||
};
|
||
</script>
|