Location>code7788 >text

Get the XLSX preview? Don’t look for it, these libraries (especially the last one) are so delicious!

Popularity:290 ℃/2025-04-23 17:24:50

- Hey, I'm Immersive Talk
-   This article was first published in [Immersive Talk], and my personal blog **** was also updated simultaneously.
- Please indicate the source and copyright information at the beginning of the article when reprinting.
- If this article is helpful to you, please **like**, **comments**, **repost**, support it, thank you!
-  The platform creation will be a little more Buddhist, and more articles will be updated on my personal blog. Welcome to my personal blog.

Front-end people often encounter this kind of demand: users send Excel to a bang, and you have to give it a decent preview on the web page? Sometimes I have to edit, which is quite troublesome.

I have stepped on a lot of pitfalls and tried many libraries on the market. Today I will talk about a few more mainstream choices, especially the last one, which I personally recommend!

## Online Preview Demo

[Stackblitz online preview](/edit/sb1-oekrt12u?file=)

## The first player: the old-fashioned team xlsx

When it comes to processing Excel, the xlsx library probably cannot be avoided. The 35k star on GitHub is simply an old-level existence.

### Install? Old rules:

```bash
npm install xlsx
```

### It's very straightforward to use. Let’s take a look at the code:

```vue
<template>
    <input type="file" @change="readExcel" />
</template>

<script setup>
import { ref } from 'vue';
import * as XLSX from 'xlsx';

// Read Excel file
const readExcel = event => {
    const file = [0];
    const reader = new FileReader();
    = e => {
        const data = new Uint8Array();
        const workbook = (data, { type: 'array' });

// Get the first worksheet
        const firstSheet = [[0]];

// Convert to JSON
        const jsonData = .sheet_to_json(firstSheet);
('Here, JSON data is obtained:', jsonData);
    };
    (file);
};
</script>
```

![alt text](/work/143/25/de82a72bdc8e4829/d00f950_image.png)

The above is to read a file and get the first sheet and convert it to JSON. Very simple and crude, right?

### It is not complicated to create a preview demo with a file selector:

```vue
<template>
    <div>
        <input type="file" accept=".xlsx,.xls" @change="handleFile" />

        <div v-if=" > 0" style="overflow-x: auto; margin-top: 20px">
            <table border="1" cellPadding="5" style="border-collapse: collapse">
                <thead>
                    <tr>
                        <th v-for="(column, index) in columns" :key="index">
                            {{ }}
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(row, rowIndex) in data" :key="rowIndex">
                        <td v-for="(column, colIndex) in columns" :key="colIndex">
                            {{ row[] }}
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import * as XLSX from 'xlsx';

const data = ref([]);
const columns = ref([]);

const handleFile = (e: Event) => {
  const file = ( as HTMLInputElement).files?.[0];
  if (!file) return;

  const reader = new FileReader();
  = (event) => {
    try {
// Modify the variable name to avoid conflicts with external responsive variables
      const fileData = new Uint8Array(?.result as ArrayBuffer);
      const workbook = (fileData, { type: 'array' });
      const worksheet = [[0]];

// Use header: 1 to get the original array format
      const jsonData = .sheet_to_json(worksheet, { header: 1 });

      if ( > 0) {
// The first row is used as the column title
        = jsonData[0] as string[];
// The rest of the rows are used as data
        = (1);
('Data loaded:', { column number: , row number: });
      }
    } catch (error) {
('Excel parsing failed:', error);
alert('File parsing failed, please check the file format');
    }
  };

  (file);
};
</script>
```

![alt text](/work/143/25/de82a72bdc8e4829/4b34881_image)

xlsx, this guy has obvious advantages: light and fast! The core library is not large in size, has a slew of parsing speed, and has good compatibility. It can basically be eaten in old formats and new formats. The community is also active, and most of the problems Google has solutions.

But the disadvantages must be mentioned: its API design feels a bit...well...old school? Or rather low-level, not very intuitive. I often have to process another data structure I want to get (just like in the demo above). Moreover, if you want to connect styles, such as cell colors, fonts, etc., then xlsx is a little overwhelmed, and the style processing ability is basically no.

I personally think that if your need is to simply read and write data and don’t care about styles, then xlsx is definitely enough and has great efficiency. If you need to be more complex, such as highly restore the Excel style or deal with complex formulas, it will feel like "a small horse pulls a big cart" when using it.

## The second contestant: heavyweight guest Handsontable

After talking about the basic model, let’s take a look at a heavyweight: Handsontable. The biggest selling point of this guy is to directly give you an online form that looks and looks like Excel!

### To install more Vue adapter packages:

```bash
npm install handsontable
npm install @handsontable/vue3 # Vue3 special package
```

Don't forget CSS:

```javascript
import 'handsontable/dist/';
```

### Basic usage, it is initialized in a DOM container:

```vue
<template>
    <div ></div>
</template>

<script setup>
import { onMounted } from 'vue';
import Handsontable from 'handsontable';
import 'handsontable/dist/';

onMounted(() => {
// Initialize the table
    const container = ('excel-preview');
    const hot = new Handsontable(container, {
        data: [
['Name', 'Age', 'City'],
['Zhang San', 28, 'Beijing'],
['Li Si', 32, 'Shanghai'],
['Wang Wu', 25, 'Guangzhou'],
        ],
        rowHeaders: true,
        colHeaders: true,
        contextMenu: true,
licenseKey: 'non-commercial-and-evaluation', // Note: Commercial use costs money! This is very important!
    });
});
</script>
```

![alt text](/work/143/25/de82a72bdc8e4829/b9cb898_image)

### Make an editable demo and take a look? This is its strength:

```vue
<template>
    <div class="handsontable-container">
<h2>Handsontable Data Analysis Tool</h2>

        <div class="toolbar">
            <div class="filter-section">
<label> Departmental Filter: </label>
                <select v-model="selectedDepartment" @change="applyFilters">
<option value="all">All departments</option>
<option value="Sales">Sales</option>
<option value="Market">Market</option>
<option value="Technology">Technology</option>
                </select>
            </div>

            <div class="toolbar-actions">
<button @click="addNewRow">Add an employee</button>
<button @click="saveData">Save Data</button>
<button @click="exportToExcel">Export Excel</button>
            </div>
        </div>

        <hot-table
            ref="hotTableRef"
            :data="filteredData"
            :colHeaders="headers"
            :rowHeaders="true"
            :width="'100%'"
            :height="500"
            :contextMenu="contextMenuOptions"
            :columns="columnDefinitions"
            :nestedHeaders="nestedHeaders"
            :manualColumnResize="true"
            :manualRowResize="true"
            :colWidths="colWidths"
            :beforeChange="beforeChangeHandler"
            :afterChange="afterChangeHandler"
            :cells="cellsRenderer"
            licenseKey="non-commercial-and-evaluation"
        ></hot-table>

        <div class="summary-section">
<h3>Data Statistics</h3>
            <div class="summary-items">
<div class="summary-item"> <strong>Total number of employees:</strong> {{ totalEmployees }} </div>
<div class="summary-item"> <strong>Average Performance Score:</strong> {{ averagePerformance }} </div>
<div class="summary-item"> <strong>Total salary and expenditure:</strong> {{ totalSalary }} </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { HotTable } from '@handsontable/vue3';
import { registerAllModules } from 'handsontable/registry';
import 'handsontable/dist/';
import * as XLSX from 'xlsx';
import Handsontable from 'handsontable';

// Register all modules
registerAllModules();

// Table header definition
const headers = ['ID', 'name', 'department', 'job', 'job date', 'salary', 'performance score', 'status'];

// Nested table headers
const nestedHeaders = [['Employee Basic Information', '', '', '', '', '', '', '', ''], headers];

// Column width setting
const colWidths = [60, 100, 100, 120, 120, 100, 100, 120];

// Column definition
const columnDefinitions = [
    { data: 'id', type: 'numeric', readOnly: true },
    { data: 'name', type: 'text' },
    {
        data: 'department',
        type: 'dropdown',
source: ['Sales', 'Market', 'Technology', 'Personnel', 'Financial'],
    },
    { data: 'position', type: 'text' },
    {
        data: 'joinDate',
        type: 'date',
        dateFormat: 'YYYY-MM-DD',
        correctFormat: true,
    },
    {
        data: 'salary',
        type: 'numeric',
        numericFormat: {
            pattern: '¥ 0,0.00',
            culture: 'zh-CN',
        },
    },
    {
        data: 'performance',
        type: 'numeric',
        numericFormat: {
            pattern: '0.0',
        },
    },
    {
        data: 'status',
        type: 'dropdown',
source: ['On-job', 'resignation', 'leave'],
    },
];

// Right-click menu options
const contextMenuOptions = {
    items: {
row_above: { name: 'Insert row above' },
row_below: { name: 'Insert row below' },
remove_row: { name: 'Delete row' },
        separator1: ,
copy: { name: 'Copy' },
cut: { name: 'cut' },
        separator2: ,
columns_resize: { name: 'Adjust column width' },
alignment: { name: 'alignment' },
    },
};

// Initial data
const initialData = [
    {
        id: 1,
name: 'Zhang San',
department: 'Sales',
position: 'Sales Manager',
        joinDate: '2022-01-15',
        salary: 15000,
        performance: 4.5,
status: 'On-service',
    },
    {
        id: 2,
name: 'Li Si',
department: 'technology',
position: 'Advanced Development',
        joinDate: '2021-05-20',
        salary: 18000,
        performance: 4.7,
status: 'On-service',
    },
    {
        id: 3,
name: 'Wang Wu',
department: 'market',
position: 'Market Specialist',
        joinDate: '2022-03-10',
        salary: 12000,
        performance: 3.8,
status: 'On-service',
    },
    {
        id: 4,
name: 'Zhao Liu',
department: 'technology',
position: 'Development Engineer',
        joinDate: '2020-11-05',
        salary: 16500,
        performance: 4.2,
status: 'On-service',
    },
    {
        id: 5,
name: 'Qian Qi',
department: 'Sales',
position: 'Sales Representative',
        joinDate: '2022-07-18',
        salary: 10000,
        performance: 3.5,
status: 'Vacation',
    },
    {
        id: 6,
name: 'Sun Ba',
department: 'market',
position: 'Market Director',
        joinDate: '2019-02-28',
        salary: 25000,
        performance: 4.8,
status: 'On-service',
    },
    {
        id: 7,
name: 'Zhou Jiu',
department: 'technology',
position: 'Test Engineer',
        joinDate: '2021-09-15',
        salary: 14000,
        performance: 4.0,
status: 'On-service',
    },
    {
        id: 8,
name: 'Wu Shi',
department: 'Sales',
position: 'Sales Representative',
        joinDate: '2022-04-01',
        salary: 11000,
        performance: 3.6,
status: 'Resignation',
    },
];

// Table reference
const hotTableRef = ref(null);
const data = ref([...initialData]);
const selectedDepartment = ref('all');

// Filtered data
const filteredData = computed(() => {
    if ( === 'all') {
        return ;
    }
    return (item => === );
});

// Statistics
const totalEmployees = computed(() => (emp => === 'On-job' || === 'Farewell').length);

const averagePerformance = computed(() => {
const activeEmployees = (emp => === 'On-Service');
    if ( === 0) return 0;

    const sum = ((acc, emp) => acc + , 0);
    return (sum / ).toFixed(1);
});

const totalSalary = computed(() => {
const activeEmployees = (emp => === 'On-job' || === 'Vacation');
    const sum = ((acc, emp) => acc + , 0);
    return `¥ ${('zh-CN')}`;
});

// Cell Renderer - Conditional Format
const cellsRenderer = (row, col, prop) => {
    const cellProperties = {};

// Performance rating condition format
    if (prop === 'performance') {
        const value = [row]?.performance;

        if (value >= 4.5) {
            = 'bg-green';
        } else if (value >= 4.0) {
            = 'bg-light-green';
        } else if (value < 3.5) {
            = 'bg-red';
        }
    }

// Status conditional format
    if (prop === 'status') {
        const status = [row]?.status;

if (status === 'On-Service') {
            = 'status-active';
} else if (status === 'Resignation') {
            = 'status-inactive';
} else if (status === 'Vacation') {
            = 'status-vacation';
        }
    }

    return cellProperties;
};

// Data verification
const beforeChangeHandler = (changes, source) => {
    if (source === 'edit') {
        for (let i = 0; i < ; i++) {
            const [row, prop, oldValue, newValue] = changes[i];

// Salary verification: cannot be less than 0
            if (prop === 'salary' && newValue < 0) {
                changes[i][3] = oldValue;
            }

// Performance verification: Scope 1-5
            if (prop === 'performance') {
                if (newValue < 1) changes[i][3] = 1;
                if (newValue > 5) changes[i][3] = 5;
            }
        }
    }
    return true;
};

// Processing after data changes
const afterChangeHandler = (changes, source) => {
    if (!changes) return;

    setTimeout(() => {
        if (?.hotInstance) {
            ();
        }
    }, 0);
};

// Apply filters
const applyFilters = () => {
    if (?.hotInstance) {
        ();
    }
};

// Add new line
const addNewRow = () => {
    const newId = (...(item => ), 0) + 1;
    ({
        id: newId,
        name: '',
        department: '',
        position: '',
        joinDate: new Date().toISOString().split('T')[0],
        salary: 0,
        performance: 3.0,
status: 'On-service',
    });

    if (?.hotInstance) {
        setTimeout(() => {
            ();
        }, 0);
    }
};

// Save data
const saveData = () => {
// Here you can add API saving logic
alert('Data saved');
};

// Export as Excel
const exportToExcel = () => {
    const currentData = ;
    const ws = .json_to_sheet(currentData);
    const wb = .book_new();
.book_append_sheet(wb, ws, 'employee data');
(wb, 'employee data report.xlsx');
};

// Make sure the component is properly rendered after it is mounted
onMounted(() => {
    setTimeout(() => {
        if (?.hotInstance) {
            ();
        }
    }, 100);
});
</script>

<style>
.handsontable-container {
    padding: 20px;
    font-family: Arial, sans-serif;
}

.toolbar {
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    align-items: center;
}

.filter-section {
    display: flex;
    align-items: center;
    gap: 10px;
}

.toolbar-actions {
    display: flex;
    gap: 10px;
}

button {
    padding: 8px 16px;
    ">#4285f4;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
    transition: background-color 0.3s;
}

button:hover {
    ">#3367d6;
}

select {
    padding: 6px;
    border-radius: 4px;
    border: 1px solid #ccc;
}

.summary-section {
    margin-top: 20px;
    padding: 15px;
    ">#f9f9f9;
    border-radius: 6px;
}

.summary-items {
    display: flex;
    gap: 30px;
    margin-top: 10px;
}

.summary-item {
    padding: 10px;
    ">;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Conditional formatting style */
.bg-green {
    ">;
}

.bg-light-green {
    background-color: rgba(139, 195, 74, 0.2) !important;
}

.bg-red {
    ">;
}

.status-active {
    font-weight: bold;
    color: #2e7d32;
}

.status-inactive {
    font-weight: bold;
    color: #d32f2f;
}

.status-vacation {
    font-weight: bold;
    color: #f57c00;
}
</style>
```

![alt text](/work/143/25/de82a72bdc8e4829/e62a149_image)

The awesomeness of Handsontable: The interface is invincible!

User experience is almost seamlessly connected with Excel, which is sorting, filtering, merging cells, formula calculation, right-click menu, drag and adjust rows, has a lot of fancy functions.

It is also very customizable, and there are many event hooks. The official also carries the integration packages of Vue and React frameworks.

But (there is always a but, right?):

expensive! Commercial licenses are not cheap, which is a threshold for many projects. Although it is not commercially licensed, you know.

Heavy! The cost of full functions is that it is large in size and may load a little slower, especially for performance-sensitive pages.

There is pressure on the volume of big data: Once there are more ranks, the performance may be a little tight.

Learning curve: There are so many configuration items, and you need to spend some time reading the documents if you want to play.

Personally, Handsontable is like you went to a luxury restaurant with luxurious decoration and exquisite dishes, and the experience was great, but your wallet hurts when you check out.

If the project is well budgeted and users strongly demand “the experience like Excel”, it is indeed a king.

## Final appearance: My favorite ExcelJS

I mentioned two things before, one is light but simple, and the other is luxurious but expensive.

So is there any compromise, powerful and free?

ExcelJS debuts! This guy gives me the feeling: modern, all-rounder, and the API is designed quite comfortable.

### Old rules, install

```bash
npm install exceljs
```

### Basic usage, note that it uses async/await, which is very modern:

```vue
<template>
    <input type="file" @change="readExcel" />
</template>

<script setup>
import { ref } from 'vue';
import ExcelJS from 'exceljs';

const readExcel = async event => {
    const file = [0];
    if (!file) return;

// It is best to add a try...catch
    try {
        const workbook = new ();
const arrayBuffer = await (); // Read ArrayBuffer directly to save trouble
        await (arrayBuffer);

const worksheet = (1); // Get the first worksheet
        const data = [];

        ((row, rowNumber) => {
            const rowData = [];
            ((cell, colNumber) => {
                ();
            });
// Its API is easy to traverse
            (rowData);
        });

        (data);
return data; // Return the parsed data
    } catch (error) {
('The parsing with ExcelJS failed, check the file?', error);
alert('The file seems to be a bit problematic, it cannot be parsed');
    }
};
</script>
```

![alt text](/work/143/25/de82a72bdc8e4829/94bc53f_image)

### Let’s have a exciting demo: I will also remove the Excel style for you!

```vue
<template>
    <div>
<button @click="exportAdvancedExcel">Export Advanced Excel</button>
    </div>
</template>

<script setup lang="ts">
import ExcelJS from 'exceljs';

// Advanced data types
interface AdvancedData {
    id: number;
    name: string;
    department: string;
    salary: number;
    joinDate: Date;
    performance: number;
}

// Generate sample data
const generateData = () => {
    const data: AdvancedData[] = [];
    for (let i = 1; i <= 5; i++) {
        ({
            id: i,
name: `employee ${i}`,
department: ['Technology Department', 'Market Department', 'Treasury Department'][i % 3],
            salary: 10000 + i * 1000,
            joinDate: new Date(2020 + i, i % 12, i),
            performance: () * 100,
        });
    }
    return data;
};

const exportAdvancedExcel = async () => {
    const workbook = new ();
const worksheet = ('Employee Report');

// Set document properties
= 'Enterprise Management System';
= 'Admin';
    = new Date();

// Set page layout
    = {
        orientation: 'landscape',
        margins: { left: 0.7, right: 0.7, top: 0.75, bottom: 0.75 },
    };

// Create custom styles
    const headerStyle = {
        font: { bold: true, color: { argb: 'FFFFFFFF' } },
        fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4F81BD' } },
        border: {
            top: { style: 'thin' },
            left: { style: 'thin' },
            bottom: { style: 'thin' },
            right: { style: 'thin' },
        },
        alignment: { vertical: 'middle', horizontal: 'center' },
    };

    const moneyFormat = '"¥"#,##0.00';
    const dateFormat = 'yyyy-mm-dd';
    const percentFormat = '0.00%';

// Merge the title line
    ('A1:F1');
    const titleCell = ('A1');
= '2023 annual employee data report';
    = {
        font: { size: 18, bold: true, color: { argb: 'FF2E75B5' } },
        alignment: { vertical: 'middle', horizontal: 'center' },
    };

// Set column definition
    = [
{ header: 'work number', key: 'id', width: 10 },
{ header: 'name', key: 'name', width: 15 },
{ header: 'department', key: 'department', width: 15 },
        {
header: 'Salary',
            key: 'salary',
            width: 15,
            style: { numFmt: moneyFormat },
        },
        {
header: 'Enterprise Date',
            key: 'joinDate',
            width: 15,
            style: { numFmt: dateFormat },
        },
        {
header: 'Performance',
            key: 'performance',
            width: 15,
            style: { numFmt: percentFormat },
        },
    ];

// Apply the header style
    (2).eachCell(cell => {
        = headerStyle;
    });

// Add data
    const data = generateData();
    (data);

// Add formula rows
    const totalRow = ({
id: 'Total',
        salary: { formula: 'SUM(D3:D7)' },
        performance: { formula: 'AVERAGE(F3:F7)' },
    });

// Set the total line style
    (cell => {
        = {
            font: { bold: true },
            fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFCE4D6' } },
        };
    });

// Add conditional format
    ({
        ref: 'F3:F7',
        rules: [
            {
                type: 'cellIs',
                operator: 'greaterThan',
                formulae: [0.8],
                style: { fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFC6EFCE' } } },
            },
        ],
    });

// Generate Blob and download
    const buffer = await ();
    const blob = new Blob([buffer], {
        type: 'application/',
    });

// Download using native API
    const url = (blob);
    const link = ('a');
    = url;
= 'Employee Report.xlsx';
    (link);
    ();
    (link);
    (url);
};
</script>
```

![alt text](/work/143/25/de82a72bdc8e4829/2162e34_image)

Why do I prefer ExcelJS?

API friendly: Promise style, chain calls, comfortable to write and easier to read. It feels like it is designed for modern JS development.

Comprehensive functions: Not only reading and writing data, but also styles, formulas, merging cells, pictures, form controls... It supports quite a lot of Excel features. Especially reading and modifying styles, this is too important for scenarios where "restore" Excel looks!

Free and open source! This is so delicious that there is no worries about commercial use.

The documentation is clear: The official documentation is well written and the examples are sufficient.

Of course, nothing is perfect:

The volume is larger than xlsx: But the function is much stronger, it is acceptable.

The support for complex formulas may be limited: extremely complex nested formulas or macros may still be difficult to handle (but most scenarios are enough).

Extra large file performance: Excel with dozens or hundreds of megabytes may be slow to parse, or memory usage is high (to be honest, which library does not have a headache to handle such files).

When I used xlsx before, I always had to write a bunch of conversion logic myself, and the data structure was very annoying to process. After changing to ExcelJS, I felt that the world became much more pure. Especially it can read out the background color, font, border information of the cell, which is very useful for previewing!

How to choose in actual combat? Or... all?

In fact, these three libraries are not forced to "live to the death". In real projects, it can be used according to the situation:

Simple and quick import and export: Users upload a template, or export a simple data, just use xlsx, which is easy to save money.

It is necessary to ensure that the style is retained or complex analysis: The user has sent a report with format, and you want to restore the preview as much as possible, so ExcelJS is the main force.

Online editing and strong interaction are required: If you are not doing previews, but an online Excel-like editor, then spending money on Handsontable may be the closest to the goal (if the budget allows).

I have even seen a project doing this: first use xlsx to quickly read basic data and Sheet names to make a "second opening" preview, and then use ExcelJS to perform detailed and styled parsing in the background or asynchronously.

This is both fast and can ensure the final result.

The following (pseudo) code snippet is probably this idea:

```vue
<template>
    <div class="excel-viewer">
        <div class="controls">
            <input type="file" @change="e => detailedParse([0])" accept=".xlsx,.xls" />
<button @click="exportToExcel">Export Excel</button>
        </div>

<div v-if="isLoading">Loading...</div>

        <template v-else>
            <div v-if=" > 0" class="sheet-tabs">
                <button
                    v-for="(name, index) in sheetNames"
                    :key="index"
                    :class="{ active: activeSheet === index }"
                    @click="handleSheetChange(index)"
                >
                    {{ name }}
                </button>
            </div>

            <hot-table
                v-if=" > 0"
                ref="hotTableRef"
                :data="data"
                :rowHeaders="true"
                :colHeaders="true"
                :width="'100%'"
                :height="400"
                licenseKey="non-commercial-and-evaluation"
            ></hot-table>
        </template>
    </div>
</template>

<script setup>
import { ref } from 'vue';
import * as XLSX from 'xlsx'; // for quick preview & export
import ExcelJS from 'exceljs'; // for detailed parsing
import { HotTable } from '@handsontable/vue3'; // for display & edit
import 'handsontable/dist/';

const data = ref([]);
const isLoading = ref(false);
const sheetNames = ref([]);
const activeSheet = ref(0);
const hotTableRef = ref(null);

// Quick preview (optional, or use detailedParse directly)
const quickPreview = file => {
    = true;
    const reader = new FileReader();
    = e => {
        try {
            const data = new Uint8Array();
            const workbook = (data, { type: 'array' });
            = ;

            const firstSheet = [[0]];
            const jsonData = .sheet_to_json(firstSheet, { header: 1 });
            = jsonData;
            = 0;
        } catch (error) {
('Preview failed:', error);
alert('File preview failed');
        } finally {
            = false;
        }
    };
    (file);
};

// Use ExcelJS to analyze in detail
const detailedParse = async file => {
    = true;
    try {
        const workbook = new ();
        const arrayBuffer = await ();
        await (arrayBuffer);

// Maybe you can save the style information parsed by ExcelJS here, and you may use it in the future
// For example, try to write back the style with ExcelJS when exporting? That's more advanced
        const names = (sheet => );
        = names;

// parse the first sheet
        parseWorksheet([0]);
        = 0;
    } catch (error) {
('Resolution failed:', error);
alert('File parsing failed');
    } finally {
        = false;
    }
};

// parse a worksheet and update Handsontable data
const parseWorksheet = worksheet => {
    const sheetData = [];
    ((row, rowNumber) => {
        const rowData = [];
        ((cell, colNumber) => {
            let value = ;
// Special types such as processing dates
            if (value instanceof Date) {
                value = ();
            }
            (value);
        });
        (rowData);
    });
// The data structure here must be adapted to Handsontable, usually a two-dimensional array
    = sheetData;
};

// Switch Sheet (re-re-call parseWorksheet)
const handleSheetChange = async index => {
    = index;
// Reload and parse the data corresponding to the Sheet... This requires saving the workbook instance
// Or parse and cache all sheet data when detailedParse? Look at memory consumption
};

// Export (for simplicity, use xlsx to quickly export the current Handsontable data)
const exportToExcel = () => {
    const ws = .aoa_to_sheet();
    const wb = .book_new();
    .book_append_sheet(wb, ws, 'Sheet1');
(wb, 'Export data.xlsx');
// If you want to export a style, you have to use ExcelJS to write it, which will be much more complicated.
};
</script>

<style scoped>
.excel-viewer {
    margin: 20px;
}
.controls {
    margin-bottom: 15px;
}
.sheet-tabs {
    display: flex;
    margin-bottom: 10px;
}
.sheet-tabs button {
    padding: 5px 10px;
    margin-right: 5px;
    border: 1px solid #ccc;
    background: #f5f5f5;
    cursor: pointer;
}
.sheet-tabs {
    background: #e0e0e0;
    border-bottom: 2px solid #1890ff;
}
</style>
```

## Let me summarize my personal opinion:

After all, these libraries have their own advantages:

xlsx (SheetJS): An experienced driver, suitable for simple scenarios that pursue extreme performance and volume. The code is written less and runs fast, but it does not pay much attention to the "interior" (style).

Handsontable: Luxury car that provides a near-perfect Excel editing experience. It’s not worth mentioning that you have a powerful function, but it depends on whether the money in your pocket is enough.

ExcelJS: A reliable all-round partner. The API is modern, has balanced functions, good style support, and the key is free! Can help you solve most problems.

Seriously, there is no silver bullet. Which one to choose ultimately depends on your specific needs and project restrictions.

But if I have to recommend one, I will definitely be ExcelJS.

It balances so well between features, ease of use, and cost (free!).

For most scenarios that require fine processing of Excel files (especially with style preview), it is the most fragrant choice!

Okay, I'll just talk so much, I hope it can help you! Go and try it now!

## Other good articles recommended

[Practical Sharing] A comprehensive analysis of the 10 payment platforms, an essential for independent development! ](/s/Nf1K_8KHLJH_aegL74NXFA)

[About MCP, you must know these websites! ](/s/pR76UwvsJQyRE__LVx6Whg)

[To do Docx preview, be sure to make this divine library! ! ](/s/gwTbX3hM_GPdDVg3W1ftAQ)

[【Full Summary】Complete Overview of New JavaScript Features in the Last Five Years](/s/f5pIdyY8grx9t6qYxMgR1w)

[About Node, be sure to learn this 100,000+ Star project! ](/s/RGFQbqzmrY1NVkdUsQcMBw)