299 lines
7.9 KiB
Vue
299 lines
7.9 KiB
Vue
<!-- 文件上传导入组件 -->
|
||
<template>
|
||
<el-dialog v-model="modelValue" :title="title" :width="width" top="5vh" @close="close" append-to-body>
|
||
|
||
<!-- 上传区域 -->
|
||
<div class="upload-section">
|
||
<el-upload class="upload-demo" drag :auto-upload="false" :limit="1" :file-list="fileList"
|
||
:before-upload="beforeUpload" :on-change="handleFileChange" :on-exceed="handleExceed">
|
||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||
<div class="el-upload__text">
|
||
<em>点击或拖拽文件到此处上传</em>
|
||
</div>
|
||
<template #tip>
|
||
<div class="el-upload__tip">
|
||
只能上传 xlsx/xls 文件,且不超过 {{ fileSizeLimit }}MB
|
||
</div>
|
||
</template>
|
||
</el-upload>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="upload-actions">
|
||
<el-button type="primary" @click="handleImport" :disabled="!canImport">导入数据</el-button>
|
||
<el-button @click="handleTemplate">下载模板</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据预览区域 -->
|
||
<div v-if="previewData.length > 0" class="preview-section">
|
||
<h4 style="margin-bottom: 16px;">数据预览</h4>
|
||
<el-table :data="previewData" style="width: 100%;" :max-height="300">
|
||
<el-table-column v-for="col in tableColumns" :key="col.prop" :prop="col.prop" :label="col.label"
|
||
show-overflow-tooltip></el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 加载状态 -->
|
||
<!-- <el-dialog v-model="loadingVisible" title="导入中" :show-close="false">
|
||
<div style="text-align: center; padding: 20px;">
|
||
<el-loading :fullscreen="false" text="正在处理数据,请稍候..." :visible="true"></el-loading>
|
||
</div>
|
||
</el-dialog> -->
|
||
|
||
<!-- <template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="close">关闭</el-button>
|
||
</div>
|
||
</template> -->
|
||
</el-dialog>
|
||
</template>
|
||
|
||
<script setup>
|
||
import * as XLSX from 'xlsx'
|
||
import { ref, computed, defineProps, defineEmits } from 'vue'
|
||
import { UploadFilled } from '@element-plus/icons-vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { qcckPost } from '@/api/qcckApi'
|
||
// Props 定义
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
width: {
|
||
type: String,
|
||
default: '30%'
|
||
},
|
||
title: {
|
||
type: String,
|
||
default: '导入数据'
|
||
},
|
||
|
||
// 文件大小限制(MB)
|
||
fileSizeLimit: {
|
||
type: Number,
|
||
default: 10
|
||
},
|
||
// 表格列配置
|
||
tableColumns: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
// 模板下载地址
|
||
templateUrl: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
// 图标地址
|
||
aiconUrl: {
|
||
type: String,
|
||
default: ''
|
||
},
|
||
isUrl: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
})
|
||
|
||
// Emits 定义
|
||
const emit = defineEmits(['update:modelValue', 'import-success', 'import-error', 'vSocial'])
|
||
|
||
// 响应式数据
|
||
const fileList = ref([])
|
||
const previewData = ref([])
|
||
const loadingVisible = ref(false)
|
||
|
||
// 计算属性:是否可以导入
|
||
const canImport = computed(() => {
|
||
return fileList.value.length > 0 && !loadingVisible.value
|
||
})
|
||
|
||
// 关闭对话框
|
||
const close = () => {
|
||
resetState()
|
||
emit('update:modelValue', false)
|
||
}
|
||
|
||
// 重置状态
|
||
const resetState = () => {
|
||
fileList.value = []
|
||
previewData.value = []
|
||
loadingVisible.value = false
|
||
}
|
||
|
||
// 处理文件选择变化
|
||
const handleFileChange = (file) => {
|
||
// 清空之前的预览数据
|
||
previewData.value = []
|
||
// 只保留最后一个文件
|
||
fileList.value = [file]
|
||
|
||
// 读取文件内容进行预览
|
||
const reader = new FileReader()
|
||
reader.onload = (e) => {
|
||
try {
|
||
const data = e.target.result
|
||
const workbook = XLSX.read(data, { type: 'binary' })
|
||
const firstSheetName = workbook.SheetNames[0]
|
||
const worksheet = workbook.Sheets[firstSheetName]
|
||
// 转换为JSON数据
|
||
const jsonData = XLSX.utils.sheet_to_json(worksheet)
|
||
// 如果有表格列配置,过滤和格式化数据
|
||
if (props.tableColumns && props.tableColumns.length > 0) {
|
||
previewData.value = jsonData.slice(0, 10).map(row => {
|
||
const formattedRow = {}
|
||
props.tableColumns.forEach(col => {
|
||
formattedRow[col.prop] = row[col.label] !== undefined ? row[col.label] : row[col.prop]
|
||
})
|
||
return formattedRow
|
||
})
|
||
} else {
|
||
// 否则直接使用前10条数据作为预览
|
||
previewData.value = jsonData.slice(0, 10)
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('文件解析失败,请检查文件格式')
|
||
console.error('文件解析错误:', error)
|
||
}
|
||
}
|
||
reader.readAsBinaryString(file.raw)
|
||
}
|
||
|
||
// 上传前校验
|
||
const beforeUpload = (file) => {
|
||
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||
file.type === 'application/vnd.ms-excel'
|
||
const isLtLimit = file.size / 1024 / 1024 < props.fileSizeLimit
|
||
|
||
if (!isExcel) {
|
||
ElMessage.error('只能上传 Excel 文件!')
|
||
return false
|
||
}
|
||
if (!isLtLimit) {
|
||
ElMessage.error(`文件大小不能超过 ${props.fileSizeLimit}MB!`)
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// 处理文件超出限制
|
||
const handleExceed = () => {
|
||
ElMessage.error('只能上传一个文件')
|
||
}
|
||
|
||
// 处理导入
|
||
const handleImport = async () => {
|
||
if (!fileList.value[0]) {
|
||
ElMessage.warning('请先选择文件')
|
||
return
|
||
}
|
||
loadingVisible.value = true
|
||
try {
|
||
// 创建FormData对象
|
||
const formData = new FormData()
|
||
formData.append('file', fileList.value[0].raw)
|
||
if (props.isUrl && props.aiconUrl) {
|
||
// 调用上传接口
|
||
qcckPost(formData, props.aiconUrl).then(res => {
|
||
emit('import-success', res)
|
||
close()
|
||
}).catch(error => {
|
||
ElMessage.error(error.message || '文件上传失败')
|
||
emit('import-success', null)
|
||
loadingVisible.value = false
|
||
})
|
||
|
||
} else {
|
||
// 如果没有配置上传接口,只解析文件并返回数据
|
||
const reader = new FileReader()
|
||
reader.onload = (e) => {
|
||
try {
|
||
const data = e.target.result
|
||
const workbook = XLSX.read(data, { type: 'binary' })
|
||
const firstSheetName = workbook.SheetNames[0]
|
||
const worksheet = workbook.Sheets[firstSheetName]
|
||
const jsonData = XLSX.utils.sheet_to_json(worksheet)
|
||
emit('import-success', { data: jsonData })
|
||
close()
|
||
} catch (error) {
|
||
throw error
|
||
}
|
||
}
|
||
reader.readAsBinaryString(fileList.value[0].raw)
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(error.message || '导入失败')
|
||
emit('import-error', error)
|
||
loadingVisible.value = false
|
||
}
|
||
}
|
||
|
||
// 处理模板下载
|
||
const handleTemplate = () => {
|
||
if (props.templateUrl) {
|
||
window.open(props.templateUrl, '_blank')
|
||
} else {
|
||
// 如果没有提供模板下载地址,根据列配置生成简单模板
|
||
try {
|
||
const wb = XLSX.utils.book_new()
|
||
|
||
// 创建表头数据
|
||
const headerData = []
|
||
if (props.tableColumns && props.tableColumns.length > 0) {
|
||
const headerRow = {}
|
||
props.tableColumns.forEach(col => {
|
||
headerRow[col.prop] = col.label
|
||
})
|
||
headerData.push(headerRow)
|
||
}
|
||
|
||
const ws = XLSX.utils.json_to_sheet(headerData)
|
||
XLSX.utils.book_append_sheet(wb, ws, '模板')
|
||
|
||
// 下载模板
|
||
XLSX.writeFile(wb, '导入模板.xlsx')
|
||
} catch (error) {
|
||
ElMessage.error('模板生成失败')
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.upload-section {
|
||
padding: 20px 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.upload-demo {
|
||
width: 100%;
|
||
max-width: 600px;
|
||
}
|
||
|
||
.upload-actions {
|
||
margin-top: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.upload-actions .el-button {
|
||
margin: 0 10px;
|
||
}
|
||
|
||
.preview-section {
|
||
margin-top: 20px;
|
||
border-top: 1px solid #ebeef5;
|
||
padding-top: 20px;
|
||
width: 100%;
|
||
max-width: 1000px;
|
||
align-self: center;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.el-table {
|
||
font-size: 14px;
|
||
}
|
||
</style>
|