效果图
DataAnalysis.vue
<div class="app-container">
<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-button @click="setTemplate" type="primary" size="default">保存模版el-button>
<el-button @click="excelExport" type="primary" size="default">Excel导出el-button>
class="item-container item-container1"
<template #item="{ element }">
<div class="item">{{ element.label }}div>
class="item-container item-container2"
<template #item="{ element }">
<div class="item">{{ element.label }}div>
class="item-container item-container3"
v-model:list="multiLevelProps"
<template #item="{ element }">
<div class="item">{{ element.label }}div>
class="item-container item-container4"
v-model:list="groupProps"
<template #item="{ element }">
<div class="item">{{ element.label }}div>
<el-table :data="brr" border :span-method="objectSpanMethod">
v-for="item in groupProps"
:multiHeaders="multiHeaderValues"
v-if="multiLevelProps && multiLevelProps.length"/>
v-for="item in baseProps"
<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"/>
<span class="dialog-footer">
<el-button @click="isShowTemplate = false">取消el-button>
<el-button type="primary" @click="saveTemplate">保存el-button>
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 {proxy} = getCurrentInstance()
const baseProps = ref([])
const multiLevelProps = ref([])
const groupProps = ref([])
const multiHeaderValues = computed(() => {
return multiLevelProps.value.map((item) => {
return findGroup(item.prop)
watch([()=>groupProps.value,()=>baseProps.value,()=>multiLevelProps.value,()=>template.value],()=>{
data.value.sort(comprisonFunction(arr.value[0]))
brr.value.sort(comprisonFunction(arr.value[0]))
setTabelRowSpan(brr.value, arr.value);
data.value.forEach((item) => {
for (let i = 0; i < groupProps.value.length; i++) {
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 = tempArr.reduce((curr, next) => {
arr.value.forEach(item=>{
obj[str] ? '' : obj[str]=curr.push(next);
function setTabelRowSpan(tableData, fieldArr){
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])){
function objectSpanMethod({ row, column, rowIndex, columnIndex }) {
if (row.mergeCell.includes(column.property)) {
const rowspan = row[`rowspan_${column.property}`]
return {rowspan: rowspan, colspan: 1};
return {rowspan: 0, colspan: 0};
if(multiHeaderValues.value.length>0){
brr.value.forEach(item=>{
data.value.forEach(temp=>{
arr.value.forEach(arrTmp=>{
if (temp[arrTmp]!==item[arrTmp]){
multiLevelProps.value.forEach(mpItem=>{
m_level = m_level === '' ? temp[mpItem.prop] : m_level+'_'+temp[mpItem.prop]
baseProps.value.forEach(bpItem=>{
if ((typeof (temp[bpItem.prop])==='number')){
if(multiLevelProps.value.length!==0){
results.value.forEach(rItem=>{
if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
if (item[bpItem.prop+"_"+rItem]===undefined){
item[bpItem.prop+"_"+rItem]=temp[bpItem.prop]
item[bpItem.prop+"_"+rItem]=item[bpItem.prop+"_"+rItem] + temp[bpItem.prop]
if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
if (item[bpItem.prop]===undefined){
item[bpItem.prop]=temp[bpItem.prop]
item[bpItem.prop]=item[bpItem.prop] + temp[bpItem.prop]
if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
if(multiLevelProps.value.length!==0){
results.value.forEach(rItem=>{
if (temp[bpItem.prop]!==''&&temp[bpItem.prop]!==null){
if (item[bpItem.prop+"_"+rItem]===undefined){
item[bpItem.prop+"_"+rItem]=temp[bpItem.prop]
if (!item[bpItem.prop+"_"+rItem].includes(temp[bpItem.prop]))
item[bpItem.prop+"_"+rItem]=item[bpItem.prop+"_"+rItem] +','+ temp[bpItem.prop]
if (temp[bpItem.prop] !== '' && temp[bpItem.prop] !== null) {
if (item[bpItem.prop] === undefined) {
item[bpItem.prop] = temp[bpItem.prop]
if (!item[bpItem.prop].includes(temp[bpItem.prop]))
item[bpItem.prop] = item[bpItem.prop] + ',' + temp[bpItem.prop]
const m_len = multiHeaderValues.value.length
results.value = recurse(multiHeaderValues.value[index.value],multiHeaderValues.value[++index.value],m_len,multiHeaderValues.value)
results.value = multiHeaderValues.value[0]
function recurse(arr1,arr2,len,arr){
newArr.push(item+'_'+item2)
return recurse(newArr,arr[index.value],len,arr)
function comprisonFunction (propName) {
return function (object1, object2) {
let value1 = object1[propName];
let value2 = object2[propName];
} else if(value1 < value2) {
function findGroup(prop) {
data.value.forEach(item => {
results.value.forEach(rItem=>{
baseProps.value.forEach(item=>{
arr.push(item.prop+"_"+rItem)
function generateArray() {
const mainLength = groupProps.value.length
const headers = multiHeaderValues.value.concat([baseProps.value])
const mHeader = excelHeader()
const allCols = headers.reduce((res, cur) => {
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)
for (let j = 1; j < colspan; j++) {
import("@/utils/Export2Excel").then((excel) => {
const {ranges, rows,mHeader} = generateArray()
const tHeader = rows[rows.length - 1].map((item) => item.label)
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({
multiHeader: multiHeader,
function formatJson(filterVal, jsonData) {
return jsonData.map((v) =>
const myOrigin = window.location.origin
window.addEventListener('message',function (e) {
if (e.origin === myOrigin) {
document.title = e.data.title+'-数据分析'
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}")
useStorage(e.data.tableName,JSON.stringify(obj))
let arr = JSON.parse(useStorage(e.data.tableName).value)
allProps.value = arr.allProps
const templateName = ref('')
const isShowTemplate = ref(false)
isShowTemplate.value = true
const templateList = ref([])
employeeId: store.state.user.info.id,
templateList.value = res.data.records
"employeeId": store.state.user.info.id,
"templateName": tForm.value.templateName,
"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
proxy.$modal.msgSuccess("保存成功");
function templateData(val){
templateList.value.forEach(item=>{
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(){
multiLevelProps.value = []
multiHeaderValues.value = []
<style lang="scss" scoped>
grid-template-columns: 200px 1fr;

MultiHeaders.vue
<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>
<el-table-column v-else v-for="bItem in baseProps" :label="bItem.label" :prop="bItem.prop+'_'+item.prop" >el-table-column>
import {computed, watch} from "vue"
import MultiHeader from "@/views/configTable/MultiHeader.vue";
const props = defineProps({
function childData(list,i){
function transListDataToTreeData(list,i) {
const child = childData(list,i+1)
obj.child = transListDataToTreeData(list,i+1)
function sliceArr(arr,size){
for (let i=0;i<Math.ceil(arr.length/size);i++){
res.push(arr.slice(start,end))
const headers = computed(() => {
return transListDataToTreeData(props.multiHeaders,0)
const subHeaders = computed(() => {
return props.multiHeaders.slice(1)
<style lang="scss" scoped>style>
