亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

前端實現(xiàn)(excel)xlsx文件預(yù)覽的詳細步驟

 更新時間:2024年03月02日 10:17:21   作者:我不是idol  
excel的預(yù)覽庫有不少,也都很強大,但是能很簡單實現(xiàn),下面這篇文章主要給大家介紹了關(guān)于前端實現(xiàn)(excel)xlsx文件預(yù)覽的詳細步驟,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

1. 概述

接到一個任務(wù),是要前端實現(xiàn)文件預(yù)覽效果,百度了一圈,發(fā)現(xiàn)也沒有什么好的方法可以完美的將表格渲染出來。在前端中有sheetjsexceljs可以對xlsx文件進行解析,本來一開始我用的是sheetjs,但是在樣式獲取上遇到了麻煩,所以我改用了exceljs,不過很難受,在樣式獲取時同樣遇到了不小的麻煩,但是我懶得換回sheetjs了,那就直接使用exceljs吧。

要實現(xiàn)xlsx文件預(yù)覽效果,我的想法是使用一個庫對xlsx文件進行解析,然后使用另一個庫對解析出來的數(shù)據(jù)在頁面上進行繪制,綜上,我采用的方案是:exceljs+handsontable

2. 實現(xiàn)步驟

2.1 安裝庫

使用命令: npm i exceljs handsontable @handsontable/react

2.2 使用exceljs解析數(shù)據(jù)并使用handsontable進行渲染

直接貼代碼了:

import Excel from 'exceljs'
import { useState } from 'react';

import { HotTable } from '@handsontable/react';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/dist/handsontable.full.min.css';
import { textRenderer, registerRenderer } from 'handsontable/renderers';

// 注冊模塊
registerAllModules();

export default function XLSXPreView() {
    const [data, setData] = useState([]);

    const handleFile = async (e) => {
        const file = e.target.files[0];

        const workbook = new Excel.Workbook();
        await workbook.xlsx.load(file)
        
        // 第一個工作表
        const worksheet = workbook.getWorksheet(1);
        
        // 遍歷工作表中的所有行(包括空行)
        const sheetData = [];
        worksheet.eachRow({ includeEmpty: true }, function(row, rowNumber) {
            // console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values));
            // 使用row.values獲取每一行的值時總會多出一條空數(shù)據(jù)(第一條),這里我把它刪除
            const row_values = row.values.slice(1);
            sheetData.push(row_values)
        });
        setData(sheetData);
    }

    return (
        <>
            <input type="file" onChange={handleFile}/>
            <div id='table_view'>
                <HotTable 
                    data={data}
                    readOnly={true}
                    rowHeaders={true}
                    colHeaders={true}
                    width="100vw"
                    height="auto"
                    licenseKey='non-commercial-and-evaluation'// 一定得加這個,handsontable是收費的,加了這個才能免費用
                />
                
            </div>
        </>
    )
}

到這里,已經(jīng)實現(xiàn)了從xlsx文件中獲取數(shù)據(jù),并使用handsontable將表格中的數(shù)據(jù)渲染出來,示例結(jié)果如下,如果只需要將數(shù)據(jù)顯示出來,并不需要將樣式什么的一起復(fù)現(xiàn)了,那到這里就已經(jīng)結(jié)束了!

但事實上,這并不是我要做到效果,我的xlsx里面還有樣式什么的,也需要復(fù)現(xiàn),頭疼??

3. 其它的雜七雜八

3.1 單元格樣式

事實上,在exceljs解析xlsx文件時,它順帶一起把樣式獲取到了,通過worksheet.getCell(1, 1).style可以獲取對應(yīng)單元格的樣式,如下,背景色存放在fill.fgColor中,字體顏色存放在font.color中,這樣的話只需要將這些樣式一一賦值給handsontable組件再添加樣式就好了。

但是實際操作的時候卻遇到了問題,先說excel中的顏色,在選擇顏色時,應(yīng)該都會打開下面這個選項框吧,如果你選擇的是標準色,它獲取到的顏色就是十六進制,但是如果你選擇主題中的顏色,那就是另一種結(jié)果了,并且還會有不同的深暗程度tint,這就很難受了!

隨后在控制臺中打印了workbook,發(fā)現(xiàn)它把主題返回了,可以通過work._themes.theme1獲取,不過獲取到的是xml格式的字符串,由于xml我沒學(xué),我不會,所以我就把它轉(zhuǎn)換成json來進行處理了。

第一步

安裝xml轉(zhuǎn)json的庫: npm i fast-xml-parser

import {XMLParser} from 'fast-xml-parser'

// 將主題xml轉(zhuǎn)換成json
const themeXml = workbook._themes.theme1;
const options = {
    ignoreAttributes: false,
    attributeNamePrefix: '_'
}
const parser = new XMLParser(options);
const json = parser.parse(themeXml)
setThemeJson(json);

其實它的theme好像是固定的,也可以在一些格式轉(zhuǎn)換的網(wǎng)站中直接轉(zhuǎn)換成json然后放到一個json文件中,讀取就行,我這里就直接放到一個state中了!

第二步

接下來就是重頭戲了!設(shè)置單元格樣式…

首先安裝一個處理顏色的庫color,用來根據(jù)tint獲得不同明暗程度的顏色: npm i color

下面是獲取顏色的函數(shù):

// 根據(jù)主題和明暗度獲取顏色
const getThemeColor = (themeJson, themeId, tint) => {
    let color = '';
    const themeColorScheme = themeJson['a:theme']['a:themeElements']['a:clrScheme'];
    switch (themeId) {
        case 0:
            color = themeColorScheme['a:lt1']['a:sysClr']['_lastClr'];
            break;
        case 1:
            color = themeColorScheme['a:dk1']['a:sysClr']['_lastClr'];
            break;
        case 2:
            color = themeColorScheme['a:lt2']['a:srgbClr']['_val'];
            break;
        case 3:
            color = themeColorScheme['a:dk2']['a:srgbClr']['_val'];
            break;
        default:
            color = themeColorScheme[`a:accent${themeId-3}`]['a:srgbClr']['_val'];
            break;
    }
    // 根據(jù)tint修改顏色深淺
    color = '#' + color;
    const colorObj = Color(color);
    if(tint){
        if(tint>0){// 淡色
            color = colorObj.lighten(tint).hex();
        }else{ // 深色
            color = colorObj.darken(Math.abs(tint)).hex();
        }
    }
    return color;
}
// 獲取顏色
const getColor = (obj, themeJson) => {
    if('argb' in obj){ // 標準色 
        // rgba格式去掉前兩位: FFFF0000 -> FF0000
        return '#' + obj.argb.substring(2);
    }else if('theme' in obj){ // 主題顏色
        if('tint' in obj){
            return getThemeColor(themeJson, obj.theme, obj.tint);
        }else{
            return getThemeColor(themeJson, obj.theme, null);
        }                
    }
}

然后設(shè)置handonsontable的單元格的一些樣式:顏色、加粗、下劃線、邊框balabala…的

順帶把行高和列寬一起設(shè)置了,這個還比較簡單,就一筆帶過了…

3.2 合并單元格

從獲取到的sheet中有一個_meages屬性,該屬性中存放了表格中所有的合并單元格區(qū)域,所以只需要將它們重新渲染在handsontable中就好。

然后就實現(xiàn)了表格的一些基本功能的預(yù)覽,結(jié)果如下圖:

3. 總結(jié)(附全代碼)

其實這個的本質(zhì)主要就是通過ecxeljs解析表格文件的數(shù)據(jù),然后通過handsontable將它們重新繪制在頁面上,個人覺得這種方法并不好,因為表格里的操作太多了要把它們一一繪制工作量實在是太大了,而且很麻煩,我這里把表格的一些常用到的功能實現(xiàn)了預(yù)覽,還有想表格里放圖片什么的都沒有實現(xiàn),如果有需要,可以根據(jù)需求再進行進行寫。

我寫的其實還有一點bug,單元格的邊框樣式我只設(shè)置了solid和dashed,但事實上excel中單元格的邊框有12種樣式,而且還有對角線邊框,設(shè)置起來好麻煩,我就不弄了,大家用的時候注意一下哈,有需要的話可以自己修改一下!

附上全部代碼:

/**
 *  exceljs + handsontable
 */
import Excel from 'exceljs'
import { useState } from 'react';

import { HotTable } from '@handsontable/react';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/dist/handsontable.full.min.css';
import { textRenderer, registerRenderer } from 'handsontable/renderers';

import {XMLParser} from 'fast-xml-parser'
import Color from 'color';

// 注冊模塊
registerAllModules();

// 根據(jù)主題和明暗度獲取顏色
const getThemeColor = (themeJson, themeId, tint) => {
    let color = '';
    const themeColorScheme = themeJson['a:theme']['a:themeElements']['a:clrScheme'];
    switch (themeId) {
        case 0:
            color = themeColorScheme['a:lt1']['a:sysClr']['_lastClr'];
            break;
        case 1:
            color = themeColorScheme['a:dk1']['a:sysClr']['_lastClr'];
            break;
        case 2:
            color = themeColorScheme['a:lt2']['a:srgbClr']['_val'];
            break;
        case 3:
            color = themeColorScheme['a:dk2']['a:srgbClr']['_val'];
            break;
        default:
            color = themeColorScheme[`a:accent${themeId-3}`]['a:srgbClr']['_val'];
            break;
    }
    // 根據(jù)tint修改顏色深淺
    color = '#' + color;
    const colorObj = Color(color);
    if(tint){
        if(tint>0){// 淡色
            color = colorObj.lighten(tint).hex();
        }else{ // 深色
            color = colorObj.darken(Math.abs(tint)).hex();
        }
    }
    return color;
}
// 獲取顏色
const getColor = (obj, themeJson) => {
    if('argb' in obj){ // 標準色 
        // rgba格式去掉前兩位: FFFF0000 -> FF0000
        return '#' + obj.argb.substring(2);
    }else if('theme' in obj){ // 主題顏色
        if('tint' in obj){
            return getThemeColor(themeJson, obj.theme, obj.tint);
        }else{
            return getThemeColor(themeJson, obj.theme, null);
        }                
    }
}
// 設(shè)置邊框
const setBorder = (style) =>{
    let borderStyle = 'solid';
    let borderWidth = '1px';
    switch (style) {
        case 'thin':
            borderWidth = 'thin';
            break;
        case 'dotted':
            borderStyle = 'dotted';
            break;
        case 'dashDot':
            borderStyle = 'dashed';
            break;
        case 'hair':
            borderStyle = 'solid';
            break;
        case 'dashDotDot':
            borderStyle = 'dashed';
            break;
        case 'slantDashDot':
            borderStyle = 'dashed';
            break;
        case 'medium':
            borderWidth = '2px';
            break;
        case 'mediumDashed':
            borderStyle = 'dashed';
            borderWidth = '2px';
            break;
        case 'mediumDashDotDot':
            borderStyle = 'dashed';
            borderWidth = '2px';
            break;
        case 'mdeiumDashDot':
            borderStyle = 'dashed';
            borderWidth = '2px';
            break;
        case 'double':
            borderStyle = 'double';
            break;
        case 'thick':
            borderWidth = '3px';
            break;
        default:
            break;
    }
    // console.log(borderStyle, borderWidth);
    return [borderStyle, borderWidth];
}

export default function XLSXPreView() {
    // 表格數(shù)據(jù)
    const [data, setData] = useState([]);
    // 表格
    const [sheet, setSheet] = useState([]);
    // 主題
    const [themeJson, setThemeJson] = useState([]);
    // 合并的單元格
    const [mergeRanges, setMergeRanges] = useState([]);

    registerRenderer('customStylesRenderer', (hotInstance, td, row, column, prop, value, cellProperties) => {
        textRenderer(hotInstance, td, row, column, prop, value, cellProperties);
        // console.log(cellProperties);
        // 填充樣式
        if('fill' in cellProperties){
            // 背景顏色
            if('fgColor' in cellProperties.fill && cellProperties.fill.fgColor){
                td.style.background = getColor(cellProperties.fill.fgColor, themeJson);
            }
        }
        // 字體樣式
        if('font' in cellProperties){
            // 加粗
            if('bold' in cellProperties.font && cellProperties.font.bold){
                td.style.fontWeight = '700';
            }
            // 字體顏色
            if('color' in cellProperties.font && cellProperties.font.color){
                td.style.color = getColor(cellProperties.font.color, themeJson);
            }
            // 字體大小
            if('size' in cellProperties.font && cellProperties.font.size){
                td.style.fontSize = cellProperties.font.size + 'px';
            }
            // 字體類型
            if('name' in cellProperties.font && cellProperties.font.name){
                td.style.fontFamily = cellProperties.font.name;
            }
            // 字體傾斜
            if('italic' in cellProperties.font && cellProperties.font.italic){
                td.style.fontStyle = 'italic';
            }
            // 下劃線
            if('underline' in cellProperties.font && cellProperties.font.underline){
                // 其實還有雙下劃線,但是雙下劃綫css中沒有提供直接的設(shè)置方式,需要使用額外的css設(shè)置,所以我也就先懶得弄了
                td.style.textDecoration = 'underline';
                // 刪除線
                if('strike' in cellProperties.font && cellProperties.font.strike){
                    td.style.textDecoration = 'underline line-through';
                }
            }else{
                // 刪除線
                if('strike' in cellProperties.font && cellProperties.font.strike){
                    td.style.textDecoration = 'line-through';
                }
            }
            
        }
        // 對齊
        if('alignment' in cellProperties){
            if('horizontal' in cellProperties.alignment){ // 水平
                // 這里我直接用handsontable內(nèi)置類做了,設(shè)置成類似htLeft的樣子。
                //(handsontable)其實至支持htLeft, htCenter, htRight, htJustify四種,但是其是它還有centerContinuous、distributed、fill,遇到這幾種就會沒有效果,也可以自己設(shè)置,但是我還是懶的弄了,用到的時候再說吧
                const name =  cellProperties.alignment.horizontal.charAt(0).toUpperCase() + cellProperties.alignment.horizontal.slice(1);
                td.classList.add(`ht${name}`);
            }
            if('vertical' in cellProperties.alignment){ // 垂直
                // 這里我直接用handsontable內(nèi)置類做了,設(shè)置成類似htTop的樣子。
                const name =  cellProperties.alignment.vertical.charAt(0).toUpperCase() + cellProperties.alignment.vertical.slice(1);
                td.classList.add(`ht${name}`);
            }
        }
        // 邊框
        if('border' in cellProperties){
            if('left' in cellProperties.border &&  cellProperties.border.left){// 左邊框
                const [borderWidth, borderStyle] = setBorder(cellProperties.border.left.style);
                let color = '';
                // console.log(row, column, borderWidth, borderStyle);
                if(cellProperties.border.left.color){
                    color = getColor(cellProperties.border.left.color, themeJson);
                }
                td.style.borderLeft = `${borderStyle} ${borderWidth} ${color}`;
            }
            if('right' in cellProperties.border &&  cellProperties.border.right){// 左邊框
                const [borderWidth, borderStyle] = setBorder(cellProperties.border.right.style);
                // console.log(row, column, borderWidth, borderStyle);
                let color = '';
                if(cellProperties.border.right.color){
                    color = getColor(cellProperties.border.right.color, themeJson);
                }
                td.style.borderRight = `${borderStyle} ${borderWidth} ${color}`;
            }
            if('top' in cellProperties.border &&  cellProperties.border.top){// 左邊框
                const [borderWidth, borderStyle] = setBorder(cellProperties.border.top.style);
                let color = '';
                // console.log(row, column, borderWidth, borderStyle);
                if(cellProperties.border.top.color){
                    color = getColor(cellProperties.border.top.color, themeJson);
                }
                td.style.borderTop = `${borderStyle} ${borderWidth} ${color}`;
            }
            if('bottom' in cellProperties.border &&  cellProperties.border.bottom){// 左邊框
                const [borderWidth, borderStyle] = setBorder(cellProperties.border.bottom.style);
                let color = '';
                // console.log(row, column, borderWidth, borderStyle);
                if(cellProperties.border.bottom.color){
                    color = getColor(cellProperties.border.bottom.color, themeJson);
                }
                td.style.borderBottom = `${borderStyle} ${borderWidth} ${color}`;
            }
        }
          
    });

    const handleFile = async (e) => {
        const file = e.target.files[0];

        const workbook = new Excel.Workbook();
        await workbook.xlsx.load(file)

        const worksheet = workbook.getWorksheet(1);

        // const sheetRows = worksheet.getRows(1, worksheet.rowCount);
        setSheet(worksheet)
        
        // console.log(worksheet.getCell(1, 1).style);
        
        // 遍歷工作表中的所有行(包括空行)
        const sheetData = [];
        worksheet.eachRow({ includeEmpty: true }, function(row, rowNumber) {
            // console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values));
            // 使用row.values獲取每一行的值時總會多出一條空數(shù)據(jù)(第一條),這里我把它刪除
            const row_values = row.values.slice(1);
            sheetData.push(row_values)
        });
        setData(sheetData);

        // 將主題xml轉(zhuǎn)換成json
        const themeXml = workbook._themes.theme1;
        const options = {
            ignoreAttributes: false,
            attributeNamePrefix: '_'
        }
        const parser = new XMLParser(options);
        const json = parser.parse(themeXml)
        setThemeJson(json);

        // 獲取合并的單元格
        const mergeCells = [];
        
        for(let i in worksheet._merges){
            const {top, left, bottom, right} = worksheet._merges[i].model;
            mergeCells.push({ row: top-1, col: left-1, rowspan: bottom-top+1 , colspan: right-left+1})
        }
        setMergeRanges(mergeCells)
        console.log(worksheet);
    }

    return (
        <>
            <input type="file" onChange={handleFile}/>
            <div id='table_view'>
                <HotTable 
                    data={data}
                    readOnly={true}
                    rowHeaders={true}
                    colHeaders={true}
                    width="100vw"
                    height="auto"
                    licenseKey='non-commercial-and-evaluation'
                    rowHeights={function(index) {
                        if(sheet.getRow(index+1).height){
                            // exceljs獲取的行高不是像素值,事實上,它是23px - 13.8 的一個映射。所以需要將它轉(zhuǎn)化為像素值
                            return sheet.getRow(index+1).height * (23 / 13.8);
                        }
                        return 23;// 默認
                    }}
                    colWidths={function(index){
                        if(sheet.getColumn(index+1).width){
                            // exceljs獲取的列寬不是像素值,事實上,它是81px - 8.22 的一個映射。所以需要將它轉(zhuǎn)化為像素值
                            return sheet.getColumn(index+1).width * (81 / 8.22);
                        }
                        return 81;// 默認
                    }}
                    cells={(row, col, prop) => {
                        const cellProperties  = {};
                        const cellStyle = sheet.getCell(row+1, col+1).style
                        
                        if(JSON.stringify(cellStyle) !== '{}'){
                            // console.log(row+1, col+1, cellStyle);
                            for(let key in cellStyle){
                                cellProperties[key] = cellStyle[key];
                            }
                        } 
                        return {...cellProperties, renderer: 'customStylesRenderer'};
                    }}
                    mergeCells={mergeRanges}
                />
                
            </div>
        </>
    )
}

總結(jié) 

到此這篇關(guān)于前端實現(xiàn)(excel)xlsx文件預(yù)覽的文章就介紹到這了,更多相關(guān)前端實現(xiàn)excel文件預(yù)覽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論