|
|
@@ -370,7 +370,7 @@ export default {
|
|
|
if (this.initialFormData) {
|
|
|
this.formData = this.cleanAndFormatFormData(this.initialFormData)
|
|
|
} else {
|
|
|
- // 使用 initFormData,确保新增模式默认填入“下个月”而不是当前月
|
|
|
+ // 使用 initFormData,确保新增模式默认填入"下个月"而不是当前月
|
|
|
this.initFormData()
|
|
|
}
|
|
|
|
|
|
@@ -542,6 +542,68 @@ export default {
|
|
|
*/
|
|
|
methods: {
|
|
|
/**
|
|
|
+ * 创建物料匹配器函数
|
|
|
+ * @description 提取通用的物料匹配逻辑,避免代码重复
|
|
|
+ * @param {Object} material - 要匹配的物料对象
|
|
|
+ * @returns {Function} 匹配器函数
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ createMaterialMatcher(/** @type {Object} */ material) {
|
|
|
+ const id = material?.id
|
|
|
+ const goodsId = material?.goodsId
|
|
|
+ const code = material?.code
|
|
|
+
|
|
|
+ return (/** @type {Object} */ target) => {
|
|
|
+ if (id != null && target?.id != null && String(id) === String(target.id)) return true
|
|
|
+ if (goodsId != null && target?.goodsId != null && String(goodsId) === String(target.goodsId)) return true
|
|
|
+ if (code && target?.code && String(code) === String(target.code)) return true
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 优化的物料过滤算法
|
|
|
+ * @description 使用Map提高查找性能,避免循环嵌套
|
|
|
+ * @param {Array} stockTableData - 表格中的物料数据
|
|
|
+ * @param {Array} stockDescList - 可选的物料数据源
|
|
|
+ * @returns {Array} 过滤后的可选物料列表
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ optimizeStockSelectOptions(/** @type {Array} */ stockTableData, /** @type {Array} */ stockDescList) {
|
|
|
+ try {
|
|
|
+ const table = Array.isArray(stockTableData) ? stockTableData : []
|
|
|
+ const source = Array.isArray(stockDescList) ? stockDescList : []
|
|
|
+
|
|
|
+ // 使用Map构建已存在物料的快速查找索引
|
|
|
+ const materialMap = new Map()
|
|
|
+ table.forEach(item => {
|
|
|
+ if (item?.id != null) {
|
|
|
+ materialMap.set(`id:${String(item.id)}`, true)
|
|
|
+ }
|
|
|
+ if (item?.goodsId != null) {
|
|
|
+ materialMap.set(`goods:${String(item.goodsId)}`, true)
|
|
|
+ }
|
|
|
+ if (item?.code) {
|
|
|
+ materialMap.set(`code:${String(item.code)}`, true)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 过滤掉已在表格中的物料
|
|
|
+ return source.filter(item => {
|
|
|
+ if (item?.id != null && materialMap.has(`id:${String(item.id)}`)) return false
|
|
|
+ if (item?.goodsId != null && materialMap.has(`goods:${String(item.goodsId)}`)) return false
|
|
|
+ if (item?.code && materialMap.has(`code:${String(item.code)}`)) return false
|
|
|
+ return true
|
|
|
+ }).map(item => ({
|
|
|
+ label: item?.cname || item?.code || '',
|
|
|
+ value: String(item?.id || '')
|
|
|
+ }))
|
|
|
+ } catch (e) {
|
|
|
+ console.warn('优化物料过滤算法失败:', e)
|
|
|
+ return []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ /**
|
|
|
* 创建初始表单数据
|
|
|
* @description 创建销售预测表单的初始数据结构
|
|
|
* @returns {ForecastFormModel} 初始化的表单数据对象
|
|
|
@@ -1023,7 +1085,6 @@ export default {
|
|
|
// 先结束 Avue 内置的按钮loading,避免未调用 done 导致一直loading
|
|
|
if (typeof done === 'function') done()
|
|
|
|
|
|
- console.log(this.formData)
|
|
|
// 采用旧实现风格:通过 this.$refs.forecastForm.validate 回调进行校验
|
|
|
if (this.$refs && this.$refs.forecastForm && typeof this.$refs.forecastForm.validate === 'function') {
|
|
|
this.$refs.forecastForm.validate((valid) => {
|
|
|
@@ -1037,9 +1098,7 @@ export default {
|
|
|
console.groupEnd && console.groupEnd()
|
|
|
}
|
|
|
}
|
|
|
- // 校验失败时,如存在 loading 回调(部分版本提供),尝试恢复按钮状态
|
|
|
if (typeof loading === 'function') loading()
|
|
|
- // 通知父组件校验失败,便于父侧重置保存按钮loading
|
|
|
this.$emit && this.$emit(FORECAST_FORM_EVENTS.SUBMIT_ERROR, { message: '表单校验未通过' })
|
|
|
return
|
|
|
}
|
|
|
@@ -1233,15 +1292,18 @@ export default {
|
|
|
this.selectedStockId = null
|
|
|
// 重置选择状态
|
|
|
this.selectedRowKeys = []
|
|
|
+
|
|
|
const res = await getUserLinkGoods()
|
|
|
const payload = res && res.data && res.data.data ? res.data.data : null
|
|
|
const brandList = (payload && payload.pjpfBrandDescList) || []
|
|
|
const stockList = (payload && payload.pjpfStockDescList) || []
|
|
|
+
|
|
|
this.brandDescList = brandList
|
|
|
// 存储库存列表供选择用,不直接展示到表格
|
|
|
this.stockDescList = stockList
|
|
|
// 默认显示全部物料至下方表格,预测数量默认 1,用户可手动删除不需要的物料
|
|
|
this.stockTableData = stockList.map(item => ({ ...item, forecastQuantity: 1 }))
|
|
|
+
|
|
|
// 根据表格中已有的物料,过滤下拉选项
|
|
|
this.updateStockSelectOptions()
|
|
|
// 规范化分页并回显选择(新增模式首次加载)
|
|
|
@@ -1256,7 +1318,7 @@ export default {
|
|
|
|
|
|
/**
|
|
|
* 导入所选物料到下方表格
|
|
|
- * @description 仅在点击"导入物料"按钮后,将选择的物料行添加到表格,默认预测数量为 0
|
|
|
+ * @description 仅在点击"导入物料"按钮后,将选择的物料行添加到表格,默认预测数量为 1
|
|
|
* @returns {void}
|
|
|
* @this {ForecastFormMixinComponent & Vue}
|
|
|
*/
|
|
|
@@ -1273,24 +1335,10 @@ export default {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 防止重复导入 - 使用多个字段进行更全面的重复检查
|
|
|
- const exists = this.stockTableData.some(row => {
|
|
|
- // 优先使用 id 进行匹配
|
|
|
- if (row.id !== undefined && row.id !== null && stock.id !== undefined && stock.id !== null && String(row.id) === String(stock.id)) {
|
|
|
- return true
|
|
|
- }
|
|
|
- // 使用 goodsId 进行匹配
|
|
|
- if (row.goodsId !== undefined && row.goodsId !== null && stock.goodsId !== undefined && stock.goodsId !== null && String(row.goodsId) === String(stock.goodsId)) {
|
|
|
- return true
|
|
|
- }
|
|
|
- // 使用 code 进行匹配
|
|
|
- if (row.code && stock.code && String(row.code) === String(stock.code)) {
|
|
|
- return true
|
|
|
- }
|
|
|
- return false
|
|
|
- })
|
|
|
+ // 使用通用匹配器检查是否重复导入
|
|
|
+ const isDuplicate = this.stockTableData.some(this.createMaterialMatcher(stock))
|
|
|
|
|
|
- if (exists) {
|
|
|
+ if (isDuplicate) {
|
|
|
this.$message.warning('该物料已在列表中')
|
|
|
this.selectedStockId = null
|
|
|
return
|
|
|
@@ -1425,30 +1473,15 @@ export default {
|
|
|
|
|
|
/**
|
|
|
* 更新物料下拉选项(过滤已在表格中的物料)
|
|
|
+ * @description 使用优化的过滤算法,提高性能和可维护性
|
|
|
* @returns {void}
|
|
|
*/
|
|
|
updateStockSelectOptions() {
|
|
|
try {
|
|
|
- const table = Array.isArray(this.stockTableData) ? this.stockTableData : []
|
|
|
- const source = Array.isArray(this.stockDescList) ? this.stockDescList : []
|
|
|
-
|
|
|
- const idSet = new Set(table.filter(r => r && r.id !== undefined && r.id !== null).map(r => String(r.id)))
|
|
|
- const goodsIdSet = new Set(table.filter(r => r && r.goodsId !== undefined && r.goodsId !== null).map(r => String(r.goodsId)))
|
|
|
- const codeSet = new Set(table.filter(r => r && r.code).map(r => String(r.code)))
|
|
|
-
|
|
|
- const options = source
|
|
|
- .filter(item => {
|
|
|
- const byId = item && item.id !== undefined && item.id !== null && idSet.has(String(item.id))
|
|
|
- const byGoods = item && item.goodsId !== undefined && item.goodsId !== null && goodsIdSet.has(String(item.goodsId))
|
|
|
- const byCode = item && item.code && codeSet.has(String(item.code))
|
|
|
- return !(byId || byGoods || byCode)
|
|
|
- })
|
|
|
- .map(item => ({
|
|
|
- label: /** @type {any} */ (item.cname || item.code || ''),
|
|
|
- value: /** @type {any} */ (String(item.id))
|
|
|
- }))
|
|
|
-
|
|
|
+ // 使用优化的过滤算法
|
|
|
+ const options = this.optimizeStockSelectOptions(this.stockTableData, this.stockDescList)
|
|
|
this.stockSelectOptions = options
|
|
|
+
|
|
|
// 如果当前选中不在可选项中,则清空
|
|
|
const hasSelected = options.some(opt => opt && opt.value === this.selectedStockId)
|
|
|
if (!hasSelected) {
|
|
|
@@ -1599,19 +1632,23 @@ export default {
|
|
|
*/
|
|
|
async mergeEchoStoreInventory() {
|
|
|
try {
|
|
|
- if (!Array.isArray(this.stockTableData) || this.stockTableData.length === 0) return
|
|
|
+ if (!Array.isArray(this.stockTableData) || this.stockTableData.length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
const res = await getUserLinkGoods()
|
|
|
const payload = res && res.data && res.data.data ? res.data.data : null
|
|
|
const stockList = (payload && payload.pjpfStockDescList) || []
|
|
|
- if (!Array.isArray(stockList) || stockList.length === 0) return
|
|
|
+ if (!Array.isArray(stockList) || stockList.length === 0) {
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
// 在编辑模式下,确保"导入物料"的选择器有数据可选
|
|
|
// 不修改现有表格数据,仅补齐选择来源
|
|
|
this.stockDescList = stockList
|
|
|
- this.stockSelectOptions = stockList.map(item => ({
|
|
|
- label: item.cname,
|
|
|
- value: item.id
|
|
|
- }))
|
|
|
+
|
|
|
+ // ✅ 修复:使用优化的过滤方法,正确过滤已在表格中的物料
|
|
|
+ this.updateStockSelectOptions()
|
|
|
|
|
|
// 构建基于 goodsId 与 code 的索引映射
|
|
|
/** @type {Map<string, string|undefined>} */
|