Commit c04b6053 authored by 罗超's avatar 罗超

机票模块修改

parent 24124116
......@@ -15,6 +15,45 @@
flex-wrap: nowrap;
align-items: center;
}
.q-mr-md{
margin-right: 32rpx;
}
.q-ml-md{
margin-left: 32rpx;
}
.q-mt-md{
margin-top: 32rpx;
}
.q-mb-md{
margin-bottom: 32rpx;
}
.q-mr-sm{
margin-right: 16rpx;
}
.q-ml-sm{
margin-left: 16rpx;
}
.q-mt-sm{
margin-top: 16rpx;
}
.q-mb-sm{
margin-bottom: 16rpx;
}
.flex-grid{
margin-left: -16rpx;
margin-bottom: -16rpx;
}
.flex-wrap{
flex-wrap: wrap;
}
.col-t-b{
width: calc(50% - 16rpx);
margin: 0 0 16rpx 16rpx
}
.col-f-b{
width: calc(25% - 16rpx);
margin: 0 0 16rpx 16rpx
}
.row-ajc-w{
display: flex;
flex-wrap: wrap;
......@@ -33,6 +72,25 @@
align-items: center;
justify-content: end;
}
.text-center{
text-align: center;
}
.text-input{
background: #F1F2F4;
/* height: 60rpx; */
border-radius: 12rpx;
padding: 12rpx;
font-size: 28rpx;
color: #111111;
}
.text-input:focus,
.text-input:active{
background-color: #111111;
color:#F1F2F4;
}
.text-input::placeholder{
font-size: 24rpx;
}
.row-sb-n{
display: flex;
align-items: center;
......@@ -59,6 +117,9 @@
.items-center{
align-items: center;
}
.justify-center{
justify-content: center;
}
.flex1,.col{
flex: 1;
}
......
@font-face {
font-family: "tffont"; /* Project id 4816119 */
src: url('//at.alicdn.com/t/c/font_4816119_ut3uqptmt1.woff2?t=1737444391856') format('woff2'),
url('//at.alicdn.com/t/c/font_4816119_ut3uqptmt1.woff?t=1737444391856') format('woff'),
url('//at.alicdn.com/t/c/font_4816119_ut3uqptmt1.ttf?t=1737444391856') format('truetype');
src: url('//at.alicdn.com/t/c/font_4816119_vhne921vx0o.woff2?t=1739518077462') format('woff2'),
url('//at.alicdn.com/t/c/font_4816119_vhne921vx0o.woff?t=1739518077462') format('woff'),
url('//at.alicdn.com/t/c/font_4816119_vhne921vx0o.ttf?t=1739518077462') format('truetype');
}
.tffont {
......@@ -13,6 +13,46 @@
-moz-osx-font-smoothing: grayscale;
}
.tffont-arrowleft:before {
content: "\e610";
}
.tffont-arrow_right:before {
content: "\e616";
}
.tffont-arrow_down:before {
content: "\e687";
}
.tffont-wangfan1:before {
content: "\e613";
}
.tffont-home_1:before {
content: "\e665";
}
.tffont-Home:before {
content: "\e64e";
}
.tffont-arrow-down:before {
content: "\e674";
}
.tffont-dingwei:before {
content: "\e6a2";
}
.tffont-ditu-xian:before {
content: "\e631";
}
.tffont-change1:before {
content: "\e66a";
}
.tffont-shouye:before {
content: "\e634";
}
......
......@@ -718,6 +718,27 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path" : "airIndex",
"style" :
{
"navigationStyle": "custom"
}
},
{
"path" : "airCity",
"style" :
{
"navigationBarTitleText" : "特价城市集合"
}
},
{
"path" : "ticketList/ticketList",
"style" :
{
"navigationStyle" : "custom"
}
}
]
}
......
......@@ -24,7 +24,7 @@
</view>
<view class="height-line1"></view>
</view>
<view class="column" style="display: none;">
<view class="column">
<view class="AirplanePassengerAddEditingC-text row-sb-n fz30">
<text class="flexS AirplanePassengerAddEditingC-textL">联系电话</text>
<view class="flexG">
......@@ -158,8 +158,8 @@
</view>
<view class="showAddEditing-buttom fz32 fontBold textCenter row-sb-n">
<view class="border1 colorDEBF7B borderDEBF7B" @click="showAddEditingPreviwe=false">返回修改</view>
<view v-if="!loading" class="border1 borderDEBF7B" @click="submit">确认{{editorIndex||editorIndex==0?'编辑':'添加'}}</view>
<view v-else class="border1 bgF5">确认{{editorIndex||editorIndex==0?'编辑':'添加'}}</view>
<view class="border1 borderDEBF7B" @click="submit">确认{{editorIndex||editorIndex==0?'编辑':'添加'}}</view>
<!-- <view v-else class="border1 bgF5">确认{{editorIndex||editorIndex==0?'编辑':'添加'}}</view> -->
</view>
</view>
</u-popup>
......@@ -325,10 +325,15 @@
this.addMsg.PassportExpiry = e.fulldate
},
submit(){
console.log('enter....')
if(this.loading) return
this.loading = true
this.addMsg.Id = Math.floor(Math.random()*(100-1)+1)
if(!this.editorIndex){
this.GuestList.push(this.addMsg)
}
this.loading=false
console.log('enter....',this.GuestList,this.editorIndex,this.addMsg)
if(this.GuestList.length>0){
uni.setStorageSync("GuestList",this.GuestList)
uni.navigateBack({
......
<template>
<view class="cities-list column">
<view class="search-box column items-center justify-center">
<view class="row items-center" @click="resetCityVisible=true">
<text class="cities-name">{{chosenResult[0].Name}}</text>
<view style="margin:0 30rpx">
<u-icon name="wangfan1" custom-prefix="tffont" color="#00000066" size="36"></u-icon>
</view>
<text class="cities-name">{{chosenResult[1].Name}}</text>
<u-icon class="q-ml-sm" name='arrow_down' custom-prefix="tffont" color="#00000066" size="32"></u-icon>
</view>
<view @click="()=>dateVisible=true" class="q-mt-sm row items-center" style="font-size: 24rpx;color:#000000BB">
<text>{{ dateRangeValue.formatStartDate }}</text>
<u-icon class="q-ml-sm" name='arrow_down' custom-prefix="tffont" color="#00000066" size="32"></u-icon>
</view>
<chosen-city @closed="resetCityVisible=false" @change="setNewCitiesHandle" :value="chosenResult" v-if="resetCityVisible"></chosen-city>
<u-popup mode="bottom" border-radius="20" :popup="false" v-model="dateVisible" :maskCloseAble="true" length="auto" :safeAreaInsetBottom="true" @close="()=>dateVisible=false" :z-index="9999">
<date-range :value="dateRangeValue" @change='setDateHandler'></date-range>
</u-popup>
</view>
<scroll-view scroll-y="true" style="width: 100%;height: 1px;" class="col">
<view class="air-low-rank">
<view class="column items-center justify-center q-mb-md" v-if="loadingLow">
<u-loading mode="circle" size="40"></u-loading>
<view class="normal-label text-center q-mt-sm">正在加载信息</view>
</view>
<view style="margin:10vh 0" class="text-center" v-else-if="lowData.length==0">
<u-empty mode="data"></u-empty>
</view>
<view v-else>
<view class="row q-mb-md" v-for="(x,i) in lowData" :key="i">
<image style="width: 160rpx;height: 200rpx;border-radius: 14rpx;"
:src="`https://pic.c-ctrip.com/flight/fuzzy/${x.ArrivalCityCode}/640.jpg`"
mode="aspectFill">
</image>
<view class="city-list-item col">
<view style="margin-bottom: 30rpx;" class=" row items-center">
<view class="bold col">{{x.DepartureName}} - {{x.ArrivalCityName}}</view>
<view class="row items-center">
<view style="font-size: 22rpx;">更多日期</view>
<u-icon name='arrow_right' custom-prefix="tffont" color="#00000066" size="28"></u-icon>
</view>
</view>
<view class="flights row items-center" v-for="(f,fi) in x.TicketProductList">
<image style="width: 30rpx;" :src="`https://static.tripcdn.com/packages/flight/airline-logo/latest/airline/48/${f.AlCode}.png`" mode="widthFix"></image>
<view style="font-size: 22rpx;width:50rpx;margin:0 4px">
{{ f.DayCount }}
</view>
<view style="font-size: 22rpx;">
{{ f.FlightDateString }}
</view>
<view class="q-ml-sm col" style="font-size: 22rpx;">
{{ f.ReturnDateString }}
</view>
<text style="color:#FF3166;font-size: 22rpx;">¥</text>
<view style="font-weight: bold;font-size: 24rpx;color:#FF3166">
{{ f.B2BPrice }}
</view>
<u-icon name='arrow_right' custom-prefix="tffont" color="#00000066" size="28"></u-icon>
</view>
<!-- <view class="row q-mt-md">
<view class="col">
<text style="font-size: 22rpx;color: #A4A4A4;font-weight: 400;">仅需:</text>
<text style="color:#FF3166;font-size: 22rpx;">¥</text>
<text
style="font-size: 32rpx;font-weight: bold;color:#FF3166;">{{x.Flight.B2BPrice}}</text>
</view>
<view class="more-date">更多日期</view>
</view> -->
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import ChosenCity from './components/address/ChosenCity.vue'
import DateRange from './components/time/DateRange.vue'
export default {
components:{ChosenCity,DateRange},
data() {
return {
dateRangeValue: {
type: 0,
startDate: '',
fuzzy: {
fuzzyType: 0,
fuzzyTypeName: '',
weeks: [],
dayRangStatus: false,
dayRange: [4, 10]
},
formatStartDate: '任意时间出发'
},
lowCity: {
type: 0,
ID: 0,
Name: '全部'
},
resetCityVisible:false,
dateVisible:false,
lowData: [],
chosenResult: [{
type: 0,
ID: 0,
Name: '全部低价城市'
},
{
type: 1,
ID: 0,
Name: '全部低价城市'
}
],
loadingLow: false,
parementers: {
StartDate: '',
EndDate: '',
DepartCityId: 0,
ArriveCityId: 0,
AirLineID: 0,
groupId: 2,
TicketType: 2,
StartPlayDay: 0,
EndPlayDay: 0,
WeekDay: ''
}
}
},
created() {
if (uni.getStorageSync('scf')) {
const s = uni.getStorageSync('scf')
this.dateRangeValue = s.dateRange
this.chosenResult = s.cities
}
this.chosenResult.sort((a, b) => {
return a.type - b.type;
});
this.getTopLowTicketHandle()
},
mounted() {
},
methods: {
resolveParameterHanle() {
this.resetParameterHandle()
this.parementers.DepartCityId = this.chosenResult[0].ID
this.parementers.ArriveCityId = this.chosenResult[1].ID
if (this.dateRangeValue.type == 1) {
this.parementers.StartDate = this.dateRangeValue.startDate
//this.parementers.EndDate = this.dateRangeValue.startDate
} else {
const d = this.resolveDateByFuzzyHandle(this.dateRangeValue.fuzzy.fuzzyType)
this.parementers.StartDate = d[0]
this.parementers.EndDate = d[1]
if (this.dateRangeValue.fuzzy.weeks.length > 0) {
const weeks = [2, 3, 4, 5, 6, 7, 1]
this.parementers.WeekDay = weeks.filter((x, i) => this.dateRangeValue.fuzzy.weeks.indexOf(i) != -1)
.join(',')
}
if (this.dateRangeValue.fuzzy.dayRangStatus) {
this.parementers.StartPlayDay = this.dateRangeValue.fuzzy.dayRange[0]
this.parementers.EndPlayDay = this.dateRangeValue.fuzzy.dayRange[1]
}
}
},
resetParameterHandle() {
this.parementers = {
StartDate: '',
EndDate: '',
DepartCityId: 0,
ArriveCityId: 0,
AirLineID: 0,
groupId: 2,
TicketType: 2,
StartPlayDay: 0,
EndPlayDay: 0,
WeekDay: ''
}
},
resolveDateByFuzzyHandle(t) {
if (t == 0) return ['', '']
let start = new Date();
let end = new Date()
if (t == -1) {
start.setTime(start.getTime() + 3600 * 1000 * 24 * 1);
end.setMonth(end.getMonth() + 1);
} else if (t == -2) {
start.setTime(start.getTime() + 3600 * 1000 * 24 * 1);
end.setMonth(end.getMonth() + 3)
} else {
const currentDate = new Date()
currentDate.setMonth(currentDate.getMonth() + t);
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
start = new Date(year, month, 1);
end = new Date(year, month + 1, 0);
}
return [this.formatDateHandle(start), this.formatDateHandle(end)]
},
formatDateHandle(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`
},
getTopLowTicketHandle() {
this.resolveParameterHanle()
this.lowData = []
this.loadingLow = true
this.apipost('AirTicket_get_GetTopTicketProduct', this.parementers, (r) => {
if (r.resultCode == 1) {
this.lowData = this.formatLowData(r.data)
console.log(this.lowData)
}
this.loadingLow = false
}, (e) => {
this.loadingLow = false
})
},
formatLowData(d) {
d.forEach((x) => {
x.TicketProductList.sort((a,b)=>{
return new Date(a.FlightDate) - new Date(b.FlightDate);
})
const newTicket = x.TicketProductList.splice(0,2)
newTicket.forEach((x)=>{
const q = x.FlightList.findLastIndex((f) => f.FlightType == 3)
x.ReturnDate = x.FlightList[q].FlightDate
x.DayCount = Math.abs((new Date(x.ReturnDate).getTime()-new Date(x.FlightDate).getTime())/(24*60*60*1000))
x.ReturnDateString = this.formatDateString(x.ReturnDate)
x.FlightDateString = this.formatDateString(x.FlightDate)
})
x.TicketProductList = newTicket
});
return d
},
formatDateString(d){
const date = new Date(d)
const weeks = ['日','一','二','三','四','五','六']
return `${String(date.getMonth()+1).padStart(2,'0')}.${String(date.getDate()).padStart(2,'0')}${weeks[date.getDay()]}` //周${weeks[date.getDay()]}
},
setNewCitiesHandle(val){
this.chosenResult = val
uni.setStorageSync('scf',{
cities:this.chosenResult,
dateRange: this.dateRangeValue
})
this.getTopLowTicketHandle()
this.resetCityVisible = false
},
setDateHandler(val){
this.dateRangeValue = val
this.dateVisible = false
uni.setStorageSync('scf',{
cities:this.chosenResult,
dateRange: this.dateRangeValue
})
this.getTopLowTicketHandle()
},
}
}
</script>
<style>
@import url('../../asset/css/flex.css');
.cities-list {
height: 100vh;
}
.search-box {
height: 200rpx;
width: 100%;
box-shadow: 0 0 26rpx #f1f1f1;
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638750707031350012.jpg');
background-size: 100% auto;
}
.search-box .cities-name {
font-weight: bold;
font-size: 32rpx;
color: #080A09;
}
.air-low-rank {
padding: 40rpx;
}
.air-low-rank .city-list-item {
margin-left: 18rpx;
}
.air-low-rank .city-list-item .bold{
font-weight: bold;
font-size: 32rpx !important;
color: #080A09;
}
.air-low-rank .city-list-item .flights{
background: #f1f1f1;
border-radius: 8rpx;
padding:10rpx;
margin-top: 10rpx;
}
.air-low-rank .city-list-item .info-text {
font-size: 24rpx;
color: #A4A4A4;
font-weight: 400;
}
</style>
\ No newline at end of file
<template>
<scroll-view scroll-y="true" style="height: 100vh;" @scroll="handleScroll">
<view class="nav-box row items-center" :style="{paddingTop:barHeight+'px',height:(barHeight+44)+'px',opacity:`${scrollTop}%`}">
<view class="home-btn row items-center" @click="goHomeHandle">
<u-icon name="home_1" custom-prefix="tffont" color="#111" size="38" style="font-weight: bolder;"></u-icon>
</view>
<view class="title">特价机票</view>
</view>
<view class="air-home">
<view class="air-home-header">
<view class="title-box">
<view class="active-item">
<view class="right"></view>
<text>特价机票</text>
</view>
</view>
<view class="air-tools">
<view class="row items-center" style="padding: 18rpx;padding-bottom: 10rpx;">
<text class="normal-label">出发地</text>
<text class="col text-center"></text>
<text class="normal-label">目的地</text>
</view>
<view class="row items-center toolsContainer"
style="border-bottom: 1px solid #EEF1F4;padding:0 18rpx; padding-bottom: 18rpx;">
<text class="cities-name left-name" @click="showCityHandler(0)" :style="{transform: `translateX(${leftPos}px)`,transition: 'transform 0.5s ease'}">{{chosenResult[0].Name}}</text>
<view class="col exchange row items-center justify-center" @click="swapLocations">
<u-icon name="change1" custom-prefix="tffont" color="#B99846" size="66" :style="{transform: `rotate(${rotateNum}deg)`,transition: 'transform 0.5s ease'}"></u-icon>
</view>
<text class="cities-name right-name" @click="showCityHandler(1)" :style="{transform: `translateX(${rightPos}px)`,transition: 'transform 0.5s ease'}">{{chosenResult[1].Name}}</text>
</view>
<view style="padding:36rpx 18rpx;" @click="showDateHandle">
<text style="font-size: 28rpx;color:#080A09;">{{dateRangeValue.formatStartDate}}</text>
</view>
<view @click="searchFlightHandle" class="text-center cities-name" style="background-color: #B99846;border-radius: 10rpx;height: 90rpx; line-height: 90rpx;color:#EEF1F4">查 询</view>
<u-popup mode="bottom" border-radius="20" :popup="false" v-model="citiesVisible" :maskCloseAble="true" length="auto" :safeAreaInsetBottom="true" @close="popupClose" :z-index="9999">
<city-list :city-type="currentCityObj.type" :city-value="currentCityObj.currentValue" @change="setCityHandler"></city-list>
</u-popup>
<u-popup mode="bottom" border-radius="20" :popup="false" v-model="dateVisible" :maskCloseAble="true" length="auto" :safeAreaInsetBottom="true" @close="popupClose" :z-index="9999">
<date-range :value="dateRangeValue" @change='setDateHandler'></date-range>
</u-popup>
</view>
</view>
<view class="air-low-rank">
<view class="header row items-center">
<view class="col">
<image style="width: 200rpx;" src="https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749544625808055.png" mode="widthFix"/>
</view>
<view class="city-box row items-center" @click="lowVisible=true">
<u-icon name="dingwei" custom-prefix="tffont" color="#D0A45C" size="25" ></u-icon>
<text class="col" style="margin-left:12rpx;margin-right: 16rpx;">{{ lowCity.Name}}</text>
<u-icon name="arrow-down" custom-prefix="tffont" color="#080A09" size="24" ></u-icon>
<u-popup mode="bottom" border-radius="20" :popup="false" v-model="lowVisible" :maskCloseAble="true" length="auto" :safeAreaInsetBottom="true" @close="popupClose" :z-index="9999">
<city-list :city-type="0" :city-value="lowCity.ID" @change="setLowCityHandler"></city-list>
</u-popup>
</view>
</view>
<view class="q-mt-md" style="padding:0 32rpx">
<view class="column items-center justify-center q-mb-md" v-if="loadingLow">
<u-loading mode="circle" size="40"></u-loading>
<view class="normal-label text-center q-mt-sm">正在加载信息</view>
</view>
<view style="margin-top:10vh" class="text-center" v-else-if="lowData.length==0">
<u-empty mode="data"></u-empty>
</view>
<view v-else>
<view class="row items-center q-mb-md" v-for="(x,i) in lowData" :key="i">
<image style="width: 160rpx;height: 186rpx;border-radius: 14rpx;" :src="`https://pic.c-ctrip.com/flight/fuzzy/${x.ArrivalCityCode}/640.jpg`" mode="aspectFill"></image>
<view class="city-list-item col">
<view class="row">
<view class="col">
<view class="">{{x.DepartureName}} - {{x.ArrivalCityName}}</view>
<view class="row items-center q-mt-sm">
<image style="width: 30rpx;" :src="`https://static.tripcdn.com/packages/flight/airline-logo/latest/airline/48/${x.Flight.AlCode}.png`" mode="widthFix"></image>
<view class="info-text q-ml-sm">
<text>{{ x.Flight.AirLineName }}</text>
<text class="q-ml-md">{{x.Flight.FlightDate.split('-').splice(0,1).join('-')}} {{x.Flight.WeekDayStr}}</text>
</view>
</view>
</view>
<view class="rank" :class="{'num1':i==0,'num2':i==1,'num3':i==2}">{{i+1}}</view>
</view>
<view class="row q-mt-md">
<view class="col">
<text style="font-size: 22rpx;color: #A4A4A4;font-weight: 400;">仅需:</text>
<text style="color:#FF3166;font-size: 22rpx;">¥</text>
<text style="font-size: 32rpx;font-weight: bold;color:#FF3166;">{{x.Flight.B2BPrice}}</text>
</view>
<view class="more-date">更多日期</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script>
import CityList from './components/address/CityList.vue';
import DateRange from './components/time/DateRange.vue';
export default {
data() {
return {
leftPos: 0,
rightPos: 0,
leftStyle:{
transform: `translateX(0px)`,
transition: 'transform 0.5s ease',
},
rightStyle:{
transform: `translateX(0px)`,
transition: 'transform 0.5s ease',
},
scrollTop:0,
containerWidth:0,
leftWidth:0,
rightWidth:0,
rotateNum:0,
citiesVisible:false,
lowVisible:false,
dateVisible:false,
loadingLow:false,
dateRangeValue:{
type:0,
startDate:'',
fuzzy:{
fuzzyType:0,
fuzzyTypeName:'',
weeks:[],
dayRangStatus:false,
dayRange:[4,10]
},
formatStartDate:'任意时间出发'
},
currentCityObj:{
type:0,
ID:0
},
lowCity:{
type:0,
ID:0,
Name:'全部'
},
lowData:[],
chosenResult:[
{
type:0,
ID:0,
Name:'全部低价城市'
},
{
type:1,
ID:0,
Name:'全部低价城市'
}
],
barHeight:0
}
},
components:{CityList,DateRange},
created() {
if(uni.getStorageSync("tdr")){
this.dateRangeValue = uni.getStorageSync("tdr")
}
this.getTopLowTicketHandle()
},
mounted() {
this.getContainerWidth()
let that = this
uni.getSystemInfo({
success(r) {
that.barHeight = r.statusBarHeight
},
});
},
methods: {
searchFlightHandle(){
if(this.chosenResult[0].ID==0||this.chosenResult[1].ID==0){
uni.setStorageSync('scf',{
cities:this.chosenResult,
dateRange: this.dateRangeValue
})
uni.navigateTo({
url:'/pages/airTicket/airCity'
})
}else{
//查询航班列表
}
},
async getContainerWidth() {
const width = await this.getDomWidthAsync(".toolsContainer");
this.containerWidth = width-18;
},
getTopLowTicketHandle(){
const msg={
StartDate:'',
EndDate:'',
DepartCityId:this.lowCity.ID,
ArriveCityId:0,
AirLineID:0,
groupId:2,
TicketType:2,
}
this.lowData = []
this.loadingLow = true
this.apipost('AirTicket_get_GetTopTicketProduct',msg,(r)=>{
if(r.resultCode==1){
this.lowData = this.formatLowData(r.data)
}
this.loadingLow=false
},(e)=>{
this.loadingLow = false
})
},
formatLowData(d){
d.forEach((x)=>{
let t = x.TicketProductList[0]
if(t){
const q = t.FlightList.findLastIndex((f)=>f.FlightType==1)
t.FlightInfo = t.FlightList[q]
}
x.Flight = t
});
return d.filter((x,i)=> i<10)
},
getDomWidthAsync(className) {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
.select(className) // 选择父容器
.boundingClientRect((rect) => {
if (rect) {
resolve(rect.width); // 获取宽度并返回
} else {
reject('Failed to get container width'); // 获取失败时返回错误
}
})
.exec();
});
},
async swapLocations() {
this.leftWidth = await this.getDomWidthAsync('.left-name')
this.rightWidth = await this.getDomWidthAsync('.right-name')
this.leftPos = this.leftPos === 0 ? (this.containerWidth-this.leftWidth) : 0;
this.rightPos = this.rightPos === 0 ? -(this.containerWidth-this.rightWidth) : 0;
this.chosenResult[0].type = this.leftPos === 0 ? 0 : 1
this.chosenResult[1].type = this.rightPos === 0 ? 1 : 0
this.rotateNum += 180
this.leftStyle = {
transform: `translateX(${this.leftPos}px)`,
transition: 'transform 0.5s ease',
}
this.rightStyle ={
transform: `translateX(${this.rightPos}px)`,
transition: 'transform 0.5s ease',
}
},
popupClose(){
this.citiesVisible = false
this.dateVisible = false
this.lowVisible = false
},
showCityHandler(i){
this.currentCityObj = this.chosenResult[i]
this.citiesVisible = true
},
showDateHandle(){
this.dateVisible=true
},
setCityHandler(val){
const i = this.chosenResult.findIndex((x)=>x.type==this.currentCityObj.type)
this.chosenResult[i] = {...this.currentCityObj,...val}
this.citiesVisible = false
},
setLowCityHandler(val){
this.lowCity = val
this.lowVisible = false
this.getTopLowTicketHandle()
},
setDateHandler(val){
this.dateRangeValue = val
//uni.setStorageSync('tdr',val)
this.dateVisible = false
},
handleScroll(val){
this.scrollTop = val.target.scrollTop
},
goHomeHandle(){
uni.redirectTo({
url:'/pages/index/index'
})
}
}
}
</script>
<style>
@import url("../../asset/css/flex.css");
.nav-box{
height: 44px; /* 这里的44px是示例高度,可根据实际设计调整 */
line-height: 44px;
background-color: #FFF;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: 2;
padding-left: 30rpx;
}
.nav-box .home-btn{
width: 60rpx;
height:60rpx;
border-radius: 60rpx;
border:2rpx solid #ccc;
display: flex;
align-items: center;
justify-content: center;
}
.nav-box .title{
font-weight: bold;
font-size: 32rpx;
color: #080A09;
text-align: center;
width: calc(100vw - 140px);
}
.nav-box .home-btn:active{
background-color: #ccc;
}
.air-home {
background-color: #F3F1EF;
min-height: 100vh;
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/uploads/versions/flight.png');
background-repeat: no-repeat;
background-size: 100% auto;
padding: 0 30rpx;
padding-top: 257rpx;
padding-bottom: 36rpx;
}
.air-low-rank{
margin-top: 36rpx;
border-radius: 16rpx;
padding: 4rpx;
background-color: #FFF;
}
.air-low-rank .header{
height: 115rpx;
background: linear-gradient(180deg, #F8EFDD, #FEFEFD);
border-radius: 16rpx 16rpx 0rpx 0rpx;
padding: 0 28rpx;
}
.air-low-rank .header .city-box{
background-color: rgba(185, 152, 70, 0.3);
height: 42rpx;
border-radius: 21rpx;
padding: 0 14rpx;
font-weight: bold;
font-size: 28rpx;
color: #080A09;
}
.air-low-rank .city-list-item{
font-weight: bold;
font-size: 32rpx;
color: #080A09;
margin-left: 18rpx;
}
.air-low-rank .city-list-item .info-text{
font-size: 24rpx;
color: #A4A4A4;
font-weight: 400;
}
.air-low-rank .city-list-item .rank{
width: 50rpx;
height: 62rpx;
line-height: 56rpx;
text-align: center;
font-size: 30rpx;
font-weight: 500;
color: #080A09AA;
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749695490481809.png');
background-size: 100% 100%;
}
.air-low-rank .city-list-item .rank.num1{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749695490598524.png');
color: #FFFFFFAA;
}
.air-low-rank .city-list-item .rank.num2{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749695489392934.png');
color: #FFFFFFAA;
}
.air-low-rank .city-list-item .rank.num3{
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749695489378365.png');
color: #FFFFFFAA;
}
.air-low-rank .city-list-item .more-date{
padding: 12rpx;
border-radius: 29rpx;
background-color: #f1f1f1;
color: #000000DD;
font-size: 22rpx;
font-weight: 400;
}
.air-home-header {
border-radius: 18rpx;
background-color: #FFF;
}
.air-home-header .title-box {
height: 79rpx;
background: #FAF0DB;
position: relative;
border-top-right-radius: 18rpx;
}
.air-home-header .title-box .active-item {
background-color: #fff;
position: absolute;
padding-top: 30rpx;
border-top-right-radius: 18rpx;
border-top-left-radius: 18rpx;
top: -10rpx;
width: 310rpx;
bottom: 0;
left: 0;
text-align: center;
font-weight: bold;
font-size: 32rpx;
color: #B99846;
}
.air-home-header .title-box .active-item .right {
width: 75rpx;
height: 89rpx;
position: absolute;
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638745412034709773.png');
background-size: 100% 100%;
right: -62rpx;
top: 0;
}
.air-home .air-home-header .air-tools {
padding: 36rpx;
}
.air-tools .cities-name {
font-weight: bold;
font-size: 32rpx;
color: #080A09;
}
.air-tools .normal-label {
font-size: 24rpx;
color: #9999A6;
}
.air-tools .exchange {
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1696990221000_663.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 38rpx auto;
/* 这里设置背景图片的宽度和高度 */
height: 30px;
}
</style>
\ No newline at end of file
<template>
<view style="height: 100vh; z-index:2; width:100vw;position: fixed;left: 0;top: 0;background-color: rgba(0, 0, 0, 0.6);">
<view style="z-index: 2; background-color: #FFF;padding-bottom:36rpx;position: absolute;top: 0;left: 0;right: 0;border-radius: 0 0 16rpx 16rpx;">
<view style="padding:24rpx;font-size: 40rpx;font-weight: bold;color: #121212;" class="text-center">
重新选择
</view>
<view class="air-tools">
<view class="row items-center" style="padding: 18rpx;padding-bottom: 10rpx;">
<text class="normal-label">出发地</text>
<text class="col text-center"></text>
<text class="normal-label">目的地</text>
</view>
<view class="row items-center toolsContainer"
style="padding:0 18rpx; padding-bottom: 18rpx;">
<text class="cities-name left-name" @click="showCityHandler(0)" :style="{transform: `translateX(${leftPos}px)`,transition: 'transform 0.5s ease'}">{{chosenResult[0].Name}}</text>
<view class="col exchange row items-center justify-center" @click="swapLocations">
<u-icon name="change1" custom-prefix="tffont" color="#B99846" size="66" :style="{transform: `rotate(${rotateNum}deg)`,transition: 'transform 0.5s ease'}"></u-icon>
</view>
<text class="cities-name right-name" @click="showCityHandler(1)" :style="{transform: `translateX(${rightPos}px)`,transition: 'transform 0.5s ease'}">{{chosenResult[1].Name}}</text>
</view>
<view @click="setCitiesHandler" class="text-center cities-name q-mt-md" style="background-color: #B99846;border-radius: 10rpx;height: 90rpx; line-height: 90rpx;color:#EEF1F4">确 认</view>
<u-popup mode="bottom" border-radius="20" :popup="false" v-model="citiesVisible" :maskCloseAble="true" length="auto" :safeAreaInsetBottom="true" @close="popupClose" :z-index="9999">
<city-list :city-type="currentCityObj.type" :city-value="currentCityObj.ID" @change="setCityHandler"></city-list>
</u-popup>
</view>
<view style="height: 100%;width: 100%;" @click="closed"></view>
</view>
</view>
</template>
<script>
import CityList from './CityList.vue';
export default {
components:{CityList},
props:{
value:{
type:Array
}
},
data(){
return {
chosenResult:[
{
type:0,
ID:0,
Name:'全部低价城市'
},
{
type:1,
ID:0,
Name:'全部低价城市'
}
],
currentCityObj:{
type:0,
ID:0
},
leftPos: 0,
rightPos: 0,
leftStyle:{
transform: `translateX(0px)`,
transition: 'transform 0.5s ease',
},
rightStyle:{
transform: `translateX(0px)`,
transition: 'transform 0.5s ease',
},
scrollTop:0,
containerWidth:336,
leftWidth:0,
rightWidth:0,
rotateNum:0,
citiesVisible:false,
}
},
watch: {
value: {
deep: true,
immediate: true,
handler: function(newVal, oldVal) {
if(this.value){
this.chosenResult = this.value
}
},
},
},
created() {
if(this.value){
this.chosenResult = this.value
}
},
mounted() {
//this.getContainerWidth()
},
methods:{
swapLocations() {
this.leftPos = this.leftPos === 0 ? (this.containerWidth-(this.chosenResult[0].Name.length*16)) : 0;
this.rightPos = this.rightPos === 0 ? -(this.containerWidth-(this.chosenResult[1].Name.length*16)) : 0;
this.chosenResult[0].type = this.leftPos === 0 ? 0 : 1
this.chosenResult[1].type = this.rightPos === 0 ? 1 : 0
this.rotateNum += 180
},
popupClose(){
this.citiesVisible = false
},
showCityHandler(i){
console.log(this.chosenResult[i])
this.currentCityObj = this.chosenResult[i]
this.citiesVisible = true
},
setCityHandler(val){
const i = this.chosenResult.findIndex((x)=>x.type==this.currentCityObj.type)
this.chosenResult[i] = {...this.currentCityObj,...val}
this.citiesVisible = false
},
setCitiesHandler(){
const result = JSON.parse(JSON.stringify(this.chosenResult))
result.sort((a, b) => {
return a.type - b.type;
});
this.$emit('change',result)
},
closed(){
this.$emit('closed')
}
}
}
</script>
<style>
@import url('../../../../asset/css/flex.css');
.air-tools {
padding:0 36rpx;
}
.air-tools .cities-name {
font-weight: bold;
font-size: 32rpx;
color: #080A09;
}
.air-tools .normal-label {
font-size: 24rpx;
color: #9999A6;
}
.air-tools .exchange {
background-image: url('https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1696990221000_663.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 38rpx auto;
/* 这里设置背景图片的宽度和高度 */
height: 30px;
}
</style>
\ No newline at end of file
<template>
<view style="padding: 36rpx;">
<view class="cities-name text-center">选择{{cityType==0?'出发地':'目的地'}}</view>
<view style="padding: 36rpx 0;">
<input type="text" class="text-input" v-model="keywords" placeholder="填写城市名称快速筛选" placeholder-style="font-size:12px" />
</view>
<template v-if="!loading">
<view class="cities-name" style="font-size: 28rpx;">{{ keywords==''?'热门城市':'搜索结果' }}</view>
<template v-if="showAddress && showAddress.length>0">
<scroll-view scroll-y="true" style="height: 55vh;">
<div class="row q-mt-md">
<view class="col city-item big q-mr-sm" v-if="keywords==''" :class="{'active':currentValue==0}" @click="setCurrentCity(0)">
<u-icon name="ditu-xian" custom-prefix="tffont" size="66" v-if="showAddress.length>2"></u-icon>
<view style="font-size: 28rpx;font-weight: 600;">全部低价城市</view>
</view>
<view class="col row flex-grid flex-wrap">
<template v-for="(x,i) in showAddress">
<view class="city-item " v-if="i<4" :key="i" @click="setCurrentCity(x.ID)" :class="{'active':currentValue==x.ID,'col-t-b':keywords=='','col-f-b':keywords!=''}">{{x.Name}}</view>
</template>
</view>
</div>
<div class="row flex-grid flex-wrap q-mt-sm">
<template v-for="(x,i) in showAddress">
<view class="city-item col-f-b" v-if="i>3" :key="i" @click="setCurrentCity(x.ID)" :class="{'active':currentValue==x.ID}">{{x.Name}}</view>
</template>
</div>
</scroll-view>
<view class="text-center q-mt-md" @click="setHandler" style="background-color: #B99846;border-radius: 10rpx;height: 90rpx; line-height: 90rpx;color:#EEF1F4">确认选择</view>
</template>
<view style="margin-top:10vh" class="text-center" v-else>
<u-empty mode="data"></u-empty>
</view>
</template>
<view style="margin-top:20vh" class="text-center" v-else>
<u-loading mode="circle" size="60"></u-loading>
<view class="normal-label text-center q-mt-sm">正在加载城市信息</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
addressList:[],
orgList:[],
keywords:'',
currentValue:0,
loading:false,
currentObj:{}
}
},
props:{
cityType:{
type:Number,
default:0
},
cityValue:{
type:Number,
default:0
}
},
created() {
this.getDataAddressList()
this.currentValue = this.cityValue
},
computed:{
showAddress(){
return this.addressList.filter((x)=>x.Name.indexOf(this.keywords)!=-1)
}
},
watch: {
cityType: {
deep: true,
immediate: true,
handler: function(newVal, oldVal) {
this.formatArray()
},
},
cityValue: {
deep: true,
immediate: true,
handler: function(newVal, oldVal) {
this.currentValue = this.cityValue,
this.mappingCityInfoHandler(this.cityValue)
},
}
},
methods: {
getDataAddressList(){
this.loading=true
const params = {
StartDate: '',
EndDate: '',
ArriveCityId: '0',
DepartCityId: '0',
AirLineID:'',
TicketType:2,//1单程 2往返
}
this.apipost("AirTicket_get_GetTicketCity",params,(res) => {
if(res.resultCode==1){
this.orgList = res.data
this.formatArray()
}
this.loading=false
},(e)=>{
this.loading=false
});
},
formatArray(){
if(!this.orgList || this.orgList.length==0) return
if(this.cityType==0) this.addressList = this.orgList
else{
let temp =[]
this.orgList.forEach(x=>{
const arr = x.CityList.map((y)=> ({ID:y.ID,Name:y.Name}))
temp = temp.concat(arr)
})
this.addressList = temp.filter((x,i)=>temp.findIndex(t=>t.ID===x.ID) === i)
}
},
setCurrentCity (id) {
this.currentValue = id
this.mappingCityInfoHandler(id)
},
mappingCityInfoHandler(id){
if(id==0){
this.currentObj = {
ID:id,
Name:'全部低价城市'
}
} else {
const i = this.showAddress.findIndex(x=>x.ID===id)
if(i>=0){
this.currentObj = {
ID:id,
Name:this.showAddress[i].Name
}
}
}
},
setHandler(){
this.$emit('change',this.currentObj)
}
}
}
</script>
<style>
.city-item{
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
font-weight: 400;
color: #000;
background-color: #f1f2f1;
text-align: center;
border-radius: 16rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}
.city-item.active{
font-size: 30rpx;
font-weight: bold;
color: #f1f2f1;
background-color: #000;
}
.city-item.big{
height: auto;
line-height: auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #B99846;
line-height: 1;
}
</style>
//节假日信息
const day=[
//节假日信息
const day =[
{
"year": 2025,
"month": 1,
"festival": [
{"name": "元旦", "day": 1},
{"name": "班", "day": 2},
{"name": "班", "day": 26},
{"name": "除夕", "day": 28},
{"name": "春节", "day": 29},
{"name": "休", "day": 30},
{"name": "休", "day": 31}
]
},
{
year:2022,
month:1,
festival:[
{name:'元旦',day:1},
{name:'除夕',day:31},
]
"year": 2025,
"month": 2,
"festival": [
{"name": "休", "day": 1},
{"name": "休", "day": 2},
{"name": "休", "day": 3},
{"name": "休", "day": 4},
{"name": "班", "day": 8},
{"name": "元宵", "day": 12}
]
},
{
year:2022,
month:2,
festival:[
{name:'春节',day:1},
{name:'元宵',day:15},
{name:'情人节',day:14}
]
"year": 2025,
"month": 3,
"festival": [
{"name": "妇女节", "day": 8}
]
},
{
"year": 2025,
"month": 4,
"festival": [
{"name": "清明节", "day": 4},
{"name": "班", "day": 27},
]
},
{
"year": 2025,
"month": 5,
"festival": [
{"name": "劳动节", "day": 1},
{"name": "休", "day": 2},
{"name": "休", "day": 3},
{"name": "休", "day": 4},
{"name": "休", "day": 5},
{"name": "端午", "day": 31}
]
},
{
year:2022,
month:3,
festival:[
{name:'妇女节',day:8},
]
"year": 2025,
"month": 6,
"festival": [
{"name": "儿童节", "day": 1},
{"name": "休", "day": 2}
]
},
{
year:2022,
month:4,
festival:[
{name:'清明',day:4},
]
"year": 2025,
"month": 8,
"festival": [
{"name": "七夕", "day": 29},
]
},
{
year:2022,
month:5,
festival:[
{name:'劳动节',day:1},
{name:'青年节',day:4},
{name:'母亲节',day:10},
]
},
{
year:2021,
month:6,
festival:[
{name:'儿童节',day:1},
{name:'父亲节',day:21},
{name:'端午',day:14},
]
},
{
year:2021,
month:7,
festival:[
{name:'建党节',day:1}
]
},
{
year:2021,
month:8,
festival:[
{name:'建军节',day:1},
{name:'七夕',day:14},
{name:'中元节',day:22},
]
},
{
year:2021,
month:9,
festival:[
{name:'教师节',day:10},
{name:'中秋',day:21}
]
},
{
year:2021,
month:10,
festival:[
{name:'国庆',day:1},
{name:'重阳',day:14}
]
},
{
year:2021,
month:11,
festival:[
{name:'感恩节',day:26}
]
},
{
year:2021,
month:12,
festival:[
{name:'平安夜',day:24},
{name:'圣诞节',day:25}
]
}
]
{
"year": 2025,
"month": 9,
"festival": [
{"name": "班", "day": 28},
]
},
{
"year": 2025,
"month": 10,
"festival": [
{"name": "国庆", "day": 1},
{"name": "休", "day": 2},
{"name": "休", "day": 3},
{"name": "休", "day": 4},
{"name": "休", "day": 5},
{"name": "中秋", "day": 6},
{"name": "休", "day": 7},
{"name": "休", "day": 8},
{"name": "班", "day": 11},
{"name": "重阳", "day": 29}
]
}
]
export default day
\ No newline at end of file
<template>
<view class="calendar-container">
<view class="week-header">
<view v-for="(day, index) in weekDays" :key="index" class="week-day">{{ day }}</view>
</view>
<scroll-view scroll-y class="col" style="width:100%;height: 1px;">
<view v-for="(month, monthIndex) in calendarData" :key="monthIndex" class="month-container">
<view class="month-title">{{ month.year }}{{ month.month }}</view>
<view class="days-grid">
<view
v-for="(dayOffset, index) in month.firstDayOffset"
:key="`offset-${index}`"
class="day-placeholder"
></view>
<view
v-for="(day, dayIndex) in month.days"
:key="dayIndex"
class="day-item"
:class="[
{ 'selected': isSelected(monthIndex, dayIndex) },
{ 'disabled': isDisabled(monthIndex, dayIndex) },
{ 'festival': isFestivalDay(month, day) }
]"
@tap="handleDaySelect(monthIndex, dayIndex)"
>
<view class="day-number">{{ day }}</view>
<view v-if="calendarFestivals[`${month.year}${month.month}${day}`]" class="day-status">{{calendarFestivals[`${month.year}${month.month}${day}`]}}</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import festivals from "./day.js";
export default {
props: {
defaultDate: {
type: String,
default: ""
}
},
data() {
return {
weekDays: ['日', '一', '二', '三', '四', '五', '六'],
calendarData: [],
selectedDate: [],
screenWidth: 375,
calendarFestivals:{}
};
},
computed: {
// 计算当前日期信息
currentDate() {
const date = new Date();
return {
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate()
};
}
},
mounted() {
this.initializeCalendar();
},
methods: {
initializeCalendar() {
this.getScreenWidth();
this.generateCalendarData();
this.setInitialSelection();
this.applyFestivalData()
},
getScreenWidth() {
uni.getSystemInfo({
success: (res) => {
this.screenWidth = res.windowWidth;
}
});
},
generateCalendarData() {
let year = this.currentDate.year;
let month = this.currentDate.month;
this.calendarData = Array.from({ length: 6 }, (_, index) => {
const monthData = this.createMonthData(year, month + index);
return monthData;
});
},
createMonthData(year, month) {
// 处理月份超过 12 的情况
const actualMonth = month > 12 ? month % 12 : month;
return {
// 如果月份超过 12,年份加 1
year: month > 12 ? year + 1 : year,
// 实际月份
month: actualMonth,
// 该月的天数数组
days: Array.from({ length: new Date(year, actualMonth, 0).getDate() }, (_, index) => index + 1),
// 该月第一天是星期几
firstDayOffset: new Date(`${year}/${actualMonth}/1`).getDay()
};
},
applyFestivalData(monthData) {
this.calendarFestivals = festivals.reduce((acc, { year, month, festival }) => {
const subResult = festival.map(({ name, day }) => {
const key = `${year}${month}${day}`;
return { [key]: name };
});
return {...acc,...Object.assign({},...subResult) };
}, {})
},
setInitialSelection() {
if (this.defaultDate) {
const [year, month, day] = this.defaultDate.split('-');
const monthIndex = this.calendarData.findIndex(m =>
m.year == year && m.month == month
);
if (monthIndex !== -1) {
this.selectedDate = [monthIndex, parseInt(day) - 1];
}
}
},
isSelected(monthIndex, dayIndex) {
return this.selectedDate[0] === monthIndex &&
this.selectedDate[1] === dayIndex;
},
isDisabled(monthIndex, dayIndex) {
if (monthIndex === 0) {
return dayIndex + 1 < this.currentDate.day;
}
return false;
},
isFestivalDay(month, day) {
return typeof day === 'string';
},
formatDayDisplay(day) {
return typeof day === 'string' ? day : day + 1;
},
handleDaySelect(monthIndex, dayIndex) {
if (this.isDisabled(monthIndex, dayIndex)) return;
this.selectedDate = [monthIndex, dayIndex];
this.$emit('selected', this.getSelectedDateString());
},
getSelectedDateString() {
const monthData = this.calendarData[this.selectedDate[0]];
const dayNumber = this.selectedDate[1] + 1;
return `${monthData.year}-${String(monthData.month).padStart(2,'0')}-${String(dayNumber).padStart(2,'0')}`;
}
}
};
</script>
<style>
.calendar-container {
height: 100%;
display: flex;
flex-direction: column;
}
.week-header {
display: flex;
justify-content: space-around;
padding: 10px 0;
background: #f8f8f8;
border-radius: 16rpx;
}
.week-day {
width: 14.28%;
text-align: center;
font-size: 14px;
color: #333;
}
.month-container {
padding: 15px 10px;
}
.month-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
padding-left: 10px;
}
.days-grid {
display: flex;
flex-wrap: wrap;
}
.day-item, .day-placeholder {
width: 14.28%;
min-height: 50px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 5px 0;
}
.day-item {
position: relative;
font-size: 14px;
color: #333;
background: #fff;
}
.day-placeholder {
visibility: hidden;
}
.day-item.selected {
background: #DEBF7B;
color: #fff;
border-radius: 4px;
}
.day-item.disabled {
color: #ccc;
}
.day-item.festival {
color: #d32f2f;
font-size: 12px;
}
.day-status {
font-size: 10px;
color: #d32f2f;
position: absolute;
bottom: 2px;
}
</style>
\ No newline at end of file
<template>
<view style="padding:36rpx;height: 80vh;padding-bottom: 0;" class="column">
<view class="col column" style="width:100%;height: 1px;">
<view class="cities-name text-center full-width">选择出发日期</view>
<view class="q-mt-md q-mb-md full-width">
<u-tabs :list="chosenTypes" :show-bar="false" :is-scroll="false" :current="dateRangeValue.type" :active-color='mainColor' @change="changeTypeHandler"></u-tabs>
</view>
<view class="col full-width" v-if="dateRangeValue.type==0">
<view class="cities-name" style="font-size: 28rpx;">出发时间</view>
<div class="row flex-grid flex-wrap q-mt-md">
<view class="date-item col-f-b" v-for="(x,i) in fuzzyTypeList" @click="setFuzzyTypeHandle(x)" :key="i" :class="{'active':dateRangeValue.fuzzy.fuzzyType==x.Id}">{{x.Name}}</view>
</div>
<view class="cities-name" style="font-size: 28rpx;margin-top: 72rpx;">周几出发</view>
<div class="row items-center q-mt-md">
<view class="date-item col q-mr-sm" v-for="(x,i) in weeks" :key="i" @click="setWeekHandler(i)" :class="{'active':dateRangeValue.fuzzy.weeks.indexOf(i)!=-1}">{{x}}</view>
</div>
<view class="row items-center" style="margin-top: 72rpx;">
<view class="col">
<text class="cities-name" style="font-size: 28rpx;margin-top: 72rpx;">出行天数</text>
<text class="q-ml-md cities-name" style="font-size: 28rpx;">({{ dateRangeValue.fuzzy.dayRange.join(' - ') }}) 天</text>
</view>
<view class="">
<u-switch v-model="dateRangeValue.fuzzy.dayRangStatus" :active-color="mainColor" :size="30"></u-switch>
</view>
</view>
<view class="q-mt-md" v-show="dateRangeValue.fuzzy.dayRangStatus">
<llt-slider-range :model-value="dateRangeValue.fuzzy.dayRange" @change="handleDayRangChange" :blockSize="24" :active-color="mainColor" :min="1" :max="15" />
</view>
</view>
<view class="col" style="height: 1px;" v-else>
<date-sign :default-date="dateRangeValue.startDate" @selected="handleSelectedDate"></date-sign>
</view>
</view>
<view class="text-center q-mt-md" @click="setValueHandler" style="background-color: #B99846;border-radius: 10rpx;height: 90rpx; line-height: 90rpx;color:#EEF1F4">确认选择</view>
</view>
</template>
<script>
import DateSign from '../sign/sign.vue';
export default {
data(){
return {
dateRangeValue:{
type:0,
startDate:'',
fuzzy:{
fuzzyType:0,
fuzzyTypeName:'',
weeks:[],
dayRangStatus:false,
dayRange:[4,10]
}
},
mainColor:'',
chosenTypes:[
{name:'模糊日期'},
{name:'精确日期'}
],
fuzzyTypeList:[
{Id:-1,Name:'未来一个月'},
{Id:-2,Name:'未来三个月'}
],
weeks:['一','二','三','四','五','六','日']
}
},
components:{DateSign},
props:{
value:{
type:Object,
default:null
}
},
created() {
this.mainColor = this.$uiConfig.mainColor;
this.fillMonthType()
if(this.value){
this.dateRangeValue = this.value
}
console.log(this.value)
},
watch:{
value: {
deep: true,
immediate: true,
handler: function(newVal, oldVal) {
if(this.value){
this.dateRangeValue = this.value
}else{
this.dateRangeValue = {
type:0,
range:['',''],
fuzzy:{
fuzzyType:0,
fuzzyTypeName:'',
weeks:[],
dayRangStatus:false,
dayRange:[4,10]
}
}
}
},
},
},
methods:{
changeTypeHandler(index){
this.dateRangeValue.type = index
},
fillMonthType(){
let date = new Date()
for (var i = 0; i < 4; i++) {
date.setMonth(date.getMonth() + (i==0?0:1));
const month = date.getMonth()+1
this.fuzzyTypeList.push({
Id:i+1,
Name:`${month}月`
})
}
},
setWeekHandler(i){
const t = this.dateRangeValue.fuzzy.weeks.indexOf(i)
if(t==-1) this.dateRangeValue.fuzzy.weeks.push(i)
else this.dateRangeValue.fuzzy.weeks.splice(t,1)
},
handleDayRangChange(val){
this.dateRangeValue.fuzzy.dayRange = val
},
setValueHandler(){
this.resolveDateFormatHandle()
this.$emit('change',this.dateRangeValue)
},
setFuzzyTypeHandle(item){
this.dateRangeValue.fuzzy.fuzzyType=item.Id
this.dateRangeValue.fuzzy.fuzzyTypeName=item.Name
},
handleSelectedDate(val){
this.dateRangeValue.startDate = val
},
resolveDateFormatHandle(){
console.log(this.dateRangeValue.fuzzy)
if(this.dateRangeValue.type==1){
if(this.dateRangeValue.startDate==''){
this.dateRangeValue.formatStartDate='任意时间出发'
}else{
this.dateRangeValue.formatStartDate=`${this.dateRangeValue.startDate} ${this.$utils.getWeekName(this.dateRangeValue.startDate)}`
}
}else{
let val = ''
if(this.dateRangeValue.fuzzy.fuzzyType!=0){
val = this.dateRangeValue.fuzzy.fuzzyTypeName
}
if(this.dateRangeValue.fuzzy.weeks && this.dateRangeValue.fuzzy.weeks.length>0){
const w = this.weeks.filter((x,i)=>this.dateRangeValue.fuzzy.weeks.indexOf(i)!=-1).map((x,i)=>{
return `周${x}`
})
val += (val==''?'':' | ')+w.join(',')
}
if(this.dateRangeValue.fuzzy.dayRangStatus){
val += (val==''?'':' | ')+this.dateRangeValue.fuzzy.dayRange.join('-')+'天'
}
this.dateRangeValue.formatStartDate = val==''?'任意时间出发':val
}
}
}
}
</script>
<style>
.full-width{
width: 100%;
}
.date-item{
height: 80rpx;
line-height: 80rpx;
font-size: 28rpx;
font-weight: 400;
color: #000;
background-color: #f1f2f1;
text-align: center;
border-radius: 16rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: clip;
}
.date-item.active{
font-size: 30rpx;
/* font-weight: bold; */
color: #f1f2f1;
background-color: #000;
}
</style>
\ No newline at end of file
<template>
<view style="height: 100vh;" class="column">
<view class="nav-box row items-center" :style="{paddingTop:barHeight+'px',height:(barHeight+44)+'px'}">
<view class="home-btn row items-center" @click="goBackHandle">
<u-icon name="arrowleft" custom-prefix="tffont" color="#111" size="34"
style="font-weight: bolder;"></u-icon>
</view>
<view class="home-btn row items-center" @click="goHomeHandle">
<u-icon name="home_1" custom-prefix="tffont" color="#111" size="38"
style="font-weight: bolder;"></u-icon>
</view>
<view class="title row items-center justify-center">
<text>{{chosenResult[0].Name}}</text>
<view style="margin:0 20rpx">
<u-icon name="wangfan1" custom-prefix="tffont" color="#00000066" size="36"></u-icon>
</view>
<text>{{chosenResult[1].Name}}</text>
<u-icon class="q-ml-sm" name='arrow_down' custom-prefix="tffont" color="#00000066" size="32"></u-icon>
</view>
</view>
<view class="column items-center justify-center q-mb-md" v-if="loadingLow">
<u-loading mode="circle" size="40"></u-loading>
<view class="normal-label text-center q-mt-sm">正在加载信息</view>
</view>
<view style="margin:10vh 0" class="text-center" v-else-if="lowData.length==0">
<u-empty mode="data"></u-empty>
</view>
<template v-else>
<view class="time-list row" style="padding:0 12rpx;">
<scroll-view scroll-x class="col" style="padding:12rpx 0;">
<view class="row items-center">
<view class="time-item" @click="setCurrentDateHandle"
:class="{'active':currentDate==x.FlightDate}" v-for="(x,i) in lowHeadData" :key="i">
<view class="">{{x.FlightDate.split('-').splice(1,2).join('-')}}</view>
<view style="margin: 10rpx 0;">{{x.WeekDayStr}}</view>
<view class="">¥{{x.B2BPrice}}</view>
</view>
</view>
</scroll-view>
<view class="time-item" style="box-shadow: -1 0 10px #00000066;">
<u-icon class="q-ml-sm" name='arrow_down' custom-prefix="tffont" color="#00000066" size="32"></u-icon>
</view>
</view>
</template>
</view>
</template>
<script>
export default {
data() {
return {
barHeight: 0,
currentDate: '',
lowData: [],
lowHeadData: [],
dateRangeValue: {
type: 0,
startDate: '',
fuzzy: {
fuzzyType: 0,
fuzzyTypeName: '',
weeks: [],
dayRangStatus: false,
dayRange: [4, 10]
},
formatStartDate: '任意时间出发'
},
chosenResult: [{
type: 0,
ID: 0,
Name: '全部城市'
},
{
type: 1,
ID: 0,
Name: '全部城市'
}
],
loadingLow: false,
parementers: {
StartDate: '',
EndDate: '',
DepartCityId: 0,
ArriveCityId: 0,
AirLineID: 0,
groupId: 2,
TicketType: 2,
StartPlayDay: 0,
EndPlayDay: 0,
WeekDay: ''
}
}
},
created() {
if (uni.getStorageSync('scf')) {
const s = uni.getStorageSync('scf')
this.dateRangeValue = s.dateRange
this.chosenResult = s.cities
}
this.chosenResult.sort((a, b) => {
return a.type - b.type;
});
this.getTopLowTicketHandle()
},
mounted() {
let that = this
uni.getSystemInfo({
success(r) {
that.barHeight = r.statusBarHeight
},
});
},
methods: {
resolveParameterHanle() {
this.resetParameterHandle()
this.parementers.DepartCityId = this.chosenResult[0].ID
this.parementers.ArriveCityId = this.chosenResult[1].ID
if (this.dateRangeValue.type == 1) {
//this.parementers.StartDate = this.dateRangeValue.startDate
this.currentDate = this.formatDateHandle(start)
//this.parementers.EndDate = this.dateRangeValue.startDate
} else {
const d = this.resolveDateByFuzzyHandle(this.dateRangeValue.fuzzy.fuzzyType)
this.parementers.StartDate = d[0]
this.parementers.EndDate = d[1]
// if (this.dateRangeValue.fuzzy.weeks.length > 0) {
// const weeks = [2, 3, 4, 5, 6, 7, 1]
// this.parementers.WeekDay = weeks.filter((x, i) => this.dateRangeValue.fuzzy.weeks.indexOf(i) != -1)
// .join(',')
// }
// if (this.dateRangeValue.fuzzy.dayRangStatus) {
// this.parementers.StartPlayDay = this.dateRangeValue.fuzzy.dayRange[0]
// this.parementers.EndPlayDay = this.dateRangeValue.fuzzy.dayRange[1]
// }
}
},
resetParameterHandle() {
this.currentDate = ''
this.parementers = {
StartDate: '',
EndDate: '',
DepartCityId: 0,
ArriveCityId: 0,
AirLineID: 0,
groupId: 2,
TicketType: 2,
StartPlayDay: 0,
EndPlayDay: 0,
WeekDay: ''
}
},
resolveDateByFuzzyHandle(t) {
if (t == 0) return ['', '']
let start = new Date();
let end = new Date()
if (t == -1) {
start.setTime(start.getTime() + 3600 * 1000 * 24 * 1);
end.setMonth(end.getMonth() + 1);
} else if (t == -2) {
start.setTime(start.getTime() + 3600 * 1000 * 24 * 1);
end.setMonth(end.getMonth() + 3)
} else {
const currentDate = new Date()
currentDate.setMonth(currentDate.getMonth() + t);
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
start = new Date(year, month, 1);
end = new Date(year, month + 1, 0);
}
this.currentDate = this.formatDateHandle(start)
return ['', ''] //[this.formatDateHandle(start), this.formatDateHandle(end)]
},
formatDateHandle(date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`
},
getTopLowTicketHandle() {
this.resolveParameterHanle()
this.lowData = []
this.loadingLow = true
this.apipost('AirTicket_get_GetTopTicketProduct', this.parementers, (r) => {
if (r.resultCode == 1) {
this.lowData = this.formatLowData(r.data)
}
this.loadingLow = false
}, (e) => {
this.loadingLow = false
})
},
formatLowData(d) {
if (!d || d.length == 0) return []
const list = d[0].TicketProductList
list.sort((a, b) => {
return new Date(a.FlightDate) - new Date(b.FlightDate);
})
this.lowHeadData =this.foramtHeaderListHandle(list)
return list
},
setCurrentDateHandle(date) {
this.currentDate = date
},
foramtHeaderListHandle(d){
const result = d.reduce((acc, item) => {
const groupValue = item.FlightDate;
const currentPrice = item.B2BPrice;
if (!acc[groupValue]) {
acc[groupValue] = {
FlightDate: groupValue,
B2BPrice: currentPrice,
WeekDayStr: item.WeekDayStr
};
} else {
acc[groupValue].B2BPrice = Math.min(acc[groupValue].B2BPrice, currentPrice);
}
return acc;
}, {});
const finalResult = Object.values(result);
return finalResult
},
formatDateString(d) {
const date = new Date(d)
const weeks = ['日', '一', '二', '三', '四', '五', '六']
return `${String(date.getMonth()+1).padStart(2,'0')}.${String(date.getDate()).padStart(2,'0')}${weeks[date.getDay()]}` //周${weeks[date.getDay()]}
},
goBackHandle() {
uni.navigateBack(-1)
},
goHomeHandle() {
uni.redirectTo({
url: '/pages/index/index'
})
}
}
}
</script>
<style>
@import url('../../../asset/css/flex.css');
.nav-box {
height: 44px;
/* 这里的44px是示例高度,可根据实际设计调整 */
line-height: 44px;
background-color: #FFF;
color: #fff;
width: 100vw;
padding-left: 30rpx;
background-color: #d4b465;
}
.nav-box .home-btn {
width: 60rpx;
height: 60rpx;
border-radius: 60rpx;
/* border:2rpx solid #ccc; */
display: flex;
align-items: center;
justify-content: center;
}
.nav-box .title {
font-weight: bold;
font-size: 32rpx;
color: #080A09;
text-align: center;
width: calc(100vw - 140px);
}
.time-list {
background: linear-gradient(0, #eacf8d, #d4b465);
width: 100%;
}
.time-list .time-item {
padding: 22rpx 33rpx;
font-size: 24rpx;
color: #111;
line-height: 1;
text-align: center;
border-radius: 16rpx;
position: relative;
min-width: 148rpx;
}
.time-list .time-item::after {
position: absolute;
display: block;
right: 0;
width: 1px;
height: 60%;
top: 20%;
content: ' ';
background-color: #FFFFFF33;
}
.time-list .time-item.active {
background-color: #e7d9b2;
font-weight: bold;
margin-left: -1px;
}
.time-list .time-item.active::after,
.time-list .time-item:last-child::after {
display: none;
}
/* .nav-box .home-btn:active{
background-color: #ccc;
} */
</style>
\ No newline at end of file
......@@ -2,8 +2,8 @@
<!--同行绑定-->
<view class="login-page">
<view class="logo-head">
<image src="https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Test/Upload/Goods/1695262844000_280.png" mode="widthFix"></image>
<view class="logo-text">旅小友</view>
<image src="https://viitto-1301420277.cos.ap-chengdu.myqcloud.com/Upload/Goods/638749517916150559.jpg" mode="heightFix"></image>
<!-- <view class="logo-text">旅小友</view> -->
</view>
<view class="login-form">
<view class="login-title">同行登录</view>
......@@ -147,7 +147,7 @@
}
.login-page .logo-head image {
width: 50px;
/* width: 50px; */
height: 50px;
margin-right: 10px;
}
......
......@@ -52,8 +52,18 @@
<view class="jz_LineTitleLText" v-else>返100/人</view>
</view>
<view class="col">
<text>同业价:</text>
<text class="jz_renmin">¥</text><text class="jz_B2bPrice">{{ discountPrice }}</text>
<text style="color: #999999; font-size: 28rpx; font-weight: 400">/人起</text>
<view class="" v-if="stepNumber==0&&dataList.currentPriceInfo.teamType==0">
<text style="font-size: 13px;color:#111">
直客价:<text style="font-size: 20rpx;">¥</text>{{ currentPrice.b2CPrice}}
</text>
</view>
<text style="font-size: 13px;color:#111;margin-left: 10px;" v-else>
直客价:<text style="font-size: 20rpx;">¥</text>{{ currentPrice.b2CPrice}}
</text>
</view>
<view v-if="stepNumber==0&&dataList.currentPriceInfo.teamType==0" class="row items-center jz_LineDetaCZ" style="border-radius: 4px;overflow: hidden;font-size: 12px;" >
......
......@@ -24,9 +24,9 @@ export default {
Vue.prototype.host = "https://wx.weibaoge.cn/web/index.php?_mall_id=1285"
// Vue.prototype.host2 = "http://192.168.5.46:8300"
// Vue.prototype.host3 = "http://192.168.5.46"
Vue.prototype.host3 = "http://192.168.5.46:8501"
Vue.prototype.host2 = "https://erpmallapi.oytour.com"
Vue.prototype.host3 = "https://reborn.oytour.com"
//Vue.prototype.host3 = "https://reborn.oytour.com"
Vue.prototype.request = function(param, success, failed) {
//网络请求
......
......@@ -17,6 +17,12 @@ function SystemInfo() {//获取屏幕宽高
})
return SystemInfo
}
function getWeekName(date) {
const weeks = ['周日','周一','周二','周三','周四','周五','周六']
const currentDate = new Date(date)
const i = currentDate.getDay()
return weeks[i]
}
function getRect(selector) {//获取元素的信息
return new Promise((resolve) => {
let view = uni.createSelectorQuery().select(selector);
......@@ -157,4 +163,5 @@ export default {
SubscribeMsgAli,
// #endif
VersionUpdate,
getWeekName
}
\ No newline at end of file
## 1.0.6(2025-01-08)
更新文档
## 1.0.5(2024-12-26)
优化显示
## 1.0.4(2024-12-26)
优化显示
## 1.0.3(2024-12-25)
- 更新依赖
## 1.0.2(2024-12-25)
- 优化滑块相近时tip显示
## 1.0.1(2024-12-25)
- 优化逻辑
## 1.0.0(2024-12-25)
新增:
- 双滑块范围选择器基础功能
- 支持设置最小值(min)和最大值(max)
- 支持自定义步长(step)设置
- 支持禁用状态(disabled)
- 自定义样式功能:
- 滑动条背景色(backgroundColor)
- 选中范围颜色(activeColor)
- 滑块大小(blockSize)
- 滑块颜色(blockColor)
- 值格式化功能(format)
- 支持移动端触摸操作
- v-model双向绑定支持
<template>
<!-- 滑块范围选择器容器 -->
<view class="slider-range" :class="{disabled}" :style="sliderStyle">
<view class="slider-range-inner">
<!-- 滑块条 -->
<view class="slider-bar">
<!-- 背景条 -->
<view class="slider-bar-bg" :style="{backgroundColor}" />
<!-- 选中区域条 -->
<view class="slider-bar-inner" :style="barInnerStyle" />
</view>
<!-- 左右两个滑块按钮 -->
<view
v-for="block in ['lowerBlock', 'higherBlock']"
:key="block"
class="slider-handle-block"
:style="block === 'lowerBlock' ? leftHandleStyle : rightHandleStyle"
:data-tag="block"
@touchstart="handleDragStart"
@touchmove="handleDragMove"
@touchend="onBlockTouchEnd"
@mousedown="onMouseDown"
/>
<!-- 滑块值提示 -->
<view class="range-tip" :style="leftTipStyle">{{ formatValue(selectedRange[0]) }}</view>
<view class="range-tip" :style="rightTipStyle">{{ formatValue(selectedRange[1]) }}</view>
<!-- 刻度线 -->
<view
v-for="n in scaleCount + 1"
:key="n"
class="slider-scale"
:style="{left: `${(n / scaleCount) * 100}%`}"
/>
<!-- 最小最大值显示 -->
<view class="slider-value" style="left: 0">{{ min }}</view>
<view class="slider-value" style="right: 0">{{ max }}</view>
</view>
</view>
</template>
<script>
import throttle from './throttle'
// 默认刻度数量
const DEFAULT_SCALE_COUNT = 24
// 默认滑块大小(rpx)
const DEFAULT_BLOCK_SIZE = 48
/**
* 滑块范围选择器
* @description 一个可以选择数值范围的滑块组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=21575
* @property {Array} modelValue 双向绑定的值,默认[0, 100]
* @property {Number} min 最小值,默认0
* @property {Number} max 最大值,默认100
* @property {Number} step 步长,默认1
* @property {Function} format 格式化显示的值的函数
* @property {Boolean} disabled 是否禁用,默认false
* @property {String} backgroundColor 背景颜色,默认#F6F6F6
* @property {String} activeColor 激活颜色,默认#4DB8F6
* @property {Number} blockSize 滑块大小,默认48
* @property {String} blockColor 滑块颜色,默认#fff
* @event {Function} update:modelValue 值变化时触发
*/
export default {
name: 'llt-slider-range',
// 支持v-model双向绑定
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
modelValue: {
type: Array,
default: () => [0, 100]
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
format: {
type: Function,
default: val => val
},
disabled: {
type: Boolean,
default: false
},
backgroundColor: {
type: String,
default: '#F6F6F6'
},
activeColor: {
type: String,
default: '#4DB8F6'
},
blockSize: {
type: Number,
default: DEFAULT_BLOCK_SIZE
},
blockColor: {
type: String,
default: '#fff'
}
},
emits: ['update:modelValue'],
data() {
return {
selectedRange: this.modelValue, // 当前选中的值
dragStartPosition: 0, // 开始拖动时的位置
dragStartValue: 0, // 开始拖动时的值
activeBlock: '', // 当前拖动的滑块
scaleCount: DEFAULT_SCALE_COUNT, // 刻度数量
isDragging: false // 是否正在拖动
}
},
computed: {
// 计算左侧滑块位置
leftHandlePosition() {
return this.calculateHandlePosition(this.selectedRange[0])
},
// 计算右侧滑块位置
rightHandlePosition() {
return this.calculateHandlePosition(this.selectedRange[1])
},
// 左侧滑块样式
leftHandleStyle() {
return this.generateHandleStyle('lowerBlock')
},
// 右侧滑块样式
rightHandleStyle() {
return this.generateHandleStyle('higherBlock')
},
// 左侧提示样式
leftTipStyle() {
return this.generateTipStyle('lowerBlock')
},
// 右侧提示样式
rightTipStyle() {
return this.generateTipStyle('higherBlock')
},
// 滑块容器样式
sliderStyle() {
const padding = this.blockSize / 2
return `padding-left: ${padding}rpx;padding-right: ${padding}rpx`
},
// 选中区域样式
barInnerStyle() {
const width = ((this.selectedRange[1] - this.selectedRange[0]) / (this.max - this.min)) * 100
return `width: ${width}%;left: ${this.leftHandlePosition}%;background-color: ${this.activeColor}`
}
},
watch: {
// 监听modelValue变化
modelValue: {
deep: true,
immediate: true,
handler(val) {
if (!this.valuesEqual(val)) {
this.updateValues(val)
}
}
}
},
methods: {
// 格式化显示值
formatValue(val) {
if (typeof this.format === 'function') {
return this.format(val)
}
return val
},
// 计算滑块位置百分比
calculateHandlePosition(value) {
return ((value - this.min) / (this.max - this.min)) * 100
},
// 生成滑块样式
generateHandleStyle(block) {
const position = block === 'lowerBlock' ? this.leftHandlePosition : this.rightHandlePosition
let zIndex = this.activeBlock === block ? 20 : 12
if ((position < 1 && block === 'lowerBlock') || (position > 99 && block === 'higherBlock')) {
zIndex = 11
}
return `background-color: ${this.blockColor};width: ${this.blockSize}rpx;height: ${this.blockSize}rpx;left: ${position}%;z-index:${zIndex}`
},
// 生成提示样式
generateTipStyle(type) {
const position = type === 'lowerBlock' ? this.leftHandlePosition : this.rightHandlePosition
// 计算最大显示距离,根据右侧值的字符长度乘以8得到基准距离
const maxDistance = String(this.selectedRange[1]).length * 8
// 计算实际距离,用最大距离减去两个滑块之间的距离
const distance = maxDistance - (this.rightHandlePosition - this.leftHandlePosition)
// 如果实际距离大于0,说明两个滑块太近,需要调整提示位置避免重叠
if (distance > 0) {
// 根据滑块类型计算偏移量,左滑块向左偏移,右滑块向右偏移
const diff = type === 'lowerBlock' ? -distance : distance
return `left: calc(${position}% + ${diff}rpx)`
}
return position < 90
? `left: ${position}%`
: `right: ${100 - position}%; transform: translate(50%, -100%)`
},
// 更新选中值
updateValues(newVal) {
if (this.step >= this.max - this.min) {
throw new RangeError('Invalid slider step or slider range')
}
if (!this.isValidValues(newVal)) {
this.selectedRange = []
this.$emit('update:modelValue', [], 'update')
this.$emit('change', [])
return
}
const newValues = this.calculateNewValues(newVal)
if (this.valuesEqual(newValues)) return
this.selectedRange = this.validateValues(newValues)
this.$emit('update:modelValue', [...this.selectedRange], 'update')
this.$emit('change', [...this.selectedRange])
},
// 计算新的值
calculateNewValues(val) {
return [
Math.round((val[0] - this.min) / this.step) * this.step + this.min,
Math.round((val[1] - this.min) / this.step) * this.step + this.min
]
},
// 验证并修正值的范围
validateValues(values) {
let [lower, higher] = values
lower = Math.max(lower, this.min)
higher = Math.min(higher, this.max)
if (lower >= higher) {
if (lower === this.selectedRange[0]) {
higher = lower + this.step
} else {
lower = higher - this.step
}
}
return [lower, higher]
},
// 判断两个值数组是否相等
valuesEqual(newValues) {
return Array.isArray(newValues) &&
Array.isArray(this.selectedRange) &&
newValues.length === this.selectedRange.length &&
newValues.every((val, index) => val === this.selectedRange[index])
},
// 开始拖动事件处理
handleDragStart(event) {
if (this.disabled) return
const tag = event.target.dataset.tag
this.activeBlock = tag
const { pageX } = event.changedTouches?.[0] || event
this.dragStartPosition = pageX
this.dragStartValue = tag === 'lowerBlock' ? this.selectedRange[0] : this.selectedRange[1]
this.isDragging = true
},
// 拖动移动事件处理
handleDragMove(event) {
if (!this.isDragging || this.disabled) return
throttle(this.processDrag(event), 500)
},
// 结束拖动事件处理
onBlockTouchEnd() {
this.isDragging = false
},
// 拖动处理
processDrag(event) {
const view = uni.createSelectorQuery().in(this).select('.slider-range-inner')
view.boundingClientRect(data => {
const sliderWidth = data.width
const { pageX } = event.changedTouches?.[0] || event
const diff = ((pageX - this.dragStartPosition) / sliderWidth) * (this.max - this.min)
const nextVal = this.dragStartValue + diff
const values = this.activeBlock === 'lowerBlock'
? [nextVal, this.selectedRange[1]]
: [this.selectedRange[0], nextVal]
this.updateValues(values)
}).exec()
},
// 验证值是否有效
isValidValues(values) {
return Array.isArray(values) && values.length === 2
},
// 添加鼠标按下事件处理
onMouseDown(event) {
if (this.disabled) return
const tag = event.target.dataset.tag
this.activeBlock = tag
this.dragStartPosition = event.pageX
this.dragStartValue = tag === 'lowerBlock' ? this.selectedRange[0] : this.selectedRange[1]
this.isDragging = true
// 添加鼠标移动和抬起的事件监听
document.addEventListener('mousemove', this.onMouseMove)
document.addEventListener('mouseup', this.onMouseUp)
},
// 添加鼠标移动事件处理
onMouseMove(event) {
if (!this.isDragging || this.disabled) return
event.preventDefault() // 防止拖动时选中文本
throttle(this.handleMouseDrag(event), 500)
},
// 添加鼠标抬起事件处理
onMouseUp() {
this.isDragging = false
// 移除事件监听
document.removeEventListener('mousemove', this.onMouseMove)
document.removeEventListener('mouseup', this.onMouseUp)
},
// 处理鼠标拖动
handleMouseDrag(event) {
const view = uni.createSelectorQuery().in(this).select('.slider-range-inner')
view.boundingClientRect(data => {
const sliderWidth = data.width
const diff = ((event.pageX - this.dragStartPosition) / sliderWidth) * (this.max - this.min)
const nextVal = this.dragStartValue + diff
const values = this.activeBlock === 'lowerBlock'
? [nextVal, this.selectedRange[1]]
: [this.selectedRange[0], nextVal]
this.updateValues(values)
}).exec()
}
}
}
</script>
<style lang="scss" scoped>
.slider-range {
position: relative;
padding-top: 40rpx;
&-inner {
position: relative;
width: 100%;
height: 100rpx;
}
&.disabled {
.slider-bar-inner {
opacity: 0.35;
}
.slider-handle-block {
cursor: not-allowed;
}
}
}
.slider-bar {
position: absolute;
top: 30%;
left: 0;
right: 0;
height: 15rpx;
transform: translateY(-30%);
&-inner,
&-bg {
position: absolute;
width: 100%;
height: 100%;
border-radius: 10000px;
}
&-inner {
z-index: 11;
}
&-bg {
z-index: 10;
}
}
.slider-handle-block {
position: absolute;
top: 30%;
transform: translate(-50%, -50%);
border-radius: 50%;
box-shadow: 0rpx 0rpx 10rpx 0rpx rgba(91, 91, 91, 0.2);
z-index: 12;
cursor: pointer;
user-select: none;
}
.range-tip {
position: absolute;
top: 0;
font-family: Source Han Sans CN;
font-weight: 400;
font-size: 26rpx;
color: #666666;
transform: translate(-30%, -100%);
}
.slider-scale {
position: absolute;
bottom: 30rpx;
width: 1rpx;
height: 14rpx;
background: #e2e2e2;
}
.slider-value {
position: absolute;
bottom: 0;
font-family: Source Han Sans CN;
font-weight: 400;
font-size: 21rpx;
color: #bbbbbb;
}
</style>
let timer; let
flag
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle
{
"id": "llt-slider-range",
"displayName": "slider range双滑块范围选择器",
"version": "1.0.6",
"description": "一个功能强大的双滑块范围选择器组件,可用于价格区间、数值范围等场景的选择。支持自定义样式、步长设置,具有良好的移动端适配性和交互体验。",
"keywords": [
"slider",
"range",
"slider-range",
"区间滑块"
],
"repository": "",
"engines": {
"HBuilderX": "^3.8.6"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n",
"app-uvue": "n",
"app-harmony": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
# SliderRange 双滑块范围选择器
> 一个轻量级但功能强大的双滑块范围选择器组件,完美适配移动端和PC端,为您的项目提供直观的范围选择体验。
## 🌟 特性
- ✨ 灵活的范围设置 - 支持自定义最大最小值
- 📏 精确的步长控制 - 可设置数值变化的最小单位
- 🎨 丰富的样式定制 - 支持自定义滑块、轨道样式
- 📱 完美的跨端适配 - 支持H5/App/小程序多端使用
- 🔄 双向绑定支持 - 支持v-model语法
- 🎯 刻度线显示 - 直观的数值参考
- ⌨️ 键盘操作支持 - 提升无障碍体验
- 🚫 禁用状态 - 支持只读模式
## 📦 安装
### 下载使用
`llt-slider-range` 组件复制到你的项目中即可。
## 🚀 快速开始
### H5/App使用示例
```vue
<template>
<llt-slider-range v-model="rangeValue" />
</template>
<script>
export default {
data() {
return {
rangeValue: [30, 50] // 设置初始范围值
}
}
}
</script>
```
### 微信小程序使用示例
```vue
<template>
<llt-slider-range
:model-value="rangeValue"
@change="handleChange"
/>
</template>
<script>
export default {
data() {
return {
rangeValue: [30, 50]
}
},
methods: {
handleChange(val) {
this.rangeValue = val
}
}
}
</script>
```
## 📝 API文档
### Props 属性
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| modelValue/v-model | Array | [0, 100] | 当前选中的范围值 |
| min | Number | 0 | 最小可选值 |
| max | Number | 100 | 最大可选值 |
| step | Number | 1 | 步长,必须大于0 |
| disabled | Boolean | false | 是否禁用 |
| backgroundColor | String | '#F6F6F6' | 滑动条背景色 |
| activeColor | String | '#4DB8F6' | 选中范围的颜色 |
| blockSize | Number | 48 | 滑块大小(rpx) |
| blockColor | String | '#fff' | 滑块颜色 |
| format | Function | val => val | 数值格式化函数 |
### Events 事件
| 事件名 | 说明 | 回调参数 |
|--------|------|----------|
| update:modelValue | 值更新时触发 | (value: number[]) |
| change | 值变化时触发 | (value: number[]) |
## 💡 高级用法
### 价格范围选择器
```vue
<template>
<llt-slider-range
v-model="priceRange"
:min="0"
:max="10000"
:step="100"
:format="formatPrice"
active-color="#FF6B6B"
/>
</template>
<script>
export default {
data() {
return {
priceRange: [1000, 5000]
}
},
methods: {
formatPrice(val) {
return `¥${val}`
}
}
}
</script>
```
### 温度选择器
```vue
<template>
<llt-slider-range
v-model="tempRange"
:min="-20"
:max="40"
:format="val => `${val}°C`"
active-color="#2196F3"
/>
</template>
```
## ⚠️ 注意事项
1. 确保传入的范围值在 min 和 max 之间
2. step 值必须为正数
3. 移动端使用时建议适当增大 blockSize 以提升触控体验
4. 小程序端需使用 model-value + change 事件方式实现双向绑定
5. 建议在父容器设置合适的宽度,以获得最佳显示效果
## 🔗 相关链接
- [GitHub 仓库](https://github.com/llt3677/slider-range)
- [插件市场](https://ext.dcloud.net.cn/plugin?id=21575)
- [问题反馈](https://github.com/llt3677/slider-range/issues)
## 📄 License
[MIT](https://opensource.org/licenses/MIT)
---
如果这个组件对你有帮助,欢迎 star ⭐️ 支持一下!
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment