如图👇

<template>
<NormalDialog ref="dialogRef" :visiable="centerDialogVisible" title="施工计划在线维护" @update:visible="centerDialogVisible = false" :isShowScreenBtn="true" dialogWidth='1100px' :contentStyle="{ 'min-height': '150px' }">
<template #content>
<div class="main">
<el-button size="small" type="primary" style="padding: 7px 11px;" @click="addMasterTask" v-if="editable">添加主任务</el-button>
<el-table
:key="tKey"
ref="xSortTable"
:data="tableData"
style="width: 100%;"
row-key="id"
v-loading="loading"
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
header-row-class-name="xmlist-head-tr-class"
row-class-name="xmlist-head-tr-class"
:border="true"
class="xmlist-table">
<el-table-column width="50" type="" header-align="center" align="center" v-if="editable">
<template #header>
<el-tooltip effect="dark" content="按住后可以上下拖动排序!" placement="top">
<el-icon><WarningFilled /></el-icon>
</el-tooltip>
</template>
<template #default>
<div class="drag-btn">
<el-icon><Rank /></el-icon>
</div>
</template>
</el-table-column>
<el-table-column prop="pmPrjInfo.agreementCode" label="任务名称" min-width="200" header-align="center" align="left">
<template #default="scope">
<el-input v-if="editable" v-model="scope.row.taskName" size="small" :maxlength="40"></el-input>
<div style="flex: 1" v-else>{{ scope.row.taskName }}</div>
</template>
</el-table-column>
<el-table-column label="预计开始时间" min-width="190" header-align="center" align="left">
<template #default="scope">
<el-date-picker type="date" v-if="editable" v-model="scope.row.startDate" size="small" value-format="YYYY-MM-DD"></el-date-picker>
<div style="flex: 1" v-else>{{ scope.row.startDate }}</div>
</template>
</el-table-column>
<el-table-column label="预计结束时间" min-width="190" header-align="center" align="left">
<template #default="scope">
<el-date-picker v-if="editable" type="date" v-model="scope.row.endDate" size="small" value-format="YYYY-MM-DD"></el-date-picker>
<div style="flex: 1" v-else>{{ scope.row.endDate }}</div>
</template>
</el-table-column>
<el-table-column label="进度(%)" min-width="190" header-align="center" align="center">
<template #default="scope">
<el-input-number v-if="editable" :precision="0" :controls="false" size="small" :max="100" :min="0" v-model="scope.row.progress" style="width: 100%"></el-input-number>
<div style="flex: 1" v-else>{{ scope.row.progress }}11</div>
</template>
</el-table-column>
<el-table-column prop="status" label="完成状态" min-width="190" header-align="center" align="center">
<template #default="scope">
<div style="flex: 1">{{ scope.row.status }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="150" header-align="center" align="center" fixed="right" v-if="editable">
<template #default="scope">
<div style="flex:1">
<el-button type="primary" link size="small" v-if="scope.row.parent == 0||(scope.row.children&&scope.row.children.length>0)" @click="addTask(scope.row)">添加子任务</el-button>
<el-button type="danger" link size="small" @click="delRow(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="searchLine">
<div class="searchBtn" @click="submit" v-if="editable">提交</div>
</div>
</div>
</template>
</NormalDialog>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, onMounted,toRaw } from 'vue'
import Sortable from "sortablejs"
import { cloneDeep } from 'lodash-es';
import { v1 as uuidv1 } from "uuid"
import { ElMessage } from 'element-plus'
import { NormalDialog } from '@code-front/dynamic-form';
import { Rank, WarningFilled } from '@element-plus/icons-vue'
import { saveSgjh,getSgjh } from "@/service/api/onsale"
interface Props {
tasks?: {
data: any[]
}
editable?: boolean,
prjCode: string
}
const props = withDefaults(defineProps<Props>(), {
tasks: () => ({ data: [] }),
editable: false,
prjCode:""
})
const emit = defineEmits(['submitGant'])
const loading = ref(false)
const tKey = ref(0)
const tableData = ref([])
const activeRows = ref([])
const xSortTable = ref()
const centerDialogVisible = ref(false)
const dialogRef = ref()
// watch([props.prjCode], (oldValue, newValue) => {
// console.log(newValue,newValue,newValue);
// newValue&&getSgjhFn(newValue)
// }, { deep: true })
const addMasterTask = () => {
const obj = { id: uuidv1().replace(/-/g, ''), parent: "0",prjCode: props.prjCode }
tableData.value.push(obj)
nextTick(() => {
tKey.value++
rowDrop()
})
}
const cancelFn = ($evnt) => {
console.log($evnt);
}
const addTask = (row: any) => {
const obj = { id: uuidv1().replace(/-/g, ''), parent: row.id,prjCode: props.prjCode }
tableData.value.forEach(v => {
if (v.id === row.id) {
v.children.push(obj)
}
})
nextTick(() => {
tKey.value++
rowDrop()
})
}
const findIdIndexToDelFn = (list,targetId) => {
return list.filter(item => item.id !== targetId).map(v => ({
...v,
children: findIdIndexToDelFn(v.children || [], targetId)
}))
}
const swapIntegers = (list, targetindex) => {
let result = null;
list.forEach((item,index) => {
if(index == targetindex){
result = item
}
if (condition) {
}
})
return result
return list.filter((item,index) => index == targetindex).map(v => ({
...v,
children: findIdFn(v.children || [], targetindex)
}))
}
const delRow = (row: any) => {
tableData.value = findIdIndexToDelFn(tableData.value,row.id)
nextTick(() => {
tKey.value++
rowDrop()
})
}
const flatToTree = (flatData: any[], rootId = "0") => {
const buildTree = (arr: any[], parent: string) => {
const tree = []
for (let i = 0; i < arr.length; i++) {
if (arr[i].parent == parent) {
const node = { ...arr[i] }
node.children = []
const children = buildTree(arr, arr[i].id)
if (children.length > 0) {
node.children = children
}
tree.push(node)
}
}
return tree
}
return buildTree(flatData, rootId)
}
const flatData = (source: any[]) => {
let result = toRaw(source)
let res: any[] = []
result.forEach(el => {
res.push(el)
el.children && res.push(...flatData(el.children))
})
return res
}
const rowDrop = () => {
nextTick(() => {
const tbody = xSortTable.value.$el.querySelector('.prod-table__body-wrapper tbody')
Sortable.create(tbody, {
handle: '.drag-btn',
animation: 200,
onMove: ({ dragged, related }: any) => {
const currentresult = flatData(tableData.value)
const oldRow = currentresult[dragged.rowIndex]
const newRow = currentresult[related.rowIndex]
if (oldRow.parent === newRow.id || oldRow.id === newRow.parent) {
return false
}
},
onEnd: ({ newIndex, oldIndex }: any) => {
activeRows.value = flatData(tableData.value)
const currRow = activeRows.value.splice(oldIndex, 1)[0]
activeRows.value.splice(newIndex, 0, currRow)
tableData.value = flatToTree(activeRows.value)
nextTick(() => {
tKey.value++
rowDrop()
})
}
})
})
}
const submit = () => {
if (tableData.value.length === 0) {
return ElMessage.warning("请添加任务")
}
let submitData = cloneDeep(tableData.value)
// 设置进度
const deepSetId = (arr: any[], pId: number | string) => {
for (let i = 0; i < arr.length; i++) {
if (pId === 0) {
arr[i].id = i + 1
} else {
arr[i].id = `${pId}.${i + 1}`
}
arr[i]['parent'] = pId
let status = null
if (arr[i].taskProgress > 0 && arr[i].taskProgress < 100) {
status = '进行中'
} else if (arr[i].taskProgress == 100) {
status = '已完成'
} else {
status = '未进行'
}
arr[i]['status'] = status
if (arr[i].children) {
deepSetId(arr[i].children, arr[i].id)
}
}
}
deepSetId(submitData, 0)
let bkData = flatData(submitData)
if (bkData.some(item => !item.taskName)) {
ElMessage.warning("请填写任务名称")
return
}
if (bkData.some(item => !item.startDate)) {
ElMessage.warning("请填预计开始时间")
return
}
if (bkData.some(item => !item.endDate)) {
ElMessage.warning("请填写预计结束时间")
return
}
saveSgjhFn(submitData)
}
// 保存计划实施
const saveSgjhFn = async (submitData) => {
const params = {
planList:getDataFilter(submitData)
}
const res = await saveSgjh(params)
console.log(res.response.data);
const { code,msg } = res.response.data
if (code == 0) {
ElMessage.success("新建成功")
tableData.value = []
dialogRef.value?.closeDialog()
} else {
ElMessage.success(msg)
}
}
// 获取详情
// 获取详情filter
const setDataFilter = (data) => {
return data.map(v => {
if (v.children && v.children.length > 0) {
return {
...v,
parent:v.parentId,
children: setDataFilter(v.children)
}
} else {
return {
...v,
parent:v.parentId,
children: []
}
}
})
}
const getSgjhFn = (prjCode) => {
loading.value = true;
getSgjh({
prjCode
}).then((res) => {
loading.value = false;
const { code,body} = res.response.data
if (code == 0) {
tableData.value = setDataFilter(body.itemList)
nextTick(() => {
tKey.value++
rowDrop()
})
}
})
}
//
const getDataFilter = (data) => {
return data.map(v => {
if (v.children && v.children.length > 0) {
return {
prjCode:v.prjCode,
taskName:v.taskName,
progress:v.progress,
status:v.status,
startDate:v.startDate,
endDate:v.endDate,
children: getDataFilter(v.children)
}
} else {
return {
prjCode:v.prjCode,
taskName:v.taskName,
progress:v.progress,
status:v.status,
startDate:v.startDate,
endDate:v.endDate,
children: []
}
}
})
}
onMounted(() => {
rowDrop()
})
const open = ()=>{
centerDialogVisible.value = true
getSgjhFn(props.prjCode)
}
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.searchLine {
display: flex;
justify-content: center;
margin-top: 10px;
}
.searchBtn {
width: 80px;
height: 32px;
font-size: 15px;
font-weight: 500;
color: #FFFFFF;
background: #4A94FF;
border-radius: 4px;
line-height: 32px;
text-align: center;
cursor: pointer;
margin-left: 10px;
margin-top: 10px;
}
.main{
box-sizing: border-box;
padding: 10px 10px;
}
::v-deep(.cell) {
display: flex;
align-items: center;
}
.drag-btn {
cursor: move;
font-size: 12px;
flex: 1;
}
.sortable-ghost,
.sortable-chosen {
background-color: #dfecfb;
}
</style>