fetch(modify):修改bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m20s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m20s
This commit is contained in:
@@ -50,10 +50,10 @@
|
||||
<template #title>
|
||||
<div style="line-height: 1.8">
|
||||
<p><strong>导入说明:</strong></p>
|
||||
<p>1. 请先下载 CSV 模板文件,按照模板格式填写IoT卡信息</p>
|
||||
<p>2. 支持 CSV 格式(.csv),单次最多导入 1000 条</p>
|
||||
<p>3. CSV 文件编码:UTF-8(推荐)或 GBK</p>
|
||||
<p>4. 必填字段:iccid(ICCID)、msisdn(MSISDN/手机号)</p>
|
||||
<p>1. 请先下载 Excel 模板文件,按照模板格式填写IoT卡信息</p>
|
||||
<p>2. 仅支持 Excel 格式(.xlsx),单次最多导入 1000 条</p>
|
||||
<p>3. 列格式请设置为文本格式,避免长数字被转为科学计数法</p>
|
||||
<p>4. 必填字段:ICCID、MSISDN(手机号)</p>
|
||||
<p>5. 必须选择运营商</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -79,12 +79,12 @@
|
||||
:auto-upload="false"
|
||||
:on-change="handleFileChange"
|
||||
:limit="1"
|
||||
accept=".csv"
|
||||
accept=".xlsx"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将 CSV 文件拖到此处,或<em>点击选择</em></div>
|
||||
<div class="el-upload__text">将 Excel 文件拖到此处,或<em>点击选择</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">只能上传 CSV 文件,且不超过 10MB</div>
|
||||
<div class="el-upload__tip">只能上传 .xlsx 格式的 Excel 文件,且不超过 10MB</div>
|
||||
</template>
|
||||
</ElUpload>
|
||||
|
||||
@@ -133,13 +133,31 @@
|
||||
}}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<ElDivider content-position="left">跳过明细</ElDivider>
|
||||
<div
|
||||
v-if="currentDetail.skipped_items && currentDetail.skipped_items.length"
|
||||
style="max-height: 300px; overflow-y: auto; margin-bottom: 20px"
|
||||
>
|
||||
<ElTable :data="currentDetail.skipped_items" border size="small">
|
||||
<ElTableColumn label="行号" prop="line" width="80" />
|
||||
<ElTableColumn label="ICCID" prop="iccid" width="200" />
|
||||
<ElTableColumn label="MSISDN" prop="msisdn" width="150" />
|
||||
<ElTableColumn label="跳过原因" prop="reason" min-width="200">
|
||||
<template #default="{ row }">
|
||||
{{ row.reason || '未知原因' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
</div>
|
||||
<ElEmpty v-else description="无跳过记录" />
|
||||
|
||||
<ElDivider content-position="left">失败明细</ElDivider>
|
||||
<div
|
||||
v-if="currentDetail.failed_items && currentDetail.failed_items.length"
|
||||
style="max-height: 300px; overflow-y: auto"
|
||||
>
|
||||
<ElTable :data="currentDetail.failed_items" border size="small">
|
||||
<ElTableColumn label="行号" type="index" width="80" :index="(index) => index + 1" />
|
||||
<ElTableColumn label="行号" prop="line" width="80" />
|
||||
<ElTableColumn label="ICCID" prop="iccid" width="200" />
|
||||
<ElTableColumn label="MSISDN" prop="msisdn" width="150" />
|
||||
<ElTableColumn label="失败原因" prop="reason" min-width="200">
|
||||
@@ -153,6 +171,14 @@
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
|
||||
<ElButton
|
||||
v-if="currentDetail.skip_count > 0"
|
||||
type="warning"
|
||||
:icon="Download"
|
||||
@click="downloadSkippedData"
|
||||
>
|
||||
下载跳过数据
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-if="currentDetail.fail_count > 0"
|
||||
type="primary"
|
||||
@@ -513,6 +539,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 下载跳过数据(从详情对话框)
|
||||
const downloadSkippedData = () => {
|
||||
downloadSkippedDataFromDetail(currentDetail.value, currentDetail.value.task_no)
|
||||
}
|
||||
|
||||
// 下载跳过数据的通用方法
|
||||
const downloadSkippedDataFromDetail = (detail: any, taskNo: string) => {
|
||||
const skippedReasons =
|
||||
detail.skipped_items?.map((item: any) => ({
|
||||
line: item.line || '-',
|
||||
iccid: item.iccid || '-',
|
||||
msisdn: item.msisdn || '-',
|
||||
message: item.reason || '未知原因'
|
||||
})) || []
|
||||
|
||||
if (skippedReasons.length === 0) {
|
||||
ElMessage.warning('没有跳过数据可下载')
|
||||
return
|
||||
}
|
||||
|
||||
const headers = ['行号', 'ICCID', 'MSISDN', '跳过原因']
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...skippedReasons.map((item: any) =>
|
||||
[item.line, item.iccid, item.msisdn, `"${item.message}"`].join(',')
|
||||
)
|
||||
]
|
||||
const csvContent = csvRows.join('\n')
|
||||
|
||||
const BOM = '\uFEFF'
|
||||
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' })
|
||||
|
||||
const link = document.createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
link.setAttribute('href', url)
|
||||
link.setAttribute('download', `IoT卡导入跳过数据_${taskNo}.csv`)
|
||||
link.style.visibility = 'hidden'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('跳过数据下载成功')
|
||||
}
|
||||
|
||||
// 下载失败数据(从详情对话框)
|
||||
const downloadFailData = () => {
|
||||
downloadFailDataFromDetail(currentDetail.value, currentDetail.value.task_no)
|
||||
@@ -521,8 +592,8 @@
|
||||
// 下载失败数据的通用方法
|
||||
const downloadFailDataFromDetail = (detail: any, taskNo: string) => {
|
||||
const failReasons =
|
||||
detail.failed_items?.map((item: any, index: number) => ({
|
||||
row: index + 1,
|
||||
detail.failed_items?.map((item: any) => ({
|
||||
line: item.line || '-',
|
||||
iccid: item.iccid || '-',
|
||||
msisdn: item.msisdn || '-',
|
||||
message: item.reason || item.error || '未知错误'
|
||||
@@ -537,7 +608,7 @@
|
||||
const csvRows = [
|
||||
headers.join(','),
|
||||
...failReasons.map((item: any) =>
|
||||
[item.row, item.iccid, item.msisdn, `"${item.message}"`].join(',')
|
||||
[item.line, item.iccid, item.msisdn, `"${item.message}"`].join(',')
|
||||
)
|
||||
]
|
||||
const csvContent = csvRows.join('\n')
|
||||
@@ -559,29 +630,58 @@
|
||||
}
|
||||
|
||||
// 下载模板
|
||||
const downloadTemplate = () => {
|
||||
// 使用 \t 前缀防止 Excel 将长数字转换为科学计数法
|
||||
const csvContent = [
|
||||
'iccid,msisdn',
|
||||
'\t89860123456789012345,\t13800138000',
|
||||
'\t89860123456789012346,\t13800138001',
|
||||
'\t89860123456789012347,\t13800138002'
|
||||
].join('\n')
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
// 动态导入 xlsx 库
|
||||
const XLSX = await import('xlsx')
|
||||
|
||||
const BOM = '\uFEFF'
|
||||
const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' })
|
||||
// 创建示例数据
|
||||
const templateData = [
|
||||
{
|
||||
ICCID: '89860123456789012345',
|
||||
MSISDN: '13800138000'
|
||||
},
|
||||
{
|
||||
ICCID: '89860123456789012346',
|
||||
MSISDN: '13800138001'
|
||||
},
|
||||
{
|
||||
ICCID: '89860123456789012347',
|
||||
MSISDN: '13800138002'
|
||||
}
|
||||
]
|
||||
|
||||
const link = document.createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
link.setAttribute('href', url)
|
||||
link.setAttribute('download', 'IoT卡导入模板.csv')
|
||||
link.style.visibility = 'hidden'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
// 创建工作簿
|
||||
const wb = XLSX.utils.book_new()
|
||||
const ws = XLSX.utils.json_to_sheet(templateData)
|
||||
|
||||
ElMessage.success('IoT卡导入模板下载成功')
|
||||
// 设置列宽
|
||||
ws['!cols'] = [
|
||||
{ wch: 25 }, // ICCID
|
||||
{ wch: 15 } // MSISDN
|
||||
]
|
||||
|
||||
// 将所有单元格设置为文本格式,防止科学计数法
|
||||
const range = XLSX.utils.decode_range(ws['!ref'] || 'A1')
|
||||
for (let R = range.s.r; R <= range.e.r; ++R) {
|
||||
for (let C = range.s.c; C <= range.e.c; ++C) {
|
||||
const cellAddress = XLSX.utils.encode_cell({ r: R, c: C })
|
||||
if (!ws[cellAddress]) continue
|
||||
ws[cellAddress].t = 's' // 设置为字符串类型
|
||||
}
|
||||
}
|
||||
|
||||
// 添加工作表到工作簿
|
||||
XLSX.utils.book_append_sheet(wb, ws, 'IoT卡导入模板')
|
||||
|
||||
// 导出文件
|
||||
XLSX.writeFile(wb, 'IoT卡导入模板.xlsx')
|
||||
|
||||
ElMessage.success('IoT卡导入模板下载成功')
|
||||
} catch (error) {
|
||||
console.error('下载模板失败:', error)
|
||||
ElMessage.error('下载模板失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 文件选择变化
|
||||
@@ -594,8 +694,8 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (uploadFile.raw && !uploadFile.raw.name.endsWith('.csv')) {
|
||||
ElMessage.error('只能上传 CSV 文件')
|
||||
if (uploadFile.raw && !uploadFile.raw.name.endsWith('.xlsx')) {
|
||||
ElMessage.error('只能上传 .xlsx 格式的 Excel 文件')
|
||||
uploadRef.value?.clearFiles()
|
||||
fileList.value = []
|
||||
return
|
||||
@@ -625,7 +725,7 @@
|
||||
}
|
||||
|
||||
if (!fileList.value.length) {
|
||||
ElMessage.warning('请先选择CSV文件')
|
||||
ElMessage.warning('请先选择 Excel 文件')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -636,7 +736,7 @@
|
||||
ElMessage.info('正在准备上传...')
|
||||
const uploadUrlRes = await StorageService.getUploadUrl({
|
||||
file_name: file.name,
|
||||
content_type: 'text/csv',
|
||||
content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
purpose: 'iot_import'
|
||||
})
|
||||
|
||||
@@ -647,7 +747,7 @@
|
||||
const { upload_url, file_key } = uploadUrlRes.data
|
||||
|
||||
ElMessage.info('正在上传文件...')
|
||||
await StorageService.uploadFile(upload_url, file, 'text/csv')
|
||||
await StorageService.uploadFile(upload_url, file, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
|
||||
ElMessage.info('正在创建导入任务...')
|
||||
const importRes = await CardService.importIotCards({
|
||||
|
||||
Reference in New Issue
Block a user