vue3的DatePicker组件

作者: 小枫枫

临枫的项目经历分享给你们啦~

扫码交朋友

标签:

特别声明:文章有少部分为网络转载,资源使用一般不提供任何帮助,特殊资源除外,如有侵权请联系!

vue3+ts的日期选择器组件 可自行按需修改

 

效果图:

t1.png

t2.png

 

t3.png

DatePicker.vue

<template>
  <div class="date-picker" ref="refDatePicker">
    <div>
      <span>{{ frontText }}</span>
      <input
        class="result"
        readonly
        type="text"
        :value="dateValue || modelValue"
        @focus="showDropdown = true"
      />
    </div>
    <transition name="slide-up">
      <div v-show="showDropdown" class="dropdown">
        <!-- 日期面板 -->
        <div v-show="showPanel === 'date'">
          <header class="date-header">
            <div class="l">
              <i class="prev-year" @click="changeYearOrMonth('year', -1)"
                >&lt;&lt;</i
              >
              <i class="next-month" @click="changeYearOrMonth('month', -1)"
                >&lt;</i
              >
            </div>
            <div class="c">
              <b class="year" @click="showYearPanel">{{ curYear }}</b>
              <b @click="showPanel = 'month'">{{ MONTH_NAME[curMonth] }}</b>
              <b @click="openHmPanel">{{ `${hms.hh}:${hms.mm}:${hms.ss}` }}</b>
            </div>
            <div class="r">
              <i class="next-month" @click="changeYearOrMonth('month', 1)"
                >&gt;</i
              >
              <i class="next-year" @click="changeYearOrMonth('year', 1)">
                &gt;&gt;
              </i>
            </div>
          </header>
          <ul class="date-week">
            <li v-for="item of WEEK_NAME" :key="item">
              {{ item }}
            </li>
          </ul>
          <ul class="date-list">
            <li
              v-for="item of dateList"
              :key="item.value"
              :data-unix="item.value"
              :class="{
                'no-current': item.type !== 'current',
                active: item.value === selectedDay,
              }"
              @click="handleDayClick(item)"
            >
              {{ item.day }}
            </li>
          </ul>
        </div>
        <!-- 年份面板 -->
        <ul v-show="showPanel === 'year'" class="year-panel">
          <li
            v-for="item of yearList"
            :key="item"
            :class="['year-panel-item', { active: curYear === item }]"
            @click="
              curYear = item;
              showPanel = 'date';
            "
          >
            {{ item }}
          </li>
        </ul>
        <!-- 月份面板 -->
        <ul v-show="showPanel === 'month'" class="month-panel">
          <li
            v-for="item of MONTH_NAME"
            :key="item"
            :class="[
              'month-panel-item',
              { active: MONTH_NAME.indexOf(item) === curMonth },
            ]"
            @click="
              curMonth = MONTH_NAME.indexOf(item);
              showPanel = 'date';
            "
          >
            {{ item.slice(0, 3) }}
          </li>
        </ul>
        <!-- 小时面板 -->
        <!-- 分钟面板 -->
        <div class="hms-panel" :style="foldHmPanel">
          <div class="hms-ok" @click="showHmPanel = false">确认</div>
          <p class="hms-value">{{ `${hms.hh}:${hms.mm}:${hms.ss}` }}</p>
          <ul class="hms-change">
            <li class="num-list" v-for="item in hms.insertEls" :key="item.type">
              <div class="scroll" @click="chooseTime">
                <li
                  v-for="(it, index) in item.count"
                  :id="item.type + index.toString()"
                  :data-value="index.toString().padStart(2, '0')"
                  :data-type="item.type"
                  :key="it"
                  :class="['num',index.toString().padStart(2, '0') == hms[item.type as keyof typeof hms]?'isactive':'']"
                >
                  {{ index.toString().padStart(2, "0") }}
                </li>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </transition>
  </div>
</template>

<script lang="ts" setup>
import {
  computed,
  nextTick,
  ref,
  watch,
  onBeforeUnmount,
  defineProps,
  defineEmits
} from "vue";
import { DateInfo } from "@/components/Map/types";
defineProps({
  modelValue: { type: String, required: false },
  frontText: { type: String, required: false },
});
const $emits = defineEmits(['update:modelValue'])
const refDatePicker = ref();
const currentDate = new Date();
const yearList = ref<number[]>([]); // 年份列表
const dateList = ref<DateInfo[]>([]);
const curYear = ref(currentDate.getFullYear());
const curMonth = ref(currentDate.getMonth());
// const curDay = ref(currentDate.getDate())
const showPanel = ref("date");
const showDropdown = ref(false);
const selectedDay = ref<number>();

const WEEK_NAME = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
const MONTH_NAME = [
  "1月",
  "2月",
  "3月",
  "4月",
  "5月",
  "6月",
  "7月",
  "8月",
  "9月",
  "10月",
  "11月",
  "12月",
];

// 监听鼠标是否点击外部
(() => {
  const handler = (e: MouseEvent) => {
    if (!refDatePicker.value.contains(e.target as Node))
      showDropdown.value = false;
  };
  document.addEventListener("click", handler);
  onBeforeUnmount(() => {
    document.removeEventListener("click", handler);
  });
})();
const hms = ref({
  hh: "23",
  mm: "59",
  ss: "59",
  insertEls: [
    {
      count: 24,
      type: "hh",
    },
    {
      count: 60,
      type: "mm",
    },
    {
      count: 60,
      type: "ss",
    },
  ],
});
const dateValue = computed<string>(() => {
  if (!selectedDay.value) return "";
  const d = new Date(selectedDay.value);
  const y = d.getFullYear();
  const m = (d.getMonth() + 1).toString().padStart(2, "0");
  const day = d.getDate().toString().padStart(2, "0");
  const currentValue = `${y}-${m}-${day} ${hms.value.hh}:${hms.value.mm}:${hms.value.ss}`
  $emits("update:modelValue",currentValue)
  return currentValue;
});

// 生成近100年的年份列表
(() => {
  for (let i = 1970; i < curYear.value + 100; i++) {
    yearList.value.push(i);
  }
})();
// 获取传入的月份有多少天
const getDayLength = (year: number, month: number): number => {
  return new Date(year, month + 1, 0).getDate();
};

// 设置日期列表
const setDateList = (year: number, month: number) => {
  const curDays = getDayLength(year, month); // 当月天数
  const prevDays = getDayLength(year, month - 1); // 上月天数
  const curFirstDayWeek = new Date(year, month, 1).getDay(); // 当月第一天星期几
  const list: DateInfo[] = [];
  // 填充上月最后几天
  for (let i = prevDays - curFirstDayWeek + 1; i <= prevDays; i++) {
    list.push({
      day: i,
      value: +new Date(year, month - 1, i),
      isRange: false,
      isSelected: false,
      type: "prev",
    });
  }

  // 填充当月
  for (let i = 1; i <= curDays; i++) {
    list.push({
      day: i,
      value: +new Date(year, month, i),
      isRange: false,
      isSelected: false,
      type: "current",
    });
  }

  const nextDays = 7 - (list.length % 7);
  if (nextDays !== 7) {
    // 填充下月
    for (let i = 1; i <= nextDays; i++) {
      list.push({
        day: i,
        value: +new Date(year, month + 1, i),
        isRange: false,
        isSelected: false,
        type: "next",
      });
    }
  }
  dateList.value = list;
};

watch(
  [curYear, curMonth],
  () => {
    setDateList(curYear.value, curMonth.value);
  },
  { immediate: true }
);

// 点击日期
const handleDayClick = (item: DateInfo) => {
  selectedDay.value = item.value;
  if (item.type !== "current") {
    curMonth.value =
      item.type === "prev" ? curMonth.value - 1 : curMonth.value + 1;
  }
  showDropdown.value = false;
};

// 切换年月
const changeYearOrMonth = (type: "year" | "month", num: number) => {
  if (type === "year") {
    curYear.value += num;
  } else {
    let month = curMonth.value + num;

    if (month > 11) {
      month = 0;
      curYear.value++;
    } else if (month < 0) {
      month = 11;
      curYear.value--;
    }
    curMonth.value = month;
  }
};

// 显示年列表面板
const showYearPanel = () => {
  showPanel.value = "year";
  nextTick(() => {
    (
      document.querySelector(".year-panel-item.active") as HTMLElement
    ).scrollIntoView({ block: "center" });
  });
};

const showHmPanel = ref(false);
const foldHmPanel = computed(() => {
  let width = showHmPanel.value ? "100%" : "0";
  return { width };
});
const openHmPanel = () => {
  let activeEl = document.querySelectorAll(".hms-change .isactive") as any;
  let count = 0;
  const initScrollIntoView = () => {
    if (count > activeEl.length) return;
    activeEl[count].scrollIntoView({ behavior: "smooth" });
    count++;
    setTimeout(initScrollIntoView, Number(activeEl[count].dataset.value) * 16);
  };

  initScrollIntoView();
  showHmPanel.value = !showHmPanel.value;
};
const chooseTime = (e: any) => {
  if (e.target.nodeName == "LI") {
    let { value, type } = e.target.dataset;
    hms.value[type as keyof typeof hms.value] = value;
    e.target.scrollIntoView({ behavior: "smooth" });
  }
};
</script>

<style lang="less" scoped>
.date-picker {
  position: relative;
  height: 0.375rem;
  margin: 0 auto;
  width: max-content;
}
.date-picker .result {
  width: 2.75rem;
  box-sizing: border-box;
  margin: 0 0 0 .05rem;
  color: #000000d9;
  font-size: 0.175rem;
  font-variant: tabular-nums;
  line-height: 1.5715;
  list-style: none;
  padding: 0.05rem 0.1375rem;
  position: relative;
  display: inline-flex;
  align-items: center;
  background: #fff;
  border: 0.0125rem solid #d9d9d9;
  border-radius: 0.025rem;
  transition: border 0.3s, box-shadow 0.3s;
}
.date-picker .result:hover {
  border-color: #bdbdbd;
}
.date-picker .result:focus {
  border-color: #2187db;
  box-shadow: 0 0 0 .025rem #3982c75e;
  border-right-width: 1px !important;
  outline: 0;
}
.date-picker .dropdown {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 3.75rem;
  padding: 0.2rem 0.1rem;
  margin-top: 0.15rem;
  margin-left: -1.875rem;
  filter: drop-shadow(0.025rem 0.025rem 0.1rem rgba(0, 0, 0, 0.1));
  background: rgba(0, 0, 0, 0.7);
  border-radius: 0.025rem;
  box-shadow: 0 0.025rem 0.1rem #00000026;
  z-index: 2;
}
.date-picker .dropdown::before {
  content: "";
  position: absolute;
  top: -0.1rem;
  left: 50%;
  margin-left: -0.1rem;
  border-width: 0 0.1rem 0.1rem 0.1rem;
  border-style: solid;
  border-color: transparent transparent rgba(0, 0, 0, 0.6) transparent;
}
.date-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.date-header {
  cursor: pointer;
  > div {
    > *:hover {
      color: #fed039;
    }
  }
  .c {
    b {
      margin: 0 0.0625rem;
    }
  }
  .prev-year {
    margin-right: .125rem;
  }
  .next-month {
    margin-right: .125rem;
  }
}

.date-week {
  display: flex;
  align-items: center;
  margin-top: 0.375rem;
}
.date-week li {
  flex: 1;
  font-size: 0.15rem;
  text-align: center;
}
.date-list {
  display: flex;
  flex-wrap: wrap;
  margin-top: 0.1rem;
}
.date-list li {
  width: calc(100% / 7);
  height: 0.5rem;
  border-radius: 50%;
  line-height: 0.5rem;
  text-align: center;
  cursor: pointer;
}
.date-list li.active {
  color: #fff;
  background: #fed039;
}
.date-list .no-current {
  color: #c4c4c4;
}
.year-panel {
  max-height: 3.75rem;
  overflow-y: auto;
}
.year-panel-item {
  height: 0.375rem;
  line-height: 0.375rem;
  cursor: pointer;
  transition: 0.15s ease-in-out;
  border: 1px solid transparent;
  text-indent: 0.05rem;
}
.year-panel-item.active {
  font-weight: bold;
  color: #fed039;
  border: 1px solid transparent;
}
.year-panel-item:hover {
  border: 1px solid rgba(214, 236, 87, 0.459);
}
.month-panel {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
.month-panel-item {
  text-align: center;
  height: 0.625rem;
  line-height: 0.625rem;
  cursor: pointer;
  transition: 0.15s ease-in-out;
  border: 1px solid transparent;
}
.month-panel-item:hover {
  border: 1px solid rgba(214, 236, 87, 0.459);
}
.month-panel-item.active {
  font-weight: bold;
  color: #fed039;
}

.hms-panel {
  height: 100%;
  background-color: rgba(20, 20, 27, 0.85);
  width: 0%;
  position: absolute;
  top: 0;
  right: .025rem;
  transition: width 0.3s ease-in-out;
  overflow: hidden;
  box-sizing: border-box;
  padding: 0.25rem 0;
  .hms-value {
    text-align: center;
    margin-bottom: 0.25rem;
    font-size: 0.25rem;
  }
  .hms-change {
    display: flex;
    width: 50%;
    margin: 0 auto;
    .num-list {
      flex: 33%;
      height: 2.5rem;
      text-align: center;
      overflow: scroll;
      padding: 0.05rem;
      box-sizing: border-box;
      .scroll {
        .num {
          line-height: 1.8;
          cursor: pointer;
          border: 1px solid rgba(214, 236, 87, 0);
        }
        .isactive {
          border: 1px solid rgba(214, 236, 87, 0.459);
          color: #fed039;
        }
      }
    }
  }
}
.hms-ok {
  cursor: pointer;
  height: 0;
  margin-right: 0.125rem;
  float: right;
}
.slide-up-enter-active,
.slide-up-leave-active {
  transition: 0.2s ease-in-out;
}
.slide-up-enter-from,
.slide-up-leave-to {
  transform: translate3d(0, -0.25rem, 0);
  opacity: 0;
}
</style>

 

使用: 

<DatePicker v-model="endTime" frontText="结束时间:" />

...
<script setup lang="ts">
import { ref } from "vue";
const endTime = ref("2022-03-04 17:35:00");
</script>

 

// 参数 EndTime 格式 YYYY-MM-DD hh:mm:ss

const endTime = ref("2022-03-04 17:35:00");
 
 
endTime通过ref创建的话 数据是双向绑定的
本文最后更新于2022-3-4,已超过 1 年没有更新,如果文章内容或图片资源失效,请留言反馈,我们会及时处理,谢谢!
分享到:
打赏

作者: 小枫枫, 转载或复制请以 超链接形式 并注明出处 小枫枫不疯喔
原文地址: 《vue3的DatePicker组件》 发布于2022-3-4

评论

切换注册

登录

您也可以使用第三方帐号快捷登录

切换登录

注册

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏