tui-datetime.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <template>
  2. <view class="tui-datetime-picker">
  3. <view class="tui-mask" :class="{ 'tui-mask-show': isShow }" @touchmove.stop.prevent="stop" catchtouchmove="stop"
  4. @tap="maskClick"></view>
  5. <view class="tui-header" :class="{ 'tui-show': isShow }">
  6. <view class="tui-picker-header" :class="{ 'tui-date-radius': radius }"
  7. :style="{ backgroundColor: headerBackground }" @touchmove.stop.prevent="stop" catchtouchmove="stop">
  8. <view class="tui-btn-picker" :style="{ color: cancelColor }" hover-class="tui-opacity"
  9. :hover-stay-time="150" @tap="hide">取消</view>
  10. <view class="tui-pickerdate__title" :style="{fontSize:titleSize+'rpx',color:titleColor}">{{title}}
  11. </view>
  12. <view class="tui-btn-picker" :style="{ color: color }" hover-class="tui-opacity" :hover-stay-time="150"
  13. @tap="btnFix">确定</view>
  14. </view>
  15. <view class="tui-date-header" :style="{ backgroundColor: unitBackground }" v-if="unitTop">
  16. <view class="tui-date-unit" v-if="type < 4 || type == 7 || type==8">年</view>
  17. <view class="tui-date-unit" v-if="type < 4 || type == 7 || type==8">月</view>
  18. <view class="tui-date-unit" v-if="type == 1 || type == 2 || type == 7 || type==8">日</view>
  19. <view class="tui-date-unit" v-if="type == 1 || type == 4 || type == 5 || type == 7 || type==8">时</view>
  20. <view class="tui-date-unit" v-if="(type == 1 || type > 3) && type!=8">分</view>
  21. <view class="tui-date-unit" v-if="type > 4 && type !=8">秒</view>
  22. </view>
  23. <view @touchstart.stop="pickerstart" class="tui-date__picker-body"
  24. :style="{ backgroundColor: bodyBackground,height:height+'rpx' }">
  25. <picker-view :value="value" @change="change" class="tui-picker-view">
  26. <picker-view-column v-if="!reset && (type < 4 || type == 7 || type==8)">
  27. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  28. v-for="(item, index) in years" :key="index">
  29. {{ item }}
  30. <text class="tui-date__unit-text" v-if="!unitTop">年</text>
  31. </view>
  32. </picker-view-column>
  33. <picker-view-column v-if="!reset && (type < 4 || type == 7 || type==8)">
  34. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  35. v-for="(item, index) in months" :key="index">
  36. {{ formatNum(item) }}
  37. <text class="tui-date__unit-text" v-if="!unitTop">月</text>
  38. </view>
  39. </picker-view-column>
  40. <picker-view-column v-if="!reset && (type == 1 || type == 2 || type == 7 || type==8)">
  41. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  42. v-for="(item, index) in days" :key="index">
  43. {{ formatNum(item) }}
  44. <text class="tui-date__unit-text" v-if="!unitTop">日</text>
  45. </view>
  46. </picker-view-column>
  47. <picker-view-column v-if="!reset && (type == 1 || type == 4 || type == 5 || type == 7 || type==8)">
  48. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  49. v-for="(item, index) in hours" :key="index">
  50. {{ formatNum(item) }}
  51. <text class="tui-date__unit-text" v-if="!unitTop">时</text>
  52. </view>
  53. </picker-view-column>
  54. <picker-view-column v-if="!reset && (type == 1 || type > 3) && type!=8">
  55. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  56. v-for="(item, index) in minutes" :key="index">
  57. {{ formatNum(item) }}
  58. <text class="tui-date__unit-text" v-if="!unitTop">分</text>
  59. </view>
  60. </picker-view-column>
  61. <picker-view-column v-if="!reset && type > 4 && type!=8">
  62. <view class="tui-date__column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }"
  63. v-for="(item, index) in seconds" :key="index">
  64. {{ formatNum(item) }}
  65. <text class="tui-date__unit-text" v-if="!unitTop">秒</text>
  66. </view>
  67. </picker-view-column>
  68. </picker-view>
  69. </view>
  70. </view>
  71. </view>
  72. </template>
  73. <script>
  74. export default {
  75. name: 'tuiDatetime',
  76. emits: ['cancel', 'confirm'],
  77. props: {
  78. //1-日期+时间(年月日+时分) 2-日期(年月日) 3-日期(年月) 4-时间(时分) 5-时分秒 6-分秒 7-年月日 时分秒 8-年月日+小时
  79. type: {
  80. type: Number,
  81. default: 1
  82. },
  83. //年份区间
  84. startYear: {
  85. type: Number,
  86. default: 1980
  87. },
  88. //年份区间
  89. endYear: {
  90. type: Number,
  91. default: 2050
  92. },
  93. //显示标题
  94. title: {
  95. type: String,
  96. default: ''
  97. },
  98. //标题字体大小
  99. titleSize: {
  100. type: [Number, String],
  101. default: 34
  102. },
  103. //标题字体颜色
  104. titleColor: {
  105. type: String,
  106. default: '#333'
  107. },
  108. //"取消"字体颜色
  109. cancelColor: {
  110. type: String,
  111. default: '#888'
  112. },
  113. //"确定"字体颜色
  114. color: {
  115. type: String,
  116. default: '#5677fc'
  117. },
  118. //设置默认显示日期 2019-08-01 || 2019-08-01 17:01 || 2019/08/01
  119. setDateTime: {
  120. type: String,
  121. default: ''
  122. },
  123. //单位置顶
  124. unitTop: {
  125. type: Boolean,
  126. default: false
  127. },
  128. //圆角设置
  129. radius: {
  130. type: Boolean,
  131. default: false
  132. },
  133. //头部背景色
  134. headerBackground: {
  135. type: String,
  136. default: '#fff'
  137. },
  138. //根据实际调整,不建议使用深颜色
  139. bodyBackground: {
  140. type: String,
  141. default: '#fff'
  142. },
  143. //单位置顶时,单位条背景色
  144. unitBackground: {
  145. type: String,
  146. default: '#fff'
  147. },
  148. height: {
  149. type: [Number, String],
  150. default: 520
  151. },
  152. //点击遮罩 是否可关闭
  153. maskClosable: {
  154. type: Boolean,
  155. default: true
  156. }
  157. },
  158. data() {
  159. return {
  160. isShow: false,
  161. years: [],
  162. months: [],
  163. days: [],
  164. hours: [],
  165. minutes: [],
  166. seconds: [],
  167. year: 0,
  168. month: 0,
  169. day: 0,
  170. hour: 0,
  171. minute: 0,
  172. second: 0,
  173. startDate: '',
  174. endDate: '',
  175. value: [0, 0, 0, 0, 0, 0],
  176. reset: false,
  177. isEnd: true
  178. };
  179. },
  180. mounted() {
  181. setTimeout(() => {
  182. this.initData();
  183. }, 20)
  184. },
  185. computed: {
  186. yearOrMonth() {
  187. return `${this.year}-${this.month}`;
  188. },
  189. propsChange() {
  190. return `${this.setDateTime}-${this.type}-${this.startYear}-${this.endYear}`;
  191. }
  192. },
  193. watch: {
  194. yearOrMonth() {
  195. this.setDays();
  196. },
  197. propsChange() {
  198. this.reset = true;
  199. setTimeout(() => {
  200. this.initData();
  201. }, 20);
  202. }
  203. },
  204. methods: {
  205. stop() {},
  206. formatNum: function(num) {
  207. return num < 10 ? '0' + num : num + '';
  208. },
  209. generateArray: function(start, end) {
  210. return Array.from(new Array(end + 1).keys()).slice(start);
  211. },
  212. getIndex: function(arr, val) {
  213. let index = arr.indexOf(val);
  214. return ~index ? index : 0;
  215. },
  216. getCharCount(str) {
  217. let regex = new RegExp('/', 'g');
  218. let result = str.match(regex);
  219. return !result ? 0 : result.length;
  220. },
  221. //日期时间处理
  222. initSelectValue() {
  223. let fdate = this.setDateTime.replace(/\-/g, '/');
  224. if (this.type == 3 && this.getCharCount(fdate) === 1) {
  225. fdate += '/01'
  226. }
  227. fdate = fdate && fdate.indexOf('/') == -1 ? `2020/01/01 ${fdate}` : fdate;
  228. let time = null;
  229. if (fdate) time = new Date(fdate);
  230. else time = new Date();
  231. this.year = time.getFullYear();
  232. this.month = time.getMonth() + 1;
  233. this.day = time.getDate();
  234. this.hour = time.getHours();
  235. this.minute = time.getMinutes();
  236. this.second = time.getSeconds();
  237. },
  238. initData() {
  239. this.initSelectValue();
  240. this.reset = false;
  241. switch (this.type) {
  242. case 1:
  243. this.value = [0, 0, 0, 0, 0];
  244. this.setYears();
  245. this.setMonths();
  246. this.setDays();
  247. this.setHours();
  248. this.setMinutes();
  249. break;
  250. case 2:
  251. this.value = [0, 0, 0];
  252. this.setYears();
  253. this.setMonths();
  254. this.setDays();
  255. break;
  256. case 3:
  257. this.value = [0, 0];
  258. this.setYears();
  259. this.setMonths();
  260. break;
  261. case 4:
  262. this.value = [0, 0];
  263. this.setHours();
  264. this.setMinutes();
  265. break;
  266. case 5:
  267. this.value = [0, 0, 0];
  268. this.setHours();
  269. this.setMinutes();
  270. this.setSeconds();
  271. break;
  272. case 6:
  273. this.value = [0, 0];
  274. this.setMinutes();
  275. this.setSeconds();
  276. break;
  277. case 7:
  278. this.value = [0, 0, 0, 0, 0, 0];
  279. this.setYears();
  280. this.setMonths();
  281. this.setDays();
  282. this.setHours();
  283. this.setMinutes();
  284. this.setSeconds();
  285. break;
  286. case 8:
  287. this.value = [0, 0, 0, 0];
  288. this.setYears();
  289. this.setMonths();
  290. this.setDays();
  291. this.setHours();
  292. break;
  293. default:
  294. break;
  295. }
  296. },
  297. setYears() {
  298. this.years = this.generateArray(this.startYear, this.endYear);
  299. setTimeout(() => {
  300. this.$set(this.value, 0, this.getIndex(this.years, this.year));
  301. }, 8);
  302. },
  303. setMonths() {
  304. this.months = this.generateArray(1, 12);
  305. setTimeout(() => {
  306. this.$set(this.value, 1, this.getIndex(this.months, this.month));
  307. }, 8);
  308. },
  309. setDays() {
  310. if (this.type == 3 || this.type == 4) return;
  311. let totalDays = new Date(this.year, this.month, 0).getDate();
  312. totalDays = !totalDays || totalDays < 1 ? 1 : totalDays
  313. this.days = this.generateArray(1, totalDays);
  314. setTimeout(() => {
  315. this.$set(this.value, 2, this.getIndex(this.days, this.day));
  316. }, 8);
  317. },
  318. setHours() {
  319. this.hours = this.generateArray(0, 23);
  320. setTimeout(() => {
  321. let index = 0
  322. if (this.type == 8) {
  323. index = this.value.length - 1
  324. } else {
  325. index = this.type == 5 || this.type == 7 ? this.value.length - 3 : this.value.length - 2;
  326. }
  327. this.$set(this.value, index, this.getIndex(this.hours, this.hour));
  328. }, 8);
  329. },
  330. setMinutes() {
  331. this.minutes = this.generateArray(0, 59);
  332. setTimeout(() => {
  333. let index = this.type > 4 ? this.value.length - 2 : this.value.length - 1;
  334. this.$set(this.value, index, this.getIndex(this.minutes, this.minute));
  335. }, 8);
  336. },
  337. setSeconds() {
  338. this.seconds = this.generateArray(0, 59);
  339. setTimeout(() => {
  340. this.$set(this.value, this.value.length - 1, this.getIndex(this.seconds, this.second));
  341. }, 8);
  342. },
  343. show() {
  344. setTimeout(() => {
  345. this.isShow = true;
  346. }, 50);
  347. },
  348. hide() {
  349. this.isShow = false;
  350. this.$emit('cancel', {});
  351. },
  352. maskClick() {
  353. if (!this.maskClosable) return;
  354. this.hide()
  355. },
  356. change(e) {
  357. if(!this.isShow) return;
  358. this.value = e.detail.value;
  359. switch (this.type) {
  360. case 1:
  361. this.year = this.years[this.value[0]];
  362. this.month = this.months[this.value[1]];
  363. this.day = this.days[this.value[2]];
  364. this.hour = this.hours[this.value[3]];
  365. this.minute = this.minutes[this.value[4]];
  366. break;
  367. case 2:
  368. this.year = this.years[this.value[0]];
  369. this.month = this.months[this.value[1]];
  370. this.day = this.days[this.value[2]];
  371. break;
  372. case 3:
  373. this.year = this.years[this.value[0]];
  374. this.month = this.months[this.value[1]];
  375. break;
  376. case 4:
  377. this.hour = this.hours[this.value[0]];
  378. this.minute = this.minutes[this.value[1]];
  379. break;
  380. case 5:
  381. this.hour = this.hours[this.value[0]];
  382. this.minute = this.minutes[this.value[1]];
  383. this.second = this.seconds[this.value[2]];
  384. break;
  385. case 6:
  386. this.minute = this.minutes[this.value[0]];
  387. this.second = this.seconds[this.value[1]];
  388. break;
  389. case 7:
  390. this.year = this.years[this.value[0]];
  391. this.month = this.months[this.value[1]];
  392. this.day = this.days[this.value[2]];
  393. this.hour = this.hours[this.value[3]];
  394. this.minute = this.minutes[this.value[4]];
  395. this.second = this.seconds[this.value[5]];
  396. break;
  397. case 8:
  398. this.year = this.years[this.value[0]];
  399. this.month = this.months[this.value[1]];
  400. this.day = this.days[this.value[2]];
  401. this.hour = this.hours[this.value[3]];
  402. break;
  403. default:
  404. break;
  405. }
  406. this.isEnd = true
  407. },
  408. selectResult() {
  409. let result = {};
  410. let year = this.year;
  411. let month = this.formatNum(this.month || 0);
  412. let day = this.formatNum(this.day || 0);
  413. let hour = this.formatNum(this.hour || 0);
  414. let minute = this.formatNum(this.minute || 0);
  415. let second = this.formatNum(this.second || 0);
  416. switch (this.type) {
  417. case 1:
  418. result = {
  419. year: year,
  420. month: month,
  421. day: day,
  422. hour: hour,
  423. minute: minute,
  424. result: `${year}-${month}-${day} ${hour}:${minute}`
  425. };
  426. break;
  427. case 2:
  428. result = {
  429. year: year,
  430. month: month,
  431. day: day,
  432. result: `${year}-${month}-${day}`
  433. };
  434. break;
  435. case 3:
  436. result = {
  437. year: year,
  438. month: month,
  439. result: `${year}-${month}`
  440. };
  441. break;
  442. case 4:
  443. result = {
  444. hour: hour,
  445. minute: minute,
  446. result: `${hour}:${minute}`
  447. };
  448. break;
  449. case 5:
  450. result = {
  451. hour: hour,
  452. minute: minute,
  453. second: second,
  454. result: `${hour}:${minute}:${second}`
  455. };
  456. break;
  457. case 6:
  458. result = {
  459. minute: minute,
  460. second: second,
  461. result: `${minute}:${second}`
  462. };
  463. break;
  464. case 7:
  465. result = {
  466. year: year,
  467. month: month,
  468. day: day,
  469. hour: hour,
  470. minute: minute,
  471. second: second,
  472. result: `${year}-${month}-${day} ${hour}:${minute}:${second}`
  473. };
  474. break;
  475. case 8:
  476. result = {
  477. year: year,
  478. month: month,
  479. day: day,
  480. hour: hour,
  481. result: `${year}-${month}-${day} ${hour}:00`
  482. };
  483. break;
  484. default:
  485. break;
  486. }
  487. this.$emit('confirm', result);
  488. },
  489. waitFix() {
  490. if (this.isEnd) {
  491. this.selectResult()
  492. } else {
  493. setTimeout(() => {
  494. this.waitFix()
  495. }, 50)
  496. }
  497. },
  498. btnFix() {
  499. setTimeout(() => {
  500. this.waitFix()
  501. this.hide();
  502. }, 80);
  503. },
  504. pickerstart() {
  505. this.isEnd = false
  506. }
  507. }
  508. };
  509. </script>
  510. <style scoped>
  511. .tui-datetime-picker {
  512. position: relative;
  513. z-index: 996;
  514. }
  515. .tui-picker-view {
  516. height: 100%;
  517. box-sizing: border-box;
  518. }
  519. .tui-mask {
  520. position: fixed;
  521. z-index: 997;
  522. top: 0;
  523. right: 0;
  524. bottom: 0;
  525. left: 0;
  526. background-color: rgba(0, 0, 0, 0.6);
  527. visibility: hidden;
  528. opacity: 0;
  529. transition: all 0.3s ease-in-out;
  530. }
  531. .tui-mask-show {
  532. visibility: visible !important;
  533. opacity: 1 !important;
  534. }
  535. .tui-header {
  536. z-index: 998;
  537. position: fixed;
  538. bottom: 0;
  539. left: 0;
  540. width: 100%;
  541. transition: all 0.3s ease-in-out;
  542. transform: translateY(100%);
  543. }
  544. .tui-date-header {
  545. width: 100%;
  546. height: 52rpx;
  547. display: flex;
  548. align-items: center;
  549. justify-content: space-between;
  550. font-size: 26rpx;
  551. line-height: 26rpx;
  552. /* #ifdef MP */
  553. box-shadow: 0 15rpx 10rpx -15rpx #efefef;
  554. /* #endif */
  555. /* #ifndef MP */
  556. box-shadow: 0 15rpx 10rpx -15rpx #888;
  557. /* #endif */
  558. position: relative;
  559. z-index: 2;
  560. }
  561. .tui-date-unit {
  562. flex: 1;
  563. text-align: center;
  564. }
  565. .tui-show {
  566. transform: translateY(0);
  567. }
  568. .tui-picker-header {
  569. width: 100%;
  570. height: 90rpx;
  571. padding: 0 40rpx;
  572. display: flex;
  573. justify-content: space-between;
  574. align-items: center;
  575. box-sizing: border-box;
  576. font-size: 32rpx;
  577. position: relative;
  578. }
  579. .tui-date-radius {
  580. border-top-left-radius: 20rpx;
  581. border-top-right-radius: 20rpx;
  582. overflow: hidden;
  583. }
  584. .tui-picker-header::after {
  585. content: '';
  586. position: absolute;
  587. border-bottom: 1rpx solid #eaeef1;
  588. -webkit-transform: scaleY(0.5);
  589. transform: scaleY(0.5);
  590. bottom: 0;
  591. right: 0;
  592. left: 0;
  593. }
  594. .tui-date__picker-body {
  595. width: 100%;
  596. /* height: 520rpx; */
  597. overflow: hidden;
  598. }
  599. .tui-date__column-item {
  600. display: flex;
  601. align-items: center;
  602. justify-content: center;
  603. font-size: 36rpx;
  604. color: #333;
  605. }
  606. .tui-font-size_32 {
  607. font-size: 32rpx !important;
  608. }
  609. .tui-date__unit-text {
  610. font-size: 24rpx !important;
  611. padding-left: 8rpx;
  612. }
  613. .tui-btn-picker {
  614. padding: 16rpx;
  615. box-sizing: border-box;
  616. text-align: center;
  617. text-decoration: none;
  618. flex-shrink: 0;
  619. /* #ifdef H5 */
  620. cursor: pointer;
  621. /* #endif */
  622. }
  623. .tui-opacity {
  624. opacity: 0.5;
  625. }
  626. .tui-pickerdate__title {
  627. white-space: nowrap;
  628. overflow: hidden;
  629. text-overflow: ellipsis;
  630. flex: 1;
  631. padding: 0 30rpx;
  632. box-sizing: border-box;
  633. text-align: center;
  634. }
  635. </style>