index.vue 10 KB

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