fetch(modify):修改bug
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 5m20s

This commit is contained in:
sexygoat
2026-01-31 16:33:21 +08:00
parent 16d53709ef
commit ecb79dae43
20 changed files with 1369 additions and 649 deletions

View File

@@ -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. 必填字段iccidICCIDmsisdnMSISDN/手机号</p>
<p>1. 请先下载 Excel 模板文件按照模板格式填写IoT卡信息</p>
<p>2. 支持 Excel 格式.xlsx单次最多导入 1000 </p>
<p>3. 列格式请设置为文本格式避免长数字被转为科学计数法</p>
<p>4. 必填字段ICCIDMSISDN手机号</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({