calendar.uvue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <template>
  2. <view class="root">
  3. <view class="date">
  4. <text class="date-text">{{ current_month }}</text>
  5. </view>
  6. <view ref="draw-header" class="calendar-header"></view>
  7. <view ref="draw-weeks" class="calendar-week" @touchstart="select"></view>
  8. <view class="btn-group">
  9. <button size="mini" @click="preDate">上个月</button>
  10. <button size="mini" @click="gotoToday">回到今天</button>
  11. <button size="mini" @click="nextDate">下个月</button>
  12. </view>
  13. <view>{{ timeData.fullDate }} {{ current_day }}</view>
  14. </view>
  15. </template>
  16. <script>
  17. import { Calendar, DateType } from './index.uts'
  18. type CoordsType = {
  19. x: number;
  20. y: number;
  21. width: number;
  22. height: number;
  23. data: DateType
  24. }
  25. export default {
  26. data () {
  27. return {
  28. weeks: [] as Array<Array<DateType>>,
  29. $coords: [] as Array<CoordsType>,
  30. $calendar: new Calendar() as Calendar,
  31. timeData: {
  32. fullDate: '',
  33. year: 0,
  34. month: 0,
  35. date: 0,
  36. day: 0,
  37. lunar: '',
  38. disabled: false,
  39. is_today: false
  40. } as DateType,
  41. testWidth: 0
  42. }
  43. },
  44. computed: {
  45. // 获取月份
  46. current_month (): string {
  47. const nowDate = this.timeData
  48. const month = nowDate.month
  49. return month < 10 ? '0' + month : month.toString()
  50. },
  51. current_day (): string {
  52. const time = this.timeData.data
  53. if (time == null) {
  54. return ''
  55. }
  56. return time.IMonthCn + time.IDayCn
  57. }
  58. },
  59. created () { },
  60. onReady () {
  61. const calendar = this.$data['$calendar'] as Calendar
  62. this.weeks = calendar.getWeeks()
  63. this.timeData = calendar.getDateInfo()
  64. // 绘制日历头部
  65. this.drawHeader()
  66. this.drawWeek(this.weeks, '')
  67. // 仅自动化测试
  68. const header = this.$refs['draw-header'] as UniElement
  69. this.testWidth = header.getBoundingClientRect().width;
  70. },
  71. methods: {
  72. // 触发整个日历的点击事件,需要计算点击位置
  73. select (event: TouchEvent) {
  74. const refs = this.$refs['draw-weeks'] as UniElement
  75. const rect = refs.getBoundingClientRect();
  76. const dom_x = rect.left; // 元素左上角相对于视口的 X 坐标
  77. const dom_y = rect.top; // 元素左上角相对于视口的 Y 坐标
  78. const touch = event.touches[0];
  79. const clientX = touch.clientX; // X 坐标
  80. const clientY = touch.clientY; // Y 坐标
  81. // 计算点击的相对位置
  82. const x = clientX - dom_x
  83. const y = clientY - dom_y
  84. this.clickGrid(x, y)
  85. },
  86. // 点击具体的日历格子
  87. clickGrid (x: number, y: number) {
  88. // 小格子数组
  89. // const gridArray = this.$data.$coords
  90. const calendar = this.$data['$calendar'] as Calendar
  91. const gridArray = this.$data['$coords'] as Array<CoordsType>
  92. // 遍历小格子数组,找到最接近点击坐标的小格子
  93. for (let i = 0; i < gridArray.length; i++) {
  94. const grid = gridArray[i]
  95. // 计算小格子理论上的最大值
  96. const max_x = grid.x + grid.width
  97. const max_y = grid.y + grid.height
  98. const is_x_limit = grid.x < x && x < max_x
  99. const is_y_limit = grid.y < y && y < max_y
  100. const is_select = is_x_limit && is_y_limit
  101. if (is_select) {
  102. const data = grid.data
  103. this.timeData = calendar.getDateInfo(data.fullDate)
  104. this.drawWeek(this.weeks, grid.data.fullDate)
  105. }
  106. }
  107. },
  108. // 切换上个月
  109. preDate () {
  110. const fulldate = this.timeData.fullDate
  111. const calendar = this.$data['$calendar'] as Calendar
  112. let time = calendar.getDate(fulldate, -1, 'month')
  113. const newDate = time.year + '-' + time.month + '-1';
  114. time = calendar.getDate(newDate)
  115. this.timeData = calendar.getDateInfo(time.fullDate)
  116. this.weeks = calendar.getWeeks(time.fullDate)
  117. // 判断是否回到当前月份
  118. if (this.isCurrentMonth(time.year, time.month)) {
  119. this.gotoToday();
  120. } else {
  121. // 否则正常绘制日历
  122. this.drawWeek(this.weeks, time.fullDate)
  123. }
  124. },
  125. // 切换下个他
  126. nextDate () {
  127. const fulldate = this.timeData.fullDate
  128. const calendar = this.$data['$calendar'] as Calendar
  129. let time = calendar.getDate(fulldate, 1, 'month')
  130. const newDate = time.year + '-' + time.month + '-1';
  131. time = calendar.getDate(newDate)
  132. this.timeData = calendar.getDateInfo(time.fullDate)
  133. this.weeks = calendar.getWeeks(time.fullDate)
  134. // 判断是否回到当前月份
  135. if (this.isCurrentMonth(time.year, time.month)) {
  136. this.gotoToday();
  137. } else {
  138. // 否则正常绘制日历
  139. this.drawWeek(this.weeks, time.fullDate)
  140. }
  141. },
  142. // 回到今天
  143. gotoToday () {
  144. const calendar = this.$data['$calendar'] as Calendar
  145. const time = calendar.getDate()
  146. this.timeData = calendar.getDateInfo(time.fullDate)
  147. this.weeks = calendar.getWeeks(time.fullDate)
  148. // 重新绘制日历
  149. this.drawWeek(this.weeks, time.fullDate)
  150. },
  151. // 判断是否为当前月份
  152. isCurrentMonth(year: number, month: number): boolean {
  153. const today = new Date();
  154. return year === today.getFullYear() && month === today.getMonth() + 1;
  155. },
  156. // 绘制日历顶部信息
  157. drawHeader () {
  158. const refs = this.$refs['draw-header'] as UniElement
  159. let ctx = refs.getDrawableContext()
  160. if (ctx == null) return
  161. const date_header_map = ['一', '二', '三', '四', '五', '六', '日']
  162. const width = refs.getBoundingClientRect().width
  163. const num = date_header_map.length
  164. const one_width = width / num
  165. ctx.font = '12'
  166. ctx.textAlign = 'center'
  167. for (let i = 0; i < num; i++) {
  168. let box_left = i * one_width + 2
  169. let box_width = one_width - 4
  170. let box_height = 26
  171. // 文本赋值
  172. const text = date_header_map[i]
  173. let text_left = box_width / 2 + box_left
  174. let text_top = box_height / 2 + 6
  175. ctx.fillText(text, text_left, text_top)
  176. }
  177. ctx.update()
  178. },
  179. // 绘制日历主体
  180. drawWeek (weeks: Array<Array<DateType>>, time: string) {
  181. const start_time = Date.now()
  182. const refs = this.$refs['draw-weeks'] as UniElement
  183. let ctx = refs.getDrawableContext()
  184. if (ctx == null) return
  185. const dom = refs.getBoundingClientRect()
  186. const width = dom.width
  187. const height = dom.height
  188. let week_len = weeks.length
  189. const one_width = width / weeks[0].length
  190. const one_height = height / week_len
  191. if (time !== '') {
  192. this.$data['$coords'] = [] as Array<CoordsType>
  193. ctx.reset()
  194. }
  195. ctx.textAlign = 'center'
  196. for (let week = 0; week < week_len; week++) {
  197. const week_item = weeks[week]
  198. for (let day = 0; day < week_item.length; day++) {
  199. const day_item = week_item[day]
  200. let day_left = day * one_width + 2
  201. let day_top = one_height * week + 2
  202. let day_width = one_width - 4
  203. let day_height = one_height - 4
  204. // 文本赋值
  205. let text = day_item.date.toString()
  206. let text_left = day * one_width + (one_width / 2)
  207. let text_top = one_height * week + 25
  208. ctx.font = '16'
  209. // 日期是否禁用
  210. if (day_item.disabled) {
  211. ctx.fillStyle = '#ccc'
  212. } else {
  213. // 是否为今天
  214. if (day_item.is_today) {
  215. ctx.fillStyle = 'red'
  216. } // 是否为选中日期
  217. else if (time == day_item.fullDate) {
  218. ctx.fillStyle = 'blue';
  219. }
  220. // 默认颜色
  221. else {
  222. ctx.fillStyle = '#666';
  223. }
  224. // 第一次渲染获取数据
  225. // if (time == '') {
  226. // 存储坐标组,用于点击事件
  227. const coords: CoordsType = {
  228. x: day_left,
  229. y: day_top,
  230. width: day_width,
  231. height: day_height,
  232. data: day_item
  233. }
  234. // TODO 兼容安卓data内$开头的属性的赋值问题
  235. let gridArr = this.$data['$coords'] as Array<CoordsType>
  236. gridArr.push(coords)
  237. // }
  238. }
  239. ctx.fillText(text, text_left, text_top)
  240. text = day_item.lunar
  241. let lunar_left = day * one_width + (one_width / 2)
  242. let lunar_top = one_height * week + 42
  243. ctx.font = '10'
  244. ctx.fillText(text, lunar_left, lunar_top)
  245. }
  246. }
  247. ctx.update()
  248. console.log('diff time', Date.now() - start_time);
  249. }
  250. }
  251. }
  252. </script>
  253. <style>
  254. .root {
  255. flex: 1;
  256. position: relative;
  257. padding: 15px;
  258. background-color: #fff;
  259. }
  260. .calendar-header {
  261. height: 30px;
  262. margin-bottom: 10px;
  263. }
  264. .date {
  265. margin-bottom: 10px;
  266. margin-left: 10px;
  267. }
  268. .date-text {
  269. font-size: 34px;
  270. font-weight: bold;
  271. }
  272. .calendar-week {
  273. height: 350px;
  274. margin: 2px 0;
  275. }
  276. .btn-group {
  277. display: flex;
  278. flex-direction: row;
  279. justify-content: space-between;
  280. margin: 20px 0;
  281. }
  282. </style>