This commit is contained in:
oneclawbot-prog 2026-06-29 15:36:31 +08:00 committed by GitHub
commit 5eeb23cad7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 116 additions and 3 deletions

View File

@ -17,6 +17,14 @@
:project-id="projectId"
@update:modelValue="loadTasks()"
/>
<button
class="collapse-all-btn"
:class="{ 'is-collapsed': collapseAll }"
@click="toggleCollapseAll"
:title="collapseAll ? 'Expand all subtasks' : 'Collapse all subtasks'"
>
<Icon icon="chevron-down" />
</button>
</div>
</template>
@ -101,7 +109,7 @@
<script setup lang="ts">
import {ref, computed, nextTick, onMounted, onBeforeUnmount, watch, toRef} from 'vue'
import {ref, computed, nextTick, onMounted, onBeforeUnmount, watch, toRef, provide} from 'vue'
import draggable from 'zhyswan-vuedraggable'
import ProjectWrapper from '@/components/project/ProjectWrapper.vue'
@ -143,6 +151,14 @@ const ctaVisible = ref(false)
const drag = ref(false)
// Collapse all subtasks
const collapseAll = ref(false)
provide('collapseAll', collapseAll)
function toggleCollapseAll() {
collapseAll.value = !collapseAll.value
}
const {
tasks: allTasks,
loading,
@ -368,6 +384,32 @@ onBeforeUnmount(() => {
}
}
.collapse-all-btn {
display: flex;
align-items: center;
justify-content: center;
inline-size: 32px;
block-size: 32px;
border: 1px solid var(--grey-300);
background: var(--white);
color: var(--grey-500);
cursor: pointer;
border-radius: $radius;
transition: all 0.2s ease;
&:hover {
color: var(--grey-700);
background: var(--grey-100);
border-color: var(--grey-400);
}
&.is-collapsed {
color: var(--primary);
border-color: var(--primary);
transform: rotate(-90deg);
}
}
.tasks {
padding: .5rem;
}

View File

@ -12,6 +12,15 @@
@click="openTaskDetail"
@keyup.enter="openTaskDetail"
>
<button
v-if="task.relatedTasks?.subtask?.length"
class="collapse-toggle collapse-toggle-left"
:class="{ 'is-collapsed': isCollapsed }"
@click.stop="toggleCollapse"
:aria-label="isCollapsed ? 'Expand subtasks' : 'Collapse subtasks'"
>
<Icon icon="chevron-down" />
</button>
<span
v-tooltip="!canMarkAsDone ? $t('task.readOnlyCheckbox') : ''"
class="is-inline-flex is-align-items-center"
@ -178,8 +187,17 @@
/>
</BaseButton>
<slot />
<button
v-if="task.relatedTasks?.subtask?.length"
class="collapse-toggle"
:class="{ 'is-collapsed': isCollapsed }"
@click.stop="toggleCollapse"
:aria-label="isCollapsed ? 'Expand subtasks' : 'Collapse subtasks'"
>
<Icon icon="chevron-down" />
</button>
</div>
<template v-if="typeof task.relatedTasks?.subtask !== 'undefined'">
<template v-if="task.relatedTasks?.subtask?.length && !isCollapsed">
<template v-for="subtask in task.relatedTasks.subtask">
<template v-if="getTaskById(subtask.id)">
<single-task-in-project
@ -197,7 +215,7 @@
</template>
<script setup lang="ts">
import {ref, watch, shallowReactive, onMounted, computed} from 'vue'
import {ref, watch, shallowReactive, onMounted, computed, inject, type Ref} from 'vue'
import {useI18n} from 'vue-i18n'
import TaskModel, {getHexColor} from '@/models/task'
@ -246,6 +264,24 @@ const props = withDefaults(defineProps<{
allTasks: () => [],
})
// Collapse state
const isCollapsed = ref(false)
const collapseAll = inject<Ref<boolean>>('collapseAll', ref(false))
// Watch for global collapse/expand all
watch(collapseAll, (newVal) => {
isCollapsed.value = newVal
})
const hasSubtasks = computed(() => {
return typeof task.value.relatedTasks?.subtask !== 'undefined' &&
task.value.relatedTasks.subtask.length > 0
})
function toggleCollapse() {
isCollapsed.value = !isCollapsed.value
}
const emit = defineEmits<{
'taskUpdated': [task: ITask],
}>()
@ -605,6 +641,41 @@ defineExpose({
margin-inline-start: 1.75rem;
}
.collapse-toggle {
display: flex;
align-items: center;
justify-content: center;
inline-size: 20px;
block-size: 20px;
border: none;
background: transparent;
color: var(--grey-400);
cursor: pointer;
border-radius: $radius;
transition: transform 0.2s ease, color 0.2s ease;
margin-inline-start: 0.5rem;
&:hover {
color: var(--grey-600);
background: var(--grey-100);
}
&.is-collapsed {
transform: rotate(-90deg);
}
}
.collapse-toggle-left {
margin-inline-start: 0;
margin-inline-end: 0.5rem;
order: -1;
//
@media (max-width: 768px) {
display: none;
}
}
:deep(.popup) {
border-radius: $radius;
background-color: var(--white);