body.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. <template>
  2. <div class="full-calendar-body">
  3. <div class="right-body">
  4. <div class="weeks">
  5. <div class="blank" v-if="tableType=='week'" style="width: 60px"></div>
  6. <strong class="week" v-for="(week,index) in weekNames">
  7. {{week}}
  8. <span v-if="tableType=='week' && weekDate.length">({{weekDate[index].showDate}})</span>
  9. </strong>
  10. </div>
  11. <div class="dates" ref="dates" v-if="tableType=='month'">
  12. <Spin v-if="loading" fix></Spin>
  13. <!-- absolute so we can make dynamic td -->
  14. <div class="dates-events">
  15. <div class="events-week" v-for="week in currentDates"
  16. v-if="week[0].isCurMonth || week[week.length-1].isCurMonth">
  17. <div class="events-day" v-for="day in week" track-by="$index"
  18. :class="{'today': day.isToday, 'not-cur-month': !day.isCurMonth}">
  19. <p class="day-number">{{day.monthDay}}</p>
  20. <div class="event-box" v-if="day.events.length">
  21. <div class="event-item"
  22. :class="{selected: showCard == event.id}"
  23. v-for="event in day.events"
  24. :style="`background-color: ${showCard == event.id ? (event.selectedColor||'#C7E6FD') : (event.color||'#C7E6FD')}`"
  25. @click="eventClick(event,$event)">
  26. <img class="avatar" v-if="event.avatar" :src="event.avatar" @error="($set(event, 'avatar', null))"/>
  27. <span v-else :class="`icon icon${event.id%4}`">{{event.name}}</span>
  28. <p class="info" v-html="isBegin(event, day.date, day.weekDay)"></p>
  29. <div id="card" :class="cardClass" v-if="event &&showCard == event.id" @click.stop>
  30. <slot name="body-card"></slot>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. </div>
  36. </div>
  37. </div>
  38. <div class="time" ref="time" v-else-if="tableType=='week'">
  39. <Spin v-if="loading" fix></Spin>
  40. <!-- <div class="column" v-for="day in weekDate" v-if="weekDate.length">
  41. <div class="single" v-for="time in timeDivide"></div>
  42. </div> -->
  43. <div class="row" v-for="(time,index) in timeDivide">
  44. <div class="left-info" v-if="tableType=='week'">
  45. <div class="time-info first" v-if="index==0">
  46. <span class="center">上午</span>
  47. </div>
  48. <div class="time-info" v-if="index==1">
  49. <span class="top">12:00</span>
  50. <span class="center">下午</span>
  51. </div>
  52. <div class="time-info" v-if="index==2">
  53. <span class="top">18:00</span>
  54. <span class="center">晚上</span>
  55. </div>
  56. </div>
  57. <div class="events-day" v-for="item in weekDate" v-if="weekDate.length"
  58. :class="{today: item.isToday}">
  59. <div class="event-box" v-if="item.events.length">
  60. <div class="event-item" v-for="event in item.events"
  61. :class="{selected: showCard == event.id}"
  62. :style="`background-color: ${showCard == event.id ? (event.selectedColor||'#C7E6FD') : (event.color||'#C7E6FD')}`"
  63. v-if="isTheday(item.date, event.start) && isInTime(time, event.start)"
  64. @click="eventClick(event,$event)">
  65. <img class="avatar" v-if="event.avatar" :src="event.avatar" @error="($set(event, 'avatar', null))"/>
  66. <span v-else :class="`icon icon${event.id%4}`">{{event.name}}</span>
  67. <p class="info" v-html="event.title"></p>
  68. <div id="card" :class="cardClass" v-if="event && showCard == event.id" @click.stop>
  69. <slot name="body-card"></slot>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. </div>
  77. </div>
  78. </template>
  79. <script type="text/babel">
  80. import dateFunc from './dateFunc'
  81. import bus from './bus'
  82. export default {
  83. props: {
  84. currentDate: {},
  85. events: {
  86. type: Array
  87. },
  88. weekNames: {
  89. type: Array,
  90. default: []
  91. },
  92. monthNames: {},
  93. firstDay: {},
  94. tableType: '',
  95. weekDays: {
  96. type: Array,
  97. default() {
  98. return dateFunc.getDates(new Date())
  99. }
  100. },
  101. isLimit: {
  102. type: Boolean,
  103. default: false
  104. },
  105. loading: {
  106. type: Boolean,
  107. default: true
  108. }
  109. },
  110. created() {
  111. let _this = this
  112. document.addEventListener('click', function (e) {
  113. _this.showCard = -1
  114. })
  115. // 监听header组件事件
  116. bus.$on('changeWeekDays', res => {
  117. })
  118. },
  119. data() {
  120. return {
  121. // weekNames : DAY_NAMES,
  122. weekMask: [1, 2, 3, 4, 5, 6, 7],
  123. // events : [],
  124. eventLimit: 18,
  125. showMore: false,
  126. morePos: {
  127. top: 0,
  128. left: 0
  129. },
  130. selectDay: {},
  131. timeDivide: [{
  132. start: 0,
  133. end: 12,
  134. label: '上午'
  135. }, {
  136. start: 12,
  137. end: 18,
  138. label: '下午'
  139. }, {
  140. start: 18,
  141. end: 23,
  142. label: '晚上'
  143. }],
  144. showCard: -1,
  145. cardLeft: 0,
  146. cardRight: 0,
  147. }
  148. },
  149. watch: {
  150. // events(val){
  151. // this.getCalendar()
  152. // },
  153. currentDate() {
  154. this.getCalendar()
  155. }
  156. },
  157. computed: {
  158. currentDates() {
  159. return this.getCalendar()
  160. },
  161. weekDate() {
  162. return this.getWeekDate()
  163. }
  164. },
  165. methods: {
  166. isBegin(event, date, index) {
  167. let st = new Date(event.start)
  168. if (index == 0 || st.toDateString() == date.toDateString()) {
  169. return event.title
  170. }
  171. return ' '
  172. },
  173. moreTitle(date) {
  174. let dt = new Date(date)
  175. return this.weekNames[dt.getDay()] + ', ' + this.monthNames[dt.getMonth()] + dt.getDate()
  176. },
  177. classNames(cssClass) {
  178. if (!cssClass) return ''
  179. // string
  180. if (typeof cssClass == 'string') return cssClass
  181. // Array
  182. if (Array.isArray(cssClass)) return cssClass.join(' ')
  183. // else
  184. return ''
  185. },
  186. getCalendar() {
  187. // calculate 2d-array of each month
  188. // first day of this month
  189. let now = new Date() // today
  190. let current = new Date(this.currentDate)
  191. let startDate = dateFunc.getStartDate(current) // 1st day of this month
  192. let curWeekDay = startDate.getDay()
  193. // begin date of this table may be some day of last month
  194. let diff = parseInt(this.firstDay) - curWeekDay + 1
  195. diff = diff > 0 ? (diff - 7) : diff
  196. startDate.setDate(startDate.getDate() + diff)
  197. let calendar = []
  198. for (let perWeek = 0; perWeek < 6; perWeek++) {
  199. let week = []
  200. for (let perDay = 0; perDay < 7; perDay++) {
  201. // console.log(startDate)
  202. week.push({
  203. monthDay: startDate.getDate(),
  204. isToday: now.toDateString() == startDate.toDateString(),
  205. isCurMonth: startDate.getMonth() == current.getMonth(),
  206. weekDay: perDay,
  207. date: new Date(startDate),
  208. events: this.slotEvents(new Date(startDate))
  209. })
  210. startDate.setDate(startDate.getDate() + 1)
  211. }
  212. calendar.push(week)
  213. }
  214. return calendar
  215. },
  216. slotEvents(date) {
  217. // console.log(date)
  218. let thisDayEvents = []
  219. this.events.filter(event => {
  220. let day = new Date(event.start)
  221. if (date.toLocaleDateString() === day.toLocaleDateString()) {
  222. thisDayEvents.push(event)
  223. }
  224. })
  225. this.judgeTime(thisDayEvents)
  226. return thisDayEvents
  227. },
  228. // 获取周视图的天元素格式化
  229. getWeekDate() {
  230. let newWeekDays = this.weekDays
  231. newWeekDays.forEach((e, index) => {
  232. e.showDate = dateFunc.format(e, 'MM-dd');
  233. e.date = dateFunc.format(e, 'yyyy-MM-dd');
  234. e.isToday = (new Date().toDateString() == e.toDateString())
  235. e.events = this.slotTimeEvents(e) // 整理事件集合 (拿事件去比较时间,分发事件到时间插槽内)
  236. })
  237. return newWeekDays
  238. },
  239. // 发现该时间段所有的事件
  240. slotTimeEvents(date) {
  241. let thisDayEvents = this.events
  242. thisDayEvents.filter(event => {
  243. let day = new Date(event.start)
  244. return date.toLocaleDateString() == day.toLocaleDateString()
  245. })
  246. this.judgeTime(thisDayEvents)
  247. return thisDayEvents
  248. },
  249. judgeTime(arr) {
  250. arr.forEach(event => {
  251. let day = new Date(event.start)
  252. // 加上时间戳后判断时间段
  253. let hour = day.getHours()
  254. let week = day.getDay()
  255. week == 0 ? week = 7 : ''
  256. if (this.timeDivide[0].start < hour < this.timeDivide[0].end) {
  257. event.time = 0
  258. } else if (this.timeDivide[1].start < hour < this.timeDivide[1].end) {
  259. event.time = 1
  260. } else if (this.timeDivide[2].start < hour < this.timeDivide[2].end) {
  261. event.time = 2
  262. }
  263. event.weekDay = this.weekNames[Number(week) - 1]
  264. event.weekDayIndex = week
  265. })
  266. },
  267. isTheday(day1, day2) {
  268. return new Date(day1).toDateString() === new Date(day2).toDateString()
  269. },
  270. isStart(eventDate, date) {
  271. let st = new Date(eventDate)
  272. return st.toDateString() == date.toDateString()
  273. },
  274. isEnd(eventDate, date) {
  275. let ed = new Date(eventDate)
  276. return ed.toDateString() == date.toDateString()
  277. },
  278. isInTime(time, date) {
  279. let hour = new Date(date).getHours()
  280. return (time.start <= hour) && (hour < time.end)
  281. },
  282. selectThisDay(day, jsEvent) {
  283. this.selectDay = day
  284. this.showMore = true
  285. this.morePos = this.computePos(event.target)
  286. this.morePos.top -= 100
  287. let events = day.events.filter(item => {
  288. return item.isShow == true
  289. })
  290. this.$emit('moreclick', day.date, events, jsEvent)
  291. },
  292. computePos(target) {
  293. let eventRect = target.getBoundingClientRect()
  294. let pageRect = this.$refs.dates.getBoundingClientRect()
  295. return {
  296. left: eventRect.left - pageRect.left,
  297. top: eventRect.top + eventRect.height - pageRect.top
  298. }
  299. },
  300. dayClick(day, jsEvent) {
  301. // console.log('dayClick')
  302. // this.$emit('dayclick', day, jsEvent)
  303. },
  304. eventClick(event, jsEvent) {
  305. // console.log(event,jsEvent, 'evenvet')
  306. this.showCard = event.id
  307. jsEvent.stopPropagation()
  308. // let pos = this.computePos(jsEvent.target)
  309. this.$emit('eventclick', event, jsEvent)
  310. let x = jsEvent.x
  311. let y = jsEvent.y
  312. // console.log(jsEvent)
  313. // 判断出左右中三边界的取值范围进而分配class
  314. if (x > 400 && x < window.innerWidth - 200) {
  315. this.cardClass = "center-card"
  316. } else if (x <= 400) {
  317. this.cardClass = "left-card"
  318. } else {
  319. this.cardClass = "right-card"
  320. }
  321. if (y > window.innerHeight - 300) {
  322. this.cardClass += ' ' + 'bottom-card'
  323. }
  324. },
  325. }
  326. }
  327. </script>
  328. <style lang="scss">
  329. .full-calendar-body {
  330. background-color: #fff;
  331. display: flex;
  332. margin-top: 12px;
  333. border: 1px solid #D0D9FF;
  334. .left-info {
  335. width: 60px;
  336. .time-info {
  337. height: 100%;
  338. position: relative;
  339. &.first {
  340. border-top: 1px solid #EFF2FF;
  341. }
  342. &:nth-child(2) {
  343. border-top: 1px solid #EFF2FF;
  344. border-bottom: 1px solid #EFF2FF;
  345. }
  346. .center {
  347. position: absolute;
  348. top: 50%;
  349. left: 50%;
  350. transform: translate(-50%, -50%);
  351. width: 14px;
  352. font-size: 14px;
  353. word-wrap: break-word;
  354. letter-spacing: 10px;
  355. }
  356. .top {
  357. position: absolute;
  358. top: -8px;
  359. width: 100%;
  360. text-align: center;
  361. }
  362. }
  363. }
  364. .right-body {
  365. flex: 1;
  366. width: 100%;
  367. position: relative;
  368. .weeks {
  369. display: flex;
  370. border-bottom: 1px solid #FFCC36;
  371. .week {
  372. flex: 1;
  373. text-align: center;
  374. height: 40px;
  375. display: flex;
  376. align-items: center;
  377. justify-content: center;
  378. }
  379. }
  380. .dates {
  381. position: relative;
  382. .dates-events {
  383. z-index: 1;
  384. width: 100%;
  385. .events-week {
  386. display: flex;
  387. min-height: 180px;
  388. .events-day {
  389. text-overflow: ellipsis;
  390. flex: 1;
  391. width: 0;
  392. height: auto;
  393. padding: 4px;
  394. border-right: 1px solid #EFF2FF;
  395. border-bottom: 1px solid #EFF2FF;
  396. background-color: #fff;
  397. .day-number {
  398. text-align: left;
  399. padding: 4px 5px 4px 4px;
  400. }
  401. &.not-cur-month {
  402. .day-number {
  403. color: #ECECED;
  404. }
  405. }
  406. &.today {
  407. background-color: #FFFCF3;
  408. }
  409. &:last-child {
  410. border-right: 0;
  411. }
  412. .event-box {
  413. max-height: 280px;
  414. overflow: auto;
  415. .event-item {
  416. cursor: pointer;
  417. font-size: 12px;
  418. background-color: #C7E6FD;
  419. margin-bottom: 2px;
  420. color: rgba(0, 0, 0, .87);
  421. padding: 8px 0 8px 4px;
  422. height: auto;
  423. line-height: 30px;
  424. display: flex;
  425. // transform:translate(0,0);
  426. align-items: flex-start;
  427. position: relative;
  428. border-radius: 4px;
  429. &.is-end {
  430. display: none;
  431. }
  432. &.is-start {
  433. display: block;
  434. }
  435. &.is-opacity {
  436. display: none;
  437. }
  438. .avatar {
  439. width: 18px;
  440. height: 18px;
  441. border: 0;
  442. border-radius: 50%;
  443. overflow: hidden;
  444. display: inline-block;
  445. }
  446. .icon {
  447. width: 18px;
  448. height: 18px;
  449. line-height: 18px;
  450. border-radius: 10px;
  451. text-align: center;
  452. color: #fff;
  453. display: inline-block;
  454. &.icon0 {
  455. background: #27BA9C;
  456. }
  457. &.icon1 {
  458. background: #5272FF;
  459. }
  460. &.icon2 {
  461. background: #FFCC36;
  462. }
  463. &.icon3 {
  464. background: #FF7062;
  465. }
  466. }
  467. .info {
  468. width: calc(100% - 30px);
  469. display: inline-block;
  470. margin-left: 5px;
  471. line-height: 18px;
  472. word-break: break-all;
  473. word-wrap: break-word;
  474. font-size: 12px;
  475. }
  476. #card {
  477. cursor: initial;
  478. position: absolute;
  479. z-index: 999;
  480. min-width: 250px;
  481. height: auto;
  482. left: 50%;
  483. top: calc(100% + 10px);
  484. transform: translate(-50%, 0);
  485. min-height: 100px;
  486. background: #fff;
  487. // border: 1px solid #eee;
  488. box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
  489. border-radius: 4px;
  490. overflow: hidden;
  491. &.left-card {
  492. left: 0;
  493. transform: translate(0, 0);
  494. }
  495. &.right-card {
  496. right: 0;
  497. left: auto;
  498. transform: translate(0, 0);
  499. }
  500. &.bottom-card {
  501. top: auto;
  502. bottom: calc(100% + 10px);
  503. }
  504. }
  505. &:hover {
  506. .info {
  507. // font-weight: bold;
  508. }
  509. }
  510. &.selected {
  511. .info {
  512. color: #fff;
  513. font-weight: normal;
  514. }
  515. .icon {
  516. background-color: transparent !important;
  517. }
  518. }
  519. }
  520. .more-link {
  521. cursor: pointer;
  522. // text-align: right;
  523. padding-left: 8px;
  524. padding-right: 2px;
  525. color: rgba(0, 0, 0, .38);
  526. font-size: 12px;
  527. }
  528. }
  529. }
  530. &:last-child {
  531. .events-day {
  532. border-bottom: 0;
  533. }
  534. }
  535. }
  536. }
  537. .more-events {
  538. position: absolute;
  539. width: 150px;
  540. z-index: 2;
  541. border: 1px solid #eee;
  542. box-shadow: 0 2px 6px rgba(0, 0, 0, .15);
  543. .more-header {
  544. background-color: #eee;
  545. padding: 5px;
  546. display: flex;
  547. align-items: center;
  548. font-size: 14px;
  549. .title {
  550. flex: 1;
  551. }
  552. .close {
  553. margin-right: 2px;
  554. cursor: pointer;
  555. font-size: 16px;
  556. }
  557. }
  558. .more-body {
  559. height: 125px;
  560. overflow: hidden;
  561. background: #fff;
  562. .body-list {
  563. height: 120px;
  564. padding: 5px;
  565. overflow: auto;
  566. background-color: #fff;
  567. .body-item {
  568. cursor: pointer;
  569. font-size: 12px;
  570. background-color: #C7E6FD;
  571. margin-bottom: 2px;
  572. color: rgba(0, 0, 0, .87);
  573. padding: 0 0 0 4px;
  574. height: 18px;
  575. line-height: 18px;
  576. white-space: nowrap;
  577. overflow: hidden;
  578. text-overflow: ellipsis;
  579. }
  580. }
  581. }
  582. }
  583. }
  584. .time {
  585. position: relative;
  586. .row {
  587. width: 100%;
  588. display: flex;
  589. min-height: 180px;
  590. .events-day {
  591. border-bottom: 1px solid #EFF2FF;
  592. border-left: 1px solid #EFF2FF;
  593. background-color: #fff;
  594. height: auto;
  595. text-overflow: ellipsis;
  596. flex: 1;
  597. width: 0;
  598. padding: 4px;
  599. .event-box {
  600. max-height: 280px;
  601. overflow: auto;
  602. }
  603. &.today {
  604. background-color: #FFFCF3;
  605. }
  606. }
  607. .event-item {
  608. cursor: pointer;
  609. font-size: 12px;
  610. background-color: #C7E6FD;
  611. margin-bottom: 2px;
  612. color: rgba(0, 0, 0, .87);
  613. padding: 8px 0 8px 4px;
  614. height: auto;
  615. line-height: 30px;
  616. display: flex;
  617. align-items: flex-start;
  618. position: relative;
  619. border-radius: 4px;
  620. .avatar {
  621. width: 18px;
  622. height: 18px;
  623. border: 0;
  624. border-radius: 50%;
  625. overflow: hidden;
  626. display: inline-block;
  627. }
  628. .icon {
  629. width: 18px;
  630. height: 18px;
  631. line-height: 18px;
  632. border-radius: 10px;
  633. text-align: center;
  634. color: #fff;
  635. display: inline-block;
  636. padding: 0 2px;
  637. overflow: hidden;
  638. &.icon0 {
  639. background: #27BA9C;
  640. }
  641. &.icon1 {
  642. background: #5272FF;
  643. }
  644. &.icon2 {
  645. background: #FFCC36;
  646. }
  647. &.icon3 {
  648. background: #FF7062;
  649. }
  650. }
  651. .info {
  652. width: calc(100% - 30px);
  653. display: inline-block;
  654. margin-left: 5px;
  655. line-height: 18px;
  656. word-break: break-all;
  657. word-wrap: break-word;
  658. font-size: 12px;
  659. }
  660. #card {
  661. cursor: initial;
  662. position: absolute;
  663. z-index: 999;
  664. min-width: 250px;
  665. height: auto;
  666. left: 50%;
  667. top: calc(100% + 10px);
  668. transform: translate(-50%, 0);
  669. min-height: 100px;
  670. background: #fff;
  671. box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
  672. border-radius: 4px;
  673. overflow: hidden;
  674. &.left-card {
  675. left: 0;
  676. transform: translate(0, 0);
  677. }
  678. &.right-card {
  679. right: 0;
  680. left: auto;
  681. transform: translate(0, 0);
  682. }
  683. &.bottom-card {
  684. top: auto;
  685. bottom: calc(100% + 10px);
  686. }
  687. }
  688. &:hover {
  689. .info {
  690. // font-weight: bold;
  691. }
  692. }
  693. &.selected {
  694. .info {
  695. color: #fff;
  696. font-weight: normal;
  697. }
  698. // background-color: #5272FF !important;
  699. .icon {
  700. background-color: transparent !important;
  701. }
  702. }
  703. }
  704. &:last-child {
  705. .single {
  706. border-bottom: 0;
  707. }
  708. }
  709. }
  710. }
  711. }
  712. }
  713. </style>