自定义拖拽列表

 效果图

 DataAnalysis.vue

<template>
  <div class="app-container">
    <div class="operate">
      <el-select class="t_select" v-model="templateName" clearable placeholder="模版" size="default" @clear="clearTemplateData" @change="templateData">
        <el-option v-for="item in templateList" :key="item.id" :label="item.templateName" :value="item.id"/>
      </el-select>
      <el-button @click="setTemplate" type="primary" size="default">保存模版</el-button>
      <el-button @click="excelExport" type="primary" size="default">Excel导出</el-button>
    </div>

    <main>
      <!-- 所有属性 -->
      <Draggable
          class="item-container item-container1"
          group="drag"
          v-model:list="allProps"
          item-key="label"
      >
        <template #item="{ element }">
          <div class="item">{{ element.label }}</div>
        </template>
      </Draggable>

      <!-- 多级表头子属性:基础表头① -->
      <Draggable
          class="item-container item-container2"
          group="drag"
          v-model:list="baseProps"
          item-key="label"
      >
        <template #item="{ element }">
          <div class="item">{{ element.label }}</div>
        </template>
      </Draggable>

      <!-- 多级表头:③  -->
      <Draggable
          class="item-container item-container3"
          group="drag"
          v-model:list="multiLevelProps"
          item-key="label"
      >
        <template #item="{ element }">
          <div class="item">{{ element.label }}</div>
        </template>
      </Draggable>

      <!-- 分组属性:② -->
      <Draggable
          class="item-container item-container4"
          group="drag"
          v-model:list="groupProps"
          item-key="label"
      >
        <template #item="{ element }">
          <div class="item">{{ element.label }}</div>
        </template>
      </Draggable>

      <!-- 表格展示数据     -->
      <el-table :data="brr" border :span-method="objectSpanMethod">
        <el-table-column
            v-for="item in groupProps"
            :prop="item.prop"
            :label="item.label"/>
        <MultiHeaders
            :multiHeaders="multiHeaderValues"
            :baseProps="baseProps"
            v-if="multiLevelProps && multiLevelProps.length"/>
        <el-table-column
            v-for="item in baseProps"
            :prop="item.prop"
            :label="item.label"
            v-else/>
      </el-table>

    </main>

    <el-dialog v-model="isShowTemplate" title="设置模版" width="30%">
      <el-form label-width="100px" :model="tForm" style="max-width: 460px">
        <el-form-item label="模版名称">
          <el-input v-model="tForm.templateName"/>
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="isShowTemplate = false">取消</el-button>
          <el-button type="primary" @click="saveTemplate">保存</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import {ref,onMounted, computed,watch,getCurrentInstance} from "vue"
import Draggable from "vuedraggable"
import MultiHeaders from "./MultiHeaders.vue"
import {useRoute} from "vue-router";
import {useStore} from "vuex";
import {useStorage} from "@vueuse/core";
import {parseTime} from "@/utils/ruoyi";
import {getAnalysisTemplate,saveAnalysisTemplate} from "@/api/common/dataAnalysis";

const route = useRoute()
document.title = '数据分析'
const store = useStore()
const {proxy} = getCurrentInstance()
//全部表头属性
const allProps = ref([
  {
    label: "项目编码",
    prop: "businessNo",
  },
  {
    label: "箱号",
    prop: "caseNum",
  },
  {
    label: "客户",
    prop: "customerName",
  },
  {
    label: "费用类型",
    prop: "feeCodeName",
  },
  {
    label: "提单号",
    prop: "mblNo",
  },
  {
    label: "币种",
    prop: "currencyName",
  },
  {
    label: "数量",
    prop: "num",
  },
  {
    label: "金额",
    prop: "shouldReceipt",
  },
])
//基础表头属性①
const baseProps = ref([])
//多级表头属性③
const multiLevelProps = ref([])
//分组表头属性②
const groupProps = ref([])
//需要分组的数据
const data = ref([])

const multiHeaderValues = computed(() => {
  //获取去重后的multiLevelProps的值
  return multiLevelProps.value.map((item) => {
    return findGroup(item.prop)
  })
})
//选中的模版
const template = ref([])

onMounted(() => {
  queryData()
  getTemplate()
})

let index = ref(0)

watch([()=>groupProps.value,()=>baseProps.value,()=>multiLevelProps.value,()=>template.value],()=>{
  addGroup()
  index.value = 0
},{
  deep:true
})

function addGroup(){
  //获取brr和arr
  getBrr()
  //排序
  // arr.value.forEach(item=>{
  //   data.value.sort(comprisonFunction(item))
  // })
  data.value.sort(comprisonFunction(arr.value[0]))
  brr.value.sort(comprisonFunction(arr.value[0]))

  //对brr去重
  goWeight()

  //分层级合并列
  setTabelRowSpan(brr.value, arr.value);

  //合并baseProps中字段
  mergeValue()
  // console.log(brr.value)
  // console.log(results.value)
  // console.log(baseProps.value)
  // console.log(groupProps.value)
  // console.log(multiHeaderValues.value)
  // console.log(multiLevelProps.value)
  // console.table(brr.value)
}

const brr = ref([])//分组列去重后的数据(groupProps中值的k:v)
const arr = ref([])//要分组的Table属性(groupProps中值的key)
function getBrr(){
  brr.value = []
  arr.value = []
  data.value.forEach((item) => {
    let obj={}
    for (let i = 0; i < groupProps.value.length; i++) {
      let tempObj = {}
      tempObj[groupProps.value[i].prop] = item[groupProps.value[i].prop]
      obj = {...obj,...tempObj}
      if ( arr.value.length < groupProps.value.length){
        arr.value.push(groupProps.value[i].prop)
      }
    }
    brr.value.push(obj)
  })
}

function goWeight(){
  let obj = {};
  let tempArr=brr.value
  brr.value = tempArr.reduce((curr, next) => {
    let str=''
    arr.value.forEach(item=>{
      str+=next[item]
    })
    obj[str] ? '' : obj[str]=curr.push(next);
    return curr;
  }, []);
}

function setTabelRowSpan(tableData, fieldArr){
  let lastItem = {};
  fieldArr.forEach((field, index) => {
    tableData.forEach(item => {
      item.mergeCell = fieldArr;
      const rowSpan = `rowspan_${field}`
      //判断是否合并到上个单元格。
      if(fieldArr.slice(0, index + 1).every(e => lastItem[e] === item[e])){
        //是:合并行
        item[rowSpan] = 0;
        lastItem[rowSpan] += 1;
      }else{
        //否:完成一次同类合并。lastItem重新赋值,进入下一次合并计算。
        item[rowSpan] = 1;
        lastItem = item;
      }
    })
  })
}

function objectSpanMethod({ row, column, rowIndex, columnIndex }) {
  //判断当前单元格是否需要合并
  if (row.mergeCell.includes(column.property)) {
    const rowspan = row[`rowspan_${column.property}`]
    if (rowspan) {
      return {rowspan: rowspan, colspan: 1};
    } else {
      return {rowspan: 0, colspan: 0};
    }
  }
}

function mergeValue(){
  if(multiHeaderValues.value.length>0){
    butes()
  }
  brr.value.forEach(item=>{
    data.value.forEach(temp=>{
      let isTrue = true
      arr.value.forEach(arrTmp=>{
        if (temp[arrTmp]!==item[arrTmp]){
          isTrue = false
        }
      })

      let m_level = ''
      multiLevelProps.value.forEach(mpItem=>{
        m_level = m_level === '' ? temp[mpItem.prop] : m_level+'_'+temp[mpItem.prop]
      })
      if (isTrue){//data中每个元素(即对象)的groupProps属性值相等才会合并
        baseProps.value.forEach(bpItem=>{
          if ((typeof (temp[bpItem.prop])==='number')){//数据类型累加
            if(multiLevelProps.value.length!==0){
              results.value.forEach(rItem=>{

                if(m_level === rItem){
                  if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
                    if (item[bpItem.prop+"_"+rItem]===undefined){
                      item[bpItem.prop+"_"+rItem]=temp[bpItem.prop]
                    }else {
                      item[bpItem.prop+"_"+rItem]=item[bpItem.prop+"_"+rItem] + temp[bpItem.prop]
                    }
                  }
                }
              })

            }else{
              if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
                if (item[bpItem.prop]===undefined){
                  item[bpItem.prop]=temp[bpItem.prop]
                }else {
                  item[bpItem.prop]=item[bpItem.prop] + temp[bpItem.prop]
                }
              }
            }
            return
          }
          //字符串类型拼接
          if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
            if(multiLevelProps.value.length!==0){

              results.value.forEach(rItem=>{

                if(m_level === rItem){
                  if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
                    if (item[bpItem.prop+"_"+rItem]===undefined){
                      item[bpItem.prop+"_"+rItem]=temp[bpItem.prop]
                    }else {
                      if (!item[bpItem.prop+"_"+rItem].includes(temp[bpItem.prop]))
                        item[bpItem.prop+"_"+rItem]=item[bpItem.prop+"_"+rItem] +','+ temp[bpItem.prop]
                    }
                  }
                }
              })

            }else {
              if (temp[bpItem.prop] !== '' && temp[bpItem.prop] !== null) {
                if (item[bpItem.prop] === undefined) {
                  item[bpItem.prop] = temp[bpItem.prop]
                } else {
                  if (!item[bpItem.prop].includes(temp[bpItem.prop]))
                    item[bpItem.prop] = item[bpItem.prop] + ',' + temp[bpItem.prop]
                }
              }
            }
          }
        })
      }
    })
  })
}

//递归循环数据
let results = ref([])
function butes(){
  const m_len = multiHeaderValues.value.length
  if(m_len>0){
    if(m_len >= 2){
      results.value = recurse(multiHeaderValues.value[index.value],multiHeaderValues.value[++index.value],m_len,multiHeaderValues.value)
    }else{
      results.value = multiHeaderValues.value[0]
    }
    return results.value
  }
}

function recurse(arr1,arr2,len,arr){
  let newArr = []
  arr1.forEach(item=>{
    arr2.forEach(item2=>{
      newArr.push(item+'_'+item2)
    })
  })
  index.value++
  if(index.value < len){
    return recurse(newArr,arr[index.value],len,arr)
  }else{
    return newArr
  }
}

function comprisonFunction (propName) {
  return function (object1, object2) {
    let value1 = object1[propName];
    let value2 = object2[propName];
    if (value1 > value2 ) {
      return -1;
    } else if(value1 < value2) {
      return 1;
    } else {
      return 0;
    }
  }

}

// const obj = ref({})
// const groupArr = ref([])
// function getGroupVal() {
//   obj.value = {}
//   groupArr.value = []
//   for (let i = 0; i < groupProps.value.length; i++) {
//     obj.value[groupProps.value[i].prop + 'Arr'] = [];
//     groupArr.value[i] = []
//     data.value.forEach(item => {
//       if (!obj.value[groupProps.value[i].prop + 'Arr'].includes(item[groupProps.value[i].prop])) {
//         obj.value[groupProps.value[i].prop + 'Arr'].push(item[groupProps.value[i].prop]);
//         let abc = {}
//         abc[groupProps.value[i].prop] = item[groupProps.value[i].prop]
//         // console.log(abc)
//         groupArr.value[i].push(abc)
//       }
//     })
//   }
// }

function findGroup(prop) {
  const set = new Set()
  data.value.forEach(item => {
    set.add(item[prop])
  })
  //去重后再转换成数组
  return Array.from(set)
}

//导出excel时数据表头
function excelHeader(){
  const newArr = []
  results.value.forEach(rItem=>{
    baseProps.value.forEach(item=>{
      let arr = []
      arr.push(item.prop+"_"+rItem)
      newArr.push(arr)
    })
  })
  return newArr
}

function generateArray() {
  const rows = [] //多重表头例如[[1,2,3][4,5]]
  const ranges = [] //合并单元格
  const mainLength = groupProps.value.length
  //所有多重表头(包括最后一行)
  const headers = multiHeaderValues.value.concat([baseProps.value])
  //数据表头
  const mHeader = excelHeader()
  //所有多重表头长度乘积
  const allCols = headers.reduce((res, cur) => {
    return res * cur.length
  }, 1)
  let colspan = allCols // 4 总共需要的单元格
  for (let i = 0; i < headers.length; i++) {
    const curRow = headers[i]
    //需要合并的单元格数量
    colspan = colspan / curRow.length
    //需要重复的次数
    const cycleTime = allCols / colspan / curRow.length
    let row = new Array(mainLength).fill('')
    if (i === headers.length - 1) {
      row = [...groupProps.value]
    }
    for (let k = 0; k < cycleTime; k++) {
      curRow.forEach((val, index) => {
        const C = index * colspan + k * curRow.length * colspan + mainLength
        const range = {s: {r: i, c: C}, e: {r: i, c: C + colspan - 1}}
        if (colspan > 1) ranges.push(range)
        row.push(val)
        for (let j = 1; j < colspan; j++) {
          row.push("")
        }
      })

    }
    rows.push(row)
  }
  // for(let m=0;m<mainLength;m++){
  //   ranges.push(
  //     {
  //       s:{
  //         r:headers.length-1,
  //         c:m
  //       },
  //       e:{
  //         r:0,
  //         c:m
  //       }
  //     }
  //   )
  // }
  return {
    ranges,
    rows,
    mHeader
  }
}

//报表导出
function excelExport() {
  import("@/utils/Export2Excel").then((excel) => {
    // const tHeader = groupProps.value.map((item)=>item.name)
    // const filterVal = groupProps.value.map((item)=>item.prop)
    const {ranges, rows,mHeader} = generateArray()
    const tHeader = rows[rows.length - 1].map((item) => item.label)
    // const filterVal = rows[rows.length - 1].map((item) => item.prop)
    const multiHeader = rows.slice(0, -1)
    const gHeader = groupProps.value.map((item)=>item.prop)
    const filterVal = gHeader.concat(mHeader.map((item)=>item[0]))
    const TData = formatJson(filterVal, brr.value)

    excel.export_json_to_excel({
      header: tHeader,
      multiHeader: multiHeader,
      data: TData,
      filename: "数据分析",
      merges: ranges,
      autoWidth: true,
      bookType: "xlsx",
    })
  })
}

function formatJson(filterVal, jsonData) {
  return jsonData.map((v) =>
      filterVal.map((j) => {
        return v[j]
      })
  )
}
const myOrigin = window.location.origin
function queryData(){
  window.addEventListener('message',function (e) {
    if (e.origin === myOrigin) {
      document.title = e.data.title+'-数据分析'
      if (e.data.isPush) {
        data.value = JSON.parse(e.data.data)
        allProps.value = JSON.parse(e.data.allProps)
        data.value.forEach(item=>{
          //业务类型
          if(item.businessType === 0){
            item.businessType = '海运出口'
          } else if(item.businessType === 1){
            item.businessType = '空运出口'
          } else if(item.businessType === 2){
            item.businessType = '海运进口'
          }
          //单据日期
          if(item.glMarineSpecialOutDTO.id){
            item.expectSailingStartDate = parseTime(item.glMarineSpecialOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          } else if(item.glMarineImportOutDTO.id){
            item.expectSailingStartDate = parseTime(item.glMarineImportOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          } else if(item.glAirExportOutDTO.id){
            item.expectSailingStartDate = parseTime(item.glAirExportOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          }
          //装运日期
          if(item.glMarineSpecialOutDTO.shipmentDate){
            item.shipmentDate = parseTime(item.glMarineSpecialOutDTO.shipmentDate, "{y}-{m}-{d}")
          }
          //送达日期
          if(item.glMarineSpecialOutDTO.deliveryDate){
            item.deliveryDate = parseTime(item.glMarineSpecialOutDTO.deliveryDate, "{y}-{m}-{d}")
          }
          //预计到港日期
          if(item.glMarineSpecialOutDTO.expectSailingArrivalDate){
            item.expectSailingArrivalDate = parseTime(item.glMarineSpecialOutDTO.expectSailingArrivalDate, "{y}-{m}-{d}")
          } else if(item.glMarineImportOutDTO.id){
            item.expectSailingArrivalDate = parseTime(item.glMarineImportOutDTO.expectSailingArrivalDate, "{y}-{m}-{d}")
          } else if(item.glAirExportOutDTO.id){
            item.expectSailingArrivalDate = parseTime(item.glAirExportOutDTO.expectSailingArrivalDate, "{y}-{m}-{d}")
          }
          //预计开航日期
          if(item.glMarineSpecialOutDTO.expectSailingStartDate){
            item.expectSailingStartDate = parseTime(item.glMarineSpecialOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          } else if(item.glMarineImportOutDTO.id){
            item.expectSailingStartDate = parseTime(item.glMarineImportOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          } else if(item.glAirExportOutDTO.id){
            item.expectSailingStartDate = parseTime(item.glAirExportOutDTO.expectSailingStartDate, "{y}-{m}-{d}")
          }
        })
        let obj = {
          data:data.value,
          allProps:allProps.value
        }
        useStorage(e.data.tableName,JSON.stringify(obj))
      } else {
        let arr = JSON.parse(useStorage(e.data.tableName).value)
        data.value = arr.data
        allProps.value = arr.allProps
      }
    }
  })
}

//保存模版
//被选中的模版
const templateName = ref('')
const isShowTemplate = ref(false)
const tForm = ref({
  templateName:''
})

//模版弹窗
function setTemplate(){
  isShowTemplate.value = true
}

//所有的模版
const templateList = ref([])

//从数据库中获取当前用户的所有模版数据
function getTemplate(){
  getAnalysisTemplate({
    employeeId: store.state.user.info.id,
    pageNum:1,
    pageSize:100
  }).then(res => {
    templateList.value = res.data.records
  })
}

//将模版数据保存数据库
function saveTemplate(){
  let params = {
    "employeeId": store.state.user.info.id,
    "templateName": tForm.value.templateName,
    "data":JSON.stringify({
      "baseProps":JSON.stringify(baseProps.value),
      "groupProps": JSON.stringify(groupProps.value),
      "multiLevelProps": JSON.stringify(multiLevelProps.value),
      "multiHeaderValues": JSON.stringify(multiHeaderValues.value)
    })
  }
  saveAnalysisTemplate(params).then(response => {
    isShowTemplate.value = false
    getTemplate()
    proxy.$modal.msgSuccess("保存成功");
  })
}

//根据模版获取数据生成数据报表
function templateData(val){
  if(val !== undefined){
    templateList.value.forEach(item=>{
      if(item.id === val){
        template.value = JSON.parse(item.data)
      }
    })
    if(template.value.hasOwnProperty("baseProps")){
      baseProps.value = JSON.parse(template.value.baseProps)
      groupProps.value = JSON.parse(template.value.groupProps)
      multiLevelProps.value = JSON.parse(template.value.multiLevelProps)
      multiHeaderValues.value = JSON.parse(template.value.multiHeaderValues)
    }
  }
}

function clearTemplateData(){
  template.value =[]
  baseProps.value = []
  groupProps.value = []
  multiLevelProps.value = []
  multiHeaderValues.value = []
}

</script>

<style lang="scss" scoped>
.item {
  background: #333;
  padding: 5px;
}

.item-container {
  background: #eee;
  padding: 10px;
  display: flex;
  min-height: 51px;
  align-self: flex-start;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
  cursor: move;
  color: #ccc;
}

.item-container1 {
  grid-column: 1/3;
}

main {
  display: grid;
  gap: 10px;
  // margin-top: 10px;
  grid-template-columns: 200px 1fr;
}
.operate{
  margin-bottom: 5px;
  .t_select{
    margin-right: 12px;
  }
}
</style>

MultiHeaders.vue

<template>
<!--  <el-table-column v-for="(item,index) in headers" :label="item" >-->
<!--    <MultiHeaders  v-for="item2 in subHeaders"  :multiHeaders="subHeaders" v-if="subHeaders&&subHeaders.length" :baseProps="baseProps" :resultsProps="resultsProps" ></MultiHeaders>-->
<!--    <el-table-column v-else v-for="items in baseProps" :label="items.prop" :prop="items.prop"></el-table-column>-->
<!--  </el-table-column>-->
    <el-table-column v-for="item in headers"  :label="item.name">
      <template v-if="item.child.length>0">
        <multi-header v-if="item.child.length>0" :child="item.child" :baseProps="baseProps" :upProp="item.prop"></multi-header>
      </template>
      <el-table-column v-else v-for="bItem in baseProps" :label="bItem.label" :prop="bItem.prop+'_'+item.prop" ></el-table-column>
    </el-table-column>
</template>

<script setup>
import {computed, watch} from "vue"
import MultiHeader from "@/views/configTable/MultiHeader.vue";
const props = defineProps({
  multiHeaders: { //右边款字段的值=table表头
    typeof: Array,
    default: [],
  },
  baseProps: { // 左上框数据
    typeof: Array,
    default: [],
  },
  upProp: {
    typeof: String,
    default: '',
  },
})
function childData(list,i){
  const arr = []
  //最后一个数组
  if(i<list.length){
    list[i].forEach(item=>{
      const obj = {
        name:'',
        prop:'',
        child:[]
      }
      obj['name'] = item
      obj['prop'] = item
      obj['child'] = []
      arr.push(obj)
    })
  }

  return arr
}
function transListDataToTreeData(list,i) {
  //共3条数据
  const arr = list[i] // 第一层数组 1
  if(arr){
    const news = []
    arr.forEach(item=>{
      let obj = {
        name:item,
        prop:item,
        child:[]
      }
      const child = childData(list,i+1)
      if(child.length > 0){
        obj.child = transListDataToTreeData(list,i+1)

      }else{
        obj.child = child
      }
      news.push(obj)
    })
    return news
  }else{
    return []
  }
}

function sliceArr(arr,size){
  const res = []
  for (let i=0;i<Math.ceil(arr.length/size);i++){
    let start = i*size
    let end = start + size
    res.push(arr.slice(start,end))
  }
  return res
}

const headers = computed(() => {
  // return props.multiHeaders[0]
  return transListDataToTreeData(props.multiHeaders,0)
})
const subHeaders = computed(() => {
  return props.multiHeaders.slice(1)
})
watch(
    () => headers.value,
  (value) => {
    console.log(value)
  }
)
</script>

<style lang="scss" scoped></style>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ahwangzc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值