Skip to content

Commit 5b07498

Browse files
committed
Coding gantt layout functions.
1 parent 03bad91 commit 5b07498

File tree

9 files changed

+185
-24
lines changed

9 files changed

+185
-24
lines changed

docs/TableDemo.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ const NAME_DICT = [{ title: '星航', value: 'xh', avatar: 'https://pic1.zhimg.c
1212
const STATS_DICT = [{ title: '初始化', value: 'init', color: '#43ad7f7f' }, { title: '进行中', value: 'progress' }, { title: '有风险', value: 'risk', color: '#be14807f' }, { title: '已完成', value: 'finish' }, { title: '已关闭', value: 'close' }]
1313
1414
const DATA: { [key: string]: any }[] = [
15-
{ no: 1, pno: null, name: 'v1.0优化任务集合', creator: 'xh', stats: ['init'], planStartTime: '2023-09-10' },
16-
{ no: 2, pno: null, name: '测试报告导出', creator: 'xh', stats: ['init'], planStartTime: '2023-09-14', planEndTime: '2024-01-30', actualStartTime: '2023-09-15', actualEndTime: '2023-09-24' },
17-
{ no: 3, pno: 1, name: '平台支持修改工程下默认分支', creator: 'xh', stats: ['progress', 'risk'], planStartTime: '2023-10-25', planEndTime: '2024-01-29' },
15+
{ no: 1, pno: null, name: 'v1.0优化任务集合', creator: 'xh', stats: ['init'], planStartTime: '2023-10-22', planEndTime: '2023-12-01' },
16+
{ no: 2, pno: null, name: '测试报告导出', creator: 'xh', stats: ['init'], planStartTime: '2023-10-14', planEndTime: '2024-01-01', actualStartTime: '2023-10-15', actualEndTime: '2023-11-24' },
17+
{ no: 3, pno: 1, name: '平台支持修改工程下默认分支', creator: 'xh', stats: ['progress', 'risk'], planStartTime: '2023-10-25', planEndTime: '2023-11-29' },
1818
{ no: 4, pno: 1, name: '工作项优化', creator: 'xh', stats: ['init'], planStartTime: '2023-10-26', planEndTime: '2023-11-25' },
1919
{ no: 5, pno: 1, name: '作业执行日志实时获取并增加搜索和支持定位', creator: 'xh', stats: ['init'], planStartTime: '2023-10-27', planEndTime: '2023-11-30' },
2020
{ no: 6, pno: null, name: '制品文件支持下载和删除', creator: 'xh', stats: ['init'], planStartTime: '2023-10-28', planEndTime: '2023-11-28' },

src/components/common/Menu.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const is_init = ref<boolean>(false)
99
const DIFF_OFFSET = 10
1010
1111
const contextmenuMathRandom = computed(() =>
12-
Math.floor(Math.random() * 1000000),
12+
IwUtils.getRandomString(12),
1313
)
1414
1515
async function showContextMenu(attachObj: HTMLElement | MouseEvent, offset: MenuOffsetKind = MenuOffsetKind.MEDIUM_TOP, size: MenuSizeKind | MenuCustomSize = MenuSizeKind.MEDIUM, force: boolean = false, boundaryEle?: HTMLElement,

src/components/conf.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as iconSvg from '../assets/icon'
22
import type { AggregateKind, TableColumnProps, TableCommonColumnProps, TableDataFilterProps, TableDataGroupProps, TableDataGroupResp, TableDataResp, TableDataSliceProps, TableDataSortProps, TableLayoutColumnProps, TableLayoutKernelProps, TableProps, TableStyleProps } from '../props'
33
import { DataKind, GanttShowKind, LayoutKind, OperatorKind, SizeKind, SubDataShowKind } from '../props'
4+
import { IwUtils } from '../utils'
45

56
export interface TableBasicConf {
67
id: string
@@ -357,7 +358,7 @@ export function initConf(props: TableProps): [TableBasicConf, TableLayoutConf[]]
357358
return convertTableColumnPropsToTableColumnConf(column)
358359
})
359360
const basicConf: TableBasicConf = {
360-
id: props.id ?? `iw-table${Math.floor(Math.random() * 1000000)}`,
361+
id: props.id ?? `iw-table${IwUtils.getRandomString(8)}`,
361362
pkColumnName: props.pkColumnName,
362363
parentPkColumnName: props.parentPkColumnName,
363364
columns,

src/components/eventbus.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import locales from '../locales'
44
import type { TableCellDictItemsResp, TableDataGroupResp, TableDataQuerySliceProps, TableDataResp, TableEventProps, TableLayoutKernelProps, TableLayoutModifyProps } from '../props'
55
import { SubDataShowKind } from '../props'
66

7+
import { IwUtils } from '../utils'
78
import { getParentWithClass } from '../utils/basic'
89
import { AlertKind, showAlert } from './common/Alert'
910
import { type TableBasicConf, type TableLayoutConf, type TableStyleConf, convertTableLayoutColumnPropsToTableLayoutColumnConf, convertTableLayoutKernelPropsToTableLayoutKernelConf } from './conf'
@@ -31,13 +32,27 @@ export async function watch() {
3132
}
3233

3334
const EVENT_EXECUTE_HANDLER: {
34-
loadDataAfter: ((data: TableDataResp | TableDataGroupResp[], layoutId: string) => Promise<void>)[]
35+
loadDataAfter: {
36+
id: string
37+
event: (data: TableDataResp | TableDataGroupResp[], layoutId: string) => Promise<void>
38+
}[]
3539
} = {
3640
loadDataAfter: [],
3741
}
3842

39-
export function registerLoadDataAfterEvent(event: (data: TableDataResp | TableDataGroupResp[], layoutId: string) => Promise<void>) {
40-
EVENT_EXECUTE_HANDLER.loadDataAfter.push(event)
43+
export function registerLoadDataAfterEvent(event: (data: TableDataResp | TableDataGroupResp[], layoutId: string) => Promise<void>): string {
44+
const id = `iw-table-load-data-after-${IwUtils.getRandomString(12)}`
45+
EVENT_EXECUTE_HANDLER.loadDataAfter.push({
46+
id,
47+
event,
48+
})
49+
return id
50+
}
51+
52+
export function unregisterLoadDataAfterEvent(id: string) {
53+
const index = EVENT_EXECUTE_HANDLER.loadDataAfter.findIndex(item => item.id === id)
54+
if (index !== -1)
55+
EVENT_EXECUTE_HANDLER.loadDataAfter.splice(index, 1)
4156
}
4257

4358
// -------------------
@@ -134,8 +149,8 @@ export async function loadData(moreForGroupedValue?: any, returnOnlyAggs?: boole
134149
showAlert(t('_.event.loadDataInvalidScene'), 2, AlertKind.ERROR, getParentWithClass(document.getElementById(`iw-tt-layout-${layout.id}`), 'iw-tt')!)
135150
throw new Error('[events.loadData] Invalid scene')
136151
}
137-
EVENT_EXECUTE_HANDLER.loadDataAfter.forEach(async (event) => {
138-
await event(layout.data!, layout.id)
152+
EVENT_EXECUTE_HANDLER.loadDataAfter.forEach(async (item) => {
153+
await item.event(layout.data!, layout.id)
139154
})
140155
}
141156

src/components/function/RowTree.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ import { getParentWithClass } from '../../utils/basic'
44

55
export const NODE_DEPTH_FLAG = '__node_depth'
66

7+
const EVENT_EXECUTE_HANDLER: {
8+
id: string
9+
event: (dataPk: any, hide: boolean) => Promise<void>
10+
}[] = []
11+
12+
export function registerRowTreeTriggerEvent(event: (dataPk: any, hide: boolean) => Promise<void>): string {
13+
const id = `iw-row-tree-event-${IwUtils.getRandomString(12)}`
14+
EVENT_EXECUTE_HANDLER.push({
15+
id,
16+
event,
17+
})
18+
return id
19+
}
20+
21+
export function unregisterRowTreeTriggerEvent(id: string) {
22+
const index = EVENT_EXECUTE_HANDLER.findIndex(item => item.id === id)
23+
if (index !== -1)
24+
EVENT_EXECUTE_HANDLER.splice(index, 1)
25+
}
26+
727
export function sortByTree(data: any[], pkColumnName: string, parentPkColumnName?: string) {
828
if (parentPkColumnName === undefined)
929
return data
@@ -44,12 +64,13 @@ export function renderTreeToggleHandler(hasSubData: boolean): string {
4464
return `${hasSubData ? `<i class="${iconSvg.SHRINK} cursor-pointer" />` : ``}`
4565
}
4666

47-
export function registerTreeRowToggleListener(rowsEle: HTMLDivElement) {
67+
export function registerTreeRowToggleListener(rowsEle: HTMLElement) {
4868
IwUtils.delegateEvent(rowsEle, 'click', `.${iconSvg.EXPAND}`, (e) => {
4969
const ele = e.target as HTMLElement
5070
const currPk = getParentWithClass(ele, 'iw-data-row')!.dataset.pk!
5171
rowsEle.querySelectorAll(`.iw-data-row[data-parent-pk="${currPk}"]`).forEach((node) => {
5272
(node as HTMLElement).style.display = 'flex'
73+
EVENT_EXECUTE_HANDLER.forEach(item => item.event((node as HTMLElement).dataset.pk, false))
5374
})
5475
ele.classList.remove(iconSvg.EXPAND)
5576
ele.classList.add(iconSvg.SHRINK)
@@ -61,6 +82,7 @@ export function registerTreeRowToggleListener(rowsEle: HTMLDivElement) {
6182
recursionShrinkRows(rowsEle, currPk)
6283
ele.classList.remove(iconSvg.SHRINK)
6384
ele.classList.add(iconSvg.EXPAND)
85+
EVENT_EXECUTE_HANDLER.forEach(item => item.event(currPk, false))
6486
e.stopImmediatePropagation()
6587
})
6688

@@ -72,6 +94,7 @@ export function registerTreeRowToggleListener(rowsEle: HTMLDivElement) {
7294
shrinkEle.classList.add(iconSvg.EXPAND)
7395
}
7496
(node as HTMLElement).style.display = 'none'
97+
EVENT_EXECUTE_HANDLER.forEach(item => item.event((node as HTMLElement).dataset.pk, true))
7598
recursionShrinkRows(rowsEle, (node as HTMLElement).dataset.pk)
7699
})
77100
}

src/components/layout/gantt/Gantt.vue

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script setup lang="ts">
22
import type { Ref } from 'vue'
3-
import { onMounted, ref } from 'vue'
3+
import { onMounted, onUnmounted, ref } from 'vue'
4+
import * as iconSvg from '../../../assets/icon'
45
import locales from '../../../locales'
5-
import type { TableDataGroupResp, TableDataResp, TableLayoutModifyProps } from '../../../props'
6+
import { GanttShowKind, type TableDataGroupResp, type TableDataResp, type TableLayoutModifyProps, translateGanttShowKind } from '../../../props'
67
import { getParentWithClass } from '../../../utils/basic'
78
import { AlertKind, showAlert } from '../../common/Alert'
9+
import MenuComp, { MenuOffsetKind, MenuSizeKind } from '../../common/Menu.vue'
810
import type { TableBasicConf, TableLayoutConf } from '../../conf'
911
import * as eb from '../../eventbus'
1012
import ColumnResizeComp from '../../function/ColumnResize.vue'
@@ -28,6 +30,8 @@ const ganttTimelineRef: Ref<HTMLElement | null> = ref(null)
2830
const ganttWith: Ref<number> = ref(0)
2931
const ganttInfo: Ref<GanttInfo | null> = ref(null)
3032
33+
const showKindCompRef = ref<InstanceType<typeof MenuComp>>()
34+
3135
async function generateGanttInfo(data: TableDataResp | TableDataGroupResp[]) {
3236
if ((props.layout.ganttPlanStartTimeColumnName === undefined || props.layout.ganttPlanEndTimeColumnName === undefined)
3337
&& (props.layout.ganttActualStartTimeColumnName === undefined || props.layout.ganttActualEndTimeColumnName === undefined)) {
@@ -103,7 +107,7 @@ onMounted(() => {
103107
})
104108
})
105109
106-
eb.registerLoadDataAfterEvent(async (data: TableDataResp | TableDataGroupResp[], layoutId: string) => {
110+
const loadDataEventId = eb.registerLoadDataAfterEvent(async (data: TableDataResp | TableDataGroupResp[], layoutId: string) => {
107111
if (props.layout.id !== layoutId) {
108112
return
109113
}
@@ -116,12 +120,23 @@ async function setNewWidth(newWidth: number, _itemId?: string) {
116120
}
117121
await eb.modifyLayout(changedLayoutReq)
118122
}
123+
124+
async function changeShowKind(showKind: GanttShowKind) {
125+
const changedLayoutReq: TableLayoutModifyProps = {
126+
ganttShowKind: showKind,
127+
}
128+
await eb.modifyLayout(changedLayoutReq)
129+
}
130+
131+
onUnmounted(() => {
132+
eb.unregisterLoadDataAfterEvent(loadDataEventId)
133+
})
119134
</script>
120135

121136
<template>
122137
<div
123138
ref="ganttRef"
124-
class="iw-gantt flex h-full"
139+
class="iw-gantt flex h-full relative"
125140
>
126141
<div ref="ganttListRef" class="overflow-y-hidden overflow-x-auto" :style="`width: ${ganttWith - props.layout.ganttTimelineWidth}px`">
127142
<ListComp :layout="props.layout" :basic="props.basic" />
@@ -180,6 +195,39 @@ async function setNewWidth(newWidth: number, _itemId?: string) {
180195
</div>
181196
<ColumnResizeComp resize-item-class="iw-gantt-timeline-container" handle-left :set-size="setNewWidth" />
182197
</div>
198+
<button
199+
class="iw-btn iw-btn-outline iw-btn-xs absolute right-1 top-1 z-[1600]"
200+
@click="(e) => { showKindCompRef?.show(e.target as HTMLElement, MenuOffsetKind.RIGHT_TOP, MenuSizeKind.MINI) }"
201+
>
202+
<span class="mr-0.5">{{ translateGanttShowKind(props.layout.ganttShowKind) }}</span>
203+
<i :class="`${iconSvg.CHEVRON_DOWN} ml-0.5`" />
204+
<MenuComp ref="showKindCompRef">
205+
<div
206+
class="p-2 hover:cursor-pointer text-xs"
207+
@click="changeShowKind(GanttShowKind.DAY)"
208+
>
209+
{{ translateGanttShowKind(GanttShowKind.DAY) }}
210+
</div>
211+
<div
212+
class="p-2 hover:cursor-pointer text-xs"
213+
@click="changeShowKind(GanttShowKind.WEEK)"
214+
>
215+
{{ translateGanttShowKind(GanttShowKind.WEEK) }}
216+
</div>
217+
<div
218+
class="p-2 hover:cursor-pointer text-xs"
219+
@click="changeShowKind(GanttShowKind.MONTH)"
220+
>
221+
{{ translateGanttShowKind(GanttShowKind.MONTH) }}
222+
</div>
223+
<div
224+
class="p-2 hover:cursor-pointer text-xs"
225+
@click="changeShowKind(GanttShowKind.YEAR)"
226+
>
227+
{{ translateGanttShowKind(GanttShowKind.YEAR) }}
228+
</div>
229+
</MenuComp>
230+
</button>
183231
</div>
184232
</template>
185233

src/components/layout/gantt/GanttTimelineHeader.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<script setup lang="ts">
2+
23
import { GanttShowKind } from '../../../props'
34
import { IwUtils } from '../../../utils'
5+
46
import type { TableStyleConf } from '../../conf'
57
import type { GanttInfo, TimelineInfo } from './gantt'
68
import { TIMELINE_COLUMN_WIDTH } from './gantt'

src/components/layout/gantt/GanttTimelineRows.vue

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<script setup lang="ts">
22
import type { Dayjs } from 'dayjs'
33
import dayjs from 'dayjs'
4-
import { nextTick, onMounted, ref, watch } from 'vue'
4+
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
55
import { GanttShowKind, SubDataShowKind } from '../../../props'
66
77
import type { TableLayoutConf, TableStyleConf } from '../../conf'
8+
import { registerRowTreeTriggerEvent, unregisterRowTreeTriggerEvent } from '../../function/RowTree'
89
import type { GanttInfo } from './gantt'
910
import { TIMELINE_COLUMN_WIDTH } from './gantt'
1011
@@ -52,6 +53,44 @@ function setTimelineBar(barEle: HTMLElement, timelineRowEle: HTMLElement, plan:
5253
barEle.style.display = `block`
5354
}
5455
56+
function drawDataRelLine(globalOffsetTop: number, currTimelineEle: HTMLElement, parentTimelineEle: HTMLElement) {
57+
function doDrawDataRelLine(x1: number, y1: number, x2: number, y2: number) {
58+
const svgEle = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
59+
svgEle.style.position = 'absolute'
60+
svgEle.style.top = `${0}px`
61+
svgEle.style.left = `${0}px`
62+
svgEle.style.width = '100%'
63+
svgEle.style.height = '100%'
64+
svgEle.style.pointerEvents = 'none'
65+
ganttTimelineRef.value!.appendChild(svgEle)
66+
67+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
68+
const curve = `M ${x1},${y1} C ${(x1 + x2) / 2},${y1} ${(x1 + x2) / 2},${y2} ${x2},${y2}`
69+
path.setAttribute('d', curve)
70+
path.setAttribute('stroke', 'black')
71+
path.setAttribute('fill', 'transparent')
72+
svgEle.appendChild(path)
73+
}
74+
const hasStartTime = currTimelineEle.dataset.startTime && parentTimelineEle.dataset.startTime
75+
const hasEndTime = currTimelineEle.dataset.endTime && parentTimelineEle.dataset.endTime
76+
const offsetHeight = globalOffsetTop - (currTimelineEle.getBoundingClientRect().top - parentTimelineEle.getBoundingClientRect().top) + currTimelineEle.offsetHeight
77+
if (hasStartTime) {
78+
const x1 = currTimelineEle.offsetLeft
79+
const y1 = globalOffsetTop + currTimelineEle.offsetTop + currTimelineEle.offsetHeight / 2
80+
const x2 = parentTimelineEle.offsetLeft + 2
81+
const y2 = offsetHeight + currTimelineEle.offsetHeight / 2
82+
83+
doDrawDataRelLine(x1, y1, x2, y2)
84+
}
85+
if (hasEndTime) {
86+
const x1 = currTimelineEle.offsetLeft + currTimelineEle.offsetWidth
87+
const y1 = globalOffsetTop + currTimelineEle.offsetTop + currTimelineEle.offsetHeight / 2
88+
const x2 = parentTimelineEle.offsetLeft + parentTimelineEle.offsetWidth - 2
89+
const y2 = offsetHeight + currTimelineEle.offsetHeight / 2
90+
doDrawDataRelLine(x1, y1, x2, y2)
91+
}
92+
}
93+
5594
function generateTimelineBar() {
5695
ganttTimelineRef.value?.querySelectorAll('.iw-gantt-timeline-plan-bar').forEach((ele) => {
5796
const barEle = ele as HTMLElement
@@ -69,25 +108,58 @@ function generateTimelineBar() {
69108
})
70109
}
71110
111+
function generateDataRelLine() {
112+
ganttTimelineRef.value!.querySelectorAll('svg').forEach((ele) => {
113+
ele.remove()
114+
})
115+
if (props.subDataShowKind === SubDataShowKind.FOLD_SUB_DATA) {
116+
ganttTimelineRef.value?.querySelectorAll('.iw-gantt-timeline-row[data-parent-pk]').forEach((ele) => {
117+
const currRowEle = ele as HTMLElement
118+
if (currRowEle.style.display === 'none') {
119+
return
120+
}
121+
const parentRowEle = ganttTimelineRef.value?.querySelector(`.iw-gantt-timeline-row[data-pk="${currRowEle.dataset.parentPk!}"]`)
122+
if (parentRowEle) {
123+
drawDataRelLine(currRowEle.offsetTop, currRowEle.querySelector('.iw-gantt-timeline-plan-bar')!, parentRowEle.querySelector('.iw-gantt-timeline-plan-bar')!)
124+
}
125+
})
126+
}
127+
}
128+
72129
watch(() => props.ganttInfo, () => {
73130
nextTick(() => {
74131
generateTimelineBar()
132+
generateDataRelLine()
75133
})
76134
})
77135
136+
let rowTreeEventId: string | null = null
78137
onMounted(() => {
79138
generateTimelineBar()
139+
generateDataRelLine()
140+
141+
rowTreeEventId = registerRowTreeTriggerEvent(async (dataPk, hide) => {
142+
if (ganttTimelineRef.value) {
143+
const rowEle = ganttTimelineRef.value?.querySelector(`.iw-gantt-timeline-row[data-pk="${dataPk}"]`) as HTMLElement
144+
rowEle.style.display = hide ? 'none' : 'flex'
145+
generateDataRelLine()
146+
}
147+
})
148+
})
149+
150+
onUnmounted(() => {
151+
unregisterRowTreeTriggerEvent(rowTreeEventId!)
80152
})
81153
</script>
82154

83155
<template>
84-
<div ref="ganttTimelineRef">
156+
<div ref="ganttTimelineRef" class="relative">
85157
<div
86158
v-for="row in props.records"
87-
:key="`${layout.id}-${row[props.pkColumnName]}-${props.subDataShowKind}`"
88-
:data-pk="row[props.pkColumnName] "
159+
:key="`${layout.id}-${row[props.pkColumnName]}`"
160+
:data-pk="row[props.pkColumnName]"
89161
:data-parent-pk="props.parentPkColumnName ? row[props.parentPkColumnName] : undefined"
90-
:class="`${props.styleConf.rowClass} iw-gantt-timeline-row ${props.subDataShowKind === SubDataShowKind.FOLD_SUB_DATA ? 'iw-data-fold' : ''} flex bg-base-100 border-b border-b-base-300 border-r border-r-base-300`"
162+
:class="`${props.styleConf.rowClass} relative iw-gantt-timeline-row flex bg-base-100 border-b border-b-base-300 border-r border-r-base-300`"
91163
>
92164
<div
93165
v-for="(timeline, idx) in ganttInfo.timeline" :key="`${layout.id}-${idx}`"

0 commit comments

Comments
 (0)