index.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import { VantComponent } from '../common/component';
  2. import {
  3. ROW_HEIGHT,
  4. getNextDay,
  5. compareDay,
  6. copyDates,
  7. calcDateNum,
  8. formatMonthTitle,
  9. compareMonth,
  10. getMonths,
  11. getDayByOffset,
  12. } from './utils';
  13. import Toast from '../toast/toast';
  14. VantComponent({
  15. props: {
  16. title: {
  17. type: String,
  18. value: '日期选择',
  19. },
  20. color: String,
  21. show: {
  22. type: Boolean,
  23. observer(val) {
  24. if (val) {
  25. this.initRect();
  26. this.scrollIntoView();
  27. }
  28. },
  29. },
  30. formatter: null,
  31. confirmText: {
  32. type: String,
  33. value: '确定',
  34. },
  35. rangePrompt: String,
  36. defaultDate: {
  37. type: [Number, Array],
  38. observer(val) {
  39. this.setData({ currentDate: val });
  40. this.scrollIntoView();
  41. },
  42. },
  43. allowSameDay: Boolean,
  44. confirmDisabledText: String,
  45. type: {
  46. type: String,
  47. value: 'single',
  48. observer: 'reset',
  49. },
  50. minDate: {
  51. type: null,
  52. value: Date.now(),
  53. },
  54. maxDate: {
  55. type: null,
  56. value: new Date(
  57. new Date().getFullYear(),
  58. new Date().getMonth() + 6,
  59. new Date().getDate()
  60. ).getTime(),
  61. },
  62. position: {
  63. type: String,
  64. value: 'bottom',
  65. },
  66. rowHeight: {
  67. type: [Number, String],
  68. value: ROW_HEIGHT,
  69. },
  70. round: {
  71. type: Boolean,
  72. value: true,
  73. },
  74. poppable: {
  75. type: Boolean,
  76. value: true,
  77. },
  78. showMark: {
  79. type: Boolean,
  80. value: true,
  81. },
  82. showTitle: {
  83. type: Boolean,
  84. value: true,
  85. },
  86. showConfirm: {
  87. type: Boolean,
  88. value: true,
  89. },
  90. showSubtitle: {
  91. type: Boolean,
  92. value: true,
  93. },
  94. safeAreaInsetBottom: {
  95. type: Boolean,
  96. value: true,
  97. },
  98. closeOnClickOverlay: {
  99. type: Boolean,
  100. value: true,
  101. },
  102. maxRange: {
  103. type: [Number, String],
  104. value: null,
  105. },
  106. },
  107. data: {
  108. subtitle: '',
  109. currentDate: null,
  110. scrollIntoView: '',
  111. },
  112. created() {
  113. this.setData({
  114. currentDate: this.getInitialDate(),
  115. });
  116. },
  117. mounted() {
  118. if (this.data.show || !this.data.poppable) {
  119. this.initRect();
  120. this.scrollIntoView();
  121. }
  122. },
  123. methods: {
  124. reset() {
  125. this.setData({ currentDate: this.getInitialDate() });
  126. this.scrollIntoView();
  127. },
  128. initRect() {
  129. if (this.contentObserver != null) {
  130. this.contentObserver.disconnect();
  131. }
  132. const contentObserver = this.createIntersectionObserver({
  133. thresholds: [0, 0.1, 0.9, 1],
  134. observeAll: true,
  135. });
  136. this.contentObserver = contentObserver;
  137. contentObserver.relativeTo('.van-calendar__body');
  138. contentObserver.observe('.month', (res) => {
  139. if (res.boundingClientRect.top <= res.relativeRect.top) {
  140. // @ts-ignore
  141. this.setData({ subtitle: formatMonthTitle(res.dataset.date) });
  142. }
  143. });
  144. },
  145. getInitialDate() {
  146. const { type, defaultDate, minDate } = this.data;
  147. if (type === 'range') {
  148. const [startDay, endDay] = defaultDate || [];
  149. return [
  150. startDay || minDate,
  151. endDay || getNextDay(new Date(minDate)).getTime(),
  152. ];
  153. }
  154. if (type === 'multiple') {
  155. return [defaultDate || minDate];
  156. }
  157. return defaultDate || minDate;
  158. },
  159. scrollIntoView() {
  160. setTimeout(() => {
  161. const {
  162. currentDate,
  163. type,
  164. show,
  165. poppable,
  166. minDate,
  167. maxDate,
  168. } = this.data;
  169. const targetDate = type === 'single' ? currentDate : currentDate[0];
  170. const displayed = show || !poppable;
  171. if (!targetDate || !displayed) {
  172. return;
  173. }
  174. const months = getMonths(minDate, maxDate);
  175. months.some((month, index) => {
  176. if (compareMonth(month, targetDate) === 0) {
  177. this.setData({ scrollIntoView: `month${index}` });
  178. return true;
  179. }
  180. return false;
  181. });
  182. }, 100);
  183. },
  184. onOpen() {
  185. this.$emit('open');
  186. },
  187. onOpened() {
  188. this.$emit('opened');
  189. },
  190. onClose() {
  191. this.$emit('close');
  192. },
  193. onClosed() {
  194. this.$emit('closed');
  195. },
  196. onClickDay(event) {
  197. const { date } = event.detail;
  198. const { type, currentDate, allowSameDay } = this.data;
  199. if (type === 'range') {
  200. const [startDay, endDay] = currentDate;
  201. if (startDay && !endDay) {
  202. const compareToStart = compareDay(date, startDay);
  203. if (compareToStart === 1) {
  204. this.select([startDay, date], true);
  205. } else if (compareToStart === -1) {
  206. this.select([date, null]);
  207. } else if (allowSameDay) {
  208. this.select([date, date]);
  209. }
  210. } else {
  211. this.select([date, null]);
  212. }
  213. } else if (type === 'multiple') {
  214. let selectedIndex;
  215. const selected = currentDate.some((dateItem, index) => {
  216. const equal = compareDay(dateItem, date) === 0;
  217. if (equal) {
  218. selectedIndex = index;
  219. }
  220. return equal;
  221. });
  222. if (selected) {
  223. const cancelDate = currentDate.splice(selectedIndex, 1);
  224. this.setData({ currentDate });
  225. this.unselect(cancelDate);
  226. } else {
  227. this.select([...currentDate, date]);
  228. }
  229. } else {
  230. this.select(date, true);
  231. }
  232. },
  233. unselect(dateArray) {
  234. const date = dateArray[0];
  235. if (date) {
  236. this.$emit('unselect', copyDates(date));
  237. }
  238. },
  239. select(date, complete) {
  240. if (complete && this.data.type === 'range') {
  241. const valid = this.checkRange(date);
  242. if (!valid) {
  243. // auto selected to max range if showConfirm
  244. if (this.data.showConfirm) {
  245. this.emit([
  246. date[0],
  247. getDayByOffset(date[0], this.data.maxRange - 1),
  248. ]);
  249. } else {
  250. this.emit(date);
  251. }
  252. return;
  253. }
  254. }
  255. this.emit(date);
  256. if (complete && !this.data.showConfirm) {
  257. this.onConfirm();
  258. }
  259. },
  260. emit(date) {
  261. const getTime = (date) => (date instanceof Date ? date.getTime() : date);
  262. this.setData({
  263. currentDate: Array.isArray(date) ? date.map(getTime) : getTime(date),
  264. });
  265. this.$emit('select', copyDates(date));
  266. },
  267. checkRange(date) {
  268. const { maxRange, rangePrompt } = this.data;
  269. if (maxRange && calcDateNum(date) > maxRange) {
  270. Toast({
  271. context: this,
  272. message: rangePrompt || `选择天数不能超过 ${maxRange} 天`,
  273. });
  274. return false;
  275. }
  276. return true;
  277. },
  278. onConfirm() {
  279. if (
  280. this.data.type === 'range' &&
  281. !this.checkRange(this.data.currentDate)
  282. ) {
  283. return;
  284. }
  285. wx.nextTick(() => {
  286. this.$emit('confirm', copyDates(this.data.currentDate));
  287. });
  288. },
  289. },
  290. });