|
|
|
|
|
|
|
|
align-center |
|
|
align-center |
|
|
class="steps-container" |
|
|
class="steps-container" |
|
|
> |
|
|
> |
|
|
<el-step title="选择配置" description="选择分类"></el-step> |
|
|
|
|
|
<el-step title="上传文件" description="上传Excel数据文件"></el-step> |
|
|
|
|
|
|
|
|
<el-step title="选择配置" description="选择分类" /> |
|
|
|
|
|
<el-step title="上传文件" description="上传Excel数据文件" /> |
|
|
</el-steps> |
|
|
</el-steps> |
|
|
|
|
|
|
|
|
<!-- 第一步:选择分类 --> |
|
|
<!-- 第一步:选择分类 --> |
|
|
|
|
|
|
|
|
:closable="false" |
|
|
:closable="false" |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
<div class="category-list" v-loading="categoriesLoading"> |
|
|
|
|
|
|
|
|
<div v-loading="categoriesLoading" class="category-list"> |
|
|
<div |
|
|
<div |
|
|
v-for="category in categoriesList" |
|
|
v-for="category in categoriesList" |
|
|
:key="category.id" |
|
|
:key="category.id" |
|
|
|
|
|
|
|
|
> |
|
|
> |
|
|
<div class="category-name">{{ category.name }}</div> |
|
|
<div class="category-name">{{ category.name }}</div> |
|
|
<div class="category-desc"> |
|
|
<div class="category-desc"> |
|
|
{{ category.description || "暂无描述" }} |
|
|
|
|
|
|
|
|
{{ category.description || '暂无描述' }} |
|
|
</div> |
|
|
</div> |
|
|
<div class="category-fields" v-if="category.fieldConfig"> |
|
|
|
|
|
|
|
|
<div v-if="category.fieldConfig" class="category-fields"> |
|
|
<el-tag |
|
|
<el-tag |
|
|
v-for="field in parseFieldConfig(category.fieldConfig)" |
|
|
v-for="field in parseFieldConfig(category.fieldConfig)" |
|
|
:key="field.fieldName" |
|
|
:key="field.fieldName" |
|
|
|
|
|
|
|
|
type="warning" |
|
|
type="warning" |
|
|
show-icon |
|
|
show-icon |
|
|
:closable="false" |
|
|
:closable="false" |
|
|
> |
|
|
|
|
|
</el-alert> |
|
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<el-upload |
|
|
<el-upload |
|
|
ref="upload" |
|
|
ref="upload" |
|
|
|
|
|
|
|
|
drag |
|
|
drag |
|
|
class="upload-area" |
|
|
class="upload-area" |
|
|
> |
|
|
> |
|
|
<i class="el-icon-upload"></i> |
|
|
|
|
|
|
|
|
<i class="el-icon-upload" /> |
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> |
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> |
|
|
<div class="el-upload__tip" slot="tip"> |
|
|
|
|
|
|
|
|
<div slot="tip" class="el-upload__tip"> |
|
|
支持多选Excel文件(.xlsx/.xls),单文件不超过10MB,文件数量不超过40个 |
|
|
支持多选Excel文件(.xlsx/.xls),单文件不超过10MB,文件数量不超过40个 |
|
|
<el-button @click="clearAllFiles" type="text" |
|
|
|
|
|
>清空文件列表</el-button |
|
|
|
|
|
> |
|
|
|
|
|
|
|
|
<el-button |
|
|
|
|
|
type="text" |
|
|
|
|
|
@click="clearAllFiles" |
|
|
|
|
|
>清空文件列表</el-button> |
|
|
</div> |
|
|
</div> |
|
|
</el-upload> |
|
|
</el-upload> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-button @click="prevStep">上一步</el-button> |
|
|
<el-button @click="prevStep">上一步</el-button> |
|
|
<el-button |
|
|
<el-button |
|
|
type="primary" |
|
|
type="primary" |
|
|
@click="saveData" |
|
|
|
|
|
:loading="saveLoading" |
|
|
:loading="saveLoading" |
|
|
:disabled="!fileList.length" |
|
|
:disabled="!fileList.length" |
|
|
|
|
|
@click="saveData" |
|
|
> |
|
|
> |
|
|
保存数据 |
|
|
保存数据 |
|
|
</el-button> |
|
|
</el-button> |
|
|
|
|
|
|
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script> |
|
|
<script> |
|
|
import { getCategoriesSimple, saveImportedData } from "@/api/sales-analysis"; |
|
|
|
|
|
import * as XLSX from "xlsx"; |
|
|
|
|
|
|
|
|
import { getCategoriesSimple, saveImportedData } from '@/api/sales-analysis' |
|
|
|
|
|
import * as XLSX from 'xlsx' |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: "ImportData", |
|
|
|
|
|
|
|
|
name: 'ImportData', |
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
currentStep: 0, |
|
|
currentStep: 0, |
|
|
|
|
|
|
|
|
fileList: [], |
|
|
fileList: [], |
|
|
processingFiles: false, |
|
|
processingFiles: false, |
|
|
errorData: [], |
|
|
errorData: [], |
|
|
errorDataVisible: false, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
errorDataVisible: false |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
computed: { |
|
|
computed: { |
|
|
uploadUrl() { |
|
|
uploadUrl() { |
|
|
if (this.selectedDataSource === "fba") { |
|
|
|
|
|
return process.env.VUE_APP_BASE_API + "/analysis-data/import/fba"; |
|
|
|
|
|
|
|
|
if (this.selectedDataSource === 'fba') { |
|
|
|
|
|
return process.env.VUE_APP_BASE_API + '/analysis-data/import/fba' |
|
|
} |
|
|
} |
|
|
return process.env.VUE_APP_BASE_API + "/analysis-data/import"; |
|
|
|
|
|
|
|
|
return process.env.VUE_APP_BASE_API + '/analysis-data/import' |
|
|
}, |
|
|
}, |
|
|
uploadData() { |
|
|
uploadData() { |
|
|
if (this.selectedDataSource === "fba") { |
|
|
|
|
|
|
|
|
if (this.selectedDataSource === 'fba') { |
|
|
return { |
|
|
return { |
|
|
shopName: this.selectedShopName, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
shopName: this.selectedShopName |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
return { |
|
|
return { |
|
|
categoryId: this.selectedCategoryId, |
|
|
|
|
|
}; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
categoryId: this.selectedCategoryId |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
mounted() { |
|
|
mounted() { |
|
|
this.fetchCategoriesList(); |
|
|
|
|
|
|
|
|
this.fetchCategoriesList() |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
// 解析字段配置字符串为 JSON 对象 |
|
|
// 解析字段配置字符串为 JSON 对象 |
|
|
parseFieldConfig(fieldConfig) { |
|
|
parseFieldConfig(fieldConfig) { |
|
|
try { |
|
|
try { |
|
|
if (!fieldConfig || typeof fieldConfig === "object") { |
|
|
|
|
|
return fieldConfig || []; |
|
|
|
|
|
|
|
|
if (!fieldConfig || typeof fieldConfig === 'object') { |
|
|
|
|
|
return fieldConfig || [] |
|
|
} |
|
|
} |
|
|
return JSON.parse(fieldConfig); |
|
|
|
|
|
|
|
|
return JSON.parse(fieldConfig) |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.warn("解析字段配置失败:", error); |
|
|
|
|
|
return []; |
|
|
|
|
|
|
|
|
console.warn('解析字段配置失败:', error) |
|
|
|
|
|
return [] |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
showErrorData() { |
|
|
showErrorData() { |
|
|
this.errorDataVisible = true; |
|
|
|
|
|
this.errorData = this.processResult.errorDetails; |
|
|
|
|
|
|
|
|
this.errorDataVisible = true |
|
|
|
|
|
this.errorData = this.processResult.errorDetails |
|
|
}, |
|
|
}, |
|
|
// 选择数据来源 |
|
|
// 选择数据来源 |
|
|
selectDataSource(source) { |
|
|
selectDataSource(source) { |
|
|
this.selectedDataSource = source; |
|
|
|
|
|
|
|
|
this.selectedDataSource = source |
|
|
// 清空之前的选择 |
|
|
// 清空之前的选择 |
|
|
this.selectedCategoryId = null; |
|
|
|
|
|
this.selectedCategory = null; |
|
|
|
|
|
this.selectedShopName = null; |
|
|
|
|
|
|
|
|
this.selectedCategoryId = null |
|
|
|
|
|
this.selectedCategory = null |
|
|
|
|
|
this.selectedShopName = null |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 获取分类列表 |
|
|
// 获取分类列表 |
|
|
async fetchCategoriesList() { |
|
|
async fetchCategoriesList() { |
|
|
try { |
|
|
try { |
|
|
this.categoriesLoading = true; |
|
|
|
|
|
const response = await getCategoriesSimple(); |
|
|
|
|
|
|
|
|
this.categoriesLoading = true |
|
|
|
|
|
const response = await getCategoriesSimple() |
|
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
if (response.code === 200) { |
|
|
this.categoriesList = response.data || []; |
|
|
|
|
|
|
|
|
this.categoriesList = response.data || [] |
|
|
} else { |
|
|
} else { |
|
|
this.$message.error(response.message || "获取分类列表失败"); |
|
|
|
|
|
|
|
|
this.$message.error(response.message || '获取分类列表失败') |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error("获取分类列表失败:", error); |
|
|
|
|
|
this.$message.error("获取分类列表失败"); |
|
|
|
|
|
|
|
|
console.error('获取分类列表失败:', error) |
|
|
|
|
|
this.$message.error('获取分类列表失败') |
|
|
} finally { |
|
|
} finally { |
|
|
this.categoriesLoading = false; |
|
|
|
|
|
|
|
|
this.categoriesLoading = false |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 选择分类 |
|
|
// 选择分类 |
|
|
selectCategory(category) { |
|
|
selectCategory(category) { |
|
|
this.selectedCategoryId = category.id; |
|
|
|
|
|
this.selectedCategory = category; |
|
|
|
|
|
|
|
|
this.selectedCategoryId = category.id |
|
|
|
|
|
this.selectedCategory = category |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 下一步 |
|
|
// 下一步 |
|
|
nextStep() { |
|
|
nextStep() { |
|
|
this.currentStep++; |
|
|
|
|
|
|
|
|
this.currentStep++ |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 上一步 |
|
|
// 上一步 |
|
|
prevStep() { |
|
|
prevStep() { |
|
|
this.currentStep--; |
|
|
|
|
|
|
|
|
this.currentStep-- |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 上传前验证 |
|
|
// 上传前验证 |
|
|
beforeUpload(file) { |
|
|
beforeUpload(file) { |
|
|
console.log("上传前验证:", { |
|
|
|
|
|
|
|
|
console.log('上传前验证:', { |
|
|
name: file.name, |
|
|
name: file.name, |
|
|
type: file.type, |
|
|
type: file.type, |
|
|
size: file.size, |
|
|
size: file.size, |
|
|
lastModified: file.lastModified, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
lastModified: file.lastModified |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
const isExcel = /\.(xlsx|xls)$/.test(file.name.toLowerCase()); |
|
|
|
|
|
|
|
|
const isExcel = /\.(xlsx|xls)$/.test(file.name.toLowerCase()) |
|
|
if (!isExcel) { |
|
|
if (!isExcel) { |
|
|
console.log("文件类型验证失败:", file.name); |
|
|
|
|
|
this.$message.error("只能上传Excel文件!"); |
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
console.log('文件类型验证失败:', file.name) |
|
|
|
|
|
this.$message.error('只能上传Excel文件!') |
|
|
|
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const isLt10M = file.size / 1024 / 1024 < 10; |
|
|
|
|
|
|
|
|
const isLt10M = file.size / 1024 / 1024 < 10 |
|
|
if (!isLt10M) { |
|
|
if (!isLt10M) { |
|
|
console.log("文件大小验证失败:", file.size / 1024 / 1024 + "MB"); |
|
|
|
|
|
this.$message.error("上传文件大小不能超过10MB!"); |
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
|
console.log('文件大小验证失败:', file.size / 1024 / 1024 + 'MB') |
|
|
|
|
|
this.$message.error('上传文件大小不能超过10MB!') |
|
|
|
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
console.log("前端验证通过,准备上传"); |
|
|
|
|
|
|
|
|
console.log('前端验证通过,准备上传') |
|
|
|
|
|
|
|
|
// 对于多文件上传,我们先不让它自动上传,而是添加到文件列表 |
|
|
// 对于多文件上传,我们先不让它自动上传,而是添加到文件列表 |
|
|
this.addToFileList(file); |
|
|
|
|
|
return false; // 阻止自动上传 |
|
|
|
|
|
|
|
|
this.addToFileList(file) |
|
|
|
|
|
return false // 阻止自动上传 |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 添加文件到列表 |
|
|
// 添加文件到列表 |
|
|
|
|
|
|
|
|
const fileItem = { |
|
|
const fileItem = { |
|
|
name: file.name, |
|
|
name: file.name, |
|
|
size: file.size, |
|
|
size: file.size, |
|
|
status: "ready", |
|
|
|
|
|
|
|
|
status: 'ready', |
|
|
file: file, |
|
|
file: file, |
|
|
uid: Date.now() + Math.random(), |
|
|
|
|
|
}; |
|
|
|
|
|
this.fileList.push(fileItem); |
|
|
|
|
|
|
|
|
uid: Date.now() + Math.random() |
|
|
|
|
|
} |
|
|
|
|
|
this.fileList.push(fileItem) |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 移除文件 |
|
|
// 移除文件 |
|
|
onFileRemove(file) { |
|
|
onFileRemove(file) { |
|
|
const index = this.fileList.findIndex((item) => item.uid === file.uid); |
|
|
|
|
|
|
|
|
const index = this.fileList.findIndex((item) => item.uid === file.uid) |
|
|
if (index > -1) { |
|
|
if (index > -1) { |
|
|
this.fileList.splice(index, 1); |
|
|
|
|
|
|
|
|
this.fileList.splice(index, 1) |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 清空文件列表 |
|
|
// 清空文件列表 |
|
|
clearAllFiles() { |
|
|
clearAllFiles() { |
|
|
this.fileList = []; |
|
|
|
|
|
|
|
|
this.fileList = [] |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 上传成功 |
|
|
// 上传成功 |
|
|
onUploadSuccess(response) { |
|
|
onUploadSuccess(response) { |
|
|
console.log("上传成功回调:", response); |
|
|
|
|
|
|
|
|
console.log('上传成功回调:', response) |
|
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
if (response.code === 200) { |
|
|
this.processResult = response.data || {}; |
|
|
|
|
|
this.$message.success(response.message || "文件处理完成"); |
|
|
|
|
|
this.currentStep = 3; |
|
|
|
|
|
|
|
|
this.processResult = response.data || {} |
|
|
|
|
|
this.$message.success(response.message || '文件处理完成') |
|
|
|
|
|
this.currentStep = 3 |
|
|
} else { |
|
|
} else { |
|
|
console.error("处理失败:", response); |
|
|
|
|
|
this.$message.error(response.message || "文件处理失败"); |
|
|
|
|
|
|
|
|
console.error('处理失败:', response) |
|
|
|
|
|
this.$message.error(response.message || '文件处理失败') |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 上传失败 |
|
|
// 上传失败 |
|
|
onUploadError(error) { |
|
|
onUploadError(error) { |
|
|
console.error("上传失败:", error); |
|
|
|
|
|
let errorMessage = "文件上传失败"; |
|
|
|
|
|
|
|
|
console.error('上传失败:', error) |
|
|
|
|
|
let errorMessage = '文件上传失败' |
|
|
|
|
|
|
|
|
// 尝试从错误中提取详细信息 |
|
|
// 尝试从错误中提取详细信息 |
|
|
if (error && error.message) { |
|
|
if (error && error.message) { |
|
|
errorMessage = error.message; |
|
|
|
|
|
} else if (typeof error === "string") { |
|
|
|
|
|
errorMessage = error; |
|
|
|
|
|
|
|
|
errorMessage = error.message |
|
|
|
|
|
} else if (typeof error === 'string') { |
|
|
|
|
|
errorMessage = error |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.$message.error(errorMessage); |
|
|
|
|
|
|
|
|
this.$message.error(errorMessage) |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 保存数据 |
|
|
// 保存数据 |
|
|
async saveData() { |
|
|
async saveData() { |
|
|
console.log(this.selectedCategory); |
|
|
|
|
|
|
|
|
console.log(this.selectedCategory) |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
this.saveLoading = true; |
|
|
|
|
|
|
|
|
this.saveLoading = true |
|
|
|
|
|
|
|
|
// 存储所有文件的解析数据 |
|
|
// 存储所有文件的解析数据 |
|
|
const allParsedData = []; |
|
|
|
|
|
|
|
|
const allParsedData = [] |
|
|
|
|
|
|
|
|
// 遍历文件列表 |
|
|
// 遍历文件列表 |
|
|
for (const fileItem of this.fileList) { |
|
|
for (const fileItem of this.fileList) { |
|
|
try { |
|
|
try { |
|
|
// 读取文件内容 |
|
|
// 读取文件内容 |
|
|
const reader = new FileReader(); |
|
|
|
|
|
|
|
|
const reader = new FileReader() |
|
|
const fileData = await new Promise((resolve, reject) => { |
|
|
const fileData = await new Promise((resolve, reject) => { |
|
|
reader.onload = (e) => resolve(e.target.result); |
|
|
|
|
|
reader.onerror = (e) => reject(e); |
|
|
|
|
|
reader.readAsArrayBuffer(fileItem.file); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
reader.onload = (e) => resolve(e.target.result) |
|
|
|
|
|
reader.onerror = (e) => reject(e) |
|
|
|
|
|
reader.readAsArrayBuffer(fileItem.file) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
// 解析Excel文件 |
|
|
// 解析Excel文件 |
|
|
const workbook = XLSX.read(fileData, { type: "array" }); |
|
|
|
|
|
const firstSheetName = workbook.SheetNames[0]; |
|
|
|
|
|
const worksheet = workbook.Sheets[firstSheetName]; |
|
|
|
|
|
|
|
|
const workbook = XLSX.read(fileData, { type: 'array' }) |
|
|
|
|
|
const firstSheetName = workbook.SheetNames[0] |
|
|
|
|
|
const worksheet = workbook.Sheets[firstSheetName] |
|
|
|
|
|
|
|
|
// 将工作表转换为JSON数据 |
|
|
// 将工作表转换为JSON数据 |
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { |
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { |
|
|
header: 1, |
|
|
header: 1, |
|
|
raw: false, // 将所有值转换为字符串 |
|
|
raw: false, // 将所有值转换为字符串 |
|
|
defval: "", // 空单元格的默认值 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
defval: '' // 空单元格的默认值 |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
// 验证数据格式 |
|
|
// 验证数据格式 |
|
|
if (jsonData.length < 2) { |
|
|
if (jsonData.length < 2) { |
|
|
throw new Error(`文件 ${fileItem.name} 数据行数不足`); |
|
|
|
|
|
|
|
|
throw new Error(`文件 ${fileItem.name} 数据行数不足`) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 获取表头 |
|
|
// 获取表头 |
|
|
const headers = jsonData[0]; |
|
|
|
|
|
|
|
|
const headers = jsonData[0] |
|
|
|
|
|
|
|
|
// 验证字段配置 |
|
|
// 验证字段配置 |
|
|
const fieldConfig = this.parseFieldConfig( |
|
|
const fieldConfig = this.parseFieldConfig( |
|
|
this.selectedCategory.fieldConfig |
|
|
this.selectedCategory.fieldConfig |
|
|
); |
|
|
|
|
|
|
|
|
) |
|
|
const requiredFields = fieldConfig |
|
|
const requiredFields = fieldConfig |
|
|
.filter((field) => field.required) |
|
|
.filter((field) => field.required) |
|
|
.map((field) => field.fieldName); |
|
|
|
|
|
|
|
|
.map((field) => field.fieldName) |
|
|
|
|
|
|
|
|
// 检查必填字段是否存在 |
|
|
// 检查必填字段是否存在 |
|
|
const missingFields = requiredFields.filter( |
|
|
const missingFields = requiredFields.filter( |
|
|
(field) => !headers.includes(field) |
|
|
(field) => !headers.includes(field) |
|
|
); |
|
|
|
|
|
|
|
|
) |
|
|
if (missingFields.length > 0) { |
|
|
if (missingFields.length > 0) { |
|
|
throw new Error( |
|
|
throw new Error( |
|
|
`文件 ${fileItem.name} 缺少必填字段: ${missingFields.join( |
|
|
`文件 ${fileItem.name} 缺少必填字段: ${missingFields.join( |
|
|
", " |
|
|
|
|
|
|
|
|
', ' |
|
|
)}` |
|
|
)}` |
|
|
); |
|
|
|
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 处理数据行 |
|
|
// 处理数据行 |
|
|
const parsedRows = []; |
|
|
|
|
|
|
|
|
const parsedRows = [] |
|
|
for (let i = 1; i < jsonData.length; i++) { |
|
|
for (let i = 1; i < jsonData.length; i++) { |
|
|
const row = jsonData[i]; |
|
|
|
|
|
if (row.length === 0 || row.every((cell) => !cell)) continue; // 跳过空行 |
|
|
|
|
|
|
|
|
const row = jsonData[i] |
|
|
|
|
|
if (row.length === 0 || row.every((cell) => !cell)) continue // 跳过空行 |
|
|
|
|
|
|
|
|
// 创建符合目标结构的数据对象 |
|
|
// 创建符合目标结构的数据对象 |
|
|
const rowData = { |
|
|
const rowData = { |
|
|
id: null, |
|
|
id: null, |
|
|
date: "", // 默认当前日期,可以从Excel中获取 |
|
|
|
|
|
shopName: "", |
|
|
|
|
|
productCode: "", |
|
|
|
|
|
productName: "", |
|
|
|
|
|
customerName: "", |
|
|
|
|
|
|
|
|
date: '', // 默认当前日期,可以从Excel中获取 |
|
|
|
|
|
shopName: '', |
|
|
|
|
|
productCode: '', |
|
|
|
|
|
productName: '', |
|
|
|
|
|
customerName: '', |
|
|
category: this.selectedCategory.name, |
|
|
category: this.selectedCategory.name, |
|
|
categorySpecs: "", |
|
|
|
|
|
|
|
|
categorySpecs: '', |
|
|
quantity: 0, |
|
|
quantity: 0, |
|
|
totalAmount: "", |
|
|
|
|
|
source: "", |
|
|
|
|
|
|
|
|
totalAmount: '', |
|
|
|
|
|
source: '', |
|
|
status: 1, |
|
|
status: 1, |
|
|
deliveryType: "", |
|
|
|
|
|
destination: "", |
|
|
|
|
|
remarks: "", |
|
|
|
|
|
orderNumber: "", |
|
|
|
|
|
|
|
|
deliveryType: '', |
|
|
|
|
|
destination: '', |
|
|
|
|
|
remarks: '', |
|
|
|
|
|
orderNumber: '', |
|
|
rowNumber: i + 1, |
|
|
rowNumber: i + 1, |
|
|
brand: "", |
|
|
|
|
|
|
|
|
brand: '', |
|
|
createdAt: null, |
|
|
createdAt: null, |
|
|
updatedAt: null, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
updatedAt: null |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
const specs = {}; |
|
|
|
|
|
|
|
|
const specs = {} |
|
|
|
|
|
|
|
|
// 根据表头映射数据 |
|
|
// 根据表头映射数据 |
|
|
headers.forEach((header, index) => { |
|
|
headers.forEach((header, index) => { |
|
|
const value = row[index] || ""; |
|
|
|
|
|
switch (header.toLowerCase()) { |
|
|
|
|
|
case "日付": |
|
|
|
|
|
|
|
|
const value = row[index] || '' |
|
|
|
|
|
const headerTrim = header.trim().replace(/[\r\n]+/g, '') |
|
|
|
|
|
console.log(headerTrim == '販売数量') |
|
|
|
|
|
switch (headerTrim) { |
|
|
|
|
|
case '出库日期': |
|
|
|
|
|
case '日付': |
|
|
// 转换日期格式 从 2025/7/18 到 2025-07-18 |
|
|
// 转换日期格式 从 2025/7/18 到 2025-07-18 |
|
|
if (value) { |
|
|
if (value) { |
|
|
const parts = value.split("/"); |
|
|
|
|
|
|
|
|
const parts = value.split('/') |
|
|
if (parts.length === 3) { |
|
|
if (parts.length === 3) { |
|
|
const year = parts[0]; |
|
|
|
|
|
const month = parts[1].padStart(2, "0"); |
|
|
|
|
|
const day = parts[2].padStart(2, "0"); |
|
|
|
|
|
rowData.date = `${year}-${month}-${day}`; |
|
|
|
|
|
|
|
|
const year = parts[0] |
|
|
|
|
|
const month = parts[1].padStart(2, '0') |
|
|
|
|
|
const day = parts[2].padStart(2, '0') |
|
|
|
|
|
rowData.date = `${year}-${month}-${day}` |
|
|
} else { |
|
|
} else { |
|
|
rowData.date = value; |
|
|
|
|
|
|
|
|
rowData.date = value |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case "販売店舗": |
|
|
|
|
|
rowData.shopName = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "商品コード": |
|
|
|
|
|
rowData.productCode = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "商品名": |
|
|
|
|
|
rowData.productName = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "客户名": |
|
|
|
|
|
rowData.customerName = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "分类": |
|
|
|
|
|
rowData.category = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "販売\n数量": |
|
|
|
|
|
case "販売数量": |
|
|
|
|
|
rowData.quantity = parseInt(value) || 0; |
|
|
|
|
|
break; |
|
|
|
|
|
case "販売金額\n合計": |
|
|
|
|
|
case "販売金額合計": |
|
|
|
|
|
rowData.totalAmount = value.toString(); |
|
|
|
|
|
break; |
|
|
|
|
|
case "来源": |
|
|
|
|
|
if (value) rowData.source = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "出库类型": |
|
|
|
|
|
if (value) rowData.deliveryType = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "目的地": |
|
|
|
|
|
rowData.destination = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "备注": |
|
|
|
|
|
rowData.remarks = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "订单号": |
|
|
|
|
|
rowData.orderNumber = value; |
|
|
|
|
|
break; |
|
|
|
|
|
case "品牌": |
|
|
|
|
|
rowData.brand = value; |
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
break |
|
|
|
|
|
case '店铺': |
|
|
|
|
|
case '販売店舗': |
|
|
|
|
|
rowData.shopName = value |
|
|
|
|
|
break |
|
|
|
|
|
case '商品コード': |
|
|
|
|
|
rowData.productCode = value |
|
|
|
|
|
break |
|
|
|
|
|
case '商品名': |
|
|
|
|
|
rowData.productName = value |
|
|
|
|
|
break |
|
|
|
|
|
case '客户名': |
|
|
|
|
|
rowData.customerName = value |
|
|
|
|
|
break |
|
|
|
|
|
case '分类': |
|
|
|
|
|
rowData.category = value |
|
|
|
|
|
break |
|
|
|
|
|
case '販売\n数量': |
|
|
|
|
|
case '販売数量': |
|
|
|
|
|
rowData.quantity = parseInt(value) || 0 |
|
|
|
|
|
break |
|
|
|
|
|
case '販売金額\n合計': |
|
|
|
|
|
case '販売金額合計': |
|
|
|
|
|
rowData.totalAmount = value.toString() |
|
|
|
|
|
break |
|
|
|
|
|
case '来源': |
|
|
|
|
|
if (value) rowData.source = value |
|
|
|
|
|
break |
|
|
|
|
|
case '出库类型': |
|
|
|
|
|
if (value) rowData.deliveryType = value |
|
|
|
|
|
break |
|
|
|
|
|
case '目的地': |
|
|
|
|
|
rowData.destination = value |
|
|
|
|
|
break |
|
|
|
|
|
case '备注': |
|
|
|
|
|
rowData.remarks = value |
|
|
|
|
|
break |
|
|
|
|
|
case '订单号': |
|
|
|
|
|
rowData.orderNumber = value |
|
|
|
|
|
break |
|
|
|
|
|
case '品牌': |
|
|
|
|
|
rowData.brand = value |
|
|
|
|
|
break |
|
|
default: |
|
|
default: |
|
|
fieldConfig.forEach((field) => { |
|
|
fieldConfig.forEach((field) => { |
|
|
const headerTrim = header.trim().replace(/\n+/g, ""); |
|
|
|
|
|
|
|
|
const headerTrim = header.trim().replace(/[\r\n]+/g, '') |
|
|
if (headerTrim.includes(field.displayLabel)) { |
|
|
if (headerTrim.includes(field.displayLabel)) { |
|
|
specs[field.displayLabel] = value; |
|
|
|
|
|
|
|
|
specs[field.displayLabel] = value |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
rowData.categorySpecs = JSON.stringify(specs); |
|
|
|
|
|
break; |
|
|
|
|
|
|
|
|
}) |
|
|
|
|
|
rowData.categorySpecs = JSON.stringify(specs) |
|
|
|
|
|
break |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
parsedRows.push(rowData); |
|
|
|
|
|
|
|
|
parsedRows.push(rowData) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
allParsedData.push(...parsedRows); |
|
|
|
|
|
|
|
|
allParsedData.push(...parsedRows) |
|
|
|
|
|
|
|
|
// 更新文件状态 |
|
|
// 更新文件状态 |
|
|
fileItem.status = "success"; |
|
|
|
|
|
fileItem.parsedCount = parsedRows.length; |
|
|
|
|
|
|
|
|
fileItem.status = 'success' |
|
|
|
|
|
fileItem.parsedCount = parsedRows.length |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error(`解析文件 ${fileItem.name} 失败:`, error); |
|
|
|
|
|
fileItem.status = "error"; |
|
|
|
|
|
fileItem.error = error.message; |
|
|
|
|
|
|
|
|
console.error(`解析文件 ${fileItem.name} 失败:`, error) |
|
|
|
|
|
fileItem.status = 'error' |
|
|
|
|
|
fileItem.error = error.message |
|
|
this.$message.error( |
|
|
this.$message.error( |
|
|
`解析文件 ${fileItem.name} 失败: ${error.message}` |
|
|
`解析文件 ${fileItem.name} 失败: ${error.message}` |
|
|
); |
|
|
|
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (allParsedData.length === 0) { |
|
|
if (allParsedData.length === 0) { |
|
|
throw new Error("没有可用的数据可以保存"); |
|
|
|
|
|
|
|
|
throw new Error('没有可用的数据可以保存') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 调用API保存数据 |
|
|
// 调用API保存数据 |
|
|
const response = await saveImportedData(allParsedData); |
|
|
|
|
|
|
|
|
const response = await saveImportedData(allParsedData) |
|
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
if (response.code === 200) { |
|
|
this.saveResult = response.data; |
|
|
|
|
|
this.$message.success(response.message || "数据保存成功"); |
|
|
|
|
|
this.currentStep = 0; |
|
|
|
|
|
|
|
|
this.saveResult = response.data |
|
|
|
|
|
this.$message.success(response.message || '数据保存成功') |
|
|
|
|
|
this.currentStep = 0 |
|
|
// 清空文件列表 |
|
|
// 清空文件列表 |
|
|
this.fileList = []; |
|
|
|
|
|
|
|
|
this.fileList = [] |
|
|
} else { |
|
|
} else { |
|
|
throw new Error(response.message || "数据保存失败"); |
|
|
|
|
|
|
|
|
throw new Error(response.message || '数据保存失败') |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error("保存数据失败:", error); |
|
|
|
|
|
this.$message.error(error.message || "数据保存失败"); |
|
|
|
|
|
|
|
|
console.error('保存数据失败:', error) |
|
|
|
|
|
this.$message.error(error.message || '数据保存失败') |
|
|
} finally { |
|
|
} finally { |
|
|
this.saveLoading = false; |
|
|
|
|
|
|
|
|
this.saveLoading = false |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
}, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<style scoped> |
|
|
<style scoped> |