<div id="app"></div>
body{
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  * {
    box-sizing: border-box;
  }
}


.date-panel {
  position: relative;
  user-select: none;
  overflow: hidden;
  width: 350px;
  color: #fff;

  .cell-list {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    text-align: center;
    .cell-item {
      width: 14.286%;
      background-color: #000;
      &:hover {
        position: relative;
        z-index: 1;
      }
      span {
        z-index: 1;
        position: relative;
      }
    }
  }
  .week-list {
    .cell-item {
      line-height: 24px;
      padding: 5px 0;
    }
  }

  .date-list {
    .cell-item {
      padding: 2px;
      .date-cell {
        height: 42px;
        border: 2px solid #fff;
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
  }

  .mask {
    width: 200%;
    height: 200%;
    pointer-events: none;
    position: absolute;
    top: 0;
    left: 0;
  }
}

View Compiled
const {
  defineComponent, ref, computed, 
  onMounted, getCurrentInstance, onUnmounted,
  createApp, h
} = Vue

const DatePanel = defineComponent({
  props: {
    radius: {
      type: Number,
      default: 60,
    },
  },
  setup(props, ctx) {
    const list = getDateList();
    const vm = getCurrentInstance();
    const { x, y, enter, height } = useMousePosition(vm);
    const maskStyle = computed(() => {
      return {
        transform: `translate(${x.value}px, ${y.value}px)`,
        backgroundImage: enter.value
          ? `radial-gradient(transparent, rgba(0, 0, 0, 1) ${props.radius}px, #000)`
          : "",
        backgroundColor: enter.value ? "" : "#000",
        height: height.value + "px",
      };
    });
    return {
      dateList: list.map((it) => it.date()),
      weeks: ["日", "一", "二", "三", "四", "五", "六"],
      maskStyle,
      x,
      y,
    };
  },
  template: `
  <div class="date-panel">
    <div class="cell-list week-list">
      <div class="cell-item" v-for="item in weeks" :key="item">
        <span>{{ item }}</span>
      </div>
    </div>
    <div class="cell-list date-list">
      <div class="cell-item" v-for="item in dateList" :key="item">
        <div class="date-cell">
          <span>{{ item }}</span>
        </div>
      </div>
    </div>

    <div class="mask" :style="maskStyle"></div>
  </div>
`
});

// 获取鼠标位置
function useMousePosition(vm) {
  let x = ref(0);
  let y = ref(0);
  let enter = ref(false);
  let height = ref(600);
  let rect = {
    top: 0,
    left: 0,
  };

  function onEnter(e) {
    const el = vm.ctx.$el;
    if (!el) return;
    enter.value = true;
    rect = el.getBoundingClientRect();
    height.value = rect.width * 2;
  }
  function onMove(e) {
    const { clientX, clientY } = e;
    x.value = clientX - rect.left - rect.width;
    y.value = clientY - rect.top - rect.width;
  }
  function onLeave() {
    enter.value = false;
  }

  onMounted(() => {
    const el = vm.ctx.$el;
    if (!el) return;
    el.addEventListener("mouseenter", onEnter);
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
  });

  onUnmounted(() => {
    const el = vm.ctx.$el;
    if (!el) return;
    el.removeEventListener("mousemove", onMove);
    el.removeEventListener("mouseenter", onEnter);
    el.removeEventListener("mouseleave", onLeave);
  });

  return {
    x,
    y,
    enter,
    height,
  };
}

// 生成日历表
function getDateList() {
  // 本月第一天
  const day0 = dayjs().startOf("month");
  // 本月第一个星期的星期日
  const firstDay = day0.subtract(day0.get("day"), "day");
  // 星期数,以上月剩余天数+本月天数
  const rows = Math.ceil((day0.get("day") + day0.daysInMonth()) / 7);
  return Array(rows * 7)
    .fill(0)
    .map((n, i) => firstDay.add(i, "day"));
}

// 挂载组件
const App = defineComponent({
  components: {DatePanel},
  template: `
  <div>
<DatePanel />
</div>
`
})

createApp(App).mount("#app");
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://unpkg.com/vue@3.0.0-beta.3
  2. https://unpkg.com/dayjs