笔记:elment plus table/form 动态添加行 可编辑提交表单

发布于:2025-03-12 ⋅ 阅读:(13) ⋅ 点赞:(0)

如图👇

<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>