v1.3.0 - add Annual/Quarter/Hourly timeline views

This commit is contained in:
LINING-PC\lining
2025-09-12 16:41:32 +08:00
parent bb0a00ad05
commit 281c2d9c5b
27 changed files with 4253 additions and 454 deletions
+82 -15
View File
@@ -68,7 +68,7 @@ watch(
() => [props.task?.isTimerRunning, props.task?.timerStartTime, props.task?.timerElapsedTime],
() => {
updateTimer()
}
},
)
// 计时器本地状态,保证点击后UI立即切换
@@ -84,7 +84,7 @@ watch(
timerInterval.value = null
}
},
{ immediate: true }
{ immediate: true },
)
// 修正计时器每秒递增逻辑,保证计时器正常跳动
@@ -102,7 +102,7 @@ watch(
updateTimer()
}
},
{ immediate: true }
{ immediate: true },
)
onUnmounted(() => {
@@ -153,7 +153,7 @@ const availableParentTasks = computed(() => {
.filter(
task =>
task.id !== props.task?.id && // 排除当前任务自己
(task.type === 'story' || task.type === 'task') // 只显示story和task类型
(task.type === 'story' || task.type === 'task'), // 只显示story和task类型
)
.map(task => ({
...task,
@@ -209,6 +209,38 @@ const handleProgressInputBlur = () => {
progressDisplayValue.value = progress.toString()
}
// 处理预计工时输入
const handleEstimatedHoursInput = (event: Event) => {
const target = event.target as HTMLInputElement
let value = parseFloat(target.value)
// 数据验证
if (isNaN(value) || value < 0) {
value = 0
} else if (value > 99999) {
value = 99999
}
// 保留两位小数
formData.estimatedHours = Math.round(value * 100) / 100
}
// 处理实际工时输入
const handleActualHoursInput = (event: Event) => {
const target = event.target as HTMLInputElement
let value = parseFloat(target.value)
// 数据验证
if (isNaN(value) || value < 0) {
value = 0
} else if (value > 99999) {
value = 99999
}
// 保留两位小数
formData.actualHours = Math.round(value * 100) / 100
}
// 处理进度输入框聚焦(选中全部文本)
const handleProgressInputFocus = (event: Event) => {
const target = event.target as HTMLInputElement
@@ -256,6 +288,31 @@ const handleProgressKeydown = (event: KeyboardEvent) => {
}
}
// 处理小时视图中的日期格式
const processDateForHourView = (dateStr: string | undefined, type: 'start' | 'end'): string => {
if (!dateStr) return ''
// 检查是否只有日期部分(YYYY-MM-DD格式)
if (typeof dateStr === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateStr.trim())) {
// 如果是开始日期,设置为当日00:00
if (type === 'start') {
return `${dateStr} 00:00`
}
// 如果是结束日期,设置为次日00:00
if (type === 'end') {
const date = new Date(dateStr)
date.setDate(date.getDate() + 1)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day} 00:00`
}
}
// 已经包含时间部分或其他格式,直接返回
return dateStr
}
// 错误信息
const errors = reactive({
name: '',
@@ -273,7 +330,13 @@ watch(
resetForm()
if (props.task && props.isEdit) {
// 编辑模式,填充表单数据
Object.assign(formData, props.task)
const taskData = { ...props.task }
// 处理日期格式:如果是只有日期部分的数据,在小时视图编辑时需要特殊处理
taskData.startDate = processDateForHourView(taskData.startDate, 'start')
taskData.endDate = processDateForHourView(taskData.endDate, 'end')
Object.assign(formData, taskData)
} else if (props.task && !props.isEdit) {
// 新建模式,自动绑定上级任务
formData.parentId = props.task.parentId ?? undefined
@@ -283,7 +346,7 @@ watch(
// 抽屉显示时重新请求任务数据,确保前置任务列表是最新的
window.dispatchEvent(new CustomEvent('request-task-list'))
}
}
},
)
// 监听 isVisible 变化,同步到父组件
@@ -300,7 +363,7 @@ watch(
formData.parentId = newTask.parentId ?? undefined
}
},
{ immediate: true }
{ immediate: true },
)
// 重置表单
@@ -485,7 +548,7 @@ watch(
newValue => {
progressDisplayValue.value = (newValue || 0).toString()
},
{ immediate: true }
{ immediate: true },
)
// 修正计时器首次启动不跳动问题:每次打开抽屉时重置 timerElapsed,且 timerStartTime 为空时立即赋值
@@ -503,7 +566,7 @@ watch(
}
}
},
{ immediate: true }
{ immediate: true },
)
const handleStartTimer = (desc?: string) => {
@@ -783,12 +846,14 @@ function confirmTimer(desc: string) {
<label class="form-label" for="task-estimated-hours">{{ t.estimatedHours }}</label>
<input
id="task-estimated-hours"
v-model.number="formData.estimatedHours"
v-model="formData.estimatedHours"
type="number"
class="form-input"
placeholder="0"
placeholder="0.00"
min="0"
max="999"
max="99999"
step="0.01"
@input="handleEstimatedHoursInput"
/>
</div>
@@ -796,12 +861,14 @@ function confirmTimer(desc: string) {
<label class="form-label" for="task-actual-hours">{{ t.actualHours }}</label>
<input
id="task-actual-hours"
v-model.number="formData.actualHours"
v-model="formData.actualHours"
type="number"
class="form-input"
placeholder="0"
placeholder="0.00"
min="0"
max="999"
max="99999"
step="0.01"
@input="handleActualHoursInput"
/>
</div>
</div>