timeSever.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <template>
  2. <view class="calendar-wrapper" @touchstart="touchStart" @touchend="touchEnd">
  3. <view class="header" v-if="headerBar">
  4. <view class="pre" @click="changeMonth('pre')">{{y+'年'+formatNum(m-1)+'月'}}</view>
  5. <view>{{y+'年'+formatNum(m)+'月'}}</view>
  6. <view class="next" @click="changeMonth('next')">{{y+'年'+formatNum(m+1)+'月'}}</view>
  7. </view>
  8. <view class="week">
  9. <view class="week-day" v-for="(item, index) in weekDay" :key="index">{{ item }}</view>
  10. </view>
  11. <view :class="{ hide: !monthOpen }" class="content" :style="{ height: height }">
  12. <view :style="{ top: positionTop + 'rpx' }" class="days">
  13. <view class="item" v-for="(item, index) in dates" :key="index">
  14. <view
  15. class="day"
  16. @click="selectOne(item, $event)"
  17. :class="{
  18. choose: choose == `${item.year}-${item.month}-${item.date}`&&item.isCurM,
  19. today: isToday(item.year, item.month, item.date),
  20. isWorkDay:item.isCurM
  21. }"
  22. >
  23. <!-- isWorkDay: isWorkDay(item.year, item.month, item.date) -->
  24. <!-- nolm: !item.isCurM, -->
  25. {{ Number(item.date) }}
  26. </view>
  27. <view class="markDay" v-if="(isMarkDay(item.year, item.month, item.date)&&item.isCurM)"></view>
  28. <!-- <view class="today-text" v-if="isToday(item.year, item.month, item.date)">今</view> -->
  29. </view>
  30. </view>
  31. </view>
  32. <image src="https://i.loli.net/2020/07/16/2MmZsucVTlRjSwK.png" mode="scaleToFill" v-if="collapsible" @click="toggle" class="weektoggle" :class="{ down: monthOpen }"></image>
  33. </view>
  34. </template>
  35. <script>
  36. export default {
  37. props: {
  38. // 星期几为第一天(0为星期日)
  39. weekstart: {
  40. type: Number,
  41. default: 1
  42. },
  43. // 标记的日期
  44. markDays: {
  45. type: Array,
  46. default: ()=>{
  47. return [];
  48. }
  49. },
  50. //是否展示月份切换按钮
  51. headerBar:{
  52. type: Boolean,
  53. default: true
  54. },
  55. // 是否展开
  56. open: {
  57. type: Boolean,
  58. default: true
  59. },
  60. //是否可收缩
  61. collapsible:{
  62. type: Boolean,
  63. default: false
  64. },
  65. //未来日期是否不可点击
  66. disabledAfter: {
  67. type: Boolean,
  68. default: false
  69. }
  70. },
  71. data() {
  72. return {
  73. weektext: ['日', '一', '二', '三', '四', '五', '六'],
  74. y: new Date().getFullYear(), // 年
  75. m: new Date().getMonth() + 1, // 月
  76. dates: [], // 当前月的日期数据
  77. positionTop: 0,
  78. monthOpen: true,
  79. choose: '',
  80. touchStartX: 0, // 触屏起始点x
  81. touchStartY: 0, // 触屏起始点y
  82. };
  83. },
  84. created() {
  85. this.dates = this.monthDay(this.y, this.m);
  86. !this.open && this.toggle();
  87. },
  88. mounted() {
  89. this.choose = this.getToday().date;
  90. console.log('%cren-calendar.vue line:134 today', 'color: white; background-color: #007acc;', this.choose);
  91. // this.$emit('onDayClick', this.choose);
  92. },
  93. computed: {
  94. // 顶部星期栏
  95. weekDay() {
  96. return this.weektext.slice(this.weekstart).concat(this.weektext.slice(0, this.weekstart));
  97. },
  98. height() {
  99. return (((this.dates.length / 7) * 80) + 100) + 'rpx';
  100. }
  101. },
  102. methods: {
  103. formatNum(num) {
  104. let res = Number(num);
  105. return res < 10 ? '0' + res : res;
  106. },
  107. getToday() {
  108. let date = new Date();
  109. let y = date.getFullYear();
  110. let m = date.getMonth();
  111. let d = date.getDate();
  112. let week = new Date().getDay();
  113. let weekText = ['日', '一', '二', '三', '四', '五', '六'];
  114. let formatWeek = '星期' + weekText[week];
  115. let today = {
  116. date: y + '-' + this.formatNum(m + 1) + '-' + this.formatNum(d),
  117. week: formatWeek
  118. };
  119. return today;
  120. },
  121. // 获取当前月份数据
  122. monthDay(y, month) {
  123. let dates = [];
  124. let m = Number(month);
  125. let firstDayOfMonth = new Date(y, m - 1, 1).getDay(); // 当月第一天星期几
  126. let lastDateOfMonth = new Date(y, m, 0).getDate(); // 当月最后一天
  127. let lastDayOfLastMonth = new Date(y, m - 1, 0).getDate(); // 上一月的最后一天
  128. let weekstart = this.weekstart == 7 ? 0 : this.weekstart;
  129. let startDay = (() => {
  130. // 周初有几天是上个月的
  131. if (firstDayOfMonth == weekstart) {
  132. return 0;
  133. } else if (firstDayOfMonth > weekstart) {
  134. return firstDayOfMonth - weekstart;
  135. } else {
  136. return 7 - weekstart + firstDayOfMonth;
  137. }
  138. })();
  139. let endDay = 7 - ((startDay + lastDateOfMonth) % 7); // 结束还有几天是下个月的
  140. for (let i = 1; i <= startDay; i++) {
  141. dates.push({
  142. date: this.formatNum(lastDayOfLastMonth - startDay + i),
  143. day: weekstart + i - 1 || 7,
  144. month: m - 1 >= 0 ? this.formatNum(m - 1) : 12,
  145. year: m - 1 >= 0 ? y : y - 1
  146. });
  147. }
  148. for (let j = 1; j <= lastDateOfMonth; j++) {
  149. dates.push({
  150. date: this.formatNum(j),
  151. day: (j % 7) + firstDayOfMonth - 1 || 7,
  152. month: this.formatNum(m),
  153. year: y,
  154. isCurM: true //是否当前月份
  155. });
  156. }
  157. for (let k = 1; k <= endDay; k++) {
  158. dates.push({
  159. date: this.formatNum(k),
  160. day: (lastDateOfMonth + startDay + weekstart + k - 1) % 7 || 7,
  161. month: m + 1 <= 11 ? this.formatNum(m + 1) : 0,
  162. year: m + 1 <= 11 ? y : y + 1
  163. });
  164. }
  165. console.log(dates);
  166. return dates;
  167. },
  168. isWorkDay(y, m, d) {
  169. //是否工作日
  170. let ymd = `${y}/${m}/${d}`;
  171. let formatDY = new Date(ymd.replace(/-/g, '/'));
  172. let week = formatDY.getDay();
  173. if (week == 0 || week == 6) {
  174. return false;
  175. } else {
  176. return true;
  177. }
  178. },
  179. isFutureDay(y, m, d) {
  180. //是否未来日期
  181. let ymd = `${y}/${m}/${d}`;
  182. let formatDY = new Date(ymd.replace(/-/g, '/'));
  183. let showTime = formatDY.getTime();
  184. let curTime = new Date().getTime();
  185. if (showTime > curTime) {
  186. return true;
  187. } else {
  188. return false;
  189. }
  190. },
  191. // 标记日期
  192. isMarkDay(y, m, d) {
  193. let flag = false;
  194. for (let i = 0; i < this.markDays.length; i++) {
  195. let dy = `${y}-${m}-${d}`;
  196. if (this.markDays[i] == dy) {
  197. flag = true;
  198. break;
  199. }
  200. }
  201. return flag;
  202. },
  203. isToday(y, m, d) {
  204. let checkD = y + '-' + m + '-' + d;
  205. let today = this.getToday().date;
  206. if (checkD == today) {
  207. return true;
  208. } else {
  209. return false;
  210. }
  211. },
  212. // 展开收起
  213. toggle() {
  214. this.monthOpen = !this.monthOpen;
  215. if (this.monthOpen) {
  216. this.positionTop = 0;
  217. } else {
  218. let index = -1;
  219. this.dates.forEach((i, x) => {
  220. this.isToday(i.year, i.month, i.date) && (index = x);
  221. });
  222. this.positionTop = -((Math.ceil((index + 1) / 7) || 1) - 1) * 80;
  223. }
  224. },
  225. // 点击回调
  226. selectOne(i, event) {
  227. let date = `${i.year}-${i.month}-${i.date}`;
  228. let time = `${i.month}.${i.date}`
  229. let selectD = new Date(date).getTime();
  230. let curTime = new Date().getTime();
  231. let week = new Date(date).getDay();
  232. let weekText = ['日', '一', '二', '三', '四', '五', '六'];
  233. let formatWeek = '周' + weekText[week];
  234. let response = {
  235. date: date,
  236. time: time,
  237. week: formatWeek
  238. };
  239. if (!i.isCurM) {
  240. // console.log('不在当前月范围内');
  241. return false;
  242. }
  243. if (selectD > curTime) {
  244. if (this.disabledAfter) {
  245. console.log('未来日期不可选');
  246. return false;
  247. } else {
  248. this.choose = date;
  249. this.$emit('onDayClick', response);
  250. }
  251. } else {
  252. this.choose = date;
  253. this.$emit('onDayClick', response);
  254. }
  255. console.log(response);
  256. },
  257. //改变年月
  258. changYearMonth(y, m) {
  259. this.dates = this.monthDay(y, m);
  260. this.y = y;
  261. this.m = m;
  262. },
  263. changeMonth(type){
  264. if(type=='pre'){
  265. if (this.m + 1 == 2) {
  266. this.m = 12;
  267. this.y = this.y - 1;
  268. } else {
  269. this.m = this.m - 1;
  270. }
  271. let time = this.y + '-' + this.formatNum(this.m) + '-' + '01'
  272. this.$emit('onDateClick', time);
  273. }else{
  274. if (this.m + 1 == 13) {
  275. this.m = 1;
  276. this.y = this.y + 1;
  277. } else {
  278. this.m = this.m + 1;
  279. }
  280. let time = this.y + '-' + this.formatNum(this.m) + '-' + '01'
  281. this.$emit('onDateClick', time);
  282. }
  283. this.dates = this.monthDay(this.y, this.m);
  284. },
  285. /**
  286. * 触摸开始
  287. **/
  288. touchStart(e) {
  289. console.log("触摸开始")
  290. this.touchStartX = e.touches[0].clientX;
  291. this.touchStartY = e.touches[0].clientY;
  292. },
  293. /**
  294. * 触摸结束
  295. **/
  296. touchEnd(e) {
  297. console.log("触摸结束")
  298. let deltaX = e.changedTouches[0].clientX - this.touchStartX;
  299. let deltaY = e.changedTouches[0].clientY - this.touchStartY;
  300. console.log('%cren-calendar.vue line:320 deltaX', 'color: white; background-color: #26bfa5;', deltaX,deltaY);
  301. if (Math.abs(deltaX) > 50 && Math.abs(deltaX) > Math.abs(deltaY)) {
  302. if (deltaX >= 0) {
  303. console.log("左滑")
  304. this.changeMonth('pre')
  305. } else {
  306. console.log("右滑")
  307. this.changeMonth('next')
  308. }
  309. } else if(Math.abs(deltaY) > 50&& Math.abs(deltaX) < Math.abs(deltaY)) {
  310. if (deltaY < 0) {
  311. console.log("上滑")
  312. } else {
  313. console.log("下滑")
  314. }
  315. } else {
  316. console.log("可能是误触!")
  317. }
  318. },
  319. }
  320. };
  321. </script>
  322. <style lang="scss" scoped>
  323. .calendar-wrapper {
  324. color: #bbb7b7;
  325. font-size: 28rpx;
  326. text-align: center;
  327. background-color: #fff;
  328. padding-bottom: 10rpx;
  329. .header{
  330. display: flex;
  331. align-items: center;
  332. justify-content: space-between;
  333. width: 100vw;
  334. height: 88rpx;
  335. color: #000;
  336. font-size: 32rpx;
  337. // font-weight: bold;
  338. border-bottom: 2rpx solid #f2f2f2;
  339. .pre,.next{
  340. color: #00000088;
  341. font-size: 28rpx;
  342. font-weight: normal;
  343. padding: 8rpx 15rpx;
  344. border-radius: 10rpx;
  345. // border: 2rpx solid #dcdfe6;
  346. }
  347. .pre{
  348. margin-right: 30rpx;
  349. }
  350. .next{
  351. margin-left: 30rpx;
  352. }
  353. }
  354. .week {
  355. display: flex;
  356. align-items: center;
  357. height: 80rpx;
  358. line-height: 80rpx;
  359. border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
  360. view {
  361. flex: 1;
  362. }
  363. }
  364. .content {
  365. position: relative;
  366. overflow: hidden;
  367. transition: height 0.4s ease;
  368. .days {
  369. height: 100%;
  370. transition: top 0.3s;
  371. display: flex;
  372. align-items: center;
  373. flex-wrap: wrap;
  374. position: relative;
  375. overflow-y: auto;
  376. .item {
  377. position: relative;
  378. display: flex;
  379. justify-content: center;
  380. align-items: center;
  381. height: 80rpx;
  382. line-height: 80rpx;
  383. width: calc(100% / 7);
  384. .day {
  385. font-style: normal;
  386. display: inline-block;
  387. vertical-align: middle;
  388. width: 60rpx;
  389. height: 60rpx;
  390. line-height: 60rpx;
  391. overflow: hidden;
  392. border-radius: 60rpx;
  393. &.choose {
  394. background-color: #836BFF;
  395. color: #fff;
  396. }
  397. &.nolm {
  398. color: #fff;
  399. opacity: 0.3;
  400. }
  401. }
  402. .isWorkDay {
  403. color: #42464a;
  404. }
  405. .notSigned {
  406. font-style: normal;
  407. width: 8rpx;
  408. height: 8rpx;
  409. background: #fa7268;
  410. border-radius: 10rpx;
  411. position: absolute;
  412. left: 50%;
  413. bottom: 0;
  414. pointer-events: none;
  415. }
  416. .today {
  417. color: #fff;
  418. background-color: #836BFF88;
  419. }
  420. .workDay {
  421. font-style: normal;
  422. width: 8rpx;
  423. height: 8rpx;
  424. background: #4d7df9;
  425. border-radius: 10rpx;
  426. position: absolute;
  427. left: 50%;
  428. bottom: 0;
  429. pointer-events: none;
  430. }
  431. .markDay{
  432. font-style: normal;
  433. width: 6px;
  434. height: 6px;
  435. background: #31CA8A;
  436. border-radius: 10rpx;
  437. position: absolute;
  438. // left: 50%;
  439. bottom: -10%;
  440. pointer-events: none;
  441. }
  442. }
  443. }
  444. }
  445. .hide {
  446. height: 80rpx !important;
  447. }
  448. .weektoggle {
  449. width: 85rpx;
  450. height: 32rpx;
  451. position: relative;
  452. bottom: -42rpx;
  453. &.down {
  454. transform: rotate(180deg);
  455. bottom: 0;
  456. }
  457. }
  458. }
  459. </style>