Files
lucia-frontend/src/components/durationjs.vue
2023-08-14 09:32:43 +08:00

276 lines
8.7 KiB
Vue

<template>
<div class=" relative">
<div class="flex justify-center items-center gap-1 px-1 w-32 border border-neutral-500 cursor-pointer" @click="openTiemSelect = !openTiemSelect">
<p>{{ days }}d</p>
<p>{{ hours }}h</p>
<p>{{ minutes }}m</p>
<p>{{ seconds }}s</p>
</div>
<div class="duration-container absolute left-1/2 top-1/2 -translate-x-1/2" v-show="openTiemSelect">
<div class="duration-box" v-for="(unit, index) in inputTypes" :key="unit">
<input
type="text"
class="duration duration-val"
:data-index="index"
:data-tunit="unit"
:data-max="tUnits[unit].max"
:data-min="tUnits[unit].min"
:maxlength="tUnits[unit].dsp === 'd' ? 3 : 2"
:value="tUnits[unit].val.toString().padStart(2, '0')"
@focus="onFocus"
@blur="onBlur"
@keyup="onKeyUp"
/>
<label class="duration">{{ tUnits[unit].dsp }}</label>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
display: 'dhms',
seconds: 0,
minutes: 0,
hours: 0,
days: 0,
maxDays: 0,
minDays: 0,
totalSeconds: 0,
maxTotal: 8740000,
minTotal: 6666666,
inputTypes: [],
lastInput: null,
openTiemSelect: false,
};
},
computed: {
tUnits: {
get() {
return {
s: { dsp: 's', inc: 1, val: this.seconds, max: 60, rate: 1, min: 0 },
m: { dsp: 'm', inc: 1, val: this.minutes, max: 60, rate: 60, min: 0 },
h: { dsp: 'h', inc: 1, val: this.hours, max: 24, rate: 3600, min: 0 },
d: { dsp: 'd', inc: 1, val: this.days, max: this.maxDays, rate: 86400, min: this.minDays }
};
},
set(newValues) {
// 大於最大值時要等於最大值
for (const unit in newValues) {
this[unit] = newValues[unit].val;
const input = document.querySelector(`[data-tunit="${unit}"]`);
if (input) {
input.value = newValues[unit].val.toString().padStart(2, '0');
}
}
},
},
},
directives: {
'click-outside': {
bind(el, { value }) {
handleOutsideClick = e => {
const isClickOutside = e.target !== el && !el.contains(e.target)
if (isClickOutside) value(e)
e.stopPropagation()
}
document.addEventListener('click', handleOutsideClick)
document.addEventListener('touchstart', handleOutsideClick)
},
unbind() {
document.removeEventListener('click', handleOutsideClick)
document.removeEventListener('touchstart', handleOutsideClick)
}
}
},
methods: {
onFocus(event) {
this.lastInput = event.target;
this.lastInput.select(); // 當呼叫該方法時,文本框內的文字會被自動選中,這樣使用者可以方便地進行複製或刪除等操作。
},
onBlur(event) {
isNaN(event.target.value) ?
event.target.value = '00' :
event.target.value = event.target.value.toString().padStart(2, '0');
// 手 key 數值大於最大值時,要等於最大值
// 先將字串轉為數字才能比大小
const inputValue = parseInt(event.target.value, 10);
const max = parseInt(event.target.dataset.max, 10); // 設定最大值
const min = parseInt(event.target.dataset.min, 10);
if(inputValue> max) event.target.value = max.toString().padStart(2, '0');
else if(inputValue < min) event.target.value= min.toString().padStart(2, '0');
// 數值更新, tUnits 也更新, 並計算 totalSeconds
const dsp = event.target.dataset.tunit;
this.tUnits[dsp].val = event.target.value;
switch (dsp) {
case 'd':
this.days = event.target.value;
break;
case 'h':
this.hours = event.target.value;
break;
case 'm':
this.minutes = event.target.value;
break;
case 's':
this.seconds = event.target.value;
break;
};
this.calculateTotalSeconds();
},
onKeyUp(event) {
event.target.value = event.target.value.replace(/\D/g, '');
if (event.keyCode === 38 || event.keyCode === 40) {
this.actionUpDown(event.target, event.keyCode === 38, true);
};
},
actionUpDown(input, goUp, selectIt = false) {
const tUnit = input.dataset.tunit;
let newVal = parseInt(input.value, 10);
newVal = isNaN(newVal) ? 0 : newVal;
newVal += (goUp ? 1 : -1) * this.tUnits[tUnit].inc;
if (newVal <= 0 || newVal >= this.tUnits[tUnit].max) {
if (newVal === 0 || (newVal < 0 && input.dataset.index < 1)) {
newVal = '00';
} else if (input.dataset.index >= 1) {
const nextUnit = document.querySelector(`input[data-index="${parseInt(input.dataset.index) - 1}"]`);
let nextUnitVal = parseInt(nextUnit.value);
if (newVal < 0 && nextUnitVal > 0) {
nextUnit.value = nextUnitVal - 1;
nextUnit.dispatchEvent(new Event('blur'));
newVal = this.tUnits[tUnit].max - this.tUnits[tUnit].inc;
} else if (newVal > 0) {
nextUnit.value = nextUnitVal + 1;
nextUnit.dispatchEvent(new Event('blur'));
newVal = '00';
} else {
newVal = '00';
}
}
}
input.value = newVal.toString().padStart(2, '0');
if (selectIt) input.select();
},
secondToDate(totalSeconds, size) {
// .toString().padStart(2, '0')
totalSeconds = parseInt(totalSeconds);
if(!isNaN(totalSeconds)) {
this.seconds = totalSeconds % 60;
this.minutes = (Math.floor(totalSeconds - this.seconds) / 60) % 60;
this.hours = (Math.floor(totalSeconds / 3600)) % 24;
this.days = Math.floor(totalSeconds / (3600 * 24));
if(size === 'max') this.maxDays = Math.floor(totalSeconds / (3600 * 24));
else if(size === 'min') this.minDays = Math.floor(totalSeconds / (3600 * 24));
};
},
calculateTotalSeconds() {
let totalSeconds = 0;
let tUnits = {
s: { dsp: 's', inc: 1, val: this.seconds, max: 60, rate: 1, min: 0 },
m: { dsp: 'm', inc: 1, val: this.minutes, max: 60, rate: 60, min: 0 },
h: { dsp: 'h', inc: 1, val: this.hours, max: 24, rate: 3600, min: 0 },
d: { dsp: 'd', inc: 1, val: this.days, max: this.maxDays, rate: 86400, min: this.minDays },
};
for (const unit in this.tUnits) {
const val = parseInt(this.tUnits[unit].val, 10);
if (!isNaN(val)) totalSeconds += val * this.tUnits[unit].rate;
}
// 大於最大值時要等於最大值
if(totalSeconds >= this.maxTotal){
totalSeconds = this.maxTotal;
this.tUnits = tUnits;
this.secondToDate(this.maxTotal, 'max');
} else if (totalSeconds <= this.minTotal) {
// 小於最小值時要等於最小值
totalSeconds = this.minTotal;
this.tUnits = tUnits;
this.secondToDate(this.minTotal, 'min');
} else {
this.totalSeconds = totalSeconds;
}
},
},
mounted() {
this.inputTypes = this.display.split('');
let size ='min';
if(size === 'max') {
this.secondToDate(this.minTotal, 'min');
this.secondToDate(this.maxTotal, 'max');
this.totalSeconds = this.maxTotal;
}else if(size === 'min') {
this.secondToDate(this.maxTotal, 'max');
this.secondToDate(this.minTotal, 'min');
this.totalSeconds = this.minTotal;
}
},
};
</script>
<style scoped>
.duration-container {
margin: 4px;
border-radius: 4px;
background: var(--10, #FFF);
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.25);
height: 36px;
width: 221px;
}
.duration-box {
float:left;
overflow: auto;
height: var(--main-input-height);
padding: 4px;
}
.duration-box > .duration {
border: 1px solid var(--main-bg-light);
border-right: 0;
border-left: 0;
background-color:transparent;
color: var(--main-bg-light);
}
.duration-box > .duration:nth-child(1) {
border-left: 1px solid var(--main-bg-light);
border-top-left-radius: var(--main-input-br);
border-bottom-left-radius: var(--main-input-br);
}
.duration-box > .duration:nth-last-child(1) {
border-right: 1px solid var(--main-bg-light);
border-top-right-radius: var(--main-input-br);
border-bottom-right-radius: var(--main-input-br);
}
.duration {
float:left;
display: block;
overflow: auto;
height: var(--main-input-height);
outline: none;
font-size: 14px;
margin: 0px 2px;
}
.duration-box > label.duration {
line-height: 28px;
width: 12px;
overflow: hidden;
}
.duration-box > input[type="text"].duration {
text-align: right;
width: 26px;
padding: 3px 2px 0px 0px;
}
.duration-box > input[type="button"].duration {
width: 60px;
cursor: pointer;
}
</style>