整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Excel文件合并工具 方便的表格合并工具

          Excel文件合并工具 方便的表格合并工具

          xcel文件合并工具是一款非常方便的表格合并工具,用戶能使用這款軟件將多個Excel表格進行合并至一張表中。用戶能使用這款軟件指定需要進行合并的列,還能自由選擇需要進行合并的單元格等。軟件使用簡單易上手,并且無需安裝,解壓之后即可使用。

          轉載自當游網,原文地址:

          http://www.3h3.com/soft/212043.html

          格元素詳解與練習

          提到表格,大家最先想到的就是EXCEL這款軟件,實際上在對表格的操作上,HTML與EXCEL非常相似。

          在展示數據,統計數據方面,表格比文字描述更具表達優勢,在網頁中,表格也經常被用來展示數據、計劃日常安排等內容。如圖所示:

          今天我們就來學習一下如何向頁面中添加表格元素。

          首先來介紹一下表格元素中的基本標簽。

          NO.1:<table></table>

          這個標簽是書寫表格的第一個標簽,它本身在頁面上看不出什么內容,但是它的屬性可以控制表格顯示的全局樣式。這個標簽的開始標簽寫在表格元素的開頭,結尾標簽寫在表格元素的結尾。

          NO.2:<caption></caption>

          這個標簽是表格的標題標簽。

          NO.3:<tr></tr>

          這個標簽定義表格的列標簽

          NO.4:<th></th>

          這個標簽是列表標題標簽,例如,男生、女士、姓名等。

          NO.5:<td></td>

          這個標簽定義表格的行標簽

          OK,這些基本標簽就可以構建一個基礎的表格元素。示例代碼如下:

          <table><!-- 寫在表格元素的開頭 --><caption>表格標題</caption><!-- 表格標題 --><tr>標題標簽<th>姓名</th><!-- 標題標簽 --><th>年齡</th></tr><tr><td>一列一行</td><td>一列二行</td></tr><tr><td>二列一行</td><td>二列二行</td></tr></table><!-- 寫在表格元素的結尾 -->

          頁面效果如圖所示:沒有表格的外邊框。

          如何添加外邊框呢?在<table>標簽中修改border屬性即可,示例代碼如下:border="1"是給表格添加寬為1的邊界線。

          <table border="1"><!-- border="1"是給表格添加寬為1的邊界線 -->

          效果如圖所示:

          這時,您會發現表格在頁面上的尺寸非常小,可不可以按照頁面尺寸來顯示表格嗎?當然可以,這就需要為<table>標簽修改第二個屬性width,示例代碼如圖所示:width="100%"指的是表格寬度與平面寬度一致。

          <table border="1" width="100%"><!-- width="100%"指的是表格寬度與平面寬度一致 -->

          效果如圖所示:

          ok!今天的講解先到這里,明天我會繼續為大家講解<thead></thead>、<tfoot></tfoot>、<tbody></tbody>三個標簽,以及合并單元格操作。

          今天的完整代碼示例如下:

          <!DOCTYPE HTML>
            <html>
            <head> 
            <title>第一個網頁</title>
          </head> 
          <body><h1>第一個網頁</h1><hr>
          <h2>表格元素</h2><hr>
          <table border="1" width="100%">
            <caption>表格標題</caption>
          <tr>
            <th>姓名</th>
          <th>年齡</th>
          </tr>
          <tr><td>一列一行</td>
          <td>一列二行</td>
          </tr>
          <tr>
            <td>二列一行</td>
          <td>二列二行</td>
          </tr>
          </table>
          </body> 
          </html>

          正所謂萬丈高樓平地起,html技術雖然簡單,但是內容相對繁瑣,也是以后進一步學習網頁制作的基礎,希望大家動手寫每一段代碼,把每一步踩堅實。

          喜歡的小伙伴請關注我,閱讀中遇到任何問題請給我留言,如有疏漏或錯誤歡迎大家斧正,不勝感激!

          HTML完整學習目錄

          HTML序章(學習目的、對象、基本概念)——零基礎自學網頁制作

          HTML是什么?——零基礎自學網頁制作

          第一個HTML頁面如何寫?——零基礎自學網頁制作

          HTML頁面中head標簽有啥用?——零基礎自學網頁制作

          初識meta標簽與SEO——零基礎自學網頁制作

          HTML中的元素使用方法1——零基礎自學網頁制作

          HTML中的元素使用方法2——零基礎自學網頁制作

          HTML元素中的屬性1——零基礎自學網頁制作

          HTML元素中的屬性2(路徑詳解)——零基礎自學網頁制作

          使用HTML添加表格1(基本元素)——零基礎自學網頁制作

          使用HTML添加表格2(表格頭部與腳部)——零基礎自學網頁制作

          使用HTML添加表格3(間距與顏色)——零基礎自學網頁制作

          使用HTML添加表格4(行顏色與表格嵌套)——零基礎自學網頁制作

          16進制顏色表示與RGB色彩模型——零基礎自學網頁制作

          HTML中的塊級元素與內聯元素——零基礎自學網頁制作

          初識HTML中的<div>塊元素——零基礎自學網頁制作

          在HTML頁面中嵌入其他頁面的方法——零基礎自學網頁制作

          封閉在家學網頁制作!為頁面嵌入PDF文件——零基礎自學網頁制作

          HTML表單元素初識1——零基礎自學網頁制作

          HTML表單元素初識2——零基礎自學網頁制作

          HTML表單3(下拉列表、多行文字輸入)——零基礎自學網頁制作

          HTML表單4(form的action、method屬性)——零基礎自學網頁制作

          HTML列表制作講解——零基礎自學網頁制作

          為HTML頁面添加視頻、音頻的方法——零基礎自學網頁制作

          音視頻格式轉換神器與html視頻元素加字幕——零基礎自學網頁制作

          HTML中使用<a>標簽實現文本內鏈接——零基礎自學網頁制作

          前些天看到Luckysheet支持協同編輯Excel,正符合我們協同項目的一部分,故而想進一步完善協同文章,但是遇到了一下困難,特此做聲明哈,若侵權,請聯系我刪除文章!

          若侵犯版權、個人隱私,請聯系刪除哈!!!(我可不想踩縫紉機)

          Luckysheet ,一款純前端類似excel的在線表格,功能強大、配置簡單、完全開源。當然,也原生支持協同,下面,我們針對協同部分做詳細講解。官網使用的是Java,也有協同的Demo,我就不說了,下面用 Node 實現協同,完整的樣例如下,我們開始吧

          ?

          Luckysheet 基礎使用

          引入依賴

          CDN

          <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css' />
          <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/plugins.css' />
          <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/css/luckysheet.css' />
          <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/luckysheet/dist/assets/iconfont/iconfont.css' />
          <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js"></script>
          <script src="https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js"></script>
          


          本地打包

          Luckysheet: Luckysheet ,一款純前端類似excel的在線表格,功能強大、配置簡單、完全開源。

          https://gitee.com/mengshukeji/Luckysheet

          官網建議我們在上網址下載完整的包,這樣,我們得到的是luckysheet的源碼,可以進行二次開發。很重要哈,最后我們也會這樣做。

          ?

          npm i --s // 執行 npm 命令,進行依賴包的下載

          npm run build // 執行打包命令(二次開發是需要修改源碼的)

          把dist包放到自己的項目中,我已經更名了哈:

          ?

          然后,index.html 直接引入這個地址的文件就行了(二開一定是引這個地址哈)。

               <!-- 引入 luck Sheet 二次開發地址  就是你剛才 build 的那個 dist 包 -->
              <link rel='stylesheet' href='./luckysheet/dist/plugins/css/pluginsCss.css' />
              <link rel='stylesheet' href='./luckysheet/dist/plugins/plugins.css' />
              <link rel='stylesheet' href='./luckysheet/dist/css/luckysheet.css' />
              <link rel='stylesheet' href='./luckysheet/dist/assets/iconfont/iconfont.css' />
          
              <script src="./luckysheet/dist/plugins/js/plugin.js"></script>
              <script src="./luckysheet/dist/luckysheet.umd.js"></script>
          


          這個方式建議大家都試試,二次開發一定是這個方式哈!

          npm

          如果大家覺得不用二開,就是用原生的功能 ,那直接使用 npm 下載就行了。

          npm i luckysheet

              <link rel='stylesheet' href='./node_modules/luckysheet/dist/plugins/css/pluginsCss.css' />
              <link rel='stylesheet' href='./node_modules/luckysheet/dist/plugins/plugins.css' />
              <link rel='stylesheet' href='./node_modules/luckysheet/dist/css/luckysheet.css' />
              <link rel='stylesheet' href='./node_modules/luckysheet/dist/assets/iconfont/iconfont.css' />
              <script src="./node_modules/luckysheet/dist/plugins/js/plugin.js"></script>
              <script src="./node_modules/luckysheet/dist/luckysheet.umd.js"></script>
          


          初始化

          指定容器

          <div id="luckysheet" style="margin:0px;padding:0px;position:absolute;width:100%;height:100%;left: 0px;top: 0px;"></div>
          


          創建表格

          onMounted(()=> {
            // 初始化表格
            var options={
              container: "luckysheet", //luckysheet為容器id
            };
            luckysheet.create(options);
          });
          


          ?

          這樣就已經是一個完善的表格編輯器了,支持函數、圖表、填充等多項功能。

          協同編輯

          ?

          因此,我們分別配置這幾個參數:

          loadUrl

          配置loadUrl接口地址,加載所有工作表的配置,并包含當前頁單元格數據,與loadSheetUrl配合使用。參數為gridKey(表格主鍵)

          $.post(loadurl, {"gridKey" : server.gridKey}, function (d) {})
          


          源碼寫法如上,因此,我們需要創建一個 post請求的地址:

          ?編輯

          app.use("/excel", excelRouter); // 添加公共前綴

          配置 loadUrl,加了 baseURL是做了請求代理哈

           allowUpdate: true,
           loadUrl: "/baseURL/excel",
          


          接口要求返回以下數據,我們直接復制,然后返回:

          "[	
          	//status為1的sheet頁,重點是需要提供初始化的數據celldata
          	{
          		"name": "Cell",
          		"index": "sheet_01",
          		"order":  0,
          		"status": 1,
          		"celldata": [{"r":0,"c":0,"v":{"v":1,"m":"1","ct":{"fa":"General","t":"n"}}}]
          	},
          	//其他status為0的sheet頁,無需提供celldata,只需要配置項即可
          	{
          		"name": "Data",
          		"index": "sheet_02",
          		"order":  1,
          		"status": 0
          	},
          	{
          		"name": "Picture",
          		"index": "sheet_03",
          		"order":  2,
          		"status": 0
          	}
          ]"
          


          本例中,只返回一個sheet表,初始化 0 0 單元格內容為 ‘默認數據’

          router.post("/", (req, res, next)=> {
            //   console.log("lucySheet");
            let sheetData=[
              //status為1的sheet頁,重點是需要提供初始化的數據celldata
              {
                name: "Cell",
                index: "sheet_01",
                order: 0,
                status: 1,
                celldata: [
                  {
                    r: 0,
                    c: 0,
                    v: { v: "默認數據", m: "111", ct: { fa: "General", t: "n" } },
                  },
                ],
              },
            ];
            res.json(JSON.stringify(sheetData));
          });
          


          ?編輯

          ?編輯

          updateUrl

          操作表格后,實時保存數據的websocket地址,此接口也是共享編輯的接口地址。注意,發送給后端的數據默認是經過pako壓縮過后的。后臺拿到數據需要先解壓。通過共享編輯功能,可以實現Luckysheet實時保存數據和多人同步數據,每一次操作都會發送不同的參數到后臺

          因此,我們需要初始化一個 ws 連接:

          module.exports=()=> {
            console.log("等待初始化 WS 服務...");
            // 搭建ws服務器
            const { WebSocketServer }=require("ws");
          
            const wss=new WebSocketServer({ port: 9000 });
          
            console.log(" WS 服務初始化成功,連接地址:ws://localhost:9000");
          
            wss.on("connection", (ws, req)=> {
              console.log("用戶連接");
            });
          };
          


          ? 打開控制臺,可以看到連接成功的提示,我們可以一下源碼是怎么處理的:

          ?編輯

          除了看到輸出語句外,我們更應該關注一個 send 事件,因為 websocket 是通過send 發送數據的,還有的是pako.gzip()壓縮。因此,服務端監聽 message 獲取數據:

          ?

          至此,我們可以獲取一些基礎信息:

          1. 每次操作都會發送 send 事件;
          2. 每次發送的數據都經過 pako.gzip 壓縮
          3. node 獲取的都是 buffer 數據

          也就是這樣,我也不知道如何進行下去了,就加了官方的微信,就發生了篇頭的那張截圖。但是革命還在繼續。加了官網微信群,特此感謝【小李飛刀刀】的指導。

          ?

          解析Buffer

          const pako=require("pako");
          
          /**
           * @DESC 導出解壓方法
           * @param { string } str
           * @returns
           */
          exports.unzip=(str)=> {
            let chartData=str
              .toString()
              .split("")
              .map((i)=> i.charCodeAt(0));
          
            let binData=new Uint8Array(chartData);
          
            let data=pako.inflate(binData);
          
            return decodeURIComponent(
              String.fromCharCode.apply(null, new Uint16Array(data))
            );
          };
          


          ?編輯

          得到上圖,就知道該怎么辦了吧,映射的是用戶的所有操作哈。需要添加用戶標記

              let id=Math.random().toString().split(".")[1].slice(0, 3);
          
              // 需要添加自定義屬性
              ws.wid=id;
          
              ws.wname="user_" + id;
          


          處理用戶光標

          我們一定要看源碼是如何處理的哈,官網文檔并沒有那么詳細:

          ?

          因此,同步光標的時候,我們應該發送type=3 的數據,我們封裝ws的事件響應中心:

          // wss.clients 所有的客戶端
          wss.clients.forEach((conn)=> {
            // 不發送給自己
            if (conn.wid===ws.wid) return;
            // 使得 this 指向當前連接對象
            wshandle.call(conn, unzip(data));
          });
          


          ?

          我們還沒做數據同步哈,因此數據沒有顯示,不影響,先顯示用戶光標。

          同步數據

          /**
           * ws 事件響應中心
           *  根據不同的事件,返回不同的數據
           *  type 1 成功/失敗
           *  type 2 更新數據
           *  type 3 用戶光標
           *  type 4 批量處理數據
           */
          function wshandle(data) {
            // 表示用戶移動鼠標 實際是需要根據指令實現不同的響應的,但是這里統一做 更新數據
            this.send(callbackdata.call(this, data, JSON.parse(data).t==="mv" ? 3 : 2));
          }
          


          ?

          至此,協同好像已經實現了,但是還沒完。

          用戶退出

          源碼中需要返回 {message ,id} 兩個數據,因此直接封裝 退出函數:

          ?編輯

          /**
           * 用戶退出
           */
          function exit() {
            this.send(JSON.stringify({ message: "用戶退出", id: this.wid }));
          }
          


          監聽ws close 事件:

           ws.on("close", (ws)=> {
                try {
                  // 實現用戶退出
                  wss.clients.forEach((conn)=> {
                    if (conn.wid===ws.wid) return;
                    // 使得 this 指向當前連接對象
                    exit.call(conn);
                  });
                } catch (error) {
                  console.log(error);
                }
              });
          


          ?

          BUG修復

          ?

          不知道大家發現沒有,當多人協作時,我們的用戶id 是錯的,原因是我們move時,傳的參數不對:

          ?

          // 使得 this 指向當前連接對象 ,并且保證,操作對象始終是當前用戶
          wshandle.call(conn, { id: ws.wid, name: ws.wname }, unzip(data));
          
          // 表示用戶移動鼠標 實際是需要根據指令實現不同的響應的,但是這里統一做 更新數據
          // 手動傳輸 user
          this.send(callbackdata(user, data, JSON.parse(data).t==="mv" ? 3 : 2));
          
          // function callback:
          
          return JSON.stringify({
              createTime: dayjs().format("YYYYMMHH mm:hh:ss"),
              data,
              id: user.id,
              returnMessage: "success",
              status: 0,
              type,
              username: user.name,
            });
          


          數據庫存儲

          全量存儲

          表格操作完成后,使用luckysheet.getAllSheets()方法獲取到全部的工作表數據,全部發送到后臺存儲。

          ?

          協同存儲

          協同存儲就是用戶的每次操作,都會觸發 websocket,因此,我們直接在websocket中調用控制層,實現數據的更新,舉例說明:

          [
              {
                  "data":[], // 每個工作表參數組成的一維數組
                  "name": "Cell", //工作表名稱
                  "color": "", //工作表顏色
                  "index": 0, //工作表索引
                  "status": 1, //激活狀態
                  "order": 0, //工作表的下標
                  "hide": 0,//是否隱藏
                  "row": 36, //行數
                  "column": 18, //列數
                  "defaultRowHeight": 19, //自定義行高
                  "defaultColWidth": 73, //自定義列寬
                  "celldata": [], //初始化使用的單元格數據
                  "config": {
                      "merge":{}, //合并單元格
                      "rowlen":{}, //表格行高
                      "columnlen":{}, //表格列寬
                      "rowhidden":{}, //隱藏行
                      "colhidden":{}, //隱藏列
                      "borderInfo":{}, //邊框
                      "authority":{}, //工作表保護
                      
                  },
                  "scrollLeft": 0, //左右滾動條位置
                  "scrollTop": 315, //上下滾動條位置
                  "luckysheet_select_save": [], //選中的區域
                  "calcChain": [],//公式鏈
                  "isPivotTable":false,//是否數據透視表
                  "pivotTable":{},//數據透視表設置
                  "filter_select": {},//篩選范圍
                  "filter": null,//篩選配置
                  "luckysheet_alternateformat_save": [], //交替顏色
                  "luckysheet_alternateformat_save_modelCustom": [], //自定義交替顏色	
                  "luckysheet_conditionformat_save": {},//條件格式
                  "frozen": {}, //凍結行列配置
                  "chart": [], //圖表配置
                  "zoomRatio":1, // 縮放比例
                  "image":[], //圖片
                  "showGridLines": 1, //是否顯示網格線
                  "dataVerification":{} //數據驗證配置
              },
             // ... 其他 sheet 頁數據與上類似
          ]
          


          上是整個sheet的配置項,數據庫表可以根據這個來構建,數據表單獨分開、樣式表也單獨分開,還有基礎配置表:

          ?

          ?

          這樣就不用存儲很多無效的數據,能實現對某一條數據的精確控制與存儲,節省數據庫存儲空間。

          文件導入

          兩種方式實現哈,先隱藏默認,然后自定定位實現添加按鈕,或者根據配置項實現配置

          /deep/.luckysheet_info_detail_save,
          /deep/.luckysheet_info_detail_update {
            display: none;
          }
          


          ?

          npm i luckyexcel

          綁定了一個 input ref='importFileRef'

          const importFileHandle=(e)=> {
            let { files }=e.target;
            LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile)=> {
              luckysheet.create({
                container: "luckysheet", // luckysheet is the container id
                data: exportJson.sheets,
                title: exportJson.info.name,
                userInfo: exportJson.info.name.creator,
              });
              // 清空
              importFileRef.value.value="";
            });
          };
          


          ?

          但是這樣會丟失協同性:

          // 文件導入
          const importFileHandle=(e)=> {
            let { files }=e.target;
            LuckyExcel.transformExcelToLucky(files[0], (exportJson, luckysheetfile)=> {
              // 【會丟失協同性】
              // luckysheet.create({
              //   container: "luckysheet", // luckysheet is the container id
              //   data: exportJson.sheets,
              //   title: exportJson.info.name,
              //   userInfo: exportJson.info.name.creator,
              // });
          
              let { info, sheets }=exportJson;
          
              luckysheet.setWorkbookName(info.name);
          
              sheets.forEach((sheet)=> {
                // sheet 便是每一個 sheet 頁,需要根據實際的數量動態創建
                luckysheet.setSheetAdd({
                  sheetObject: sheet,
                });
              });
              // 清空
              importFileRef.value.value="";
            });
          };
          


          ?

          文件導出

          npm i exceljs file-saver

          import Excel from "exceljs";
          
          import FileSaver from "file-saver";
          
          import { ElMessage } from "element-plus";
          
          export const exportExcel=async (name, luckysheet)=> {
            // 獲取 buffer
            let buffer=await getBuffer(luckysheet);
            download(name, buffer);
          };
          
          /**
           *  使用 fileSaver 進行文件保存操作
           * @param {Buffer} buffer
           */
          function download(name, buffer) {
            try {
              const blob=new Blob([buffer], {
                type: "application/vnd.ms-excel;charset=utf-8",
              });
              FileSaver.saveAs(blob, `${name}.xlsx`);
              ElMessage.success("文件導出成功");
            } catch (error) {
              ElMessage.error("文件導出失敗");
            }
          }
          
          /**
           *
           * @param { Array as luckysheet.getluckysheetfile() } luckysheet
           * @returns
           */
          async function getBuffer(luckysheet) {
            // 參數為luckysheet.getluckysheetfile()獲取的對象
            // 1.創建工作簿,可以為工作簿添加屬性
            const workbook=new Excel.Workbook();
            // 2.創建表格,第二個參數可以配置創建什么樣的工作表
            luckysheet.every(function (table) {
              if (table.data.length===0) return true;
              const worksheet=workbook.addWorksheet(table.name);
              // 3.設置單元格合并,設置單元格邊框,設置單元格樣式,設置值
              setStyleAndValue(table.data, worksheet);
              setMerge(table.config.merge, worksheet);
              setBorder(table.config.borderInfo, worksheet);
              return true;
            });
            // 4.寫入 buffer
            const buffer=await workbook.xlsx.writeBuffer();
            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) {
              // const dbrow=worksheet.getRow(rowid+1);
              // //設置單元格行高,默認乘以1.2倍
              // dbrow.height=luckysheet.getRowHeight([rowid])[rowid]*1.2;
              row.every(function (cell, columnid) {
                if (rowid==0) {
                  const dobCol=worksheet.getColumn(columnid + 1);
                  //設置單元格列寬除以8
                  dobCol.width=luckysheet.getColumnWidth([columnid])[columnid] / 8;
                }
                if (!cell) {
                  return true;
                }
                //設置背景色
                let bg=cell.bg || "#FFFFFF"; //默認white
                bg=bg==="yellow" ? "FFFF00" : bg.replace("#", "");
                let fill={
                  type: "pattern",
                  pattern: "solid",
                  fgColor: { argb: 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 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
          
            //設置字體顏色
            fc=fc==="red" ? "FFFF0000" : fc.replace("#", "");
            let font={
              name: typeof ff==="number" ? luckyToExcel[ff] : ff,
              family: 1,
              size: fs,
              color: { argb: fc },
              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;
          }
          


          ?

          關聯文件

          在excel協同的時候,還需要跟我們quill編輯器類似,綁定fileid:

          updateUrl:

          "ws://localhost:9000?fileid=" + router.currentRoute.value.params.fileid, // 實現傳參,

          二開實現websocket的關閉連接:

          // 源碼中 server.js 添加方法
          closeWebSocket: function () {
              let _this=this;
              if ("WebSocket" in window) {
                _this.websocket.close();
              } else console.error("## closeWebSocket", locale().websocket.support);
            },
          
          global.api(api.js 文件)
          /**
           * 導出 websocket 的關閉方法:
           * luckysheet.wsclose() 進行調用
           */
          export function wsclose() {
            console.log('調用自定義方法 server.closeWebSocket()')
            server.closeWebSocket();
          }
          


          重新打包,在需要的地方進行調用:

          ?

          但是每次關閉連接后,都會alert,把這個關了:

          ?

          ?

          與文件關聯后,不是同一個文件的不能協同編輯。

          總結

          到此,功能都已經開發完了。還是那句話哈:

          如果侵權了,請聯系刪除!

          如果侵權了,請聯系刪除!

          如果侵權了,請聯系刪除!

          ****對luckysheet的協同做一下總結吧:

          1. 對pako壓縮數據進行解析,這是第一個難點;
          2. 數據存儲按照分布式存儲會更快;這里是結合著 loadUrl的哈,后端返回保存后的數據進行渲染;
          3. luckyexcel 進行文件導入;
          4. exceljs file-saver 實現文件導出;
          5. 對源碼進行二次開發,實現手動關閉 websocket 連接;
          6. 還有很多細節哈,大家根據需要可以自行定義,有問題歡迎留言討論。


          作者:樸shu
          鏈接:https://juejin.cn/post/7298170736480485376


          主站蜘蛛池模板: 午夜精品一区二区三区免费视频| 在线观看国产一区| 国产香蕉一区二区在线网站| 精品无码一区二区三区在线| 亚洲欧美日韩一区二区三区在线| 福利电影一区二区| 中文字幕在线一区二区三区| 亚洲一区免费观看| 无码少妇一区二区三区芒果| 国产免费一区二区三区免费视频| 福利一区二区三区视频在线观看| 日韩欧美一区二区三区免费观看| 日本一区二区高清不卡| 人妻少妇久久中文字幕一区二区| 人妻av综合天堂一区| 上原亚衣一区二区在线观看| 亚洲午夜一区二区电影院| 日本丰满少妇一区二区三区| 亚洲一区二区在线视频| 日亚毛片免费乱码不卡一区| 亚洲第一区香蕉_国产a| 波霸影院一区二区| 亚州日本乱码一区二区三区 | 国产丝袜美女一区二区三区| 亚洲综合激情五月色一区| 国产一区二区三区免费观看在线| 亚洲丰满熟女一区二区哦| 国产av成人一区二区三区| 国产精品乱码一区二区三| 免费日本一区二区| 蜜桃视频一区二区三区| 精品人妻一区二区三区浪潮在线 | 亚洲日韩国产精品第一页一区| 亚洲熟女综合色一区二区三区| 中文字幕一区二区免费| 午夜性色一区二区三区不卡视频| 亚洲av无码一区二区乱子伦as | 在线精品一区二区三区| 亚洲韩国精品无码一区二区三区| 国产精品电影一区二区三区| 蜜臀AV免费一区二区三区|