|
|
@@ -77,18 +77,25 @@
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
+ <!--
|
|
|
+ 物料下拉:点开即给默认前 10 条,输入关键字后按关键字远程检索。
|
|
|
+ 父项 / 子项各用各的候选列表,且每次点开都重新取默认候选——
|
|
|
+ 不保留上一次的结果,避免让人误以为「只有这几条可选」。
|
|
|
+ -->
|
|
|
<el-col :span="24">
|
|
|
<el-form-item label="父项物料" prop="parentMaterialId">
|
|
|
<el-select
|
|
|
v-model="form.parentMaterialId"
|
|
|
filterable
|
|
|
remote
|
|
|
- :remote-method="remoteSearchMaterials"
|
|
|
- :loading="materialLoading"
|
|
|
- placeholder="搜索物料"
|
|
|
+ :remote-method="loadParentMaterials"
|
|
|
+ :loading="parentMaterialLoading"
|
|
|
+ placeholder="点击选择,或输入物料编码/名称搜索"
|
|
|
style="width: 100%"
|
|
|
+ @visible-change="onParentVisibleChange"
|
|
|
+ @change="onParentMaterialChange"
|
|
|
>
|
|
|
- <el-option v-for="m in materialOptions" :key="m.id" :label="`${m.code} ${m.name}`" :value="m.id" />
|
|
|
+ <el-option v-for="m in parentMaterialOptions" :key="m.id" :label="`${m.code} ${m.name}`" :value="m.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
@@ -98,12 +105,14 @@
|
|
|
v-model="form.componentMaterialId"
|
|
|
filterable
|
|
|
remote
|
|
|
- :remote-method="remoteSearchMaterials"
|
|
|
- :loading="materialLoading"
|
|
|
- placeholder="搜索物料"
|
|
|
+ :remote-method="loadComponentMaterials"
|
|
|
+ :loading="componentMaterialLoading"
|
|
|
+ placeholder="点击选择,或输入物料编码/名称搜索"
|
|
|
style="width: 100%"
|
|
|
+ @visible-change="onComponentVisibleChange"
|
|
|
+ @change="onComponentMaterialChange"
|
|
|
>
|
|
|
- <el-option v-for="m in materialOptions" :key="m.id" :label="`${m.code} ${m.name}`" :value="m.id" />
|
|
|
+ <el-option v-for="m in componentMaterialOptions" :key="m.id" :label="`${m.code} ${m.name}`" :value="m.id" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
@@ -195,8 +204,15 @@ const { companyOptions, factoryOptions, loadOrgs, factoriesForCompany } = useS0M
|
|
|
const queryFactories = computed(() => factoriesForCompany(query.companyRefId));
|
|
|
const formFactories = computed(() => factoriesForCompany(form.companyRefId));
|
|
|
|
|
|
-const materialOptions = ref<MaterialRow[]>([]);
|
|
|
-const materialLoading = ref(false);
|
|
|
+// 父项 / 子项各自独立的候选列表:共用一份会让「编辑时预置的两条」同时出现在两个下拉里,
|
|
|
+// 看上去像「系统记住了历史选择」,实际只是回显数据,容易误导使用者。
|
|
|
+const parentMaterialOptions = ref<MaterialRow[]>([]);
|
|
|
+const componentMaterialOptions = ref<MaterialRow[]>([]);
|
|
|
+const parentMaterialLoading = ref(false);
|
|
|
+const componentMaterialLoading = ref(false);
|
|
|
+// 当前已选物料(仅用于 label 回显,不作候选记忆)
|
|
|
+const parentSelected = ref<MaterialRow | null>(null);
|
|
|
+const componentSelected = ref<MaterialRow | null>(null);
|
|
|
|
|
|
const query = reactive({
|
|
|
keyword: '',
|
|
|
@@ -280,27 +296,92 @@ function removeOpRow(index: number) {
|
|
|
form.ops.splice(index, 1);
|
|
|
}
|
|
|
|
|
|
-async function remoteSearchMaterials(keyword: string) {
|
|
|
+/** 点开下拉(未输入关键字)时给出的候选条数 —— 只是「先看几条」,不是全量。 */
|
|
|
+const MATERIAL_DEFAULT_SIZE = 10;
|
|
|
+/** 输入关键字后的检索条数(沿用原值,不缩小搜索范围)。 */
|
|
|
+const MATERIAL_SEARCH_SIZE = 50;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 物料候选取数。
|
|
|
+ * 物料主数据存在未 scope 化(CompanyRefId/FactoryRefId=0)的历史数据,按公司+工厂硬过滤会搜不到,
|
|
|
+ * 故一律按关键字全局搜索(不传 org)。BOM 表单自身的公司/工厂归属不受影响。
|
|
|
+ */
|
|
|
+async function fetchMaterials(keyword: string): Promise<MaterialRow[]> {
|
|
|
+ const data = await searchMaterialsForMfg({
|
|
|
+ keyword: keyword || undefined,
|
|
|
+ page: 1,
|
|
|
+ pageSize: keyword ? MATERIAL_SEARCH_SIZE : MATERIAL_DEFAULT_SIZE,
|
|
|
+ });
|
|
|
+ return (data.list as any[]).map((m) => ({ id: m.id, code: m.itemNum, name: m.descr }));
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 已选项必须留在候选里,否则 el-select 回显不出 label(编辑态打开就会变成一个裸 id)。
|
|
|
+ * 这不是「记忆历史选择」——只保当前这一条,且不跨字段共享。
|
|
|
+ */
|
|
|
+function withCurrent(list: MaterialRow[], current: MaterialRow | null): MaterialRow[] {
|
|
|
+ if (!current) return list;
|
|
|
+ return list.some((m) => m.id === current.id) ? list : [current, ...list];
|
|
|
+}
|
|
|
+
|
|
|
+// 并发保护:点开下拉触发的「默认 10 条」与随后输入触发的「关键字检索」可能乱序返回,
|
|
|
+// 用序号确保只有最后一次请求的结果被采用,避免默认列表覆盖掉搜索结果。
|
|
|
+let parentSeq = 0;
|
|
|
+let componentSeq = 0;
|
|
|
+
|
|
|
+/** 父项物料候选:空关键字=默认前 N 条,有关键字=按关键字检索。 */
|
|
|
+async function loadParentMaterials(keyword: string) {
|
|
|
const kw = (keyword || '').trim();
|
|
|
- if (!kw) return;
|
|
|
- if (!form.companyRefId || !form.factoryRefId) return;
|
|
|
- materialLoading.value = true;
|
|
|
+ const seq = ++parentSeq;
|
|
|
+ parentMaterialLoading.value = true;
|
|
|
try {
|
|
|
- // 物料主数据存在未 scope 化(CompanyRefId/FactoryRefId=0)的历史数据,按公司+工厂硬过滤会搜不到;
|
|
|
- // 改为按关键字全局搜索。BOM 表单本身的公司/工厂仍为必填(见 rules),此处仅解除物料检索侧的 org 过滤。
|
|
|
- const data = await searchMaterialsForMfg({
|
|
|
- keyword: kw,
|
|
|
- page: 1,
|
|
|
- pageSize: 50,
|
|
|
- });
|
|
|
- materialOptions.value = (data.list as any[]).map((m) => ({ id: m.id, code: m.itemNum, name: m.descr }));
|
|
|
+ const list = await fetchMaterials(kw);
|
|
|
+ if (seq !== parentSeq) return;
|
|
|
+ parentMaterialOptions.value = withCurrent(list, parentSelected.value);
|
|
|
+ } finally {
|
|
|
+ if (seq === parentSeq) parentMaterialLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** 子项物料候选:与父项各用各的列表,互不串味。 */
|
|
|
+async function loadComponentMaterials(keyword: string) {
|
|
|
+ const kw = (keyword || '').trim();
|
|
|
+ const seq = ++componentSeq;
|
|
|
+ componentMaterialLoading.value = true;
|
|
|
+ try {
|
|
|
+ const list = await fetchMaterials(kw);
|
|
|
+ if (seq !== componentSeq) return;
|
|
|
+ componentMaterialOptions.value = withCurrent(list, componentSelected.value);
|
|
|
} finally {
|
|
|
- materialLoading.value = false;
|
|
|
+ if (seq === componentSeq) componentMaterialLoading.value = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/** 每次点开都按空关键字重新取默认候选,不沿用上一次的列表(即「不记忆」)。 */
|
|
|
+function onParentVisibleChange(visible: boolean) {
|
|
|
+ if (visible) void loadParentMaterials('');
|
|
|
+}
|
|
|
+function onComponentVisibleChange(visible: boolean) {
|
|
|
+ if (visible) void loadComponentMaterials('');
|
|
|
+}
|
|
|
+
|
|
|
+/** 记住当前选中项,仅用于 label 回显(见 withCurrent)。 */
|
|
|
+function onParentMaterialChange(id?: number) {
|
|
|
+ parentSelected.value = parentMaterialOptions.value.find((m) => m.id === id) ?? null;
|
|
|
+}
|
|
|
+function onComponentMaterialChange(id?: number) {
|
|
|
+ componentSelected.value = componentMaterialOptions.value.find((m) => m.id === id) ?? null;
|
|
|
+}
|
|
|
+
|
|
|
+function clearMaterialCandidates() {
|
|
|
+ parentMaterialOptions.value = [];
|
|
|
+ componentMaterialOptions.value = [];
|
|
|
+ parentSelected.value = null;
|
|
|
+ componentSelected.value = null;
|
|
|
+}
|
|
|
+
|
|
|
function onFormOrgChange() {
|
|
|
- materialOptions.value = [];
|
|
|
+ clearMaterialCandidates();
|
|
|
}
|
|
|
|
|
|
async function loadList() {
|
|
|
@@ -353,7 +434,7 @@ function resetForm() {
|
|
|
endEff: undefined,
|
|
|
isEnabled: true,
|
|
|
});
|
|
|
- materialOptions.value = [];
|
|
|
+ clearMaterialCandidates();
|
|
|
productItemOptions.value = [];
|
|
|
formRef.value?.clearValidate();
|
|
|
}
|
|
|
@@ -370,10 +451,11 @@ async function openEdit(row: S0MfgStandardBomLineRow) {
|
|
|
resetForm();
|
|
|
editingId.value = row.id;
|
|
|
dialogTitle.value = `编辑 ${row.parentItemNum} → ${row.componentItemNum}`;
|
|
|
- materialOptions.value = [
|
|
|
- { id: row.parentMaterialId, code: row.parentItemNum, name: row.parentDescr },
|
|
|
- { id: row.componentMaterialId, code: row.componentItemNum, name: row.componentDescr },
|
|
|
- ];
|
|
|
+ // 只预置各自那一条当前值用于 label 回显;点开下拉时会按空关键字重新取默认候选。
|
|
|
+ parentSelected.value = { id: row.parentMaterialId, code: row.parentItemNum, name: row.parentDescr };
|
|
|
+ componentSelected.value = { id: row.componentMaterialId, code: row.componentItemNum, name: row.componentDescr };
|
|
|
+ parentMaterialOptions.value = [parentSelected.value];
|
|
|
+ componentMaterialOptions.value = [componentSelected.value];
|
|
|
const detail = (await s0MfgStandardBomLinesApi.get(row.id)) as S0MfgStandardBomLineDetail;
|
|
|
const m = detail.master;
|
|
|
Object.assign(form, {
|