@@ -1,7 +1,7 @@
< script setup lang = "ts" >
import { computed , nextTick , onMounted , ref , watch } from "vue" ;
import { useRoute , useRouter } from "vue-router" ;
import { ChevronDown , ChevronRight , RefreshCw , ZoomIn , ZoomOut } from "lucide-vue-next" ;
import { ArrowLeft , ChevronDown , ChevronRight , RefreshCw , ZoomIn , ZoomOut } from "lucide-vue-next" ;
import { tasktreeAPI , type TaskTreeGroup , type TaskTreeGroupOverview , type TaskTreeTask , type TaskTreeTimeline } from "@/api/tasktree" ;
import AsyncBoundary from "@/components/common/AsyncBoundary.vue" ;
import BaseDialog from "@/components/common/BaseDialog.vue" ;
@@ -337,6 +337,21 @@ function barStyle(item: { startAt: string | null; dueAt: string | null }) {
const rightEdge = Math . min ( 100 , Math . max ( left , ( end - rangeStart ) / 86400000 * unit ) ) ;
return { right : ` ${ 100 - rightEdge } % ` , width : ` ${ Math . max ( 0 , rightEdge - left ) } % ` } ;
}
function overviewItemTotal ( item : TaskTreeGroupOverview ) {
return item . taskCount + item . subtaskCount + item . subsubtaskCount ;
}
function overviewCompletion ( item : TaskTreeGroupOverview ) {
const total = overviewItemTotal ( item ) ;
return total > 0 ? Math . round ( item . completedCount * 100 / total ) : 0 ;
}
function groupBarStyle ( item : TaskTreeGroupOverview ) {
return { ... barStyle ( item ) , "--completion" : ` ${ overviewCompletion ( item ) } % ` } ;
}
function groupBarClass ( item : TaskTreeGroupOverview ) {
const start = displaySerial ( new Date ( item . startAt || item . dueAt || range . value . start ) ) ;
const rangeStart = displaySerial ( range . value . start ) ;
return { "label-after" : ( start - rangeStart ) / 86400000 / range . value . dayCount < 0.12 } ;
}
function trackStyle ( ) {
return {
backgroundImage : ` ${ weekendWash . value } , linear-gradient(to right, #edf1f0 1px, transparent 1px) ` ,
@@ -420,6 +435,7 @@ function syncSelectedTaskFromRoute() {
< div class = "tasktree-context-copy" >
< div class = "tasktree-product-title" > < span > 项目时间线 < / span > < strong > TaskTree < / strong > < / div >
< span class = "tasktree-context-separator" aria -hidden = " true " / >
< RouterLink v-if = "!isGlobalView" class="icon-button tasktree-back-link" to="/projects/tasktree" title="返回全部 TaskGroups" aria-label="返回全部 TaskGroups"><ArrowLeft :size="16" aria-hidden="true" / > < / RouterLink >
< div v-if = "isGlobalView" class="tasktree-current-group" > < strong > 全局 TaskGroups < / strong > < p > 跨项目时间范围与工作量概览 < / p > < / div >
< div v-else-if = "timeline" class="tasktree-current-group" > < strong > { { timeline . group . name } } < / strong > < p > { { timeline . group . description || '未填写 taskgroup 说明' } } < / p > < / div >
< / div >
@@ -525,12 +541,15 @@ function syncSelectedTaskFromRoute() {
< button
v-if = "item.startAt || item.dueAt"
class = "tasktree-group-bar"
:style = "barStyle (item)"
:class = "groupBarClass (item)"
:style = "groupBarStyle(item)"
type = "button"
: aria -label = " ` 打开 $ { item.group.name } ` "
: aria -label = " ` 打开 $ { item.group.name } , 已完成 $ { item.completedCount } / $ { overviewItemTotal ( item ) } , 完成度 $ { overviewCompletion ( item ) } % ` "
@click ="openGroup(item.group.id)"
>
< span > { { item . taskCount + item . subtaskCount + item . subsubtaskCount } } 项 < / span >
< span class = "tasktree-group-progress" aria -hidden = " true " / >
< span class = "tasktree-group-progress-label inside" > < strong > { { item . completedCount } } / { { overviewItemTotal ( item ) } } < / strong > < small > { { overviewCompletion ( item ) } } % < / small > < / span >
< span class = "tasktree-group-progress-label outside" aria -hidden = " true " > { { item . completedCount } } / { { overviewItemTotal ( item ) } } < / span >
< / button >
< / div >
< / div >
@@ -605,6 +624,8 @@ function syncSelectedTaskFromRoute() {
. tasktree - current - group strong { flex : 0 1 auto ; overflow : hidden ; color : var ( -- text - primary ) ; font - size : 13 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. tasktree - current - group p { min - width : 0 ; margin : 0 ; overflow : hidden ; color : var ( -- text - secondary ) ; font - size : 10 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. tasktree - context - actions { display : flex ; flex : 0 0 auto ; align - items : center ; gap : 7 px ; }
. tasktree - back - link { width : 30 px ; height : 30 px ; min - height : 30 px ; flex : 0 0 30 px ; color : var ( -- text - secondary ) ; }
. tasktree - back - link : hover { color : var ( -- text - primary ) ; }
. tasktree - view - layout . docked . tasktree - current - group { max - width : 190 px ; }
. tasktree - view - layout . docked . tasktree - current - group p { display : none ; }
. tasktree - view - layout . docked . tasktree - current - group strong { max - width : 190 px ; }
@@ -690,9 +711,19 @@ function syncSelectedTaskFromRoute() {
. tasktree - bar . is - summary : : after { position : absolute ; top : 0 ; width : 2 px ; height : 8 px ; border - radius : 0 0 1 px 1 px ; background : inherit ; content : "" ; }
. tasktree - bar . is - summary : : before { left : 0 ; }
. tasktree - bar . is - summary : : after { right : 0 ; }
. tasktree - group - bar { position : absolute ; top : 16 px ; height : 36 px ; min - width : 28 px ; overflow : hidden ; border : 1 px solid # 135 b94 ; border - radius : 4 px ; background : # 176 ba8 ; color : white ; box - shadow : inset 0 1 px 0 rgb ( 255 255 255 / 22 % ) , 0 2 px 5 px rgb ( 17 67 101 / 20 % ) ; cursor : pointer ; }
. tasktree - group - bar : hover { background : # 125 d95 ; }
. tasktree - group - bar span { display : block ; padding : 0 10 px ; overflow : hidden ; font - size : 11 px ; font - weight : 700 ; line - height : 34 px ; text - overflow : ellipsis ; white - space : nowrap ; }
. tasktree - group - bar { container - type : inline - size ; position : absolute ; top : 16 px ; height : 36 px ; min - width : 28 px ; overflow : visible ; border : 1 px solid # 4 e6674 ; border - radius : 4 px ; background : # 667 d8b ; color : # fff ; box - shadow : inset 0 1 px 0 rgb ( 255 255 255 / 28 % ) , 0 2 px 5 px rgb ( 28 48 58 / 16 % ) ; cursor : pointer ; isolation : isolate ; }
. tasktree - group - bar : hover { border - color : # 4 f6976 ; box - shadow : inset 0 1 px 0 rgb ( 255 255 255 / 68 % ) , 0 3 px 8 px rgb ( 28 48 58 / 22 % ) ; }
. tasktree - group - progress { position : absolute ; inset : 0 auto 0 0 ; z - index : - 1 ; width : var ( -- completion ) ; overflow : hidden ; border - radius : 3 px 0 0 3 px ; background : linear - gradient ( 90 deg , # 276 d50 , # 4 c9470 ) ; transition : width 180 ms ease ; }
. tasktree - group - progress - label { position : relative ; z - index : 1 ; align - items : baseline ; white - space : nowrap ; }
. tasktree - group - progress - label . inside { display : flex ; height : 34 px ; justify - content : center ; gap : 7 px ; padding : 0 10 px ; line - height : 34 px ; text - shadow : 0 1 px 1 px rgb ( 20 42 31 / 55 % ) ; }
. tasktree - group - progress - label . inside strong { font - size : 11 px ; font - weight : 750 ; }
. tasktree - group - progress - label . inside small { font - family : var ( -- console - font - mono ) ; font - size : 9 px ; opacity : 0.82 ; }
. tasktree - group - progress - label . outside { position : absolute ; top : 50 % ; right : calc ( 100 % + 8 px ) ; display : none ; padding : 2 px 4 px ; transform : translateY ( - 50 % ) ; background : rgb ( 255 255 255 / 88 % ) ; color : # 41545 d ; font - family : var ( -- console - font - mono ) ; font - size : 10 px ; font - weight : 700 ; line - height : 16 px ; }
. tasktree - group - bar . label - after . tasktree - group - progress - label . outside { right : auto ; left : calc ( 100 % + 8 px ) ; }
@ container ( max - width : 96 px ) {
. tasktree - group - progress - label . inside { display : none ; }
. tasktree - group - progress - label . outside { display : block ; }
}
. tasktree - bar . status - completed { border - color : rgb ( 29 111 76 / 62 % ) ; background : linear - gradient ( 90 deg , # 277 d5a , # 4 a9a72 ) ; }
. tasktree - bar . status - blocked { border - color : rgb ( 144 43 50 / 62 % ) ; background : linear - gradient ( 90 deg , # a83c45 , # c55a60 ) ; }
. tasktree - bar . status - pending { border - color : rgb ( 79 92 102 / 58 % ) ; background : linear - gradient ( 90 deg , # 63717 b , # 81909 a ) ; }