diff --git a/src/views/modules/order/poOrderStatusOptions.js b/src/views/modules/order/poOrderStatusOptions.js
index 06b86a8..04e8281 100644
--- a/src/views/modules/order/poOrderStatusOptions.js
+++ b/src/views/modules/order/poOrderStatusOptions.js
@@ -9,6 +9,7 @@ export const PO_ORDER_STATUS_OPTIONS = [
{ label: '验货完成', value: '验货完成' },
{ label: '部分发货', value: '部分发货' },
{ label: '已发货', value: '已发货' },
+ { label: '完全发货', value: '完全发货' },
{ label: '已取消', value: '已取消' },
{ label: '已关闭', value: '已关闭' }
]
diff --git a/src/views/modules/sys/initialImport.vue b/src/views/modules/sys/initialImport.vue
index 4c55d3b..6d1c230 100644
--- a/src/views/modules/sys/initialImport.vue
+++ b/src/views/modules/sys/initialImport.vue
@@ -66,9 +66,18 @@
选择导入文件
+
+ 导出校验结果
+
{{ fileName || '未选择文件' }}
+ 存在校验失败行,请先导出并修正后再保存
已读取 {{ previewRows.length }} 行,表格显示前{{ previewDisplayLimit }}行
+ ,其中 {{ invalidPreviewCount }} 行校验失败
@@ -163,6 +172,7 @@ export default {
previewLoading: false,
saving: false,
templateDownloading: false,
+ exporting: false,
fileName: '',
importFile: null,
poCustomer: '',
@@ -208,10 +218,28 @@ export default {
return this.activeImportType === 'poOrder'
},
activeColumns () {
+ let columns
if (this.isPoOrderImport) {
- return this.poOrderColumns
- }
- return this.activeImportType === 'partSku' ? this.partSkuColumns : this.supplierPriceColumns
+ columns = this.poOrderColumns
+ } else if (this.activeImportType === 'partSku') {
+ columns = this.partSkuColumns
+ } else {
+ columns = this.supplierPriceColumns
+ }
+ // 三个导入预览都把错误信息放在最后一列,和导出文件保持一致。
+ const withoutError = (columns || []).filter(column => column.prop !== 'validationMessage')
+ return withoutError.concat([{
+ prop: 'validationMessage',
+ label: '错误信息',
+ width: 280,
+ align: 'left'
+ }])
+ },
+ invalidPreviewCount () {
+ return this.previewRows.filter(row => this.isPreviewRowInvalid(row)).length
+ },
+ hasInvalidPreviewRows () {
+ return this.invalidPreviewCount > 0
},
previewDisplayRows () {
return this.previewRows.slice(0, this.previewDisplayLimit)
@@ -226,16 +254,20 @@ export default {
if (this.activeImportType === 'poOrder') {
return '94%'
}
- return this.activeImportType === 'partSku' ? '680px' : '94%'
+ return this.activeImportType === 'partSku' ? '860px' : '94%'
},
saveButtonText () {
return '保存'
},
saveDisabled () {
+ // 任意导入类型只要存在黄标行,就不允许继续保存。
+ if (this.previewLoading || this.saving || !this.previewRows.length || this.hasInvalidPreviewRows) {
+ return true
+ }
if (this.isPoOrderImport) {
- return this.previewLoading || this.saving || !this.previewRows.length || !this.poCustomer
+ return !this.poCustomer
}
- return this.previewLoading || this.saving || !this.previewRows.length
+ return false
}
},
methods: {
@@ -333,6 +365,7 @@ export default {
: previewSupplierPrice(rawFile, site)
},
buildPoOrderColumns (columnMapping) {
+ // 预览表按模板字段展示;物料编码、供应商编码分别由 Item、Vendor Name 解析得到。
const columns = Object.keys(columnMapping).map(prop => ({
prop,
label: columnMapping[prop],
@@ -341,8 +374,7 @@ export default {
}))
columns.push(
{ prop: 'part_no', label: '物料编码', width: 140, align: 'left' },
- { prop: 'supplier_no', label: '供应商编码', width: 140, align: 'left' },
- { prop: 'validationMessage', label: '校验结果', width: 220, align: 'left' }
+ { prop: 'supplier_no', label: '供应商编码', width: 140, align: 'left' }
)
return columns
},
@@ -418,17 +450,17 @@ export default {
if (!this.ensureInitialImportAvailable()) {
return
}
- if (this.isPoOrderImport) {
- const invalidRows = this.previewRows.filter(row => !row || row.valid === false)
- if (invalidRows.length) {
- this.$message.warning(`有${invalidRows.length}行校验未通过,请修正Excel后重新预览`)
- return
- }
- this.performPoOrderImport()
+ if (!this.previewRows.length) {
+ this.$message.warning('请先选择导入文件并完成预览')
return
}
- if (!this.previewRows.length) {
- this.$message.warning('没有可保存的导入数据')
+ // 三个导入统一:有黄标行一律禁止保存,避免把失败数据写入系统。
+ if (this.hasInvalidPreviewRows) {
+ this.$message.warning(`有${this.invalidPreviewCount}行校验未通过,请修正Excel后重新预览。可先导出校验结果`)
+ return
+ }
+ if (this.isPoOrderImport) {
+ this.performPoOrderImport()
return
}
const site = this.$store.state.user.site
@@ -436,21 +468,6 @@ export default {
this.$message.error('当前登录用户没有Site')
return
}
- const invalidCount = this.previewRows.filter(row => row && row.valid === false).length
- if (invalidCount > 0) {
- this.$confirm(
- `存在${invalidCount}行淡黄色数据。如果继续保存,无效数据行将跳过,是否继续?`,
- '数据校验提示',
- {
- confirmButtonText: '继续保存',
- cancelButtonText: '取消',
- type: 'warning'
- }
- ).then(() => {
- this.performSave(site)
- }).catch(() => {})
- return
- }
this.performSave(site)
},
performSave (site) {
@@ -531,8 +548,10 @@ export default {
if (this.missingItems.length) {
messages.push(`以下Item不存在:${this.missingItems.join('、')}`)
}
+ // PO 导入按 Vendor Name 查供应商;价格期初仍按 ShortNPC 提示。
if ((this.activeImportType === 'supplierPrice' || this.isPoOrderImport) && this.missingSuppliers.length) {
- messages.push(`以下ShortNPC不存在:${this.missingSuppliers.join('、')}`)
+ const supplierLabel = this.isPoOrderImport ? 'Vendor Name' : 'ShortNPC'
+ messages.push(`以下${supplierLabel}不存在:${this.missingSuppliers.join('、')}`)
}
const content = this.$createElement(
'div',
@@ -545,7 +564,128 @@ export default {
}).catch(() => {})
},
previewRowClassName ({ row }) {
- return row && row.valid === false ? 'initial-import-warning-row' : ''
+ return this.isPreviewRowInvalid(row) ? 'initial-import-warning-row' : ''
+ },
+ /**
+ * 预览行是否校验失败。只认 valid === false,与页面黄标规则一致。
+ */
+ isPreviewRowInvalid (row) {
+ return !!(row && row.valid === false)
+ },
+ /**
+ * 导出当前预览数据及校验结果。
+ * 触发条件:已完成预览且存在数据行。
+ * 结果:下载 Excel,失败行整行标黄,最后一列为错误信息。
+ */
+ handleExportPreview () {
+ if (!this.previewRows.length) {
+ this.$message.warning('请先选择导入文件并完成预览')
+ return
+ }
+ this.exporting = true
+ try {
+ const columns = this.buildPreviewExportColumns()
+ const xml = this.buildPreviewExportXml(columns, this.previewRows)
+ const blob = new Blob(['\ufeff' + xml], { type: 'application/vnd.ms-excel;charset=utf-8' })
+ const url = window.URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = this.resolvePreviewExportFileName()
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ window.URL.revokeObjectURL(url)
+ this.$message.success('校验结果已导出')
+ } catch (error) {
+ this.$message.error((error && error.message) || '导出失败,请稍后重试')
+ } finally {
+ this.exporting = false
+ }
+ },
+ /**
+ * 导出列与预览表一致:PO 额外带 Excel 行号,最后一列固定为错误信息。
+ */
+ buildPreviewExportColumns () {
+ const columns = []
+ if (this.isPoOrderImport) {
+ columns.push({ prop: 'excelRowNo', label: 'Excel行号' })
+ }
+ return columns.concat(this.activeColumns || [])
+ },
+ resolvePreviewExportFileName () {
+ const now = new Date()
+ const month = String(now.getMonth() + 1).padStart(2, '0')
+ const day = String(now.getDate()).padStart(2, '0')
+ const hour = String(now.getHours()).padStart(2, '0')
+ const minute = String(now.getMinutes()).padStart(2, '0')
+ const second = String(now.getSeconds()).padStart(2, '0')
+ return `${this.dialogTitle}校验结果_${now.getFullYear()}${month}${day}${hour}${minute}${second}.xls`
+ },
+ /**
+ * 生成带行底色的 SpreadsheetML,Excel 打开后失败行仍为黄色。
+ * 入参 columns 为导出列;rows 为当前全部预览行(不只表格显示的前 500 行)。
+ */
+ buildPreviewExportXml (columns, rows) {
+ const headerCells = columns.map(column => {
+ const width = column.prop === 'validationMessage' ? 280 : 80
+ return ``
+ }).join('')
+ const headerRow = '' + columns.map(column => {
+ return `| ${this.escapeExcelXml(column.label)} | `
+ }).join('') + '
'
+ const dataRows = (rows || []).map(row => {
+ const styleAttr = this.isPreviewRowInvalid(row) ? ' ss:StyleID="Warn"' : ''
+ const cells = columns.map(column => {
+ const value = this.formatExportCellValue(row, column.prop)
+ return `${this.escapeExcelXml(value)} | `
+ }).join('')
+ return `${cells}
`
+ }).join('')
+ return `
+
+
+
+
+
+
+
+
+ ${headerCells}${headerRow}${dataRows}
+
+`
+ },
+ /**
+ * 导出单元格文本。空值输出空串,避免 Excel 出现 undefined。
+ */
+ formatExportCellValue (row, prop) {
+ if (!row || !prop) {
+ return ''
+ }
+ const value = row[prop]
+ if (value == null || value === '') {
+ return ''
+ }
+ return String(value)
+ },
+ /**
+ * SpreadsheetML 特殊字符转义,防止错误信息里的符号破坏 XML。
+ */
+ escapeExcelXml (value) {
+ return String(value == null ? '' : value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
},
handleDialogClose (done) {
if (!this.saving) {
@@ -564,6 +704,7 @@ export default {
this.previewLoading = false
this.saving = false
this.templateDownloading = false
+ this.exporting = false
this.previewRows = []
this.missingItems = []
this.missingSuppliers = []
@@ -626,6 +767,11 @@ export default {
white-space: nowrap;
}
+.invalid-hint {
+ color: #e6a23c;
+ font-size: 13px;
+}
+
.preview-count {
margin-left: auto;
color: #606266;