primefaces / primevue

Next Generation Vue UI Component Library
https://primevue.org
MIT License
10.41k stars 1.22k forks source link

Component Name: Clock picker with calendar #3309

Closed MorningZheng closed 1 year ago

MorningZheng commented 1 year ago

Describe the feature you would like to see added

Here is a way to add clock picker with calendar.

<style lang="scss" scoped>
.info {
    position: relative;
    width: 100%;
    background-color: var(--primary-color);
    color: var(--primary-color-text);
    display: flex;
    flex-direction: column;
    padding: 0.5rem 0.5rem 0.5rem 0.5rem;

    div.line {
        width: 50%;
        position: absolute;
        bottom: -0.5rem;
        height: 3px;
        background-color: var(--pink-600);
        animation: {
            duration: 0.2s;
            timing-function: linear;
            fill-mode: forwards;
            iteration-count: 1;
        }

        &.full{
            width: 100%;
        }

        &.date,&.year,&.month {
            animation: {
                name: rightToLeft;
            };
        }

        &.hours,&.minutes {
            animation: {
                name: leftToRight;
            };
        }

        @keyframes leftToRight {
            0% {
                left: 0;
            }
            100% {
                left: 50%;
            }
        }

        @keyframes rightToLeft {
            0% {
                right: 0;
            }
            100% {
                right: 50%;
            }
        }
    } ;

    .sleep {
        opacity: 0.6;
    } ;

    .dateTimeGroup {
        position: relative;
        width: 100%;
        display: flex;
        flex-direction: row;
        justify-content: space-around;

        & > div:not(.line) { //.data,.time
            cursor: pointer;
            position: relative;
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            align-items: center;
            flex-grow: 1;

            .text {
                font-family: Roboto, sans-serif;
                font-weight: 400;
                font-size: 300%;
            }

            &.time { //相对.text
                font-size: 115%;
            } ;

            &.date {

                & > div {
                    display: flex;
                    flex-direction: column;
                }

                .year {
                    font-size: 115%;
                    /*opacity:0.8;*/
                    padding: {
                        top: 0.5rem;
                        bottom: 0.5rem;
                    }
                }
            }

            span.pi {
                width: 100%;
                font-size: 120%;
                text-align: center;
                padding: {
                    top: 2rem;
                    bottom: 0.5rem;
                };
            }
        }
    }
}
//覆盖
.p-datepicker {
    padding: 0 0 0 0;

    &:not(.p-datepicker-touch-ui){
        min-width:24em;
    }

    ::v-deep(.p-datepicker-header,.p-timepicker){
        padding: 0 0 0 0;
    }

    .footer {
        width: 100%;
        padding: {
            bottom: 0.5rem;
            left: 0.5rem;
            right: 0.5rem;
        };
    }
}
</style>
<template>
    <FixCalendar v-bind="PropsForCalendar" v-model:view="view.calendar" v-model="data.value" v-model:is-show="view.open"
                 :panelStyle="{minWidth:'22em;'}">
        <template #header>
            <div class="info">
                <div class="dateTimeGroup">
                    <div v-if="!props.timeOnly" :class="{date:true,}">
                        <div>
                            <span class="year" :class="{sleep:view.calendar!=='year'}" @click="view.calendar='year'">{{ data.year }}</span>
                            <span class="text">
                                <span :class="{sleep:view.calendar!=='month'}" @click="view.calendar='month'">{{ data.month }}</span>
                                <span class="sleep">-</span>
                                <span :class="{sleep:view.calendar!=='date'}" @click="view.calendar='date'">{{ data.date }}</span>
                            </span>
                        </div>

                        <span class="pi pi-calendar" @click="view.calendar='date'"></span>
                    </div>

                    <div :class="{time:true,}">
                        <span class="text">
                            <span :class="{sleep:view.calendar!=='hours'}"
                                  @click="view.calendar='hours'">{{ data.hour }}</span>
                            <span class="sleep">:</span>
                            <span :class="{sleep:view.calendar!=='minutes'}"
                                  @click="view.calendar='minutes'">{{ data.minute }}</span>
                        </span>
                        <span v-if="!props.timeOnly" class="pi pi-clock" @click="view.calendar='hours'"></span>
                    </div>

                    <div class="line" :class="{[props.timeOnly?'full':view.calendar]:true}"></div>
                </div>
            </div>
        </template>

        <template #clock>
            <ClockPicker class="flex center middle" v-model:view="view.clock" v-model:value="data.value" :is12="props.hourFormat==='12'"></ClockPicker>
        </template>

        <template #footer>
            <div class="width-100% flex center gap-1em padding-round-3px">
                <Button class="p-button-text grow-1" label="现在" @click="data.value=new Date()"></Button>

                <Button class="p-button-text grow-1" label="确定" @click="view.open=false"></Button>
            </div>
        </template>
    </FixCalendar>
</template>
<script setup>
import Calendar from "primevue/calendar";
import {h, reactive, toRef, watch,} from 'vue';
import ClockPicker from "./ClockPicker";
import Button from "primevue/button";

//循环劫持组件,按需修改
const walk=function (node,root){
    if (node.props && node.props.class) {
        if (node.props.class.indexOf('p-datepicker-header') !== -1) {
            let a = [], b = [];
            for (const o of node.children) {
                (o.type.constructor === String ? b : a).push(o);
            }
            node.props.class += ' flex-as-col';
            node.children = [...a, ];
            if(this.currentView==='year'||this.currentView==='month'||this.currentView==='date')
                node.children.push(h('div', {class: 'width-100% flex-as-row padding-round-3px'}, b));

        }else if (node.props.class.indexOf('p-timepicker') !== -1) {
            node.children = [];
        }else if (/(^|[\s\t])p\-datepicker([^\-]|[\s\t]|$)/.test(node.props.class)) {
            node.props[out.__scopeId] = '';
            //增加clock
            if((this.currentView==='hours'||this.currentView==='minutes') && this.$slots.clock){
                node.children=[...node.children.slice(0,-1),...this.$slots.clock(),...node.children.slice(-1)]
            }
        }
    }

    if (node.children) {
        if (Array.isArray(node.children)) for (const o of node.children) walk.call(this,o, node);
        else {
            for (const i in node.children) {
                if (node.children[i] instanceof Function) {
                    const fn=node.children[i];
                    node.children[i]=(...args)=>{
                        const temp=[];
                        for(const o of fn(...args)){
                            temp.push(walk.call(this,o,node));
                        }
                        return temp;
                    }
                }
            }
        }
    }

    return node;
}

const FixCalendar = {
    mixins: [Calendar],
    emits:['update:view','update:isShow'],
    props:{
        isShow:{
            type:Boolean,
            default:false,
        },
    },
    watch:{
        isShow:{
            handler(nv,ov){
                nv?this.open():this.close();
            },
            immediate:true,
        },
        currentView(nv){
            this.$emit('update:view',nv);
        },
        overlayVisible(nv){
            this.$emit('update:isShow',nv);
        },
        view(nv){
            this.currentView=nv;
        },
    },
    render(ctx, cache, $props, $setup, $data, $options) {
        //使用父类生成组件,并劫持
        return walk.call(this,Calendar.render.call(this, ctx, cache, $props, $setup, $data, $options),null);
    },
    methods:{
        open(){
            if(!this.overlayVisible)this.overlayVisible=true;
        },
        close(){
            if(this.overlayVisible)this.overlayVisible=false;
        },
        alignOverlay(){
            Calendar.methods.alignOverlay.apply(this,arguments);
            if(this.overlay.style.minWidth)this.overlay.style.minWidth='';//自定义min-width,允许其他组件修改
        },
    }
}

const props=defineProps({
    ...Calendar.props,
    value:{
        type:[String,Date],
        default:null,
    },
});

//批量绑定
const PropsForCalendar=reactive({});
for(const k in Calendar.props){
    if(k==='view' || k==='value'|| k==='modelValue')continue;
    PropsForCalendar[k]=toRef(props,k);
}

const emits=defineEmits(['update:view','update:value','update:modelValue']);

const view=reactive({
    clock:undefined,
    calendar:undefined,

    open:false,
});

watch(()=>view.clock,nv=>view.calendar=nv+'s');
watch(()=>view.calendar,nv=>{
    if(nv.slice(-1)==='s')view.clock=nv.slice(0,-1);
    emits('update:view',nv);
})
watch(()=> props.view,nv=>view.calendar=nv,{immediate:true});

const data=reactive({
    value:null,

    year: '-',
    month: '-',
    date: -'',
    hour: '-',
    minute: '-',
    second: '-',
});

watch(()=>data.value,nv=>{
    if(nv){
        data.year=nv.getFullYear();
        data.month=`0${nv.getMonth()+1}`.slice(-2);
        data.date=`0${nv.getDate()}`.slice(-2);

        data.hour=`0${nv.getHours()}`.slice(-2);
        data.minute=`0${nv.getMinutes()}`.slice(-2);
        data.second=`0${nv.getSeconds()}`.slice(-2);
    }else for(const i of 'year,month,date,hour,minute,second'.split(','))view[i]='-';
})

watch(()=>props.value,nv=>data.value=nv,{immediate:!!props.value});
watch(()=>props.modelValue,nv=>data.value=nv,{immediate:!!props.modelValue});
watch(()=>data.value,nv=>{
    emits('update:value',nv)
    emits('update:modelValue',nv);
});

</script>
<script>
const out={};
export default out;
</script>

ClockPicker.vue

<style scoped lang='scss'>
/*!
 * ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/)
 * Copyright 2014 Wang Shenwei.
 * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
 */

/*!
 * ClockPicker v{package.version} for Bootstrap (http://weareoutman.github.io/clockpicker/)
 * Copyright 2014 Wang Shenwei.
 * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
 */

.clockpicker{
    .input-group-addon {
        cursor: pointer;
    }

    &-moving {
        cursor: move;
    }

    background-color: #f8f8f8;
}

.clockpicker-align-left.popover > .arrow {
    left: 25px;
}
.clockpicker-align-top.popover > .arrow {
    top: 17px;
}
.clockpicker-align-right.popover > .arrow {
    left: auto;
    right: 25px;
}
.clockpicker-align-bottom.popover > .arrow {
    top: auto;
    bottom: 6px;
}

.popover-title {
    background-color: #fff;
    color: #999;
    font-size: 24px;
    font-weight: bold;
    line-height: 30px;
    text-align: center;
}
.popover-title span {
    cursor: pointer;
}

.popover-content {
    padding: 12px;
    &:last-child {
        border-bottom-left-radius: 5px;
        border-bottom-right-radius: 5px;
    }
}

.clockpicker-plate {
    background-color: #fff;
    border: 1px solid #ccc;
    border-radius: 50%;
    width: 200px;
    height: 200px;
    overflow: visible;
    position: relative;
    /* Disable text selection highlighting. Thanks to Hermanya */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}
.clockpicker-canvas,
.clockpicker-dial {
    width: 200px;
    height: 200px;
    position: absolute;
    left: -1px;
    top: -1px;
}
.clockpicker-minutes {
    visibility: hidden;
}
.clockpicker-tick {
    border-radius: 50%;
    color: #666;
    line-height: 26px;
    text-align: center;
    width: 26px;
    height: 26px;
    position: absolute;
    cursor: pointer;

    &.active,&:hover {
        background-color: rgb(192, 229, 247);
        background-color: rgba(0, 149, 221, .25);
    }
}

.clockpicker-button {
    width: 100%;
    position: absolute;
    bottom: 32px;

    button{
        position: absolute;
        background-image: none;
        background-color: #fff;
        border-width: 1px 0 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
        margin: 0;
        padding: 10px 0;
        width: 6em !important;

        &:hover {
            background-image: none;
            background-color: #ebebeb;
        }

        &:focus {
            outline: none!important;
        }

        &.am-button {
            left: 5px;
            padding: 5px;
            border: 1px solid rgba(0, 0, 0, .2);
            border-radius: 4px;

        }
        &.pm-button {
            right: 5px;
            padding: 5px;
            border: 1px solid rgba(0, 0, 0, .2);
            border-radius: 4px;
        }

        &.selected{
            color: rgb(0, 149, 221);
            font-weight: bolder;
        }
    }
}

.clockpicker-dial {
    -webkit-transition: -webkit-transform 350ms, opacity 350ms;
    -moz-transition: -moz-transform 350ms, opacity 350ms;
    -ms-transition: -ms-transform 350ms, opacity 350ms;
    -o-transition: -o-transform 350ms, opacity 350ms;
    transition: transform 350ms, opacity 350ms;

    &-out {
        opacity: 0;
    }
}

.clockpicker-dial-out{
    &.clockpicker-hours {
        -webkit-transform: scale(1.2, 1.2);
        -moz-transform: scale(1.2, 1.2);
        -ms-transform: scale(1.2, 1.2);
        -o-transform: scale(1.2, 1.2);
        transform: scale(1.2, 1.2);
    }
    &.clockpicker-minutes {
        -webkit-transform: scale(.8, .8);
        -moz-transform: scale(.8, .8);
        -ms-transform: scale(.8, .8);
        -o-transform: scale(.8, .8);
        transform: scale(.8, .8);
    }
}

.clockpicker-canvas {
    -webkit-transition: opacity 175ms;
    -moz-transition: opacity 175ms;
    -ms-transition: opacity 175ms;
    -o-transition: opacity 175ms;
    transition: opacity 175ms;

    &-out {
        opacity: 0.25;
    }

    &-bearing, &-fg {
        stroke: none;
        fill: rgb(0, 149, 221);
    }
    &-bg {
        stroke: none;
        fill: rgb(192, 229, 247);
    }
    &-bg-trans {
        fill: rgba(0, 149, 221, .25);
    }
    & line {
        stroke: rgb(0, 149, 221);
        stroke-width: 1;
        stroke-linecap: round;
        /*shape-rendering: crispEdges;*/
    }
}

</style>

<template>
    <div :class="['relative','clockpicker',{'clockpicker-moving':view.moving}]">
        <div class="popover-content">
            <div class="clockpicker-plate" :ref="_=>view.plate=_"
                 @mousemove="pick.move" @touchmove="pick.move"
                 @mousedown="mouse.plate" @touchstart="mouse.plate"
                 @mouseup="pick.done" @touchend="pick.done">

                <div class="clockpicker-canvas" :ref="_=>view.canvas=_">
                    <svg class="clockpicker-svg" width="200" height="200" @mousedown="mouse.plate">
                        <g transform="translate(100,100)" :ref="_=>view.g=_">
                            <circle class="clockpicker-canvas-bg" :ref="_=>view.bg=_" r="13" :cx="data.cx" :cy="data.cy"></circle>
                            <line x1="0" y1="0" :ref="_=>view.hand=_" :x2="data.cx" :y2="data.cy"></line>
                            <circle class="clockpicker-canvas-bearing" :ref="_=>view.bearing=_" cx="0" cy="0" r="2"></circle>
                            <circle class="clockpicker-canvas-fg" :ref="_=>view.fg=_" r="3.5" :cx="data.cx" :cy="data.cy"></circle>
                        </g>
                    </svg>
                </div>

                <div class="clockpicker-dial clockpicker-hours"
                     :class="[
                         {'clockpicker-dial':data.currentView==='hour','clockpicker-dial-out':data.currentView!=='hour',},
                     ]"
                     :style="{visibility: data.currentView==='hour'?'visible':'hidden'}">
                    <div v-for="(o,i) of view.hours" :style="o.css" :key="i"
                         :class="['clockpicker-tick',{active:data.hour===o.value}]"
                         @mousedown="pick.start" @touchstart="pick.start" @click="pick.click">{{o.html}}</div>
                </div>

                <div class="clockpicker-dial clockpicker-minutes"
                     :class="[
                         {'clockpicker-dial':data.currentView==='minute','clockpicker-dial-out':data.currentView!=='minute',},
                     ]"
                     :style="{visibility: data.currentView==='minute'?'visible':'hidden'}">
                    <div v-for="(o,i) of view.minutes" :style="o.css"
                         :class="['clockpicker-tick',{active:data.minute===o.value}]"
                         @mousedown="pick.start" @touchstart="pick.start">{{o.html}}</div>
                </div>
            </div>
        </div>

        <div class="clockpicker-button" v-if="props.is12" :midday="data.midday">
            <button v-for="(v,i) of ['am','pm']"
                    :class="[`${v}-button`,{selected:data.midday===v}]"
                    @click="data.midday=v">{{v.toUpperCase()}}</button>
        </div>

    </div>
</template>

<script>
export default {
    name: "TimeInput"
}

const Radius={
    dial:100,
    outer:80,
    inner:54,
    tick:13,
},leadingZero=num=>(num < 10 ? '0' : '') + num,transitionSupported = (()=>{
    const style = document.createElement('div').style;
    return 'transition' in style || 'WebkitTransition' in style || 'MozTransition' in style || 'msTransition' in style || 'OTransition' in style;
})(),duration = transitionSupported ? 350 : 1;
const vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;

const events={
    on(el,type,listener){
        if(Array.isArray(type)){
            for(const t of type)events.on(el,t,listener);
        }else{
            if(!el['#listener'])el['#listener']={};
            const {'#listener':_}=el;

            if(!_[type])_[type]=new Map;
            if(!_[type].has(listener))_[type].set(listener);

            el.addEventListener(type,listener);
        }
        return events;
    },
    off(el,type,listener){
        if(Array.isArray(type)){
            for(const t of type)events.off(el,t,listener);
        }else{
            if(!el['#listener'])return events;
            const {'#listener':_}=el;
            if(!_[type])_[type]=new Map;
            if(_[type].has(listener))_[type].delete(listener);

            el.removeEventListener(type,listener);
        }
        return events;
    },
    removeAll(el,type){
        if(Array.isArray(type)){
            for(const t of type)events.removeAll(el,t);
        }else{
            if(!el['#listener'])return events;
            const {'#listener':_}=el;
            if(!_[type])return events;

            for (const listener of _[type].keys()) {
                el.removeEventListener(type,listener);
                _[type].delete(listener);
            };
        }
        return events;
    },
};
</script>
<script setup>
import {reactive, watch} from 'vue';

const props = defineProps({
    value: {
        type: Date,
        default: null,
    },
    is12:{
        type:Boolean,
        default: false,
    },
    midday:{
        type:String,
        default:'pm',
        validator(val){
            if(val){
                val=String(val).toLowerCase();
                return val==='am'||val==='pm';
            }else return false;
        },
    },
    vibrate:{
        type:Boolean,
        default:true,
    },
    autoSwitch:{
        type:Boolean,
        default:true,
    },
    view:{
        type:String,
        validator(val){
            if(val){
                val=String(val).toLowerCase();
                return val==='hour'||val==='minute';
            }else return true;
        },
        default:'hour',
    },
});

const emits=defineEmits(['update:midday','update:value','update:view',]);

const data = reactive({
    value: null,
    currentView:null,
    midday:null,

    minute:0, hour:0,second:0,

    cx:0, cy:0,
})

const view=reactive({
    minutes:[],
    hours:[],

    moving:false, moved:false, editing:false,

    plate:null, g:null, bg:null, fg:null,
    hand:null, bearing:null, canvas:null,
})

const flag={
    setting:false,
    moved:true,
    up:true,

    tmr:{
        up:-1,
    }
}

const setView=()=>{
    const value=data[data.currentView],isHours = data.currentView === 'hour', unit = Math.PI / (isHours ? 6 : 30);
    const radian = value * unit, radius = !props.is12 && (isHours && value > 0 && value < 13) ? Radius.inner : Radius.outer;
    data.cx = Math.sin(radian) * radius;
    data.cy = - Math.cos(radian) * radius;
};

const toggleView=(view, delay)=>setTimeout(()=>data.currentView=view,delay);

const setHand = (x, y, roundBy5, dragging)=>{
    const isHours = data.currentView === 'hour',
        unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
        z = Math.sqrt(x * x + y * y),
        inner = isHours && z < (Radius.outer + Radius.inner) / 2;

    let radian = Math.atan2(x, - y),radius = inner ? Radius.inner : Radius.outer,value;

    if (props.is12) radius = Radius.outer;

    // Radian should in range [0, 2PI]
    if (radian < 0) radian = Math.PI * 2 + radian;

    // Get the round value
    value = Math.round(radian / unit);

    // Get the round radian
    radian = value * unit;

    const cx = Math.sin(radian) * radius, cy = - Math.cos(radian) * radius;
    data.cx=cx,data.cy=cy;

    // Correct the hours or minutes
    if(isHours){
        if(props.is12){
            if (value === 0) value = 12;
            if(data.midday==='pm')value+=12;
        }else{
            if (value === 12) value = 0;
            value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
        }
    }else{
        if (roundBy5) value *= 5;
        if (value === 60) value = 0;
    }

    // Once hours or minutes changed, vibrate the device
    if (data[data.currentView] !== value) {
        if (vibrate && props.vibrate) {
            // Do not vibrate too frequently
            if (! data.vibrateTimer) {
                navigator[vibrate](10);
                data.vibrateTimer = setTimeout(()=>data.vibrateTimer=-1, 100);
            }
        }
    }
    data[data.currentView] = value;

    // Place clock hand at the top when dragging
    if(!view.g)return;
    if (dragging || (! isHours && value % 5)) {
        view.g.insertBefore(view.hand, view.bearing);
        view.g.insertBefore(view.bg, view.fg);
        view.bg.setAttribute('class', 'clockpicker-canvas-bg clockpicker-canvas-bg-trans');
    } else {
        // Or place it at the bottom
        view.g.insertBefore(view.hand, view.bg);
        view.g.insertBefore(view.fg, view.bg);
        view.bg.setAttribute('class', 'clockpicker-canvas-bg');
    }
};

const mouse={
    plate(e,start=true){
        if(e.defaultPrevented)return;
        const rect=view.bg.getBoundingClientRect(),{top,left} = {
            top: rect.top + window.scrollY,
            left: rect.left + window.scrollX,
        },{clientX:x,clientY:y}=e;

        const inner = x>=left && x<=(left+rect.width) && y>=top && (y<=top+rect.height);
        if(inner && start)pick.start(e);
    },
}

const pick={
    start(e, space){
        e.preventDefault();
        if(e.touches) pick['#']={
            touches:e.touches,
            changedTouches:e.changedTouches,
        };

        view.editing=true;
        flag.moved=false;

        clearTimeout(flag.tmr.up);
        flag.tmr.up=setTimeout(()=>flag.up=false,200);
    },
    move(e){
        if(!view.editing||flag.moved)return;
        e.preventDefault();

        const rect=view.plate.getBoundingClientRect();
        const offset = {
                top: rect.top + window.scrollY,
                left: rect.left + window.scrollX,
            },
            isTouch = /^touch/.test(e.type),
            x0 = offset.left + Radius.dial,
            y0 = offset.top + Radius.dial,
            dx = (isTouch ? e.touches[0] : e).pageX - x0,
            dy = (isTouch ? e.touches[0] : e).pageY - y0,
            z = Math.sqrt(dx * dx + dy * dy);

        // When clicking on minutes view space, check the mouse position
        if (view.space && (z < Radius.outer - Radius.tick || z > Radius.outer + Radius.tick)) return;

        // Clock
        setHand(dx, dy, data.currentView==='hour'?true:flag.up, true);
    },
    done(e){
        if(!view.editing)return;
        e.preventDefault();

        clearTimeout(flag.tmr.up);
        flag.moved=true;
        view.editing=false;

        const isTouch = /^touch/.test(e.type);
        const rect=view.plate.getBoundingClientRect();
        const offset = {
                top: rect.top + window.scrollY,
                left: rect.left + window.scrollX,
            },
            x0 = offset.left + Radius.dial,
            y0 = offset.top + Radius.dial;

        const dx = (isTouch ? e.touches[0]||pick['#'].touches[0] : e).pageX - x0, dy = (isTouch ? e.touches[0]||pick['#'].touches[0] : e).pageY - y0;
        const x = (isTouch ? e.changedTouches[0]||pick['#'].changedTouches[0] : e).pageX - x0;
        const y = (isTouch ? e.changedTouches[0]||pick['#'].changedTouches[0] : e).pageY - y0;

        if (flag.moved && x === dx && y === dy) setHand(x, y,data.currentView==='hour'?true:flag.up,true);
        view.moving=false;
        flag.up=true;

        if(props.autoSwitch){
            if(data.currentView==='hour')toggleView('minute', duration / 2);
            else if(data.currentView==='minute')toggleView('hour', duration / 2);
        };
    },
}

{
    watch(()=>props.is12,nv=>{
        if(nv){
            for (let i = 1; i < 13; i ++) {
                const tick = {value:i},radian = i / 6 * Math.PI,radius = Radius.outer;
                tick.css={
                    left: Radius.dial + Math.sin(radian) * radius - Radius.tick +'px',
                    top: Radius.dial - Math.cos(radian) * radius - Radius.tick +'px',
                    'font-size': '120%',
                };
                tick.html=leadingZero(i);
                view.hours.push(tick);
            }
        }else{
            for (let i = 0; i < 24; i ++) {
                const tick = {value:i};
                const radian = i / 6 * Math.PI;
                const inner = i > 0 && i < 13;
                const radius = inner ? Radius.inner : Radius.outer;
                tick.css={
                    left: Radius.dial + Math.sin(radian) * radius - Radius.tick +'px',
                    top: Radius.dial - Math.cos(radian) * radius - Radius.tick +'px'
                };
                if (inner) tick.css['font-size']='120%';
                tick.html=i>13||i===0?leadingZero(i):i;
                view.hours.push(tick);
            }
        }
    },{immediate:true});
    for (let i = 0; i < 60; i += 5) {
        const tick = {'font-size': '120%'},radian = i / 30 * Math.PI,radius = Radius.outer;
        tick.css={
            left: Radius.dial + Math.sin(radian) * radius - Radius.tick +'px',
            top: Radius.dial - Math.cos(radian) * radius - Radius.tick +'px',
        };
        tick.html=leadingZero(i);
        view.minutes.push(tick);
    }
}

//初始化
watch(()=>data.currentView,nv=>{
    setView();
    if(props.view!==nv)emits('update:view',nv);
});
watch(()=>props.view,nv=>{
    if(data.currentView!==nv)data.currentView=nv;
},{immediate:true});

watch(()=>props.midday,nv=>{
    if(data.midday!==nv)data.midday=nv;
})
watch(()=>data.midday,nv=>{
    if(props.midday!==nv)emits('update:midday',nv);

    const time=new Date;
    if(data.value)time.setTime(data.value.getTime());
    let prefix=0;
    if(props.is12){
        if((data.midday==='pm'&&data.hour<12))prefix=12;
        else if(data.midday==='am'&&data.hour===12)prefix=-12;
    }

    time.setHours(data.hour+prefix);
    if(!data.value || data.value.getTime()!==time.getTime())data.value=time;
})

watch(()=>[data.hour,data.minute,view.editing],nv=>{
    if(view.editing)return;
    const time=new Date();
    if(data.value)time.setTime(data.value.getTime());

    let prefix=0;
    if(props.is12){
        if((data.midday==='pm'&&data.hour<12))prefix=12;
        else if(data.midday==='am'&&data.hour===12)prefix=-12;
    }

    time.setHours(data.hour+prefix);
    time.setMinutes(data.minute);
    time.setSeconds(data.second);
    // console.log(time)

    if(!data.value || time.getTime()!==data.value.getTime())data.value=time;
})

watch(()=>data.value,nv=>{
    let hour=0,minute=0,second=0,midday=null;
    if(nv){
        hour=nv.getHours();
        minute=nv.getMinutes();
        second=nv.getSeconds();

        if(props.is12){
            midday='am';
            if(hour>11){
                if(hour<24)midday='pm';
                hour-=12;
            }
            if(hour===0)hour=12;
        }
    }

    if(data.hour!==hour)data.hour=hour;
    if(data.minute!==minute)data.minute=minute;
    if(data.second!==second)data.second=second;
    if(data.midday!==midday)data.midday=midday;

    setView();
    flag.setting=false;

    if(props.value && nv){
        if(props.value.getTime()===nv.getTime())return;
    }
    emits('update:value',nv);
});

watch(() => props.value, nv => {
    flag.setting=true;
    if(nv){
        if(!data.value || data.value.getTime()!==nv.getTime())data.value=nv;
    }else data.value=nv;
},{immediate:true})

defineExpose({
    toggleView,
})

</script>

Is your feature request related to a problem?

No response

Describe the solution you'd like

No response

Describe alternatives you have considered

No response

Additional context

No response

MorningZheng commented 1 year ago

Hope it helps you

tugcekucukoglu commented 1 year ago

Please describe the issue properly. We would be grateful if you create a PR with the possible solution.