SFH5/pagesb/flashSale/index.vue
2026-03-16 11:10:28 +08:00

452 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="invoice-wrap" :class="[`${themeInfo.theme}-theme`]">
<navBar></navBar>
<!-- 彩带背景 -->
<view class="seckill-bg">
<!-- 彩带 -->
<view class="confetti"></view>
<view class="confetti"></view>
<view class="confetti"></view>
<view class="confetti"></view>
<view class="confetti"></view>
<view class="confetti"></view>
<!-- 光斑 -->
<view class="dot"></view>
<view class="dot"></view>
<view class="dot"></view>
<!-- 烟雾 -->
<view class="smoke"></view>
<view class="smoke"></view>
</view>
<view class="content">
<view class="title" style="transform: scaleY(1.5);height: 100rpx;display: flex;justify-content: center;color: #ffffff;font-size: 68rpx;font-weight: bolder;font-style: italic;letter-spacing: 4rpx;margin-bottom: 40rpx;">
{{ $t('common.FlashSale') }}
</view>
<view class="info">
<view class="cityBox">
<view>{{$t('common.SwitchRegion')}}</view>
<view>
<myDropdown v-model="state.city" :items="state.cityList" @change="changeCity"></myDropdown>
</view>
</view>
</view>
<view class="siteListBox">
<view class="top">
<view>
{{$t('common.Countdown')}}
</view>
<view>
{{ countDown?.formatted?.value }}
</view>
</view>
<view class="siteList">
<view class="ul" v-for="(item,index) in state.dataList" :key="index" @click="goOrder(item)">
<view class="left">
<view class="tags">
{{ $t("discount",{discount:item.discount}) }}
</view>
<view class="name">
{{item.siteName}}
</view>
<view class="type">
<text>
{{item.unitTypeName}} &nbsp;
</text>
<text>
[ {{ item.lockerName }} ]
</text>
</view>
<view class="volume">
<text>
{{$t('site.ReferenceVolume')}}{{item.volume}}
</text>
</view>
</view>
<view class="right">
<view class="priceBox">
<view class="price1">
{{currency}} {{item.flashSalePrice}}
</view>
<view class="price2">
{{currency}} {{item.price}}
</view>
</view>
<view class="icon">
<uv-icon name="arrow-right" size="14" color="#000000"></uv-icon>
</view>
</view>
</view>
<view class="ul" v-if="state.dataList.length==0">
<view style="text-align: center;width: 100%;font-size: 32rpx;padding: 10rpx;">
{{ $t('common.noData') }}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
// 主题色配置
import { useMainStore } from "@/store/index.js";
import navBar from "@/components/navBar.vue";
import flashSaleApi from "@/pagesb/Apis/flashSale.js";
import { onLoad,onShow } from '@dcloudio/uni-app';
import { getDistance} from '@/utils/common.js';
import myDropdown from "@/components/my-dropdown.vue";
import { useCountDown } from "@/hooks/useCountDown.js";
import { reactive, ref } from "vue";
import { currency } from '@/config/index.js'
import { useLocation } from "@/hooks/useLocation";
const { themeInfo } = useMainStore();
const { locationState,getLocation } = useLocation();
const state = reactive({
goodsIds: [],
orderId: "",
dataList: [],
goodsListRemark: "",
cityList: [],
startTime:null,
endTime:null,
allDataList: [],
});
onShow(() => {
getDataList()
})
const changeCity = () => {
applyCityFilter()
// 切换城市后,如需重置倒计时显示(如果 startTime/endTime 是全局一样,其实不用)
if (state.dataList.length > 0 && state.startTime && state.endTime) {
initCountDown()
}
}
// ✅ 根据当前 city 筛选展示列表
const applyCityFilter = () => {
if (!state.city) {
state.dataList = []
return
}
state.dataList = state.allDataList.filter(x => x.district === state.city)
}
// ✅ 从全量数据生成下拉 district 列表
const buildCityListFromAllData = () => {
const districts = Array.from(
new Set((state.allDataList || []).map(x => x.district).filter(Boolean))
)
state.cityList = districts.map(d => ({ label: d, value: d }))
// 如果当前 city 不存在,就默认第一个
if (!state.city || !districts.includes(state.city)) {
state.city = districts[0] || ""
}
}
const goOrder = (item) => {
uni.navigateTo({
url: `/pages/setOrder/index?id=${item.id}`,
});
}
const getCityList = () => {
uni.showLoading()
// let data = {}
// if (locationState?.latitude && locationState?.longitude) {
// data = {
// latitude: locationState.latitude,
// longitude: locationState.longitude
// }
// }
flashSaleApi.GetFlashSaleDistrict().then(res => {
state.cityList = []
if (res.code == 200) {
state.cityList = res.data.map(item =>({label:item,value:item}));
if(state.cityList.length>0){
state.city = state.cityList[0].value
getDataList()
}
}
}).finally(() => {
uni.hideLoading()
})
}
const getDataList = () => {
uni.showLoading()
state.dataList = []
flashSaleApi.GetFlashSaleInfo().then(res => {
if (res.code == 200) {
state.startTime = res.data.flashSaleStartTime
state.endTime = res.data.flashSaleEndTime
const lockerData = res.data.lockerData;
// 按照距离排序
if (locationState?.latitude && locationState?.longitude) {
const { latitude, longitude } = locationState;
lockerData.forEach(item => {
const { distance, number } = getDistance(latitude, longitude, item.latitude, item.longitude);
item.distance = distance;
item.distanceNumber = number;
});
lockerData.sort((a, b) => a.distanceNumber - b.distanceNumber);
}
state.dataList= lockerData
// ✅ 全量源数据
state.allDataList = lockerData || []
// ✅ 用 district 生成 cityList
buildCityListFromAllData()
// ✅ 按当前 city 过滤展示
applyCityFilter()
if(state.dataList.length>0 && (state.startTime && state.endTime)){
initCountDown()
}
}
}).finally(() => {
uni.hideLoading()
})
}
let countDown = null
const initCountDown = () => {
if (!countDown) {
countDown = useCountDown(
state.startTime,
state.endTime,
() => {
getDataList()
uni.showToast({
title: '倒计时结束',
icon: 'none'
})
}
)
} else {
// 重置倒计时,不会产生重复计时器
countDown.reset(state.startTime, state.endTime)
}
countDown.start()
}
onLoad((option) => {
state.orderId = option.orderId;
getLocation().finally(() => {
getDataList();
});
})
</script>
<style lang="scss" scoped>
@import "@/static/style/theme.scss";
.invoice-wrap {
min-height: 100vh;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 40rpx;
padding-bottom: 20rpx;
background: linear-gradient(to bottom,
var(--right-linear),
var(--left-linear2));
.content {
width: 100%;
z-index: 2;
.info {
.cityBox {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
font-weight: bold;
border-radius: 16rpx;
background-color: #FFFFFF;
}
}
.siteListBox {
margin-top: 20rpx;
margin-bottom: 200rpx;
.top {
border-radius: 16rpx 16rpx 0 0;
display: flex;
justify-content: space-between;
padding: 20rpx;
background-color: #12324D;
color: #ffffff;
font-size: 42rpx;
font-weight: bold;
letter-spacing: 4rpx;
}
.siteList {
background-color: #FFFFFF;
border-radius: 0 0 16rpx 16rpx;
font-size: 26rpx;
font-weight: bold;
.ul {
padding: 20rpx;
border-bottom: 1px dashed var(--main-color);
display: flex;
justify-content: space-between;
align-items: center;
&:last-child{
border: none;
}
.left {
.tags{
text-align: center;
padding: 6rpx 10rpx;
width: fit-content;
color: #FFFFFF;
background-color: rgb(252, 89, 116);
font-size: 22rpx;
border-radius: 8rpx;
}
.name {
margin-top: 4rpx;
font-size: 32rpx;
font-weight: bold;
}
.type {
margin-top: 4rpx;
text {
display: inline-block;
}
}
.volume {
margin-top: 4rpx;
text {
display: inline-block;
}
}
}
.right {
display: flex;
align-items: center;
.priceBox {
display: flex;
align-items: center;
flex-direction: column;
font-style: italic;
.price1 {
font-size: 52rpx;
font-weight: bold;
margin-bottom: 10rpx;
}
.price2 {
font-size: 52rpx;
font-weight: bold;
color: #a8a8a8;
position: relative;
&:after {
content: " ";
display: block;
width: 120%;
height: 2px;
background-color: var(--btn-color3);
// transform: rotate(9deg);
position: absolute;
top: 49%;
left: -5px;
}
}
}
.icon {
margin-left: 20rpx;
display: flex;
align-items: center;
}
}
}
}
}
}
}
.seckill-bg {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 0;
// background-color: #ff4b2b; /* 固定纯色背景 */
}
/* 彩带,限制在顶部 300px 内 */
.confetti {
position: absolute;
width: 8px;
height: 20px;
border-radius: 2px;
top: 0; /* 起点在顶部 */
animation: fall 4s linear infinite;
transform-origin: center;
}
.confetti:nth-child(1) { left: 5%; animation-delay: 0s; animation-duration: 4s; background: #ffeb3b; }
.confetti:nth-child(2) { left: 18%; animation-delay: 0.5s; animation-duration: 3.5s; background: #00e5ff; }
.confetti:nth-child(3) { left: 32%; animation-delay: 1s; animation-duration: 4.2s; background: #ff4081; }
.confetti:nth-child(4) { left: 46%; animation-delay: 1.2s; animation-duration: 3.8s; background: #76ff03; }
.confetti:nth-child(5) { left: 60%; animation-delay: 1.5s; animation-duration: 4.5s; background: #ff6e40; }
.confetti:nth-child(6) { left: 75%; animation-delay: 0.3s; animation-duration: 4.1s; background: #e040fb; }
.confetti:nth-child(7) { left: 88%; animation-delay: 0.7s; animation-duration: 3.7s; background: #ffea00; }
@keyframes fall {
0% { transform: translate(0,0) rotate(0deg) scale(0.8); opacity: 1; }
50% { transform: translate(20px,150px) rotate(180deg) scale(1.2); opacity: 0.9; }
100% { transform: translate(-20px,300px) rotate(360deg) scale(0.8); opacity: 0; }
}
/* 光斑 */
.dot {
position: absolute;
width: 10px;
height: 10px;
background: rgba(255,255,255,0.7);
border-radius: 50%;
animation: blink 3s infinite;
}
.dot:nth-child(7) { top: 10%; left: 20%; animation-delay: 0s;}
.dot:nth-child(8) { top: 16%; left: 60%; animation-delay: 1s;}
.dot:nth-child(9) { top: 13%; left: 80%; animation-delay: 2s;}
@keyframes blink {
0%,100% {opacity: 0.2;}
50% {opacity: 1;}
}
/* 烟雾 */
.smoke {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.08), transparent 70%);
top: 70%;
animation: smokeDrift 15s linear infinite;
filter: blur(20px);
opacity: 0.5;
}
.smoke:nth-child(10) { left: 10%; animation-duration: 18s; width: 250px; height: 250px; }
.smoke:nth-child(11) { left: 60%; animation-duration: 20s; width: 300px; height: 300px; }
@keyframes smokeDrift {
0% { transform: translateX(0) translateY(0) scale(0.5);}
50% { transform: translateX(100px) translateY(-50vh) scale(1);}
100% { transform: translateX(200px) translateY(-100vh) scale(1.5);}
}
</style>