KELECHUAN 3 年之前
父節點
當前提交
23a852e5e8

文件差異過大導致無法顯示
+ 4 - 2
common/base64.scss


文件差異過大導致無法顯示
+ 18 - 6
common/iconfont-weapp-icon.css


+ 100 - 0
components/zsy-calendar/dateBox.vue

@@ -0,0 +1,100 @@
+<template>
+	<!-- 日期显示 -->
+	<view class="date_box">
+		<view
+			v-for="(dateInfo, dateIndex) in dates"
+			:key="dateIndex"
+			class="calendar_date__box"
+		>
+			<view
+				class="calendar_date"
+				:class="{ isSelected: dateInfo.isSelected }"
+				:style="{
+					height: cellHeight + 'rpx',
+					width: cellHeight + 'rpx',
+					color: swiperMode === 'open' ? dateInfo.type === 'cur' ? '#2C2C2C' : '#959595' : '#2C2C2C',
+					backgroundColor: dateInfo.isSelected ? dateActiveColor : ''
+				}"
+				@tap="chooseDate(dateInfo, dateIndex)"
+			>
+				<view class="calendar_date__number">{{ dateInfo.date }}</view>
+				<view class="calendar_date__isToday" v-if="dateInfo.isToday" :style="{ backgroundColor: dateActiveColor }"></view>
+				<view class="calendar_date__cricle"></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			dates: {
+				type: Array,
+				default: []
+			},
+			cellHeight: { // 一列的高度
+				type: Number,
+				default: 75
+			},
+			dateActiveColor: { // 日期选中颜色
+				type: String,
+				default: '#FE6601'
+			},
+			swiperMode: { // 日历显示模式
+				type: String,
+				default: 'open'
+			}
+		},
+		methods: {
+			chooseDate(dateInfo, dateIndex) {
+				this.$emit('chooseDate', dateInfo, dateIndex)
+			}
+		}
+	}
+</script>
+
+<style>
+	/* 日历轮播 */
+	.date_box {
+		display: flex;
+		flex-wrap: wrap;
+		justify-content: space-between;
+	}
+	.date_box .calendar_date__box {
+		width: calc(100% / 7);
+		margin-top: 20rpx;
+	}
+	.calendar_date__box .calendar_date {
+		text-align: center;
+		margin: 0 auto;
+		font-weight: bold;
+		font-size: 28rpx;
+		border-radius: 50%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		position: relative;
+	}
+	.calendar_date__box .calendar_date.isSelected {
+		color: #FFFFFF !important;
+	}
+	.calendar_date .calendar_date__isToday {
+		width: 100%;
+		height: 100%;
+		position: absolute;
+		top: 0;
+		left: 0;
+		border-radius: 50%;
+		z-index: -1;
+		opacity: 0.4;
+	}
+	.calendar_date .calendar_date__cricle {
+		width: 9rpx;
+		height: 9rpx;
+		border-radius: 50%;
+		margin-top: 5rpx;
+		background-color: #FFFFFF;
+	}
+	/* 日历轮播 */
+</style>

+ 68 - 0
components/zsy-calendar/js/utils.js

@@ -0,0 +1,68 @@
+/**
+ * 时间格式化
+ * @param {String} time
+ * @param {String} cFormat
+ */
+export function parseTime(time, cFormat) {
+	if (arguments.length === 0) {
+	  return null
+	}
+  if (!time) return ''
+  /* 修复IOS系统上面的时间不兼容*/
+  if (time.toString().indexOf('-') > 0) {
+    time = time.replace(/-/g, '/')
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') {
+      return ['日', '一', '二', '三', '四', '五', '六'][value]
+    }
+    return value.toString().padStart(2, '0')
+  })
+  return time_str
+}
+
+/**
+ * This is just a simple version of deep copy
+ * Has a lot of edge cases bug
+ * If you want to use a perfect deep copy, use lodash's _.cloneDeep
+ * @param {Object} source
+ * @returns {Object}
+ */
+export function deepClone(source) {
+	if (!source && typeof source !== 'object') {
+		throw new Error('error arguments', 'deepClone')
+	}
+	const targetObj = Object.prototype.toString.call(source) === "[object Array]" ? [] : {}
+	Object.keys(source).forEach(keys => {
+		if (source[keys] && typeof source[keys] === 'object') {
+			targetObj[keys] = deepClone(source[keys])
+		} else {
+			targetObj[keys] = source[keys]
+		}
+	})
+	return targetObj
+}

+ 625 - 0
components/zsy-calendar/zsy-calendar.vue

@@ -0,0 +1,625 @@
+<template>
+	<!-- 日历滚动插件 -->
+	<view class="zsy_calendar">
+		<!-- 日历顶部信息 -->
+		<view class="calendar_info">
+			<text class="title">每日记录</text>
+			<text class="desc">
+				({{ getAssignDateInfo(false, 0) === getAssignDateInfo(true, 0) ? '' : getAssignDateInfo(false, 0) + '年' }}{{ getAssignDateInfo(false, 1) }}月)
+			</text>
+			<text v-show="showBackToTodayBtn" class="backToToday" :style="{color: dateActiveColor}" @tap="goToDate()">回到今天</text>
+		</view>
+		
+		<!-- 日历周数 -->
+		<view class="calendar_week">
+			<view v-for="(item, index) in week" :key="index" class="calendar_week__item">{{ item }}</view>
+		</view>
+		
+		<!-- 日历轮播 -->
+		<view class="calendar_swiper">
+			<!-- 展开情况下的日历轮播 -->
+			<swiper
+				v-if="swiperMode === 'open'"
+				key="normalSwiper"
+				circular
+				:style="{height: swiperHeight('open')}"
+				:current="current"
+				:duration="duration"
+				:skip-hidden-item-layout="true"
+				@change="e => current = e.detail.current"
+			>
+				<swiper-item v-for="(swiper, swiperIndex) in 3" :key="swiperIndex" class="swiper-item">
+					<DateBox
+						:dates="calendarSwiperDates[swiperIndex]"
+						:cellHeight="cellHeight"
+						:dateActiveColor="dateActiveColor"
+						:swiperMode="swiperMode"
+						@chooseDate="chooseDate"
+					/>
+				</swiper-item>
+			</swiper>
+			
+			<!-- 收缩情况下的日历轮播 -->
+			<swiper
+				v-else
+				key="shrinkSwiper"
+				circular
+				:style="{height: swiperHeight('close')}"
+				:current="shrinkCurrent"
+				:duration="duration"
+				:skip-hidden-item-layout="true"
+				@change="e => shrinkCurrent = e.detail.current"
+			>
+				<swiper-item v-for="(swiper, swiperIndex) in 3" :key="swiperIndex" class="swiper-item">
+					<DateBox
+						:dates="calendarSwiperShrinkDates[swiperIndex]"
+						:cellHeight="cellHeight"
+						:dateActiveColor="dateActiveColor"
+						:swiperMode="swiperMode"
+						@chooseDate="chooseShrinkDate"
+					/>
+				</swiper-item>
+			</swiper>
+		</view>
+	
+		<!-- 日历切换模式 -->
+		<view class="calendar_toggle" @tap="swiperMode = swiperMode === 'open' ? 'close' : 'open'">
+			<view class="icon" :class="{down: swiperMode === 'close'}"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { parseTime, deepClone } from './js/utils.js'
+	import DateBox from './dateBox.vue'
+	export default {
+		name: 'ZsyCalendar',
+		components: {
+			DateBox
+		},
+		props: {
+			duration: { // 轮播图动画时长
+				type: Number,
+				default: 300
+			},
+			cellHeight: { // 一列的高度
+				type: Number,
+				default: 75
+			},
+			dateActiveColor: { // 日期选中颜色
+				type: String,
+				default: '#FE6601'
+			},
+			sundayIndex: { // 星期天所在索引,0表示第一个、6表示最后一个
+				type: Number,
+				default: 6
+			},
+			mode: { // 日历模式
+				type: String,
+				default: 'close'
+			},
+			changeSetDefault: { // 月份切换时是否显示一号还是当前月份选中高亮
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				today: parseTime(new Date(), '{y}-{m}-{d}'), // 今天日期
+				selectedDate: null, // 选中日期
+				week: [], // 日历周数
+				current: 1, // 当前日历轮播默认显示索引
+				shrinkCurrent: 1, // 缩放日历轮播默认显示索引
+				calendarSwiperDates: [], // 日历轮播日期信息
+				calendarSwiperShrinkDates: [], // 日历轮播收缩时的日期信息
+				dateActive: -1, // 日期选中索引
+				swiperByClick: false, // 是否通过点击上月份或下月份的日期进行轮播切换
+				shrinkSwiperByClick: false, // 是否通过点击上月份或下月份的日期进行收缩日历的轮播切换
+				swiperMode: this.mode, // 日历轮播显示模式 open:展开 close:收缩
+				dateCache: {}, // 日期缓存
+				emitTimer: null, // 日期改变向父级传递当前选中日期计时器
+				dateClick: false // 是否进行了日期的点击选择
+			}
+		},
+		computed: {
+			/* 获取指定日期信息
+			isCurDate: 是否获取当天的信息还是选中日期的信息
+			index: 0 表示年份 1 表示月份 2 表示日期 */
+			getAssignDateInfo() {
+				return (isCurDate, index) => {
+					return (isCurDate ? this.today : this.selectedDate).split('-')[index] * 1
+				}
+			},
+			// 是否显示回到今天按钮
+			showBackToTodayBtn() {
+				return this.getAssignDateInfo(false, 0) !== this.getAssignDateInfo(true, 0) || this.getAssignDateInfo(false, 1) !== this.getAssignDateInfo(true, 1)
+			},
+			// 返回轮播图高度
+			swiperHeight() {
+				return (swiperMode) => {
+					const normalHeight = (this.calendarSwiperDates[this.current] || []).length / 7 * (this.cellHeight + 20) + 'rpx'
+					const shrinkHeight = this.cellHeight + 20 + 'rpx'
+					return swiperMode === 'open' ? normalHeight : shrinkHeight
+				}
+			}
+		},
+		watch: {
+			// 展开日历轮播切换
+			current(newV, oldV) {
+				if (newV === 0 && oldV === 2) { // 右滑
+					this.swiperChange(1)
+					return
+				}
+				if (newV === 2 && oldV === 0) { // 左滑
+					this.swiperChange(-1)
+					return
+				}
+				if (newV > oldV) { // 右滑
+					this.swiperChange(1)
+				} else { // 左滑
+					this.swiperChange(-1)
+				}
+			},
+			// 收缩日历轮播切换
+			shrinkCurrent(newV, oldV) {
+				if (newV === 0 && oldV === 2) { // 右滑
+					this.shrinkSwiperChange(1)
+					return
+				}
+				if (newV === 2 && oldV === 0) { // 左滑
+					this.shrinkSwiperChange(-1)
+					return
+				}
+				if (newV > oldV) { // 右滑
+					this.shrinkSwiperChange(1)
+				} else { // 左滑
+					this.shrinkSwiperChange(-1)
+				}
+			},
+			// 日历显示方式切换
+			swiperMode(newV) {
+				// 当收缩时初始化收缩轮播图的日期数据
+				if (newV === 'close') {
+					this.initCalendarShrinkSwiperDates()
+				}
+			},
+			selectedDate: {
+				deep: true,
+				handler(newV, oldV) {
+					if (newV && (oldV === null || this.dateClick)) { // 初始化/日历点击选择时直接返回
+						this.emitDate()
+						this.dateClick = false
+					} else { // 其它情况做防抖处理
+						if (this.emitTimer !== null) {
+							clearTimeout(this.emitTimer)
+						}
+						this.emitTimer = setTimeout(() => {
+							this.emitDate()
+						}, this.duration + 200)
+					}
+				}
+			}
+		},
+		created() {
+			this.init() // 初始化数据
+		},
+		methods: {
+			// 初始化数据
+			init() {
+				if (this.selectedDate === null) this.selectedDate = this.today // 默认选中日期为当天
+				this.initWeek() // 初始化要显示的周数
+				this.initCalendarSwiperDates() // 初始化日历轮播日期信息
+				// 解决swiperMode初始化为收缩时没有初始化日历收缩轮播日期信息
+				if (this.swiperMode === 'close') {
+					this.initCalendarShrinkSwiperDates()
+				}
+			},
+			// 初始化周数
+			initWeek() {
+				const normalWeek = ['日', '一', '二', '三', '四', '五', '六'] // 正常周数
+				const sIndex = this.sundayIndex < 0 ? 0 : this.sundayIndex >= normalWeek.length ? normalWeek.length - 1 : this.sundayIndex
+				normalWeek.unshift(...normalWeek.slice(-sIndex))
+				normalWeek.length = 7
+				this.week = normalWeek
+			},
+			// 初始化展开时的日历轮播日期信息
+			initCalendarSwiperDates(cb) {
+				const year = this.getAssignDateInfo(false, 0)
+				const month = this.getAssignDateInfo(false, 1)
+				const cur = this.generateCalendar(year, month)
+				const prev = this.generateCalendar(month === 1 ? year - 1 : year, month === 1 ? 12 : month - 1)
+				const next = this.generateCalendar(month === 12 ? year + 1 : year, month === 12 ? 1 : month + 1)
+				// 根据current来判断相邻的轮播存放哪些日历数据
+				if (this.current === 0) {
+					this.calendarSwiperDates = [cur, next, prev]
+				} else if (this.current === 1) {
+					this.calendarSwiperDates = [prev, cur, next]
+				} else if (this.current === 2) {
+					this.calendarSwiperDates = [next, prev, cur]
+				}
+				this.swiperByClick = false
+				// 初始化日期信息完毕执行回调函数
+				cb && cb()
+			},
+			// 生成展开的日历数据
+			generateCalendar(year, month) {
+				let calendarDate = []
+				// 先获取缓存里面有没有该月的日期数据
+				if (this.dateCache[`${year}-${month}`]) {
+					calendarDate = deepClone(this.dateCache[`${year}-${month}`])
+				} else { // 进行月份日期的计算
+					const monthDates = new Date(year, month, 0).getDate() // 获取此月份总天数
+					const normalWeek = ['一', '二', '三', '四', '五', '六', '日'] // 正常周数
+					const monthFirstDay = normalWeek[new Date(year, month - 1, 0).getDay()] // 获取本月一号为星期几
+					const monthFirstDayIndex = this.week.indexOf(monthFirstDay) // 计算本月一号在日历周数中的索引,索引之前的填充上个月的后几天
+					// 本月一号在日历中不是第一个位置,需要进行填充
+					if (monthFirstDayIndex !== 0) {
+						const prevMonthDates = new Date(year, month - 1, 0).getDate() // 获取上一个月份的总天数
+						// 填充本月一号之前的数据
+						for (let i = 0; i < monthFirstDayIndex; i ++) {
+							const item = {
+								year: month === 1 ? year - 1 : year,
+								month: month === 1 ? 12 : month - 1,
+								date: prevMonthDates - i,
+								type: 'prev'
+							}
+							// 判断填充的日期是否包含今天日期
+							this.theDateIsToday(item)
+							calendarDate.unshift(item)
+						}
+					}
+					// 循环生成当月所有日期
+					for (let i = 1; i <= monthDates; i ++) {
+						const item = {
+							year,
+							month,
+							date: i,
+							isSelected: false,
+							isToday: false,
+							type: 'cur'
+						}
+						// 今天的日期在不在里面
+						this.theDateIsToday(item)
+						calendarDate.push(item)
+					}
+					const residue = calendarDate.length % 7
+					// 判断是否需要填充下个月的前几天
+					if (residue !== 0) {
+						for (let i = 1; i <= 7 - residue; i ++) {
+							const item = {
+								year: month === 12 ? year + 1 : year,
+								month: month === 12 ? 1 : month + 1,
+								date: i,
+								type: 'next'
+							}
+							// 下个月的前几天包含今天
+							this.theDateIsToday(item)
+							calendarDate.push(item)
+						}
+					}
+					this.dateCache[`${year}-${month}`] = deepClone(calendarDate)
+				}
+				// 进行日期的默认选中
+				if (year === this.getAssignDateInfo(false, 0) && month === this.getAssignDateInfo(false, 1)) {
+					for (let i = 0, len = calendarDate.length; i < len; i++) {
+						if (calendarDate[i].type === 'cur' && calendarDate[i].date === this.getAssignDateInfo(false, 2)) {
+							calendarDate[i].isSelected = true
+							this.dateActive = i
+							break
+						}
+					}
+				}
+				return calendarDate
+			},
+			// 判断日期是否为今天
+			theDateIsToday(item) {
+				if (item.year + '-' + item.month + '-' + item.date === this.getAssignDateInfo(true, 0) + '-' + this.getAssignDateInfo(true, 1) + '-' + this.getAssignDateInfo(true, 2)) {
+					item.isToday = true
+				}
+			},
+			// 初始化收缩时的日历轮播日期信息
+			initCalendarShrinkSwiperDates(swiperChangeType) {
+				let line = null
+				/**
+				 * 日历收缩事件/当前滑动不涉及到到上个/下个月的日期数据
+				 * 日历滑动到上一周并且本周不属于第一行并且上一周选中的日期必须是本月份里面的日期
+				 * 日历滑动到下一周且本周不属于最后一行
+				 */
+				const curDateLine = Math.floor(this.dateActive / 7)
+				if (!swiperChangeType ||
+						(swiperChangeType === -1 && curDateLine !== 0 && this.calendarSwiperDates[this.current][(curDateLine - 1) * 7].type === 'cur') ||
+						(swiperChangeType === 1 && curDateLine + 1 !== this.calendarSwiperDates[this.current].length / 7)
+				) {
+					// 计算当前周选中日期处于日历中的哪一行位置
+					const curCalendarSwiperDates = this.calendarSwiperDates[this.current]
+					line = Math.floor(curCalendarSwiperDates.map(item => item.type === 'cur' ? item.date : -1).indexOf(this.getAssignDateInfo(false, 2)) / 7)
+					// 收缩日历滑动事件需要进行日期的选中处理
+					if (swiperChangeType) {
+						// 将当前选中日期清除选中状态
+						this.calendarSwiperDates[this.current][this.dateActive].isSelected = false
+						// 重新计算日期选中高亮并把下一个日期进行选中
+						this.dateActive = line * 7
+						this.calendarSwiperDates[this.current][this.dateActive].isSelected = true
+					}
+				} else { // 收缩日历滑动事件
+					// 将当前选中日期清除选中状态
+					this.calendarSwiperDates[this.current][this.dateActive].isSelected = false
+					// 涉及了上个月/下个月的日期数据,需要重新计算展开日历轮播的日期数据
+					let currentNum = this.current + swiperChangeType
+					currentNum = currentNum > 2 ? 0 : currentNum < 0 ? 2 : currentNum
+					this.current = currentNum
+					// 计算上一周/下一周选中日期处于日历中的哪一行位置
+					const curCalendarSwiperDates = this.calendarSwiperDates[this.current]
+					line = Math.floor(curCalendarSwiperDates.map(item => item.type === 'cur' ? item.date : -1).indexOf(this.getAssignDateInfo(false, 2)) / 7)
+					// 重新计算日期选中高亮并把下一个日期进行选中
+					this.dateActive = line * 7
+					this.calendarSwiperDates[this.current][this.dateActive].isSelected = true
+				}
+				const cur = this.generateShrinkCalendar(0, line)
+				const prev = this.generateShrinkCalendar(-1, line)
+				const next = this.generateShrinkCalendar(1, line)
+				// 根据shrinkCurrent来判断相邻的轮播存放哪些日历数据
+				if (this.shrinkCurrent === 0) {
+					this.calendarSwiperShrinkDates = [cur, next, prev]
+				} else if (this.shrinkCurrent === 1) {
+					this.calendarSwiperShrinkDates = [prev, cur, next]
+				} else if (this.shrinkCurrent === 2) {
+					this.calendarSwiperShrinkDates = [next, prev, cur]
+				}
+			},
+			// 生成收缩的日历数据
+			generateShrinkCalendar(type, line) {
+				// 返回当前这一周的日期数据
+				if (type === 0) {
+					return this.calendarSwiperDates[this.current].slice(line * 7, (line + 1) * 7)
+				}
+				// 返回上一周的日期数据
+				if (type === -1) {
+					// 当前选中的日期是否位于第一行
+					if (line === 0) {
+						/**
+						 * 当前日历的第一行是否包含有上个月的日期
+						 * 如果有包含,则返回上个月的倒数第二行日期
+						 * 如果没有包含,则返回上个月的倒数第一行日期
+						 */
+						// 计算上个月的索引值
+						const prevIndex = this.current === 0 ? 2 : this.current - 1
+						// 获取上个月的日期数据
+						const prevCalendarSwiperDates = this.calendarSwiperDates[prevIndex]
+						// 获取上个月的日历行数
+						const prevCalendarSwiperDatesLine = prevCalendarSwiperDates.length / 7
+						if (this.calendarSwiperDates[this.current][0].type === 'prev') { // 倒数第二行
+							return prevCalendarSwiperDates.slice((prevCalendarSwiperDatesLine - 2) * 7, (prevCalendarSwiperDatesLine - 1) * 7)
+						} else { // 倒数第一行
+							return prevCalendarSwiperDates.slice((prevCalendarSwiperDatesLine - 1) * 7)
+						}
+					} else {
+						return this.calendarSwiperDates[this.current].slice((line - 1) * 7, line * 7)
+					}
+				}
+				// 返回下一周的日期数据
+				if (type === 1) {
+					// 计算当前日历月份总共有多少行
+					const curMonthMaxLine = this.calendarSwiperDates[this.current].length / 7
+					// 当前选中的日期是否位于最后一行
+					if (line === curMonthMaxLine - 1) {
+						/**
+						 * 当前日历的最后一行是否包含有下个月的日期
+						 * 如果有包含,则返回下个月的第二行日期
+						 * 如果没有包含,则返回上个月的第一行日期
+						 */
+						// 计算下个月的索引值
+						const nextIndex = this.current === 2 ? 0 : this.current + 1
+						// 获取下个月的日期数据
+						const nextCalendarSwiperDates = this.calendarSwiperDates[nextIndex]
+						// 获取下个月的日历行数
+						const nextCalendarSwiperDatesLine = nextCalendarSwiperDates.length / 7
+						if (this.calendarSwiperDates[this.current][this.calendarSwiperDates[this.current].length - 1].type === 'next') { // 第二行
+							return nextCalendarSwiperDates.slice(7, 14)
+						} else { // 第一行
+							return nextCalendarSwiperDates.slice(0, 7)
+						}
+					} else {
+						return this.calendarSwiperDates[this.current].slice((line + 1) * 7, (line + 2) * 7)
+					}
+				}
+			},
+			// 展开日历轮播切换
+			swiperChange(type) {
+				// 通过点击上个月/下个月日期进行切换,不需要默认选中下个月的一号,直接选中点击的那个日期
+				if (!this.swiperByClick && this.swiperMode === 'open') {
+					this.getPrevOrNextDate(type)
+				}
+				setTimeout(() => { // 设置定时器是为了防止轮播切换时生成数据造成页面卡顿
+					this.initCalendarSwiperDates(() => {
+						this.swiperMode === 'close' && this.initCalendarShrinkSwiperDates()
+					}) // 初始化日历轮播日期信息
+				}, this.swiperMode === 'open' ? this.duration : 0)
+			},
+			// 收缩日历轮播切换
+			shrinkSwiperChange(type) {
+				// 默认选中下个星期的开始日期
+				this.getPrevOrNextStartDate(type)
+				setTimeout(() => { // 设置定时器是为了防止轮播切换时生成数据造成页面卡顿
+					this.initCalendarShrinkSwiperDates(type) // 初始化日历轮播日期信息
+				}, this.duration)
+			},
+			// 获取上一个月/下一个月的一号日期
+			getPrevOrNextDate(type) {
+				const year = this.getAssignDateInfo(false, 0)
+				let month = this.getAssignDateInfo(false, 1)
+				month = month + type
+				// 判断切换月份时选中当前日期高亮还是一号,若选中当前日期高亮需进行大小判断
+				const curActiveDate = this.getAssignDateInfo(false, 2)
+				const maxDate = new Date(year, month, 0).getDate()
+				const date = this.changeSetDefault ? 1 : curActiveDate > maxDate ? maxDate : curActiveDate
+				this.selectedDate = parseTime(new Date(year, month - 1, date), '{y}-{m}-{d}')
+			},
+			// 获取上个星期/下一星期的开始日期
+			getPrevOrNextStartDate(type) {
+				const date = this.calendarSwiperShrinkDates[this.shrinkCurrent][0]
+				this.selectedDate = parseTime(new Date(date.year, date.month - 1, date.date), '{y}-{m}-{d}')
+			},
+			// 前往某一天 格式 YYYY-MM | YYYY-MM-DD
+			goToDate(date = this.today) {
+				try {
+					if (date.split('-').length < 2 || date.split('-').length > 3) throw '参数有误'
+					if (date.split('-').length === 2) {
+						date += '-01'
+					}
+				} catch (err) {
+					throw Error('请检查参数是否符合规范')
+				}
+				this.selectedDate = date
+				this.initCalendarSwiperDates(() => {
+					this.initCalendarShrinkSwiperDates()
+				})
+			},
+			// 日历轮播展开的情况下选择日期
+			chooseDate(dateInfo, dateIndex) {
+				// 重复点击后续不做处理
+				if (dateInfo.isSelected) return false
+				// 是否点击了上个月份的后几天或者点击了下个月份的前几天
+				if (dateInfo.type !== 'cur') {
+					if (dateInfo.type === 'prev') { // 点击了上个月份的后几天,滑到上个月
+						this.current = this.current === 0 ? 2 : this.current - 1
+					} else { // 点击了下个月份的前几天,滑到下个月
+						this.current = this.current === 2 ? 0 : this.current + 1
+					}
+					// 将选中日期赋值为当前点击的那个日期
+					this.selectedDate = parseTime(new Date(dateInfo.year, dateInfo.month - 1, dateInfo.date), '{y}-{m}-{d}')
+					this.swiperByClick = true
+					return false
+				}
+				// 将当前选中的日期清空并选中最新的日期
+				this.calendarSwiperDates[this.current][this.dateActive].isSelected = false
+				this.dateActive = dateIndex
+				const date = this.calendarSwiperDates[this.current][this.dateActive]
+				date.isSelected = true
+				this.selectedDate = parseTime(new Date(date.year, date.month - 1, date.date), '{y}-{m}-{d}')
+				this.dateClick = true
+			},
+			// 日历轮播收缩的情况下选择日期
+			chooseShrinkDate(dateInfo, dateIndex) {
+				// 重复点击后续不做处理
+				if (dateInfo.isSelected) return false
+				this.dateClick = true
+				// 是否点击了上个月份的后几天或者点击了下个月份的前几天
+				if (dateInfo.type !== 'cur') {
+					if (dateInfo.type === 'prev') { // 点击了上个月份的后几天,切换到上个月
+						this.current = this.current === 0 ? 2 : this.current - 1
+					} else { // 点击了下个月份的前几天,切换到下个月
+						this.current = this.current === 2 ? 0 : this.current + 1
+					}
+					this.dateActive = dateIndex
+					// 将选中日期赋值为当前点击的那个日期
+					this.selectedDate = parseTime(new Date(dateInfo.year, dateInfo.month - 1, dateInfo.date), '{y}-{m}-{d}')
+					return false
+				}
+				// 计算当前选中日期之前有多少个日期
+				const dateActiveLine = Math.floor(this.dateActive / 7) * 7
+				// 将当前选中的日期清空并选中最新的日期
+				this.calendarSwiperDates[this.current][this.dateActive].isSelected = false
+				this.dateActive = dateIndex + dateActiveLine
+				const date = this.calendarSwiperDates[this.current][this.dateActive]
+				date.isSelected = true
+				this.selectedDate = parseTime(new Date(date.year, date.month - 1, date.date), '{y}-{m}-{d}')
+			},
+			// 向父组件传递当前选中数据
+			emitDate() {
+				const { year, month, date } = this.calendarSwiperDates[this.current][this.dateActive]
+				const e = {
+					selectedDate: this.selectedDate,
+					year,
+					month,
+					date
+				}
+				this.$emit('change', e)
+			}
+		}
+	}
+</script>
+
+<style>
+	.zsy_calendar {
+		width: 100%;
+		padding: 20rpx 0;
+		box-sizing: border-box;
+		background-color: #fff;
+		border-radius: 20rpx;
+	}
+	
+	/* 日历顶部信息 */
+	.calendar_info {
+		display: flex;
+		align-items: center;
+		padding: 0 20rpx;
+	}
+	.calendar_info .title {
+		font-size: 30rpx;
+		font-weight: bold;
+		color: #2C2C2C;
+	}
+	.calendar_info .desc {
+		margin-left: 29rpx;
+		font-size: 28rpx;
+		color: #959595;
+	}
+	.calendar_info .backToToday {
+		margin-left: auto;
+		font-size: 24rpx;
+	}
+	/* 日历顶部信息 */
+	
+	/* 日历周数 */
+	.calendar_week {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		font-size: 26rpx;
+		color: #959595;
+		margin: 20rpx 0rpx;
+	}
+	.calendar_week .calendar_week__item {
+		width: calc(100% / 7);
+		text-align: center;
+	}
+	/* 日历周数 */
+	
+	/* 日历切换模式 */
+	.calendar_toggle {
+		position: relative;
+		padding: 10rpx 0;
+		margin: 10rpx 20rpx 0;
+		display: flex;
+		justify-content: center;
+	}
+	.calendar_toggle .icon {
+		width: 30rpx;
+		height: 30rpx;
+		background-image: url('../../static/zsy-calendar/arrow.png');
+		background-size: contain;
+		background-repeat: no-repeat;
+		margin: 0 auto;
+		transition: all .3s;
+	}
+	.icon.down {
+		transform: rotate(180deg);
+	}
+	.calendar_toggle::before, .calendar_toggle::after {
+		width: calc(50% - 30rpx);
+		border-top: solid 2rpx #EAEAEA;
+		content: '';
+		display: block;
+		position: absolute;
+		top: 50%;
+		transform: translateY(-50%);
+	}
+	.calendar_toggle::before {
+		left: 0;
+	}
+	.calendar_toggle::after {
+		right: 0;
+	}
+	/* 日历切换模式 */
+</style>

+ 13 - 3
package.json

@@ -1,5 +1,15 @@
 {
-    "dependencies": {
-        "@escook/request-miniprogram": "^0.2.1"
+    "id": "zsy-calendar",
+    "name": "zsy-calendar 仿钉钉打卡统计日历 支持周与月的滑动切换",
+    "version": "1.0.4",
+    "description": "仿钉钉打卡统计日历、支持周与月的滑动切换",
+    "keywords": [
+        "兼容多端、源码注释清晰方便二次开发"
+    ],
+    "dcloudext": {
+        "category": [
+            "前端组件",
+            "通用组件"
+        ]
     }
-}
+}

+ 179 - 40
pages/home/home.vue

@@ -4,7 +4,8 @@
 		<top-box v-bind:timeStamp="timeStamp"></top-box>
 		<!-- 滚动条 -->
 		<view class="notice">
-			<u-notice-bar :text="noticeMsg" mode="closable" bgColor="rgba(73, 125, 220, 0.25)" color="#FFF" url="/subpkg/mymsg/mymsg"></u-notice-bar>
+			<u-notice-bar :text="noticeMsg" mode="closable" bgColor="rgba(73, 125, 220, 0.25)" color="#FFF"
+				url="/subpkg/mymsg/mymsg"></u-notice-bar>
 		</view>
 		<!-- 卡片区域 -->
 		<view class="card-box">
@@ -13,9 +14,15 @@
 			<view class="card-item" style="width: 100%;">
 				<view class="card-info">
 					<view :class="icon.record"></view>
-					<text class="info-text">今日出勤</text>
+					<text class="info-text">今日打卡</text>
+				</view>
+				<view class="home-title-text">
+					<view class="msg">今日出勤:</view>
+					<view class="card-content">{{homeData.attendance}}</view>
+				</view>
+				<view class="calendar_container">
+					<zsyCalendar :sundayIndex="6" @change="dateHandler" />
 				</view>
-				<view></view>
 			</view>
 
 			<!-- 今日课程 -->
@@ -25,23 +32,46 @@
 					<text class="info-text">今日课程</text>
 				</view>
 				<!-- 内容 -->
-				<view style="display: flex; align-items: center;">
-					<view style="color: #3B4144;margin-left: 30rpx;font-size: 30rpx;font-weight: bold;">个人课程数:</view>
-					<view class="card-content">{{classNum}}</view>
+				<view class="home-title-text" style="justify-content: space-between;">
+					<view style="display: flex;align-items: center;">
+						<view class="msg">课程数:</view>
+						<view class="card-content">{{homeData.classList.length}}</view>
+					</view>
+
+
+					<view class="time-box" style=" margin:-20rpx 30rpx 20rpx 0;">
+						<view class="msg">今日课程在:</view>
+						<view style="display: flex; align-items: center;">
+							<u-count-down :time="8*60 * 60 * 1000" format="HH:mm:ss" autoStart millisecond
+								@change="dayChange">
+								<view class="time">
+									<view class="time__custom">
+										<text
+											class="time__custom__item">{{ dayTimeData.hours>10?dayTimeData.hours:'0'+dayTimeData.hours}}</text>
+									</view>
+									<text class="time__doc">:</text>
+									<view class="time__custom">
+										<text class="time__custom__item">{{ dayTimeData.minutes }}</text>
+									</view>
+									<text class="time__doc">:</text>
+									<view class="time__custom">
+										<text class="time__custom__item">{{ dayTimeData.seconds }}</text>
+									</view>
+								</view>
+							</u-count-down>
+							<view class="msg">后结束</view>
+						</view>
+					</view>
+
+
 				</view>
 				<view class="u-page__item">
-					<u-collapse accordion :border="false">
-						<u-collapse-item title="孩子今日课程:">
-							<view style="display: flex; flex-wrap: wrap;">
-								<u-tag v-for="(item,index) in classList" :key="index" :text="item" plain shape="circle"
-									size="mini"></u-tag>
-							</view>
+					<u-collapse :border="false">
+						<u-collapse-item title="今日课程列表:">
+							<u-subsection :list="homeData.classList" :current="homeData.classCurrent"></u-subsection>
 						</u-collapse-item>
 						<u-collapse-item title="今日授课老师:">
-							<view style="display: flex; flex-wrap: wrap;">
-								<u-tag v-for="(item,index) in teacherList" :key="index" :text="item" plain
-									shape="circle" size="mini"></u-tag>
-							</view>
+							<u-subsection :list="homeData.teacherList" :current="homeData.classCurrent"></u-subsection>
 						</u-collapse-item>
 					</u-collapse>
 				</view>
@@ -50,8 +80,40 @@
 			<view class="card-item">
 				<view class="card-info">
 					<view :class="icon.classNow"></view>
-					<text class="info-text">正在参与课程</text>
+					<text class="info-text">当前课堂</text>
+				</view>
+				<view style="display: flex; justify-content: center; flex-direction: column;">
+					<view class="home-title-text">
+						<view class="msg">孩子正在上:
+						</view>
+						<view class="card-content">{{homeData.classList[homeData.classCurrent]}}课</view>
+					</view>
+					<view class="home-title-text">
+						<view class="msg">教学老师是:
+						</view>
+						<view class="card-content">{{homeData.teacherList[homeData.classCurrent]}}</view>
+					</view>
+					<!-- 倒计时组件 -->
+					<u-count-down :time="60 * 60 * 1000" format="HH:mm:ss" autoStart millisecond @change="classChange">
+						<view class="time">
+							<view class="time__custom">
+								<text
+									class="time__custom__item">{{ classTimeData.hours>10?classTimeData.hours:'0'+classTimeData.hours}}</text>
+							</view>
+							<text class="time__doc">:</text>
+							<view class="time__custom">
+								<text class="time__custom__item">{{ classTimeData.minutes }}</text>
+							</view>
+							<text class="time__doc">:</text>
+							<view class="time__custom">
+								<text class="time__custom__item">{{ classTimeData.seconds }}</text>
+							</view>
+						</view>
+					</u-count-down>
+
 				</view>
+
+
 			</view>
 			<!-- 评测 -->
 			<view class="card-item" @click="navHomework">
@@ -93,28 +155,45 @@
 </template>
 
 <script>
+	import zsyCalendar from '@/components/zsy-calendar/zsy-calendar'
 	import {
 		mapState
 	} from 'vuex'
 	export default {
+		name: 'Calendar',
+		components: {
+			zsyCalendar
+		},
 		computed: {
 			...mapState('m_chart', ['todayData']),
 			...mapState('m_children', ['childreninfo']),
 			...mapState('m_parent', ['myData'])
 		},
-		components: {
-		},
+		components: {},
 		data() {
 			return {
 				//滚动通知
-				noticeMsg:'',
-				//今日课堂数
-				classNum: 5,
-				//今日课程
-				classList: ['数学', '语文', '英语', '体育', '思品'],
-				//授课老师
-				teacherList: ['张老师', '李老师', '王老师', '陈老师', '黄老师'],
-
+				noticeMsg: '',
+				//首页数据
+				homeData: {
+					//出勤状况
+					attendance: '已打卡',
+					//今日课程
+					classList: ['数学', '语文', '英语', '体育', '思品'],
+					//授课老师
+					teacherList: ['张老师', '李老师', '王老师', '陈老师', '黄老师'],
+					//当前课程
+					classCurrent: 2,
+				},
+				//图标
+				icon: {
+					record: 't-icon t-icon-dakaqiandao',
+					class: 't-icon t-icon-banji',
+					classNow: 't-icon t-icon-shijian',
+					evaluation: 't-icon t-icon-ceping',
+					homework: 't-icon t-icon-bianji1',
+					activity: 't-icon t-icon-huodong'
+				},
 				//评测进度条属性
 				evaluationOpts: {},
 				//作业进度条属性
@@ -123,16 +202,9 @@
 				activityOpts: {},
 				//时间戳
 				timeStamp: '',
-				//图标
-				icon: {
-					record : 't-icon t-icon-dakaqiandao',
-					class: 't-icon t-icon-banji',
-					classNow: 't-icon t-icon-shijian',
-					evaluation: 't-icon t-icon-ceping',
-					homework: 't-icon t-icon-bianji1',
-					activity: 't-icon t-icon-huodong'
-				}
-				
+				//倒计时时间
+				dayTimeData: {},
+				classTimeData: {}
 			}
 		},
 		onLoad() {
@@ -149,10 +221,10 @@
 		},
 		methods: {
 			//获取滚动通知
-			getNoticeMsg(){
+			getNoticeMsg() {
 				this.noticeMsg = `您有 ${this.myData.msgList.length} 个通知,请点击查看`
 			},
-			navHomework(){
+			navHomework() {
 				uni.switchTab({
 					url: '/pages/homework/homework'
 				})
@@ -206,7 +278,17 @@
 				this.homeworkOpts = homework;
 				this.activityOpts = activity;
 			},
-
+			//日历事件
+			dateHandler(e) {
+				console.log(e)
+			},
+			//倒计时事件
+			classChange(e) {
+				this.classTimeData = e
+			},
+			dayChange(e) {
+				this.dayTimeData = e
+			}
 
 		},
 	}
@@ -214,4 +296,61 @@
 
 <style lang="scss">
 	@import '@/pages/common/mainpage.scss';
+
+	.calendar_container {
+		height: 100%;
+		width: auto;
+		background-color: #FFF;
+		margin: 10rpx;
+		box-sizing: border-box;
+	}
+
+	.home-title-text {
+		display: flex;
+		align-items: center;
+
+		.msg {
+			color: #3B4144;
+			margin-left: 30rpx;
+			font-size: 30rpx;
+			font-weight: bold;
+		}
+	}
+
+	.time {
+		@include flex;
+		align-items: center;
+		margin-left: 30rpx;
+		margin-bottom: 5rpx;
+
+		&__custom {
+			margin-top: 4px;
+			width: 22px;
+			height: 22px;
+			background-color: $u-primary;
+			border-radius: 4px;
+			/* #ifndef APP-NVUE */
+			display: flex;
+			/* #endif */
+			justify-content: center;
+			align-items: center;
+
+			&__item {
+				color: #fff;
+				font-size: 12px;
+				text-align: center;
+			}
+		}
+
+		&__doc {
+			color: $u-primary;
+			padding: 0px 4px;
+		}
+
+		&__item {
+			color: #606266;
+			font-size: 15px;
+			margin-right: 4px;
+		}
+	}
 </style>

+ 68 - 83
pages/homework/homework.vue

@@ -2,22 +2,20 @@
 	<view class="homework-container">
 		<!-- 头部区域 -->
 		<top-box :timeStamp="timeStamp"></top-box>
-		<text class="card-title">孩子今日任务</text>
-		<view class="card-box">
+		<view class="module-container">
+			<u-tabs :list="list" :current="current" @change="changeModule"
+				:activeStyle="{color: '#5b7cff',fontWeight: 'bold',transform: 'scale(1.04)',}"
+				:inactiveStyle="{fontWeight: 'bold',transform: 'scale(1)'}" lineColor="#5b7cff" lineWidth="60"></u-tabs>
 
-			<view class="box-list">
-				<view class="box-card" v-for="(item,index) in card" :key="index" :style="item.color">
-					<view>
-						<view class="card-name">{{item.title}}</view>
-					</view>
-					<view class="radio">
-						<view class=""></view>
-					</view>
+			<!-- 详情模块 -->
+			<view class="module-box" v-if="current == 0">
+				<view class="card">
+					<text class="card-title">今日评测数:</text>
+					<view class="image"></view>
 				</view>
 			</view>
-			<view class="box-detail">
-
-			</view>
+			<view class="module-box" v-if="current == 1"></view>
+			<view class="module-box" v-if="current == 2"></view>
 		</view>
 
 	</view>
@@ -27,19 +25,31 @@
 	export default {
 		data() {
 			return {
+				//模块名
+				list: [{
+					name: '评测详情'
+				}, {
+					name: '活动详情'
+				}, {
+					name: '作业详情'
+				}],
+				//当前模块
+				current: 0,
 				//时间戳
 				timeStamp: '',
 				//卡片内容
 				card: [{
-					title: '评测',
-					color: 'background-color: #0080ff;',
-					icon: ''
+					title: '评测详情',
+					color: 'background-color: #8c8ff6;',
+					icon: '$test-base64'
 				}, {
-					title: '作业',
-					color: 'background-color: #ff8caf;'
+					title: '作业详情',
+					color: 'background-color: #64d2fd;',
+					icon: '$homework-base64'
 				}, {
-					title: '活动',
-					color: 'background-color: #f9c752;'
+					title: '活动详情',
+					color: 'background-color: #ff8caf;',
+					icon: '$actility-base64'
 				}]
 			};
 		},
@@ -54,84 +64,59 @@
 				uni.stopPullDownRefresh();
 			}, 1000);
 		},
+		methods: {
+			//更改模块
+			changeModule(index) {
+				this.current = index.index
+			},
+		}
 	}
 </script>
 
 <style lang="scss">
 	.homework-container {
-		display: flex;
-		flex-direction: column;
 
 		.home-topinfo {
-			background: linear-gradient($color-green, $page-background-color) !important;
-		}
-
-		.card-title {
-			font-size: 45rpx;
-			font-weight: bold;
-			color: #FFF;
-			padding: 0 0 30rpx 50rpx;
-			margin-top: -90rpx;
+			background: linear-gradient(#ff8419, $page-background-color) !important;
 		}
+	}
 
-		.card-box {
-			position: relative;
-			box-shadow: 0 6rpx 10rpx rgba(0, 0, 0, 0.1);
-			background-color: #FFF;
-			border-radius: 50rpx;
-			width: 100%;
-			height: 100vh;
-
-			.box-list {
-				margin: 40rpx auto;
-				width: 96%;
-				height: auto;
-				display: flex;
-				flex-direction: row;
-				justify-content: space-between;
-				background-color: #000000;
-
-				.box-card {
-					width: 30%;
-					height: 300rpx;
-					display: flex;
-					flex-direction: column;
-					align-items: center;
-					border-radius: $card-border-radius;
+	.module-container {
+		margin: -70rpx 20rpx 0 20rpx;
 
-					.card-name {
-						color: #FFF;
-						margin: 15rpx 0 15rpx 0;
-						font-size: 35rpx;
-						font-weight: bold;
-					}
+		.card {
+			display: flex;
+			justify-content: space-between;
+			margin: 40rpx auto 20rpx auto;
+			height: 250rpx;
+			width: 96%;
+			background: linear-gradient(to right, #fd76007a,#ff8419);
+			border-radius: $card-border-radius;
+			
+			.card-title{
+				font-size: 40rpx;
+				font-weight: bold;
+				color: #FFF;
+			}
 
-					.radio {
-						width: 140rpx;
-						height: 140rpx;
-						border-radius: 100%;
-						background: #FFF;
-					}
-				}
+			.image {
+				position: relative;
+				top: -40rpx;
+				right: -200rpx;
+				height: 500rpx;
+				width: 500rpx;
+				background-image: $test-base64;
+				background-size: contain;
+				background-repeat: no-repeat;
+				z-index: 2;
 			}
 		}
-	}
 
-	.box-detail {
-		position: relative;
-		background-color: #666;
-		border-radius: $card-border-radius;
-		margin: 40rpx auto;
-		width: 96%;
-		height: 500rpx;
 	}
 
-	.box-detail::before {
-		content: " ";
-		position: absolute;
-		right: 13%;
-		top: -35rpx;
-		border-bottom: 50px solid #666;
-		border-right-color: #666;
+	.t-icon {
+		margin: 20rpx;
+		width: 110rpx;
+		height: 110rpx;
 	}
 </style>

+ 1 - 1
pages/init/init.vue

@@ -1,6 +1,6 @@
 <template>
 	<view>
-		
+		<u-loading-page :loading="true" bgColor="#F5F5F5"></u-loading-page>
 	</view>
 </template>
 

二進制
static/zsy-calendar/arrow.png


+ 2 - 2
uni_modules/qiun-data-charts/js_sdk/u-charts/config-ucharts.js

@@ -341,9 +341,9 @@ const cfu = {
 		"color": color,
 		"padding": [
 			0,
-			-50,
+			-20,
 			0,
-			-50
+			-20
 		],
 		"dataLabel": false,
 		"dataPointShape": false,

+ 1 - 1
uni_modules/uview-ui/components/u-subsection/u-subsection.vue

@@ -221,7 +221,7 @@ export default {
         height: 32px;
         background-color: rgb(238, 238, 239);
         padding: 3px;
-        border-radius: 3px;
+        border-radius: 10px;
         align-items: stretch;
 
         &__bar {