v1.0.6 - TaskDrawer progressbar style updates
This commit is contained in:
+17
-8
@@ -20,7 +20,8 @@
|
|||||||
"progress": 100,
|
"progress": 100,
|
||||||
"estimatedHours": 16,
|
"estimatedHours": 16,
|
||||||
"actualHours": 15,
|
"actualHours": 15,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"estimatedHours": 24,
|
"estimatedHours": 24,
|
||||||
"actualHours": 28,
|
"actualHours": 28,
|
||||||
"type": "story",
|
"type": "story",
|
||||||
|
"parentId": 1,
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
@@ -43,7 +45,8 @@
|
|||||||
"progress": 80,
|
"progress": 80,
|
||||||
"estimatedHours": 8,
|
"estimatedHours": 8,
|
||||||
"actualHours": 10,
|
"actualHours": 10,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
@@ -55,7 +58,8 @@
|
|||||||
"progress": 0,
|
"progress": 0,
|
||||||
"estimatedHours": 16,
|
"estimatedHours": 16,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task"
|
"type": "task",
|
||||||
|
"parentId": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
@@ -84,7 +88,8 @@
|
|||||||
"estimatedHours": 80,
|
"estimatedHours": 80,
|
||||||
"actualHours": 35,
|
"actualHours": 35,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 11,
|
"id": 11,
|
||||||
@@ -96,7 +101,8 @@
|
|||||||
"estimatedHours": 80,
|
"estimatedHours": 80,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
@@ -108,7 +114,8 @@
|
|||||||
"estimatedHours": 88,
|
"estimatedHours": 88,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "5"
|
"predecessor": "5",
|
||||||
|
"parentId": 7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
@@ -134,7 +141,8 @@
|
|||||||
"estimatedHours": 48,
|
"estimatedHours": 48,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "7"
|
"predecessor": "7",
|
||||||
|
"parentId": 13
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 15,
|
"id": 15,
|
||||||
@@ -146,7 +154,8 @@
|
|||||||
"estimatedHours": 24,
|
"estimatedHours": 24,
|
||||||
"actualHours": 0,
|
"actualHours": 0,
|
||||||
"type": "task",
|
"type": "task",
|
||||||
"predecessor": "7"
|
"predecessor": "7",
|
||||||
|
"parentId": 13
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"collapsed": false
|
"collapsed": false
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const allTasks = ref<Task[]>([])
|
|||||||
// 获取可作为前置任务的任务列表(只包含type="task"的任务,且不包含当前任务)
|
// 获取可作为前置任务的任务列表(只包含type="task"的任务,且不包含当前任务)
|
||||||
const availablePredecessorTasks = computed(() => {
|
const availablePredecessorTasks = computed(() => {
|
||||||
return allTasks.value.filter(
|
return allTasks.value.filter(
|
||||||
task => task.type === 'task' && task.id !== props.task?.id, // 排除当前任务自己
|
task => task.type === 'task' && task.id !== props.task?.id // 排除当前任务自己
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ const availableParentTasks = computed(() => {
|
|||||||
.filter(
|
.filter(
|
||||||
task =>
|
task =>
|
||||||
task.id !== props.task?.id && // 排除当前任务自己
|
task.id !== props.task?.id && // 排除当前任务自己
|
||||||
(task.type === 'story' || task.type === 'task'), // 只显示story和task类型
|
(task.type === 'story' || task.type === 'task') // 只显示story和task类型
|
||||||
)
|
)
|
||||||
.map(task => ({
|
.map(task => ({
|
||||||
...task,
|
...task,
|
||||||
@@ -78,6 +78,97 @@ const getTaskTypeDisplay = (type: string): string => {
|
|||||||
return (t.value.taskTypeMap as Record<string, string>)?.[type] || type
|
return (t.value.taskTypeMap as Record<string, string>)?.[type] || type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算进度条样式
|
||||||
|
const progressSliderStyle = computed(() => {
|
||||||
|
const progressPercent = formData.progress || 0
|
||||||
|
return {
|
||||||
|
'--progress-percent': `${progressPercent}%`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 进度输入框的显示值
|
||||||
|
const progressDisplayValue = ref('0')
|
||||||
|
|
||||||
|
// 处理进度输入
|
||||||
|
const handleProgressInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
let value = target.value
|
||||||
|
|
||||||
|
// 只保留数字
|
||||||
|
value = value.replace(/[^\d]/g, '')
|
||||||
|
|
||||||
|
// 更新输入框显示
|
||||||
|
target.value = value
|
||||||
|
progressDisplayValue.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理进度输入失焦或回车
|
||||||
|
const handleProgressInputBlur = () => {
|
||||||
|
const value = progressDisplayValue.value
|
||||||
|
|
||||||
|
// 转换为数字
|
||||||
|
let progress = parseInt(value) || 0
|
||||||
|
|
||||||
|
// 数据验证:小于0或非数字显示0,大于100显示100
|
||||||
|
if (progress < 0 || isNaN(progress)) {
|
||||||
|
progress = 0
|
||||||
|
} else if (progress > 100) {
|
||||||
|
progress = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新数据和显示
|
||||||
|
formData.progress = progress
|
||||||
|
progressDisplayValue.value = progress.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理进度输入框聚焦(选中全部文本)
|
||||||
|
const handleProgressInputFocus = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
// 延迟选中,确保聚焦完成
|
||||||
|
setTimeout(() => {
|
||||||
|
target.select()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleProgressKeydown = (event: KeyboardEvent) => {
|
||||||
|
const key = event.key
|
||||||
|
|
||||||
|
// 允许的按键:数字、退格、删除、方向键、Tab等
|
||||||
|
const allowedKeys = [
|
||||||
|
'Backspace',
|
||||||
|
'Delete',
|
||||||
|
'Tab',
|
||||||
|
'Escape',
|
||||||
|
'Enter',
|
||||||
|
'ArrowLeft',
|
||||||
|
'ArrowRight',
|
||||||
|
'ArrowUp',
|
||||||
|
'ArrowDown',
|
||||||
|
'Home',
|
||||||
|
'End',
|
||||||
|
'Control',
|
||||||
|
'Meta',
|
||||||
|
'Alt',
|
||||||
|
'Shift',
|
||||||
|
]
|
||||||
|
|
||||||
|
// 允许数字
|
||||||
|
const isNumber = /^[0-9]$/.test(key)
|
||||||
|
const isAllowedKey = allowedKeys.includes(key)
|
||||||
|
|
||||||
|
// 如果是Ctrl+A、Ctrl+C、Ctrl+V、Ctrl+X等组合键,允许通过
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是允许的按键或数字,阻止输入
|
||||||
|
if (!isNumber && !isAllowedKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 错误信息
|
// 错误信息
|
||||||
const errors = reactive({
|
const errors = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -100,7 +191,7 @@ watch(
|
|||||||
// 抽屉显示时重新请求任务数据,确保前置任务列表是最新的
|
// 抽屉显示时重新请求任务数据,确保前置任务列表是最新的
|
||||||
window.dispatchEvent(new CustomEvent('request-task-list'))
|
window.dispatchEvent(new CustomEvent('request-task-list'))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听 isVisible 变化,同步到父组件
|
// 监听 isVisible 变化,同步到父组件
|
||||||
@@ -264,6 +355,15 @@ const showMessage = (message: string, type: 'success' | 'error') => {
|
|||||||
}
|
}
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听 formData.progress 变化,同步更新显示值
|
||||||
|
watch(
|
||||||
|
() => formData.progress,
|
||||||
|
newValue => {
|
||||||
|
progressDisplayValue.value = (newValue || 0).toString()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -422,11 +522,26 @@ const showMessage = (message: string, type: 'success' | 'error') => {
|
|||||||
v-model.number="formData.progress"
|
v-model.number="formData.progress"
|
||||||
type="range"
|
type="range"
|
||||||
class="progress-slider"
|
class="progress-slider"
|
||||||
|
:style="progressSliderStyle"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
step="5"
|
step="5"
|
||||||
/>
|
/>
|
||||||
<div class="progress-value">{{ formData.progress }}%</div>
|
<div class="progress-input-group">
|
||||||
|
<input
|
||||||
|
v-model="progressDisplayValue"
|
||||||
|
type="text"
|
||||||
|
class="progress-input"
|
||||||
|
placeholder="0"
|
||||||
|
maxlength="3"
|
||||||
|
@blur="handleProgressInputBlur"
|
||||||
|
@keyup.enter="handleProgressInputBlur"
|
||||||
|
@input="handleProgressInput"
|
||||||
|
@focus="handleProgressInputFocus"
|
||||||
|
@keydown="handleProgressKeydown"
|
||||||
|
/>
|
||||||
|
<span class="progress-unit">%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -653,8 +768,67 @@ const showMessage = (message: string, type: 'success' | 'error') => {
|
|||||||
outline: none;
|
outline: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
/* 确保input元素本身垂直居中 */
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 移除悬停时的高度变化动画,避免抖动 */
|
||||||
|
|
||||||
|
/* WebKit 浏览器的已完成部分样式 */
|
||||||
|
.progress-slider::-webkit-slider-runnable-track {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--gantt-primary, #409eff) 0%,
|
||||||
|
var(--gantt-primary, #409eff) var(--progress-percent, 0%),
|
||||||
|
var(--gantt-border-light, #e4e7ed) var(--progress-percent, 0%),
|
||||||
|
var(--gantt-border-light, #e4e7ed) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox 浏览器的已完成部分样式 */
|
||||||
|
.progress-slider::-moz-range-track {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--gantt-primary, #409eff) 0%,
|
||||||
|
var(--gantt-primary, #409eff) var(--progress-percent, 0%),
|
||||||
|
var(--gantt-border-light, #e4e7ed) var(--progress-percent, 0%),
|
||||||
|
var(--gantt-border-light, #e4e7ed) 100%
|
||||||
|
);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox 需要额外重置,确保轨道居中 */
|
||||||
|
.progress-slider::-moz-range-progress {
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: var(--gantt-primary, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-slider::-moz-range-track {
|
||||||
|
background: var(--gantt-border-light, #e4e7ed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为更好的兼容性,添加一个伪元素来显示已完成部分 */
|
||||||
|
.progress-slider::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 6px;
|
||||||
|
width: var(--progress-percent, 0%);
|
||||||
|
background: var(--gantt-primary, #409eff);
|
||||||
|
border-radius: 3px;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: width 0.2s ease; /* 只保留宽度变化的过渡效果 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除悬停时的高度变化,避免抖动 */
|
||||||
|
|
||||||
.progress-slider::-webkit-slider-thumb {
|
.progress-slider::-webkit-slider-thumb {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@@ -662,6 +836,15 @@ const showMessage = (message: string, type: 'success' | 'error') => {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--gantt-primary, #409eff);
|
background: var(--gantt-primary, #409eff);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: box-shadow 0.2s ease; /* 只保留阴影过渡效果 */
|
||||||
|
margin-top: -5px; /* 将滑块向上偏移,使其垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-slider::-webkit-slider-thumb:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-slider::-moz-range-thumb {
|
.progress-slider::-moz-range-thumb {
|
||||||
@@ -671,14 +854,90 @@ const showMessage = (message: string, type: 'success' | 'error') => {
|
|||||||
background: var(--gantt-primary, #409eff);
|
background: var(--gantt-primary, #409eff);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: box-shadow 0.2s ease; /* 只保留阴影过渡效果 */
|
||||||
|
margin-top: -5px; /* 将滑块向上偏移,使其垂直居中 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-value {
|
.progress-slider::-moz-range-thumb:hover {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除悬停和聚焦状态下的滑块位置调整,保持一致 */
|
||||||
|
|
||||||
|
/* 进度输入组样式 */
|
||||||
|
.progress-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--gantt-border-medium, #dcdfe6);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--gantt-bg-primary, white);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
width: 70px; /* 固定宽度,保持紧凑 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-input-group:hover {
|
||||||
|
border-color: var(--gantt-primary, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-input-group:focus-within {
|
||||||
|
border-color: var(--gantt-primary, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进度输入框样式 - 与其他input保持一致 */
|
||||||
|
.progress-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--gantt-text-primary, #303133);
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 12px 8px; /* 与其他input的padding保持一致 */
|
||||||
|
min-width: 0; /* 允许收缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-input::placeholder {
|
||||||
|
color: var(--gantt-text-placeholder, #c0c4cc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 百分号单位样式 */
|
||||||
|
.progress-unit {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
|
||||||
color: var(--gantt-text-secondary, #606266);
|
color: var(--gantt-text-secondary, #606266);
|
||||||
min-width: 40px;
|
padding: 12px 12px 12px 4px; /* 与input的padding匹配 */
|
||||||
text-align: right;
|
user-select: none;
|
||||||
|
flex-shrink: 0; /* 防止%号被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 暗黑模式样式 */
|
||||||
|
:global(html[data-theme='dark']) .progress-input-group {
|
||||||
|
border-color: var(--gantt-border-medium, #4c4d4f);
|
||||||
|
background: var(--gantt-bg-primary, #2b2b2b);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .progress-input-group:hover {
|
||||||
|
border-color: var(--gantt-primary, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .progress-input-group:focus-within {
|
||||||
|
border-color: var(--gantt-primary, #409eff);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .progress-input {
|
||||||
|
color: var(--gantt-text-primary, #e5eaf3);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .progress-input::placeholder {
|
||||||
|
color: var(--gantt-text-placeholder, #8b949e);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html[data-theme='dark']) .progress-unit {
|
||||||
|
color: var(--gantt-text-secondary, #a8a8a8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 抽屉底部 */
|
/* 抽屉底部 */
|
||||||
|
|||||||
Reference in New Issue
Block a user