452 lines
13 KiB
Vue
452 lines
13 KiB
Vue
<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}}
|
||
</text>
|
||
<text>
|
||
[ {{ item.lockerName }} ]
|
||
</text>
|
||
</view>
|
||
<view class="volume">
|
||
<text>
|
||
{{$t('site.ReferenceVolume')}}:{{item.volume}}m³
|
||
</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>
|