SOURCE

console 命令行工具 X clear

                    
>
console
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, #000 ${props.radius}px)`
          : "",
        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");
<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;
}

.date-panel .week-list .cell-item {
    line-height: 24px;
    padding: 5px 0;
}

.date-panel .date-list .cell-item {
    padding: 2px;
}

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

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

.date-panel .cell-list {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    text-align: center;
}

.date-panel .cell-list .cell-item {
    width: 14.286%;
    background-color: #000;
}

.date-panel .cell-list .cell-item:hover {
    position: relative;
    z-index: 1;
}

.date-panel .cell-list .cell-item span {
    z-index: 1;
    position: relative;
}

本项目引用的自定义外部资源