|
@@ -296,7 +296,7 @@ const goSelectItemLocation = (item) => {
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-onMounted(async () => {
|
|
|
|
|
|
|
+async function bootstrapVarietyPage() {
|
|
|
initCategoryTabsFromSession();
|
|
initCategoryTabsFromSession();
|
|
|
restoreSelectedListDraft();
|
|
restoreSelectedListDraft();
|
|
|
ensureDefaultForm();
|
|
ensureDefaultForm();
|
|
@@ -304,19 +304,19 @@ onMounted(async () => {
|
|
|
await Promise.all(
|
|
await Promise.all(
|
|
|
categoryTabs.value.map((tab) => fetchVarietiesByCrop(tab.id))
|
|
categoryTabs.value.map((tab) => fetchVarietiesByCrop(tab.id))
|
|
|
);
|
|
);
|
|
|
|
|
+ await fetchInitialInteractOptionsWithoutZone();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ bootstrapVarietyPage();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-onActivated(async () => {
|
|
|
|
|
- initCategoryTabsFromSession();
|
|
|
|
|
- restoreSelectedListDraft();
|
|
|
|
|
- ensureDefaultForm();
|
|
|
|
|
|
|
+/** keep-alive 返回时只同步点位/地图,避免重复拉接口 */
|
|
|
|
|
+onActivated(() => {
|
|
|
syncItemLocationFromSession();
|
|
syncItemLocationFromSession();
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
itemPreviewMaps.forEach((map) => map.kmap?.map?.updateSize?.());
|
|
itemPreviewMaps.forEach((map) => map.kmap?.map?.updateSize?.());
|
|
|
});
|
|
});
|
|
|
- await Promise.all(
|
|
|
|
|
- categoryTabs.value.map((tab) => fetchVarietiesByCrop(tab.id))
|
|
|
|
|
- );
|
|
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
onBeforeUnmount(() => {
|
|
@@ -404,6 +404,129 @@ const searchKeyword = ref("");
|
|
|
const appliedKeyword = ref("");
|
|
const appliedKeyword = ref("");
|
|
|
const gridExpanded = ref(false);
|
|
const gridExpanded = ref(false);
|
|
|
const selectedList = ref([]);
|
|
const selectedList = ref([]);
|
|
|
|
|
+/** 无分区初始互动选项,业务侧自行对接 */
|
|
|
|
|
+const initialInteractOptions = ref(null);
|
|
|
|
|
+/** 进行中的品种请求,防止并发重复 */
|
|
|
|
|
+const varietyFetchPromises = new Map();
|
|
|
|
|
+/** 进行中的初始互动请求 key */
|
|
|
|
|
+let interactFetchKey = "";
|
|
|
|
|
+let interactFetchPromise = null;
|
|
|
|
|
+
|
|
|
|
|
+function readJson(key) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const raw = sessionStorage.getItem(key);
|
|
|
|
|
+ return raw ? JSON.parse(raw) : null;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function writeJson(key, value) {
|
|
|
|
|
+ sessionStorage.setItem(key, JSON.stringify(value));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function saveCropDetailFromApi(data) {
|
|
|
|
|
+ if (data?.crop_id == null || data?.crop_id === "") return;
|
|
|
|
|
+ const cache = readJson("ENTRY_CROP_DETAIL") || {};
|
|
|
|
|
+ cache[String(data.crop_id)] = {
|
|
|
|
|
+ crop_id: data.crop_id,
|
|
|
|
|
+ crop_code: data.crop_code || "",
|
|
|
|
|
+ crop_group: data.crop_group || "",
|
|
|
|
|
+ crop_name: data.crop_name || "",
|
|
|
|
|
+ crop_type: data.crop_type || "",
|
|
|
|
|
+ };
|
|
|
|
|
+ writeJson("ENTRY_CROP_DETAIL", cache);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** options_interact.interact → 物候期下拉(展示 period_name,提交 period_code) */
|
|
|
|
|
+function mapOptionsInteractToPhenology(interact) {
|
|
|
|
|
+ if (!interact) return [];
|
|
|
|
|
+ const list = Array.isArray(interact)
|
|
|
|
|
+ ? interact
|
|
|
|
|
+ : Object.keys(interact)
|
|
|
|
|
+ .sort((a, b) => Number(a) - Number(b))
|
|
|
|
|
+ .map((key) => interact[key]);
|
|
|
|
|
+
|
|
|
|
|
+ return list
|
|
|
|
|
+ .map((item) => {
|
|
|
|
|
+ if (!item || typeof item !== "object") return null;
|
|
|
|
|
+ const code = item.period_code ?? item.code ?? item.id;
|
|
|
|
|
+ const name = item.period_name ?? item.name ?? "";
|
|
|
|
|
+ if (!name) return null;
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: code != null && code !== "" ? code : name,
|
|
|
|
|
+ name,
|
|
|
|
|
+ code: code != null && code !== "" ? String(code) : "",
|
|
|
|
|
+ url: item.url || "",
|
|
|
|
|
+ };
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 将无分区初始互动结果写入当前品类:起种问题文案 + 物候期选项 */
|
|
|
|
|
+function applyInitialInteractToCurrentCategory(data) {
|
|
|
|
|
+ const tab = currentCategory.value;
|
|
|
|
|
+ if (!tab || !data) return;
|
|
|
|
|
+
|
|
|
|
|
+ const theme = data.date_interact?.interact_theme;
|
|
|
|
|
+ if (theme) {
|
|
|
|
|
+ tab.startingPointQuestion = theme;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const phenology = mapOptionsInteractToPhenology(data.options_interact?.interact);
|
|
|
|
|
+ if (!phenology.length) return;
|
|
|
|
|
+
|
|
|
|
|
+ tab.phenology = phenology;
|
|
|
|
|
+ const form = getCurrentForm();
|
|
|
|
|
+ if (!form?.phenologyId) return;
|
|
|
|
|
+ const stillExists = phenology.some((opt) => String(opt.id) === String(form.phenologyId));
|
|
|
|
|
+ if (!stillExists) form.phenologyId = "";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 组装并请求无分区初始互动选项 */
|
|
|
|
|
+async function fetchInitialInteractOptionsWithoutZone() {
|
|
|
|
|
+ const category = currentCategory.value;
|
|
|
|
|
+ if (!category) return;
|
|
|
|
|
+
|
|
|
|
|
+ const form = getCurrentForm();
|
|
|
|
|
+ const resident = readJson(LOCATION_KEY);
|
|
|
|
|
+ const cropDetail = readJson("ENTRY_CROP_DETAIL") || {};
|
|
|
|
|
+ const detail = cropDetail[String(category.id)] || {};
|
|
|
|
|
+
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ crop_big_type: category.firstCrop || detail.crop_group || "",
|
|
|
|
|
+ crop_code: category.code || detail.crop_code || form?.cropCode || "",
|
|
|
|
|
+ crop_maturing: "P0",
|
|
|
|
|
+ location: form?.location || resident?.point || getDefaultPoint(),
|
|
|
|
|
+ date: form?.startTime || new Date().toISOString().split("T")[0],
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const key = `${params.crop_code}|${params.location}|${params.date}`;
|
|
|
|
|
+ if (interactFetchPromise && interactFetchKey === key) {
|
|
|
|
|
+ return interactFetchPromise;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ interactFetchKey = key;
|
|
|
|
|
+ interactFetchPromise = (async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const raw = await VE_API.questionnaire.getInitialInteractionWithoutZone(params);
|
|
|
|
|
+ const res = Array.isArray(raw) ? raw[0] : raw;
|
|
|
|
|
+ if (res?.code === 200) {
|
|
|
|
|
+ initialInteractOptions.value = res.data ?? null;
|
|
|
|
|
+ applyInitialInteractToCurrentCategory(res.data);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("fetchInitialInteractOptionsWithoutZone failed", error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (interactFetchKey === key) {
|
|
|
|
|
+ interactFetchPromise = null;
|
|
|
|
|
+ interactFetchKey = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ return interactFetchPromise;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
const fetchVarietiesByCrop = async (cropId, force = false) => {
|
|
const fetchVarietiesByCrop = async (cropId, force = false) => {
|
|
|
if (cropId == null || cropId === "") return;
|
|
if (cropId == null || cropId === "") return;
|
|
@@ -411,48 +534,85 @@ const fetchVarietiesByCrop = async (cropId, force = false) => {
|
|
|
if (!tab) return;
|
|
if (!tab) return;
|
|
|
if (tab.loaded && !force) return;
|
|
if (tab.loaded && !force) return;
|
|
|
|
|
|
|
|
|
|
+ const cacheKey = String(cropId);
|
|
|
|
|
+ if (!force && varietyFetchPromises.has(cacheKey)) {
|
|
|
|
|
+ return varietyFetchPromises.get(cacheKey);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const showLoading = String(cropId) === String(activeCategoryId.value);
|
|
const showLoading = String(cropId) === String(activeCategoryId.value);
|
|
|
if (showLoading) varietyLoading.value = true;
|
|
if (showLoading) varietyLoading.value = true;
|
|
|
- try {
|
|
|
|
|
- const res = await VE_API.entry.getVarietiesByCrop({
|
|
|
|
|
- crop_id: cropId,
|
|
|
|
|
- });
|
|
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
|
|
- const data = res.data;
|
|
|
|
|
- tab.varieties = (data.varieties || [])
|
|
|
|
|
- .map((item) => {
|
|
|
|
|
- if (typeof item === "string") {
|
|
|
|
|
- return { id: item, name: item };
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const request = (async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await VE_API.entry.getVarietiesByCrop({
|
|
|
|
|
+ crop_id: cropId,
|
|
|
|
|
+ });
|
|
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
|
|
+ const data = res.data;
|
|
|
|
|
+ if (data.crop_code) tab.code = data.crop_code;
|
|
|
|
|
+ if (data.crop_group) tab.firstCrop = tab.firstCrop || data.crop_group;
|
|
|
|
|
+ tab.varieties = (data.varieties || [])
|
|
|
|
|
+ .map((item) => {
|
|
|
|
|
+ if (typeof item === "string") {
|
|
|
|
|
+ return { id: item, name: item };
|
|
|
|
|
+ }
|
|
|
|
|
+ const name = item?.name || "";
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: item?.id ?? name,
|
|
|
|
|
+ name,
|
|
|
|
|
+ };
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter((item) => item.name);
|
|
|
|
|
+ tab.phenology = mapPhenologyList(data.phenology);
|
|
|
|
|
+ tab.startingPointQuestion =
|
|
|
|
|
+ data.starting_point_question || "起种点问题";
|
|
|
|
|
+ tab.loaded = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 优先用已缓存的 crop_code,避免重复打 farmer_crop_detail
|
|
|
|
|
+ if (!tab.code) {
|
|
|
|
|
+ const cached = readJson("ENTRY_CROP_DETAIL") || {};
|
|
|
|
|
+ const detail = cached[cacheKey];
|
|
|
|
|
+ if (detail?.crop_code) {
|
|
|
|
|
+ tab.code = detail.crop_code;
|
|
|
|
|
+ tab.firstCrop = tab.firstCrop || detail.crop_group || "";
|
|
|
}
|
|
}
|
|
|
- const name = item?.name || "";
|
|
|
|
|
- return {
|
|
|
|
|
- id: item?.id ?? name,
|
|
|
|
|
- name,
|
|
|
|
|
- };
|
|
|
|
|
- })
|
|
|
|
|
- .filter((item) => item.name);
|
|
|
|
|
- tab.phenology = mapPhenologyList(data.phenology);
|
|
|
|
|
- tab.startingPointQuestion =
|
|
|
|
|
- data.starting_point_question || "起种点问题";
|
|
|
|
|
- tab.loaded = true;
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!tab.code) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const detailRes = await VE_API.entry.getMachines({ crop_id: cropId });
|
|
|
|
|
+ const detail = detailRes?.data || {};
|
|
|
|
|
+ if (detailRes?.code === 200 && detail.crop_code) {
|
|
|
|
|
+ tab.code = detail.crop_code;
|
|
|
|
|
+ tab.firstCrop = tab.firstCrop || detail.crop_group || "";
|
|
|
|
|
+ saveCropDetailFromApi(detail);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // ignore
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ tab.varieties = [];
|
|
|
|
|
+ tab.phenology = [];
|
|
|
|
|
+ tab.startingPointQuestion = "起种点问题";
|
|
|
|
|
+ if (showLoading) {
|
|
|
|
|
+ ElMessage.error(res.msg || "获取品种列表失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
tab.varieties = [];
|
|
tab.varieties = [];
|
|
|
tab.phenology = [];
|
|
tab.phenology = [];
|
|
|
tab.startingPointQuestion = "起种点问题";
|
|
tab.startingPointQuestion = "起种点问题";
|
|
|
if (showLoading) {
|
|
if (showLoading) {
|
|
|
- ElMessage.error(res.msg || "获取品种列表失败");
|
|
|
|
|
|
|
+ ElMessage.error("获取品种列表失败,请稍后再试");
|
|
|
}
|
|
}
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (showLoading) varietyLoading.value = false;
|
|
|
|
|
+ varietyFetchPromises.delete(cacheKey);
|
|
|
}
|
|
}
|
|
|
- } catch {
|
|
|
|
|
- tab.varieties = [];
|
|
|
|
|
- tab.phenology = [];
|
|
|
|
|
- tab.startingPointQuestion = "起种点问题";
|
|
|
|
|
- if (showLoading) {
|
|
|
|
|
- ElMessage.error("获取品种列表失败,请稍后再试");
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
- if (showLoading) varietyLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ })();
|
|
|
|
|
+
|
|
|
|
|
+ varietyFetchPromises.set(cacheKey, request);
|
|
|
|
|
+ return request;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
watch(
|
|
watch(
|
|
@@ -567,14 +727,16 @@ const currentStartingPointQuestion = computed(() =>
|
|
|
getStartingPointQuestion(currentSelected.value?.categoryId || activeCategoryId.value)
|
|
getStartingPointQuestion(currentSelected.value?.categoryId || activeCategoryId.value)
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-watch(activeCategoryId, (id) => {
|
|
|
|
|
|
|
+watch(activeCategoryId, async (id, prevId) => {
|
|
|
searchKeyword.value = "";
|
|
searchKeyword.value = "";
|
|
|
appliedKeyword.value = "";
|
|
appliedKeyword.value = "";
|
|
|
gridExpanded.value = false;
|
|
gridExpanded.value = false;
|
|
|
ensureDefaultForm();
|
|
ensureDefaultForm();
|
|
|
- if (id != null) {
|
|
|
|
|
- fetchVarietiesByCrop(id);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 初始化赋值由 bootstrap 拉取,避免与 onMounted 重复请求
|
|
|
|
|
+ if (id == null || prevId == null) return;
|
|
|
|
|
+ if (String(id) === String(prevId)) return;
|
|
|
|
|
+ await fetchVarietiesByCrop(id);
|
|
|
|
|
+ fetchInitialInteractOptionsWithoutZone();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const isSelected = (id) =>
|
|
const isSelected = (id) =>
|
|
@@ -724,6 +886,7 @@ const handleConfirm = () => {
|
|
|
const options = getPhenologyOptions(row.categoryId);
|
|
const options = getPhenologyOptions(row.categoryId);
|
|
|
const found = options.find((opt) => String(opt.id) === String(row.phenologyId));
|
|
const found = options.find((opt) => String(opt.id) === String(row.phenologyId));
|
|
|
row.phenophase = found?.name || String(row.phenologyId || "");
|
|
row.phenophase = found?.name || String(row.phenologyId || "");
|
|
|
|
|
+ row.phenophaseCode = found?.code || String(row.phenologyId || "");
|
|
|
});
|
|
});
|
|
|
saveSelectedListDraft();
|
|
saveSelectedListDraft();
|
|
|
emit("next");
|
|
emit("next");
|