Devosend
3 years ago
committed by
GitHub
8 changed files with 401 additions and 3 deletions
@ -0,0 +1,214 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import { defineComponent, ref, PropType } from 'vue' |
||||||
|
import * as echarts from 'echarts' |
||||||
|
import type { Ref } from 'vue' |
||||||
|
import { useI18n } from 'vue-i18n' |
||||||
|
import initChart from '@/components/chart' |
||||||
|
import { tasksState } from '@/utils/common' |
||||||
|
import { format } from 'date-fns' |
||||||
|
import { parseTime } from '@/utils/common' |
||||||
|
import { ISeriesData } from '../type' |
||||||
|
|
||||||
|
const props = { |
||||||
|
height: { |
||||||
|
type: [String, Number] as PropType<string | number>, |
||||||
|
default: window.innerHeight - 174 |
||||||
|
}, |
||||||
|
width: { |
||||||
|
type: [String, Number] as PropType<string | number>, |
||||||
|
default: '100%' |
||||||
|
}, |
||||||
|
seriesData: { |
||||||
|
type: Array as PropType<Array<any>>, |
||||||
|
default: () => [] |
||||||
|
}, |
||||||
|
taskList: { |
||||||
|
type: Array as PropType<Array<string>>, |
||||||
|
default: [] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const GanttChart = defineComponent({ |
||||||
|
name: 'GanttChart', |
||||||
|
props, |
||||||
|
setup(props) { |
||||||
|
const graphChartRef: Ref<HTMLDivElement | null> = ref(null) |
||||||
|
const { t } = useI18n() |
||||||
|
|
||||||
|
const state = tasksState(t) |
||||||
|
|
||||||
|
const data: ISeriesData = {} |
||||||
|
Object.keys(state).forEach((key) => (data[key] = [])) |
||||||
|
const series = Object.keys(state).map((key) => ({ |
||||||
|
id: key, |
||||||
|
type: 'custom', |
||||||
|
name: state[key].desc, |
||||||
|
renderItem: renderItem, |
||||||
|
itemStyle: { |
||||||
|
opacity: 0.8, |
||||||
|
color: state[key].color, |
||||||
|
color0: state[key].color |
||||||
|
}, |
||||||
|
encode: { |
||||||
|
x: [1, 2], |
||||||
|
y: 0 |
||||||
|
}, |
||||||
|
data: data[key] |
||||||
|
})) |
||||||
|
|
||||||
|
// format series data
|
||||||
|
let minTime = Number(new Date()) |
||||||
|
props.seriesData.forEach(function (task, index) { |
||||||
|
minTime = minTime < task.startDate[0] ? minTime : task.startDate[0] |
||||||
|
data[task.status].push({ |
||||||
|
name: task.name, |
||||||
|
value: [ |
||||||
|
index, |
||||||
|
task.startDate[0], |
||||||
|
task.endDate[0], |
||||||
|
task.endDate[0] - task.startDate[0] |
||||||
|
], |
||||||
|
itemStyle: { |
||||||
|
color: state[task.status].color |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
// customer render
|
||||||
|
function renderItem(params: any, api: any) { |
||||||
|
const taskIndex = api.value(0) |
||||||
|
const start = api.coord([api.value(1), taskIndex]) |
||||||
|
const end = api.coord([api.value(2), taskIndex]) |
||||||
|
const height = api.size([0, 1])[1] * 0.6 |
||||||
|
|
||||||
|
const rectShape = echarts.graphic.clipRectByRect( |
||||||
|
{ |
||||||
|
x: start[0], |
||||||
|
y: start[1] - height / 2, |
||||||
|
width: end[0] - start[0], |
||||||
|
height: height |
||||||
|
}, |
||||||
|
{ |
||||||
|
x: params.coordSys.x, |
||||||
|
y: params.coordSys.y, |
||||||
|
width: params.coordSys.width, |
||||||
|
height: params.coordSys.height |
||||||
|
} |
||||||
|
) |
||||||
|
return ( |
||||||
|
rectShape && { |
||||||
|
type: 'rect', |
||||||
|
transition: ['shape'], |
||||||
|
shape: rectShape, |
||||||
|
style: api.style() |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const option = { |
||||||
|
title: { |
||||||
|
text: t('project.workflow.task_state'), |
||||||
|
textStyle: { |
||||||
|
fontWeight: 'normal', |
||||||
|
fontSize: 14 |
||||||
|
}, |
||||||
|
left: 50 |
||||||
|
}, |
||||||
|
tooltip: { |
||||||
|
formatter: function (params: any) { |
||||||
|
const taskName = params.data.name |
||||||
|
const data = props.seriesData.filter( |
||||||
|
(item) => item.taskName === taskName |
||||||
|
) |
||||||
|
let str = `taskName : ${taskName}</br>` |
||||||
|
str += `status : ${state[data[0].status].desc} (${ |
||||||
|
data[0].status |
||||||
|
})</br>` |
||||||
|
str += `startTime : ${data[0].isoStart}</br>` |
||||||
|
str += `endTime : ${data[0].isoEnd}</br>` |
||||||
|
str += `duration : ${data[0].duration}</br>` |
||||||
|
return str |
||||||
|
} |
||||||
|
}, |
||||||
|
legend: { |
||||||
|
left: 150, |
||||||
|
padding: [5, 5, 5, 5] |
||||||
|
}, |
||||||
|
dataZoom: [ |
||||||
|
{ |
||||||
|
type: 'slider', |
||||||
|
filterMode: 'weakFilter', |
||||||
|
showDataShadow: false, |
||||||
|
top: |
||||||
|
props.taskList.length * 100 > 200 |
||||||
|
? props.taskList.length * 100 |
||||||
|
: 200, |
||||||
|
labelFormatter: '' |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'inside', |
||||||
|
filterMode: 'weakFilter' |
||||||
|
} |
||||||
|
], |
||||||
|
grid: { |
||||||
|
height: props.taskList.length * 50, |
||||||
|
top: 80 |
||||||
|
}, |
||||||
|
xAxis: { |
||||||
|
min: minTime, |
||||||
|
scale: true, |
||||||
|
position: 'top', |
||||||
|
axisTick: { show: true }, |
||||||
|
splitLine: { show: false }, |
||||||
|
axisLabel: { |
||||||
|
formatter: function (val: number) { |
||||||
|
return format(parseTime(val), 'HH:mm:ss') |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
yAxis: { |
||||||
|
axisTick: { show: false }, |
||||||
|
splitLine: { show: false }, |
||||||
|
axisLine: { show: false }, |
||||||
|
max: props.taskList.length, |
||||||
|
data: props.taskList |
||||||
|
}, |
||||||
|
series: series |
||||||
|
} |
||||||
|
|
||||||
|
initChart(graphChartRef, option) |
||||||
|
|
||||||
|
return { graphChartRef } |
||||||
|
}, |
||||||
|
render() { |
||||||
|
const { height, width } = this |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
ref='graphChartRef' |
||||||
|
style={{ |
||||||
|
height: typeof height === 'number' ? height + 'px' : height, |
||||||
|
width: typeof width === 'number' ? width + 'px' : width |
||||||
|
}} |
||||||
|
/> |
||||||
|
) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
export default GanttChart |
@ -0,0 +1,73 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import { defineComponent, onMounted, toRefs, watch } from 'vue' |
||||||
|
import { useI18n } from 'vue-i18n' |
||||||
|
import { useRoute } from 'vue-router' |
||||||
|
import Card from '@/components/card' |
||||||
|
import GanttChart from './components/gantt-chart' |
||||||
|
import { useGantt } from './use-gantt' |
||||||
|
|
||||||
|
const workflowRelation = defineComponent({ |
||||||
|
name: 'workflow-relation', |
||||||
|
setup() { |
||||||
|
const { t, locale } = useI18n() |
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const { variables, getGantt } = useGantt() |
||||||
|
|
||||||
|
const id = Number(route.params.id) |
||||||
|
const code = Number(route.params.projectCode) |
||||||
|
|
||||||
|
const handleResetDate = () => { |
||||||
|
variables.seriesData = [] |
||||||
|
variables.taskList = [] |
||||||
|
getGantt(id, code) |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
getGantt(id, code) |
||||||
|
}) |
||||||
|
|
||||||
|
watch( |
||||||
|
() => [locale.value], |
||||||
|
() => { |
||||||
|
handleResetDate() |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
return { t, ...toRefs(variables) } |
||||||
|
}, |
||||||
|
render() { |
||||||
|
const { t } = this |
||||||
|
return ( |
||||||
|
<Card title={t('project.workflow.gantt')}> |
||||||
|
{{ |
||||||
|
default: () => |
||||||
|
this.seriesData.length > 0 && ( |
||||||
|
<GanttChart |
||||||
|
seriesData={this.seriesData} |
||||||
|
taskList={this.taskList} |
||||||
|
/> |
||||||
|
) |
||||||
|
}} |
||||||
|
</Card> |
||||||
|
) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
export default workflowRelation |
@ -0,0 +1,39 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
interface ITask { |
||||||
|
taskName: string |
||||||
|
startDate: Array<number> |
||||||
|
endDate: Array<number> |
||||||
|
isoStart: string |
||||||
|
isoEnd: string |
||||||
|
status: string |
||||||
|
duration: string |
||||||
|
} |
||||||
|
|
||||||
|
interface IGanttRes { |
||||||
|
height: number |
||||||
|
taskNames: Array<number> |
||||||
|
taskStatus: Object |
||||||
|
tasks: Array<ITask> |
||||||
|
} |
||||||
|
|
||||||
|
interface ISeriesData { |
||||||
|
[taskState: string]: Array<any> |
||||||
|
} |
||||||
|
|
||||||
|
export { ITask, IGanttRes, ISeriesData } |
@ -0,0 +1,53 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
import { reactive } from 'vue' |
||||||
|
import { useAsyncState } from '@vueuse/core' |
||||||
|
import { viewGanttTree } from '@/service/modules/process-instances' |
||||||
|
import { IGanttRes } from './type' |
||||||
|
|
||||||
|
export function useGantt() { |
||||||
|
const variables = reactive({ |
||||||
|
seriesData: [], |
||||||
|
taskList: [] as Array<string> |
||||||
|
}) |
||||||
|
|
||||||
|
const formatGantt = (obj: IGanttRes) => { |
||||||
|
variables.seriesData = [] |
||||||
|
variables.taskList = [] |
||||||
|
|
||||||
|
variables.seriesData = obj.tasks.map((item) => { |
||||||
|
variables.taskList.push(item.taskName) |
||||||
|
return { |
||||||
|
name: item.taskName, |
||||||
|
...item |
||||||
|
} |
||||||
|
}) as any |
||||||
|
} |
||||||
|
|
||||||
|
const getGantt = (id: number, code: number) => { |
||||||
|
const { state } = useAsyncState( |
||||||
|
viewGanttTree(id, code).then((res: IGanttRes) => { |
||||||
|
formatGantt(res) |
||||||
|
}), |
||||||
|
{} |
||||||
|
) |
||||||
|
return state |
||||||
|
} |
||||||
|
|
||||||
|
return { variables, getGantt } |
||||||
|
} |
Loading…
Reference in new issue