123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- <script lang="ts" setup>
- import { computed, ref } from 'vue'
- import chineseDays from "chinese-days"
- const { getDayDetail, getLunarDate, getSolarTermsInRange, isInLieu } = chineseDays
- const props = withDefaults(
- defineProps<{
- lang: 'zh' | 'en'
- startOfWeek?: 1 | 2 | 3 | 4 | 5 | 6 | 0
- }>(),
- {
- lang: 'zh',
- startOfWeek: 1,
- },
- )
- const currentDate = ref(new Date())
- const currentMonth = ref(currentDate.value.getMonth())
- const currentYear = ref(currentDate.value.getFullYear())
- const daysOfWeek = computed(() =>
- props.lang === 'zh'
- ? ['日', '一', '二', '三', '四', '五', '六']
- : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
- )
- const monthNames = [
- 'January',
- 'February',
- 'March',
- 'April',
- 'May',
- 'June',
- 'July',
- 'August',
- 'September',
- 'October',
- 'November',
- 'December',
- ]
- const daysInMonth = computed(() => {
- const year = currentYear.value
- const month = currentMonth.value
- const firstDay = new Date(year, month, 1)
- const lastDay = new Date(year, month + 1, 0)
- const days: Date[] = []
- let firstDayIndex = firstDay.getDay() - props.startOfWeek
- if (firstDayIndex < 0) {
- firstDayIndex += 7
- }
- for (let i = firstDayIndex; i > 0; i--) {
- days.push(new Date(year, month, -i + 1))
- }
- for (let day = 1; day <= lastDay.getDate(); day++) {
- days.push(new Date(year, month, day))
- }
- let lastDayIndex = lastDay.getDay() - props.startOfWeek
- if (lastDayIndex < 0) {
- lastDayIndex += 7
- }
- for (let i = 1; i < 7 - lastDayIndex; i++) {
- days.push(new Date(year, month + 1, i))
- }
- return days
- })
- function prevMonth() {
- if (currentMonth.value === 0) {
- currentMonth.value = 11
- currentYear.value--
- }
- else {
- currentMonth.value--
- }
- }
- function nextMonth() {
- if (currentMonth.value === 11) {
- currentMonth.value = 0
- currentYear.value++
- }
- else {
- currentMonth.value++
- }
- }
- function isToday(date: Date) {
- const today = new Date()
- return (
- date.getDate() === today.getDate()
- && date.getMonth() === today.getMonth()
- && date.getFullYear() === today.getFullYear()
- )
- }
- function getDayInfo(date: Date) {
- const dayDetail = getDayDetail(date)
- const holidayName = dayDetail.name.split(',')[1]
- return {
- disable: currentMonth.value !== date.getMonth(),
- isToday: isToday(date),
- isInLieu: isInLieu(date),
- solarTerm: getSolarTermsInRange(date)[0],
- ...getLunarDate(date),
- ...dayDetail,
- holidayName,
- date,
- }
- }
- const selectedDate = ref(getDayInfo(new Date()))
- function selectDate(date: any) {
- selectedDate.value = date
- }
- function isSelected(date: Date) {
- return (
- date.getDate() === selectedDate.value?.date.getDate()
- && date.getMonth() === selectedDate.value?.date.getMonth()
- && date.getFullYear() === selectedDate.value?.date.getFullYear()
- )
- }
- const daysInfo = computed(() => daysInMonth.value.map((date: Date) => getDayInfo(date)))
- </script>
- <template>
- <div class="calendar">
- <header class="calendar-header">
- <button @click="prevMonth">
- <svg
- viewBox="0 0 1024 1024"
- version="1.1"
- xmlns="http://www.w3.org/2000/svg"
- width="200"
- height="200"
- >
- <path
- d="M684.29 799.276L393.929 513.019 684.29 226.762c37.685-37.153 38.003-97.625 0.707-134.384-37.297-36.758-98.646-36.435-136.331 0.718l-357.43 352.378c-0.155 0.153-0.297 0.314-0.451 0.468-0.084 0.082-0.172 0.157-0.256 0.239-18.357 18.092-27.581 41.929-27.743 65.902-0.004 0.311-0.017 0.623-0.018 0.934 0.001 0.316 0.014 0.632 0.018 0.948 0.165 23.97 9.389 47.803 27.743 65.892 0.083 0.082 0.171 0.157 0.255 0.239 0.154 0.154 0.296 0.315 0.452 0.468l357.43 352.378c37.685 37.153 99.034 37.476 136.331 0.718 37.297-36.758 36.979-97.231-0.707-134.384z"
- fill="currentColor"
- />
- </svg>
- </button>
- <h2 v-if="lang === 'zh'">
- <select v-model="currentYear" style="width: 130px;">
- <option v-for="(y, index) in 201" :key="index" :value="1900 + index">
- {{ 1900 + index }}
- </option>
- </select>
- 年
- <select v-model="currentMonth">
- <option v-for="(month, index) in 12" :key="index" :value="index">
- {{ month < 10 ? `0${month}` : month }}
- </option>
- </select>
- 月
- </h2>
- <h2 v-else>
- <select v-model="currentMonth" style="width: 160px;">
- <option v-for="(month, index) in 12" :key="index" :value="index">
- {{ monthNames[month - 1] }}
- </option>
- </select>
-
- <select v-model="currentYear">
- <option v-for="(y, index) in 201" :key="index" :value="1900 + index">
- {{ 1900 + index }}
- </option>
- </select>
- </h2>
- <button @click="nextMonth">
- <svg
- class="r"
- viewBox="0 0 1024 1024"
- version="1.1"
- xmlns="http://www.w3.org/2000/svg"
- width="200"
- height="200"
- >
- <path
- d="M684.29 799.276L393.929 513.019 684.29 226.762c37.685-37.153 38.003-97.625 0.707-134.384-37.297-36.758-98.646-36.435-136.331 0.718l-357.43 352.378c-0.155 0.153-0.297 0.314-0.451 0.468-0.084 0.082-0.172 0.157-0.256 0.239-18.357 18.092-27.581 41.929-27.743 65.902-0.004 0.311-0.017 0.623-0.018 0.934 0.001 0.316 0.014 0.632 0.018 0.948 0.165 23.97 9.389 47.803 27.743 65.892 0.083 0.082 0.171 0.157 0.255 0.239 0.154 0.154 0.296 0.315 0.452 0.468l357.43 352.378c37.685 37.153 99.034 37.476 136.331 0.718 37.297-36.758 36.979-97.231-0.707-134.384z"
- fill="currentColor"
- />
- </svg>
- </button>
- </header>
- <div class="calendar-grid">
- <div v-for="(day, i) in 7" :key="day" class="calendar-day">
- {{ daysOfWeek[daysInfo[i].date.getDay()] }}
- </div>
- <div
- v-for="(day, index) in daysInfo"
- :key="index"
- class="calendar-cell"
- :class="{
- today: day.isToday,
- disable: day.disable,
- holiday: day.holidayName,
- inlieu: day.isInLieu,
- work: day.holidayName && day.work,
- solar: day.solarTerm?.index === 1,
- selected: isSelected(day.date),
- }"
- @click="selectDate(day)"
- >
- <span v-if="day.isToday" class="today-dot">{{ lang === 'en' ? 'Today' : '今' }}</span>
- <span v-if="day.holidayName" class="holiday-dot">{{
- day.work ? '班' : day.isInLieu ? '调' : '休'
- }}</span>
- <span class="day">{{ day.date.getDate() }}</span>
- <span class="desc">{{
- day.solarTerm?.index === 1 ? day.solarTerm?.name : day.holidayName || day.lunarDayCN
- }}</span>
- </div>
- </div>
- </div>
- <div class="calendar-day-info">
- <div class="left">
- <p>
- {{ selectedDate.lunarYearCN }}
- {{ selectedDate.lunarMonCN }}{{ selectedDate.lunarDayCN }}
- </p>
- <p>
- {{ selectedDate.yearCyl }}{{ selectedDate.zodiac }}年 {{ selectedDate.monCyl }}月
- {{ selectedDate.dayCyl }}日
- </p>
- </div>
- <div class="right">
- <p>
- {{ selectedDate.isToday ? '今天是' : '此日是' }}
- <span>{{ selectedDate.solarTerm?.name }}</span> 节气的第
- <span>{{ selectedDate.solarTerm?.index }}</span> 天。
- </p>
- <p>
- {{
- selectedDate.work
- ? '又是需要工作的一天!😥'
- : selectedDate.isInLieu
- ? '虽然调休,但要补班还回来的!🤬'
- : '休息啦~😃'
- }}
- </p>
- </div>
- </div>
- </template>
- <style lang="postcss">
- body {
- --calendar-max-width: 660px;
- --calendar-padding: 30px;
- --calendar-border-width: 1px;
- --calendar-grid-gap: 18px 12px;
- --calendar-border-radius: 10px;
- }
- @media screen and (max-width: 560px) {
- body {
- --calendar-padding: 10px;
- --calendar-border-width: 0;
- --calendar-grid-gap: 12px 6px;
- --calendar-border-radius: 0;
- }
- }
- </style>
- <style lang="postcss" scoped>
- .calendar {
- max-width: var(--calendar-max-width);
- margin: 0 auto;
- padding: var(--calendar-padding);
- border: var(--calendar-border-width) solid var(--vp-c-gray-2);
- border-radius: var(--calendar-border-radius);
- position: relative;
- background: var(--vp-c-bg);
- z-index: 1;
- h2, p {
- margin: 0;
- padding: 0;
- border: 0;
- }
- select {
- font-size: 24px;
- width: 100px;
- margin: 0 15px;
- font-weight: bold;
- text-align: center;
- border: 1px solid var(--vp-c-default-3);
- border-radius: 6px;
- }
- .calendar-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: calc(20px + var(--calendar-padding));
- button {
- display: flex;
- flex-flow: column nowrap;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- width: 40px;
- height: 40px;
- opacity: 0.5;
- transition: all 0.2s ease;
- &:hover {
- opacity: 0.8;
- }
- svg {
- width: 22px;
- height: 22px;
- &.r {
- transform: rotate(180deg);
- }
- }
- }
- h2 {
- font-size: 24px;
- font-weight: bold;
- }
- }
- @media screen and (max-width: 560px) {
- .calendar-header {
- button {
- width: 30px;
- height: 30px;
- svg {
- width: 18px;
- height: 18px;
- }
- }
- h2 {
- font-size: 18px;
- }
- }
- }
- .calendar-grid {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: var(--calendar-grid-gap);
- .calendar-day {
- font-weight: bold;
- text-align: center;
- line-height: 3;
- }
- .calendar-cell {
- min-height: 70px;
- display: flex;
- flex-flow: column nowrap;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- border-radius: var(--calendar-border-radius);
- position: relative;
- transition: all 0.2s ease;
- color: var(--vp-c-text-1);
- &:nth-child(7n + 6),
- &:nth-child(7n + 7) {
- .day {
- color: #eb3333;
- }
- }
- .day {
- font-size: 24px;
- font-weight: bold;
- }
- .desc {
- font-size: 12px;
- }
- .today-dot,
- .holiday-dot {
- position: absolute;
- right: -6px;
- top: -6px;
- font-size: 12px;
- padding: 0 4px;
- border-radius: 4px;
- min-width: 20px;
- line-height: 20px;
- transform: scale(0.9);
- }
- &.work {
- background: transparent;
- }
- &.holiday {
- &:not(&.work) {
- background: rgba(235, 51, 51, 0.05);
- color: #eb3333;
- }
- /** background: #f28c28; */
- .holiday-dot {
- background: #eb3333;
- color: #fff;
- }
- &.work {
- .day {
- color: #4e5877;
- }
- .holiday-dot {
- background: #4e5877;
- color: #fff;
- }
- }
- }
- &:hover {
- background: rgba(118, 142, 240, 0.2);
- color: var(--vp-c-text-1);
- }
- &.solar {
- .desc {
- color: #f28c28;
- border: 1px solid #f28c28;
- border-radius: 4px;
- padding: 0 4px;
- }
- }
- &.today {
- color: #4e6ef2;
- .today-dot {
- background: #6b88ff;
- color: #fff;
- }
- &.selected {
- background: #4e6ef2;
- color: #fff;
- }
- }
- &.selected {
- background: #4e6ef2 !important;
- color: #fff !important;
- .day {
- color: #fff !important;
- }
- &.solar {
- .desc {
- color: #fff;
- border: 1px solid #fff;
- }
- }
- }
- &.disable {
- opacity: 0.2;
- pointer-events: none;
- }
- }
- }
- }
- .calendar-day-info {
- max-width: var(--calendar-max-width);
- margin: 0 auto;
- padding: 50px 20px 30px;
- background: var(--vp-c-gray-3);
- border: var(--calendar-border-width) solid var(--vp-c-gray-3);
- border-radius: var(--calendar-border-radius);
- position: relative;
- top: -20px;
- z-index: 0;
- font-size: 16px;
- display: flex;
- align-items: center;
- .left {
- display: flex;
- flex-flow: column nowrap;
- align-items: flex-start;
- margin-right: var(--calendar-padding);
- p {
- font-weight: bold;
- font-size: 14px;
- margin: 0;
- &:first-child {
- font-size: 22px;
- }
- }
- }
- .right {
- display: flex;
- flex-flow: column nowrap;
- align-items: flex-start;
- padding-left: var(--calendar-padding);
- border-left: 1px solid var(--vp-c-gray-2);
- p {
- font-size: 14px;
- margin: 0;
- span {
- font-weight: bold;
- }
- &:first-child {
- font-size: 18px;
- }
- }
- }
- @media screen and (max-width: 560px) {
- .left,
- .right {
- p {
- font-size: 12px;
- &:first-child {
- font-size: 14px;
- }
- }
- }
- }
- }
- </style>
|