timeSever.vue 16 KB

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