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>
|