Browse Source

feature/新增excel页面

dy 11 months ago
parent
commit
4f363e0ac9

+ 7 - 2
index.html

@@ -13,11 +13,16 @@
       name="keywords"
     />
     <title>生产线综合管控采集平台-一体机</title>
+    <link rel="stylesheet" href="/LuckExcel/pluginsCss.css" />
+    <link rel="stylesheet" href="/LuckExcel/plugins.css" />
+    <link rel="stylesheet" href="/LuckExcel/luckysheet.css" />
+    <link rel="stylesheet" href="/LuckExcel/iconfont.css" />
+    <script src="/LuckExcel/plugin.js"></script>
+    <script src="/LuckExcel/luckysheet.umd.js"></script>
   </head>
 
   <body>
-    <div id="app">
-    </div>
+    <div id="app"></div>
   </body>
   <script src="/src/main.ts" type="module"></script>
 

+ 5 - 0
package.json

@@ -54,7 +54,11 @@
     "dayjs": "^1.11.10",
     "echarts": "^5.5.0",
     "element-plus": "^2.6.0",
+    "exceljs": "^4.4.0",
+    "file-saver": "^2.0.5",
+    "less": "^4.2.0",
     "lodash-es": "^4.17.21",
+    "luckyexcel": "^1.0.1",
     "mitt": "^3.0.1",
     "net": "^1.0.2",
     "nprogress": "^0.2.0",
@@ -71,6 +75,7 @@
     "vue-pdf-embed": "^2.0.2",
     "vue-router": "^4.3.0",
     "vue3-pdfjs": "^0.1.6",
+    "x-data-spreadsheet": "^1.1.9",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {

File diff suppressed because it is too large
+ 457 - 0
public/LuckExcel/iconfont.css


File diff suppressed because it is too large
+ 1 - 0
public/LuckExcel/luckysheet.css


File diff suppressed because it is too large
+ 111621 - 0
public/LuckExcel/luckysheet.umd.formatted.js


File diff suppressed because it is too large
+ 27 - 0
public/LuckExcel/luckysheet.umd.js


File diff suppressed because it is too large
+ 22392 - 0
public/LuckExcel/plugin.formatted.js


File diff suppressed because it is too large
+ 1 - 0
public/LuckExcel/plugin.js


File diff suppressed because it is too large
+ 10 - 0
public/LuckExcel/plugins.css


File diff suppressed because it is too large
+ 1 - 0
public/LuckExcel/pluginsCss.css


File diff suppressed because it is too large
+ 457 - 0
public/luckysheet/iconfont.css


File diff suppressed because it is too large
+ 7039 - 0
public/luckysheet/luckysheet.css


File diff suppressed because it is too large
+ 27 - 0
public/luckysheet/luckysheet.umd.js


File diff suppressed because it is too large
+ 1 - 0
public/luckysheet/plugin.js


File diff suppressed because it is too large
+ 10 - 0
public/luckysheet/plugins.css


File diff suppressed because it is too large
+ 717 - 0
public/luckysheet/pluginsCss.css


+ 7 - 0
src/App.vue

@@ -37,3 +37,10 @@ const fontColor = computed(() => {
     : "rgba(0, 0, 0, .15)";
 });
 </script>
+
+<style lang="scss" scoped>
+#app {
+  overflow-x: hidden !important;
+  overflow: hidden !important;
+}
+</style>

+ 7 - 0
src/router/modules/excel.ts

@@ -0,0 +1,7 @@
+const Layout = () => import("@/layout/index.vue");
+
+export default {
+  path: "/excel",
+  name: "excel",
+  component: () => import("@/views/excel/index.vue"),
+};

+ 2 - 2
src/styles/reset.scss

@@ -22,8 +22,8 @@ html {
 }
 
 body {
-  width: 100%;
-  height: 100%;
+  width: 100vw;
+  height: 100vh;
   margin: 0;
   font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
     "Microsoft YaHei", "微软雅黑", Arial, sans-serif;

+ 208 - 0
src/views/excel/components/LuckySheet.vue

@@ -0,0 +1,208 @@
+<template>
+  <div style="position: absolute; top: 0">
+    <input id="uploadBtn" type="file" @change="loadExcel" />
+
+    <span>Or Load remote xlsx file:</span>
+
+    <select v-model="selected" @change="selectExcel">
+      <option disabled value="">Choose</option>
+      <option
+        v-for="option in options"
+        :key="option.text"
+        :value="option.value"
+      >
+        {{ option.text }}
+      </option>
+    </select>
+
+    <a href="javascript:void(0)" @click="downloadExcel"
+      >Download source xlsx file</a
+    >
+  </div>
+  <div id="luckysheet"></div>
+  <div v-show="isMaskShow" id="tip">Downloading</div>
+</template>
+
+<script setup>
+import { ref, onMounted } from "vue";
+import { exportExcel } from "../utils/export";
+import LuckyExcel from "luckyexcel";
+
+const isMaskShow = ref(false);
+const selected = ref("");
+const jsonData = ref({});
+const options = ref([
+  {
+    text: "Money Manager.xlsx",
+    value: "https://minio.cnbabylon.com/public/luckysheet/money-manager-2.xlsx",
+  },
+  {
+    text: "Activity costs tracker.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Activity%20costs%20tracker.xlsx",
+  },
+  {
+    text: "House cleaning checklist.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/House%20cleaning%20checklist.xlsx",
+  },
+  {
+    text: "Student assignment planner.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Student%20assignment%20planner.xlsx",
+  },
+  {
+    text: "Credit card tracker.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Credit%20card%20tracker.xlsx",
+  },
+  {
+    text: "Blue timesheet.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Blue%20timesheet.xlsx",
+  },
+  {
+    text: "Student calendar (Mon).xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Student%20calendar%20%28Mon%29.xlsx",
+  },
+  {
+    text: "Blue mileage and expense report.xlsx",
+    value:
+      "https://minio.cnbabylon.com/public/luckysheet/Blue%20mileage%20and%20expense%20report.xlsx",
+  },
+]);
+
+const loadExcel = (evt) => {
+  const files = evt.target.files;
+  if (files == null || files.length == 0) {
+    alert("No files wait for import");
+    return;
+  }
+
+  let name = files[0].name;
+  let suffixArr = name.split("."),
+    suffix = suffixArr[suffixArr.length - 1];
+  if (suffix != "xlsx") {
+    alert("Currently only supports the import of xlsx files");
+    return;
+  }
+  LuckyExcel.transformExcelToLucky(
+    files[0],
+    function (exportJson, luckysheetfile) {
+      if (exportJson.sheets == null || exportJson.sheets.length == 0) {
+        alert(
+          "Failed to read the content of the excel file, currently does not support xls files!"
+        );
+        return;
+      }
+      console.log("exportJson", exportJson);
+      jsonData.value = exportJson;
+
+      window.luckysheet.destroy();
+
+      window.luckysheet.create({
+        container: "luckysheet", //luckysheet is the container id
+        showinfobar: false,
+        data: exportJson.sheets,
+        title: exportJson.info.name,
+        userInfo: exportJson.info.name.creator,
+      });
+    }
+  );
+};
+const selectExcel = (evt) => {
+  const value = selected.value;
+  const name = evt.target.options[evt.target.selectedIndex].innerText;
+
+  if (value == "") {
+    return;
+  }
+  isMaskShow.value = true;
+
+  LuckyExcel.transformExcelToLuckyByUrl(
+    value,
+    name,
+    (exportJson, luckysheetfile) => {
+      if (exportJson.sheets == null || exportJson.sheets.length == 0) {
+        alert(
+          "Failed to read the content of the excel file, currently does not support xls files!"
+        );
+        return;
+      }
+      console.log("exportJson", exportJson);
+      jsonData.value = exportJson;
+
+      isMaskShow.value = false;
+      window.luckysheet.destroy();
+
+      window.luckysheet.create({
+        container: "luckysheet", //luckysheet is the container id
+        showinfobar: false,
+        data: exportJson.sheets,
+        title: exportJson.info.name,
+        userInfo: exportJson.info.name.creator,
+      });
+    }
+  );
+};
+const downloadExcel = () => {
+  // const value = selected.value;;
+  //
+  // if(value.length==0){
+  //     alert("Please select a demo file");
+  //     return;
+  // }
+  //
+  // var elemIF = document.getElementById("Lucky-download-frame");
+  // if(elemIF==null){
+  //     elemIF = document.createElement("iframe");
+  //     elemIF.style.display = "none";
+  //     elemIF.id = "Lucky-download-frame";
+  //     document.body.appendChild(elemIF);
+  // }
+  // elemIF.src = value;
+  exportExcel(luckysheet.getAllSheets(), "下载");
+};
+
+// !!! create luckysheet after mounted
+onMounted(() => {
+  luckysheet.create({
+    container: "luckysheet",
+  });
+});
+</script>
+
+<style lang="scss" scoped>
+.body {
+  max-height: 300px !important;
+}
+#luckysheet {
+  margin: 0px;
+  padding: 0px;
+  position: absolute;
+  width: 100%;
+  left: 0px;
+  top: 30px;
+  bottom: 0px;
+}
+
+#uploadBtn {
+  font-size: 16px;
+}
+
+#tip {
+  position: absolute;
+  z-index: 1000000;
+  left: 0px;
+  top: 0px;
+  bottom: 0px;
+  right: 0px;
+  background: rgba(255, 255, 255, 0.8);
+  text-align: center;
+  font-size: 40px;
+  align-items: center;
+  justify-content: center;
+  display: flex;
+}
+</style>

+ 18 - 0
src/views/excel/index.vue

@@ -0,0 +1,18 @@
+<template>
+  <div id="ss">
+    <ExcelView />
+  </div>
+</template>
+
+<script setup>
+import ExcelView from "@/views/excel/components/LuckySheet.vue";
+</script>
+
+<style lang="scss" scoped>
+#ss {
+  position: relative;
+  width: 500px;
+  height: 100%;
+  background-color: red;
+}
+</style>

+ 347 - 0
src/views/excel/utils/export.js

@@ -0,0 +1,347 @@
+// import { createCellPos } from './translateNumToLetter'
+import Excel from "exceljs";
+
+import FileSaver from "file-saver";
+
+const exportExcel = function (luckysheet, value) {
+  // 参数为luckysheet.getluckysheetfile()获取的对象
+  // 1.创建工作簿,可以为工作簿添加属性
+  const workbook = new Excel.Workbook();
+  // 2.创建表格,第二个参数可以配置创建什么样的工作表
+  if (Object.prototype.toString.call(luckysheet) === "[object Object]") {
+    luckysheet = [luckysheet];
+  }
+  luckysheet.forEach(function (table) {
+    if (table.data.length === 0) return true;
+    // ws.getCell('B2').fill = fills.
+    const worksheet = workbook.addWorksheet(table.name);
+    const merge = (table.config && table.config.merge) || {};
+    const borderInfo = (table.config && table.config.borderInfo) || {};
+    // 3.设置单元格合并,设置单元格边框,设置单元格样式,设置值
+    setStyleAndValue(table.data, worksheet);
+    setMerge(merge, worksheet);
+    setBorder(borderInfo, worksheet);
+    return true;
+  });
+
+  // return
+  // 4.写入 buffer
+  const buffer = workbook.xlsx.writeBuffer().then((data) => {
+    // console.log('data', data)
+    const blob = new Blob([data], {
+      type: "application/vnd.ms-excel;charset=utf-8",
+    });
+    console.log("导出成功!");
+    FileSaver.saveAs(blob, `${value}.xlsx`);
+  });
+  return buffer;
+};
+
+var setMerge = function (luckyMerge = {}, worksheet) {
+  const mergearr = Object.values(luckyMerge);
+  mergearr.forEach(function (elem) {
+    // elem格式:{r: 0, c: 0, rs: 1, cs: 2}
+    // 按开始行,开始列,结束行,结束列合并(相当于 K10:M12)
+    worksheet.mergeCells(
+      elem.r + 1,
+      elem.c + 1,
+      elem.r + elem.rs,
+      elem.c + elem.cs
+    );
+  });
+};
+
+var setBorder = function (luckyBorderInfo, worksheet) {
+  if (!Array.isArray(luckyBorderInfo)) return;
+  // console.log('luckyBorderInfo', luckyBorderInfo)
+  luckyBorderInfo.forEach(function (elem) {
+    // 现在只兼容到borderType 为range的情况
+    // console.log('ele', elem)
+    if (elem.rangeType === "range") {
+      let border = borderConvert(elem.borderType, elem.style, elem.color);
+      let rang = elem.range[0];
+      // console.log('range', rang)
+      let row = rang.row;
+      let column = rang.column;
+      for (let i = row[0] + 1; i < row[1] + 2; i++) {
+        for (let y = column[0] + 1; y < column[1] + 2; y++) {
+          worksheet.getCell(i, y).border = border;
+        }
+      }
+    }
+    if (elem.rangeType === "cell") {
+      // col_index: 2
+      // row_index: 1
+      // b: {
+      //   color: '#d0d4e3'
+      //   style: 1
+      // }
+      const { col_index, row_index } = elem.value;
+      const borderData = Object.assign({}, elem.value);
+      delete borderData.col_index;
+      delete borderData.row_index;
+      let border = addborderToCell(borderData, row_index, col_index);
+      // console.log('bordre', border, borderData)
+      worksheet.getCell(row_index + 1, col_index + 1).border = border;
+    }
+    // console.log(rang.column_focus + 1, rang.row_focus + 1)
+    // worksheet.getCell(rang.row_focus + 1, rang.column_focus + 1).border = border
+  });
+};
+var setStyleAndValue = function (cellArr, worksheet) {
+  if (!Array.isArray(cellArr)) return;
+  cellArr.forEach(function (row, rowid) {
+    row.every(function (cell, columnid) {
+      if (!cell) return true;
+      let fill = fillConvert(cell.bg);
+
+      let font = fontConvert(
+        cell.ff,
+        cell.fc,
+        cell.bl,
+        cell.it,
+        cell.fs,
+        cell.cl,
+        cell.ul
+      );
+      let alignment = alignmentConvert(cell.vt, cell.ht, cell.tb, cell.tr);
+      let value = "";
+
+      if (cell.f) {
+        value = { formula: cell.f, result: cell.v };
+      } else if (!cell.v && cell.ct && cell.ct.s) {
+        // xls转为xlsx之后,内部存在不同的格式,都会进到富文本里,即值不存在与cell.v,而是存在于cell.ct.s之后
+        // value = cell.ct.s[0].v
+        cell.ct.s.forEach((arr) => {
+          value += arr.v;
+        });
+      } else {
+        value = cell.v;
+      }
+      //  style 填入到_value中可以实现填充色
+      let letter = createCellPos(columnid);
+      let target = worksheet.getCell(letter + (rowid + 1));
+      // console.log('1233', letter + (rowid + 1))
+      for (const key in fill) {
+        target.fill = fill;
+        break;
+      }
+      target.font = font;
+      target.alignment = alignment;
+      target.value = value;
+
+      return true;
+    });
+  });
+};
+
+var fillConvert = function (bg) {
+  if (!bg) {
+    return {};
+  }
+  // const bgc = bg.replace('#', '')
+  let fill = {
+    type: "pattern",
+    pattern: "solid",
+    fgColor: { argb: bg.replace("#", "") },
+  };
+  return fill;
+};
+
+var fontConvert = function (
+  ff = 0,
+  fc = "#000000",
+  bl = 0,
+  it = 0,
+  fs = 10,
+  cl = 0,
+  ul = 0
+) {
+  // luckysheet:ff(样式), fc(颜色), bl(粗体), it(斜体), fs(大小), cl(删除线), ul(下划线)
+  const luckyToExcel = {
+    0: "微软雅黑",
+    1: "宋体(Song)",
+    2: "黑体(ST Heiti)",
+    3: "楷体(ST Kaiti)",
+    4: "仿宋(ST FangSong)",
+    5: "新宋体(ST Song)",
+    6: "华文新魏",
+    7: "华文行楷",
+    8: "华文隶书",
+    9: "Arial",
+    10: "Times New Roman ",
+    11: "Tahoma ",
+    12: "Verdana",
+    num2bl: function (num) {
+      return num === 0 ? false : true;
+    },
+  };
+  // 出现Bug,导入的时候ff为luckyToExcel的val
+
+  let font = {
+    name: typeof ff === "number" ? luckyToExcel[ff] : ff,
+    family: 1,
+    size: fs,
+    color: { argb: fc.replace("#", "") },
+    bold: luckyToExcel.num2bl(bl),
+    italic: luckyToExcel.num2bl(it),
+    underline: luckyToExcel.num2bl(ul),
+    strike: luckyToExcel.num2bl(cl),
+  };
+
+  return font;
+};
+
+var alignmentConvert = function (
+  vt = "default",
+  ht = "default",
+  tb = "default",
+  tr = "default"
+) {
+  // luckysheet:vt(垂直), ht(水平), tb(换行), tr(旋转)
+  const luckyToExcel = {
+    vertical: {
+      0: "middle",
+      1: "top",
+      2: "bottom",
+      default: "top",
+    },
+    horizontal: {
+      0: "center",
+      1: "left",
+      2: "right",
+      default: "left",
+    },
+    wrapText: {
+      0: false,
+      1: false,
+      2: true,
+      default: false,
+    },
+    textRotation: {
+      0: 0,
+      1: 45,
+      2: -45,
+      3: "vertical",
+      4: 90,
+      5: -90,
+      default: 0,
+    },
+  };
+
+  let alignment = {
+    vertical: luckyToExcel.vertical[vt],
+    horizontal: luckyToExcel.horizontal[ht],
+    wrapText: luckyToExcel.wrapText[tb],
+    textRotation: luckyToExcel.textRotation[tr],
+  };
+  return alignment;
+};
+
+var borderConvert = function (borderType, style = 1, color = "#000") {
+  // 对应luckysheet的config中borderinfo的的参数
+  if (!borderType) {
+    return {};
+  }
+  const luckyToExcel = {
+    type: {
+      "border-all": "all",
+      "border-top": "top",
+      "border-right": "right",
+      "border-bottom": "bottom",
+      "border-left": "left",
+    },
+    style: {
+      0: "none",
+      1: "thin",
+      2: "hair",
+      3: "dotted",
+      4: "dashDot", // 'Dashed',
+      5: "dashDot",
+      6: "dashDotDot",
+      7: "double",
+      8: "medium",
+      9: "mediumDashed",
+      10: "mediumDashDot",
+      11: "mediumDashDotDot",
+      12: "slantDashDot",
+      13: "thick",
+    },
+  };
+  let template = {
+    style: luckyToExcel.style[style],
+    color: { argb: color.replace("#", "") },
+  };
+  let border = {};
+  if (luckyToExcel.type[borderType] === "all") {
+    border["top"] = template;
+    border["right"] = template;
+    border["bottom"] = template;
+    border["left"] = template;
+  } else {
+    border[luckyToExcel.type[borderType]] = template;
+  }
+  // console.log('border', border)
+  return border;
+};
+
+function addborderToCell(borders, row_index, col_index) {
+  let border = {};
+  const luckyExcel = {
+    type: {
+      l: "left",
+      r: "right",
+      b: "bottom",
+      t: "top",
+    },
+    style: {
+      0: "none",
+      1: "thin",
+      2: "hair",
+      3: "dotted",
+      4: "dashDot", // 'Dashed',
+      5: "dashDot",
+      6: "dashDotDot",
+      7: "double",
+      8: "medium",
+      9: "mediumDashed",
+      10: "mediumDashDot",
+      11: "mediumDashDotDot",
+      12: "slantDashDot",
+      13: "thick",
+    },
+  };
+  // console.log('borders', borders)
+  for (const bor in borders) {
+    // console.log(bor)
+    if (borders[bor].color.indexOf("rgb") === -1) {
+      border[luckyExcel.type[bor]] = {
+        style: luckyExcel.style[borders[bor].style],
+        color: { argb: borders[bor].color.replace("#", "") },
+      };
+    } else {
+      border[luckyExcel.type[bor]] = {
+        style: luckyExcel.style[borders[bor].style],
+        color: { argb: borders[bor].color },
+      };
+    }
+  }
+
+  return border;
+}
+
+function createCellPos(n) {
+  let ordA = "A".charCodeAt(0);
+
+  let ordZ = "Z".charCodeAt(0);
+  let len = ordZ - ordA + 1;
+  let s = "";
+  while (n >= 0) {
+    s = String.fromCharCode((n % len) + ordA) + s;
+
+    n = Math.floor(n / len) - 1;
+  }
+  return s;
+}
+
+export { exportExcel };

+ 7 - 0
src/views/excel/utils/is.js

@@ -0,0 +1,7 @@
+export function getType(val) {
+  return Object.prototype.toString.call(val).slice(8, -1);
+}
+
+export function isFunction(val) {
+  return getType(val) === "Function";
+}