接口化

This commit is contained in:
JenniferW 2025-11-25 15:45:42 +08:00
parent ab07644f22
commit fc7fd4f2cf
1 changed files with 273 additions and 250 deletions

View File

@ -210,32 +210,32 @@
<div class="result-card-list">
<el-card
v-for="item in currentPageData"
:key="item.productNumber"
:key="item.partNumber"
class="result-card"
:class="{ active: item.selected }"
@click="toggleSelect(item)"
>
<div class="result-card-info">
<div class="result-card-name" :title="item.name">
{{ item.name }}
<div class="result-card-name" :title="item.partName">
{{ item.partName }}
</div>
<div
class="result-card-parameter"
:title="`品号:${item.productNumber}`"
:title="`品号:${item.partNumber}`"
>
<span>品号</span>{{ item.productNumber }}
<span>品号</span>{{ item.partNumber }}
</div>
<div
class="result-card-parameter"
:title="`品号-规格型号:${item.specificationModel}`"
:title="`品号-规格型号:${item.partNumberSpec}`"
>
<span>品号-规格型号 </span>{{ item.specificationModel }}
<span>品号-规格型号 </span>{{ item.partNumberSpec }}
</div>
<div
class="result-card-parameter text-truncate"
:title="`车型:${item.carModel}`"
:title="`车型:${item.trainModel}`"
>
<span>车型</span> {{ item.carModel }}
<span>车型</span> {{ item.trainModel }}
</div>
</div>
</el-card>
@ -309,9 +309,9 @@
/>
<el-table-column
v-for="item in sortedCompareList"
:key="item.productNumber"
:label="item.productNumber"
:prop="item.productNumber"
:key="item.partNumber"
:label="item.partNumber"
:prop="item.partNumber"
align="center"
header-align="center"
min-width="300"
@ -344,8 +344,8 @@
</span>
<span style="font-size: 12px; color: #666">{{
sortedCompareList.find(
(i) => i.productNumber === column.label
).name
(i) => i.partNumber === column.label
)?.partName || "-"
}}</span>
</div>
</div>
@ -407,14 +407,13 @@
<script setup>
import { ref, computed, watch, onMounted, nextTick } from "vue";
import {
paramsToCompare,
fieldMap,
allFields,
mockData,
fieldValueMap,
} from "@/data/step2MockData";
searchHint,
search as searchProducts,
differenceWords,
compare,
} from "@/api/order";
import { ElMessage, ElEmpty, ElDialog } from "element-plus";
import { Search, Edit, RefreshLeft, Microphone } from "@element-plus/icons-vue";
import { Search, RefreshLeft, Microphone } from "@element-plus/icons-vue";
// propsemit
const props = defineProps({
@ -448,6 +447,11 @@ const showSuggestions = ref(false);
const activeSuggestionIndex = ref(-1);
const possibleFields = ref([]);
const activePossibleFieldIndex = ref(-1);
const allFields = ref([]);
const fieldValueMap = ref({});
const allValues = ref([]);
const hintTimer = ref(null);
const pendingSearchTimer = ref(null);
//
const showVoicePopup = ref(false);
@ -466,8 +470,7 @@ const totalItems = ref(0);
//
const currentPageData = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
return filteredData.value.slice(startIndex, startIndex + pageSize.value);
return filteredData.value;
});
//
@ -478,7 +481,7 @@ const selectedCount = computed(() => {
//
const selectedProductNumber = computed(() => {
const selected = filteredData.value.find((item) => item.selected);
return selected ? selected.productNumber : "";
return selected ? selected.partNumber : "";
});
//
@ -506,8 +509,8 @@ const sortedCompareList = computed(() => {
//
list.sort((a, b) => {
//
const aMatch = a.productNumber.match(/-(\d{5})$/);
const bMatch = b.productNumber.match(/-(\d{5})$/);
const aMatch = a.partNumber?.match(/-(\d{5})$/);
const bMatch = b.partNumber?.match(/-(\d{5})$/);
const aNum = aMatch ? parseInt(aMatch[1], 10) : 0;
const bNum = bMatch ? parseInt(bMatch[1], 10) : 0;
@ -525,78 +528,21 @@ const sortedCompareList = computed(() => {
return list;
});
//
const compareTableData = computed(() => {
if (sortedCompareList.value.length === 0) {
parameterDifferences.value = {};
return [];
}
const compareTableData = ref([]);
//
const updateParameterDifferenceFlags = (rows) => {
const differences = {};
//
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
// yyyy-MM-dd
const formatDate = (date) => {
return date.toISOString().split("T")[0];
};
const formattedToday = formatDate(today);
const formattedYesterday = formatDate(yesterday);
//
const result = [
//
{ param: "品号-规格型号" },
//
{ param: "品号申请时间" },
{ param: "图号" },
{ param: "技术规范编号" },
{ param: "技术规范版本" },
{ param: "技术规范名称" },
//
{ param: "技术要求签订时间" },
{ param: "CBC编号" },
{ param: "CBC版本" },
{ param: "车型" },
{ param: "型号" },
{ param: "材质" },
{ param: "采购属性" },
{ param: "车轮踏面形式" },
{ param: "油漆制造商" },
].map((item) => {
const row = { param: item.param };
//
sortedCompareList.value.forEach((product) => {
//
if (item.param === "品号申请时间") {
row[product.productNumber] = formattedToday;
} else if (item.param === "技术要求签订时间") {
row[product.productNumber] = formattedYesterday;
} else {
//
const fieldKey = fieldMap[item.param];
row[product.productNumber] = fieldKey ? product[fieldKey] ?? "-" : "-";
}
});
//
const values = sortedCompareList.value.map((p) => row[p.productNumber]);
const allSame = values.every((v) => v === values[0]);
differences[item.param] = !allSame;
return row;
rows.forEach((row) => {
if (!row || !row.param) return;
const values = sortedCompareList.value.map((item) => row[item.partNumber]);
const baseline = values.find(
(val) => val !== undefined && val !== null && val !== "-"
);
differences[row.param] =
baseline !== undefined && values.some((val) => val !== baseline);
});
//
parameterDifferences.value = differences;
return result;
});
};
// ""
const filteredCompareTableData = computed(() => {
@ -616,26 +562,24 @@ const isValueDifferent = (param) => {
return parameterDifferences.value[param];
};
//
const allValues = ref([]);
//
const initializeValues = () => {
const values = [];
allFields.forEach((field) => {
allFields.value.forEach((field) => {
const uniqueValues = [
...new Set(
mockData.map((item) => item[field.key] ?? "") // null
(fieldValueMap.value[field.key] || []).map((item) =>
item === null || item === undefined ? "" : String(item)
)
),
];
uniqueValues.forEach((value) => {
//
if (value !== "") {
values.push({
value: String(value),
value,
fieldKey: field.key,
fieldLabel: field.label,
weight: fieldValueMap[field.key]?.includes(String(value)) ? 2 : 1,
weight: 2,
});
}
});
@ -643,32 +587,30 @@ const initializeValues = () => {
allValues.value = values;
};
//
const updateKeyDiffHint = () => {
// CBC
const techSpecField = allFields.find(
(field) => field.label === "技术规范编号"
);
const cbcField = allFields.find((field) => field.label === "CBC编号");
//
const hasTechSpec = parsedConditions.value.some(
(cond) => cond.field === techSpecField?.key
);
const hasCbc = parsedConditions.value.some(
(cond) => cond.field === cbcField?.key
const mergeFieldMetadata = (fields = []) => {
if (!Array.isArray(fields) || fields.length === 0) return;
const fieldMapCache = new Map(
allFields.value.map((item) => [item.key, item])
);
//
const diffFields = [];
if (!hasTechSpec && techSpecField) diffFields.push(techSpecField);
if (!hasCbc && cbcField) diffFields.push(cbcField);
fields.forEach((item) => {
const label = item.fieldName || item.fieldLabel || item.label;
const key = item.fieldKey || item.key;
if (!label || !key) return;
if (!fieldMapCache.has(key)) {
fieldMapCache.set(key, { key, label });
}
//
keyDiffFields.value = diffFields;
//
showKeyDiffHint.value =
currentInput.value.trim() !== "" && diffFields.length > 0;
if (Array.isArray(item.fieldValues) && item.fieldValues.length > 0) {
const existingValues = fieldValueMap.value[key] || [];
fieldValueMap.value[key] = Array.from(
new Set([...existingValues, ...item.fieldValues])
);
}
});
allFields.value = Array.from(fieldMapCache.values());
initializeValues();
};
//
@ -834,7 +776,7 @@ const triggerConditionSuggestions = (conditionText) => {
const fieldLabel = conditionText.substring(0, colonIndex).trim();
const valuePart = conditionText.substring(colonIndex + 1).trim();
const field = allFields.find((f) => f.label === fieldLabel);
const field = allFields.value.find((f) => f.label === fieldLabel);
if (field) {
//
@ -869,10 +811,11 @@ const triggerConditionSuggestions = (conditionText) => {
}
};
//
const handleInput = (value) => {
//
const updateSuggestionsFromValue = (value) => {
const normalizedValue = value ?? "";
//
const parts = value.split(/[;]/);
const parts = normalizedValue.split(/[;]/);
const currentPart = parts[parts.length - 1].trim();
//
@ -884,7 +827,7 @@ const handleInput = (value) => {
}
//
showSuggestions.value = value.trim() !== "";
showSuggestions.value = normalizedValue.trim() !== "";
//
if (!currentPart) {
@ -902,7 +845,7 @@ const handleInput = (value) => {
if (isEditingField) {
//
fieldSuggestions = allFields
fieldSuggestions = allFields.value
.filter((field) =>
field.label.toLowerCase().includes(currentPart.toLowerCase())
)
@ -934,7 +877,7 @@ const handleInput = (value) => {
valueFieldCounts[val.fieldKey] = {
count: 0,
label: val.fieldLabel,
field: allFields.find((f) => f.key === val.fieldKey),
field: allFields.value.find((f) => f.key === val.fieldKey),
};
}
valueFieldCounts[val.fieldKey].count += val.weight;
@ -954,7 +897,7 @@ const handleInput = (value) => {
const valuePart = currentPart.substring(colonIndex + 1).trim();
//
const field = allFields.find((f) => f.label === fieldLabel);
const field = allFields.value.find((f) => f.label === fieldLabel);
if (field) {
//
@ -1029,6 +972,46 @@ const handleInput = (value) => {
activePossibleFieldIndex.value = -1;
};
//
const handleInput = (value, options = {}) => {
const normalizedValue = value ?? "";
updateSuggestionsFromValue(normalizedValue);
if (options.skipFetch) {
return;
}
const parts = normalizedValue.split(/[;]/);
const currentPart = parts[parts.length - 1]?.trim();
if (currentPart) {
scheduleSearchHint(currentPart);
}
};
const fetchSearchHints = async (keyword) => {
if (!keyword) return;
try {
const res = await searchHint({ keyword });
const hintList = Array.isArray(res?.data)
? res.data
: Array.isArray(res)
? res
: [];
mergeFieldMetadata(hintList);
updateSuggestionsFromValue(currentInput.value, { skipFetch: true });
} catch (error) {
console.error("searchHint error:", error);
}
};
const scheduleSearchHint = (keyword) => {
if (hintTimer.value) {
clearTimeout(hintTimer.value);
}
if (!keyword) return;
hintTimer.value = setTimeout(() => {
fetchSearchHints(keyword);
}, 300);
};
// - 4
const selectPossibleField = (field) => {
//
@ -1252,7 +1235,7 @@ const parseConditions = (input) => {
const valuePart = part.substring(colonIndex + 1).trim();
// key
const field = allFields.find((f) => f.label === fieldLabel);
const field = allFields.value.find((f) => f.label === fieldLabel);
conditions.push({
fieldLabel,
@ -1274,6 +1257,148 @@ const parseConditions = (input) => {
return conditions;
};
const getPreciseIndexSet = () => {
const preciseIndexSet = new Set();
preciseConditions.value.forEach((condition) => {
if (typeof condition.originalIndex === "number") {
preciseIndexSet.add(condition.originalIndex);
} else {
const idx = parsedConditions.value.findIndex(
(item) =>
item.field === condition.field && item.value === condition.value
);
if (idx !== -1) {
preciseIndexSet.add(idx);
}
}
});
return preciseIndexSet;
};
const buildFieldConditionsPayload = () => {
const preciseIndexSet = getPreciseIndexSet();
return parsedConditions.value
.filter((condition) => condition.value)
.map((condition, index) => {
const queryType = preciseIndexSet.has(index) ? "EXACT" : "FUZZY";
if (condition.valid && condition.field) {
return {
fieldName: condition.fieldLabel,
fieldValue: condition.value,
keyword: "",
queryType,
};
}
const keyword = condition.fieldLabel
? `${condition.fieldLabel}:${condition.value}`.replace(/:$/, "")
: condition.value;
return {
fieldName: "",
fieldValue: "",
keyword,
queryType,
};
});
};
const executeSearch = async ({ page = currentPage.value } = {}) => {
if (pendingSearchTimer.value) {
clearTimeout(pendingSearchTimer.value);
pendingSearchTimer.value = null;
}
const payload = {
fieldConditions: buildFieldConditionsPayload(),
inputWord: currentInput.value.trim(),
page: Math.max(page - 1, 0),
size: pageSize.value,
};
try {
const res = await searchProducts(payload);
const pageData = res?.data ?? res ?? {};
const list = pageData?.content ?? [];
filteredData.value = list.map((item) => ({
...item,
selected: false,
}));
totalItems.value = pageData?.totalElements ?? list.length;
const serverPage = pageData?.number ?? Math.max(page - 1, 0);
currentPage.value = serverPage + 1;
pageSize.value = pageData?.size ?? pageSize.value;
showResults.value = true;
} catch (error) {
console.error("search error:", error);
}
};
const scheduleSearchExecution = (page = 1) => {
if (pendingSearchTimer.value) {
clearTimeout(pendingSearchTimer.value);
}
pendingSearchTimer.value = setTimeout(() => {
executeSearch({ page });
}, 500);
};
const fetchDifferenceRecommendations = async () => {
if (!currentInput.value.trim()) {
keyDiffFields.value = [];
showKeyDiffHint.value = false;
return;
}
try {
const res = await differenceWords({
fieldConditions: buildFieldConditionsPayload(),
inputWord: currentInput.value.trim(),
});
const diffList = Array.isArray(res?.data)
? res.data
: Array.isArray(res)
? res
: [];
mergeFieldMetadata(diffList);
keyDiffFields.value = diffList.slice(0, 5).map((item) => ({
label: item.fieldName || "",
key: item.fieldKey,
}));
showKeyDiffHint.value =
keyDiffFields.value.length > 0 && currentInput.value.trim() !== "";
} catch (error) {
console.error("differenceWords error:", error);
}
};
const fetchCompareTable = async () => {
const idList = selectedCompareList.value
.map((item) => item.id || item.partNumber)
.filter(Boolean);
const ids = idList.join(",");
if (!ids) {
compareTableData.value = [];
parameterDifferences.value = {};
return;
}
try {
const res = await compare({ ids });
const rows = Array.isArray(res?.data)
? res.data
: Array.isArray(res)
? res
: [];
compareTableData.value = rows;
updateParameterDifferenceFlags(rows);
} catch (error) {
console.error("compare error:", error);
compareTableData.value = [];
parameterDifferences.value = {};
}
};
//
const addPreciseCondition = (index) => {
const condition = parsedConditions.value[index];
@ -1284,34 +1409,27 @@ const addPreciseCondition = (index) => {
if (!exists) {
preciseConditions.value.push({ ...condition, originalIndex: index });
//
applyPreciseFilter();
executeSearch({ page: 1 });
}
};
//
const removePreciseCondition = (index) => {
preciseConditions.value.splice(index, 1);
//
applyPreciseFilter();
executeSearch({ page: 1 });
};
//
const handleSearch = () => {
const handleSearch = async () => {
//
parsedConditions.value = parseConditions(currentInput.value);
//
updateKeyDiffHint();
//
preciseConditions.value = [];
//
applyPreciseFilter();
//
showResults.value = true;
//
await executeSearch({ page: 1 });
await fetchDifferenceRecommendations();
//
showSuggestions.value = false;
@ -1323,106 +1441,10 @@ const triggerRealTimeSearch = () => {
//
if (currentInput.value.trim()) {
parsedConditions.value = parseConditions(currentInput.value);
//
updateKeyDiffHint();
applyPreciseFilter();
showResults.value = true;
scheduleSearchExecution(1);
}
};
//
const applyPreciseFilter = () => {
//
let filtered = filterData(parsedConditions.value);
//
if (preciseConditions.value.length > 0) {
filtered = filtered.filter((item) => {
return preciseConditions.value.every((condition) => {
if (!condition.valid) return false;
if (condition.field) {
const fieldValue = item[condition.field];
return checkPreciseCondition(fieldValue, condition.value);
} else {
return allFields.some((field) => {
const fieldValue = item[field.key];
return checkPreciseCondition(fieldValue, condition.value);
});
}
});
});
}
//
filteredData.value = filtered.map((item) => ({ ...item, selected: false }));
totalItems.value = filteredData.value.length;
currentPage.value = 1;
};
//
const filterData = (conditions) => {
return mockData.filter((item) => {
return conditions.every((condition) => {
if (!condition.valid) return false;
//
if (condition.field) {
const fieldValue = item[condition.field];
return checkCondition(fieldValue, condition.value);
}
//
else {
return allFields.some((field) => {
const fieldValue = item[field.key];
return checkCondition(fieldValue, condition.value);
});
}
});
});
};
//
const checkCondition = (fieldValue, conditionValue) => {
// null
const processedValue = fieldValue ?? "";
const processedCondition = conditionValue ?? "";
// 使processedValueprocessedCondition
const operators = [
{ symbol: ">=", func: (a, b) => a >= b },
{ symbol: "<=", func: (a, b) => a <= b },
{ symbol: "!=", func: (a, b) => a != b },
{ symbol: ">", func: (a, b) => a > b },
{ symbol: "<", func: (a, b) => a < b },
{ symbol: "=", func: (a, b) => a == b },
];
for (const op of operators) {
if (processedCondition.startsWith(op.symbol)) {
const value = processedCondition.substring(op.symbol.length).trim();
if (!isNaN(Number(processedValue)) && !isNaN(Number(value))) {
return op.func(Number(processedValue), Number(value));
}
return op.func(String(processedValue), value);
}
}
return String(processedValue)
.toLowerCase()
.includes(processedCondition.toLowerCase());
};
//
const checkPreciseCondition = (fieldValue, conditionValue) => {
// null
const processedValue = fieldValue ?? "";
const processedCondition = conditionValue ?? "";
// 使
return String(processedValue) === processedCondition;
};
//
const removeCondition = (index) => {
const parts = currentInput.value.split(/[;]/);
@ -1444,8 +1466,8 @@ const removeCondition = (index) => {
preciseConditions.value.splice(preciseIndex, 1);
}
//
applyPreciseFilter();
//
executeSearch({ page: 1 });
//
if (selectedConditionIndex.value === index) {
@ -1479,10 +1501,12 @@ const clearAll = () => {
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1; //
executeSearch({ page: 1 });
};
const handleCurrentChange = (val) => {
currentPage.value = val;
executeSearch({ page: val });
//
window.scrollTo({ top: 0, behavior: "smooth" });
};
@ -1532,19 +1556,18 @@ watch(
//
function toggleSelect(item) {
//
const selectedCount = filteredData.value.filter((i) => i.selected).length;
item.selected = !item.selected;
}
//
function onCompare() {
const onCompare = async () => {
if (selectedCompareList.value.length === 0) {
ElMessage.warning("请先选择要对比的卡片");
return;
}
await fetchCompareTable();
compareDialogVisible.value = true;
}
};
//
function handleConfirm() {