c-datepicker.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. <template>
  2. <view class="picker">
  3. <!-- 日期选择器 -->
  4. <view v-if="type!='time'" class="picker-modal">
  5. <view class="picker-modal-header">
  6. <!-- <view class="picker-icon picker-icon-zuozuo" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetYear('-1')"></view> -->
  7. <view class="date-btn" style="justify-content: flex-start;padding-left: 8px;" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetMonth('-1')">
  8. <image class="inner-img" src="/static/c-datepicker/left_btn.png" mode="aspectFit"></image>
  9. </view>
  10. <view class="picker-modal-header-title"><text style="margin-left: 0px;">{{title.substr(0,5)}}</text><text style="margin-left: 10px;">{{title.substr(5)}}</text></view>
  11. <view class="date-btn" style="justify-content: flex-end;padding-right: 8px;" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetMonth('+1')">
  12. <image class="inner-img" src="/static/c-datepicker/right_btn.png" mode="aspectFit"></image>
  13. </view>
  14. <!-- <view class="picker-icon picker-icon-youyou" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetYear('+1')"></view> -->
  15. </view>
  16. <swiper class="picker-modal-body" :circular="true" :duration="200" :skip-hidden-item-layout="true" :current="calendarIndex" @change="onSwiperChange">
  17. <swiper-item class="picker-calendar" v-for="(calendar,calendarIndex2) in calendars" :key="calendarIndex2">
  18. <view class="picker-calendar-view" v-for="(week,index) in weeks" :key="index">
  19. <view class="picker-calendar-view-item" style="color: #000000;font-weight: bold;">{{week}}</view>
  20. </view>
  21. <view class="picker-calendar-view" v-for="(date,dateIndex) in calendar" :key="dateIndex" @click="onSelectDate(date)">
  22. <!-- 背景样式 -->
  23. <view v-show="date.bgStyle.type" :class="'picker-calendar-view-'+date.bgStyle.type" :style="{background: date.bgStyle.background}"></view>
  24. <!-- 正常和选中样式 -->
  25. <view class="picker-calendar-view-item" :style="{opacity: date.statusStyle.opacity, color: date.statusStyle.color, background: date.statusStyle.background}">
  26. <text class="text">{{date.title}}</text>
  27. </view>
  28. <!-- 小圆点样式 -->
  29. <view class="picker-calendar-view-dot" :style="{opacity: date.dotStyle.opacity, background: date.dotStyle.background}"></view>
  30. <!-- 信息样式 -->
  31. <view v-show="date.tips" class="picker-calendar-view-tips">{{date.tips}}</view>
  32. </view>
  33. </swiper-item>
  34. </swiper>
  35. </view>
  36. </view>
  37. </template>
  38. <script>
  39. /**
  40. * 工具函数库
  41. */
  42. const DateTools = {
  43. /**
  44. * 获取公历节日
  45. * @param date Date对象
  46. */
  47. getHoliday(date) {
  48. let holidays = {
  49. '0101': '元旦',
  50. '0214': '情人',
  51. '0308': '妇女',
  52. '0312': '植树',
  53. '0401': '愚人',
  54. '0501': '劳动',
  55. '0504': '青年',
  56. '0601': '儿童',
  57. '0701': '建党',
  58. '0801': '建军',
  59. '0903': '抗日',
  60. '0910': '教师',
  61. '1001': '国庆',
  62. '1031': '万圣',
  63. '1224': '平安',
  64. '1225': '圣诞'
  65. };
  66. let value = this.format(date, 'mmdd');
  67. if (holidays[value]) return holidays[value];
  68. return false;
  69. },
  70. /**
  71. * 解析标准日期格式
  72. * @param s 日期字符串
  73. * @return 返回Date对象
  74. */
  75. parse: s => new Date(s.replace(/(年|月|-)/g, '/').replace(/(日)/g, '')),
  76. /**
  77. * 比较日期是否为同一天
  78. * @param a Date对象
  79. * @param b Date对象
  80. * @return Boolean
  81. */
  82. isSameDay: (a, b) => a.getMonth() == b.getMonth() && a.getFullYear() == b.getFullYear() && a.getDate() == b.getDate(),
  83. isBigDay: (a, b) => {
  84. if(new Date(a.getFullYear(),a.getMonth(),a.getDate()) >= new Date(b.getFullYear(),b.getMonth(),b.getDate())){
  85. return true
  86. }else {
  87. return false
  88. }
  89. },
  90. /**
  91. * 格式化Date对象
  92. * @param d 日期对象
  93. * @param f 格式字符串
  94. * @return 返回格式化后的字符串
  95. */
  96. format(d, f) {
  97. var o = {
  98. "m+": d.getMonth() + 1,
  99. "d+": d.getDate(),
  100. "h+": d.getHours(),
  101. "i+": d.getMinutes(),
  102. "s+": d.getSeconds(),
  103. "q+": Math.floor((d.getMonth() + 3) / 3),
  104. };
  105. if (/(y+)/.test(f))
  106. f = f.replace(RegExp.$1, (d.getFullYear() + "").substr(4 - RegExp.$1.length));
  107. for (var k in o)
  108. if (new RegExp("(" + k + ")").test(f))
  109. f = f.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
  110. return f;
  111. },
  112. /**
  113. * 用于format格式化后的反解析
  114. * @param s 日期字符串
  115. * @param f 格式字符串
  116. * @return 返回Date对象
  117. */
  118. inverse(s, f) {
  119. var o = {
  120. "y": '',
  121. "m": '',
  122. "d": '',
  123. "h": '',
  124. "i": '',
  125. "s": '',
  126. };
  127. let d = new Date();
  128. if (s.length != f.length) return d;
  129. for (let i in f)
  130. if (o[f[i]] != undefined) o[f[i]] += s[i];
  131. if (o.y) d.setFullYear(o.y.length < 4 ? (d.getFullYear() + '').substr(0, 4 - o.y.length) + o.y : o.y);
  132. o.m && d.setMonth(o.m - 1, 1);
  133. o.d && d.setDate(o.d - 0);
  134. o.h && d.setHours(o.h - 0);
  135. o.i && d.setMinutes(o.i - 0);
  136. o.s && d.setSeconds(o.s - 0);
  137. return d;
  138. },
  139. /**
  140. * 获取日历数组(42天)
  141. * @param date 日期对象或日期字符串
  142. * @param proc 处理日历(和forEach类似),传递一个数组中的item
  143. * @return Array
  144. */
  145. getCalendar(date, proc) {
  146. let it = new Date(date),
  147. calendars = [];
  148. it.setDate(1);
  149. it.setDate(it.getDate() - ((it.getDay() == 0 ? 7 : it.getDay()) - 0)); //偏移量
  150. for (let i = 0; i < 42; i++) {
  151. let tmp = {
  152. dateObj: new Date(it),
  153. title: it.getDate(),
  154. isOtherMonth: it.getMonth() < date.getMonth() || it.getMonth() > date.getMonth()
  155. };
  156. calendars.push(Object.assign(tmp, proc ? proc(tmp) : {}));
  157. it.setDate(it.getDate() + 1);
  158. }
  159. return calendars;
  160. },
  161. /**
  162. * 获取日期到指定的月份1号(不改变原来的date对象)
  163. * @param d Date对象
  164. * @param v 指定的月份
  165. * @return Date对象
  166. */
  167. getDateToMonth(d, v) {
  168. let n = new Date(d);
  169. n.setMonth(v, 1);
  170. return n;
  171. },
  172. /**
  173. * 把时间数组转为时间字符串
  174. * @param t Array[时,分,秒]
  175. * @param showSecinds 是否显示秒
  176. * @return 字符串 时:分[:秒]
  177. */
  178. formatTimeArray(t, s) {
  179. let r = [...t];
  180. if (!s) r.length = 2;
  181. r.forEach((v, k) => r[k] = ('0' + v).slice(-2));
  182. return r.join(':');
  183. }
  184. };
  185. export default {
  186. props: {
  187. //颜色
  188. color: {
  189. type: String,
  190. default: '#79A9CE'
  191. },
  192. //是否显示秒 针对type为datetime或time时生效
  193. showSeconds: {
  194. type: Boolean,
  195. default: false
  196. },
  197. //初始的值
  198. value: [String, Array],
  199. //类型date time datetime range rangetime
  200. type: {
  201. type: String,
  202. default: 'date'
  203. },
  204. //是否显示
  205. show: {
  206. type: Boolean,
  207. default: false
  208. },
  209. //初始格式
  210. format: {
  211. type: String,
  212. default: ''
  213. },
  214. //显示公历节日
  215. showHoliday: {
  216. type: Boolean,
  217. default: false
  218. },
  219. //显示提示
  220. showTips: {
  221. type: Boolean,
  222. default: false
  223. },
  224. //开始文案 针对type为范围选择时生效
  225. beginText: {
  226. type: String,
  227. default: '开始'
  228. },
  229. //结束文案 针对type为范围选择时生效
  230. endText: {
  231. type: String,
  232. default: '结束'
  233. },
  234. // 开始时间
  235. beginDate: {
  236. type: String,
  237. default: ''
  238. },
  239. // 结束时间
  240. endDate: {
  241. type: String,
  242. default: ''
  243. }
  244. },
  245. data() {
  246. return {
  247. isShow: false, //是否显示
  248. isMultiSelect: false, //是否为多选
  249. isContainTime: false, //是否包含时间
  250. date: {}, //当前日期对象
  251. weeks: ["日", "一", "二", "三", "四", "五", "六"],
  252. title: DateTools.format(new Date(), 'yyyy年mm月'), //标题
  253. calendars: [[],[],[]], //日历数组
  254. calendarIndex: 1, //当前日历索引
  255. checkeds: [], //选中的日期对象集合
  256. showTimePicker: false, //是否显示时间选择器
  257. timeValue: [0, 0, 0], //时间选择器的值
  258. timeType: 'begin', //当前时间选择的类型
  259. beginTime: [0, 0, 0], //当前所选的开始时间值
  260. endTime: [0, 0, 0], //当前所选的结束时间值
  261. };
  262. },
  263. methods: {
  264. //设置值
  265. setValue(value) {
  266. if(this.beginDate && this.endDate){
  267. if(DateTools.isBigDay(new Date(),new Date(this.beginDate)) && DateTools.isBigDay(new Date(this.endDate), new Date())){
  268. this.date = new Date();
  269. }else{
  270. this.date = new Date(this.endDate);
  271. }
  272. }else if(this.beginDate){
  273. if(DateTools.isBigDay(new Date(),new Date(this.beginDate)) ){
  274. this.date = new Date();
  275. }else{
  276. this.date = new Date(this.beginDate);
  277. }
  278. }else if(this.endDate){
  279. if(DateTools.isBigDay(new Date(this.endDate),new Date()) ){
  280. this.date = new Date();
  281. }else{
  282. this.date = new Date(this.endDate);
  283. }
  284. }else{
  285. this.date = new Date();
  286. }
  287. this.checkeds = [];
  288. this.isMultiSelect = this.type.indexOf('range') >= 0;
  289. this.isContainTime = this.type.indexOf('time') >= 0;
  290. //将字符串解析为Date对象
  291. let parseDateStr = (str) => (this.format ? DateTools.inverse(str, this.format) : DateTools.parse(str));
  292. if (value) {
  293. if (this.isMultiSelect) {
  294. Array.isArray(value) && value.forEach((dateStr, index) => {
  295. let date = parseDateStr(dateStr);
  296. let time = [date.getHours(), date.getMinutes(), date.getSeconds()];
  297. if (index == 0) this.beginTime = time;
  298. else this.endTime = time;
  299. this.checkeds.push(date);
  300. });
  301. } else {
  302. if (this.type == 'time') {
  303. let date = parseDateStr('2019/1/1 ' + value);
  304. this.beginTime = [date.getHours(), date.getMinutes(), date.getSeconds()];
  305. this.onShowTimePicker('begin');
  306. } else {
  307. this.checkeds.push(parseDateStr(value));
  308. if (this.isContainTime) this.beginTime = [
  309. this.checkeds[0].getHours(),
  310. this.checkeds[0].getMinutes(),
  311. this.checkeds[0].getSeconds()
  312. ];
  313. }
  314. }
  315. if (this.checkeds.length) this.date = new Date(this.checkeds[0]);
  316. } else {
  317. if (this.isContainTime) {
  318. this.beginTime = [this.date.getHours(), this.date.getMinutes(), this.date.getSeconds()];
  319. if (this.isMultiSelect) this.endTime = [...this.beginTime];
  320. }
  321. this.checkeds.push(new Date(this.date));
  322. }
  323. if (this.type != 'time') this.refreshCalendars(true);
  324. else this.onShowTimePicker('begin');
  325. },
  326. //改变年份
  327. onSetYear(value) {
  328. this.date.setFullYear(this.date.getFullYear() + parseInt(value));
  329. this.refreshCalendars(true);
  330. },
  331. //改变月份
  332. onSetMonth(value) {
  333. this.date.setMonth(this.date.getMonth() + parseInt(value));
  334. this.refreshCalendars(true);
  335. },
  336. //时间选择变更
  337. onTimeChange(e) {
  338. this.timeValue = e.detail.value;
  339. },
  340. //设置时间选择器的显示状态
  341. onShowTimePicker(type) {
  342. this.showTimePicker = true;
  343. this.timeType = type;
  344. this.timeValue = type == 'begin' ? [...this.beginTime] : [...this.endTime];
  345. },
  346. //处理日历
  347. procCalendar(item) {
  348. let color = '#ddd';
  349. let isChoice = false;
  350. if(this.beginDate && this.endDate){
  351. if(DateTools.isBigDay(item.dateObj,new Date(this.beginDate)) && DateTools.isBigDay(new Date(this.endDate),item.dateObj) && !item.isOtherMonth){
  352. color = '#000';
  353. }
  354. if(DateTools.isBigDay(item.dateObj,new Date(this.beginDate)) && DateTools.isBigDay(new Date(this.endDate),item.dateObj)){
  355. isChoice = true;
  356. }
  357. }else if(this.beginDate){
  358. if(DateTools.isBigDay(item.dateObj,new Date(this.beginDate)) && !item.isOtherMonth){
  359. color = '#000';
  360. }
  361. if(DateTools.isBigDay(item.dateObj,new Date(this.beginDate))){
  362. isChoice = true;
  363. }
  364. }else if(this.endDate){
  365. if(DateTools.isBigDay(new Date(this.endDate),item.dateObj) && !item.isOtherMonth){
  366. color = '#000';
  367. }
  368. if(DateTools.isBigDay(new Date(this.endDate),item.dateObj)){
  369. isChoice = true;
  370. }
  371. }else{
  372. if(!item.isOtherMonth){
  373. color = '#000';
  374. }
  375. isChoice = true;
  376. }
  377. item.isChoice = isChoice;
  378. //定义初始样式
  379. item.statusStyle = {
  380. opacity: 1,
  381. color: color,
  382. background: 'transparent'
  383. };
  384. item.bgStyle = {
  385. type: '',
  386. background: 'transparent'
  387. };
  388. item.dotStyle = {
  389. opacity: 1,
  390. background: 'transparent'
  391. };
  392. item.tips = "";
  393. //标记今天的日期
  394. if (DateTools.isSameDay(new Date(), item.dateObj)) {
  395. item.statusStyle.color = this.color;
  396. if (item.isOtherMonth) item.statusStyle.opacity = 0.3;
  397. }
  398. //标记选中项
  399. this.checkeds.forEach(date => {
  400. if (DateTools.isSameDay(date, item.dateObj)) {
  401. item.statusStyle.background = this.color;
  402. item.statusStyle.color = '#fff';
  403. item.statusStyle.opacity = 1;
  404. if (this.isMultiSelect && this.showTips) item.tips = this.beginText;
  405. }
  406. });
  407. //节假日或今日的日期标点
  408. if (item.statusStyle.background != this.color) {
  409. let holiday = this.showHoliday ? DateTools.getHoliday(item.dateObj) : false;
  410. if (holiday || DateTools.isSameDay(new Date(), item.dateObj)) {
  411. item.title = holiday || item.title;
  412. item.dotStyle.background = this.color;
  413. if (item.isOtherMonth) item.dotStyle.opacity = 0.2;
  414. }
  415. } else {
  416. item.title = item.dateObj.getDate();
  417. }
  418. //有两个日期
  419. if (this.checkeds.length == 2) {
  420. if (DateTools.isSameDay(this.checkeds[0], item.dateObj)) { //开始日期
  421. item.bgStyle.type = 'bgbegin';
  422. }
  423. if (DateTools.isSameDay(this.checkeds[1], item.dateObj)) { //结束日期
  424. if (this.isMultiSelect && this.showTips) item.tips = item.bgStyle.type ? this.beginText + ' / ' + this.endText : this.endText;
  425. if (!item.bgStyle.type) { //开始日期不等于结束日期
  426. item.bgStyle.type = 'bgend';
  427. } else {
  428. item.bgStyle.type = '';
  429. }
  430. }
  431. if (!item.bgStyle.type && (+item.dateObj > +this.checkeds[0] && +item.dateObj < +this.checkeds[1])) { //中间的日期
  432. item.bgStyle.type = 'bg';
  433. item.statusStyle.color = this.color;
  434. }
  435. if (item.bgStyle.type) {
  436. item.bgStyle.background = this.color;
  437. item.dotStyle.opacity = 1;
  438. item.statusStyle.opacity = 1;
  439. }
  440. }
  441. },
  442. //刷新日历
  443. refreshCalendars(refresh = false) {
  444. let date = new Date(this.date);
  445. let before = DateTools.getDateToMonth(date, date.getMonth() - 1);
  446. let after = DateTools.getDateToMonth(date, date.getMonth() + 1);
  447. if (this.calendarIndex == 0) {
  448. if(refresh) this.calendars.splice(0, 1, DateTools.getCalendar(date, this.procCalendar));
  449. this.calendars.splice(1, 1, DateTools.getCalendar(after, this.procCalendar));
  450. this.calendars.splice(2, 1, DateTools.getCalendar(before, this.procCalendar));
  451. } else if (this.calendarIndex == 1) {
  452. this.calendars.splice(0, 1, DateTools.getCalendar(before, this.procCalendar));
  453. if(refresh) this.calendars.splice(1, 1, DateTools.getCalendar(date, this.procCalendar));
  454. this.calendars.splice(2, 1, DateTools.getCalendar(after, this.procCalendar));
  455. } else if (this.calendarIndex == 2) {
  456. this.calendars.splice(0, 1, DateTools.getCalendar(after, this.procCalendar));
  457. this.calendars.splice(1, 1, DateTools.getCalendar(before, this.procCalendar));
  458. if(refresh) this.calendars.splice(2, 1, DateTools.getCalendar(date, this.procCalendar));
  459. }
  460. this.title = DateTools.format(this.date, 'yyyy年mm月');
  461. },
  462. //滑块切换
  463. onSwiperChange(e) {
  464. this.calendarIndex = e.detail.current;
  465. let calendar = this.calendars[this.calendarIndex];
  466. this.date = new Date(calendar[22].dateObj); //取中间一天,保证是当前的月份
  467. this.refreshCalendars();
  468. },
  469. //选中日期
  470. onSelectDate(date) {
  471. if(!date.isChoice) {
  472. uni.showToast({
  473. title: '禁止时间',
  474. icon: 'none'
  475. });
  476. return false
  477. }
  478. if (~this.type.indexOf('range') && this.checkeds.length == 2) this.checkeds = [];
  479. else if (!(~this.type.indexOf('range')) && this.checkeds.length) this.checkeds = [];
  480. this.checkeds.push(new Date(date.dateObj));
  481. this.checkeds.sort((a, b) => a - b); //从小到大排序
  482. this.calendars.forEach(calendar => {
  483. calendar.forEach(this.procCalendar); //重新处理
  484. });
  485. this.onConfirm(date)
  486. },
  487. //时间选择取消
  488. onCancelTime() {
  489. this.showTimePicker = false;
  490. this.type == 'time' && this.onCancel();
  491. },
  492. //时间选择确定
  493. onConfirmTime() {
  494. if (this.timeType == 'begin') this.beginTime = this.timeValue;
  495. else this.endTime = this.timeValue;
  496. this.showTimePicker = false;
  497. this.type == 'time' && this.onConfirm();
  498. },
  499. //取消
  500. onCancel() {
  501. this.$emit('cancel', false);
  502. },
  503. //确定
  504. onConfirm(date) {
  505. const value = DateTools.format(date.dateObj, 'yyyy/mm/dd')
  506. this.$emit('confirm', value);
  507. this.$emit('input', value);
  508. this.$emit('change', value);
  509. }
  510. },
  511. computed: {
  512. BeginTitle() {
  513. let value = '未选择';
  514. if (this.checkeds.length) value = DateTools.format(this.checkeds[0], 'yy/mm/dd');
  515. return value;
  516. },
  517. EndTitle() {
  518. let value = '未选择';
  519. if (this.checkeds.length == 2) value = DateTools.format(this.checkeds[1], 'yy/mm/dd');
  520. return value;
  521. },
  522. PickerTimeTitle() {
  523. return DateTools.formatTimeArray(this.timeValue, this.showSeconds);
  524. },
  525. BeginTimeTitle() {
  526. return this.BeginTitle != '未选择' ? DateTools.formatTimeArray(this.beginTime, this.showSeconds) : '';
  527. },
  528. EndTimeTitle() {
  529. return this.EndTitle != '未选择' ? DateTools.formatTimeArray(this.endTime, this.showSeconds) : '';
  530. }
  531. },
  532. watch: {
  533. show(newValue, oldValue) {
  534. newValue && this.setValue(this.value);
  535. this.isShow = newValue;
  536. },
  537. value(newValue, oldValue) {
  538. setTimeout(()=>{
  539. this.setValue(newValue);
  540. }, 0);
  541. }
  542. },
  543. mounted(){
  544. setTimeout(()=>{
  545. this.setValue(this.value);
  546. }, 0);
  547. }
  548. }
  549. </script>
  550. <style lang="scss" scoped>
  551. @import './index.scss';
  552. </style>