Calendar.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <template>
  2. <section class='m-calendar' :style="dateStyle">
  3. <!-- 切换月份 -->
  4. <header class='changeMonth'>
  5. <!-- <span class='prepMonth' @click="changeMonth(-1)"></span> -->
  6. <h1>{{year}}年{{month}}月</h1>
  7. <!-- <span class='nextMonth' @click="changeMonth(1)"></span> -->
  8. </header>
  9. <ul class='dates'>
  10. <!-- 星期 -->
  11. <li class='weeks' v-for="item in weeks" :key="item">{{item}}</li>
  12. <!-- 日期 -->
  13. <li
  14. class='day'
  15. v-for="(item, i) in dates" :key="i"
  16. :class="{
  17. isPrep: item.isPrep,
  18. isNext: item.isNext,
  19. hidden: (item.isNext || item.isPrep) && !showPrepNext,
  20. isToday: item.date == today,
  21. isSelected: item.date == selectedDate,
  22. isMarked: markDates.includes(item.date)
  23. }"
  24. @click="clickDate(item)"
  25. >{{item.date == today ? '今' : item.day}}</li>
  26. </ul>
  27. </section>
  28. </template>
  29. <script>
  30. const D = new Date()
  31. const ThisYear = D.getFullYear()
  32. const ThisMonth = D.getMonth() + 1
  33. const today = new Date().toLocaleDateString()
  34. export default {
  35. // props都为非必传
  36. props: {
  37. // 初始年月
  38. startYearMonth: {
  39. type: String,
  40. default () {
  41. return `${ThisYear}/${ThisMonth}`
  42. // 格式:2021-1或2020-01或者2020/1或者2020/01
  43. }
  44. },
  45. // 需要标记的日期数组
  46. markDate: {
  47. type: Array,
  48. default () {
  49. return []
  50. // 格式:['2020-01-01', '2020-02-12']
  51. }
  52. },
  53. // 选中的日期
  54. checkedDate: {
  55. type: String,
  56. default () {
  57. return ''
  58. // 格式:'2020-01-01'
  59. }
  60. },
  61. // 是否星期一开始,默认星期日开始
  62. mondayStart: {
  63. type: Boolean,
  64. default () {
  65. return false
  66. }
  67. },
  68. // 是否显示上个月和下个月日期
  69. showPrepNext: {
  70. type: Boolean,
  71. default () {
  72. return true
  73. }
  74. },
  75. // 日期字体颜色
  76. fontColor: {
  77. type: String,
  78. default () {
  79. return '#000'
  80. }
  81. },
  82. // 标记点颜色
  83. markColor: {
  84. type: String,
  85. default () {
  86. return '#1e80ff'
  87. }
  88. },
  89. // 选中的日期字体颜色
  90. activeColor: {
  91. type: String,
  92. default () {
  93. return '#fff'
  94. }
  95. },
  96. // 选中的日期背景颜色
  97. activeBgColor: {
  98. type: String,
  99. default () {
  100. return '#1e80ff'
  101. }
  102. }
  103. },
  104. data () {
  105. return {
  106. // 当前年
  107. year: ThisYear,
  108. // 当前月
  109. month: ThisMonth,
  110. // 今天
  111. today,
  112. // 日期数组
  113. dates: [],
  114. // 选中的日期
  115. selectedDate: ''
  116. }
  117. },
  118. computed: {
  119. // 标记的日期
  120. markDates () {
  121. // 获得不带0的日期,如:2021/1/1 => 2021/1/1
  122. return this.markDate.map(item => this.formatDate(item))
  123. },
  124. // 星期
  125. weeks () {
  126. if (this.mondayStart) {
  127. return ['一', '二', '三', '四', '五', '六', '日']
  128. } else {
  129. return ['日', '一', '二', '三', '四', '五', '六']
  130. }
  131. },
  132. dateStyle () {
  133. return {
  134. '--font-color': this.fontColor,
  135. '--mark-color': this.markColor,
  136. '--active-color': this.activeColor,
  137. '--active-bg-color': this.activeBgColor
  138. }
  139. }
  140. },
  141. created () {
  142. this.year = new Date(this.startYearMonth).getFullYear()
  143. this.month = new Date(this.startYearMonth).getMonth() + 1
  144. // 选中的日期
  145. if (this.checkedDate) {
  146. // 获得不带0的日期,如:2021-1-1 => 2021/1/1
  147. this.selectedDate = this.formatDate(this.checkedDate)
  148. }
  149. // 初始化日历
  150. this.initCalendar()
  151. },
  152. methods: {
  153. // 初始化日历
  154. initCalendar () {
  155. // ⚠️注意:new Date中的month要减1才是真正的本月月数,即本月:this.month - 1,下个月:this.month
  156. // ⚠️注意:下个月的第0天即为本月的最后一天
  157. // 上个月总天数(本月第0天日期)
  158. const prepMonthDays = new Date(this.year, this.month - 1, 0).getDate()
  159. // 上个月最后一天星期几(本月第0天星期数)
  160. const prepMonthEndDayWeek = new Date(this.year, this.month - 1, 0).getDay()
  161. // 当前月总天数(下个月第0天日期)
  162. const thisMonthDays = new Date(this.year, this.month, 0).getDate()
  163. // 当前月第一天是星期几
  164. const firstDayWeek = new Date(this.year, this.month - 1, 1).getDay()
  165. // 当前月最后一天是星期几(下个月第0天星期数)
  166. const thisEndDayWeek = new Date(this.year, this.month, 0).getDay()
  167. var dates = []
  168. // 需要计算的总天数
  169. var totalDays = firstDayWeek + thisMonthDays
  170. // 从星期一开始index为1,从星期天开始index为0
  171. var index = this.mondayStart ? 1 : 0
  172. // 星期一开始且本月最后一天是星期天之后,需补满最后一行
  173. if (this.mondayStart && thisEndDayWeek > 0) {
  174. totalDays += 7 - thisEndDayWeek
  175. } else if (!this.mondayStart && thisEndDayWeek < 6) {
  176. // 星期天开始且本月最后一天是星期天之前,需补满最后一行
  177. totalDays += 6 - thisEndDayWeek
  178. }
  179. for (index; index < totalDays; index++) {
  180. // 上个月月底
  181. if (index < firstDayWeek) {
  182. // 上个月天数 - 上个月最后一天星期数 + 下标 (如:31 - 5 + 0)
  183. const day = prepMonthDays - prepMonthEndDayWeek + index
  184. const date = new Date(this.year, this.month - 2, day).toLocaleDateString()
  185. dates.push({ isPrep: true, day, date })
  186. } else if (index >= firstDayWeek + thisMonthDays) {
  187. // 下个月月初
  188. // 下标 - 当前月总天数 - 当前月第一天星期数 + 1 (如:30 - 31 + 1 + 1)
  189. const day = index - thisMonthDays - firstDayWeek + 1
  190. const date = new Date(this.year, this.month, day).toLocaleDateString()
  191. dates.push({ isNext: true, day, date })
  192. } else {
  193. // 本月
  194. // 下标 - 当前月第一天星期数 + 1 (如:5 - 5 + 1)
  195. const day = index - firstDayWeek + 1
  196. const date = new Date(this.year, this.month - 1, day).toLocaleDateString()
  197. dates.push({ day, date })
  198. }
  199. }
  200. this.dates = [...dates]
  201. },
  202. // 点击日期
  203. clickDate ({ date, isPrep, isNext }) {
  204. if (isPrep || isNext) return
  205. this.selectedDate = date
  206. this.$emit('clickDate', date.replace(/\//g, '-'))
  207. },
  208. // 切换月份
  209. changeMonth (month) {
  210. this.month += month
  211. if (this.month === 0) {
  212. this.month = 12
  213. this.year--
  214. } else if (this.month === 13) {
  215. this.month = 1
  216. this.year++
  217. }
  218. this.initCalendar()
  219. this.$emit('changeMonth', `${this.year}-${this.month}`)
  220. },
  221. // 格式化日期
  222. formatDate (date) {
  223. // 获得不带0,且分隔符为/的日期,如:2020-01-01 => 2021/1/1
  224. return new Date(date).toLocaleDateString()
  225. }
  226. }
  227. }
  228. </script>
  229. <style lang='scss' scoped>
  230. $fontColor: var(--font-color);
  231. $markColor: var(--mark-color);
  232. $activeColor: var(--active-color);
  233. $activeBgColor: var(--active-bg-color);
  234. .m-calendar{
  235. max-width: 720px;
  236. max-height: 810px;
  237. // border: 1px solid #054C96;
  238. border-radius: 8px 8px 0 0;
  239. padding-bottom: 20px;
  240. header{
  241. display: flex;
  242. align-items: center;
  243. justify-content: center;
  244. // border-bottom: 1px solid #054C96;
  245. padding: 20px 0;
  246. margin-bottom: 5px;
  247. h1{
  248. margin: 0 20px;
  249. color: #444;
  250. font-size: 20px;
  251. font-weight: bold;
  252. width: 120px;
  253. text-align: center;
  254. }
  255. span{
  256. cursor: pointer;
  257. padding: 4px 10px;
  258. &::after{
  259. display: inline-block;
  260. content: '';
  261. width: 10px;
  262. height: 10px;
  263. border-top: 2px solid $fontColor;
  264. }
  265. &.prepMonth::after{
  266. border-left: 2px solid $fontColor;
  267. transform: rotate(-45deg);
  268. }
  269. &.nextMonth::after{
  270. border-right: 2px solid $fontColor;
  271. transform: rotate(45deg);
  272. }
  273. &:hover::after{
  274. border-color: $markColor;
  275. }
  276. }
  277. }
  278. ul{
  279. display: flex;
  280. flex-wrap: wrap;
  281. margin: 0 auto;
  282. padding: 0 12px;
  283. li{
  284. width: 42px;
  285. height: 42px;
  286. margin: 20px calc((100% / 7 - 42px) / 2);
  287. display: flex;
  288. flex-direction: column;
  289. justify-content: center;
  290. align-items: center;
  291. font-weight: bold;
  292. position: relative;
  293. transition: all ease .25s;
  294. border-radius: 6px;
  295. // 标记
  296. &::after{
  297. bottom: 0;
  298. left: 50%;
  299. transform: translateX(-50%);
  300. position: absolute;
  301. display: inline-block;
  302. content: '';
  303. width: 5px;
  304. height: 5px;
  305. border-radius: 50%;
  306. }
  307. // 星期
  308. &.weeks{
  309. font-size: 18px;
  310. color: #444;
  311. margin-bottom: 12px;
  312. }
  313. &.day{
  314. cursor: pointer;
  315. font-size: 20px;
  316. color: $fontColor;
  317. // 今天
  318. &.isToday{
  319. color: $markColor;
  320. }
  321. // 标记
  322. &.isMarked::after{
  323. transition: all ease .25s;
  324. background: $markColor;
  325. }
  326. // 选中、hover
  327. &:hover, &.isSelected{
  328. background: $activeBgColor;
  329. color: $activeColor;
  330. &:after{
  331. display: none;
  332. }
  333. }
  334. // 上个月、下个月
  335. &.isNext, &.isPrep{
  336. cursor: default;
  337. opacity: .3;
  338. &:hover{
  339. color: $fontColor;
  340. opacity: .3;
  341. background: transparent;
  342. }
  343. }
  344. // hidden
  345. &.hidden{
  346. opacity: 0;
  347. &:hover{
  348. opacity: 0;
  349. }
  350. }
  351. }
  352. }
  353. }
  354. }
  355. </style>