雨辰7日gantt

This commit is contained in:
严争鸣 2025-03-12 09:13:41 +08:00
parent a2b20d4b56
commit 7426a33282
60 changed files with 4927 additions and 135 deletions

45
src/utils/timer.js Normal file
View File

@ -0,0 +1,45 @@
export const useTimer = () => {
return {
Interval,
}
}
class Interval {
constructor(callback, interval, options = {}) {
this.callback = callback
this.interval = interval
this.timer = null
this.options = options
}
immediateRun() {
if (this.options.immediate) {
this.callback()
}
}
startInterval() {
this.immediateRun()
let expectedTime = performance.now() + this.interval
const self = this
function loop(currentTime) {
const deltaTime = currentTime - expectedTime
// console.log(deltaTime)
if (deltaTime >= 0) {
self.callback()
expectedTime += self.interval
}
self.timer = requestAnimationFrame(loop)
}
this.timer = requestAnimationFrame(loop)
return self.timer
}
stopInterval() {
cancelAnimationFrame(this.timer)
}
}

View File

View File

@ -19,7 +19,7 @@ export default defineComponent({
setup() {
const imageValue = ref()
const {
universalRules,
mainEventRules,
showMainEvent,
mainEventData,
targetId,
@ -28,6 +28,8 @@ export default defineComponent({
const close = () => {
showMainEvent.value = false
uploadImg.value = []
timeRange.value = null
}
const changeFile = async file => {
console.log(uploadImg)
@ -36,34 +38,55 @@ export default defineComponent({
const res = await uploadImage(formData)
imageValue.value = res.data.path
}
const sure = async () => {
const formRef = ref(null)
async function sure(e) {
e.preventDefault()
const data = {
...mainEventData.value,
targetId: targetId.value,
fileUrl: imageValue.value,
startTime: new Date(timeRange.value[0]).toISOString(),
endTime: new Date(timeRange.value[0]).toISOString(),
startTime: mainEventData.value.timeRange
? mainEventData.value.timeRange[0]
: null,
endTime: mainEventData.value.timeRange
? mainEventData.value.timeRange[1]
: null,
}
const res = mainEventData.value.id
? await updateSimp(data)
: await addSimp(data)
if (res.code === 200) {
showMainEvent.value = false
searchTreeList()
// formRef.value?.validate(async erros => {
// if (!erros) {
// const res = mainEventData.value.id
// ? await updateSimp(data)
// : await addSimp(data)
// if (res.code === 200) {
// close()
// searchTreeList()
// }
// }
// })
if (mainEventData.value.name && mainEventData.value.timeRange) {
const res = mainEventData.value.id
? await updateSimp(data)
: await addSimp(data)
if (res.code === 200) {
close()
searchTreeList()
}
} else {
formRef.value?.validate(erros => {})
}
}
watch(
[
() => mainEventData.value.startTime,
() => mainEventData.value.endTime,
() => mainEventData.value.fileUrl,
// () => mainEventData.value.startTime,
// () => mainEventData.value.endTime,
],
([start, end, fileUrl]) => {
timeRange.value = start
? [new Date(start).getTime(), new Date(end).getTime()]
: null
([fileUrl, start, end]) => {
// timeRange.value = start
// ? [new Date(start).getTime(), new Date(end).getTime()]
// : null
if (fileUrl) {
uploadImg.value = [
{
@ -87,19 +110,20 @@ export default defineComponent({
title={`${mainEventData.value.id ? '编辑' : '添加'}事件`}
>
<NForm
ref={formRef}
class="w-[500px]"
model={mainEventData}
model={mainEventData.value}
label-placement="left"
label-width="auto"
rules={universalRules}
rules={mainEventRules}
>
<NFormItem label="事件名称" path="name">
<NInput v-model:value={mainEventData.value.name} />
</NFormItem>
<NFormItem label="事件时间" path="startTime">
<NFormItem label="事件时间" path="timeRange">
<NDatePicker
v-model:value={timeRange.value}
type="daterange"
v-model:value={mainEventData.value.timeRange}
type="datetimerange"
clearable
/>
</NFormItem>

View File

@ -19,6 +19,8 @@ export default defineComponent({
// },
setup() {
const {
subEventRules,
showNewEvent,
eventData,
sonOptions,
@ -27,6 +29,9 @@ export default defineComponent({
} = useEvent()
const imageValue = ref()
const close = () => {
timeRange.value = null
uploadImg.value = []
showNewEvent.value = false
}
const changeFile = async file => {
@ -35,7 +40,11 @@ export default defineComponent({
const res = await uploadImage(formData)
imageValue.value = res.data.path
}
const sure = async () => {
const formRef = ref(null)
async function sure(e) {
e.preventDefault()
const data = {
// id: eventData.value.id ?? null,
// activityId: oneClassData.value.activityId ?? null,
@ -43,32 +52,53 @@ export default defineComponent({
activityId: oneClassData.value?.pid ?? null,
oneType: oneClassData.value?.id,
...eventData.value,
twoType: twoTypeId.value,
// twoType: twoTypeId.value,
fileUrl: imageValue.value,
startTime: new Date(timeRange.value[0]).toISOString(),
endTime: new Date(timeRange.value[1]).toISOString(),
startTime: eventData.value.timeRange
? eventData.value.timeRange[0]
: null,
endTime: eventData.value.timeRange
? eventData.value.timeRange[1]
: null,
}
// eventData.value.startTime = data.startTime
// formRef.value?.validate(async erros => {
// console.log(erros, eventData, '')
// if (!erros) {
if (
eventData.value.name &&
eventData.value.timeRange &&
eventData.value.twoType
) {
const res = eventData.value.id
? await updateSon(data)
: await addSon(data)
if (res.code === 200) {
close()
searchTreeList()
}
} else {
formRef.value?.validate(erros => {})
}
// }
// })
// console.log(data, 'data')
const res = eventData.value.id
? await updateSon(data)
: await addSon(data)
if (res.code === 200) {
showNewEvent.value = false
searchTreeList()
}
}
watch(
[
() => eventData.value.startTime,
() => eventData.value.endTime,
() => eventData.value.fileUrl,
() => eventData.value.twoType,
// () => eventData.value.startTime,
// () => eventData.value.endTime,
// () => eventData.value.twoType,
],
([start, end, fileUrl, twoType]) => {
timeRange.value = start
? [new Date(start).getTime(), new Date(end).getTime()]
: null
([fileUrl, start, end, twoType]) => {
// timeRange.value = start
// ? [new Date(start).getTime(), new Date(end).getTime()]
// : null
if (fileUrl) {
uploadImg.value = [
{
@ -80,15 +110,15 @@ export default defineComponent({
]
console.log(uploadImg.value)
}
console.log(twoType, 'twoType')
// console.log(twoType, 'twoType')
twoType && (twoTypeId.value = twoType)
// twoType && (twoTypeId.value = twoType)
}
)
const timeRange = ref(null)
const uploadImg = ref([])
const twoTypeId = ref(null)
// const twoTypeId = ref(null)
return () => (
<ModalCom
@ -96,14 +126,16 @@ export default defineComponent({
title={`${eventData.value.id ? '编辑' : '添加'}子事件`}
>
<NForm
ref={formRef}
class="w-[500px]"
model={eventData}
model={eventData.value}
label-placement="left"
label-width="auto"
rules={subEventRules}
>
<NFormItem label="二级分类">
<NFormItem label="二级分类" path="twoType">
<NSelect
v-model:value={twoTypeId.value}
v-model:value={eventData.value.twoType}
options={sonOptions.value}
label-field="name"
value-field="id"
@ -118,10 +150,11 @@ export default defineComponent({
<NFormItem label="事件名称" path="name">
<NInput v-model:value={eventData.value.name} />
</NFormItem>
<NFormItem label="事件时间" path="startTime">
<NFormItem label="事件时间" path="timeRange">
<NDatePicker
v-model:value={timeRange.value}
type="daterange"
// v-model:value={timeRange.value}
v-model:value={eventData.value.timeRange}
type="datetimerange"
clearable
/>
</NFormItem>

View File

58
src/views/Gantt/components/EventList/hooks.jsx Executable file → Normal file
View File

@ -1,8 +1,26 @@
import { getSimpTreeList, getTwoClass } from '@/api/Gantt'
const universalRules = {
name: { required: true, message: '请输入', trigger: 'blur' },
startTime: { required: true, message: '请输入', trigger: 'blur' },
const subEventRules = {
name: { required: true, message: '请输入名称', trigger: ['blur', 'input'] },
timeRange: {
required: true,
message: '请选择时间',
trigger: ['blur', 'input'],
},
twoType: {
required: true,
message: '请选择二级分类',
trigger: ['blur', 'change'],
},
}
const mainEventRules = {
name: { required: true, message: '请输入名称', trigger: ['blur', 'input'] },
timeRange: {
required: true,
message: '请选择时间',
trigger: ['blur', 'input'],
},
}
const showMainEvent = ref(false)
@ -14,26 +32,25 @@ watch(showMainEvent, show => {
const mainEventData = ref({
name: '',
startTime: '',
endTime: '',
timeRange: null,
// type: 'mainEvent',
describe: '',
fileUrl: '',
})
function resetMainEventData() {
mainEventData.value = ref({
mainEventData.value = {
name: '',
startTime: '',
endTime: '',
// type: 'mainEvent',
timeRange: null,
describe: '',
fileUrl: '',
})
}
}
const targetId = ref(null)
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const range = ref(null)
const showNewEvent = ref(false)
@ -46,25 +63,25 @@ watch(showNewEvent, show => {
const eventData = ref({
name: '',
startTime: '',
endTime: '',
timeRange: null,
fileUrl: '',
describe: '',
equipModel: '',
reportSite: '',
twoType: '',
// type: 'subEvent',
})
function resetEventData() {
eventData.value = ref({
eventData.value = {
name: '',
startTime: '',
endTime: '',
timeRange: null,
fileUrl: '',
describe: '',
equipModel: '',
reportSite: '',
})
twoType: '',
}
}
const tableData = ref([])
@ -75,8 +92,8 @@ async function searchTreeList() {
const res = await getSimpTreeList({
targetId: targetId.value,
startTime: new Date(range.value[0]).toISOString(),
endTime: new Date(range.value[1]).toISOString(),
startTime: range.value ? range.value[0] : null,
endTime: range.value ? range.value[1] : null,
})
tableData.value = res.data.list
// console.log('searchTreeList', tableData)
@ -90,7 +107,8 @@ async function getTwoClassList(oneTypeId) {
// const getSonList = async()
export const useEvent = () => {
return {
universalRules,
subEventRules,
mainEventRules,
showMainEvent,
mainEventData,
showNewEvent,

5
src/views/Gantt/components/EventList/index.jsx Executable file → Normal file
View File

@ -222,7 +222,10 @@ export default defineComponent({
function editSubEvent(row) {
showNewEvent.value = true
eventData.value = cloneDeep(row)
eventData.value = cloneDeep({
...row,
timeRange: [row.startTime, row.endTime],
})
getTwoClassList(row.oneType)
console.log('子事件编辑:', row, 'onClassData', oneClassData)
}

View File

101
src/views/Gantt/components/Gantt/hooks/gantt.ts Executable file → Normal file
View File

@ -4,7 +4,8 @@ import { Gantt, tools, TYPES } from '@visactor/vtable-gantt'
import { getMainGantt, getSubGantt } from '@/api/Gantt/gantt'
import { getSon } from '@/api/Gantt'
import { useTree } from '@/utils/tree'
import * as dayjs from 'dayjs'
import { useInfoBox } from './infoBox.jsx'
import dayjs from 'dayjs'
type GanttParams = {
route?: any
@ -35,7 +36,6 @@ const useGantt = ({ router, route }: GanttParams) => {
: await getMainGantt(params)
if (code === 200) {
// records.value = data.list
console.log(subId, ',,,,,')
if (subId) {
records.value = data.list
.reduce((acc, cur) => {
@ -67,7 +67,7 @@ const useGantt = ({ router, route }: GanttParams) => {
return acc
}, [])
.flat()
console.log(records.value)
// console.log(records.value)
records.value.length > 0 &&
(timeRange.value = getTimeRangeForTree(records.value))
@ -91,19 +91,15 @@ const useGantt = ({ router, route }: GanttParams) => {
month,
lastDayOfMonth.getDate()
)
endOfMonth.setHours(23, 59, 59, 9999)
// new Date().toISOString()
endOfMonth.setHours(23, 59, 59, 0)
// console.log(
// mainEvent.startTime,
// startOfMonth.toISOString(),
// endOfMonth.toISOString()
// startOfMonth.toLocaleString(),
// endOfMonth.toLocaleString()
// )
return {
...mainEvent,
start: startOfMonth.toISOString(),
end: endOfMonth.toISOString(),
start: startOfMonth,
end: endOfMonth,
}
}),
}
@ -200,10 +196,10 @@ const useGantt = ({ router, route }: GanttParams) => {
maxDate: timeRange.value[1],
markLine: [
{
date: '2024-01-14T21:12:40',
date: new Date(),
style: {
lineWidth: 1,
lineColor: 'blue',
lineColor: 'rgb(13, 255, 255)',
lineDash: [8, 4],
},
},
@ -337,6 +333,8 @@ const useGantt = ({ router, route }: GanttParams) => {
return taskListTable
}
const { createInfoBox, updatePosition, removeInfoBox } = useInfoBox()
function renderTaskBar(subId: string | number) {
// console.log(subId, '------');
const taskBar = {
@ -353,20 +351,43 @@ const useGantt = ({ router, route }: GanttParams) => {
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fill: 'transparent',
// fill: '#ff0',
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
})
container.addEventListener('mouseenter', event => {
// const { x, y } = useMouse()
createInfoBox({
x: event.client.x,
y: event.client.y,
content: taskRecord.describe,
target: event.target,
})
})
container.addEventListener('mouseleave', event => {
removeInfoBox()
})
container.addEventListener('mousemove', event => {
updatePosition({
x: event.client.x,
y: event.client.y,
content: taskRecord.describe,
target: event.target,
})
})
container.addEventListener('click', async () => {
console.log(taskRecord, 'ooooooo')
removeInfoBox()
if (!subId) {
router.push({
path: `/gantt/sub/${taskRecord.id}`,
@ -434,6 +455,10 @@ const useGantt = ({ router, route }: GanttParams) => {
y: 10,
boundsPadding: [0, 0, 10, 0],
cursor: 'pointer',
// texture: 'rect',
// textureColor: '#0006',
// html: { dom: `<img width='200' height='200' src='${imgUrl}' />` },
opacity: taskRecord.status === 2 ? 0.3 : 1,
})
container.add(image)
image.addEventListener('click', e => {
@ -485,6 +510,8 @@ const useGantt = ({ router, route }: GanttParams) => {
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
opacity: taskRecord.status === 2 ? 0.3 : 1,
// boundsPadding: [10, 0, 0, 0],
})
nameContainer.add(name)
@ -495,6 +522,7 @@ const useGantt = ({ router, route }: GanttParams) => {
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
opacity: taskRecord.status === 2 ? 0.3 : 1,
})
container.add(start)
const end = new Text({
@ -503,6 +531,7 @@ const useGantt = ({ router, route }: GanttParams) => {
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
opacity: taskRecord.status === 2 ? 0.3 : 1,
})
container.add(end)
const rect = new Rect({
@ -526,6 +555,7 @@ const useGantt = ({ router, route }: GanttParams) => {
],
},
boundsPadding: [10, 0, 0, 0],
opacity: taskRecord.status === 2 ? 0.3 : 1,
})
container.add(rect)
@ -546,6 +576,20 @@ const useGantt = ({ router, route }: GanttParams) => {
// console.log(taskBar, '0000000');
return taskBar
}
function updateMarkLine() {
ganttInstance?.updateMarkLine([
{
date: new Date(),
style: {
lineWidth: 1,
lineColor: 'rgb(13, 255, 255)',
lineDash: [8, 4],
},
},
])
console.log(ganttInstance)
}
function renderGroup(opt: IGroupGraphicAttribute) {
return new Group(opt)
}
@ -583,7 +627,9 @@ const useGantt = ({ router, route }: GanttParams) => {
})
const day = new Text({
text: startDate.toLocaleDateString(),
text: subId
? dayjs(startDate).format('YYYY年M月D日')
: dayjs(startDate).format('YYYY年M月'),
// scale === 'day'
// ? startDate.toLocaleDateString()
// : startDate.toLocaleTimeString(),
@ -600,9 +646,24 @@ const useGantt = ({ router, route }: GanttParams) => {
}
},
},
// {
// unit: 'hour',
// step: 1,
// style: {
// fontSize: 10,
// fontWeight: 'normal',
// },
// visible: false,
// },
]
}
return { renderMainTask, changeTimeScales, currentImage }
return {
getGanttData,
renderMainTask,
changeTimeScales,
currentImage,
updateMarkLine,
}
}
export default useGantt

View File

@ -0,0 +1,57 @@
// import { NCard } from 'naive-ui'
let infoBox
export const useInfoBox = () => {
function createInfoBox({ x, y, content, target }) {
if (!content) return
if (!infoBox) {
infoBox = document.createElement('div')
infoBox.id = 'infoBox'
infoBox.className = `absolute z-50 shadow-sm bg-[var(--color-bg)] max-w-[300px] text-sm p-2`
document.body.appendChild(infoBox)
infoBox.innerHTML = `<p>${content}</p>`
infoBox.offsetHeight
}
updatePosition({ x, y })
}
function updatePosition({ x, y }) {
if (!infoBox) return
const pageWidth = window.innerWidth
const pageHeight = window.innerHeight
let topPosition = y + 20
let leftPosition = x + 20
let rightPosition = 0
let bottomPosition = 0
if (
pageWidth - x < infoBox.offsetWidth + 20 ||
pageHeight - y < infoBox.offsetHeight + 20
) {
leftPosition = 0
rightPosition = infoBox.offsetWidth + 20
// topPosition = 0
// bottomPosition = pageHeight - infoBox.offsetHeight + 10
}
infoBox.style.top = topPosition ? `${topPosition}px` : null
infoBox.style.left = leftPosition ? `${leftPosition}px` : null
infoBox.style.right = rightPosition ? `${rightPosition}px` : null
infoBox.style.bottom = bottomPosition ? `${bottomPosition}px` : null
}
function removeInfoBox() {
if (infoBox) {
infoBox.remove()
infoBox = null
}
}
return { createInfoBox, updatePosition, removeInfoBox }
}

65
src/views/Gantt/components/Gantt/index.jsx Executable file → Normal file
View File

@ -1,6 +1,8 @@
import { NImage } from 'naive-ui'
import useGantt from './hooks/gantt'
import { useRouter, useRoute } from 'vue-router'
import { useTimer } from '@/utils/timer.js'
import { onBeforeUnmount } from 'vue'
export default defineComponent({
props: {
@ -20,26 +22,33 @@ export default defineComponent({
setup(props, { expose }) {
const router = useRouter()
const route = useRoute()
const { renderMainTask, changeTimeScales, currentImage } = useGantt({
const {
getGanttData,
renderMainTask,
changeTimeScales,
currentImage,
updateMarkLine,
} = useGantt({
route,
router,
})
const { Interval } = useTimer()
let intervalTimer = null
// let markLineIntervalTimer = null
const refresh = ref(false)
expose({ refresh })
watch(refresh, val => {
if (val) {
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange
? new Date(props.dateRange[0]).toISOString()
: null,
endTime: props.dateRange
? new Date(props.dateRange[1]).toISOString()
: null,
})
stopRefresh()
intervalTimer = new Interval(startRefresh, 5000, { immediate: true })
intervalTimer && intervalTimer.startInterval()
refresh.value = false
}
})
@ -47,15 +56,16 @@ export default defineComponent({
onMounted(() => {
nextTick(() => {
// console.log(props);
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange
? new Date(props.dateRange[0]).toISOString()
: null,
endTime: props.dateRange
? new Date(props.dateRange[1]).toISOString()
: null,
startTime: props.dateRange ? props.dateRange[0] : null,
endTime: props.dateRange ? props.dateRange[1] : null,
})
stopRefresh()
intervalTimer = new Interval(startRefresh, 5000, { immediate: true })
intervalTimer.startInterval()
refresh.value = false
})
})
@ -74,6 +84,29 @@ export default defineComponent({
}
})
})
async function startRefresh() {
await getGanttData({
ids: props.types,
startTime: props.dateRange ? props.dateRange[0] : null,
endTime: props.dateRange ? props.dateRange[1] : null,
})
updateMarkLine()
}
function stopRefresh() {
console.log(intervalTimer, 'stop!!!!!!!!!!!!!!!!!!!!!!!!!!!')
intervalTimer && intervalTimer.stopInterval()
intervalTimer = null
// markLineIntervalTimer && markLineIntervalTimer.stopInterval()
// intervalTimer = null
}
onBeforeUnmount(() => {
stopRefresh()
})
return () => (
<>
<div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>

View File

0
src/views/Gantt/components/Gantt1/hooks/gantt.ts Executable file → Normal file
View File

0
src/views/Gantt/components/Gantt1/index.jsx Executable file → Normal file
View File

View File

0
src/views/Gantt/components/Gantt2/hooks/gantt.ts Executable file → Normal file
View File

6
src/views/Gantt/components/Gantt2/index.jsx Executable file → Normal file
View File

@ -15,7 +15,7 @@ export default defineComponent({
types: {
type: Array,
require: true,
}
},
},
setup(props, { expose }) {
const router = useRouter()
@ -32,8 +32,8 @@ export default defineComponent({
// console.log(props);
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange ? new Date(props.dateRange[0]).toISOString() : null,
endTime: props.dateRange ? new Date(props.dateRange[1]).toISOString() : null
startTime: props.dateRange ? props.dateRange[0] : null,
endTime: props.dateRange ? props.dateRange[1] : null,
})
})
})

View File

0
src/views/Gantt/components/GanttEdit/index.jsx Executable file → Normal file
View File

9
src/views/Gantt/components/MainGantt/index.jsx Executable file → Normal file
View File

@ -12,7 +12,7 @@ import { onBeforeMount } from 'vue'
export default defineComponent({
setup() {
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const range = ref([new Date('2000-01-01 00:00:00').getTime(), Date.now()])
const value = ref('year')
const types = ref([])
@ -43,7 +43,12 @@ export default defineComponent({
return () => (
<>
<div class="flex gap-2">
<NDatePicker v-model:value={range.value} type="daterange" clearable />
<NDatePicker
class="w-[600px]"
v-model:value={range.value}
type="datetimerange"
clearable
/>
{/* <NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />

2
src/views/Gantt/components/MainGantt1/index.jsx Executable file → Normal file
View File

@ -24,7 +24,7 @@ export default defineComponent({
<div class="flex gap-2">
<NDatePicker
v-model:value={range.value}
type="daterange"
type="datetimerange"
clearable
disabled
/>

43
src/views/Gantt/components/MainGantt2/index.jsx Executable file → Normal file
View File

@ -1,4 +1,4 @@
import { useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
import {
NDatePicker,
NRadioButton,
@ -7,12 +7,12 @@ import {
NSelect,
} from 'naive-ui'
import GanttCom from '../Gantt'
import {getDDList}from '@/api/Gantt/gantt'
import { getDDList } from '@/api/Gantt/gantt'
import { onBeforeMount } from 'vue'
export default defineComponent({
setup() {
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const range = ref([new Date('2000-01-01 00:00:00').getTime(), Date.now()])
const value = ref('year')
const types = ref([])
@ -22,40 +22,36 @@ export default defineComponent({
router.push('/gantt/mainEdit')
}
onBeforeMount(async ()=>{
onBeforeMount(async () => {
await getDDOptions()
})
const ddOptions = ref([])
async function getDDOptions (){
const {code,data} = await getDDList()
if(code === 200) {
async function getDDOptions() {
const { code, data } = await getDDList()
if (code === 200) {
ddOptions.value = data.list
types.value = ddOptions.value.map(item=>item.id)
types.value = ddOptions.value.map(item => item.id)
}
}
}
const ganttRef = ref(null)
function searchGanttData(){
function searchGanttData() {
// console.log(ganttRef);
renderMainTask(document.querySelector('#tableContainer'),{
renderMainTask(document.querySelector('#tableContainer'), {
ids: types.value,
startTime: new Date(range.value[0]).toISOString(),
endTime: new Date(range.value[1]).toISOString()
startTime: range.value[0],
endTime: range.value[1],
})
}
return () => (
<>
<div class="flex gap-2">
<NDatePicker
v-model:value={range.value}
type="daterange"
type="datetimerange"
clearable
/>
{/* <NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
@ -65,9 +61,9 @@ export default defineComponent({
v-model:value={types.value}
multiple
options={ddOptions.value}
label-field='name'
label-field="name"
clearable
value-field='id'
value-field="id"
></NSelect>
{/* <NButton class="ml-auto" type="primary" onClick={editEvent}>
编辑事件
@ -76,7 +72,12 @@ export default defineComponent({
搜索
</NButton>
</div>
<GanttCom ref={ganttRef} scale={value.value} dateRange={range.value} types={types.value}/>
<GanttCom
ref={ganttRef}
scale={value.value}
dateRange={range.value}
types={types.value}
/>
</>
)
},

6
src/views/Gantt/components/MainGanttEdit/index.jsx Executable file → Normal file
View File

@ -21,7 +21,11 @@ export default defineComponent({
return () => (
<>
<div class="flex gap-2">
<NDatePicker v-model:value={range.value} type="daterange" clearable />
<NDatePicker
v-model:value={range.value}
type="datetimerange"
clearable
/>
<NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />

0
src/views/Gantt/components/SubGantt/index.jsx Executable file → Normal file
View File

0
src/views/Gantt/components/SubGanttEdit/index.jsx Executable file → Normal file
View File

View File

View File

0
src/views/Gantt/components/TaskList/index.jsx Executable file → Normal file
View File

2
src/views/Gantt/index copy.vue Executable file → Normal file
View File

@ -519,7 +519,7 @@ onMounted(() => {
</div>
<div class="z-30 flex flex-1 flex-col gap-2 p-5">
<div class="flex gap-2">
<n-date-picker v-model:value="range" type="daterange" clearable />
<n-date-picker v-model:value="range" type="datetimerange" clearable />
<n-radio-group v-model:value="value" name="radiobuttongroup">
<n-radio-button value="day" label="日" />
<n-radio-button value="month" label="月" />

2
src/views/Gantt/index.jsx Executable file → Normal file
View File

@ -141,7 +141,7 @@ export default defineComponent({
></NSelect>
<NDatePicker
v-model:value={range.value}
type="daterange"
type="datetimerange"
clearable
/>
<NButton type="primary" onClick={searchTreeList}>

0
src/views/Gantt/index.vue Executable file → Normal file
View File

View File

@ -0,0 +1,94 @@
import { NTabs, NTabPane, NButton, NScrollbar } from 'naive-ui'
import TrajTable from '@/views/Daodan/components/TrajTable'
import TrajUpload from '@/views/Daodan/components/TrajUpload'
import { useEventDdConfig } from './useEventDdConfig'
const panels = ['手动配置', 'STK轨迹文件配置']
export default defineComponent({
name: 'EventDaodan',
props: {
data: {
type: Object,
default: () => ({}),
},
},
setup() {
const name = ref('手动配置')
const { trajData, interceptData, addIntercept } = useEventDdConfig()
// const { trajData, interceptData, updateInterceptData } = useEvent()
const removeIntercept = id => {
interceptData.value.splice(
interceptData.value.findIndex(item => item.id === id),
1
)
}
const showOrHideDdConfig = () => {}
return () => (
<div class="flex h-full w-[80vw] flex-col gap-2 p-2">
<NTabs
class="flex h-[calc(100%-42px)] flex-col"
v-model:value={name.value}
type="card"
tab-style="min-width: 80px;"
>
{panels.map(panel => (
<NTabPane
class="flex-1 overflow-y-auto rounded-b-[var(--n-tab-border-radius)] border border-[var(--n-tab-border-color)] border-t-transparent"
key={panel}
tab={panel}
name={panel}
>
<NScrollbar>
<div class="px-4 pb-4">
<div class="detail-container">
{panel === '手动配置' ? (
<>
<div class="rounded border border-blue-500 p-4">
<TrajTable
title="轨迹点"
showPosIcon={false}
data={trajData.value}
/>
</div>
<div class="flex flex-col gap-4 rounded border border-red-500 p-4">
{interceptData.value.map(data => (
<TrajTable
title="拦截"
data={data}
showPosIcon={false}
onRemoveIntercept={removeIntercept}
/>
))}
<div>
<NButton type="primary" onClick={addIntercept}>
添加拦截
</NButton>
</div>
</div>
</>
) : (
<>
<TrajUpload title="轨迹点" />
<TrajUpload title="拦截" />
</>
)}
</div>
</div>
</NScrollbar>
</NTabPane>
))}
</NTabs>
<div class="flex justify-end gap-2">
<NButton type="primary" onClick={confirm}>
确认
</NButton>
<NButton onClick={() => showOrHideDdConfig({})}>取消</NButton>
</div>
</div>
)
},
})

View File

@ -0,0 +1,130 @@
import {
NForm,
NFormItem,
NInput,
NButton,
NDatePicker,
NUpload,
} from 'naive-ui'
import ModalCom from '@/components/Modal/index.vue'
import { useEvent } from '../hooks'
import { addSimp, updateSimp, uploadImage } from '@/api/Gantt'
export default defineComponent({
// props: {
// show: {
// type: Boolean,
// default: false,
// },
// },
setup() {
const imageValue = ref()
const {
universalRules,
showMainEvent,
mainEventData,
targetId,
searchTreeList,
} = useEvent()
const close = () => {
showMainEvent.value = false
}
const changeFile = async file => {
console.log(uploadImg)
const formData = new FormData()
formData.append('file', file.file.file)
const res = await uploadImage(formData)
imageValue.value = res.data.path
}
const sure = async () => {
const data = {
...mainEventData.value,
targetId: targetId.value,
fileUrl: imageValue.value,
startTime: new Date(timeRange.value[0]).toISOString(),
endTime: new Date(timeRange.value[0]).toISOString(),
}
const res = mainEventData.value.id
? await updateSimp(data)
: await addSimp(data)
if (res.code === 200) {
showMainEvent.value = false
searchTreeList()
}
}
watch(
[
() => mainEventData.value.startTime,
() => mainEventData.value.endTime,
() => mainEventData.value.fileUrl,
],
([start, end, fileUrl]) => {
timeRange.value = start
? [new Date(start).getTime(), new Date(end).getTime()]
: null
if (fileUrl) {
uploadImg.value = [
{
id: '1',
name: fileUrl,
url: `${window.settings.imgServer}${fileUrl}`,
status: 'finished',
},
]
}
}
)
const timeRange = ref(null)
const uploadImg = ref([])
// const desc = ref ()
return () => (
<ModalCom
v-model:show={showMainEvent.value}
title={`${mainEventData.value.id ? '编辑' : '添加'}事件`}
>
<NForm
class="w-[500px]"
model={mainEventData}
label-placement="left"
label-width="auto"
rules={universalRules}
>
<NFormItem label="事件名称" path="name">
<NInput v-model:value={mainEventData.value.name} />
</NFormItem>
<NFormItem label="事件时间" path="startTime">
<NDatePicker
v-model:value={timeRange.value}
type="daterange"
clearable
/>
</NFormItem>
<NFormItem label="事件描述" path="describe">
<NInput
v-model:value={mainEventData.value.describe}
type="textarea"
/>
</NFormItem>
<NFormItem label="上传图片" path="fileUrl">
<NUpload
default-file-list={uploadImg.value}
list-type="image-card"
max={1}
onChange={changeFile}
/>
</NFormItem>
</NForm>
<div class="flex justify-end gap-2">
<NButton onClick={close}>取消</NButton>
<NButton type="primary" onClick={sure}>
确认
</NButton>
</div>
</ModalCom>
)
},
})

View File

@ -0,0 +1,155 @@
import {
NForm,
NFormItem,
NInput,
NButton,
NDatePicker,
NUpload,
NSelect,
} from 'naive-ui'
import ModalCom from '@/components/Modal/index.vue'
import { useEvent } from '../hooks'
import { addSon, updateSon, uploadImage } from '@/api/gantt'
export default defineComponent({
// props: {
// show: {
// type: Boolean,
// default: false,
// },
// },
setup() {
const {
showNewEvent,
eventData,
sonOptions,
oneClassData,
searchTreeList,
} = useEvent()
const imageValue = ref()
const close = () => {
showNewEvent.value = false
}
const changeFile = async file => {
const formData = new FormData()
formData.append('file', file.file.file)
const res = await uploadImage(formData)
imageValue.value = res.data.path
}
const sure = async () => {
const data = {
// id: eventData.value.id ?? null,
// activityId: oneClassData.value.activityId ?? null,
// name: eventData.value.name,
activityId: oneClassData.value?.pid ?? null,
oneType: oneClassData.value?.id,
...eventData.value,
twoType: twoTypeId.value,
fileUrl: imageValue.value,
startTime: new Date(timeRange.value[0]).toISOString(),
endTime: new Date(timeRange.value[1]).toISOString(),
}
// console.log(data, 'data')
const res = eventData.value.id
? await updateSon(data)
: await addSon(data)
if (res.code === 200) {
showNewEvent.value = false
searchTreeList()
}
}
watch(
[
() => eventData.value.startTime,
() => eventData.value.endTime,
() => eventData.value.fileUrl,
() => eventData.value.twoType,
],
([start, end, fileUrl, twoType]) => {
timeRange.value = start
? [new Date(start).getTime(), new Date(end).getTime()]
: null
if (fileUrl) {
uploadImg.value = [
{
id: fileUrl,
name: fileUrl,
url: `${window.settings.imgServer}${fileUrl}`,
status: 'finished',
},
]
console.log(uploadImg.value)
}
console.log(twoType, 'twoType')
twoType && (twoTypeId.value = twoType)
}
)
const timeRange = ref(null)
const uploadImg = ref([])
const twoTypeId = ref(null)
return () => (
<ModalCom
v-model:show={showNewEvent.value}
title={`${eventData.value.id ? '编辑' : '添加'}子事件`}
>
<NForm
class="w-[500px]"
model={eventData}
label-placement="left"
label-width="auto"
>
<NFormItem label="二级分类">
<NSelect
v-model:value={twoTypeId.value}
options={sonOptions.value}
label-field="name"
value-field="id"
></NSelect>
</NFormItem>
{/* <NFormItem label="">
<NSelect v-model:value={value2.value}></NSelect>
</NFormItem>
<NFormItem label="目标">
<NSelect v-model:value={value3.value}></NSelect>
</NFormItem> */}
<NFormItem label="事件名称" path="name">
<NInput v-model:value={eventData.value.name} />
</NFormItem>
<NFormItem label="事件时间" path="startTime">
<NDatePicker
v-model:value={timeRange.value}
type="daterange"
clearable
/>
</NFormItem>
<NFormItem label="装备型号" path="equipModel">
<NInput v-model:value={eventData.value.equipModel} />
</NFormItem>
<NFormItem label="上报站点" path="reportSite">
<NInput v-model:value={eventData.value.reportSite} />
</NFormItem>
<NFormItem label="事件描述" path="describe">
<NInput v-model:value={eventData.value.describe} type="textarea" />
</NFormItem>
<NFormItem label="上传图片" path="fileUrl">
<NUpload
default-file-list={uploadImg.value}
list-type="image-card"
onChange={changeFile}
max={1}
/>
</NFormItem>
</NForm>
<div class="flex justify-end gap-2">
<NButton onClick={close}>取消</NButton>
<NButton type="primary" onClick={sure}>
确认
</NButton>
</div>
</ModalCom>
)
},
})

View File

@ -0,0 +1,103 @@
export const useEventDdConfig = () => {
const trajData = ref({
id: 'dd',
data: [
{
name: '起始点',
lon: 120,
lat: 21,
alt: 0,
time: 1183135260000,
},
{
name: '中间特征点',
lon: 122,
lat: 21,
alt: 1000000,
time: 1183135265000,
detached: true,
},
{
name: '中间特征点',
lon: 124,
lat: 21,
alt: 1500000,
time: 1183135270000,
detached: true,
},
{
name: '中间特征点',
lon: 128,
lat: 21,
alt: 2000000,
time: 1183135280000,
detached: true,
},
{
name: '落点',
lon: 135,
lat: 21,
alt: 1500000,
time: 1183135290000,
},
],
})
const interceptData = ref([
{
id: 'dd1',
data: [
{
name: '起始点',
lon: 137,
lat: 25,
alt: 0,
time: 1183135270000,
},
{
name: '中间特征点',
lon: 138,
lat: 24,
alt: 1000000,
time: 1183135280000,
detached: true,
},
{
name: '落点',
lon: 135,
lat: 21,
alt: 1500000,
time: 1183135290000,
},
],
},
])
function addIntercept() {
// d
interceptData.value.push({
data: [
{
name: '起始点',
lon: 120,
lat: 21,
alt: 0,
time: 1183135260000,
},
{
name: '中间特征点',
lon: 120,
lat: 21,
alt: 0,
time: 1183135260000,
detached: false,
},
trajData.value.data.at(-1),
],
})
}
return {
trajData,
interceptData,
addIntercept,
}
}

View File

@ -0,0 +1,106 @@
import { getSimpTreeList, getTwoClass } from '@/api/Gantt'
const universalRules = {
name: { required: true, message: '请输入', trigger: 'blur' },
startTime: { required: true, message: '请输入', trigger: 'blur' },
}
const showMainEvent = ref(false)
watch(showMainEvent, show => {
if (!show) {
resetMainEventData()
}
})
const mainEventData = ref({
name: '',
startTime: '',
endTime: '',
// type: 'mainEvent',
describe: '',
fileUrl: '',
})
function resetMainEventData() {
mainEventData.value = ref({
name: '',
startTime: '',
endTime: '',
// type: 'mainEvent',
describe: '',
fileUrl: '',
})
}
const targetId = ref(null)
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const showNewEvent = ref(false)
watch(showNewEvent, show => {
if (!show) {
oneClassData.value = null
resetEventData()
}
})
const eventData = ref({
name: '',
startTime: '',
endTime: '',
fileUrl: '',
describe: '',
equipModel: '',
reportSite: '',
// type: 'subEvent',
})
function resetEventData() {
eventData.value = ref({
name: '',
startTime: '',
endTime: '',
fileUrl: '',
describe: '',
equipModel: '',
reportSite: '',
})
}
const tableData = ref([])
const oneClassData = ref(null)
async function searchTreeList() {
tableData.value = []
const res = await getSimpTreeList({
targetId: targetId.value,
startTime: new Date(range.value[0]).toISOString(),
endTime: new Date(range.value[1]).toISOString(),
})
tableData.value = res.data.list
// console.log('searchTreeList', tableData)
}
const sonOptions = ref([])
async function getTwoClassList(oneTypeId) {
const res = await getTwoClass({ oneType: oneTypeId })
sonOptions.value = res.data.list
}
// const getSonList = async()
export const useEvent = () => {
return {
universalRules,
showMainEvent,
mainEventData,
showNewEvent,
eventData,
targetId,
searchTreeList,
tableData,
range,
oneClassData,
sonOptions,
getTwoClassList,
}
}

View File

@ -0,0 +1,296 @@
import { NDataTable, NIcon, NButton, useDialog, NTag } from 'naive-ui'
import {
getEventListByDDType,
deleteSimp,
getTwoClass,
deleteSon,
} from '@/api/gantt'
import { useTree } from '@/utils/tree'
import {
HelpCircleOutline,
CreateOutline,
TrashBinOutline,
AddCircleOutline,
EnterOutline,
} from '@vicons/ionicons5'
import { cloneDeep } from 'es-toolkit'
import MainEventEdit from './components/MainEventEdit'
import SubEventEdit from './components/SubEventEdit'
import EventDdConfig from './components/EventDdConfig'
import { useEvent } from './hooks'
export default defineComponent({
props: {
dd: {
type: Number,
require: true,
},
tableData: {
type: Array,
require: true,
},
},
setup(props) {
const {
showMainEvent,
mainEventData,
showNewEvent,
eventData,
searchTreeList,
oneClassData,
// sonOptions,
getTwoClassList,
} = useEvent()
const dict = window.settings.gantt
const columns = [
{
title: '事件名称',
key: 'name',
// width: 'auto',
render: row => {
return (
<div class="inline-flex items-center gap-2">
{/* <NIcon>
<HelpCircleOutline />
</NIcon> */}
<NTag
size="small"
round
bordered={false}
type={dict[row.level].color}
>
{dict[row.level].label}
</NTag>
{row.name}
</div>
)
},
},
{
title: '开始时间',
key: 'startTime',
width: '300',
},
{
title: '结束时间',
key: 'endTime',
width: '300',
},
// {
// title: '',
// key: 'type',
// render(row) {
// return (
// <NTag
// size="small"
// round
// bordered={false}
// type={dict[row.type].color}
// >
// {dict[row.type].label}
// </NTag>
// )
// },
// },
{
title: '图片',
key: 'filePath',
width: '200',
render(row) {
if (row.fileUrl) {
return (
<img
src={`${window.settings.imgServer}${row.fileUrl}`}
width="50"
alt=""
/>
)
} else {
return <span>-</span>
}
},
},
{
title: '操作',
key: 'action',
width: '240',
render(row, rowIndex) {
// console.log(row, rowIndex)
return (
<div class="flex justify-end">
{row.level === 1 && (
<NButton
type="primary"
size="small"
quaternary
onClick={() => editMainEvent(row)}
>
<NIcon>
<CreateOutline />
</NIcon>
编辑事件
</NButton>
)}
{row.level === 2 ? (
<NButton
type="success"
size="small"
quaternary
onClick={() => addSubEvent(row)}
>
<NIcon>
<AddCircleOutline />
</NIcon>
添加子事件
</NButton>
) : null}
{row.level === 4 && (
<NButton
type="primary"
size="small"
quaternary
onClick={() => editSubEvent(row)}
>
<NIcon>
<CreateOutline />
</NIcon>
编辑子事件
</NButton>
)}
{Reflect.has(row, 'trajData') && (
<NButton
type="primary"
size="small"
quaternary
onClick={() => ddConfig(row)}
>
<NIcon>
<CreateOutline />
</NIcon>
编辑DD轨迹
</NButton>
)}
{/* {row.level == 4 && (
<NButton
type="primary"
size="small"
quaternary
onClick={() => editSubEvent(row)}
>
<NIcon>
<CreateOutline />
</NIcon>
编辑子事件
</NButton>
)} */}
{![2, 3].includes(row.level) ? (
<NButton
type="error"
size="small"
quaternary
onClick={() => deleteEvent(row)}
>
<NIcon>
<TrashBinOutline />
</NIcon>
</NButton>
) : (
<></>
)}
</div>
)
},
},
]
function editMainEvent(row) {
showMainEvent.value = true
mainEventData.value = cloneDeep(row)
}
const addSubEvent = async row => {
console.log(row, 'row')
oneClassData.value = row
showNewEvent.value = true
// eventData.value = {}
await getTwoClassList(row.id)
}
function editSubEvent(row) {
showNewEvent.value = true
eventData.value = cloneDeep(row)
getTwoClassList(row.oneType)
console.log('子事件编辑:', row, 'onClassData', oneClassData)
}
const dialog = useDialog()
function deleteEvent(row) {
console.log(row, 'row')
dialog.warning({
title: '删除事件',
content: `确定删除事件 ${row.name} 吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
//await deleteEventById(row.id)
if (row.level == 1) {
await deleteSimp({ id: row.id })
} else if (row.level == 4) {
await deleteSon({ id: row.id })
}
searchTreeList()
},
})
}
function ddConfig(row) {
console.log(row)
dialog.create({
style: 'width:auto;height:90vh',
maskClosable: false,
class: 'flex flex-col',
title: 'DD轨迹',
contentClass: 'flex-1 h-0',
content: () => <EventDdConfig />,
// positiveText: '',
// negativeText: '',
// onPositiveClick: () => {},
})
}
const { getAllKeys } = useTree()
const expandedRowKeys = ref([])
watch(
() => props.tableData,
() => {
expandedRowKeys.value = getAllKeys(props.tableData, 'name') || []
},
{
immediate: true,
}
)
return () => (
<>
<NDataTable
remote
class="h-full"
flex-height
indent={20}
v-model:expanded-row-keys={expandedRowKeys.value}
columns={columns}
data={props.tableData}
row-key={row => row.name}
/>
<MainEventEdit v-model:show={showMainEvent.value} />
<SubEventEdit v-model:show={showNewEvent.value} />
</>
)
},
})

View File

@ -0,0 +1,47 @@
import { VImage, VGroup, VRect, VText } from '@visactor/vtable/es/vrender'
const textColor = '#65c5e7'
export default defineComponent({
props: {
width: {
type: Number,
default: 0,
},
height: {
type: Number,
default: 0,
},
taskRecord: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const { width, height, taskRecord } = props
return () => (
<VGroup
attribute={{
width,
height,
fill: 'transparent',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
}}
>
<VText
attribute={{
text: 'taskRecord.name',
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
}}
></VText>
</VGroup>
)
},
})

View File

@ -0,0 +1,608 @@
import { Group, Image, Text, CheckBox, Rect } from '@visactor/vtable/es/vrender'
import { useDialog } from 'naive-ui'
import { Gantt, tools, TYPES } from '@visactor/vtable-gantt'
import { getMainGantt, getSubGantt } from '@/api/Gantt/gantt'
import { getSon } from '@/api/Gantt'
import { useTree } from '@/utils/tree'
import * as dayjs from 'dayjs'
type GanttParams = {
route?: any
router?: any
}
let ganttInstance: null | Gantt = null
const bgColor = '#1c202c'
const headerBgColor = '#33566f22'
const textColor = '#65c5e7'
const textColorWithOp = '#75fbfd22'
const { getTimeRangeForTree } = useTree()
const useGantt = ({ router, route }: GanttParams) => {
const currentImage = ref()
const { subId } = route.params
const records = ref([])
const timeRange = ref([])
// onMounted(() => {
// getGanttData()
// })
async function getGanttData(params: Record<string, string>) {
timeRange.value = [params.startTime, params.endTime]
console.log('%csubId', 'color:red;font-size:20px', subId)
const { code, data } = subId
? await getSubGantt({ activityId: subId })
: await getMainGantt(params)
if (code === 200) {
// records.value = data.list
console.log(subId, ',,,,,')
if (subId) {
records.value = data.list
.reduce((acc, cur) => {
if (Array.isArray(cur.children) && cur.children.length > 0) {
acc.push(
cur.children.map(twoType => {
return {
...twoType,
children:
twoType.children &&
twoType.children.map(eventItem => {
console.log(
eventItem.startTime,
eventItem.endTime
// new Date(eventItem.startTime).getMonth()
)
return {
...eventItem,
start: eventItem.startTime,
end: eventItem.endTime,
}
}),
parentName: cur.name,
childrenLengthForParent: cur.children.length,
}
})
)
}
return acc
}, [])
.flat()
console.log(records.value)
records.value.length > 0 &&
(timeRange.value = getTimeRangeForTree(records.value))
} else {
// console.log(data.list, '------')
records.value = data.list.map(item => {
return {
...item,
children:
item.children &&
item.children.map(mainEvent => {
const date = new Date(mainEvent.startTime)
const year = date.getFullYear()
const month = date.getMonth()
const startOfMonth = new Date(year, month, 1)
startOfMonth.setHours(0, 0, 0, 0)
const lastDayOfMonth = new Date(year, month + 1, 0)
const endOfMonth = new Date(
year,
month,
lastDayOfMonth.getDate()
)
endOfMonth.setHours(23, 59, 59, 9999)
// new Date().toISOString()
// console.log(
// mainEvent.startTime,
// startOfMonth.toISOString(),
// endOfMonth.toISOString()
// )
return {
...mainEvent,
start: startOfMonth.toISOString(),
end: endOfMonth.toISOString(),
}
}),
}
})
}
records.value.length > 0 && ganttInstance?.setRecords(records.value)
}
}
async function renderMainTask(
dom: HTMLElement,
params: Record<string, string>
) {
// console.log(subId, 'renderMainTask')
await getGanttData(params)
const option = getOption()
// if (ganttInstance) {
// ganttInstance.setRecords(records.value)
// } else {
if (records.value.length === 0) return
ganttInstance && ganttInstance?.release()
ganttInstance = new Gantt(dom, option)
window['ganttInstance'] = ganttInstance
// }
}
function getOption(): TYPES.GanttConstructorOptions {
// console.log(records.value);
const option = {
records: records.value,
taskListTable: renderTaskListTable(),
tasksShowMode: TYPES.TasksShowMode.Sub_Tasks_Arrange,
// groupBy: 'name',
// groupField: 'name',
// widthMode: 'standard',
// groupTitleFieldFormat: (record, col, row, table) => {
// console.log(record, col, row, table, '----')
// const groupData = table.getGroupData(record); // 获取分组数据
// const count = groupData ? groupData.length : 0; // 计算分组下的记录数量
// return `${record.name} (${count})`; // 返回格式化的分组标题
// },
frame: {
outerFrameStyle: {
borderLineWidth: 2,
borderColor: textColor,
cornerRadius: 3,
},
// verticalSplitLineHighlight: {
// lineColor: 'green',
// lineWidth: 3,
// },
},
grid: {
// backgroundColor: bgColor,
horizontalLine: {
lineWidth: 1,
lineColor: textColorWithOp,
},
// verticalLine: {
// lineWidth: 1,
// lineColor: textColorWithOp,
// lineDash: [4, 8],
// },
},
taskList: {
// backgroundColor: bgColor,
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
},
headerRowHeight: 59,
rowHeight: subId ? 200 : 100,
taskBar: renderTaskBar(subId),
timelineHeader: {
backgroundColor: headerBgColor,
colWidth: 150,
// colWidth: 1040,
// verticalLine: {
// lineColor: textColorWithOp,
// lineWidth: 1,
// lineDash: [4, 2],
// },
horizontalLine: {
lineColor: textColorWithOp,
lineWidth: 1,
lineDash: [4, 2],
},
scales: getTimeScales(subId ? 'day' : 'month'),
},
minDate: timeRange.value[0],
maxDate: timeRange.value[1],
markLine: [
{
date: '2024-01-14T21:12:40',
style: {
lineWidth: 1,
lineColor: 'blue',
lineDash: [8, 4],
},
},
// {
// date: '2024-08-17',
// style: {
// lineWidth: 2,
// lineColor: 'red',
// lineDash: [8, 4],
// },
// },
],
scrollStyle: {
scrollRailColor: 'RGBA(246,246,246,0)',
visible: 'focus',
width: 10,
scrollSliderCornerRadius: 2,
scrollSliderColor: 'rgba(255,255,255,0.25)',
},
underlayBackgroundColor: bgColor,
}
return option as TYPES.GanttConstructorOptions
}
function renderColumn() {
const columns = [
{
field: 'name',
title: subId ? '事件类型' : '事件主体',
width: '120',
mergeCell: true,
customLayout: args => {
// console.log(args, 'srgs')
const { table, row, col, rect, dataValue } = args
const { height, width } = rect ?? table.getCellRect(col, row)
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
boundsPadding: [10, 0, 0, 0],
})
// console.log('args-----', args);
const count =
table.records.find(r => dataValue === r.name)?.children?.length || 0
const values = new Text({
text: `${dataValue}`,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'center',
maxLineWidth: width,
whiteSpace: 'normal',
// boundsPadding: [10, 10, 10, 10],
})
const counts = new Text({
text: `( ${count} )`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(values)
container.add(counts)
return {
rootContainer: container,
}
},
},
]
if (subId) {
columns.unshift({
field: 'parentName',
title: '事件分类',
width: '120',
mergeCell: true,
})
columns.unshift({
field: 'isChecked',
title: '',
width: '60',
headerType: 'checkbox',
cellType: 'checkbox',
})
}
return columns
}
function renderTaskListTable() {
const taskListTable = {
columns: renderColumn(),
// tableWidth: 'auto',
theme: {
underlayBackgroundColor: bgColor,
headerStyle: {
borderColor: textColorWithOp,
borderLineWidth: 1,
fontWeight: 'bold',
color: textColor,
bgColor: headerBgColor,
textAlign: 'center',
fontSize: 20,
hover: {
cellBgColor: 'transparent',
},
},
bodyStyle: {
borderColor: textColorWithOp,
textAlign: 'center',
borderLineWidth: 1,
autoWrapText: true,
fontSize: 16,
color: textColor,
bgColor: bgColor,
hover: {
cellBgColor: textColorWithOp,
},
},
},
}
return taskListTable
}
function renderTaskBar(subId: string | number) {
// console.log(subId, '------');
const taskBar = {
resizable: false,
moveable: false,
startDateField: 'start',
endDateField: 'end',
// progressField: 'progress',
barStyle: { width: subId ? 200 : 80 },
customLayout: args => {
// console.log(args, 'args');
const { width, height, startDate, endDate, taskRecord } = args
// console.log(taskRecord, 'taskRecord');
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
})
container.addEventListener('click', async () => {
console.log(taskRecord, 'ooooooo')
if (!subId) {
router.push({
path: `/gantt/sub/${taskRecord.id}`,
})
} else {
const { code, data } = await getSon({ id: taskRecord.id })
if (code === 200) {
const showData = {
事件名称: data.name,
: `${data.startTime} - ${data.endTime}`,
装备型号: data.equipModel,
上报站点: data.reportSite,
事件描述: data.describe,
}
window.$dialog.info({
title: '子事件详情',
class: '!w-[40vw]',
content: () => {
return h(
'div',
{ class: 'flex flex-col gap-4 w-full h-full' },
[
...Object.keys(showData).map(key => {
return h('div', { class: 'flex w-full h-full ' }, [
h('div', { class: 'w-[120px]' }, key),
h(
'div',
{ class: 'flex-1 text-wrap' },
showData[key]
),
])
}),
h('div', { class: 'flex w-full h-full' }, [
h('div', { class: 'w-[120px]' }, '图片'),
h('img', {
src: `${window.settings.imgServer}${data.fileUrl}`,
}),
]),
]
)
},
positiveText: '确定',
})
}
}
})
// if (!subId) {
// container.addEventListener('click', () => {
// console.log(taskRecord, 'ooooooo')
// router.push({
// path: `/gantt/sub/${taskRecord.id}`,
// })
// })
// }
if (subId) {
const imgUrl = `${window.settings.imgServer}${taskRecord.fileUrl}`
// console.log(imgUrl);
const image = new Image({
image: imgUrl,
width: 100,
height: 100,
x: 10,
y: 10,
boundsPadding: [0, 0, 10, 0],
cursor: 'pointer',
})
container.add(image)
image.addEventListener('click', e => {
e.stopPropagation()
currentImage.value = [imgUrl, Date.now()]
// console.log(currentImage, 'currentImage')
})
}
// const checkbox = new CheckBox({
// width: 20,
// height: 20,
// checked: false,
// })
// container.add(checkbox)
// checkbox.addEventListener('click', event => {
// console.log(event, 'event')
// })
// console.log(taskRecord, 'taskRecord')
const nameContainer = new Group({
fill: 'transparent',
display: 'flex',
// flexDirection: 'column',
// justifyContent: 'center',
alignItems: 'center',
})
container.add(nameContainer)
if ('trajData' in taskRecord && taskRecord.trajData) {
const taskRecordSymbol = new Image({
width: 20,
height: 20,
fill: '#ff0',
image:
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5s4.5-2.01 4.5-4.5s-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1s1 .45 1 1s-.45 1-1 1z" fill="#ff0"></path></svg>',
boundsPadding: [-3, 10, 0, 0],
cursor: 'pointer',
})
nameContainer.add(taskRecordSymbol)
}
const name = new Text({
text: taskRecord.name,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
// boundsPadding: [10, 0, 0, 0],
})
nameContainer.add(name)
const start = new Text({
text: `${taskRecord.startTime}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(start)
const end = new Text({
text: `${taskRecord.endTime}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(end)
const rect = new Rect({
width: width,
height: 7,
fill: {
gradient: 'linear',
x0: 0,
y0: 0,
x1: 1,
y1: 0,
stops: [
{
offset: 0,
color: textColor,
},
{
offset: 1,
color: textColorWithOp,
},
],
},
boundsPadding: [10, 0, 0, 0],
})
container.add(rect)
return {
rootContainer: container,
}
},
hoverBarStyle: {
cornerRadius: 2,
barOverlayColor: textColorWithOp,
},
selectedBarStyle: {
// cornerRadius: 2,
borderColor: textColorWithOp,
borderLineWidth: 2,
},
}
// console.log(taskBar, '0000000');
return taskBar
}
function renderGroup(opt: IGroupGraphicAttribute) {
return new Group(opt)
}
function renderText(opt: ITextGraphicAttribute) {
return new Text(opt)
}
function renderImage(opt: IImageGraphicAttribute) {
return new Image(opt)
}
function changeTimeScales(scale: TYPES.ITimelineScale['unit']) {
const scales = getTimeScales(scale)
ganttInstance && ganttInstance.updateScales(scales)
}
function getTimeScales(
scale: TYPES.ITimelineScale['unit']
): TYPES.ITimelineScale[] {
return [
{
unit: scale,
step: 1,
customLayout: args => {
const { width, height, startDate } = args
const container = new Group({
width,
height,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'nowrap',
})
const day = new Text({
text: startDate.toLocaleDateString(),
// scale === 'day'
// ? startDate.toLocaleDateString()
// : startDate.toLocaleTimeString(),
fontSize: 14,
fontWeight: 'bold',
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'right',
maxLineWidth: width,
})
container.add(day)
return {
rootContainer: container,
}
},
},
]
}
return { renderMainTask, changeTimeScales, currentImage }
}
export default useGantt

View File

@ -0,0 +1,88 @@
import { NImage } from 'naive-ui'
import useGantt from './hooks/gantt'
import { useRouter, useRoute } from 'vue-router'
export default defineComponent({
props: {
scale: {
type: String,
default: 'day',
},
dateRange: {
type: Array,
require: true,
},
types: {
type: Array,
require: true,
},
},
setup(props, { expose }) {
const router = useRouter()
const route = useRoute()
const { renderMainTask, changeTimeScales, currentImage } = useGantt({
route,
router,
})
const refresh = ref(false)
expose({ refresh })
watch(refresh, val => {
if (val) {
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange
? new Date(props.dateRange[0]).toISOString()
: null,
endTime: props.dateRange
? new Date(props.dateRange[1]).toISOString()
: null,
})
refresh.value = false
}
})
onMounted(() => {
nextTick(() => {
// console.log(props);
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange
? new Date(props.dateRange[0]).toISOString()
: null,
endTime: props.dateRange
? new Date(props.dateRange[1]).toISOString()
: null,
})
refresh.value = false
})
})
watch(
() => props.scale,
val => {
changeTimeScales(val)
}
)
const imgRef = ref(null)
watch(currentImage, imgUrl => {
nextTick(() => {
if (imgUrl) {
imgRef.value.click()
}
})
})
return () => (
<>
<div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>
<NImage
class="absolute h-0"
ref={imgRef}
src={currentImage.value?.[0]}
></NImage>
</>
)
},
})

View File

@ -0,0 +1,47 @@
import { VImage, VGroup, VRect, VText } from '@visactor/vtable/es/vrender'
const textColor = '#65c5e7'
export default defineComponent({
props: {
width: {
type: Number,
default: 0,
},
height: {
type: Number,
default: 0,
},
taskRecord: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const { width, height, taskRecord } = props
return () => (
<VGroup
attribute={{
width,
height,
fill: 'transparent',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
}}
>
<VText
attribute={{
text: 'taskRecord.name',
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
}}
></VText>
</VGroup>
)
},
})

View File

@ -0,0 +1,452 @@
import { Group, Image, Text, CheckBox, Rect } from '@visactor/vtable/es/vrender'
import { Gantt, tools, TYPES } from '@visactor/vtable-gantt'
import { getMainGantt, getSubGantt } from '@/api/Gantt'
type GanttParams = {
route?: any
router?: any
}
let ganttInstance: null | Gantt = null
const bgColor = '#1c202c'
const headerBgColor = '#33566f22'
const textColor = '#65c5e7'
const textColorWithOp = '#75fbfd22'
const useGantt = ({ router, route }: GanttParams) => {
const currentImage = ref()
const { subId } = route.params
const records = ref([])
onMounted(() => {
getGanttData()
})
async function getGanttData() {
if (subId) {
const res = await getSubGantt(subId)
records.value = res
ganttInstance?.setRecords(records.value)
} else {
const res = await getMainGantt()
// console.log(res, '----')
records.value = res
ganttInstance?.setRecords(records.value)
}
}
function renderMainTask(dom: HTMLElement) {
const option = getOption()
ganttInstance = new Gantt(dom, option)
window['ganttInstance'] = ganttInstance
// console.log(ganttInstance)
}
function getOption(): TYPES.GanttConstructorOptions {
const option = {
records: records.value,
taskListTable: renderTaskListTable(),
tasksShowMode: TYPES.TasksShowMode.Sub_Tasks_Arrange,
// groupBy: 'name',
// groupField: 'name',
// widthMode: 'standard',
// groupTitleFieldFormat: (record, col, row, table) => {
// console.log(record, col, row, table, '----')
// const groupData = table.getGroupData(record); // 获取分组数据
// const count = groupData ? groupData.length : 0; // 计算分组下的记录数量
// return `${record.name} (${count})`; // 返回格式化的分组标题
// },
frame: {
outerFrameStyle: {
borderLineWidth: 2,
borderColor: textColor,
cornerRadius: 3,
},
// verticalSplitLineHighlight: {
// lineColor: 'green',
// lineWidth: 3,
// },
},
grid: {
// backgroundColor: bgColor,
horizontalLine: {
lineWidth: 1,
lineColor: textColorWithOp,
},
// verticalLine: {
// lineWidth: 1,
// lineColor: textColorWithOp,
// lineDash: [4, 8],
// },
},
taskList: {
// backgroundColor: bgColor,
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
},
headerRowHeight: 59,
rowHeight: subId ? 200 : 100,
taskBar: renderTaskBar(subId),
timelineHeader: {
backgroundColor: headerBgColor,
colWidth: 140,
// colWidth: 1040,
// verticalLine: {
// lineColor: textColorWithOp,
// lineWidth: 1,
// lineDash: [4, 2],
// },
horizontalLine: {
lineColor: textColorWithOp,
lineWidth: 1,
lineDash: [4, 2],
},
scales: getTimeScales('day'),
},
minDate: '2024-11-14',
maxDate: '2024-12-30',
markLine: [
{
date: '2024-07-29',
style: {
lineWidth: 1,
lineColor: 'blue',
lineDash: [8, 4],
},
},
// {
// date: '2024-08-17',
// style: {
// lineWidth: 2,
// lineColor: 'red',
// lineDash: [8, 4],
// },
// },
],
scrollStyle: {
scrollRailColor: 'RGBA(246,246,246,0)',
visible: 'focus',
width: 6,
scrollSliderCornerRadius: 2,
scrollSliderColor: 'rgba(255,255,255,0.25)',
},
underlayBackgroundColor: bgColor,
}
return option as TYPES.GanttConstructorOptions
}
function renderColumn() {
const columns = [
{
field: 'name',
title: subId ? '事件类型' : '事件主体',
width: '120',
mergeCell: true,
customLayout: args => {
const { table, row, col, rect, dataValue } = args
// console.log(
// table,
// '1',
// row,
// '2',
// col,
// '3',
// rect,
// '4',
// dataValue,
// '5',
// '-----------'
// )
const { height, width } = rect ?? table.getCellRect(col, row)
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
})
console.log(args)
const count = table.records.find(r => dataValue === r.name)?.children
?.length
const values = new Text({
text: `${dataValue}`,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'center',
// boundsPadding: [10, 0, 0, 0],
})
const counts = new Text({
text: `( ${count} )`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(values)
container.add(counts)
return {
rootContainer: container,
}
},
},
]
if (subId) {
columns.unshift({
field: 'isChecked',
title: '',
width: '60',
headerType: 'checkbox',
cellType: 'checkbox',
})
}
return columns
}
function renderTaskListTable() {
const taskListTable = {
columns: renderColumn(),
// tableWidth: 'auto',
theme: {
underlayBackgroundColor: bgColor,
headerStyle: {
borderColor: textColorWithOp,
borderLineWidth: 1,
fontWeight: 'bold',
color: textColor,
bgColor: headerBgColor,
textAlign: 'center',
fontSize: 20,
hover: {
cellBgColor: 'transparent',
},
},
bodyStyle: {
borderColor: textColorWithOp,
textAlign: 'center',
borderLineWidth: 1,
autoWrapText: true,
fontSize: 16,
color: textColor,
bgColor: bgColor,
hover: {
cellBgColor: textColorWithOp,
},
},
},
}
return taskListTable
}
function renderTaskBar() {
const taskBar = {
resizable: false,
moveable: false,
startDateField: 'start',
endDateField: 'end',
// progressField: 'progress',
barStyle: { width: subId ? 180 : 60 },
customLayout: args => {
const { width, height, startDate, endDate, taskRecord } = args
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
})
if (!subId) {
container.addEventListener('click', () => {
console.log(taskRecord, 'ooooooo')
router.push({
path: `/gantt/sub/${taskRecord.id}`,
})
})
}
if (subId) {
const image = new Image({
image: taskRecord.avatar,
width: 100,
height: 100,
x: 10,
y: 10,
boundsPadding: [0, 0, 10, 0],
cursor: 'pointer',
})
container.add(image)
image.addEventListener('click', () => {
currentImage.value = [taskRecord.avatar, Date.now()]
// console.log(currentImage, 'currentImage')
})
}
// const checkbox = new CheckBox({
// width: 20,
// height: 20,
// checked: false,
// })
// container.add(checkbox)
// checkbox.addEventListener('click', event => {
// console.log(event, 'event')
// })
// console.log(taskRecord, 'taskRecord')
const nameContainer = new Group({
fill: 'transparent',
display: 'flex',
// flexDirection: 'column',
// justifyContent: 'center',
alignItems: 'center',
})
container.add(nameContainer)
if ('trajData' in taskRecord && taskRecord.trajData) {
const taskRecordSymbol = new Image({
width: 20,
height: 20,
fill: '#ff0',
image:
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5s4.5-2.01 4.5-4.5s-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1s1 .45 1 1s-.45 1-1 1z" fill="#ff0"></path></svg>',
boundsPadding: [-3, 10, 0, 0],
cursor: 'pointer',
})
nameContainer.add(taskRecordSymbol)
}
const name = new Text({
text: taskRecord.name,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
// boundsPadding: [10, 0, 0, 0],
})
nameContainer.add(name)
const days = new Text({
text: `${startDate.toLocaleDateString()} ~ ${endDate.toLocaleDateString()}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(days)
const rect = new Rect({
width: width,
height: 7,
fill: {
gradient: 'linear',
x0: 0,
y0: 0,
x1: 1,
y1: 0,
stops: [
{
offset: 0,
color: textColor,
},
{
offset: 1,
color: textColorWithOp,
},
],
},
boundsPadding: [10, 0, 0, 0],
})
container.add(rect)
return {
rootContainer: container,
}
},
hoverBarStyle: {
cornerRadius: 2,
barOverlayColor: textColorWithOp,
},
selectedBarStyle: {
// cornerRadius: 2,
borderColor: textColorWithOp,
borderLineWidth: 2,
},
}
return taskBar
}
function renderGroup(opt: IGroupGraphicAttribute) {
return new Group(opt)
}
function renderText(opt: ITextGraphicAttribute) {
return new Text(opt)
}
function renderImage(opt: IImageGraphicAttribute) {
return new Image(opt)
}
function changeTimeScales(scale: TYPES.ITimelineScale['unit']) {
const scales = getTimeScales(scale)
ganttInstance && ganttInstance.updateScales(scales)
}
function getTimeScales(
scale: TYPES.ITimelineScale['unit']
): TYPES.ITimelineScale[] {
return [
{
unit: scale,
step: 1,
customLayout: args => {
const { width, height, startDate } = args
const container = new Group({
width,
height,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'nowrap',
})
const day = new Text({
text: startDate.toLocaleDateString(),
// scale === 'day'
// ? startDate.toLocaleDateString()
// : startDate.toLocaleTimeString(),
fontSize: 14,
fontWeight: 'bold',
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'right',
maxLineWidth: width,
})
container.add(day)
return {
rootContainer: container,
}
},
},
]
}
return { renderMainTask, changeTimeScales, currentImage }
}
export default useGantt

View File

@ -0,0 +1,51 @@
import { NImage } from 'naive-ui'
import useGantt from './hooks/gantt'
import { useRouter, useRoute } from 'vue-router'
export default defineComponent({
props: {
scale: {
type: String,
default: 'day',
},
},
setup(props) {
const router = useRouter()
const route = useRoute()
const { renderMainTask, changeTimeScales, currentImage } = useGantt({
route,
router,
})
onMounted(() => {
nextTick(() => {
renderMainTask(document.querySelector('#tableContainer'))
})
})
watch(
() => props.scale,
val => {
changeTimeScales(val)
}
)
const imgRef = ref(null)
watch(currentImage, imgUrl => {
nextTick(() => {
if (imgUrl) {
imgRef.value.click()
}
})
})
return () => (
<>
<div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>
<NImage
class="absolute h-0"
ref={imgRef}
src={currentImage.value?.[0]}
></NImage>
</>
)
},
})

View File

@ -0,0 +1,47 @@
import { VImage, VGroup, VRect, VText } from '@visactor/vtable/es/vrender'
const textColor = '#65c5e7'
export default defineComponent({
props: {
width: {
type: Number,
default: 0,
},
height: {
type: Number,
default: 0,
},
taskRecord: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const { width, height, taskRecord } = props
return () => (
<VGroup
attribute={{
width,
height,
fill: 'transparent',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
}}
>
<VText
attribute={{
text: 'taskRecord.name',
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
}}
></VText>
</VGroup>
)
},
})

View File

@ -0,0 +1,461 @@
import { Group, Image, Text, CheckBox, Rect } from '@visactor/vtable/es/vrender'
import { Gantt, tools, TYPES } from '@visactor/vtable-gantt'
import { getMainGantt, getSubGantt } from '@/api/Gantt/gantt'
type GanttParams = {
route?: any
router?: any
}
let ganttInstance: null | Gantt = null
const bgColor = '#1c202c'
const headerBgColor = '#33566f22'
const textColor = '#65c5e7'
const textColorWithOp = '#75fbfd22'
const useGantt = ({ router, route }: GanttParams) => {
const currentImage = ref()
const { subId } = route.params
const records = ref([])
const timeRange = ref([])
// onMounted(() => {
// getGanttData()
// })
async function getGanttData(params: Record<string, string>) {
timeRange.value = [params.startTime, params.endTime]
console.log('%csubId', 'color:red;font-size:20px', subId);
const { code, data } = subId ? await getSubGantt({ activityId: subId }) : await getMainGantt(params)
if (code === 200) {
records.value = data.list
ganttInstance?.setRecords(records.value)
}
}
async function renderMainTask(dom: HTMLElement, params: Record<string, string>) {
await getGanttData(params)
const option = getOption()
ganttInstance = new Gantt(dom, option)
window['ganttInstance'] = ganttInstance
}
function getOption(): TYPES.GanttConstructorOptions {
console.log(records.value);
const option = {
records: records.value,
taskListTable: renderTaskListTable(),
tasksShowMode: TYPES.TasksShowMode.Sub_Tasks_Arrange,
// groupBy: 'name',
// groupField: 'name',
// widthMode: 'standard',
// groupTitleFieldFormat: (record, col, row, table) => {
// console.log(record, col, row, table, '----')
// const groupData = table.getGroupData(record); // 获取分组数据
// const count = groupData ? groupData.length : 0; // 计算分组下的记录数量
// return `${record.name} (${count})`; // 返回格式化的分组标题
// },
frame: {
outerFrameStyle: {
borderLineWidth: 2,
borderColor: textColor,
cornerRadius: 3,
},
// verticalSplitLineHighlight: {
// lineColor: 'green',
// lineWidth: 3,
// },
},
grid: {
// backgroundColor: bgColor,
horizontalLine: {
lineWidth: 1,
lineColor: textColorWithOp,
},
// verticalLine: {
// lineWidth: 1,
// lineColor: textColorWithOp,
// lineDash: [4, 8],
// },
},
taskList: {
// backgroundColor: bgColor,
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
},
headerRowHeight: 59,
rowHeight: subId ? 200 : 100,
taskBar: renderTaskBar(subId),
timelineHeader: {
backgroundColor: headerBgColor,
colWidth: 4000,
// colWidth: 1040,
// verticalLine: {
// lineColor: textColorWithOp,
// lineWidth: 1,
// lineDash: [4, 2],
// },
horizontalLine: {
lineColor: textColorWithOp,
lineWidth: 1,
lineDash: [4, 2],
},
scales: getTimeScales('month'),
},
minDate: timeRange.value[0],
maxDate: timeRange.value[1],
markLine: [
{
date: '2024-03-13T13:15:10',
style: {
lineWidth: 1,
lineColor: 'blue',
lineDash: [8, 4],
},
},
// {
// date: '2024-08-17',
// style: {
// lineWidth: 2,
// lineColor: 'red',
// lineDash: [8, 4],
// },
// },
],
scrollStyle: {
scrollRailColor: 'RGBA(246,246,246,0)',
visible: 'focus',
width: 10,
scrollSliderCornerRadius: 2,
scrollSliderColor: 'rgba(255,255,255,0.25)',
},
underlayBackgroundColor: bgColor,
}
return option as TYPES.GanttConstructorOptions
}
function renderColumn() {
const columns = [
{
field: 'name',
title: subId ? '事件类型' : '事件主体',
width: '120',
mergeCell: true,
customLayout: args => {
// console.log(args, 'srgs')
const { table, row, col, rect, dataValue } = args
// console.log(
// table,
// '1',
// row,
// '2',
// col,
// '3',
// rect,
// '4',
// dataValue,
// '5',
// '-----------'
// )
const { height, width } = rect ?? table.getCellRect(col, row)
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
boundsPadding: [10, 0, 0, 0],
})
// console.log('args-----', args);
const count = table.records.find(r => dataValue === r.name)?.children
?.length || 0
const values = new Text({
text: `${dataValue}`,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'center',
maxLineWidth: width,
whiteSpace: 'normal'
// boundsPadding: [10, 10, 10, 10],
})
const counts = new Text({
text: `( ${count} )`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(values)
container.add(counts)
return {
rootContainer: container,
}
},
},
]
if (subId) {
columns.unshift({
field: 'isChecked',
title: '',
width: '60',
headerType: 'checkbox',
cellType: 'checkbox',
})
}
return columns
}
function renderTaskListTable() {
const taskListTable = {
columns: renderColumn(),
// tableWidth: 'auto',
theme: {
underlayBackgroundColor: bgColor,
headerStyle: {
borderColor: textColorWithOp,
borderLineWidth: 1,
fontWeight: 'bold',
color: textColor,
bgColor: headerBgColor,
textAlign: 'center',
fontSize: 20,
hover: {
cellBgColor: 'transparent',
},
},
bodyStyle: {
borderColor: textColorWithOp,
textAlign: 'center',
borderLineWidth: 1,
autoWrapText: true,
fontSize: 16,
color: textColor,
bgColor: bgColor,
hover: {
cellBgColor: textColorWithOp,
},
},
},
}
return taskListTable
}
function renderTaskBar(subId: string | number) {
// console.log(subId, '------');
const taskBar = {
resizable: false,
moveable: false,
startDateField: 'startTime',
endDateField: 'endTime',
// progressField: 'progress',
barStyle: { width: subId ? 180 : 60 },
customLayout: args => {
const { width, height, startDate, endDate, taskRecord } = args
// console.log(taskRecord, 'taskRecord');
const container = new Group({
width,
height,
fill: 'transparent',
// fill: textColor,
// fillOpacity: 0.1,
// stroke: textColor,
// strokeOpacity: 0.2,
// lineWidth: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
cursor: 'pointer',
})
if (!subId) {
container.addEventListener('click', () => {
console.log(taskRecord, 'ooooooo')
router.push({
path: `/gantt/sub/${taskRecord.id}`,
})
})
}
if (subId) {
const image = new Image({
image: taskRecord.avatar,
width: 100,
height: 100,
x: 10,
y: 10,
boundsPadding: [0, 0, 10, 0],
cursor: 'pointer',
})
container.add(image)
image.addEventListener('click', () => {
currentImage.value = [taskRecord.avatar, Date.now()]
// console.log(currentImage, 'currentImage')
})
}
// const checkbox = new CheckBox({
// width: 20,
// height: 20,
// checked: false,
// })
// container.add(checkbox)
// checkbox.addEventListener('click', event => {
// console.log(event, 'event')
// })
// console.log(taskRecord, 'taskRecord')
const nameContainer = new Group({
fill: 'transparent',
display: 'flex',
// flexDirection: 'column',
// justifyContent: 'center',
alignItems: 'center',
})
container.add(nameContainer)
if ('trajData' in taskRecord && taskRecord.trajData) {
const taskRecordSymbol = new Image({
width: 20,
height: 20,
fill: '#ff0',
image:
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10s10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8s8 3.59 8 8s-3.59 8-8 8zm0-12.5c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5s4.5-2.01 4.5-4.5s-2.01-4.5-4.5-4.5zm0 5.5c-.55 0-1-.45-1-1s.45-1 1-1s1 .45 1 1s-.45 1-1 1z" fill="#ff0"></path></svg>',
boundsPadding: [-3, 10, 0, 0],
cursor: 'pointer',
})
nameContainer.add(taskRecordSymbol)
}
const name = new Text({
text: taskRecord.name,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
// boundsPadding: [10, 0, 0, 0],
})
nameContainer.add(name)
const days = new Text({
text: `${startDate.toLocaleDateString()} ~ ${endDate.toLocaleDateString()}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(days)
const rect = new Rect({
width: width,
height: 7,
fill: {
gradient: 'linear',
x0: 0,
y0: 0,
x1: 1,
y1: 0,
stops: [
{
offset: 0,
color: textColor,
},
{
offset: 1,
color: textColorWithOp,
},
],
},
boundsPadding: [10, 0, 0, 0],
})
container.add(rect)
return {
rootContainer: container,
}
},
hoverBarStyle: {
cornerRadius: 2,
barOverlayColor: textColorWithOp,
},
selectedBarStyle: {
// cornerRadius: 2,
borderColor: textColorWithOp,
borderLineWidth: 2,
},
}
console.log(taskBar, '0000000');
return taskBar
}
function renderGroup(opt: IGroupGraphicAttribute) {
return new Group(opt)
}
function renderText(opt: ITextGraphicAttribute) {
return new Text(opt)
}
function renderImage(opt: IImageGraphicAttribute) {
return new Image(opt)
}
function changeTimeScales(scale: TYPES.ITimelineScale['unit']) {
const scales = getTimeScales(scale)
ganttInstance && ganttInstance.updateScales(scales)
}
function getTimeScales(
scale: TYPES.ITimelineScale['unit']
): TYPES.ITimelineScale[] {
return [
{
unit: scale,
step: 1,
customLayout: args => {
const { width, height, startDate } = args
const container = new Group({
width,
height,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'nowrap',
})
const day = new Text({
text: startDate.toLocaleDateString(),
// scale === 'day'
// ? startDate.toLocaleDateString()
// : startDate.toLocaleTimeString(),
fontSize: 14,
fontWeight: 'bold',
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'right',
maxLineWidth: width,
})
container.add(day)
return {
rootContainer: container,
}
},
},
]
}
return { renderMainTask, changeTimeScales, currentImage }
}
export default useGantt

View File

@ -0,0 +1,66 @@
import { NImage } from 'naive-ui'
import useGantt from './hooks/gantt'
import { useRouter, useRoute } from 'vue-router'
export default defineComponent({
props: {
scale: {
type: String,
default: 'day',
},
dateRange: {
type: Array,
require: true,
},
types: {
type: Array,
require: true,
}
},
setup(props, { expose }) {
const router = useRouter()
const route = useRoute()
const { renderMainTask, changeTimeScales, currentImage } = useGantt({
route,
router,
})
expose({ renderMainTask })
onMounted(() => {
nextTick(() => {
// console.log(props);
renderMainTask(document.querySelector('#tableContainer'), {
ids: props.types,
startTime: props.dateRange ? new Date(props.dateRange[0]).toISOString() : null,
endTime: props.dateRange ? new Date(props.dateRange[1]).toISOString() : null
})
})
})
watch(
() => props.scale,
val => {
changeTimeScales(val)
}
)
const imgRef = ref(null)
watch(currentImage, imgUrl => {
nextTick(() => {
if (imgUrl) {
imgRef.value.click()
}
})
})
return () => (
<>
<div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>
<NImage
class="absolute h-0"
ref={imgRef}
src={currentImage.value?.[0]}
></NImage>
</>
)
},
})

View File

@ -0,0 +1,389 @@
import { Group, Image, Text, CheckBox, Rect } from '@visactor/vtable/es/vrender'
import { useDialog } from 'naive-ui'
import { Gantt, tools, TYPES } from '@visactor/vtable-gantt'
import { getMainGantt, getSubGantt } from '@/api/Gantt'
type GanttParams = {
route?: any
router?: any
}
let ganttInstance: null | Gantt = null
const bgColor = '#1c202c'
const headerBgColor = '#33566f22'
const textColor = '#65c5e7'
const textColorWithOp = '#75fbfd22'
const useGanttEdit = ({ router, route }: GanttParams) => {
const { subId } = route.params
const records = ref([])
const dialog = useDialog()
onMounted(() => {
getGanttData()
})
async function getGanttData() {
if (subId) {
const res = await getSubGantt(subId)
records.value = res
ganttInstance?.setRecords(records.value)
} else {
const res = await getMainGantt()
// console.log(res, '----')
records.value = res
ganttInstance?.setRecords(records.value)
}
}
function renderMainTask(dom: HTMLElement) {
const option = getOption()
ganttInstance = new Gantt(dom, option)
window['ganttInstance'] = ganttInstance
console.log(ganttInstance)
}
function getOption(): TYPES.GanttConstructorOptions {
const option = {
records: records.value,
taskListTable: renderTaskListTable(),
tasksShowMode: TYPES.TasksShowMode.Sub_Tasks_Arrange,
frame: {
outerFrameStyle: {
borderLineWidth: 2,
borderColor: textColor,
cornerRadius: 3,
},
// verticalSplitLineHighlight: {
// lineColor: 'green',
// lineWidth: 3
// }
},
grid: {
// backgroundColor: bgColor,
horizontalLine: {
lineWidth: 1,
lineColor: textColorWithOp,
},
verticalLine: {
lineWidth: 1,
lineColor: textColorWithOp,
lineDash: [4, 8],
},
},
taskList: {
// backgroundColor: bgColor,
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
},
headerRowHeight: 59,
rowHeight: subId ? 200 : 100,
taskBar: renderTaskBar(),
timelineHeader: {
backgroundColor: headerBgColor,
colWidth: 150,
verticalLine: {
lineColor: textColorWithOp,
lineWidth: 1,
lineDash: [4, 2],
},
horizontalLine: {
lineColor: textColorWithOp,
lineWidth: 1,
lineDash: [4, 2],
},
scales: getTimeScales('day'),
},
minDate: '2024-11-14',
maxDate: '2024-12-30',
scrollStyle: {
scrollRailColor: 'RGBA(246,246,246,0)',
visible: 'focus',
width: 6,
scrollSliderCornerRadius: 2,
scrollSliderColor: 'rgba(255,255,255,0.25)',
},
underlayBackgroundColor: bgColor,
}
return option as TYPES.GanttConstructorOptions
}
function renderColumn() {
const columns = [
{
field: 'name',
title: subId ? '事件类型' : '事件主体',
width: '120',
mergeCell: true,
},
]
// if (subId) {
// columns.unshift({
// field: 'isChecked',
// title: '',
// width: '60',
// headerType: 'checkbox',
// cellType: 'checkbox',
// })
// }
return columns
}
function renderTaskListTable() {
const taskListTable = {
columns: renderColumn(),
// tableWidth: 'auto',
theme: {
underlayBackgroundColor: bgColor,
headerStyle: {
borderColor: textColorWithOp,
borderLineWidth: 1,
fontWeight: 'bold',
color: textColor,
bgColor: headerBgColor,
textAlign: 'center',
fontSize: 20,
hover: {
cellBgColor: 'transparent',
},
},
bodyStyle: {
borderColor: textColorWithOp,
textAlign: 'center',
borderLineWidth: 1,
autoWrapText: true,
fontSize: 16,
color: textColor,
bgColor: bgColor,
hover: {
cellBgColor: textColorWithOp,
},
},
},
}
return taskListTable
}
function renderTaskBar() {
const taskBar = {
resizable: false,
moveable: false,
startDateField: 'start',
endDateField: 'end',
// progressField: 'progress',
barStyle: { width: subId ? 180 : 60 },
customLayout: args => {
const { width, height, startDate, endDate, taskRecord } = args
const container = new Group({
width,
height,
fill: 'transparent',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
// alignItems: 'center',
cursor: 'pointer',
})
if (!subId) {
container.addEventListener('click', event => {
router.push({
path: `/gantt/subEdit/${taskRecord.id}`,
})
})
}
if (subId) {
const image = new Image({
image: taskRecord.avatar,
width: 100,
height: 100,
x: 10,
y: 10,
boundsPadding: [0, 0, 10, 0],
})
container.add(image)
}
const checkBoxGroup = new Group({
width,
//height,
fill: 'transparent',
display: 'flex',
})
container.add(checkBoxGroup)
// const checkbox = new CheckBox({
// id: taskRecord.id,
// text: '',
// checked: false,
// })
// checkBoxGroup.add(checkbox)
// checkbox.addEventListener('click', event => {
// event.stopPropagation()
// console.log(checkbox, 'checkbox')
// })
const name = new Text({
text: taskRecord.name,
fontSize: 16,
fontFamily: 'sans-serif',
fill: textColor,
fontWeight: 'bold',
maxLineWidth: width,
textAlign: 'center',
// boundsPadding: [0, 0, 0, 10],
})
checkBoxGroup.add(name)
const editSymbol = new Image({
width: 20,
height: 20,
fill: '#fff',
image:
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><g fill="none"><path d="M4.5 2A2.5 2.5 0 0 0 2 4.5v7A2.5 2.5 0 0 0 4.5 14h1.547l.25-1H4.5A1.5 1.5 0 0 1 3 11.5V5h10v1.036c.331-.058.671-.05 1 .023V4.5A2.5 2.5 0 0 0 11.5 2h-7zM3.085 4A1.5 1.5 0 0 1 4.5 3h7a1.5 1.5 0 0 1 1.415 1h-9.83zm11.46 3.455a1.56 1.56 0 0 0-2.207 0l-4.289 4.288a2.777 2.777 0 0 0-.73 1.29l-.303 1.212a.61.61 0 0 0 .739.739l1.211-.303a2.777 2.777 0 0 0 1.29-.73l4.289-4.289a1.56 1.56 0 0 0 0-2.207z" fill="#5c8e9e"></path></g></svg>',
boundsPadding: [-2, 0, 0, 10],
cursor: 'pointer',
})
checkBoxGroup.add(editSymbol)
editSymbol.addEventListener('click', event => {
event.stopPropagation()
console.log(editSymbol, 'editSymbol')
})
const deleteSymbol = new Image({
width: 20,
height: 20,
fill: '#fff',
image:
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><g fill="none"><path d="M16.5 12a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11zM12 1.75a3.25 3.25 0 0 1 3.245 3.066L15.25 5h5.25a.75.75 0 0 1 .102 1.493L20.5 6.5h-.796l-.5 5.087c-.46-.21-.95-.37-1.46-.468l.453-4.619H5.802l1.267 12.872a1.25 1.25 0 0 0 1.117 1.122l.127.006h2.42c.286.551.65 1.056 1.076 1.5H8.313a2.75 2.75 0 0 1-2.714-2.307l-.023-.174L4.295 6.5H3.5a.75.75 0 0 1-.743-.648L2.75 5.75a.75.75 0 0 1 .648-.743L3.5 5h5.25A3.25 3.25 0 0 1 12 1.75zm1.716 12.839l-.07.057l-.057.07a.5.5 0 0 0 0 .568l.057.07l2.147 2.146l-2.147 2.146l-.057.07a.5.5 0 0 0 0 .568l.057.07l.07.057a.5.5 0 0 0 .568 0l.07-.057l2.146-2.147l2.146 2.147l.07.057a.5.5 0 0 0 .568 0l.07-.057l.057-.07a.5.5 0 0 0 0-.568l-.057-.07l-2.147-2.146l2.147-2.146l.057-.07a.5.5 0 0 0 0-.568l-.057-.07l-.07-.057a.5.5 0 0 0-.568 0l-.07.057l-2.146 2.147l-2.146-2.147l-.07-.057a.5.5 0 0 0-.492-.044l-.076.044zM12 3.25a1.75 1.75 0 0 0-1.744 1.606L10.25 5h3.5A1.75 1.75 0 0 0 12 3.25z" fill="#933"></path></g></svg>',
boundsPadding: [-2, 0, 0, 10],
cursor: 'pointer',
})
checkBoxGroup.add(deleteSymbol)
deleteSymbol.addEventListener('click', event => {
event.stopPropagation()
// console.log(dialog, '----')
dialog.warning({
title: '警告',
content: '确认删除当前事件?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {},
onNegativeClick: () => {},
})
console.log(deleteSymbol, 'deleteSymbol')
})
const days = new Text({
text: `${startDate.toLocaleDateString()} ~ ${endDate.toLocaleDateString()}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: textColor,
boundsPadding: [10, 0, 0, 0],
})
container.add(days)
const rect = new Rect({
width: width,
height: 7,
fill: {
gradient: 'linear',
x0: 0,
y0: 0,
x1: 1,
y1: 0,
stops: [
{
offset: 0,
color: textColor,
},
{
offset: 1,
color: textColorWithOp,
},
],
},
boundsPadding: [10, 0, 0, 0],
})
container.add(rect)
return {
rootContainer: container,
}
},
hoverBarStyle: {
cornerRadius: 2,
barOverlayColor: textColorWithOp,
},
selectedBarStyle: {
// cornerRadius: 2,
borderColor: textColorWithOp,
borderLineWidth: 2,
},
}
return taskBar
}
function renderGroup(opt: IGroupGraphicAttribute) {
return new Group(opt)
}
function renderText(opt: ITextGraphicAttribute) {
return new Text(opt)
}
function renderImage(opt: IImageGraphicAttribute) {
return new Image(opt)
}
function changeTimeScales(scale: TYPES.ITimelineScale['unit']) {
const scales = getTimeScales(scale)
ganttInstance && ganttInstance.updateScales(scales)
}
function getTimeScales(
scale: TYPES.ITimelineScale['unit']
): TYPES.ITimelineScale[] {
return [
{
unit: scale,
step: 1,
customLayout: args => {
const { width, height, startDate } = args
const container = new Group({
width,
height,
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
flexWrap: 'nowrap',
})
const day = new Text({
text:
scale === 'day'
? startDate.toLocaleDateString()
: startDate.toLocaleTimeString(),
fontSize: 14,
fontWeight: 'bold',
fontFamily: 'sans-serif',
fill: textColor,
textAlign: 'center',
maxLineWidth: width,
// boundsPadding: [25, 12, 10, 12],
})
container.add(day)
return {
rootContainer: container,
}
},
},
]
}
return { renderMainTask, changeTimeScales }
}
export default useGanttEdit

View File

@ -0,0 +1,29 @@
import useGanttEdit from './hooks/ganttEdit'
import { useRouter, useRoute } from 'vue-router'
export default defineComponent({
props: {
scale: {
type: String,
default: 'day',
},
},
setup(props) {
const router = useRouter()
const route = useRoute()
const { renderMainTask, changeTimeScales } = useGanttEdit({ route, router })
onMounted(() => {
nextTick(() => {
renderMainTask(document.querySelector('#tableContainer'))
})
})
watch(
() => props.scale,
val => {
changeTimeScales(val)
}
)
return () => <div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>
},
})

View File

@ -0,0 +1,75 @@
import { useRouter } from 'vue-router'
import {
NDatePicker,
NRadioButton,
NRadioGroup,
NButton,
NSelect,
} from 'naive-ui'
import GanttCom from '../Gantt'
import { getDDList } from '@/api/Gantt/gantt'
import { onBeforeMount } from 'vue'
export default defineComponent({
setup() {
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const value = ref('year')
const types = ref([])
const router = useRouter()
const editEvent = () => {
console.log(router)
router.push('/gantt/mainEdit')
}
onBeforeMount(async () => {
await getDDOptions()
})
const ddOptions = ref([])
async function getDDOptions() {
const { code, data } = await getDDList()
if (code === 200) {
ddOptions.value = data.list
// types.value = ddOptions.value.map(item => item.id)
}
}
const ganttRef = ref(null)
function searchGanttData() {
ganttRef.value.refresh = true
}
return () => (
<>
<div class="flex gap-2">
<NDatePicker v-model:value={range.value} type="daterange" clearable />
{/* <NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />
</NRadioGroup> */}
<NSelect
v-model:value={types.value}
multiple
options={ddOptions.value}
label-field="name"
clearable
value-field="id"
></NSelect>
{/* <NButton class="ml-auto" type="primary" onClick={editEvent}>
编辑事件
</NButton> */}
<NButton class="ml-auto" type="primary" onClick={searchGanttData}>
搜索
</NButton>
</div>
<GanttCom
ref={ganttRef}
scale={value.value}
dateRange={range.value}
types={types.value}
/>
</>
)
},
})

View File

@ -0,0 +1,48 @@
import { useRouter } from 'vue-router'
import {
NDatePicker,
NRadioButton,
NRadioGroup,
NButton,
NSelect,
} from 'naive-ui'
import GanttCom from '../Gantt'
export default defineComponent({
setup() {
const range = ref()
const value = ref('day')
const type = ref()
const router = useRouter()
const editEvent = () => {
console.log(router)
router.push('/gantt/mainEdit')
}
return () => (
<>
<div class="flex gap-2">
<NDatePicker
v-model:value={range.value}
type="daterange"
clearable
disabled
/>
<NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />
</NRadioGroup>
<NSelect
v-model:value={type.value}
mode="multiple"
style="width: 200px"
></NSelect>
{/* <NButton class="ml-auto" type="primary" onClick={editEvent}>
编辑事件
</NButton> */}
</div>
<GanttCom scale={value.value} />
</>
)
},
})

View File

@ -0,0 +1,83 @@
import { useRouter } from 'vue-router'
import {
NDatePicker,
NRadioButton,
NRadioGroup,
NButton,
NSelect,
} from 'naive-ui'
import GanttCom from '../Gantt'
import {getDDList}from '@/api/Gantt/gantt'
import { onBeforeMount } from 'vue'
export default defineComponent({
setup() {
const range = ref([new Date('2000-01-01').getTime(), Date.now()])
const value = ref('year')
const types = ref([])
const router = useRouter()
const editEvent = () => {
console.log(router)
router.push('/gantt/mainEdit')
}
onBeforeMount(async ()=>{
await getDDOptions()
})
const ddOptions = ref([])
async function getDDOptions (){
const {code,data} = await getDDList()
if(code === 200) {
ddOptions.value = data.list
types.value = ddOptions.value.map(item=>item.id)
}
}
const ganttRef = ref(null)
function searchGanttData(){
// console.log(ganttRef);
renderMainTask(document.querySelector('#tableContainer'),{
ids: types.value,
startTime: new Date(range.value[0]).toISOString(),
endTime: new Date(range.value[1]).toISOString()
})
}
return () => (
<>
<div class="flex gap-2">
<NDatePicker
v-model:value={range.value}
type="daterange"
clearable
/>
{/* <NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />
</NRadioGroup> */}
<NSelect
v-model:value={types.value}
multiple
options={ddOptions.value}
label-field='name'
clearable
value-field='id'
></NSelect>
{/* <NButton class="ml-auto" type="primary" onClick={editEvent}>
编辑事件
</NButton> */}
<NButton class="ml-auto" type="primary" onClick={searchGanttData}>
搜索
</NButton>
</div>
<GanttCom ref={ganttRef} scale={value.value} dateRange={range.value} types={types.value}/>
</>
)
},
})

View File

@ -0,0 +1,45 @@
import { NDatePicker, NRadioButton, NRadioGroup, NButton } from 'naive-ui'
import { useRouter } from 'vue-router'
import GanttCom from '../GanttEdit'
export default defineComponent({
setup() {
const range = ref()
const value = ref('day')
const addEvent = () => {
console.log('tianjia --- event')
}
const addTask = () => {
console.log('tianjiarenwu')
}
const router = useRouter()
const ok = () => {
router.push('/gantt')
}
return () => (
<>
<div class="flex gap-2">
<NDatePicker v-model:value={range.value} type="daterange" clearable />
<NRadioGroup v-model:value={value.value} name="radiobuttongroup">
<NRadioButton value="hour" label="日" />
<NRadioButton value="day" label="月" />
</NRadioGroup>
<div class="ml-auto flex gap-2">
<NButton type="primary" onClick={addEvent}>
添加事件
</NButton>
{/* <NButton type="primary" onClick={addTask}>
添加任务
</NButton> */}
<NButton type="primary" onClick={ok}>
完成编辑
</NButton>
</div>
</div>
<GanttCom scale={value.value} />
</>
)
},
})

View File

@ -0,0 +1,33 @@
import { NButton } from 'naive-ui'
import GanttCom from '../Gantt'
import { useRouter } from 'vue-router'
export default defineComponent({
setup() {
const router = useRouter()
const value = ref('day')
const showView = () => {
// getCheckboxState
const checked = ganttInstance.taskListTableInstance?.getCheckboxState()
console.log(checked)
}
return () => (
<>
<div class="flex gap-2">
<NButton onClick={showView}>态势展示</NButton>
<NButton
onClick={() => {
router.go(-1)
}}
>
返回
</NButton>
</div>
<GanttCom scale={value.value} />
</>
)
},
})

View File

@ -0,0 +1,33 @@
import { NButton } from 'naive-ui'
import GanttCom from '../GanttEdit'
import { useRouter } from 'vue-router'
export default defineComponent({
setup() {
const router = useRouter()
const value = ref('day')
const showView = () => {
// getCheckboxState
const checked = ganttInstance.taskListTableInstance?.getCheckboxState()
console.log(checked)
}
return () => (
<>
<div class="flex gap-2">
{/* <NButton onClick={showView}>态势展示</NButton> */}
<NButton
onClick={() => {
router.go(-1)
}}
>
返回
</NButton>
</div>
<GanttCom scale={value.value} />
</>
)
},
})

View File

@ -0,0 +1,15 @@
const showNewTask = ref(false)
const curTaskData = ref({
name: '',
description: '',
checkedEvent: [],
})
const useTask = () => {
return {
showNewTask,
curTaskData,
}
}
export default useTask

View File

@ -0,0 +1,118 @@
import { NForm, NFormItem, NInput, NDataTable, NButton } from 'naive-ui'
import ModalCom from '@/components/Modal/index.vue'
import { getMainGantt } from '@/api/gantt'
import useTask from './hooks'
export default defineComponent({
// props: {
// show: {
// type: Boolean,
// default: false,
// },
// },
setup() {
// const checkedRowKeys = ref([])
// watch(checkedRowKeys, newval => {
// console.log(newval, '-----')
// })
const columns = [
{ type: 'selection' },
{
title: '事件名称',
key: 'name',
width: 220,
// render: row => {
// return (
// <div class="inline-flex items-center gap-2">
// <NIcon>
// <HelpCircleOutline />
// </NIcon>
// {row.name}
// </div>
// )
// },
},
{
title: '开始时间',
key: 'start',
},
{
title: '结束时间',
key: 'end',
},
// {
// title: '',
// key: 'type',
// },
{
title: '图片',
key: 'avatar',
render(row) {
if (row.avatar) {
return <img src={row.avatar} width="50" alt="" />
} else {
return <span>-</span>
}
},
},
]
const tableData = ref([])
onMounted(async () => {
const res = await getMainGantt()
tableData.value = res
})
const { showNewTask, curTaskData } = useTask()
const close = () => {
showNewTask.value = false
console.log(curTaskData, '---')
}
// watch(
// showNewTask,
// show => {
// if (show) {
// taskData.value = {
// name: '',
// description: '',
// checkedEvent: [],
// }
// }
// },
// { immediate: true }
// )
return () => (
<ModalCom v-model:show={showNewTask.value} title="新增任务">
<NForm
class="w-[900px]"
model={curTaskData}
label-placement="left"
label-width="auto"
>
<NFormItem label="任务名称" path="name">
<NInput v-model:value={curTaskData.value.name} />
</NFormItem>
<NFormItem label="任务描述" path="description">
<NInput v-model:value={curTaskData.value.description} />
</NFormItem>
<NFormItem label="事件" path="event">
<NDataTable
v-model:checked-row-keys={curTaskData.value.checkedEvent}
class="h-[400px]"
flex-height
columns={columns}
data={tableData.value}
row-key={row => row.name}
/>
</NFormItem>
</NForm>
<div class="flex justify-end gap-2">
<NButton onClick={close}>取消</NButton>
<NButton type="primary">确认</NButton>
</div>
</ModalCom>
)
},
})

View File

@ -0,0 +1,172 @@
import { NDataTable, NIcon, NButton, NTag } from 'naive-ui'
import { useTree } from '@/utils/tree'
import { getTask } from '@/api/gantt'
import {
HelpCircleOutline,
CreateOutline,
TrashBinOutline,
AddCircleOutline,
EnterOutline,
} from '@vicons/ionicons5'
import useTask from './components/NewTask/hooks'
export default defineComponent({
setup() {
const dict = window.settings.gantt
const columns = [
{
title: '任务名称/事件名称',
key: 'name',
width: 'auto',
render: row => {
return (
<div class="inline-flex items-center gap-2">
{row.type && (
<NTag
size="small"
round
bordered={false}
type={dict[row.type]?.color}
>
{dict[row.type].label}
</NTag>
)}
{row.name}
</div>
)
},
},
{
title: '开始时间',
key: 'start',
},
{
title: '结束时间',
key: 'end',
},
// {
// title: '',
// key: 'type',
// render(row) {
// return (
// row.type && (
// <NTag
// size="small"
// round
// bordered={false}
// type={dict[row.type]?.color}
// >
// {dict[row.type].label}
// </NTag>
// )
// )
// },
// },
{
title: '图片',
key: 'avatar',
render(row) {
if (row.avatar) {
return <img src={row.avatar} width="50" alt="" />
} else {
return <span>-</span>
}
},
},
{
title: '操作',
key: 'action',
render(row) {
return (
<div class="flex justify-end">
{row.type === 'task' ? (
<>
<NButton
type="primary"
size="small"
quaternary
onClick={() => editTask(row)}
>
<NIcon>
<EnterOutline />
</NIcon>
进入任务
</NButton>
<NButton
type="primary"
size="small"
quaternary
onClick={() => editTask(row)}
>
<NIcon>
<CreateOutline />
</NIcon>
编辑
</NButton>
</>
) : null}
{/* {!row.avatar ? (
<NButton
type="success"
size="small"
quaternary
onClick={() => handleEdit(row)}
>
<NIcon>
<AddCircleOutline />
</NIcon>
</NButton>
) : (
<></>
)} */}
{/* <NButton
type="error"
size="small"
quaternary
onClick={() => handleEdit(row)}
>
<NIcon>
<TrashBinOutline />
</NIcon>
</NButton> */}
</div>
)
},
},
]
const tableData = ref([])
const { getAllKeys } = useTree()
const expandedRowKeys = ref([])
onMounted(async () => {
await getTaskList()
})
async function getTaskList() {
const res = await getTask()
tableData.value = res
expandedRowKeys.value = getAllKeys(tableData.value, 'name')
}
const { showNewTask, curTaskData } = useTask()
function editTask(row) {
showNewTask.value = true
curTaskData.value = {
...row,
checkedEvent: getAllKeys(row.children, 'name'),
}
}
return () => (
<NDataTable
class="h-full"
flex-height
indent={30}
v-model:expanded-row-keys={expandedRowKeys.value}
columns={columns}
data={tableData.value}
row-key={row => row.name}
/>
)
},
})

View File

@ -0,0 +1,531 @@
<script setup>
import HeaderCom from '../Content/components/Header/index.vue'
import { Gantt, tools } from '@visactor/vtable-gantt'
// import * as VTableGantt from '@visactor/vtable-gantt';
import * as VRender from '@visactor/vtable/es/vrender'
const checked = ref({
id: 'test',
width: 20,
height: 20,
checked: false,
})
let ganttInstance
const barColors0 = [
'#aecde6',
'#c6a49a',
'#ffb582',
'#eec1de',
'#b3d9b3',
'#cccccc',
'#e59a9c',
'#d9d1a5',
'#c9bede',
]
const barColors = [
'#1f77b4',
'#8c564b',
'#ff7f0e',
'#e377c2',
'#2ca02c',
'#7f7f7f',
'#d62728',
'#bcbd22',
'#9467bd',
]
const records = [
{
id: 1,
title: 'Project Task 1',
developer: 'bear.xiong',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/bear.jpg',
start: '2024-07-24',
end: '2024-07-26',
progress: 31,
priority: 'P0',
},
{
id: 2,
title: 'Project Task 2',
developer: 'wolf.lang',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/wolf.jpg',
start: '07/25/2024',
end: '07/28/2024',
progress: 60,
priority: 'P0',
},
{
id: 3,
title: 'Project Task 3',
developer: 'rabbit.tu',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/rabbit.jpg',
start: '2024-07-28',
end: '2024-08-01',
progress: 100,
priority: 'P1',
},
{
id: 1,
title: 'Project Task 4',
developer: 'cat.mao',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/cat.jpg',
start: '2024-07-31',
end: '2024-08-03',
progress: 31,
priority: 'P0',
},
{
id: 2,
title: 'Project Task 5',
developer: 'bird.niao',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/bird.jpeg',
start: '2024-08-02',
end: '2024-08-04',
progress: 60,
priority: 'P0',
},
{
id: 6,
title: 'Project Task 5',
developer: 'bird.niao',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/bird.jpeg',
start: '2024-08-02',
end: '2024-08-04',
progress: 60,
priority: 'P0',
},
{
id: 3,
title: 'Project Task 6',
developer: 'flower.hua',
avatar:
'https://lf9-dp-fe-cms-tos.byteorg.com/obj/bit-cloud/VTable/custom-render/flower.jpg',
start: '2024-08-03',
end: '2024-08-10',
progress: 100,
priority: 'P1',
},
]
const columns = [
{
field: 'title',
title: 'TASK',
width: '200',
mergeCell: true,
headerStyle: {
textAlign: 'center',
fontSize: 20,
fontWeight: 'bold',
color: 'white',
bgColor: '#1c202c',
},
style: {
bgColor: '#1c202c',
},
customLayout: args => {
const { table, row, col, rect } = args
const taskRecord = table.getCellOriginRecord(col, row)
const { height, width } = rect ?? table.getCellRect(col, row)
const container = new VRender.Group({
y: 10,
x: 20,
height: height - 20,
width: width - 40,
fill: 'white',
display: 'flex',
flexDirection: 'column',
cornerRadius: 30,
})
const developer = new VRender.Text({
text: taskRecord.developer,
fontSize: 16,
fontFamily: 'sans-serif',
fill: barColors[args.row],
fontWeight: 'bold',
maxLineWidth: width - 120,
boundsPadding: [10, 0, 0, 0],
alignSelf: 'center',
})
container.add(developer)
const days = new VRender.Text({
text: `${tools.formatDate(
new Date(taskRecord.start),
'mm/dd'
)}-${tools.formatDate(new Date(taskRecord.end), 'mm/dd')}`,
fontSize: 12,
fontFamily: 'sans-serif',
fontWeight: 'bold',
fill: 'black',
boundsPadding: [10, 0, 0, 0],
alignSelf: 'center',
})
container.add(days)
return {
rootContainer: container,
expectedWidth: 160,
}
},
},
]
const option = {
records,
taskListTable: {
columns,
tableWidth: 'auto',
theme: {
underlayBackgroundColor: '#1c202c',
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
// bgColor: '#EEF1F5'
},
bodyStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 16,
color: '#4D4D4D',
bgColor: '#FFF',
},
},
},
frame: {
outerFrameStyle: {
borderLineWidth: 0,
borderColor: 'red',
cornerRadius: 0,
},
// verticalSplitLineHighlight: {
// lineColor: 'green',
// lineWidth: 3
// }
},
grid: {
backgroundColor: '#1c202c',
// horizontalLine: {
// lineWidth: 2,
// lineColor: '#d5d9ee',
// },
verticalLine: {
lineWidth: 1,
lineColor: '#d5d9ee',
lineDash: [8, 4],
},
},
taskList: {
backgroundColor: '#1c202c',
headerStyle: {
borderColor: '#e1e4e8',
borderLineWidth: 0,
fontSize: 18,
fontWeight: 'bold',
color: 'red',
},
},
headerRowHeight: 60,
rowHeight: 100,
taskBar: {
resizable: false,
moveable: false,
startDateField: 'start',
endDateField: 'end',
progressField: 'progress',
barStyle: { width: 60 },
customLayout: args => {
const colorLength = barColors.length
const {
width,
height,
index,
startDate,
endDate,
taskDays,
progress,
taskRecord,
ganttInstance,
} = args
const container = new VRender.Group({
width,
height,
// cornerRadius: 30,
fill: {
gradient: 'linear',
x0: 0,
y0: 0,
x1: 1,
y1: 0,
stops: [
{
offset: 0,
color: barColors0[index % colorLength],
},
{
offset: 0.5,
color: barColors[index % colorLength],
},
{
offset: 1,
color: barColors0[index % colorLength],
},
],
},
display: 'flex',
// flexDirection: 'column',
flexWrap: 'nowrap',
})
const containerLeft = new VRender.Group({
height: 60,
width: 60,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around',
// fill: 'red'
})
container.add(containerLeft)
const avatar = new VRender.Image({
width: 50,
height: 50,
image: taskRecord.avatar,
cornerRadius: 25,
})
containerLeft.add(avatar)
const containerCenter = new VRender.Group({
height: 60,
width: width - (width >= 120 ? 120 : 60),
display: 'flex',
// flexDirection: 'column',
// alignItems: 'left'
})
container.add(containerCenter)
const checkbox = new VRender.CheckBox(checked.value)
container.add(checkbox)
checkbox.addEventListener('click', event => {
console.log(event, checkbox, 'event')
})
const developer = new VRender.Text({
text: taskRecord.developer,
fontSize: 16,
fontFamily: 'sans-serif',
fill: 'white',
fontWeight: 'bold',
maxLineWidth: width - (width >= 120 ? 120 : 60),
boundsPadding: [10, 0, 0, 0],
})
containerCenter.add(developer)
const days = new VRender.Text({
text: `${taskDays}`,
fontSize: 13,
fontFamily: 'sans-serif',
fill: 'white',
boundsPadding: [10, 0, 0, 0],
})
containerCenter.add(days)
if (width >= 120) {
const containerRight = new VRender.Group({
cornerRadius: 20,
fill: 'white',
height: 40,
width: 40,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center', //
boundsPadding: [10, 0, 0, 0],
})
container.add(containerRight)
const progressText = new VRender.Text({
text: `${progress}%`,
fontSize: 12,
fontFamily: 'sans-serif',
fill: 'black',
alignSelf: 'center',
fontWeight: 'bold',
maxLineWidth: (width - 60) / 2,
boundsPadding: [0, 0, 0, 0],
})
containerRight.add(progressText)
}
return {
rootContainer: container,
// renderDefaultBar: true
// renderDefaultText: true
}
},
hoverBarStyle: {
cornerRadius: 0,
barOverlayColor: '#f0f0',
},
},
timelineHeader: {
backgroundColor: '#1c202c',
colWidth: 100,
// verticalLine: {
// lineColor: 'red',
// lineWidth: 1,
// lineDash: [4, 2]
// },
// horizontalLine: {
// lineColor: 'green',
// lineWidth: 1,
// lineDash: [4, 2]
// },
scales: [
{
unit: 'day',
step: 1,
format(date) {
return date.dateIndex.toString()
},
customLayout: args => {
const colorLength = barColors.length
const {
width,
height,
index,
startDate,
endDate,
days,
dateIndex,
title,
ganttInstance,
} = args
console.log(width, height, 'height')
const container = new VRender.Group({
width,
height,
fill: '#1c202c',
display: 'flex',
flexDirection: 'row',
flexWrap: 'nowrap',
})
const containerLeft = new VRender.Group({
height,
width: 30,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around',
// fill: 'red'
})
container.add(containerLeft)
const avatar = new VRender.Image({
width: 20,
height: 30,
image:
'<svg t="1724675965803" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4299" width="200" height="200"><path d="M53.085678 141.319468C23.790257 141.319468 0 165.035326 0 194.34775L0 918.084273C0 947.295126 23.796789 971.112572 53.085678 971.112572L970.914322 971.112572C1000.209743 971.112572 1024 947.396696 1024 918.084273L1024 194.34775C1024 165.136896 1000.203211 141.319468 970.914322 141.319468L776.827586 141.319468 812.137931 176.629813 812.137931 88.275862C812.137931 68.774506 796.328942 52.965517 776.827586 52.965517 757.32623 52.965517 741.517241 68.774506 741.517241 88.275862L741.517241 176.629813 741.517241 211.940158 776.827586 211.940158 970.914322 211.940158C961.186763 211.940158 953.37931 204.125926 953.37931 194.34775L953.37931 918.084273C953.37931 908.344373 961.25643 900.491882 970.914322 900.491882L53.085678 900.491882C62.813237 900.491882 70.62069 908.306097 70.62069 918.084273L70.62069 194.34775C70.62069 204.087649 62.74357 211.940158 53.085678 211.940158L247.172414 211.940158C266.67377 211.940158 282.482759 196.131169 282.482759 176.629813 282.482759 157.128439 266.67377 141.319468 247.172414 141.319468L53.085678 141.319468ZM211.862069 176.629813C211.862069 196.131169 227.671058 211.940158 247.172414 211.940158 266.67377 211.940158 282.482759 196.131169 282.482759 176.629813L282.482759 88.275862C282.482759 68.774506 266.67377 52.965517 247.172414 52.965517 227.671058 52.965517 211.862069 68.774506 211.862069 88.275862L211.862069 176.629813ZM1024 353.181537 1024 317.871192 988.689655 317.871192 35.310345 317.871192 0 317.871192 0 353.181537 0 441.457399C0 460.958755 15.808989 476.767744 35.310345 476.767744 54.811701 476.767744 70.62069 460.958755 70.62069 441.457399L70.62069 353.181537 35.310345 388.491882 988.689655 388.491882 953.37931 353.181537 953.37931 441.457399C953.37931 460.958755 969.188299 476.767744 988.689655 476.767744 1008.191011 476.767744 1024 460.958755 1024 441.457399L1024 353.181537ZM776.937913 582.62069C796.439287 582.62069 812.248258 566.811701 812.248258 547.310345 812.248258 527.808989 796.439287 512 776.937913 512L247.172414 512C227.671058 512 211.862069 527.808989 211.862069 547.310345 211.862069 566.811701 227.671058 582.62069 247.172414 582.62069L776.937913 582.62069ZM247.172414 688.551724C227.671058 688.551724 211.862069 704.360713 211.862069 723.862069 211.862069 743.363425 227.671058 759.172414 247.172414 759.172414L600.386189 759.172414C619.887563 759.172414 635.696534 743.363425 635.696534 723.862069 635.696534 704.360713 619.887563 688.551724 600.386189 688.551724L247.172414 688.551724ZM776.827586 211.940158 741.517241 176.629813 741.517241 247.328574C741.517241 266.829948 757.32623 282.638919 776.827586 282.638919 796.328942 282.638919 812.137931 266.829948 812.137931 247.328574L812.137931 176.629813 812.137931 141.319468 776.827586 141.319468 247.172414 141.319468C227.671058 141.319468 211.862069 157.128439 211.862069 176.629813 211.862069 196.131169 227.671058 211.940158 247.172414 211.940158L776.827586 211.940158ZM282.482759 176.629813C282.482759 157.128439 266.67377 141.319468 247.172414 141.319468 227.671058 141.319468 211.862069 157.128439 211.862069 176.629813L211.862069 247.328574C211.862069 266.829948 227.671058 282.638919 247.172414 282.638919 266.67377 282.638919 282.482759 266.829948 282.482759 247.328574L282.482759 176.629813Z" fill="#389BFF" p-id="4300"></path></svg>',
})
containerLeft.add(avatar)
const containerCenter = new VRender.Group({
height,
width: width - 30,
display: 'flex',
flexDirection: 'column',
// alignItems: 'left'
})
container.add(containerCenter)
const dayNumber = new VRender.Text({
text: String(dateIndex).padStart(2, '0'),
fontSize: 20,
fontWeight: 'bold',
fontFamily: 'sans-serif',
fill: 'white',
textAlign: 'right',
maxLineWidth: width - 30,
boundsPadding: [15, 0, 0, 0],
})
containerCenter.add(dayNumber)
const weekDay = new VRender.Text({
text: tools.getWeekday(startDate, 'short').toLocaleUpperCase(),
fontSize: 12,
fontFamily: 'sans-serif',
fill: 'white',
boundsPadding: [0, 0, 0, 0],
})
containerCenter.add(weekDay)
return {
rootContainer: container,
//renderDefaultText: true
}
},
},
],
},
minDate: '2024-07-20',
maxDate: '2024-08-30',
markLine: [
{
date: '2024-07-29',
style: {
lineWidth: 1,
lineColor: 'blue',
lineDash: [8, 4],
},
},
{
date: '2024-08-17',
style: {
lineWidth: 2,
lineColor: 'red',
lineDash: [8, 4],
},
},
],
scrollStyle: {
scrollRailColor: 'RGBA(246,246,246,0)',
visible: 'focus',
width: 6,
scrollSliderCornerRadius: 2,
scrollSliderColor: 'rgba(255,255,255,0.25)',
},
underlayBackgroundColor: '#1c202c',
}
const range = ref()
const value = ref('month')
onMounted(() => {
nextTick(() => {
ganttInstance = new Gantt(document.getElementById('tableContainer'), option)
window['ganttInstance'] = ganttInstance
})
})
</script>
<template>
<div class="flex flex-col bg-[#1c202c] w-h-full">
<div class="relative h-[60px]">
<header-com />
</div>
<div class="z-30 flex flex-1 flex-col gap-2 p-5">
<div class="flex gap-2">
<n-date-picker v-model:value="range" type="daterange" clearable />
<n-radio-group v-model:value="value" name="radiobuttongroup">
<n-radio-button value="day" label="日" />
<n-radio-button value="month" label="月" />
</n-radio-group>
</div>
<div id="tableContainer" class="bg-[#1c202c] w-h-full"></div>
</div>
</div>
</template>

179
src/views/Gantt0305/index.jsx Executable file
View File

@ -0,0 +1,179 @@
import { RouterView } from 'vue-router'
import {
NDatePicker,
NButton,
NFloatButton,
NIcon,
NDrawer,
NDrawerContent,
NTabs,
NTabPane,
NSelect,
} from 'naive-ui'
import { ArrowForward } from '@vicons/ionicons5'
import HeaderCom from '../Content/components/Header/index.vue'
import TaskList from './components/TaskList'
import EventList from './components/EventList'
import NewTask from './components/TaskList/components/NewTask'
import useTask from './components/TaskList/components/NewTask/hooks'
import { useEvent } from './components/EventList/hooks'
import { getSimpList, getSimpTreeList } from '@/api/Gantt'
import { onBeforeMount, nextTick } from 'vue'
export default defineComponent({
setup() {
const show = ref(false)
const { showNewTask } = useTask()
const addNewTask = () => {
showNewTask.value = true
}
async function getSimpListData() {
const res = await getSimpList()
ddList.value = res.data.list
targetId.value = res.data.list[0].id
}
// const ddList = Array.from({ length: 8 }, (_, i) => ({
// label: `DD-${i + 1}`,
// value: `DD-${i + 1}`,
// }))
const ddList = ref([])
const paneClass = `border-1 h-full border-l-0 border-[var(--n-tab-border-color)] !p-2`
const {
showMainEvent,
mainEventData,
targetId,
range,
searchTreeList,
tableData,
} = useEvent()
const addNewMainEvent = async () => {
showMainEvent.value = true
// const res = await addNewMainEvent({ })
mainEventData.value = {}
}
onBeforeMount(() => {
nextTick(async () => {
await getSimpListData()
await searchTreeList()
})
})
return () => (
<div class="flex flex-col bg-[#1c202c] w-h-full">
<div class="relative h-[60px]">
<HeaderCom />
</div>
<div class="z-30 flex flex-1 flex-col gap-4 p-5">
<RouterView />
</div>
<NFloatButton
class="z-40"
left={-10}
bottom={document.body.clientHeight / 2}
shape="square"
onClick={() => {
show.value = true
}}
>
<NIcon>
<ArrowForward />
</NIcon>
</NFloatButton>
<NDrawer
class="h-[100vh] bg-[#1c202cee]"
v-model:show={show.value}
width={document.body.clientWidth - 200}
placement="left"
display-directive={'show'}
>
<NDrawerContent title="事件管理" closable>
<div class="flex h-full flex-col gap-2">
{/* <NTabs
class="h-full"
pane-wrapper-class="h-full"
type="card"
animated
placement="left"
defaultValue={'事件列表'}
>
<NTabPane
class={paneClass}
name="任务列表"
tab="任务列表"
display-directive={'show'}
>
<div class="flex h-full flex-col gap-2">
<div class="flex justify-end gap-2">
<NDatePicker
v-model:value={range.value}
type="daterange"
clearable
/>
<NButton type="primary" onClick={addNewTask}>
添加任务
</NButton>
</div>
<div class="flex-1">
<TaskList />
</div>
</div>
</NTabPane>
<NTabPane
class={paneClass}
name="事件列表"
tab="事件列表"
display-directive={'show'}
> */}
<div class="flex h-full flex-col gap-2">
<div class="flex justify-end gap-2 ">
<NSelect
class="w-[200px]"
v-model:value={targetId.value}
options={ddList.value}
label-field="name"
value-field="id"
></NSelect>
<NDatePicker
v-model:value={range.value}
type="daterange"
clearable
/>
<NButton type="primary" onClick={searchTreeList}>
搜索
</NButton>
<NButton type="primary" onClick={addNewMainEvent}>
添加事件
</NButton>
</div>
<div class="flex-1">
<EventList dd={targetId.value} tableData={tableData.value} />
</div>
</div>
{/* </NTabPane>
</NTabs> */}
{/* <div class="flex justify-end gap-2 ">
<NDatePicker
v-model:value={range.value}
type="daterange"
clearable
/>
</div>
<div class="flex-1">
<TaskList />
</div> */}
</div>
</NDrawerContent>
</NDrawer>
<NewTask v-model:show={showNewTask.value} />
</div>
)
},
})

7
src/views/Gantt0305/index.vue Executable file
View File

@ -0,0 +1,7 @@
<script setup>
import TestJsx from './index.jsx'
</script>
<template>
<test-jsx />
</template>