index.js 7.7 KB

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