vue仿钉钉日历组件,移动端,展开折叠月历周历

21 篇文章 0 订阅

重新用ts+v3写了一下,简化了一些功能,需要的自取
功能:
1.左右滚动切换月历周历
2.下面活动事项支持轮播,根据选中天自动更新当天事项
月历
在这里插入图片描述
周历
在这里插入图片描述

<template>
  <div class="calender-container">
    <div class="calender-content" :style="{minHeight:isWeek ? '180px' : '400px'}">
      <!-- 头部 -->
      <div class="calender-title">
        <div class="calender-title-left">{{ checkedDay }}</div>
        <div class="calender-title-right" @click="toggleMove">{{ isWeek ? '按周' : '按月' }}</div>
      </div>
      <!-- 星期头部 -->
      <div class="week-head-con">
        <div class="week-head-item" v-for="(item, index) in WEEK_LIST" :key="index">{{ item.text }}</div>
      </div>
      <!-- 月历 -->
      <transition name="fade">
        <div
          class="month-container"
          @touchstart="handleMoveStart"
          @touchend="handleMoveEnd"
          @touchmove="handleMove"
          :class="{ transition: transition }"
          :style="{ transform: 'translate3d(' + calenderMove.x + 'px,' + calenderMove.y + 'px,0px)', height: isWeek ? '70px' : '300px' }"
        >
          <div v-for="(month, index) in monthList" :key="index" class="month-item">
            <div v-for="(week, weekindex) in month" :key="weekindex" class="month-week">
              <div
                v-for="(day, dayindex) in week"
                :class="{ ischecked: checkedDay == day.date, istoday: day.istoday, }"
                @click.stop="chooseDay(day)"
                :key="dayindex"
                class="month-week-item"
              >
                <div
                  class="week-day"
                  :class="{ ischecked: checkedDay == day.date, othermonth: day.othermonth, istoday: day.istoday, }"
                >
                  <span class="one-day">
                    <i class="day" :class="day.istoday ? 'today' : ''">{{ day.day }}</i>
                  </span>
                  <!-- 有事项标记 -->
                  <div class="thing" v-if="day.thing && day.thing.infos && day.thing.infos.length > 0">
                    <a></a>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </div>
    <div class="things-content" v-if="dateThing && dateThing.length > 0">
      <Swipe :autoplay="5000" indicator-color="#7687E9">
        <SwipeItem v-for="(item, index) in dateThing" :key="index">
          <div class="mb-20">
            <p>
              <span class="title thing-title">今日事项:</span><span class="dis-in-block">{{
                item.title }}</span>
            </p>
            <p>
              <span class="title">地点:</span><span class="dis-in-block">{{ item.address }}</span>
            </p>
            <p>
              <span class="title">时间:</span><span class="dis-in-block">{{ item.date }}</span>
            </p>
          </div>
        </SwipeItem>
      </Swipe>
    </div>
    <div v-else class="things-content">暂无事项</div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, Ref } from 'vue';
import { Swipe, SwipeItem,Popup,DatetimePicker } from 'vant';
const isShowDatePicker = ref<boolean>(false);//是否显示时间选择弹窗
// 头部星期列表
const WEEK_LIST = [
  {
    text: '一'
  },
  {
    text: '二'
  },
  {
    text: '三'
  },
  {
    text: '四'
  },
  {
    text: '五'
  },
  {
    text: '六'
  },
  {
    text: '日'
  }
];
const dateThing = ref([]);//某天事项
// 全部事项,用来插入到日历中
const things = ref([
  {
    date: '2023-04-11',
    infos: [
      {
        title: '人才招聘001',
        address: '北京紫荆城',
        date: '2020-11-11 09:00-18:00'
      },
      {
        title: '人才招聘002',
        address: '上海外滩',
        date: '2020-11-11 09:00-18:00'
      }
    ]
  },
  {
    date: '2023-03-15',
    infos: [
      {
        title: '人才招聘会人才',
        address: '苏州市昆山市',
        date: '2020-11-15 09:00-18:00'
      }
    ]
  },
  {
    date: '2023-03-14',
    infos: [
      {
        title: '人才招聘004',
        address: '苏州市吴中区',
        date: '2020-11-14 09:00-18:00'
      },
      {
        title: '人才招聘005',
        address: '苏州市工业园区',
        date: '2020-11-14 09:00-18:00'
      }
    ]
  },
  {
    date: '2023-03-29',
    infos: [
      {
        title: '人才招聘004',
        address: '苏州市吴中区',
        date: '2020-11-18 09:00-18:00'
      },
      {
        title: '人才招聘005',
        address: '苏州市工业园区',
        date: '2020-11-18 09:00-18:00'
      }
    ]
  },
  {
    date: '2023-02-11',
    infos: [
      {
        title: '人才招聘004',
        address: '苏州市吴中区',
        date: '2020-12-11 09:00-18:00'
      },
      {
        title: '人才招聘005',
        address: '苏州市工业园区',
        date: '2020-12-11 09:00-18:00'
      }
    ]
  }
]);
const dispDate = ref<Date>(new Date());//当前时间
type DayType = {
  date?: string | number;
  istoday?: boolean;
  othermonth?: boolean;
  thing?: {
    infos: any[]; // 替换任何具体的类型,如果有具体的事件信息类型
  };
}
type MonthList = DayType[];
const monthList = ref<MonthList>([]);
const today = ref<Date>(new Date());//当前时间
const checkedDay = ref('');//选中时间
const currentDay = ref<Date>(new Date());//当前时间
const transition = ref<boolean>(true);//是否显示动画
const calenderHeight = ref<number>(-100); //日历的高度
const calenderMove: Ref<{ x: number, y: number }> = ref({ x: 0, y: 0 });
const moveStartX = ref<number>(0); //开始移动的位置x轴
const moveStartY = ref<number>(0); //开始移动的位置y轴
const moveStartCalenderHeight = ref<number>(0); //开始移动的高度初始值
const itemWidth = ref<number>(0); //窗口的宽度
const moveIndex = ref<number>(0); // 左右移动 1 向右移动,-1向左移动,点击天0
const isWeek = ref<boolean>(false); //是否是周历模式
onMounted(() => {
  todayDate();
  get3month();
  initCalenderInfo();
});
const selectDay = ref<Date>(new Date());
/**
 * 获取今天日期
 */
const todayDate = () => {
  checkedDay.value = formatDateTime(today.value);
  selectDay.value = new Date(checkedDay.value);
};
/**
 * 初始化当天事项
 */
const initCalenderInfo = () => {
  const todayThing = monthList.value.flat(2).find(item => item.date === checkedDay.value)?.thing;
  dateThing.value = todayThing?.infos || [];
};
/**
 * 转换时间格式
 * @param date 标准时间
 */
const formatDateTime = (date: Date): string => {
  let y = date.getFullYear();
  let m: string = date.getMonth() + 1 + '';
  m = Number(m) < 10 ? '0' + m : m;
  let d = date.getDate() + '';
  d = Number(d) < 10 ? '0' + d : d;
  return y + '-' + m + '-' + d;
};

/**
 * 获取三个月(上月,本月,下月)
 */
const get3month = () => {
  let year = dispDate.value.getFullYear();
  let month = dispDate.value.getMonth();
  monthList.value = [];
  monthList.value.push(getMonth(year, month - 1));
  monthList.value.push(getMonth(year, month));
  monthList.value.push(getMonth(year, month + 1));
};

interface weekParams {
  mode: string,
  day: number,
  year: number,
  month: number,
  date: string,
  //日历要显示的其他内容
  thing: ReturnType<typeof ifOrder>,
  istoday: boolean,
  ischecked: boolean,
  othermonth?: number | boolean
}
/**
 * 创建单月历
 * @param year 年
 * @param month 月
 */
const getMonth = (year: number, month: number): DayType => {
  let monthArr = [];
  let dtFirst = new Date(year, month, 1); //每个月第一天
  let dtLast = new Date(year, month + 1, 0); //每个月最后一天
  let monthLength = dtLast.getDate() - dtFirst.getDate() + 1;
  let space = (dtFirst.getDay() - 1 + 7) % 7; //月历前面空格

  for (let i = -space; i < 36; i += 7) {
    let week = [];
    for (let j = 0; j < 7; j++) {
      let day = i + j + 1;
      if (day > 0 && day <= monthLength) {
        let weekItem: weekParams = {
          mode: 'month',
          day: day,
          year: year,
          month: month,
          date: format(year, month, day),
          //日历要显示的其他内容
          thing: ifOrder(year, month, day),
          istoday:
            today.value.getFullYear() === year &&
              today.value.getMonth() === month &&
              today.value.getDate() === day
              ? true
              : false,
          ischecked: false,
        };
        week.push(weekItem);
      } else {
        //其它月份
        let newdt = new Date(year, month, day);
        let years = newdt.getFullYear();
        let months = newdt.getMonth();
        let days = newdt.getDate();
        let weeksItem: weekParams = {
          mode: 'month',
          day: days,
          year: years,
          month: months,
          date: format(year, month, day),
          thing: ifOrder(year, month, day),
          istoday:
            today.value.getFullYear() === years &&
              today.value.getMonth() === months &&
              today.value.getDate() === days
              ? true
              : false,
          ischecked: false,
          othermonth: day <= 0 ? -1 : 1,
        };
        week.push(weeksItem);

      }
    }
    monthArr.push(week);
  }
  return monthArr;
};
/**
 * 返回该天事项
 * @param year 年
 * @param month 月
 * @param day 日
 */
const ifOrder = (year: number, month: number, day: number) => {
  let dateTime = format(year, month, day);
  let dateItem = {};
  things.value.map(item => {
    if (dateTime === item.date) {
      dateItem = item;
    }
  });
  return dateItem;
};
/**
 * 转换时间
 * @param year 年
 * @param month 月
 * @param day 日
 */
const format = (year: number, month: number, day: number | string) => {
  month++;
  let m = month < 10 ? '0' + month : month;
  day < 10 && (day = '0' + day);
  return year + '-' + m + '-' + day;
};
/**
 * 选中某一天
 * @param year 年
 * @param month 月
 * @param day 日
 * @param othermonth 其他月份,当前月前面空值
 * @param mode 类型,'month','week'
 * @param thing 事项
 */
interface chooseDayParams { year: number; month: number; day: number; othermonth: number; mode: string; thing: Thing[]; }

interface Thing { date: string; infos?: ThingInfo[]; }

interface ThingInfo { title: string; address: string; dates: string; }
const chooseDay = ({ year, month, day, othermonth, mode, thing }: chooseDayParams): void => {
  currentDay.value = new Date(year, month, day);//标准时间
  checkedDay.value = format(year, month, day);//"2020-11-11"
  if (othermonth && mode === 'month') {
    let tmpDt = new Date(
      dispDate.value.getFullYear(),
      dispDate.value.getMonth() - othermonth,
      0
    );
    let maxday = tmpDt.getDate();
    let days = maxday < day ? maxday : day;
    dispDate.value = new Date(year, month - othermonth, days);
    changeIndex(othermonth || 0, true);
  } else {
    dispDate.value = currentDay.value;
  }
  dateThing.value = thing?.infos || [];
};
/**
 * 获取三周
 */
const get3week = () => {
  let year = dispDate.value.getFullYear();
  let month = dispDate.value.getMonth();
  let day = dispDate.value.getDate();
  monthList.value = [];
  monthList.value.push(getWeek(year, month, day - 7));
  monthList.value.push(getWeek(year, month, day));
  monthList.value.push(getWeek(year, month, day + 7));
};
/**
 * 获取星期
 * @param year 为选中当天的年
 * @param month 为选中当天的月
 * @param day 为选中当天的日
 */
const getWeek = (year: number, month: number, day: number) => {
  let dt = new Date(year, month, day);
  let weekArr = [];
  let dtFirst = new Date(year, month, day - (dt.getDay() + 6) % 7);
  let week = [];
  //循环选中当天所在那一周的每一天
  for (let j = 0; j < 7; j++) {
    let newdt = new Date(
      dtFirst.getFullYear(),
      dtFirst.getMonth(),
      dtFirst.getDate() + j
    );
    let years = newdt.getFullYear();
    let months = newdt.getMonth();
    let days = newdt.getDate();
    let weekItem: weekParams = {
      mode: 'week',
      day: days,
      year: years,
      month: months,
      date: format(years, months, days),
      //日历要显示的其他内容
      thing: ifOrder(years, months, days),
      istoday:
        today.value.getFullYear() === years &&
          today.value.getMonth() === months &&
          today.value.getDate() === days
          ? true
          : false,
      ischecked: false,
      othermonth: months !== month,
    };
    week.push(weekItem);
  }
  weekArr.push(week);
  return weekArr;
};
/**
 * 左右移动
 * @param index 月的index
 * @param isWeek 是否显示周
 * @param isClick 移动不可点击
 */
const changeIndex = (index: number, isClick = false) => {
  if (isWeek.value) {
    dispDate.value = new Date(
      dispDate.value.getFullYear(),
      dispDate.value.getMonth(),
      dispDate.value.getDate() + 7 * index
    );
    checkedDay.value = format(
      dispDate.value.getFullYear(),
      dispDate.value.getMonth(),
      dispDate.value.getDate()
    );
    currentDay.value = dispDate.value;
    get3week();
  } else {
    let tmpDt = new Date(
      dispDate.value.getFullYear(),
      dispDate.value.getMonth() + index,
      0
    );
    let maxday = tmpDt.getDate();
    let days = maxday < dispDate.value.getDate() ? maxday : dispDate.value.getDate();
    dispDate.value = new Date(
      dispDate.value.getFullYear(),
      dispDate.value.getMonth() + index,
      days
    );
    if (!isClick) {
      checkedDay.value = format(
        dispDate.value.getFullYear(),
        dispDate.value.getMonth(),
        dispDate.value.getDate()
      );
    }
    get3month();
  }
  initCalenderInfo();
};
/**
 * 手机端端开始移动
 * @param e event
 */
const handleMoveStart = (e: any) => {
  let touch = e?.changedTouches[0] || e;
  moveStartX.value = touch.clientX;
  moveStartY.value = touch.clientY;
  moveStartCalenderHeight.value = calenderHeight.value;
  moveIndex.value = 0;
};

/**
 * 手机端端移动
 * @param e event
 */
const handleMove = (e: any) => {
  let touch = e?.changedTouches[0] || e;
  calenderMove.value.x = touch.clientX - moveStartX.value;
};
/**
 * 手机端端结束移动
 * @param e event
 */
const handleMoveEnd = (e: any) => {
  let touch = e?.changedTouches[0] || e;
  if (moveStartX.value < touch.clientX) {
    if (moveStartX.value - touch.clientX < -150) {
      calenderMove.value.x = itemWidth.value;
      moveIndex.value = -1;
    } else {
      calenderMove.value.x = 0;
    }
  } else {
    if (moveStartX.value - touch.clientX > 150) {
      calenderMove.value.x = -itemWidth.value;
      moveIndex.value = 1;
    } else {
      calenderMove.value.x = 0;
    }
  }

  changeIndex(moveIndex.value, false);
};

/**
 * 切换月或周
 * @param e event
 */
const toggleMove = () => {
  isWeek.value = !isWeek.value;
  if (isWeek.value) {
    get3week();
  } else {
    get3month();
  }
};
</script>
<style scoped lang="scss">
.calender-container {
  margin: 16px;
  color: #232323;

  .calender-content,
  .things-content {
    box-shadow: -2px -2px 16px #d9dee7, 2px 2px 16px #d9dee7;
    padding: 14px;
    min-width: 320px;
    min-height: 400px;
    overflow: hidden;
    border-radius: 7px;

    .calender-title {
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 14px;

      &-right {
        color: #dddddd;
      }
    }

    .week-head-con {
      display: flex;
      align-items: center;
      padding-top: 20px;

      .week-head-item {
        width: 14.2%;
        text-align: center;
      }
    }

    .month-container {
      display: flex;
      position: relative;
      height: 300px;

      .month-item {
        position: absolute;
        width: 100%;
        min-height: 64px;
        border-right: 1px solid #eee;
        padding-top: 20px;

        .month-week {
          display: flex;
          align-items: center;

          .month-week-item {
            width: 14.2%;
            text-align: center;
            position: relative;
          }
        }
      }

      .month-item:nth-child(1) {
        left: -100%;
      }

      .month-item:nth-child(2) {
        left: 0;
      }

      .month-item:nth-child(3) {
        left: 100%;
      }

      .week-day i {
        display: block;
        text-align: center;
        font-style: normal;
        padding: 1px;
        line-height: 44px;
        height: 44px;
        width: 44px;
      }

      .week-day.ischecked i {
        border-radius: 50px;
        color: #fff !important;
        background: #7687E9;
      }

      .thing a {
        color: #fff;
        background: #fff;
      }

      .thing {
        position: absolute;
        left: 50%;
        bottom: 2px;
        transform: translateX(-50%);
        color: #f5a623;
      }

      .thing a {
        cursor: pointer;
        display: block;
        width: 8px;
        height: 8px;
        word-break: break-all;
        line-height: 8px;
        color: #f5a623;
        background: #f5a623;
        border-radius: 50%;
      }

      .othermonth {
        color: #87d7e8;
      }

      .istoday .today {
        color: #fff;
        background: #13C2C2;
        border-radius: 50%;
      }
    }
  }

  .things-content {
    margin-top: 20px;
    min-height: 180px;
    font-size: 14px;
    color: #666666;

    .van-swipe {
      min-height: 180px;
    }

    p {
      display: flex;
      align-items: center;

    }
    .title.thing-title::before {
      content: '';
      display: inline-block;
      width: 4px;
      height: 18px;
      background: #7687E9;
      margin-right: 6px;
      position: relative;
      bottom: -2px;
    }

    .title {
      color: #999999;
    }
  }
}
</style>
  • 7
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
引:《移动应用开发》小组项目--网易云音乐播放器移动项目源码《移动应用开发》小组项目--网易云音乐播放器移动项目源码《移动应用开发》小组项目--网易云音乐播放器移动项目源码《移动应用开发》小组项目--网易云...。 引用:要实现这个功能我们就要拿到当前歌曲列表的所有数据。我请求的是网易云的接口,将数据存在vuex里面。我们只需通过切换数组索引即可达到切换歌曲。 歌曲列表是通过遍出来的,所以我们可以给节点加上data-index属性。结果像下面这样 。 引用:这是一个仿网易云音乐的移动应用后工程说明。 (网易云有登录注册等的接口,但是由于没有做过移动的应用,对于express和mongoDB都不熟悉,所以没有用接口,直接自己写了个后来实践一些交互操作,目前还是大三,对于优化之类的都不大熟悉,文件仅供参考,欢迎讨论指正。) 本工程github链接 前工程github链接 前工程博客链接 技术栈 express:node.js的后框架MongoDB:数据库 工程目录 。 鉴于您提供的引用内容中没有直接回答您的问题,我无法为您提供确切的答案。但是,根据引用内容中的描述,仿网易云音乐移动可以通过以下步骤实现: 1. 获取网易云音乐的接口,并请求数据存储在vuex中。 2. 遍歌曲列表,并给节点加上data-index属性,以便切换歌曲时使用。 以上是根据引用内容推测出的一种实现方法。如果您需要更多详细的信息,建议您查阅相关的文档或资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值