因項目需求,最近使用taro,開發了一款應用於票務商品購票的價格日曆,感受這個東西的應用仍是比較多的,所以,就分享出來,關公門前耍個大刀,路過的大佬們手下留情哦!css
爲了響應國家號召,節能減排,小弟就將一些邊緣的邏輯和樣式省略,重點分享日曆這部分的邏輯,完整代碼請 猛戳 這裏。git
ticketPriceList(設置有價格日期等信息的商品集合)
經過props
傳遞進calendar
組件,calendar
組件中initDate
方法對日曆數據進行初始化(初始化日期顯示數據,該方法會在組件初始化時先調用一次),同時遍歷ticketPriceList
,經過匹配二者日期是否相等來將有價格的日期同步到日曆上顯示。經過調用prevMonth
和nextMonth
來切換顯示的月份,最終一樣調用initDate
來更新日曆顯示的數據。github
<!--calendar組件-->
import Taro, {
Component
} from '@tarojs/taro'
import { View, Image } from '@tarojs/components'
import './index.scss'
export default class SkuSelector extends Component {
//props default
static defaultProps = {
ticketPriceList: []
};
constructor(props) {
super(props);
this.state = {
date: new Date(), //獲取系統日期
currentDate: '', //當前選擇的日期
selectMonth: '', //選擇的月份
weeks: ['日', '一', '二', '三', '四', '五', '六'],
color: '#fff',//選中日期文字顏色
bgColor: 'f5f6f7',//非今日的日期背景色
dayArr: [], //月份天數
year: '', //當前選擇的年
month: '', //當前選擇的月
status: 1, //1:屬於當月的天,0:屬於上月2:屬於下月
prevMonthStatus: false,//返回上個月按鈕禁用狀態,false:禁用,true,不由用
}
}
//時間格式 xxxx-xx-xx 轉時間戳
fmtDate(date) {
let dateStamp = new Date(date.replace(/-/g, '/'));
return dateStamp.getTime()
}
//設置有效的日期範圍
setValidDate(dateObj) {
const currentDateStamp = this.fmtDate(dateObj.sellDate);//當前傳入日期時間戳
const startDateStamp = this.fmtDate(this.props.ticketPriceList[0].sellDate);//設訂價格的票券起始日期時間戳
const endDateStamp = this.fmtDate(this.props.ticketPriceList[this.props.ticketPriceList.length - 1].sellDate)///設訂價格的票券結束日期時間戳
if (currentDateStamp >= startDateStamp && currentDateStamp <= endDateStamp) {
if (dateObj.marketAmount && dateObj.dailyStock > 0) {//有效的日期(可點擊)
dateObj.ban = 1;//可選狀態
dateObj.color = '#666';
} else if (dateObj.marketAmount && dateObj.dailyStock <= 0) {//已售罄
dateObj.bgColor = '#999';
dateObj.color = '#333';
dateObj.ban = 3;//已售罄
}
}
}
//判斷傳入時間跟當日時間大小,返回值爲二者差值
checkValidDate(date) {
let localYear = this.state.date.getFullYear()
let localMonth = this.state.date.getMonth() + 1
let day = this.state.date.getDate()
let localDate = this.formatNum(localYear) + '-' + this.formatNum(localMonth) + '-' + this.formatNum(day)//當日完整日期:'xxxx-xx-xx'
const currentDateStamp = this.fmtDate(date);//當前傳入日期時間戳
const localDateStamp = this.fmtDate(localDate)//當日時間戳
return currentDateStamp - localDateStamp
}
//初始化日期
initDate(year, month) {
return new Promise(resolve => {
let weekValue = '';
let totalDay = new Date(year, month + 1, 0).getDate()//獲取當前所選擇月份總天數
this.setState({
selectMonth: month + 1,
dayArr: []
}, () => {
//獲取並填充當前所查詢年份對應月份數據
for (let i = 1; i <= totalDay; i++) {
let dayDate = this.formatNum(year) + '-' + this.formatNum(month + 1) + '-' + this.formatNum(i);
let obj = {
sellDate: dayDate,
day: i,
marketAmount: '',//零售價
storeAmount: '',//門市價
dailyStock: '',//庫存
bgColor: 'none',
color: '#ccc',
status: 1, //當月的天
ban: 2//1:正常,2:禁用,3:售罄
}
weekValue = (new Date(year, month, i)).getDay(); //獲取這天對應的是星期幾 0 - 6,0 表示星期天
if (i == 1 && weekValue != 0) {
this.addBeforeValue(weekValue)//填充日曆開始的空白,
}
let index = this.props.ticketPriceList.findIndex((item) => {
return item.sellDate == dayDate //匹配設置價格的天,並返回其索引
})
if (index >= 0) {
obj.marketAmount = this.props.ticketPriceList[index].marketAmount;//將價格賦值給日曆中匹配到的項
obj.dailyStock = this.props.ticketPriceList[index].dailyStock;
obj.storeAmount = this.props.ticketPriceList[index].storeAmount;
}
this.setValidDate(obj) //設置有效的日期範圍
let dayArr = this.state.dayArr;
dayArr.push(obj)//將當前月的天push進數組
this.setState({
dayArr
}, () => {
if (i == totalDay && weekValue != 6) {
this.addAfterValue(weekValue) //填充日曆結尾的空白
}
resolve(true);
})
}
})
//判斷當前展現的月份跟本月關係,若是是當月則禁用返回上個月按鈕
let dayDate = this.formatNum(year) + '-' + this.formatNum(month + 1) + '-' + this.formatNum(1);
if (this.checkValidDate(dayDate) <= 0) {
this.setState({
prevMonthStatus: false
})
} else {
this.setState({
prevMonthStatus: true
})
}
})
}
//補充前面空白日期
addBeforeValue(weekValue) {
let totalDay = new Date(this.state.year, this.state.month, 0).getDate();
for (let i = 0; i < weekValue; i++) {
let obj = {
sellDate: '',
day: '',
marketAmount: '',//零售價
storeAmount: '',//門市價
bgColor: 'none',
color: '#ccc',
status: 0,
ban: 2//禁用
}
// obj.day = totalDay - (weekValue - i) + 1;
let dayArr = this.state.dayArr;
dayArr.push(obj);
this.setState({
dayArr
})
}
}
//補充後空白日期
addAfterValue(weekValue) {
let totalDay = new Date(this.state.year, this.state.month, 0).getDate();
for (let i = 0; i < (6 - weekValue); i++) {
let obj = {
sellDate: '',
day: '',
marketAmount: '',//零售價
storeAmount: '',//門市價
bgColor: 'none',
color: '#ccc',
status: 2,
ban: 2//禁用
}
// obj.day = i + 1;
let dayArr = this.state.dayArr;
dayArr.push(obj);
this.setState({
dayArr
})
}
}
//日期時間的格式化
formatNum(num) {
return num < 10 ? '0' + num : num + '';
}
//調用initDate方法
async callInitDateFunc(year, month) {
await this.initDate(year, month);
this.initInfo();
}
//上一個月
prevMonth() {
if (!this.state.prevMonthStatus) return
if (this.state.month == 0) {//1月
let year = this.state.year - 1;//須要返回到上一年
this.setState({
year,
month: 11//12月
}, () => {
this.callInitDateFunc(this.state.year, this.state.month);
})
} else {
let month = this.state.month - 1;
this.setState({
month
}, () => {
this.callInitDateFunc(this.state.year, this.state.month);
})
}
}
//下一個月
nextMonth() {
if (this.state.month == 11) {//12月
let year = this.state.year + 1;//到下一年
this.setState({
year,
month: 0//1月
}, () => {
this.callInitDateFunc(this.state.year, this.state.month);
})
} else {
let month = this.state.month + 1;
this.setState({
month
}, () => {
this.callInitDateFunc(this.state.year, this.state.month);
})
}
}
//輸出當前點擊項(選中的哪天)
handleClick(obj) {
//查詢當前選中日期下標
let idx = this.state.dayArr.findIndex((item) => {
return item.sellDate == obj.sellDate
})
if (this.state.dayArr[idx].status == 0) {//若是點擊日曆開始的填充日期
this.setState({
status: 0
}, () => {
// this.prevMonth();//自動跳轉上一個月
})
return;
}
if (this.state.dayArr[idx].status == 2) { //若是點擊日曆結尾的填充日期
this.setState({
status: 2
}, () => {
// this.nextMonth();//自動跳轉下一個月
})
return;
}
if (this.state.dayArr[idx].status == 1 && this.state.dayArr[idx].ban == 1) {//點擊日曆日期,而且爲可點擊狀態
let dayArr = this.state.dayArr;
dayArr[idx].bgColor = '#FEB100';//設定選中日期的背景色
dayArr[idx].color = '#fff';//選中日期的文字顏色
//將日曆其餘可點擊狀態的日期文字重置爲默認色
let count = 0;
for (let i = 0; i < this.state.dayArr.length; i++) {
if (this.state.dayArr[i].status == 1 && this.state.dayArr[i].ban == 1 && i != idx) {
dayArr[i].bgColor = 'none';
dayArr[i].color = '#666';
count++;
}
if (count >= this.props.ticketPriceList.length) break;//說明已經所有重置完成,直接結束循環
}
this.setState({
dayArr
})
this.props.handleClick(obj)//返回當前點擊的日期給父組件
}
}
//價格日曆映射表(當前查詢日期->價格日曆 ,返回查詢日期在價格日曆的索引位置)
findTicketIndex(obj) {
let findDateObj = obj;
let index = this.state.dayArr.findIndex((item) => {
return item.sellDate == findDateObj.sellDate //返回其索引
})
return index;
}
//初始化商品信息
initInfo() {
const findIndex = this.findTicketIndex(this.props.currentSelectTicket);
if (findIndex !== -1) {
this.handleClick(this.state.dayArr[findIndex])
}
}
componentDidMount() {
let year = this.state.date.getFullYear(); //獲取當前所在年份
let month = this.state.date.getMonth(); //獲取當前所在月份 0-11
this.setState({
year,
month
}, async () => {
await this.initDate(this.state.year, this.state.month); //初始化日曆數據
this.initInfo() //初始化當前商品價格等展現信息,該方法只在組件初始化的時候執行一次
})
}
render() {
const { year, selectMonth, dayArr, weeks, prevMonthStatus } = this.state
return (
<View className="calendar-selector-container">
<View className="calendar-header">
<View className="header-left" onClick={() => this.prevMonth()}>
<Image className='left-arrow' src={prevMonthStatus ? 'http://static.ledouya.com/FkhXIKoqvceD_ieVbVlUWzM4X_PR' : 'http://static.ledouya.com/Fr3ECEFgaTuTbQPkPKLl-PA2eV8m'} />
<Text className={['left-text', !prevMonthStatus && 'ban-text']}>上月</Text>
</View>
<View className="header-center">{year + '年' + selectMonth}月</View>
<View className="header-right" onClick={() => this.nextMonth()}>
<Text className="right-text">下月</Text>
<Image className="right-arrow" src="http://static.ledouya.com/Fl7CcBEZszqiWTpfN0bKSB9NeZdX" />
</View>
</View>
<View className="calendar-week">
{
weeks.map((v) => (
<View className="list">{v}</View>
))
}
</View>
<View className="calendar-content">
<View className="calendar-day">
{dayArr.map((v, i) => (
<View className="list" onClick={() => this.handleClick(v)}>
<View className={['day', v.ban == 3 && 'sell-out']} style={{ background: v.bgColor, color: v.color }}>{v.ban == 1 || v.ban == 2 ? v.day : '售罄'}</View>
{v.ban == 1 && <View className="price">¥{(parseFloat(v.marketAmount) / 100).toFixed(2)}</View>}
</View>
))
}
<View className="local-month">{selectMonth}</View>
</View>
</View>
</View>
)
}
}
複製代碼
以上就是calendar組件實現的核心代碼,雖然功能比較簡單,但實現的過程當中細節邏輯感受仍是挺多的,也是費了些時間去思考細節。所以分享出來,共同進步。若有錯誤,望大佬們多多指正。數組