|
|
@ -32,7 +32,7 @@ |
|
|
:close-on-click-modal="false" |
|
|
:close-on-click-modal="false" |
|
|
:close-on-press-escape="!saving" |
|
|
:close-on-press-escape="!saving" |
|
|
:before-close="handleDialogClose" |
|
|
:before-close="handleDialogClose" |
|
|
@closed="resetDialog"> |
|
|
|
|
|
|
|
|
@closed="handleDialogClosed"> |
|
|
<div class="dialog-toolbar"> |
|
|
<div class="dialog-toolbar"> |
|
|
<com-po-order-customer-picker |
|
|
<com-po-order-customer-picker |
|
|
v-if="isPoOrderImport" |
|
|
v-if="isPoOrderImport" |
|
|
@ -90,6 +90,13 @@ |
|
|
empty-text="请选择Excel文件进行预览" |
|
|
empty-text="请选择Excel文件进行预览" |
|
|
:row-class-name="previewRowClassName" |
|
|
:row-class-name="previewRowClassName" |
|
|
class="initial-import-preview"> |
|
|
class="initial-import-preview"> |
|
|
|
|
|
<el-table-column |
|
|
|
|
|
prop="validationMessage" |
|
|
|
|
|
label="错误信息" |
|
|
|
|
|
min-width="280" |
|
|
|
|
|
fixed="left" |
|
|
|
|
|
align="left" |
|
|
|
|
|
show-overflow-tooltip /> |
|
|
<el-table-column |
|
|
<el-table-column |
|
|
v-if="isPoOrderImport" |
|
|
v-if="isPoOrderImport" |
|
|
prop="excelRowNo" |
|
|
prop="excelRowNo" |
|
|
@ -154,13 +161,13 @@ export default { |
|
|
{ |
|
|
{ |
|
|
type: 'supplierPrice', |
|
|
type: 'supplierPrice', |
|
|
name: '供应商价格期初导入', |
|
|
name: '供应商价格期初导入', |
|
|
description: '导入供应商物料的MOQ、阶梯数量和阶梯价格。' |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'partSku', |
|
|
|
|
|
name: '物料SKU导入', |
|
|
|
|
|
description: '按Item更新物料SKU,不存在的SKU会自动新增。' |
|
|
|
|
|
|
|
|
description: '导入供应商物料的QTY/PO、MOQ/COLOR、QTY/CARTON、阶梯数量和阶梯价格。' |
|
|
}, |
|
|
}, |
|
|
|
|
|
// { |
|
|
|
|
|
// type: 'partSku', |
|
|
|
|
|
// name: '物料SKU导入', |
|
|
|
|
|
// description: '按Item更新物料SKU,不存在的SKU会自动新增。' |
|
|
|
|
|
// }, |
|
|
{ |
|
|
{ |
|
|
type: 'poOrder', |
|
|
type: 'poOrder', |
|
|
name: 'PO订单导入', |
|
|
name: 'PO订单导入', |
|
|
@ -180,12 +187,16 @@ export default { |
|
|
previewDisplayLimit: 500, |
|
|
previewDisplayLimit: 500, |
|
|
poOrderColumns: [], |
|
|
poOrderColumns: [], |
|
|
saveProgressText: '', |
|
|
saveProgressText: '', |
|
|
|
|
|
// 保存成功后先关弹框,等关闭动画结束再弹出绿色提示,避免提示被弹框盖住或一起消失。 |
|
|
|
|
|
pendingSuccessMessage: '', |
|
|
missingItems: [], |
|
|
missingItems: [], |
|
|
missingSuppliers: [], |
|
|
missingSuppliers: [], |
|
|
supplierPriceColumns: [ |
|
|
supplierPriceColumns: [ |
|
|
{ prop: 'rawCode', label: 'RAW CODE', width: 120, align: 'center' }, |
|
|
{ prop: 'rawCode', label: 'RAW CODE', width: 120, align: 'center' }, |
|
|
{ prop: 'supplier', label: 'Short_NPC', width: 100, align: 'center' }, |
|
|
{ prop: 'supplier', label: 'Short_NPC', width: 100, align: 'center' }, |
|
|
{ prop: 'moqColor', label: 'MOQ/COLOR', width: 80, align: 'center' }, |
|
|
{ prop: 'moqColor', label: 'MOQ/COLOR', width: 80, align: 'center' }, |
|
|
|
|
|
{ prop: 'qtyPo', label: 'QTY/PO', width: 80, align: 'center' }, |
|
|
|
|
|
{ prop: 'qtyCarton', label: 'QTY/CARTON', width: 90, align: 'center' }, |
|
|
{ prop: 'startDate', label: 'START DATE', width: 100, align: 'center' }, |
|
|
{ prop: 'startDate', label: 'START DATE', width: 100, align: 'center' }, |
|
|
{ prop: 'tierQty1', label: 'Tier_QTY_1', width: 80, align: 'center' }, |
|
|
{ prop: 'tierQty1', label: 'Tier_QTY_1', width: 80, align: 'center' }, |
|
|
{ prop: 'tierQty2', label: 'Tier_QTY_2', width: 80, align: 'center' }, |
|
|
{ prop: 'tierQty2', label: 'Tier_QTY_2', width: 80, align: 'center' }, |
|
|
@ -226,14 +237,8 @@ export default { |
|
|
} else { |
|
|
} else { |
|
|
columns = this.supplierPriceColumns |
|
|
columns = this.supplierPriceColumns |
|
|
} |
|
|
} |
|
|
// 三个导入预览都把错误信息放在最后一列,和导出文件保持一致。 |
|
|
|
|
|
const withoutError = (columns || []).filter(column => column.prop !== 'validationMessage') |
|
|
|
|
|
return withoutError.concat([{ |
|
|
|
|
|
prop: 'validationMessage', |
|
|
|
|
|
label: '错误信息', |
|
|
|
|
|
width: 280, |
|
|
|
|
|
align: 'left' |
|
|
|
|
|
}]) |
|
|
|
|
|
|
|
|
// 预览业务列不含错误信息;弹框里错误信息单独固定在最左侧。 |
|
|
|
|
|
return (columns || []).filter(column => column.prop !== 'validationMessage') |
|
|
}, |
|
|
}, |
|
|
invalidPreviewCount () { |
|
|
invalidPreviewCount () { |
|
|
return this.previewRows.filter(row => this.isPreviewRowInvalid(row)).length |
|
|
return this.previewRows.filter(row => this.isPreviewRowInvalid(row)).length |
|
|
@ -365,7 +370,7 @@ export default { |
|
|
: previewSupplierPrice(rawFile, site) |
|
|
: previewSupplierPrice(rawFile, site) |
|
|
}, |
|
|
}, |
|
|
buildPoOrderColumns (columnMapping) { |
|
|
buildPoOrderColumns (columnMapping) { |
|
|
// 预览表按模板字段展示;物料编码、供应商编码分别由 Item、Vendor Name 解析得到。 |
|
|
|
|
|
|
|
|
// 预览表按模板字段展示;物料编码等于 Item,供应商编码仍由 Vendor Name 解析。 |
|
|
const columns = Object.keys(columnMapping).map(prop => ({ |
|
|
const columns = Object.keys(columnMapping).map(prop => ({ |
|
|
prop, |
|
|
prop, |
|
|
label: columnMapping[prop], |
|
|
label: columnMapping[prop], |
|
|
@ -478,19 +483,12 @@ export default { |
|
|
rows: this.previewRows |
|
|
rows: this.previewRows |
|
|
}).then(({ data }) => { |
|
|
}).then(({ data }) => { |
|
|
if (data && data.code === 0) { |
|
|
if (data && data.code === 0) { |
|
|
const savedCount = data.savedCount == null ? this.previewRows.length : data.savedCount |
|
|
|
|
|
const skippedCount = data.skippedCount || 0 |
|
|
|
|
|
const skippedText = skippedCount > 0 ? `,跳过${skippedCount}行` : '' |
|
|
|
|
|
this.$message.success(`保存完成,成功${savedCount}行${skippedText}`) |
|
|
|
|
|
this.dialogVisible = false |
|
|
|
|
|
|
|
|
this.closeImportDialogAfterSuccess() |
|
|
} else { |
|
|
} else { |
|
|
this.$message.error((data && data.msg) || '保存失败') |
|
|
|
|
|
|
|
|
this.showImportSaveError((data && data.msg) || '保存失败') |
|
|
} |
|
|
} |
|
|
}).catch(error => { |
|
|
}).catch(error => { |
|
|
const message = error && error.response && error.response.data && error.response.data.msg |
|
|
|
|
|
? error.response.data.msg |
|
|
|
|
|
: '保存失败,请稍后重试' |
|
|
|
|
|
this.$message.error(message) |
|
|
|
|
|
|
|
|
this.showImportSaveError(this.resolveImportSaveErrorMessage(error, '保存失败,请稍后重试')) |
|
|
}).finally(() => { |
|
|
}).finally(() => { |
|
|
this.saving = false |
|
|
this.saving = false |
|
|
}) |
|
|
}) |
|
|
@ -530,26 +528,63 @@ export default { |
|
|
this.previewRows.splice(0, rows.length) |
|
|
this.previewRows.splice(0, rows.length) |
|
|
this.saveProgressText = `正在分批保存:${savedCount}/${totalCount}` |
|
|
this.saveProgressText = `正在分批保存:${savedCount}/${totalCount}` |
|
|
} |
|
|
} |
|
|
this.$message.success(`保存完成,共成功${savedCount}行`) |
|
|
|
|
|
this.dialogVisible = false |
|
|
|
|
|
|
|
|
this.closeImportDialogAfterSuccess() |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
const responseMessage = error && error.response && error.response.data && error.response.data.msg |
|
|
|
|
|
const message = responseMessage || error.message || '保存失败,请稍后重试' |
|
|
|
|
|
this.$message.error(`已保存${savedCount}行,后续批次停止:${message}`) |
|
|
|
|
|
|
|
|
const message = this.resolveImportSaveErrorMessage(error, '保存失败,请稍后重试') |
|
|
|
|
|
this.showImportSaveError(`已保存${savedCount}行,后续批次停止:${message}`) |
|
|
} finally { |
|
|
} finally { |
|
|
this.saving = false |
|
|
this.saving = false |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 从保存接口失败结果中取出用户可读错误。 |
|
|
|
|
|
* HTTP 200 + code=500 时错误在 data.msg;网络/超时时走 axios error。 |
|
|
|
|
|
*/ |
|
|
|
|
|
resolveImportSaveErrorMessage (error, fallback) { |
|
|
|
|
|
if (!error) { |
|
|
|
|
|
return fallback |
|
|
|
|
|
} |
|
|
|
|
|
const responseData = error.response && error.response.data |
|
|
|
|
|
if (responseData && responseData.msg) { |
|
|
|
|
|
return responseData.msg |
|
|
|
|
|
} |
|
|
|
|
|
if (error.message && /timeout/i.test(error.message)) { |
|
|
|
|
|
return '保存超时,请缩小导入行数后重试' |
|
|
|
|
|
} |
|
|
|
|
|
return error.message || fallback |
|
|
|
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 导入保存失败必须弹确认框,点确定后才关闭,避免 $message 几秒后消失看不清。 |
|
|
|
|
|
*/ |
|
|
|
|
|
showImportSaveError (message) { |
|
|
|
|
|
const text = message || '保存失败' |
|
|
|
|
|
const content = this.$createElement( |
|
|
|
|
|
'div', |
|
|
|
|
|
{ style: { maxHeight: '360px', overflowY: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-all', lineHeight: '1.6', textAlign: 'left' } }, |
|
|
|
|
|
text |
|
|
|
|
|
) |
|
|
|
|
|
this.$alert(content, '导入保存失败', { |
|
|
|
|
|
confirmButtonText: '确定', |
|
|
|
|
|
type: 'error', |
|
|
|
|
|
customClass: 'initial-import-error-alert' |
|
|
|
|
|
}).catch(() => {}) |
|
|
|
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 预览后弹出缺失主数据提示。 |
|
|
|
|
|
* PO 订单导入不再校验 Item 是否存在,因此不展示 Item 缺失清单。 |
|
|
|
|
|
*/ |
|
|
showMissingDataAlert () { |
|
|
showMissingDataAlert () { |
|
|
if (!this.missingItems.length && !this.missingSuppliers.length) { |
|
|
|
|
|
|
|
|
const showMissingItems = this.missingItems.length && !this.isPoOrderImport |
|
|
|
|
|
const showMissingSuppliers = (this.activeImportType === 'supplierPrice' || this.isPoOrderImport) |
|
|
|
|
|
&& this.missingSuppliers.length |
|
|
|
|
|
if (!showMissingItems && !showMissingSuppliers) { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
const messages = [] |
|
|
const messages = [] |
|
|
if (this.missingItems.length) { |
|
|
|
|
|
|
|
|
if (showMissingItems) { |
|
|
messages.push(`以下Item不存在:${this.missingItems.join('、')}`) |
|
|
messages.push(`以下Item不存在:${this.missingItems.join('、')}`) |
|
|
} |
|
|
} |
|
|
// PO 导入按 Vendor Name 查供应商;价格期初仍按 ShortNPC 提示。 |
|
|
|
|
|
if ((this.activeImportType === 'supplierPrice' || this.isPoOrderImport) && this.missingSuppliers.length) { |
|
|
|
|
|
|
|
|
if (showMissingSuppliers) { |
|
|
const supplierLabel = this.isPoOrderImport ? 'Vendor Name' : 'ShortNPC' |
|
|
const supplierLabel = this.isPoOrderImport ? 'Vendor Name' : 'ShortNPC' |
|
|
messages.push(`以下${supplierLabel}不存在:${this.missingSuppliers.join('、')}`) |
|
|
messages.push(`以下${supplierLabel}不存在:${this.missingSuppliers.join('、')}`) |
|
|
} |
|
|
} |
|
|
@ -603,10 +638,15 @@ export default { |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
/** |
|
|
/** |
|
|
* 导出列与预览表一致:PO 额外带 Excel 行号,最后一列固定为错误信息。 |
|
|
|
|
|
|
|
|
* 导出列与弹框预览一致:错误信息放在最左侧,PO 额外带 Excel 行号。 |
|
|
*/ |
|
|
*/ |
|
|
buildPreviewExportColumns () { |
|
|
buildPreviewExportColumns () { |
|
|
const columns = [] |
|
|
|
|
|
|
|
|
const columns = [{ |
|
|
|
|
|
prop: 'validationMessage', |
|
|
|
|
|
label: '错误信息', |
|
|
|
|
|
width: 280, |
|
|
|
|
|
align: 'left' |
|
|
|
|
|
}] |
|
|
if (this.isPoOrderImport) { |
|
|
if (this.isPoOrderImport) { |
|
|
columns.push({ prop: 'excelRowNo', label: 'Excel行号' }) |
|
|
columns.push({ prop: 'excelRowNo', label: 'Excel行号' }) |
|
|
} |
|
|
} |
|
|
@ -687,6 +727,26 @@ export default { |
|
|
.replace(/>/g, '>') |
|
|
.replace(/>/g, '>') |
|
|
.replace(/"/g, '"') |
|
|
.replace(/"/g, '"') |
|
|
}, |
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 导入保存成功:先关弹框,提示文案等 closed 后再弹出。 |
|
|
|
|
|
*/ |
|
|
|
|
|
closeImportDialogAfterSuccess () { |
|
|
|
|
|
this.saving = false |
|
|
|
|
|
this.pendingSuccessMessage = '导入成功' |
|
|
|
|
|
this.dialogVisible = false |
|
|
|
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 弹框关闭动画结束后再清理状态。 |
|
|
|
|
|
* 若本次是保存成功关闭,再弹出绿色“导入成功”,避免和弹框一起消失。 |
|
|
|
|
|
*/ |
|
|
|
|
|
handleDialogClosed () { |
|
|
|
|
|
const successMessage = this.pendingSuccessMessage |
|
|
|
|
|
this.pendingSuccessMessage = '' |
|
|
|
|
|
this.resetDialog() |
|
|
|
|
|
if (successMessage) { |
|
|
|
|
|
this.$message.success(successMessage) |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
handleDialogClose (done) { |
|
|
handleDialogClose (done) { |
|
|
if (!this.saving) { |
|
|
if (!this.saving) { |
|
|
done() |
|
|
done() |
|
|
@ -825,3 +885,15 @@ export default { |
|
|
text-align: right; |
|
|
text-align: right; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</style> |
|
|
|
|
|
|
|
|
|
|
|
<style> |
|
|
|
|
|
/* MessageBox 挂到 body,不能写 scoped,否则超长 SQL 错误会撑破弹框。 */ |
|
|
|
|
|
.initial-import-error-alert .el-message-box__message { |
|
|
|
|
|
max-height: 360px; |
|
|
|
|
|
overflow-y: auto; |
|
|
|
|
|
white-space: pre-wrap; |
|
|
|
|
|
word-break: break-all; |
|
|
|
|
|
line-height: 1.6; |
|
|
|
|
|
text-align: left; |
|
|
|
|
|
} |
|
|
|
|
|
</style> |