vue 自己捣鼓周日程日历组件
vue
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
项目地址:https://gitcode.com/gh_mirrors/vu/vue
免费下载资源
·
需求:想要一个周日程表,记录每天的计划,点击可查看详情。可自定义时间段通过后台获取时间段显示
分析:
通过需求,超级课程表app这款软件其中课表和这个需求很像,只不过这个需求第一列的时间段是自定义的,不是上午下午两个,但是原理都差不多
原本想找一些第三方插件使用,由于时间充足,而且自己也想封装成一个组件方便以后或许会碰到类似的需求,于是自己手动写了一个日程日历。
效果如下
优化修改:数据量大时,格子显示太长问题,这里进行了优化,如果超过2个就进行展开与收缩操作
例子
<template>
<div id="app">
<WSchedule :planList="timePeriodList" :isFirstDayOfMondayOrSunday="7" :hasNumExpend="2" @handleDetail="handleDetail" @handleCardDetail="handleCardDetail" @changeWeek="changeWeek">
<template v-slot:thing="{row}">
<span>时段:{{ row.timePeriod }}</span>
<span>课程:{{ row.course }}</span>
<span>值班员:{{ row.watchman }}</span>
<span>地点:{{ row.place }}</span>
</template>
</WSchedule>
</div>
</template>
<script>
import WSchedule from '@/components/WeekSchedule'
export default {
name: 'App',
components: {
WSchedule,
},
data() {
/**
* 获取当天时间
* @returns {string}
*/
function getCurDay(num = 0) {
var datetime = new Date();
var year = datetime.getFullYear();
var month = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
let day = datetime.getDate()
if ((day + num) > 0) {
day = (day + num) < 10 ? "0" + (datetime.getDate() + num) : datetime.getDate() + num;
} else {
day = (day - num) < 10 ? "0" + (datetime.getDate() - num) : datetime.getDate() - num;
}
return `${year}-${month}-${day}`
}
return {
timePeriodList: [
{
timePeriod: '8:00~10:00',
schedule: [
{
isExpend: false,
[getCurDay()]: [
{
timePeriod: '8:00~10:00',
date: getCurDay(),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 1,
},
{
timePeriod: '8:00~10:00',
date: getCurDay(),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 2,
},
{
timePeriod: '8:00~10:00',
date: getCurDay(),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 3,
},
],
},
{
isExpend: false,
[getCurDay(-1)]: [
{
id: 1,
timePeriod: '8:00~10:00',
date: getCurDay(-1),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 1,
},
]
}
]
},
{
timePeriod: '12:00~14:00',
schedule: [
{
isExpend: false,
[getCurDay()]: [
{
timePeriod: '12:00~14:00',
date: getCurDay(),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 2,
},
{
timePeriod: '12:00~14:00',
date: getCurDay(),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 3,
},
],
},
{
isExpend: false,
[getCurDay(-1)]: [
{
timePeriod: '12:00~14:00',
date: getCurDay(-1),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 1,
},
{
timePeriod: '实验室1',
date: getCurDay(-1),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 1,
},
{
timePeriod: '实验室1',
date: getCurDay(-1),
course: '大学英语',
watchman: '井底的蜗牛',
place: '测试地点',
status: 1,
},
]
}
]
},
{
timePeriod: '14:00~16:00',
schedule: []
},
],
}
},
methods: {
/**
* 点击详情
* @param row
*/
handleDetail(row){
console.log(row)
},
/**
* 点击卡片查看全部内容
*/
handleCardDetail(row) {
console.log(row)
},
/**
* 切换周
* @param date
*/
changeWeek(date){
console.log(date)
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
封装组件 weekTools WeekSchedule/index.vue
<template>
<div class="course-week">
<div class="week-top">
<div class="week-btn-wrap">
<span @click="getLastWeek">上周</span>
<span @click="getCurWeek">本周</span>
<span @click="getNextWeek">下周</span>
</div>
<span class="w-today-date"> {{ todayDate }}</span>
<div class="w-choose-status">
<div v-for="sta in cardStatus">
<span class="square" :style="{background:sta.color}"></span>
<span class="title">{{ sta.title }}</span>
</div>
</div>
</div>
<div class="week-table">
<div class="table-header">
<div class="table-week">
<template v-for="(item,index) of weeks">
<span class="w-first" v-if="index===0" :key="index">{{ item }}</span>
<span v-else :key="index">{{ item }}</span>
</template>
</div>
<div class="w-table-date">
<template v-for="(item,index) of months">
<span class="w-first" v-if="index===0" :key="index">
</span>
<template v-else>
<span :key="index" class="w-day-item" :class="{'w-isCurDate':item&&item.isCurDate}">
{{ `${item && item.isCurDate ? item && item.showDate + '(今天)' || '' : item && item.showDate || ''}` }}
</span>
</template>
</template>
</div>
</div>
<div class="w-time-period-list">
<ul class="w-time-period-row">
<!--循环时段,看时段有多少个-->
<template v-if="planList.length>0">
<li class="w-time-period-col" v-for="(period,p_index) in planList"
:key="`period${p_index}`">
<!--第一列显示时段-->
<div class="w-time-period"> {{ period.timePeriod }}</div>
<!-- 后面显示周一到周日的计划-->
<div class="w-row-day">
<!-- 循环显示每周的日期-->
<template v-for="(month,m_index) of months">
<!-- v-if="month" 去除数据处理的时候移除数组第一个为empty的问题-->
<div v-if="month" :key="`month${m_index}`" class="w-things" @click="handleCardDetail(month,period)">
<!-- 循环每个时间段的计划-->
<template v-for="(card,t_index) of period.schedule">
<template v-for="(single,sIndex) in card[month.date]">
<template v-if="!card.isExpend">
<div v-if="single.date===month.date&&sIndex<hasNumExpend"
:key="`thing${sIndex}`"
class="w-thing-item"
@click="handleDetail(single)"
:style="{background: cardStatus[single.status].color}">
<slot name="thing" :row="single"></slot>
</div>
</template>
<template v-if="card.isExpend">
<div v-if="single.date===month.date"
:key="`thing${sIndex}`"
class="w-thing-item"
@click="handleDetail(single)"
:style="{background: cardStatus[single.status].color}">
<slot name="thing" :row="single"></slot>
</div>
</template>
<div class="w_expand"
v-if="card[month.date].length>hasNumExpend&&(card[month.date].length-1)===sIndex&&!card.isExpend&&single.date===month.date"
@click="handleExpand(card)">展开
</div>
<div class="w_shrink"
v-if="card[month.date].length>hasNumExpend&&(card[month.date].length-1)===sIndex&&card.isExpend&&single.date===month.date"
@click="handleExpand(card)">收缩
</div>
</template>
</template>
</div>
</template>
</div>
</li>
</template>
<div class="w-noMore" v-else><span>暂无数据</span></div>
</ul>
</div>
</div>
</div>
</template>
<script>
import { formatDate , getCurDay } from "@/utils/weekTools";
export default {
name: 'WeekSchedule',
props: {
planList: {
type: Array,
default: []
},
//卡片状态
cardStatus: {
type: Object,
default: () => {
return {
1: {
title: '已过期',
color: '#9CADADB7'
},
2: {
title: '进行中',
color: '#FF6200'
},
3: {
title: '未开始',
color: '#3291F8'
},
}
}
},
//第一列是星期几
isFirstDayOfMondayOrSunday: {
type: Number,
default: 1,
},
hasNumExpend:{
type:Number,
default:2
}
},
data () {
return {
weeks: [
'时段', '周一', '周二', '周三', '周四', '周五', '周六', '周日',
],
todayDate: '',
months: [],
curDate: '',
nowDate: new Date(),
}
},
watch: {
isFirstDayOfMondayOrSunday: {
handler (val) {
if (val > 1) {
let arr = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
const arr1 = arr.slice(val - 1)
const arr2 = arr.slice(0, val - 1)
this.weeks = ['时段', ...arr1, ...arr2]
}
},
immediate: true
}
},
mounted () {
this.getCurWeek()
},
methods: {
//展开与缩放操作
handleExpand (row) {
row.isExpend = !row.isExpend
},
/**
* 获取 时间
* @param time
*/
getWeek (time) {
this.curDate = new Date(time)
//当前是周几
const whichDay = time.getDay()
let num = 0
if (this.isFirstDayOfMondayOrSunday <= whichDay) {
num = this.isFirstDayOfMondayOrSunday
} else {
num = this.isFirstDayOfMondayOrSunday - 7
}
const weekDay = time.getDay() - num
time = this.addDate(time, weekDay * -1)
for (let i = 0; i < 7; i++) {
const { year, month, day } = formatDate(i === 0 ? time : this.addDate(time, 1))
this.months.push({
date: `${year}-${month}-${day}`,
showDate: `${month}-${day}`,
timestamp: new Date(`${year}-${month}-${day}`).getTime()
})
}
this.months.sort((a, b) => a.timestamp - b.timestamp)
delete this.months[0]
this.todayDate = `${this.months[1].date} ~ ${this.months[this.months.length - 1].date}`
},
/**
* 处理日期
* @param date
* @param n
* @returns {*}
*/
addDate (date, n) {
date.setDate(date.getDate() + n)
return date
},
/**
* 上周
*/
getLastWeek () {
const date = this.addDate(this.curDate, -7),
{ year, month, day } = formatDate(date),
dateObj = {
date: `${year}-${month}-${day}`,
timestamp: new Date(`${year}-${month}-${day}`).getTime()
}
this.dealDate(date)
this.$emit('changeWeek', dateObj)
},
/**
* 本周
*/
getCurWeek () {
const { year, month, day } = formatDate(new Date()),
dateObj = {
date: `${year}-${month}-${day}`,
timestamp: new Date(`${year}-${month}-${day}`).getTime()
}
this.dealDate(new Date())
this.$emit('changeWeek', dateObj)
},
/**
* 下周
*/
getNextWeek () {
const date = this.addDate(this.curDate, 7),
{ year, month, day } = formatDate(date),
dateObj = {
date: `${year}-${month}-${day}`,
timestamp: new Date(`${year}-${month}-${day}`).getTime()
}
this.dealDate(date)
this.$emit('changeWeek', dateObj)
},
/**
* 显示当天日期状态
* @param date
*/
dealDate (date) {
this.months = ['']
this.getWeek(date)
const curDate = getCurDay()
this.months.forEach(item => {
item.isCurDate = item.date === curDate
})
},
/**
* 点击卡片子内容查看详情
* @param row
*/
handleDetail (row) {
this.$emit('handleDetail', row)
},
/**
* 点击卡片查看全部内容
* @param month
* @param period
*/
handleCardDetail (month, period) {
this.$emit('handleCardDetail', { ...month, ...period })
}
}
}
</script>
<style>
ul {
list-style: none;
}
ul, li {
margin: 0;
padding: 0;
}
.course-week {
width: 100%;
border: 1px solid #ddd;
padding: 1%;
box-sizing: border-box;
}
.week-top {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 40px;
padding: 0 1%;
box-sizing: border-box;
}
.week-top .week-btn-wrap {
width: 200px;
display: flex;
justify-content: space-around;
color: #409EFF;
}
.week-top .week-btn-wrap span {
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 15px;
}
.w-today-date {
font-weight: bold;
font-size: 16px;
}
.w-choose-status {
display: flex;
justify-content: flex-end;
width: 200px;
}
.w-choose-status > div {
width: 100%;
flex: 1;
display: flex;
padding: 0 2%;
white-space: nowrap;
line-height: 20px;
box-sizing: border-box;
}
.w-choose-status > div .square {
display: flex;
width: 16px;
height: 16px;
border-radius: 4px;
box-sizing: border-box;
}
.w-choose-status > div .title {
display: flex;
align-items: center;
line-height: 16px;
padding-left: 4px;
font-size: 14px;
box-sizing: border-box;
}
.week-table {
display: flex;
flex-direction: column;
}
.week-table .table-header {
width: 100%;
height: 80px;
background: #EAEDF2;
display: flex;
flex-direction: column;
align-items: center;
border-bottom: 1px solid #EAEDF2;
box-sizing: border-box;
}
.table-header .w-table-date, .table-week {
width: 100%;
height: 40px;
text-align: left;
display: flex;
justify-content: center;
align-items: center;
}
.table-header .w-table-date > span, .table-week > span {
flex: 1;
color: #000;
height: 100%;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
}
.w-table-date .w-day-item, .table-week .w-day-item {
color: #000;
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
}
.week-table .w-time-period-list {
width: 100%;
}
.w-time-period-list .w-time-period-row {
width: 100%;
min-height: 60px;
}
.w-time-period-col {
width: 100%;
min-height: 60px;
display: flex;
}
.w-time-period-col .w-time-period {
width: 12.5%;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px solid #EAEDF2;
border-bottom: 1px solid #EAEDF2;
box-sizing: border-box;
}
.w-time-period-col .w-row-day {
width: 87.5%;
display: flex;
justify-content: center;
}
.w-row-day .w-things {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
border-left: 1px solid #EAEDF2;
border-bottom: 1px solid #EAEDF2;
box-sizing: border-box;
}
.w-row-day .w-things:last-child {
border-right: 1px solid #EAEDF2;
}
.w-things .w-thing-item {
display: flex;
width: 80%;
font-size: 14px;
flex-direction: column;
justify-content: space-around;
min-height: 90px;
border-radius: 10px;
margin: 2% 1%;
padding: 1% 2%;
cursor: pointer;
color: #fff;
background: #FF6200;
box-sizing: border-box;
transition: all 1s linear .5s;
}
.w-isCurDate {
color: #FF2525 !important;
}
.w-noMore {
min-height: 200px;
padding: 2%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid rgba(156, 173, 173, 0.3);
color: #9CADADB7;
box-sizing: border-box;
}
.w_expand, .w_shrink {
color: #0A98D5;
cursor: pointer;
width: 100%;
padding: 2% 0;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
</style>
js代码 weekTools.js
/**
* 唯一的随机字符串,用来区分每条数据
* @returns {string}
*/
export function getUid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* 计算时间差
* @param beginTime:2022-01-13
* @param endTime:2022-01-13
* @returns {{hours: number, seconds: number, minutes: number, day: number}}
*/
export function dealTime(beginTime, endTime) {
var dateBegin = new Date(beginTime);
var dateEnd = new Date(endTime);
var dateDiff = dateEnd.getTime() - dateBegin.getTime(); //时间差的毫秒数
var day = Math.floor(dateDiff / (24 * 3600 * 1000)); //计算出相差天数
var leave1 = dateDiff % (24 * 3600 * 1000); //计算天数后剩余的毫秒数
var hours = Math.floor(leave1 / (3600 * 1000)); //计算出小时数
//计算相差分钟数
var leave2 = leave1 % (3600 * 1000); //计算小时数后剩余的毫秒数
var minutes = Math.floor(leave2 / (60 * 1000)); //计算相差分钟数
//计算相差秒数
var leave3 = leave2 % (60 * 1000); //计算分钟数后剩余的毫秒数
var seconds = Math.round(leave3 / 1000);
return {
day,
hours,
minutes,
seconds
}
}
/**
* 获取当天时间
* @returns {string}
*/
export function getCurDay() {
var datetime = new Date();
var year = datetime.getFullYear();
var month = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
return `${year}-${month}-${date}`
}
// 日期格式处理
export function formatDate(date) {
var year = date.getFullYear();
var months = date.getMonth() + 1;
var month = (months < 10 ? '0' + months : months).toString();
var day = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()).toString();
return {
year: year.toString(),
month,
day
}
}
/**
* 数组中,某个属性相同的数据放在一块,如把某个日期相同的相连一起
* @param list 传入的数组
* @param prop 那个属性相同的数据
* @returns {*[]}
*/
export function margePropData(list = [], prop) {
let arr = [], tempArr = {};
list.forEach(item => {
if (!tempArr[item[prop]]) {
tempArr[item[prop]] = [item]
} else {
tempArr[item[prop]].push(item)
}
})
for (const tempArrKey in tempArr) {
arr = [...arr, ...tempArr[tempArrKey]]
}
return arr
}
/**
* 合并行
* @param list
* @param prop
*/
export function mergeRows(list = [], prop) {
list.forEach(ele => {
ele.rowspan = 1
})
const len = list.length
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (list[i][prop] === list[j][prop]) {
list[i].rowspan++
list[j].rowspan--
}
}
// 这里跳过已经重复的数据
i = i + list[i].rowspan - 1
}
return list
}
/**
* 根据当前数据的位置,在数组中插入数据
* 如数组【1,2,4,5】想要在2后面插入3,
*1:首先获取到2的下标,
*2:然后获取要插入之前的数据,获取要插入之后的数据,中间就是插入的位置
*3:最后把这三个按顺序合并就得到在想要的位置插入数据
* @param list
* @param index
* @param target
*/
export function insertArrPositionOfIndex(list = [], index = 0, target = {}) {
//根据index 找出小于index的数据放在左边
const leftList = list.filter((t, i) => i < index);
//根据index 找出大于index的数据放在右边
const rightList = list.filter((t, i) => i >= index);
// 最终合并数据
return [...leftList, target, ...rightList]
}
/**
* 校验规则
*/
export function verifyRules(list = [], require = []) {
let message = null
for (const key of require) {
const isEmpty = list.every(it => !it[key.prop])
if (isEmpty) {
message = key.message
break;
}
}
return message
}
/**
* 获取元素下标
* @param dir 为 1:得到正序遍历方法;为 -1: 得到逆序遍历方法。
* @returns {(function(*, *, *=): (number|number|number))|*}
*/
export function findArrIndex(dir = 1) {
return function (array, cb, context) {
let length = array.length;
// 控制初始 index,0 或者 length-1
let index = dir >= 0 ? 0 : length - 1;
// 条件: 在数组范围内;
// 递增或递减:递加 1 或者 -1; 妙啊~
for (; index >= 0 && index <= length - 1; index += dir) {
if (cb.call(context, array[index], index)) return index
}
return -1
}
}
/**
* map转换成数组
* @param target
* @returns {*[]}
* @constructor
*/
export function MapConvertArr(target = {}) {
let list = [];
for (let key in target) {
list.push(target[key]);
}
return list;
}
/**
* 对象数组去重
* @param arr 数组
* @param prop 根据什么字段去重
* @returns {any[]}
*/
export function arrayDeduplication(arr, prop) {
let map = new Map();
return arr.filter(item => !map.has(item[prop]) && map.set(item[prop], 1));
}
/**
* 获取当前天时间
* @param param 【Y:年;M:月;D:日;h:小时;m:分钟;s:秒;】 默认精确到秒
* @returns {*}
*/
export function getCurrentDate(param = 's') {
var now = new Date();
var year = now.getFullYear(); //得到年份
var month = now.getMonth();//得到月份
var date = now.getDate();//得到日期
var day = now.getDay();//得到周几
var hour = now.getHours();//得到小时
var minu = now.getMinutes();//得到分钟
var sec = now.getSeconds();//得到秒
month = month + 1;
if (month < 10) month = "0" + month;
if (date < 10) date = "0" + date;
if (hour < 10) hour = "0" + hour;
if (minu < 10) minu = "0" + minu;
if (sec < 10) sec = "0" + sec;
const arr = {
'Y': year,
'M': year + "-" + month,
'D': year + "-" + month + "-" + date,
'h': year + "-" + month + "-" + date + " " + hour,
'm': year + "-" + month + "-" + date + " " + hour + ":" + minu,
's': year + "-" + month + "-" + date + " " + hour + ":" + minu + ":" + sec
}
return arr[param];
}
/**
* 获取当天时间前后七天时间
* @param day day>0 当天时间的后几天 day<0 当天时间前几天
* @returns {string}
*/
export function getRecentDate(day) {
var date1 = new Date(),
time1 = date1.getFullYear() + "-" + (date1.getMonth() + 1) + "-" + date1.getDate();//time1表示当前时间
var date2 = new Date(date1);
date2.setDate(date1.getDate() + day);
const y = date2.getFullYear();
const m = (date2.getMonth() + 1) > 9 ? (date2.getMonth() + 1) : '0' + (date2.getMonth() + 1)
const d = date2.getDate() > 9 ? date2.getDate() : '0' + date2.getDate()
return y + "-" + m + "-" + d;
}
export function MyDebounce(fn, duration = 100) {
let timer = null
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
fn(...args);
}, duration)
}
}
export function MyThrottle(fn, duration = 100) {
let target = true;
return (...arg) => {
if (!target) {
return false;
}
target = false;
setTimeout(() => {
fn(...arg);
target = true
}, duration)
}
}
GitHub 加速计划 / vu / vue
83
16
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:4 个月前 )
9e887079
[skip ci] 3 个月前
73486cb5
* chore: fix link broken
Signed-off-by: snoppy <michaleli@foxmail.com>
* Update packages/template-compiler/README.md [skip ci]
---------
Signed-off-by: snoppy <michaleli@foxmail.com>
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 6 个月前
更多推荐
已为社区贡献4条内容
所有评论(0)