【EXCELJS】 Vue2 实现excel编辑导出,简单实现各种单元格合并,开箱即用逻辑简单(elementui)

171 阅读3分钟

第一步

一定要安装这个版本,否则就会报错
npm install exceljs@4.3.0

demo直接改就行

<template>
  <div style="height: 500px;">
    <el-row :gutter="20" style="height: 480px;overflow: auto;">
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          空间
        </div>
        <div class="white-table" :style="{ 'height': (bigdatalength * 60) + 'px' }">
          {{ itemall.roomName }}
        </div>
      </el-col>
      <el-col :span="1" style="padding: 0 0;">
        <!-- <el-tooltip content="这是一个提示"> -->
        <div class="black-table">
          类型
        </div>
        <div v-for="(itemtype, index) in itemall.category" :key="index">
          <div class="white-table" :style="{ 'height': (middledatalength[index] * 60) + 'px' }">
            {{ itemtype.categoryName }}
          </div>
        </div>
      </el-col>
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          位置
        </div>
        <div v-for="(itemtype, index) in locationName" :key="index">
          <div class="white-table" :style="{ 'height': (smalldatalength[index] * 60) + 'px' }">
            {{ itemtype }}
          </div>
        </div>
      </el-col>
      <!-- <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          图例
        </div>
        <div class="white-table">
          <el-image style="width: 70px; height: 50px" :src="src" :preview-src-list="[src]"></el-image>
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
        <div class="white-table">
          图
        </div>
      </el-col> -->
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          面积
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.areaNum"></el-input>
        </div>
      </el-col>
      <el-col :span="4" style="padding: 0 0;">
        <div class="black-table">
          商品
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.name"></el-input>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="black-table">
          品牌
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.brandName"></el-input>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="black-table">
          产品型号
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.model"></el-input>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="black-table">
          规格尺寸
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.size"></el-input>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="black-table">
          单价
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <div class="white-table" style="position: relative;">
            <el-input autosize placeholder="" v-model="item.price"></el-input>
            <div style="position: absolute;top: 20px;left: 3px;"></div>
          </div>
        </div>
      </el-col>
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          建议用量
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.dosage"></el-input>
        </div>
      </el-col>
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          单位
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.unit"></el-input>
        </div>
      </el-col>
      <el-col :span="1" style="padding: 0 0;">
        <div class="black-table">
          损耗
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <el-input type="textarea" class="white-table" autosize placeholder="" v-model="item.loss"></el-input>
        </div>
      </el-col>
      <el-col :span="3" style="padding: 0 0;">
        <div class="black-table">
          小计
        </div>
        <div v-for="(item, index) in smalldata" :key="index">
          <div class="white-table">
            ¥{{ item.price * item.dosage * (1 + (parseInt(item.loss, 10) / 100)) }}
          </div>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="black-table">
          备注
        </div>
        <!-- <div class="white-table">
          该报价仅为建收用量下的主材价格。最终价格已订单价为准。加工学角以实行测量为主,收货及时验块,港货留好原包装,免费送货或补货一次。加工和水砖不退
        </div> -->
        <el-input type="textarea" style="border-bottom: 0;" class="white-table" autosize
          :style="{ 'height': ((bigdatalength) * 60) + 'px' }" placeholder="" v-model="note"></el-input>
      </el-col>
      <el-col :span="22" style="padding: 0 0;">
        <div class="white-table" style="align-items: flex-end;flex-direction: column;width: 100%;">
          <div style="display: flex;justify-content: center;align-items: center;">
            优惠金额:
            <el-input style="width: 50px;" class="white-table" autosize placeholder="" v-model="free"></el-input>
            <div style="margin-left: 10px;margin-right: 5px;"></div>

          </div>

        </div>
        <div class="white-table" style="align-items: flex-end;flex-direction: column;width: 100%;">
          <div style="float: right;margin-right: 5px;">
            商品总计: {{ computedData.toFixed(2) }}元
          </div>
        </div>
      </el-col>
      <el-col :span="2" style="padding: 0 0;">
        <div class="white-table"
          style="border-bottom: 1px solid rgb(195, 200, 209);border-top: 0;border-right: 1px solid rgb(195, 200, 209);height: 120px;">
        </div>
      </el-col>
    </el-row>
  </div>
</template>
<script>
import ExcelJS from 'exceljs';
export default {
  data() {
    return {
      inputshow: false,
      src: "https://img2.baidu.com/it/u=1341091381,1189643914&fm=253&fmt=auto&app=120&f=JPEG?w=666&h=416",
      input: 99999,
      note: "该报价仅为建收用量下的主材价格。最终价格已订单价为准。加工学角以实行测量为主,收货及时验块,港货留好原包装,免费送货或补货一次。加工和水砖不退。",
      locationName: [],
      smalldata: [],
      smalldatalength: [],
      middledatalength: [],
      locationNamelength: [],
      bigdatalength: [],
      free: 0,
      itemall:
      {
        roomName: "未命名",
        category: [
          {
            categoryName: "瓷砖",
            location: [
              {
                locationName: "墙面",
                goods: [
                  {
                    areaNum: 0,
                    name: "TDMF03160120PS",
                    brandName: "",
                    model: "",
                    size: "600*200",
                    price: 0,
                    dosage: 57.366578249999996,
                    unit: "块",
                    loss: "0%",
                    subtotal: 0
                  },
                  {
                    areaNum: 0,
                    name: "东鹏中板墙砖SG848101_A",
                    brandName: "东鹏",
                    model: "中板墙砖SG848101_A",
                    size: "600*200",
                    price: 38.7,
                    dosage: 52.333156498,
                    unit: "块",
                    loss: "0%",
                    subtotal: 1780.2
                  },
                  {
                    areaNum: 0,
                    name: "750*1500海浪灰XD1PA715208",
                    brandName: "熊小住",
                    model: "",
                    size: "600*200",
                    price: 199,
                    dosage: 51.76657825,
                    unit: "块",
                    loss: "0%",
                    subtotal: 9552
                  },
                  {
                    areaNum: 0,
                    name: "750*1500爵士灰XD1PA715205",
                    brandName: "熊小住",
                    model: "",
                    size: "600*300",
                    price: 199,
                    dosage: 39.333156498,
                    unit: "块",
                    loss: "0%",
                    subtotal: 6567
                  }
                ]
              },
              {
                locationName: "地面",
                goods: []
              }
            ]
          },
          {
            categoryName: "砖缝",
            location: [
              {
                locationName: "墙面",
                goods: [
                  {
                    areaNum: 0,
                    name: "木纹色",
                    brandName: "通配",
                    model: "鎏金黄",
                    size: "100*100",
                    price: 18.8,
                    dosage: 688.36657825,
                    unit: "米",
                    loss: "0%",
                    subtotal: 12765.2
                  },
                  {
                    areaNum: 0,
                    name: "水泥灰",
                    brandName: "通配",
                    model: "亚麻灰",
                    size: "100*100",
                    price: 1.99,
                    dosage: 263.76657825,
                    unit: "米",
                    loss: "0%",
                    subtotal: 517.4
                  }
                ]
              },
              {
                locationName: "地面",
                goods: []
              }
            ]
          }
        ]
      },

    };
  },
  methods: {
    async updata() {
      // 创建一个新的工作簿
      const workbook = new ExcelJS.Workbook();
      const worksheet = workbook.addWorksheet('瓷砖美缝');

      // 添加表头
      worksheet.addRow(['空间', '类型', '位置', '面积', '商品', '品牌', '产品型号', '规格尺寸', '单价', '建议用量', '单位', '损耗', '小计', '备注']);

      // 添加数据

      // worksheet.addRow(['张三', 25, 'zhangsan@example.com']);
      // worksheet.addRow(['李四', 30, 'lisi@example.com']);
      // worksheet.addRow(['王五', 28, 'wangwu@example.com']);

      this.smalldata.map(item => {
        worksheet.addRow([0, 0, 0, item.areaNum, item.name, item.brandName, item.model, item.size, item.price, item.dosage, item.unit, item.loss, item.price * item.dosage * (1 + (parseInt(item.loss, 10) / 100))]);
      })
      // 合并单元格面积A
      worksheet.mergeCells(`A2:A${this.bigdatalength + 1}`);
      // // 设置合并单元格的样式
      const mergedCell = worksheet.getCell('A2');
      mergedCell.value = this.itemall.roomName;
      mergedCell.alignment = { vertical: 'middle', horizontal: 'center' };
      mergedCell.font = { bold: true, size: 14 };
      // 合并单元格-备注
      worksheet.mergeCells(`N2:N${this.bigdatalength + 1}`);
      const mergedCellnote = worksheet.getCell('N2');
      mergedCellnote.value = this.note;
      mergedCellnote.alignment = { vertical: 'middle', horizontal: 'center' ,wrapText: true };
      mergedCellnote.font = { bold: true, size: 10 };
      // 合并单元格面积B
      let merge = 2
      this.itemall.category.map((item, index) => {
        if (!index) {
          worksheet.mergeCells(`B${merge}:B${this.middledatalength[index] + 1}`);
          const mergedCell = worksheet.getCell(`B${merge}`);
          mergedCell.value = item.categoryName;
          mergedCell.alignment = { vertical: 'middle', horizontal: 'center' };
          mergedCell.font = { bold: true, size: 14 };
          merge = this.middledatalength[index] + 1
        } else {
          console.log(index, merge, this.middledatalength[index] + 1 + merge)
          worksheet.mergeCells(`B${merge + 1}:B${this.middledatalength[index] + merge}`);
          const mergedCell = worksheet.getCell(`B${merge + 1}`);
          mergedCell.value = item.categoryName;
          mergedCell.alignment = { vertical: 'middle', horizontal: 'center' };
          mergedCell.font = { bold: true, size: 14 };
          merge = this.middledatalength[index] + merge
        }
      })
      let mergeplace = 2
      this.locationName.map((item, index) => {
        if (!index) {
          worksheet.mergeCells(`C${mergeplace}:C${this.smalldatalength[index] + 1}`);
          const mergeplacedCell = worksheet.getCell(`C${mergeplace}`);
          mergeplacedCell.value = item;
          mergeplacedCell.alignment = { vertical: 'middle', horizontal: 'center' };
          mergeplacedCell.font = { bold: true, size: 14 };
          mergeplace = this.smalldatalength[index] + 1
        } else {
          worksheet.mergeCells(`C${mergeplace + 1}:C${this.smalldatalength[index] + mergeplace}`);
          const mergeplacedCell = worksheet.getCell(`C${mergeplace + 1}`);
          mergeplacedCell.value = item;
          mergeplacedCell.alignment = { vertical: 'middle', horizontal: 'center' };
          mergeplacedCell.font = { bold: true, size: 14 };
          mergeplace = this.smalldatalength[index] + mergeplace
        }
      })      
      // 生成Excel文件并下载
      const buffer = await workbook.xlsx.writeBuffer();
      const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
      const link = document.createElement('a');
      link.href = window.URL.createObjectURL(blob);
      link.download = '瓷砖美缝.xlsx';
      link.click();
    },
  },
  computed: {
    computedData() {
      // 计算属性依赖于 originalData
      let amous = 0
      this.smalldata.map(item => {
        amous += item.price * item.dosage * (1 + (parseInt(item.loss, 10) / 100))
      })
      return amous - this.free;
    },
  },
  mounted() {
    this.locationName = []
    this.smalldata = []
    this.smalldatalength = []
    this.middledatalength = []
    this.bigdatalength = 0
    // this.locationNamelength=[]
    let space = {
      areaNum: "",
      name: "",
      brandName: "",
      model: "",
      size: "",
      price: 0,
      dosage: 0,
      unit: "",
      loss: "0%",
      subtotal: 0
    }
    this.itemall.category.map(item => {
      let middlespace = 0
      item.location.map(item1 => {
        this.locationName.push(item1.locationName)
        let lengthnumber = 1
        item1.goods.map(item2 => {
          item2.dosage = Math.floor(item2.dosage)
          item2.areaNum ? item2.areaNum : item2.areaNum = "";
          this.smalldata.push(item2)
          lengthnumber++
        })
        this.smalldatalength.push(lengthnumber)
        this.smalldata.push(space)
        middlespace += lengthnumber
      })
      this.middledatalength.push(middlespace)
    }),
      this.middledatalength.map(item => {
        this.bigdatalength += item
      })
  }
}
</script>
<style lang="less" scoped>
.black-table {
  width: 100%;
  height: 30px;
  background-color: rgb(195, 200, 209);
  color: black;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 900;
}

.white-table {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 60px;
  border: 1px solid rgb(195, 200, 209);
  overflow: hidden;
  // white-space: nowrap;
  // text-overflow: ellipsis;
  // word-break: break-all;
}

& /deep/ .el-textarea__inner {
  border: none;
  resize: none;
  overflow-y: hidden;
}

& /deep/ .el-input__inner {
  border: none;
  resize: none;
  overflow-y: hidden;

  .el-input-group__append,
  .el-input-group__prepend {
    background-color: #fff;
    border: 0;
  }
}
</style>