| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844 |
- <script>
- import dayjs from "./day";
- import {
- monthsChinese,
- weeksChinese,
- closable,
- left,
- left_double,
- } from "./utils";
- import KButton from "../k-bottom-button/KBottomButton.vue";
- import KBottomPopup from "../k-bottom-popup/KBottomPopup.vue";
-
- export default {
- components: {KBottomPopup, KButton},
- options: {
- virtualHost: true,
- },
- // #ifndef VUE3
- model: {
- prop: "value",
- event: "input",
- },
- // #endif
- props: {
- // #ifndef VUE3
- value: {
- type: Boolean,
- default: false,
- },
- // #endif
- confirmText: {
- type: String,
- default: "确定",
- },
- title: {
- type: String,
- default: "请选择",
- },
- // #ifdef VUE3
- modelValue: {
- type: Boolean,
- default: false,
- },
- // #endif
- defaultValue: {
- type: [String, Number, Array],
- },
- type: {
- type: String, //"day" | "month" | "year"
- default: "day",
- },
- /** 限制开始时间 **/
- limitStartDate: {
- type: [String, Number],
- },
- /** 限制结束时间 **/
- limitEndDate: {
- type: [String, Number],
- },
- /** 格式化数据 **/
- formatter: {
- type: String,
- },
- /** 是否为范围选择器 **/
- range: {
- type: Boolean,
- default: () => false,
- },
- /** 是否为多选 **/
- multiple: {
- type: Boolean,
- default: () => false,
- },
- isDisabled: {
- type: Function,
- }
- },
- computed: {
- briefDate() {
- if (this.type === "day") {
- return this.currentDay.format("YYYY年MM月");
- }
- if (this.type === "month") {
- return this.currentDay.format("YYYY年");
- }
- if (this.type === "year") {
- return `${this.startYear.format("YYYY")} - ${this.startYear.add(9, "year").format("YYYY")}`;
- }
- },
- selectedDate() {
- const formatMap = {
- day: "YYYY-MM-DD",
- month: "YYYY-MM",
- year: "YYYY年",
- };
- const format = formatMap[this.type];
- if (this.range) {
- return `${this.rangeStart ? this.rangeStart.format(format) : "-"} 至 ${this.rangeEnd ? this.rangeEnd.format(format) : "-"}`;
- } else {
- if (this.checked.length > 0) {
- return this.checked.map((item) => item.format(format)).join("、");
- } else {
- return "-";
- }
- }
- },
- backText() {
- if (this.type === "day") {
- return this.currentDay.format("M");
- }
- return "";
- },
- blocks() {
- const dates =
- this.type === "day"
- ? this.days
- : this.type === "month"
- ? this.months
- : this.years;
- const result = [];
- const chineseMap = {
- day: (date) => {
- return date.format("D");
- },
- month: (date) => {
- return monthsChinese[date.month()];
- },
- year: (date) => {
- return date.format("YYYY年");
- },
- };
- for (const date of dates) {
- if (!date) {
- result.push(date);
- } else {
- const obj = {
- date,
- chinese: chineseMap[this.type](date),
- isLimit: this.isDisabled ? this.isDisabled(date.startOf(this.type).valueOf()) : ((this.limitStartDate &&
- date.isBefore(dayjs(this.limitStartDate).startOf(this.type))) ||
- (this.limitEndDate && date.isAfter(dayjs(this.limitEndDate).endOf(this.type)))),
- isCurrent: date
- .startOf(this.type)
- .isSame(this.sameDay.startOf(this.type)),
- isSelect:
- this.checked.findIndex(
- (item) => item.valueOf() === date.valueOf(),
- ) !== -1,
- isStart:
- this.rangeStart &&
- date
- .startOf(this.type)
- .isSame(this.rangeStart.startOf(this.type)),
- isEnd:
- this.rangeEnd &&
- date.startOf(this.type).isSame(this.rangeEnd.startOf(this.type)),
- isInRange:
- this.rangeStart &&
- this.rangeEnd &&
- date
- .startOf(this.type)
- .isAfter(this.rangeStart.startOf(this.type)) &&
- date
- .startOf(this.type)
- .isBefore(this.rangeEnd.startOf(this.type)),
- };
- // 若果既是范围起点又是范围终点,那么设置为选中
- if (this.range) {
- obj.isSelect = obj.isStart && obj.isEnd;
- }
- result.push(obj);
- }
- }
- return result;
- },
- days() {
- const result = [];
- let start = this.currentDay.startOf("month");
- const end = this.currentDay.endOf("month");
- //计算当前月的第一天是星期几,然后在result头部补上空字符串
- const firstDayWeek = start.day();
- for (let i = 0; i < firstDayWeek; i++) {
- result.push("");
- }
- while (start.isBefore(end)) {
- result.push(start);
- start = start.add(1, "day");
- }
- return result;
- },
- months() {
- const result = [];
- for (let i = 0; i < 12; i++) {
- const date = this.currentDay.startOf("year").add(i, "month");
- result.push(date);
- }
- return result;
- },
- startYear() {
- return this.currentDay.subtract(this.currentDay.year() % 10, "year");
- },
- years() {
- // 根据currentDay生成10个年份,如果是当前年则加上is-current
- const result = [];
- // 从startYear开始生成一个10年的数组
- for (let i = 0; i < 10; i++) {
- const date = this.startYear.add(i, "year");
- result.push(date);
- }
- return result;
- },
- },
- data() {
- return {
- sameDay: dayjs(), // 当日
- rangeStart: null, // 范围选择器开始日期
- rangeEnd: null, // 范围选择器结束日期
- checked: [], // 选中的日期
- currentDay: dayjs(), // 当前选择器显示的日期
- weeksChinese,
- left,
- closable,
- left_double,
- show: false,
- };
- },
- methods: {
- onClick() {
- if (this.range) {
- const _rangeStart = this.rangeStart
- ? this.formatter
- ? this.rangeStart.format(this.formatter)
- : this.rangeStart.valueOf()
- : null;
- const _rangeEnd = this.rangeEnd
- ? this.formatter
- ? this.rangeEnd.format(this.formatter)
- : this.rangeEnd.endOf(this.type).valueOf()
- : null;
- this.$emit("change", [_rangeStart, _rangeEnd]);
- } else {
- const _checked =
- this.selectedDate !== "-"
- ? this.multiple
- ? this.checked.map((item) =>
- this.formatter ? item.format(this.formatter) : item.valueOf(),
- )
- : this.formatter
- ? this.checked[0].format(this.formatter)
- : this.checked[0].valueOf()
- : null;
- this.$emit("change", _checked);
- }
- },
- handleClickItem(block) {
- this.show = true
- /**当day为空字符串或者在限制开始时间之前,限制开始时间之后 不允许点击 **/
- if (
- block === "" ||
- (this.isDisabled ? this.isDisabled(block.date.startOf(this.type).valueOf()) : (
- (this.limitEndDate && block.date.isAfter(dayjs(this.limitEndDate).endOf(this.type))) ||
- (this.limitStartDate && block.date.isBefore(dayjs(this.limitStartDate).startOf(this.type)))
- ))
- )
- return;
- if (this.range) {
- //如果有结束日期和开始日期,并且点击的不是开始日期和结束日期,则将其设置为开始日期并清除结束日期
- if (
- this.rangeStart &&
- this.rangeEnd &&
- !block.isStart &&
- !block.isEnd
- ) {
- this.rangeStart = block.date;
- this.rangeEnd = null;
- return;
- }
- // 如果点击的是选择的日期,那么就清除这些选项
- if (block.isSelect) {
- this.rangeStart = null;
- this.rangeEnd = null;
- return;
- }
-
- // 如果重复点击开始日期或者结束日期则取消选择
- if (block.isStart) {
- // 如果没有结束日期 那么将结束日期设置为开始日期
- if (!this.rangeEnd) {
- this.rangeEnd = block.date;
- return;
- }
- this.rangeStart = null;
- return;
- }
- if (block.isEnd) {
- this.rangeEnd = null;
- return;
- }
- const setStartAndEndDay = (start) => {
- if (block.date.isBefore(start)) {
- this.rangeEnd = start;
- this.rangeStart = block.date;
- } else {
- this.rangeEnd = block.date;
- }
- };
- if (this.rangeStart) {
- setStartAndEndDay(this.rangeStart);
- } else {
- // 如果有结束日期,需要判断是否在结束日期之前
- if (this.rangeEnd) {
- setStartAndEndDay(this.rangeEnd);
- } else {
- this.rangeStart = block.date;
- }
- }
- } else {
- // 如果重复点击则取消选择
- if (block.isSelect) {
- this.checked = this.checked.filter(
- (item) => item.valueOf() !== block.date.valueOf(),
- );
- return;
- }
- this.multiple
- ? this.checked.push(block.date)
- : (this.checked = [block.date]);
- }
- },
- toggle(type, direction) {
- if (this.type === "day") {
- this.currentDay = this.currentDay.add(
- direction === "left" ? -1 : 1,
- type === "single" ? "month" : "year",
- );
- }
- if (this.type === "month") {
- this.currentDay = this.currentDay.add(
- direction === "left" ? -1 : 1,
- "year",
- );
- }
- if (this.type === "year") {
- this.currentDay = this.currentDay.add(
- direction === "left" ? -10 : 10,
- "year",
- );
- }
- },
- close() {
- // #ifndef VUE3
- this.$emit("input", false);
- // #endif
- // #ifdef VUE3
- this.$emit("update:modelValue", false);
- // #endif
- },
- init() {
- // 检查类型是否为 day | month | year
- if (!["day", "month", "year"].includes(this.type)) {
- console.error("类型必须为 day | month | year");
- return;
- }
- // 检查 isRange和 multiple 是否同时为 true
- if (this.range && this.multiple) {
- console.error("目前暂不支持时间段的多选");
- return;
- }
-
- // 清空原数据
- this.checked = [];
- this.rangeStart = null;
- this.rangeEnd = null;
- // 初始化数据
- if (!this.defaultValue) return;
- if (this.range) {
- if (
- !Array.isArray(this.defaultValue) ||
- this.defaultValue.length !== 2
- ) {
- console.error("区间选择器的默认值必须为长度为2的数组");
- return;
- }
- this.currentDay = this.defaultValue[0]
- ? dayjs(this.defaultValue[0])
- : dayjs();
- this.defaultValue[0] && (this.rangeStart = dayjs(this.defaultValue[0]));
- this.defaultValue[1] && (this.rangeEnd = dayjs(this.defaultValue[1]));
- } else {
- if (this.multiple && Array.isArray(this.defaultValue)) {
- this.checked = this.defaultValue.map((item) => dayjs(item));
- } else {
- this.checked = [dayjs(this.defaultValue)];
- }
- this.currentDay = this.checked[0] ? dayjs(this.checked[0]) : dayjs();
- }
- },
- },
- mounted() {
- this.init();
- },
- watch: {
- // #ifdef VUE3
- modelValue: {
- handler(value) {
- this.show = !!value;
- },
- },
- // #endif
- // #ifndef VUE3
- value: {
- handler(value) {
- this.show = !!value;
- },
- },
- // #endif
- show: {
- handler(value) {
- // #ifdef VUE3
- this.$emit("update:modelValue", value);
- // #endif
- // #ifndef VUE3
- this.$emit("input", value);
- // #endif
- },
- },
- range: {
- handler() {
- this.init();
- },
- },
- multiple: {
- handler() {
- this.init();
- },
- },
- type: {
- handler() {
- this.init();
- },
- },
- defaultValue: {
- handler() {
- this.init();
- },
- deep: true,
- },
- },
- };
- </script>
-
- <template>
- <KBottomPopup v-model="show">
- <view class="k-date-picker">
- <view class="inner-top">
- <view>
- <slot name="left">
- <text>{{ title }}</text>
- </slot>
- </view>
- <view @click="close">
- <slot name="right">
- <image :src="closable" class="image-closable"></image>
- </slot>
- </view>
- </view>
- <view class="inner-body">
- <view class="inner-body__arrow">
- <image
- v-if="type === 'day'"
- class="image-arrow"
- :src="left_double"
- @click="toggle('double', 'left')"
- />
- <image
- class="image-arrow"
- :src="left"
- @click="toggle('single', 'left')"
- />
- <view class="brief">{{ briefDate }}</view>
- <image
- class="image-arrow right-arrow"
- :src="left"
- @click="toggle('single', 'right')"
- />
- <image
- v-if="type === 'day'"
- class="image-arrow right-arrow"
- :src="left_double"
- @click="toggle('double', 'right')"
- />
- </view>
- <view class="inner-body__calendar">
- <view
- class="inner-body__calendar_week"
- :style="{ columnGap: range ? '0' : '12rpx' }"
- v-if="type === 'day'"
- >
- <view
- class="inner-body__calendar_week--item"
- v-for="(item, index) in weeksChinese"
- :key="index"
- >{{ item }}
- </view
- >
- </view>
- <view
- class="inner-body__calendar_block"
- :class="{
- 'is-day': type === 'day',
- 'is-month': type === 'month',
- 'is-year': type === 'year',
- }"
- :data-text="backText"
- :style="{ columnGap: range ? '0' : '12rpx' }"
- >
- <view
- class="inner-body__calendar_block--item"
- :class="{
- 'is-current': block.isCurrent,
- 'is-start': block.isStart,
- 'is-end': block.isEnd,
- 'is-in-range': block.isInRange,
- 'is-selected': block.isSelect,
- 'is-limit': block.isLimit,
- }"
- v-for="(block, index) in blocks"
- :key="index"
- @click="handleClickItem(block)"
- >
- {{ block.chinese || "" }}
- </view>
- </view>
- </view>
- <!-- 多选由于需要显示的数据可能会较多,这里不再就多选的数据进行展示,需要显示可以自己处理展示方式-->
- <view class="selected-wrap" v-if="!multiple">
- <view class="selected-date">
- {{ selectedDate }}
- </view>
- </view>
- </view>
- </view>
- <KButton
- :label="confirmText"
- :box-shadow="false"
- :safe-area="true"
- @onClick="onClick"
- ></KButton>
- </KBottomPopup>
- </template>
-
- <style lang="scss">
- .k-date-picker {
- /** 控制提示文字的位置 **/
- --bottom-offset: 10%;
- /** 控制提示文字的大小 **/
- --bottom-font-size: 18rpx;
- /** 选中时字体的颜色**/
- --seletct--text-color: white;
- /** 选中时背景颜色 **/
- --seletct--background-color: #005ceeff;
- /** 处于区间的字体颜色 **/
- --in-range--text-color: #005ceeff;
- /** 处于区间的背景颜色 **/
- --in-range--background-color: #f2f6fc;
- /** 限制的字体颜色 **/
- --limit--text-color: #a8abb2;
- /** 限制的背景颜色 **/
- --limit--background-color: #f5f7fa;
- /** current的背景颜色 **/
- --current--background-color: #e4edfe;
- /** current的字体颜色 **/
- --current--text-color: #7994b2;
-
- box-sizing: border-box;
- width: 100%;
- padding: 30rpx 30rpx 20rpx;
- background-color: white;
- border-radius: 10px 10px 0 0;
- position: relative;
- display: flex;
- flex-direction: column;
-
- .is-current {
- border-radius: 8rpx;
- background-color: var(--current--background-color);
- position: relative;
- --color: var(--current--text-color);
-
- &:after {
- color: var(--color);
- position: absolute;
- left: 50%;
- bottom: var(--bottom-offset);
- transform: translate(-50%);
- font-size: var(--bottom-font-size);
- line-height: var(--bottom-font-size);
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- }
- }
-
- .is-day {
- grid-template-columns: repeat(7, minmax(0, 1fr));
-
- .is-current:after {
- content: "本日";
- }
-
- .is-start:after {
- content: "开始";
- }
-
- .is-end:after {
- content: "结束";
- }
- }
-
- .is-month {
- grid-template-columns: repeat(4, minmax(0, 1fr));
- margin-top: 24rpx;
-
- .is-current:after {
- content: "本月";
- }
-
- .is-start:after {
- content: "开始";
- }
-
- .is-end:after {
- content: "结束";
- }
- }
-
- .is-year {
- grid-template-columns: repeat(4, minmax(0, 1fr));
- margin-top: 24rpx;
-
- .is-current:after {
- content: "本年";
- }
-
- .is-start:after {
- content: "开始";
- }
-
- .is-end:after {
- content: "结束";
- }
- }
-
- .inner-top {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding-bottom: 30rpx;
- }
-
- .inner-body {
- &__arrow {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- }
-
- &__calendar {
- &_week {
- display: grid;
- grid-template-columns: repeat(7, minmax(0, 1fr));
-
- &--item {
- /* #ifdef MP-WEIXIN */
- font-weight: bolder;
- /* #endif */
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- color: #303133;
- font-size: 30rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 88rpx;
- }
- }
-
- &_block {
- &:after {
- display: block;
- content: attr(data-text);
- color: #eef2f8ff;
- font-size: 280rpx;
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- z-index: 1;
- }
-
- position: relative;
- display: grid;
- row-gap: 20rpx;
-
- &--item {
- z-index: 2;
- font-size: 30rpx;
- color: #7994b2;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- aspect-ratio: 1;
- }
- }
- }
- }
-
- .is-limit {
- color: var(--limit--text-color) !important;
- background-color: var(--limit--background-color);
- }
-
- .is-in-range {
- --color: var(--in-range--text-color) !important;
- background-color: var(--in-range--background-color) !important;
- border-radius: 0 !important;
- color: var(--in-range--text-color) !important;
- }
-
- .is-selected {
- &:after {
- content: "已选" !important;
- color: var(--seletct--text-color) !important;
- position: absolute;
- left: 50%;
- bottom: var(--bottom-offset);
- transform: translate(-50%);
- font-size: var(--bottom-font-size);
- line-height: var(--bottom-font-size);
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- }
-
- position: relative;
- background-color: var(--seletct--background-color) !important;
- color: var(--seletct--text-color) !important;
- border-radius: 8rpx !important;
- }
-
- .is-start {
- &:after {
- content: "开始";
- color: var(--seletct--text-color) !important;
- position: absolute;
- left: 50%;
- bottom: var(--bottom-offset);
- transform: translate(-50%);
- font-size: var(--bottom-font-size);
- line-height: var(--bottom-font-size);
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- }
-
- position: relative;
- background-color: var(--seletct--background-color) !important;
- color: var(--seletct--text-color) !important;
- border-radius: 8rpx 0 0 8rpx;
- }
-
- .is-end {
- &:after {
- content: "结束";
- color: var(--seletct--text-color) !important;
- position: absolute;
- left: 50%;
- bottom: var(--bottom-offset);
- transform: translate(-50%);
- font-size: var(--bottom-font-size);
- line-height: var(--bottom-font-size);
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- }
-
- position: relative;
- background-color: var(--seletct--background-color) !important;
- color: var(--seletct--text-color) !important;
- border-radius: 0 8rpx 8rpx 0;
- }
-
- .image-closable {
- width: 36rpx;
- height: 36rpx;
- }
-
- .image-arrow {
- width: 36rpx;
- height: 36rpx;
- }
-
- .right-arrow {
- transform: rotate(180deg);
- }
-
- .brief {
- margin: 0 30rpx;
- color: #000000d9;
- font-size: 30rpx;
- /* #ifdef MP-WEIXIN */
- font-weight: bolder;
- /* #endif */
- /* #ifdef APP-PLUS */
- font-weight: 500;
- /* #endif */
- }
-
- .selected-date {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- padding: 10rpx 0;
- color: #94a0b4ff;
- font-size: 30rpx;
- margin-top: 20rpx;
- width: 600rpx;
- }
-
- .selected-wrap {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- width: 100%;
- }
- }
- </style>
|