主题
Excel 导入导出
项目已封装好 Excel 工具函数,位于 @/utils/excel.ts,可直接使用。
已内置
Excel 工具函数已经封装好,无需额外安装或编写。直接导入使用即可。
导出 Excel
基础导出
用户导出示例
vue
<script setup lang="ts">
import { exportToExcel } from '@/utils/excel'
import { userApi } from '@/api/user'
const handleExport = async () => {
// 1. 获取数据
const users = await userApi.getAll()
// 2. 定义导出列
const columns = [
{ header: '用户名', key: 'name' as const, width: 20 },
{ header: '邮箱', key: 'email' as const, width: 30 },
{ header: '创建时间', key: 'createdAt' as const, width: 20 },
]
// 3. 导出
await exportToExcel(users, columns, '用户列表')
}
</script>
<template>
<Button @click="handleExport">导出 Excel</Button>
</template>带数据转换的导出
导出前通常需要转换数据格式(枚举值、日期等):
数据转换导出
typescript
import { exportToExcel } from '@/utils/excel'
import { formatDate, formatCurrency } from '@/utils/format'
const handleExport = async () => {
const orders = await orderApi.getAll()
// 转换数据格式
const exportData = orders.map(order => ({
...order,
// 枚举转中文
status: order.status === 'PAID' ? '已支付' : '未支付',
// 日期格式化
createdAt: formatDate(order.createdAt, 'YYYY-MM-DD HH:mm:ss'),
// 金额格式化
amount: Number(order.amount).toFixed(2),
}))
const columns = [
{ header: '订单号', key: 'orderNo' as const, width: 20 },
{ header: '金额', key: 'amount' as const, width: 15 },
{ header: '状态', key: 'status' as const, width: 10 },
{ header: '创建时间', key: 'createdAt' as const, width: 20 },
]
await exportToExcel(exportData, columns, '订单列表')
}导入 Excel
基础导入
用户导入示例
vue
<script setup lang="ts">
import { ref } from 'vue'
import { parseExcel, validateImportData } from '@/utils/excel'
import { userApi } from '@/api/user'
// Excel 表头类型(使用中文)
interface ImportUser {
用户名: string
邮箱: string
手机号: string
}
const fileInput = ref<HTMLInputElement>()
const importing = ref(false)
const handleFileChange = async (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
importing.value = true
try {
// 1. 解析 Excel 文件
const data = await parseExcel<ImportUser>(file)
// 2. 校验数据
const errors = validateImportData(data, {
用户名: { required: true, message: '用户名不能为空' },
邮箱: { required: true, pattern: /^.+@.+$/, message: '邮箱格式不正确' },
})
if (errors.length > 0) {
toast.error('数据校验失败:\n' + errors.join('\n'))
return
}
// 3. 转换为后端接口格式(中文表头 -> 英文字段)
const users = data.map(item => ({
name: item['用户名'],
email: item['邮箱'],
phone: item['手机号'],
}))
// 4. 调用后端批量导入接口
await userApi.batchImport(users)
toast.success(`成功导入 ${users.length} 条数据`)
} catch (error) {
console.error('导入失败:', error)
toast.error('导入失败,请检查文件格式')
} finally {
importing.value = false
// 清空文件选择器
if (fileInput.value) {
fileInput.value.value = ''
}
}
}
</script>
<template>
<div>
<input
ref="fileInput"
type="file"
accept=".xlsx,.xls"
class="hidden"
@change="handleFileChange"
/>
<Button :loading="importing" @click="fileInput?.click()">
导入 Excel
</Button>
</div>
</template>下载导入模板
提供标准模板供用户下载,避免格式错误:
下载模板
typescript
import { downloadTemplate } from '@/utils/excel'
const handleDownloadTemplate = () => {
downloadTemplate(
['用户名', '邮箱', '手机号'],
'用户导入模板'
)
}API 参考
exportToExcel
导出数据为 Excel 文件。
API 定义
typescript
function exportToExcel<T>(
data: T[],
columns: ExportColumn<T>[],
filename: string,
options?: ExportOptions
): Promise<void>
interface ExportColumn<T> {
header: string // 表头名称
key: keyof T // 数据字段名
width?: number // 列宽度,默认 15
}
interface ExportOptions {
sheetName?: string // 工作表名称,默认 'Sheet1'
headerBgColor?: string // 表头背景色(ARGB),默认 'FFE0E0E0'
headerBold?: boolean // 表头字体加粗,默认 true
}parseExcel
解析 Excel 文件为 JSON 数据。
API 定义
typescript
function parseExcel<T>(file: File): Promise<T[]>downloadTemplate
生成并下载导入模板。
API 定义
typescript
function downloadTemplate(
headers: string[],
filename: string,
options?: ExportOptions
): Promise<void>validateImportData
校验导入数据。
API 定义
typescript
function validateImportData<T>(
data: T[],
rules: Record<string, ValidationRule>
): string[]
interface ValidationRule {
required?: boolean // 是否必填
pattern?: RegExp // 正则校验
message: string // 错误提示信息
}完整示例
带导入导出的管理页面
vue
<script setup lang="ts">
import { ref } from 'vue'
import { exportToExcel, parseExcel, downloadTemplate, validateImportData } from '@/utils/excel'
import { useProducts } from '@/composables/useProducts'
import { productApi } from '@/api/product'
// 数据查询
const page = ref(1)
const pageSize = ref(10)
const { data: products } = useProducts({ page, pageSize })
// 导出
const handleExport = async () => {
const allProducts = await productApi.getAll()
const columns = [
{ header: '商品名称', key: 'name' as const, width: 25 },
{ header: '价格', key: 'price' as const, width: 15 },
{ header: '库存', key: 'stock' as const, width: 10 },
{ header: '状态', key: 'statusText' as const, width: 10 },
]
const exportData = allProducts.map(p => ({
...p,
statusText: p.status === 'active' ? '在售' : '下架',
}))
await exportToExcel(exportData, columns, '商品列表')
}
// 下载模板
const handleDownloadTemplate = () => {
downloadTemplate(['商品名称', '价格', '库存'], '商品导入模板')
}
// 导入
const fileInput = ref<HTMLInputElement>()
const importing = ref(false)
interface ImportProduct {
商品名称: string
价格: string
库存: string
}
const handleImport = async (event: Event) => {
const file = (event.target as HTMLInputElement).files?.[0]
if (!file) return
importing.value = true
try {
const data = await parseExcel<ImportProduct>(file)
const errors = validateImportData(data, {
商品名称: { required: true, message: '商品名称不能为空' },
价格: { required: true, pattern: /^\d+(\.\d{1,2})?$/, message: '价格格式不正确' },
})
if (errors.length) {
toast.error(errors.join('\n'))
return
}
const products = data.map(item => ({
name: item['商品名称'],
price: Number(item['价格']),
stock: Number(item['库存']) || 0,
}))
await productApi.batchImport(products)
toast.success(`成功导入 ${products.length} 条数据`)
} finally {
importing.value = false
if (fileInput.value) fileInput.value.value = ''
}
}
</script>
<template>
<div class="flex gap-2 mb-4">
<Button @click="handleExport">导出 Excel</Button>
<Button @click="handleDownloadTemplate">下载模板</Button>
<input ref="fileInput" type="file" accept=".xlsx" class="hidden" @change="handleImport" />
<Button :loading="importing" @click="fileInput?.click()">导入 Excel</Button>
</div>
<!-- 数据表格 -->
<DataTable :data="products?.items ?? []" />
</template>最佳实践
开发建议
- 后端接口设计:导入需要后端提供批量导入接口(如
POST /api/products/batch) - 数据量限制:建议单次导入不超过 1000 条
- 字段映射:Excel 表头使用中文,导入时转换为后端字段名
- 错误提示:导入失败时明确提示错误原因和行号
- 文件格式:只接受
.xlsx格式,兼容性更好 - 模板下载:提供标准模板供用户下载