Files
sgxt_web/src/views/backOfficeSystem/HumanIntelligence/infoCollection/components/toChannel.vue

299 lines
7.9 KiB
Vue
Raw Normal View History

2025-12-05 21:36:34 +08:00
<!-- 文件上传导入组件 -->
<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>