|
|
@@ -7,66 +7,20 @@ |
|
|
|
align-center |
|
|
|
class="steps-container" |
|
|
|
> |
|
|
|
<el-step title="选择来源" description="选择导入数据类型"></el-step> |
|
|
|
<el-step title="选择配置" description="选择分类或店铺"></el-step> |
|
|
|
<el-step title="选择配置" description="选择分类"></el-step> |
|
|
|
<el-step title="上传文件" description="上传Excel数据文件"></el-step> |
|
|
|
<el-step title="预览数据" description="查看处理结果"></el-step> |
|
|
|
<el-step title="保存入库" description="确认并保存数据"></el-step> |
|
|
|
</el-steps> |
|
|
|
|
|
|
|
<!-- 第一步:选择数据来源 --> |
|
|
|
<!-- 第一步:选择分类 --> |
|
|
|
<div v-if="currentStep === 0" class="step-wrapper"> |
|
|
|
<div class="step-header"> |
|
|
|
<h2>选择数据来源</h2> |
|
|
|
<p>不同的数据来源有不同的处理方式和字段要求。</p> |
|
|
|
</div> |
|
|
|
<div class="source-options"> |
|
|
|
<div |
|
|
|
class="source-option" |
|
|
|
:class="{ active: selectedDataSource === 'logistics' }" |
|
|
|
@click="selectDataSource('logistics')" |
|
|
|
> |
|
|
|
<i class="el-icon-truck source-icon"></i> |
|
|
|
<div class="source-info"> |
|
|
|
<h3>物流数据来源</h3> |
|
|
|
<p>导入物流出库数据</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
class="source-option" |
|
|
|
:class="{ active: selectedDataSource === 'fba' }" |
|
|
|
@click="selectDataSource('fba')" |
|
|
|
> |
|
|
|
<i class="el-icon-box source-icon"></i> |
|
|
|
<div class="source-info"> |
|
|
|
<h3>FBA数据来源</h3> |
|
|
|
<p>导入Amazon FBA出库数据</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="step-actions"> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
:disabled="!selectedDataSource" |
|
|
|
@click="nextStep" |
|
|
|
> |
|
|
|
下一步 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第二步:选择配置 --> |
|
|
|
<div v-if="currentStep === 1" class="step-wrapper"> |
|
|
|
<div class="step-header"> |
|
|
|
<h2 v-if="selectedDataSource === 'logistics'">选择分类</h2> |
|
|
|
<h2 v-if="selectedDataSource === 'fba'">选择店铺</h2> |
|
|
|
<h2>选择分类</h2> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 物流数据来源:选择分类 --> |
|
|
|
<div v-if="selectedDataSource === 'logistics'" class="category-selection"> |
|
|
|
<!-- 选择分类 --> |
|
|
|
<div class="category-selection"> |
|
|
|
<el-alert |
|
|
|
title="请选择数据分类" |
|
|
|
description="选择分类后,系统会根据分类的字段配置来匹配和处理导入的基准数据。" |
|
|
|
type="info" |
|
|
|
show-icon |
|
|
|
:closable="false" |
|
|
@@ -98,40 +52,10 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- FBA数据来源:选择店铺 --> |
|
|
|
<div v-if="selectedDataSource === 'fba'" class="shop-selection"> |
|
|
|
<el-alert |
|
|
|
title="请选择店铺" |
|
|
|
description="选择店铺后,系统会根据店铺客户关联关系来匹配客户信息。" |
|
|
|
type="info" |
|
|
|
show-icon |
|
|
|
:closable="false" |
|
|
|
/> |
|
|
|
|
|
|
|
<div class="shop-list" v-loading="shopsLoading"> |
|
|
|
<div |
|
|
|
v-for="shop in fbaShopsList" |
|
|
|
:key="shop.shopName" |
|
|
|
class="shop-item" |
|
|
|
:class="{ active: selectedShopName === shop.shopName }" |
|
|
|
@click="selectShop(shop.shopName)" |
|
|
|
> |
|
|
|
<div class="shop-name">{{ shop.shopName }}</div> |
|
|
|
<div class="shop-customer"> |
|
|
|
客户:{{ shop.customerName || "未关联" }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="step-actions"> |
|
|
|
<el-button @click="prevStep">上一步</el-button> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
:disabled=" |
|
|
|
(selectedDataSource === 'logistics' && !selectedCategoryId) || |
|
|
|
(selectedDataSource === 'fba' && !selectedShopName) |
|
|
|
" |
|
|
|
:disabled="!selectedCategoryId" |
|
|
|
@click="nextStep" |
|
|
|
> |
|
|
|
下一步 |
|
|
@@ -139,8 +63,8 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第三步:上传文件 --> |
|
|
|
<div v-if="currentStep === 2" class="step-wrapper"> |
|
|
|
<!-- 第二步:上传文件 --> |
|
|
|
<div v-if="currentStep === 1" class="step-wrapper"> |
|
|
|
<div class="step-header"> |
|
|
|
<h2>上传Excel文件</h2> |
|
|
|
</div> |
|
|
@@ -148,35 +72,11 @@ |
|
|
|
<div class="file-upload"> |
|
|
|
<!-- 物流数据文件格式要求 --> |
|
|
|
<el-alert |
|
|
|
v-if="selectedDataSource === 'logistics'" |
|
|
|
title="物流数据文件格式要求" |
|
|
|
:title="`当前选择分类是:${selectedCategory.name}`" |
|
|
|
type="warning" |
|
|
|
show-icon |
|
|
|
:closable="false" |
|
|
|
> |
|
|
|
<p style="color: #303030;">当前选择分类是:<u>{{ selectedCategory.name }}</u>,请确保Excel文件包含以下列:</p> |
|
|
|
<p style="color: #303030;"> |
|
|
|
<strong |
|
|
|
>出库日期、目的地、店铺名称、出库类型、发送方式、发送番号、注文番号、商品编号、商品名称、数量、单价、送料、代引、客户名称、备注</strong |
|
|
|
> |
|
|
|
</p> |
|
|
|
</el-alert> |
|
|
|
|
|
|
|
<!-- FBA数据文件格式要求 --> |
|
|
|
<el-alert |
|
|
|
v-if="selectedDataSource === 'fba'" |
|
|
|
title="FBA数据文件格式要求" |
|
|
|
type="warning" |
|
|
|
show-icon |
|
|
|
:closable="false" |
|
|
|
> |
|
|
|
<p style="color: #303030;">当前选择的店铺是:<u>{{ selectedShopName }}</u>,请确保Excel文件包含以下列:</p> |
|
|
|
<p style="color: #303030;"> |
|
|
|
<strong |
|
|
|
>出荷日、出品者SKU、FNSKU、ASIN、FC、数量、Amazon注文番号、通貨、商品金額(商品1点ごと)、配送料、ギフト包装手数料、配送先(市区町村)、都道府県名、配送先(郵便番号)、付与されたAmazon |
|
|
|
ポイント</strong |
|
|
|
> |
|
|
|
</p> |
|
|
|
</el-alert> |
|
|
|
|
|
|
|
<el-upload |
|
|
@@ -208,228 +108,21 @@ |
|
|
|
<el-button @click="prevStep">上一步</el-button> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
@click="processBatchFiles" |
|
|
|
:loading="processingFiles" |
|
|
|
@click="saveData" |
|
|
|
:loading="saveLoading" |
|
|
|
:disabled="!fileList.length" |
|
|
|
> |
|
|
|
下一步,处理所有文件 ({{ fileList.length }}) |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第四步:预览数据 --> |
|
|
|
<div v-if="currentStep === 3" class="step-wrapper"> |
|
|
|
<div class="step-header"> |
|
|
|
<h2>预览处理结果</h2> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="data-preview"> |
|
|
|
<!-- 处理结果统计 --> |
|
|
|
<div class="statistics"> |
|
|
|
<div class="stat-card"> |
|
|
|
<div class="stat-number">{{ processResult.total || 0 }}</div> |
|
|
|
<div class="stat-label">总计行数</div> |
|
|
|
</div> |
|
|
|
<div class="stat-card success"> |
|
|
|
<div class="stat-number">{{ processResult.processed || 0 }}</div> |
|
|
|
<div class="stat-label">可处理</div> |
|
|
|
</div> |
|
|
|
<div class="stat-card warning"> |
|
|
|
<div class="stat-number">{{ processResult.filtered || 0 }}</div> |
|
|
|
<div class="stat-label">已过滤</div> |
|
|
|
</div> |
|
|
|
<div class="stat-card danger"> |
|
|
|
<div class="stat-number">{{ processResult.errors || 0 }}</div> |
|
|
|
<div class="stat-label"> |
|
|
|
错误数据<small |
|
|
|
v-if="processResult.errors > 0" |
|
|
|
@click="showErrorData" |
|
|
|
>查看错误数据</small |
|
|
|
> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 数据预览表格 --> |
|
|
|
<div |
|
|
|
class="data-table" |
|
|
|
v-if="processResult.data && processResult.data.length > 0" |
|
|
|
> |
|
|
|
<h4>数据预览(前50条)</h4> |
|
|
|
<el-table |
|
|
|
:data="processResult.data.slice(0, 50)" |
|
|
|
stripe |
|
|
|
border |
|
|
|
size="mini" |
|
|
|
height="calc(100vh - 490px)" |
|
|
|
> |
|
|
|
<el-table-column |
|
|
|
prop="rowNumber" |
|
|
|
label="行号" |
|
|
|
width="55" |
|
|
|
align="center" |
|
|
|
/> |
|
|
|
<el-table-column |
|
|
|
prop="date" |
|
|
|
label="日期" |
|
|
|
width="100" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column prop="shopName" label="店铺"></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="productCode" |
|
|
|
label="商品编号" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="productName" |
|
|
|
label="商品名称" |
|
|
|
show-overflow-tooltip |
|
|
|
></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="customerName" |
|
|
|
label="客户" |
|
|
|
width="100" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="category" |
|
|
|
label="分类" |
|
|
|
width="100" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="quantity" |
|
|
|
label="数量" |
|
|
|
width="60" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="totalAmount" |
|
|
|
label="合计" |
|
|
|
width="100" |
|
|
|
></el-table-column> |
|
|
|
<el-table-column prop="status" label="状态" width="80"> |
|
|
|
<template slot-scope="scope"> |
|
|
|
<el-tag |
|
|
|
:type=" |
|
|
|
scope.row.status === 1 |
|
|
|
? 'success' |
|
|
|
: scope.row.status === 2 |
|
|
|
? 'warning' |
|
|
|
: 'danger' |
|
|
|
" |
|
|
|
size="mini" |
|
|
|
> |
|
|
|
{{ getStatusText(scope.row.status) }} |
|
|
|
</el-tag> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
<el-table-column |
|
|
|
prop="deliveryType" |
|
|
|
label="出库类型" |
|
|
|
width="120" |
|
|
|
></el-table-column> |
|
|
|
</el-table> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="step-actions"> |
|
|
|
<el-button @click="prevStep">上一步</el-button> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
:disabled="!processResult.data || processResult.data.length === 0" |
|
|
|
@click="nextStep" |
|
|
|
> |
|
|
|
确认保存 |
|
|
|
保存数据 |
|
|
|
</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 第五步:保存数据 --> |
|
|
|
<div v-if="currentStep === 4" class="step-wrapper"> |
|
|
|
<div class="step-header"> |
|
|
|
<h2>保存数据</h2> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="save-data"> |
|
|
|
<div v-if="saveLoading" class="saving"> |
|
|
|
<i class="el-icon-loading"></i> |
|
|
|
<p>正在保存数据,请稍候...</p> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else-if="saveResult" class="save-success"> |
|
|
|
<i |
|
|
|
class="el-icon-success" |
|
|
|
style="color: #67c23a; font-size: 48px" |
|
|
|
></i> |
|
|
|
<h3>数据保存成功!</h3> |
|
|
|
|
|
|
|
<div class="save-statistics"> |
|
|
|
<el-row :gutter="20"> |
|
|
|
<el-col :span="8"> |
|
|
|
<div class="stat-item"> |
|
|
|
<div class="stat-value">{{ saveResult.saved || 0 }}</div> |
|
|
|
<div class="stat-label">总计保存</div> |
|
|
|
</div> |
|
|
|
</el-col> |
|
|
|
<el-col :span="8"> |
|
|
|
<div class="stat-item success"> |
|
|
|
<div class="stat-value">{{ saveResult.inserted || 0 }}</div> |
|
|
|
<div class="stat-label">新增记录</div> |
|
|
|
</div> |
|
|
|
</el-col> |
|
|
|
<el-col :span="8"> |
|
|
|
<div class="stat-item warning"> |
|
|
|
<div class="stat-value">{{ saveResult.updated || 0 }}</div> |
|
|
|
<div class="stat-label">更新记录</div> |
|
|
|
</div> |
|
|
|
</el-col> |
|
|
|
</el-row> |
|
|
|
|
|
|
|
<div v-if="saveResult.failed > 0" class="failed-info"> |
|
|
|
<el-alert |
|
|
|
:title="`${saveResult.failed}条数据保存失败`" |
|
|
|
type="error" |
|
|
|
show-icon |
|
|
|
:closable="false" |
|
|
|
> |
|
|
|
<div v-if="saveResult.errors && saveResult.errors.length > 0"> |
|
|
|
<p>错误详情:</p> |
|
|
|
<ul> |
|
|
|
<li v-for="error in saveResult.errors" :key="error"> |
|
|
|
{{ error }} |
|
|
|
</li> |
|
|
|
</ul> |
|
|
|
</div> |
|
|
|
</el-alert> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="step-actions"> |
|
|
|
<el-button type="primary" @click="goToDataList" |
|
|
|
>查看数据列表</el-button |
|
|
|
> |
|
|
|
<el-button @click="startOver">重新导入</el-button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<el-dialog title="错误数据" :visible.sync="errorDataVisible" width="50%"> |
|
|
|
<ul class="error-list"> |
|
|
|
<li v-for="error in errorData" :key="error"> |
|
|
|
{{ error }} |
|
|
|
</li> |
|
|
|
</ul> |
|
|
|
</el-dialog> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import { |
|
|
|
getCategoriesSimple, |
|
|
|
getShopCustomerList, |
|
|
|
importAnalysisDataBatch, |
|
|
|
importFBADataBatch, |
|
|
|
saveImportedData, |
|
|
|
} from "@/api/sales-analysis"; |
|
|
|
import { getCategoriesSimple, saveImportedData } from "@/api/sales-analysis"; |
|
|
|
import * as XLSX from "xlsx"; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: "ImportData", |
|
|
@@ -470,15 +163,9 @@ export default { |
|
|
|
categoryId: this.selectedCategoryId, |
|
|
|
}; |
|
|
|
}, |
|
|
|
fbaShopsList() { |
|
|
|
return this.shopsList.filter((item) => { |
|
|
|
return item.shopName.includes("FBA"); |
|
|
|
}); |
|
|
|
}, |
|
|
|
}, |
|
|
|
mounted() { |
|
|
|
this.fetchCategoriesList(); |
|
|
|
this.fetchShopsList(); |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
// 解析字段配置字符串为 JSON 对象 |
|
|
@@ -526,33 +213,6 @@ export default { |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 获取店铺列表 |
|
|
|
async fetchShopsList() { |
|
|
|
try { |
|
|
|
this.shopsLoading = true; |
|
|
|
const response = await getShopCustomerList({ |
|
|
|
page: 1, |
|
|
|
pageSize: 1000, |
|
|
|
}); |
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
|
this.shopsList = response.data.list || []; |
|
|
|
} else { |
|
|
|
this.$message.error(response.message || "获取店铺列表失败"); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error("获取店铺列表失败:", error); |
|
|
|
this.$message.error("获取店铺列表失败"); |
|
|
|
} finally { |
|
|
|
this.shopsLoading = false; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 选择店铺 |
|
|
|
selectShop(shopName) { |
|
|
|
this.selectedShopName = shopName; |
|
|
|
}, |
|
|
|
|
|
|
|
// 选择分类 |
|
|
|
selectCategory(category) { |
|
|
|
this.selectedCategoryId = category.id; |
|
|
@@ -561,12 +221,7 @@ export default { |
|
|
|
|
|
|
|
// 下一步 |
|
|
|
nextStep() { |
|
|
|
if (this.currentStep === 3) { |
|
|
|
// 保存数据 |
|
|
|
this.saveData(); |
|
|
|
} else { |
|
|
|
this.currentStep++; |
|
|
|
} |
|
|
|
this.currentStep++; |
|
|
|
}, |
|
|
|
|
|
|
|
// 上一步 |
|
|
@@ -629,50 +284,6 @@ export default { |
|
|
|
this.fileList = []; |
|
|
|
}, |
|
|
|
|
|
|
|
// 批量处理文件 |
|
|
|
async processBatchFiles() { |
|
|
|
if (this.fileList.length === 0) { |
|
|
|
this.$message.warning("请先选择文件"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
this.processingFiles = true; |
|
|
|
|
|
|
|
const formData = new FormData(); |
|
|
|
|
|
|
|
// 根据数据来源添加不同的参数 |
|
|
|
if (this.selectedDataSource === "fba") { |
|
|
|
formData.append("shopName", this.selectedShopName); |
|
|
|
} else { |
|
|
|
formData.append("categoryId", this.selectedCategoryId); |
|
|
|
} |
|
|
|
|
|
|
|
this.fileList.forEach((fileItem, index) => { |
|
|
|
formData.append("files", fileItem.file); |
|
|
|
}); |
|
|
|
|
|
|
|
// 根据数据来源调用不同的API |
|
|
|
const response = |
|
|
|
this.selectedDataSource === "fba" |
|
|
|
? await importFBADataBatch(formData) |
|
|
|
: await importAnalysisDataBatch(formData); |
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
|
this.processResult = response.data || {}; |
|
|
|
this.$message.success(`成功处理${this.fileList.length}个文件`); |
|
|
|
this.currentStep = 3; |
|
|
|
} else { |
|
|
|
this.$message.error(response.message || "批量处理失败"); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error("批量处理失败:", error); |
|
|
|
this.$message.error("批量处理失败"); |
|
|
|
} finally { |
|
|
|
this.processingFiles = false; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 上传成功 |
|
|
|
onUploadSuccess(response) { |
|
|
|
console.log("上传成功回调:", response); |
|
|
@@ -704,51 +315,209 @@ export default { |
|
|
|
|
|
|
|
// 保存数据 |
|
|
|
async saveData() { |
|
|
|
console.log(this.selectedCategory); |
|
|
|
|
|
|
|
try { |
|
|
|
this.currentStep = 4; |
|
|
|
this.saveLoading = true; |
|
|
|
const response = await saveImportedData(this.processResult.data); |
|
|
|
|
|
|
|
// 存储所有文件的解析数据 |
|
|
|
const allParsedData = []; |
|
|
|
|
|
|
|
// 遍历文件列表 |
|
|
|
for (const fileItem of this.fileList) { |
|
|
|
try { |
|
|
|
// 读取文件内容 |
|
|
|
const reader = new FileReader(); |
|
|
|
const fileData = await new Promise((resolve, reject) => { |
|
|
|
reader.onload = (e) => resolve(e.target.result); |
|
|
|
reader.onerror = (e) => reject(e); |
|
|
|
reader.readAsArrayBuffer(fileItem.file); |
|
|
|
}); |
|
|
|
|
|
|
|
// 解析Excel文件 |
|
|
|
const workbook = XLSX.read(fileData, { type: "array" }); |
|
|
|
const firstSheetName = workbook.SheetNames[0]; |
|
|
|
const worksheet = workbook.Sheets[firstSheetName]; |
|
|
|
|
|
|
|
// 将工作表转换为JSON数据 |
|
|
|
const jsonData = XLSX.utils.sheet_to_json(worksheet, { |
|
|
|
header: 1, |
|
|
|
raw: false, // 将所有值转换为字符串 |
|
|
|
defval: "", // 空单元格的默认值 |
|
|
|
}); |
|
|
|
|
|
|
|
// 验证数据格式 |
|
|
|
if (jsonData.length < 2) { |
|
|
|
throw new Error(`文件 ${fileItem.name} 数据行数不足`); |
|
|
|
} |
|
|
|
|
|
|
|
// 获取表头 |
|
|
|
const headers = jsonData[0]; |
|
|
|
|
|
|
|
// 验证字段配置 |
|
|
|
const fieldConfig = this.parseFieldConfig( |
|
|
|
this.selectedCategory.fieldConfig |
|
|
|
); |
|
|
|
const requiredFields = fieldConfig |
|
|
|
.filter((field) => field.required) |
|
|
|
.map((field) => field.fieldName); |
|
|
|
|
|
|
|
// 检查必填字段是否存在 |
|
|
|
const missingFields = requiredFields.filter( |
|
|
|
(field) => !headers.includes(field) |
|
|
|
); |
|
|
|
if (missingFields.length > 0) { |
|
|
|
throw new Error( |
|
|
|
`文件 ${fileItem.name} 缺少必填字段: ${missingFields.join( |
|
|
|
", " |
|
|
|
)}` |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 处理数据行 |
|
|
|
const parsedRows = []; |
|
|
|
for (let i = 1; i < jsonData.length; i++) { |
|
|
|
const row = jsonData[i]; |
|
|
|
if (row.length === 0 || row.every((cell) => !cell)) continue; // 跳过空行 |
|
|
|
|
|
|
|
// 创建符合目标结构的数据对象 |
|
|
|
const rowData = { |
|
|
|
id: null, |
|
|
|
date: "", // 默认当前日期,可以从Excel中获取 |
|
|
|
shopName: "", |
|
|
|
productCode: "", |
|
|
|
productName: "", |
|
|
|
customerName: "", |
|
|
|
category: this.selectedCategory.name, |
|
|
|
categorySpecs: "", |
|
|
|
quantity: 0, |
|
|
|
totalAmount: "", |
|
|
|
source: "", |
|
|
|
status: 1, |
|
|
|
deliveryType: "", |
|
|
|
destination: "", |
|
|
|
remarks: "", |
|
|
|
orderNumber: "", |
|
|
|
rowNumber: i + 1, |
|
|
|
brand: "", |
|
|
|
createdAt: null, |
|
|
|
updatedAt: null, |
|
|
|
}; |
|
|
|
|
|
|
|
const specs = {}; |
|
|
|
|
|
|
|
// 根据表头映射数据 |
|
|
|
headers.forEach((header, index) => { |
|
|
|
const value = row[index] || ""; |
|
|
|
switch (header.toLowerCase()) { |
|
|
|
case "日付": |
|
|
|
// 转换日期格式 从 2025/7/18 到 2025-07-18 |
|
|
|
if (value) { |
|
|
|
const parts = value.split("/"); |
|
|
|
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}`; |
|
|
|
} else { |
|
|
|
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; |
|
|
|
default: |
|
|
|
fieldConfig.forEach((field) => { |
|
|
|
const headerTrim = header.trim().replace(/\n+/g, ""); |
|
|
|
if (headerTrim.includes(field.displayLabel)) { |
|
|
|
specs[field.displayLabel] = value; |
|
|
|
} |
|
|
|
}); |
|
|
|
rowData.categorySpecs = JSON.stringify(specs); |
|
|
|
break; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
parsedRows.push(rowData); |
|
|
|
} |
|
|
|
|
|
|
|
allParsedData.push(...parsedRows); |
|
|
|
|
|
|
|
// 更新文件状态 |
|
|
|
fileItem.status = "success"; |
|
|
|
fileItem.parsedCount = parsedRows.length; |
|
|
|
} catch (error) { |
|
|
|
console.error(`解析文件 ${fileItem.name} 失败:`, error); |
|
|
|
fileItem.status = "error"; |
|
|
|
fileItem.error = error.message; |
|
|
|
this.$message.error( |
|
|
|
`解析文件 ${fileItem.name} 失败: ${error.message}` |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (allParsedData.length === 0) { |
|
|
|
throw new Error("没有可用的数据可以保存"); |
|
|
|
} |
|
|
|
|
|
|
|
// 调用API保存数据 |
|
|
|
const response = await saveImportedData(allParsedData); |
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
|
this.saveResult = response.data; |
|
|
|
this.$message.success(response.message || "数据保存成功"); |
|
|
|
this.currentStep = 0; |
|
|
|
// 清空文件列表 |
|
|
|
this.fileList = []; |
|
|
|
} else { |
|
|
|
this.$message.error(response.message || "数据保存失败"); |
|
|
|
throw new Error(response.message || "数据保存失败"); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error("保存数据失败:", error); |
|
|
|
this.$message.error("数据保存失败"); |
|
|
|
this.$message.error(error.message || "数据保存失败"); |
|
|
|
} finally { |
|
|
|
this.saveLoading = false; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 获取状态文本 |
|
|
|
getStatusText(status) { |
|
|
|
const statusMap = { |
|
|
|
1: "正常", |
|
|
|
2: "客户未匹配", |
|
|
|
3: "规格未匹配", |
|
|
|
}; |
|
|
|
return statusMap[status] || "未知"; |
|
|
|
}, |
|
|
|
|
|
|
|
// 跳转到数据列表 |
|
|
|
goToDataList() { |
|
|
|
this.$router.push("/sales-analysis/analysis-data/list"); |
|
|
|
}, |
|
|
|
|
|
|
|
// 重新开始 |
|
|
|
startOver() { |
|
|
|
this.currentStep = 0; |
|
|
|
this.selectedDataSource = null; |
|
|
|
this.selectedCategoryId = null; |
|
|
|
this.selectedCategory = null; |
|
|
|
this.selectedShopName = null; |
|
|
|
this.processResult = {}; |
|
|
|
this.saveResult = null; |
|
|
|
this.fileList = []; |
|
|
|
}, |
|
|
|
}, |
|
|
|
}; |
|
|
|
</script> |