Calendar.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. <template>
  2. <transition :name="transition">
  3. <div class="calendar-tz" v-if="isShow" :class="isFixed&&'fixed'">
  4. <slot name="header"></slot>
  5. <div class="week-number">
  6. <span v-for="(item,index) in weekList" :style="{color:(index==0||index==weekList.length-1)&&themeColor}" :key="index">{{item}}</span>
  7. </div>
  8. <p class="tips" v-if="title">{{title}}</p>
  9. <div class="content" id="scrollWrap">
  10. <div class="con" v-for="(item,index) in calendar" :key="index" :id="item.year+''+item.month">
  11. <h3 v-text="item.year + '年' + item.month + '月'"></h3>
  12. <span class="month-bg" :style="{color:getBetweenColor}">{{item.month}}</span>
  13. <ul class="each-month">
  14. <li class="each-day" v-for="(day,idx) in item.dayList" :key="idx" :class="[addClassBg(day, item.month, item.year)]" :style="{background:themeOpacityBg(day, item.month, item.year)}" @click="chooseDate(day, item.month, item.year)">
  15. <div :class="[addClassName(day, item.month, item.year)]" :style="{background:themeBg(day, item.month, item.year)}">
  16. <p class="day-tip" :style="{color:themeColor}"><i v-text="setTip(day, item.month, item.year,1)"></i></p>
  17. <p class="day">{{day?day:''}}</p>
  18. <p class="recent"><i v-text="setTip(day, item.month, item.year,2)"></i></p>
  19. </div>
  20. </li>
  21. </ul>
  22. </div>
  23. </div>
  24. <slot name="footer"></slot>
  25. </div>
  26. </transition>
  27. </template>
  28. <script>
  29. export default {
  30. props: {
  31. isShow: {//是否显示
  32. type: [Boolean],
  33. default() {
  34. return false;
  35. }
  36. },
  37. isFixed: {//是否定位全屏
  38. type: [Boolean],
  39. default() {
  40. return true;
  41. }
  42. },
  43. transition: {//动画类型slide
  44. type: [String],
  45. default() {
  46. return "";
  47. }
  48. },
  49. title: {//头部的一段文本
  50. type: [String, Object],
  51. default() {
  52. return "";
  53. }
  54. },
  55. mode: {//模式:1普通日历,2酒店,3飞机往返
  56. type: [String, Number],
  57. default() {
  58. return 1;
  59. }
  60. },
  61. startDate: {//开始日期
  62. type: [String, Object, Date]
  63. },
  64. endDate: {//结束日期
  65. type: [String, Object, Date]
  66. },
  67. betweenStart: {//日历可选范围开始
  68. type: [String, Object, Date],
  69. default() {
  70. return "";
  71. }
  72. },
  73. betweenEnd: { //日历可选结束日期
  74. type: [String, Object, Date],
  75. default() {
  76. return "";
  77. }
  78. },
  79. initMonth: {//初始化的月数
  80. type: [String, Number],
  81. default() {
  82. return 6;
  83. }
  84. },
  85. themeColor: {//主题色
  86. type: [String],
  87. default: "#1C75FF"
  88. }
  89. },
  90. data() {
  91. return {
  92. startDates: "",
  93. endDates: "",
  94. betweenStarts: "",
  95. betweenEnds: "",
  96. calendar: [],
  97. weekList: ["日", "一", "二", "三", "四", "五", "六"]
  98. };
  99. },
  100. watch: {
  101. isShow() {
  102. this.init();
  103. },
  104. betweenStart() {
  105. this.init();
  106. },
  107. betweenEnd() {
  108. this.init();
  109. }
  110. },
  111. mounted() {
  112. this.init();
  113. },
  114. computed: {
  115. //设置主题色入住离开之间的背景色
  116. getBetweenColor() {
  117. if (!this.themeColor) return;
  118. var hex = this.themeColor;
  119. if (hex.length == 4) {
  120. hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
  121. }
  122. var str = "rgba(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt("0x" + hex.slice(5, 7)) + ",0.1)";
  123. return str;
  124. }
  125. },
  126. methods: {
  127. init() {
  128. var date = new Date();
  129. this.year = date.getFullYear();
  130. this.month = date.getMonth() + 1;
  131. this.day = date.getDate();
  132. this.today = new Date(this.year + "/" + this.month + "/" + this.day) * 1;
  133. if (!this.startDate) {
  134. const year = date.getFullYear(),
  135. month = date.getMonth() + 1,
  136. day = date.getDate();
  137. this.startDates = this.resetTime(year + "/" + month + "/" + day);
  138. this.startYear = year;
  139. this.startMonth = month;
  140. } else {
  141. this.startDates = this.resetTime(this.startDate);
  142. var dd = this.startDate.replace(/-/g, "/").split("/");
  143. this.startYear = dd[0];
  144. this.startMonth = dd[1];
  145. }
  146. if (!this.endDate) {
  147. // var temp = this.startDates + 24 * 60 * 60 * 1000;
  148. // var dateTemp = new Date(temp);
  149. // const year = dateTemp.getFullYear(),
  150. // month = dateTemp.getMonth() + 1,
  151. // day = dateTemp.getDate();
  152. // this.endDates = this.resetTime(year + "/" + month + "/" + day);
  153. // this.endYear = year;
  154. // this.endMonth = month;
  155. } else {
  156. this.endDates = this.resetTime(this.endDate);
  157. var dd = this.endDate.replace(/-/g, "/").split("/");
  158. this.endYear = dd[0];
  159. this.endMonth = dd[1];
  160. }
  161. this.betweenStarts = this.resetTime(this.betweenStart);
  162. this.betweenEnds = this.resetTime(this.betweenEnd);
  163. this.createClendar(); //创建日历数据
  164. },
  165. //创建每个月日历数据,传入月份1号前面用null填充
  166. createDayList(month, year) {
  167. const count = this.getDayNum(month, year),
  168. _week = new Date(year + "/" + month + "/1").getDay();
  169. let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28];
  170. for (let i = 29; i <= count; i++) {
  171. list.push(i);
  172. }
  173. for (let i = 0; i < _week; i++) {
  174. list.unshift(null);
  175. }
  176. return list;
  177. },
  178. //计算传入月份有多少天
  179. getDayNum(month, year) {
  180. let dayNum = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  181. if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
  182. dayNum[1] = 29;
  183. }
  184. return dayNum[month - 1];
  185. },
  186. //根据当天和结束日期创建日历数据
  187. createClendar() {
  188. var yearTemp = this.year;
  189. var monthTemp = this.month;
  190. if (!!this.betweenStarts) {
  191. //如果有范围起始日期,可选范围从betweenStart开始
  192. yearTemp = new Date(this.betweenStarts).getFullYear();
  193. monthTemp = new Date(this.betweenStarts).getMonth() + 1;
  194. }
  195. this.calendar = [];
  196. for (let i = 0; i < this.initMonth; i++) {
  197. let year = yearTemp;
  198. let month = monthTemp + i;
  199. let _monthData = {
  200. dayList: [],
  201. month: "",
  202. year: ""
  203. };
  204. var m = Math.ceil(month / 12);
  205. if (m > 0) {
  206. year += m - 1;
  207. } else {
  208. year += m - 1;
  209. }
  210. if (month > 12) {
  211. month = month % 12 == 0 ? 12 : month % 12;
  212. }
  213. if (month <= 0) {
  214. month = 12 + month % 12;
  215. }
  216. _monthData.year = year;
  217. _monthData.month = month;
  218. _monthData.dayList = this.createDayList(month, year);
  219. this.calendar.push(_monthData);
  220. }
  221. //h5默认页面加载到当前日期start-date的位置
  222. if (document) {
  223. this.scrollTop(this.startYear, this.startMonth);
  224. }
  225. },
  226. scrollTop(year, month) {
  227. var id = year + "" + parseInt(month)
  228. setTimeout(() => {
  229. var obj = document.getElementById(id)
  230. if(!obj) return
  231. var wrap = document.getElementById("scrollWrap");
  232. wrap.scrollTop = obj.offsetTop - 40;
  233. }, 0);
  234. },
  235. //添加日历样式
  236. addClassName(day, month, year) {
  237. if (!day) return;
  238. const _date = new Date(year + "/" + month + "/" + day);
  239. let className = [];
  240. if (_date.getDay() == 0 || _date.getDay() == 6) { //周末或周六样式
  241. className.push('weekend')
  242. }
  243. if (_date * 1 == this.today) {
  244. className.push("today");
  245. }
  246. if (this.mode == 1) {
  247. if (_date * 1 == this.startDates) {
  248. className.push("trip-time");
  249. }
  250. } else {
  251. if (_date * 1 == this.startDates || _date * 1 == this.endDates) {
  252. className.push("trip-time");
  253. }
  254. }
  255. if (this.betweenStarts) {
  256. _date * 1 < this.betweenStarts && className.push("disabled");
  257. } else {
  258. _date * 1 < this.today && className.push("disabled"); //当天和结束日期之外不可选
  259. }
  260. _date * 1 > this.betweenEnds && className.push("disabled");
  261. return className.join(" ");
  262. },
  263. //入住离开的区间背景色
  264. addClassBg(day, month, year) {
  265. if (!day) return;
  266. const _date = this.resetTime(year + "/" + month + "/" + day);
  267. let className = [];
  268. if (_date >= this.startDates && _date <= this.endDates && this.mode > 1) {
  269. className.push("between");
  270. }
  271. return className.join(" ");
  272. },
  273. //theme入住离开的区间背景色
  274. themeOpacityBg(day, month, year) {
  275. if (!this.themeColor) return;
  276. if (!day) return;
  277. const _date = this.resetTime(year + "/" + month + "/" + day);
  278. if (_date >= this.startDates && _date <= this.endDates && this.mode > 1) {
  279. return this.getBetweenColor;
  280. }
  281. },
  282. //theme获取普通日期选中样式背景
  283. themeBg(day, month, year) {
  284. if (!this.themeColor) return;
  285. const _date = this.resetTime(year + "/" + month + "/" + day);
  286. //正常模式
  287. if (this.mode == 1) {
  288. if (_date == this.startDates) {
  289. return this.themeColor;
  290. }
  291. } else {
  292. //酒店和往返模式
  293. if (_date == this.startDates || _date == this.endDates) {
  294. return this.themeColor;
  295. }
  296. }
  297. },
  298. //清除时间 时 分 秒 毫秒
  299. resetTime(dateStr) {
  300. var date = new Date(dateStr.replace(/-/g, "/"));
  301. date.setHours(0);
  302. date.setMinutes(0);
  303. date.setSeconds(0);
  304. date.setMilliseconds(0);
  305. return date * 1;
  306. },
  307. //flag==1(返回今天,明天,后天),flag==2(返回入住,离开,去返)
  308. setTip(day, month, year,flag) {
  309. if (!day) return
  310. var tip = ""
  311. var _date = this.resetTime(year + "/" + month + "/" + day);
  312. if(flag==1){
  313. if (_date == this.today) {
  314. tip = "今天";
  315. } else if (_date - this.today == 24 * 3600 * 1000) {
  316. tip = "明天";
  317. } else if (_date - this.today == 2 * 24 * 3600 * 1000) {
  318. tip = "后天";
  319. }
  320. return tip
  321. }else{
  322. if (this.mode == 2) {
  323. if (_date == this.endDates) {
  324. tip = "离开";
  325. } else if (_date == this.startDates) {
  326. tip = "入住";
  327. }
  328. } else if (this.mode == 3) {
  329. if (_date == this.startDates && !this.endDates) {
  330. tip = "去/返";
  331. } else {
  332. if (_date == this.endDates) {
  333. tip = "返程";
  334. } else if (_date == this.startDates) {
  335. tip = "去程";
  336. }
  337. }
  338. }
  339. return tip;
  340. }
  341. },
  342. //是否是选中当天,或者入住离开当天
  343. isCurrent(day, month, year) {
  344. if (!day) {
  345. return false;
  346. }
  347. const _date = this.resetTime(year + "/" + month + "/" + day);
  348. //正常模式
  349. if (this.mode == 1) {
  350. if (_date == this.startDates) {
  351. return true;
  352. }
  353. } else {
  354. //酒店和往返模式
  355. if (_date == this.startDates || _date == this.endDates) {
  356. return true;
  357. }
  358. }
  359. },
  360. dateFormat(times) {
  361. let date = new Date(times);
  362. let recent = "";
  363. if (times == this.today) {
  364. recent = "今天";
  365. } else if (times - this.today === 24 * 3600 * 1000) {
  366. recent = "明天";
  367. } else if (times - this.today === 2 * 24 * 3600 * 1000) {
  368. recent = "后天";
  369. }
  370. var year = date.getFullYear()
  371. var month = parseInt(date.getMonth() + 1) > 9 ? parseInt(date.getMonth() + 1) : '0' + parseInt(date.getMonth() + 1)
  372. var day = date.getDate() > 9 ? date.getDate() : '0' + date.getDate()
  373. return {
  374. dateStr: year + "-" + month + "-" + day,
  375. week: "周" + this.weekList[date.getDay()],
  376. recent
  377. };
  378. },
  379. chooseDate(day, month, year) {
  380. const _date = this.resetTime(year + "/" + month + "/" + day);
  381. const week = this.weekList[new Date(_date).getDay()];
  382. //判断日期区域是否可点击
  383. if (!day) return;
  384. if (this.betweenStarts) {
  385. if (_date * 1 < this.betweenStarts) return;
  386. } else {
  387. if (_date < this.today) return;
  388. }
  389. if (_date > this.betweenEnds) return;
  390. //判断酒店或者往返模式的选择逻辑
  391. if (this.startDates && this.endDates && _date > this.endDates) {
  392. this.startDates = _date;
  393. this.endDates = "";
  394. } else if (this.endDates && _date > this.endDates) {
  395. this.endDates = _date;
  396. } else if (_date >= this.startDates && _date <= this.endDates) {
  397. this.startDates = _date;
  398. this.endDates = "";
  399. } else if (_date < this.startDates) {
  400. this.startDates = _date;
  401. this.endDates = "";
  402. } else if (_date > this.startDates) {
  403. if (this.mode == 1) {
  404. this.startDates = _date;
  405. } else {
  406. this.endDates = _date;
  407. }
  408. }
  409. const choose = {
  410. startStr: this.dateFormat(this.startDates)
  411. };
  412. if (this.mode == 1) {
  413. this.$emit("callback", choose);
  414. } else if (this.mode == 2 && this.startDates && this.endDates) {
  415. choose.dayCount = (this.endDates - this.startDates) / 24 / 3600 / 1000;
  416. choose.endStr = this.dateFormat(this.endDates);
  417. this.$emit("callback", choose);
  418. } else if (this.mode == 3) {
  419. if (this.startDates && this.endDates) {
  420. choose.dayCount = (this.endDates - this.startDates) / 24 / 3600 / 1000;
  421. choose.endStr = this.dateFormat(this.endDates);
  422. } else if (this.startDates && !this.endDates) {
  423. choose.dayCount = 0;
  424. choose.endStr = this.dateFormat(this.startDates);
  425. }
  426. this.$emit("callback", choose);
  427. }
  428. }
  429. }
  430. };
  431. </script>
  432. <style lang="less" scoped>
  433. @color: #1C75FF;
  434. .calendar-tz {
  435. width: 100%;
  436. height: 100vh;
  437. background: #fff;
  438. display: -webkit-box;
  439. display: flex;
  440. -webkit-flex-direction: column;
  441. flex-direction: column;
  442. &.fixed{
  443. position: fixed;
  444. width:100%;
  445. height:100%;
  446. left:0;
  447. top:0;
  448. z-index: 900;
  449. }
  450. .week-number {
  451. background: #fff;
  452. padding: 0 1%;
  453. box-shadow: 0 2px 15px rgba(100, 100, 100, 0.1);
  454. span {
  455. display: inline-block;
  456. text-align: center;
  457. padding: 12px 0;
  458. font-size: 14px;
  459. width: 14.28%;
  460. &:first-child,
  461. &:last-child {
  462. color: @color;
  463. }
  464. }
  465. }
  466. .tips {
  467. padding: 6px 10px;
  468. background: #fff7dc;
  469. font-size: 12px;
  470. color: #9e8052;
  471. overflow: hidden;
  472. text-overflow: ellipsis;
  473. white-space: nowrap;
  474. }
  475. .content{
  476. -webkit-box-flex: 1;
  477. flex:1;
  478. overflow-y: scroll;
  479. -webkit-overflow-scrolling: touch;
  480. .con {
  481. color: #333;
  482. padding-top: 10px;
  483. position: relative;
  484. h3 {
  485. width: 100%;
  486. font-weight: normal;
  487. text-align: center;
  488. font-size: 16px;
  489. padding: 10px 0;
  490. }
  491. .month-bg{
  492. position: absolute;
  493. text-align: center;
  494. opacity: 0.4;
  495. left:0;
  496. right:0;
  497. bottom:0;
  498. top:20%;
  499. font-size:220px;
  500. font-weight: bold;
  501. color:#f8f8f8;
  502. }
  503. .each-month {
  504. display: block;
  505. width: 98%;
  506. font-size: 0;
  507. margin: 0 auto;
  508. padding-left: 0;
  509. padding-bottom: 10px;
  510. border-bottom: 1px solid #eee;
  511. .each-day {
  512. position: relative;
  513. display: inline-block;
  514. text-align: center;
  515. vertical-align: middle;
  516. width: 14.28%;
  517. font-size: 16px;
  518. height: 50px;
  519. margin:2px auto;
  520. div {
  521. display: inline-block;
  522. font-size: 14px;
  523. width:98%;
  524. height:100%;
  525. justify-content: space-around;
  526. display: -webkit-box;
  527. display: flex;
  528. -webkit-flex-direction: column;
  529. flex-direction: column;
  530. border-radius: 4px;
  531. }
  532. &.between {
  533. background: rgba(75, 217, 173, 0.1);
  534. }
  535. .day{
  536. font-size: 16px;
  537. }
  538. .day-tip,.recent{
  539. font-size:10px;
  540. height:14px;
  541. i{
  542. font-size:10px;
  543. }
  544. }
  545. .recent {
  546. color: #ccc;
  547. }
  548. .disabled {
  549. color: #ccc !important;
  550. .day-tip{
  551. color: #ccc !important;
  552. }
  553. }
  554. .today {
  555. background: rgba(100,100,100,0.1);
  556. }
  557. .trip-time {
  558. background: @color;
  559. color: #fff !important;
  560. .recent,.day-tip{
  561. color: #fff!important;
  562. }
  563. }
  564. .weekend {
  565. color: @color;
  566. }
  567. }
  568. }
  569. }
  570. }
  571. }
  572. /***右侧进入动画***/
  573. .slide-enter-active,
  574. .slide-leave-active {
  575. -webkit-transition: all 0.2s ease;
  576. transition: all 0.2s ease;
  577. }
  578. .slide-enter,
  579. .slide-leave-to {
  580. -webkit-transform: translateX(100%);
  581. transform: translateX(100%);
  582. }
  583. </style>