|
|
@@ -48,6 +48,25 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="form-block">
|
|
|
+ <div class="form-block__title">当前物候期</div>
|
|
|
+ <el-select
|
|
|
+ v-model="form.phenologyId"
|
|
|
+ class="form-select"
|
|
|
+ placeholder="选择物候期"
|
|
|
+ placement="bottom-end"
|
|
|
+ popper-class="variety-select-popper"
|
|
|
+ :disabled="!form.categoryId"
|
|
|
+ clearable
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="opt in phenologyOptions"
|
|
|
+ :key="opt.id"
|
|
|
+ :label="opt.name"
|
|
|
+ :value="opt.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="form-block">
|
|
|
<div class="form-block__title">{{ startingPointQuestion }}</div>
|
|
|
<div class="form-date-wrap">
|
|
|
<el-date-picker
|
|
|
@@ -92,6 +111,7 @@ const { t } = useI18n();
|
|
|
|
|
|
const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
|
|
|
const DEFAULT_COUNTY_CODE = "440113";
|
|
|
+const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
|
|
|
const META_KEYS = new Set(["city", "county", "county_code", "province"]);
|
|
|
|
|
|
const props = defineProps({
|
|
|
@@ -111,15 +131,25 @@ const emit = defineEmits(["update:show", "confirm"]);
|
|
|
const form = reactive({
|
|
|
categoryId: "",
|
|
|
varietyId: "",
|
|
|
+ phenologyId: "",
|
|
|
plantDate: "",
|
|
|
zoneName: "",
|
|
|
});
|
|
|
|
|
|
+/** getVarietiesByCrop 返回的作物编码,供无分区互动接口使用 */
|
|
|
+const cropMeta = reactive({
|
|
|
+ crop_code: "",
|
|
|
+ crop_group: "",
|
|
|
+});
|
|
|
+
|
|
|
const categoryLoading = ref(false);
|
|
|
const varietyLoading = ref(false);
|
|
|
const categoryOptions = ref([]);
|
|
|
const varietyOptions = ref([]);
|
|
|
+const phenologyOptions = ref([]);
|
|
|
const startingPointQuestion = ref(t("agriFile.plantStartPoint"));
|
|
|
+let interactFetchKey = "";
|
|
|
+let interactFetchPromise = null;
|
|
|
|
|
|
const showValue = computed({
|
|
|
get: () => props.show,
|
|
|
@@ -144,20 +174,126 @@ const resolveCountyCode = () => {
|
|
|
return DEFAULT_COUNTY_CODE;
|
|
|
};
|
|
|
|
|
|
+function getDefaultPoint() {
|
|
|
+ try {
|
|
|
+ const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
|
|
|
+ const wkt = farm.wkt || farm.geom_wkt || farm.farm_location;
|
|
|
+ if (typeof wkt === "string" && /^POINT\s*\(/i.test(wkt.trim())) return wkt.trim();
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const saved = JSON.parse(sessionStorage.getItem(LOCATION_KEY) || "null");
|
|
|
+ if (saved?.point) return saved.point;
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ return localStorage.getItem("MINI_USER_LOCATION_POINT") || DEFAULT_POINT;
|
|
|
+}
|
|
|
+
|
|
|
+/** 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 applyInitialInteract(data) {
|
|
|
+ if (!data) return;
|
|
|
+
|
|
|
+ const theme = data.date_interact?.interact_theme;
|
|
|
+ if (theme) {
|
|
|
+ startingPointQuestion.value = theme;
|
|
|
+ }
|
|
|
+
|
|
|
+ const phenology = mapOptionsInteractToPhenology(data.options_interact?.interact);
|
|
|
+ if (!phenology.length) return;
|
|
|
+
|
|
|
+ phenologyOptions.value = phenology;
|
|
|
+ if (!form.phenologyId) return;
|
|
|
+ const stillExists = phenology.some((opt) => String(opt.id) === String(form.phenologyId));
|
|
|
+ if (!stillExists) form.phenologyId = "";
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchInitialInteractOptionsWithoutZone() {
|
|
|
+ const category = categoryOptions.value.find(
|
|
|
+ (item) => String(item.id) === String(form.categoryId)
|
|
|
+ );
|
|
|
+ if (!category) return;
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ crop_big_type: cropMeta.crop_group || category.firstCrop || "",
|
|
|
+ crop_code: cropMeta.crop_code || category.code || "",
|
|
|
+ crop_maturing: "P0",
|
|
|
+ location: getDefaultPoint(),
|
|
|
+ date: form.plantDate || new Date().toISOString().split("T")[0],
|
|
|
+ };
|
|
|
+ if (!params.crop_code) return;
|
|
|
+
|
|
|
+ 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) {
|
|
|
+ applyInitialInteract(res.data);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn("[addZoneInfoPopup] getInitialInteractionWithoutZone failed", error);
|
|
|
+ } finally {
|
|
|
+ if (interactFetchKey === key) {
|
|
|
+ interactFetchPromise = null;
|
|
|
+ interactFetchKey = "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })();
|
|
|
+
|
|
|
+ return interactFetchPromise;
|
|
|
+}
|
|
|
+
|
|
|
/** getCrops 可能返回对象(果树/大田分组)或数组,统一展平为下拉选项 */
|
|
|
const flattenCropsToOptions = (data) => {
|
|
|
const map = new Map();
|
|
|
- const pushCrop = (crop) => {
|
|
|
+ const pushCrop = (crop, firstCrop = "") => {
|
|
|
const id = crop?.crop_id ?? crop?.id;
|
|
|
const name = crop?.crop_name ?? crop?.name;
|
|
|
if (id == null || !name) return;
|
|
|
- map.set(String(id), { id, name, code: crop.crop_code || "" });
|
|
|
+ map.set(String(id), {
|
|
|
+ id,
|
|
|
+ name,
|
|
|
+ code: crop.crop_code || "",
|
|
|
+ firstCrop: firstCrop || crop.crop_group || "",
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
if (Array.isArray(data)) {
|
|
|
data.forEach((item) => {
|
|
|
if (Array.isArray(item?.items)) {
|
|
|
- item.items.forEach(pushCrop);
|
|
|
+ item.items.forEach((crop) => pushCrop(crop, item.firstCrop || ""));
|
|
|
return;
|
|
|
}
|
|
|
pushCrop(item);
|
|
|
@@ -171,10 +307,10 @@ const flattenCropsToOptions = (data) => {
|
|
|
.forEach((key) => {
|
|
|
data[key].forEach((group) => {
|
|
|
if (Array.isArray(group?.items)) {
|
|
|
- group.items.forEach(pushCrop);
|
|
|
+ group.items.forEach((crop) => pushCrop(crop, key));
|
|
|
return;
|
|
|
}
|
|
|
- pushCrop(group);
|
|
|
+ pushCrop(group, key);
|
|
|
});
|
|
|
});
|
|
|
return Array.from(map.values());
|
|
|
@@ -204,6 +340,9 @@ const fetchCategoryOptions = async () => {
|
|
|
const fetchVarietyOptions = async (cropId) => {
|
|
|
if (cropId == null || cropId === "") {
|
|
|
varietyOptions.value = [];
|
|
|
+ phenologyOptions.value = [];
|
|
|
+ cropMeta.crop_code = "";
|
|
|
+ cropMeta.crop_group = "";
|
|
|
startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
return;
|
|
|
}
|
|
|
@@ -214,15 +353,22 @@ const fetchVarietyOptions = async (cropId) => {
|
|
|
});
|
|
|
if (res.code === 200 && res.data) {
|
|
|
varietyOptions.value = res.data.varieties;
|
|
|
- startingPointQuestion.value =
|
|
|
- res.data.starting_point_question || t("agriFile.plantStartPoint");
|
|
|
+ cropMeta.crop_code = res.data.crop_code || "";
|
|
|
+ cropMeta.crop_group = res.data.crop_group || "";
|
|
|
+ await fetchInitialInteractOptionsWithoutZone();
|
|
|
} else {
|
|
|
varietyOptions.value = [];
|
|
|
+ phenologyOptions.value = [];
|
|
|
+ cropMeta.crop_code = "";
|
|
|
+ cropMeta.crop_group = "";
|
|
|
startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
ElMessage.error(res.msg || "获取品种列表失败");
|
|
|
}
|
|
|
} catch {
|
|
|
varietyOptions.value = [];
|
|
|
+ phenologyOptions.value = [];
|
|
|
+ cropMeta.crop_code = "";
|
|
|
+ cropMeta.crop_group = "";
|
|
|
startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
ElMessage.error("获取品种列表失败,请稍后再试");
|
|
|
} finally {
|
|
|
@@ -243,10 +389,14 @@ const applyAutoZoneName = (name) => {
|
|
|
const resetForm = () => {
|
|
|
form.categoryId = "";
|
|
|
form.varietyId = "";
|
|
|
+ form.phenologyId = "";
|
|
|
form.plantDate = "";
|
|
|
form.zoneName = "";
|
|
|
lastAutoZoneName.value = "";
|
|
|
varietyOptions.value = [];
|
|
|
+ phenologyOptions.value = [];
|
|
|
+ cropMeta.crop_code = "";
|
|
|
+ cropMeta.crop_group = "";
|
|
|
startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
};
|
|
|
|
|
|
@@ -261,6 +411,9 @@ watch(
|
|
|
|
|
|
const handleCategoryChange = (cropId) => {
|
|
|
form.varietyId = "";
|
|
|
+ form.phenologyId = "";
|
|
|
+ phenologyOptions.value = [];
|
|
|
+ startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
fetchVarietyOptions(cropId);
|
|
|
const category = categoryOptions.value.find(
|
|
|
(item) => String(item.id) === String(cropId)
|
|
|
@@ -282,6 +435,10 @@ const handleConfirm = () => {
|
|
|
ElMessage.warning(t("agriFile.pleaseSelectVariety"));
|
|
|
return;
|
|
|
}
|
|
|
+ if (!form.phenologyId) {
|
|
|
+ ElMessage.warning("请选择物候期");
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (!form.plantDate) {
|
|
|
ElMessage.warning(t("agriFile.pleaseSelectPlantDate"));
|
|
|
return;
|
|
|
@@ -298,13 +455,19 @@ const handleConfirm = () => {
|
|
|
const variety = varietyOptions.value.find(
|
|
|
(item) => String(item.id) === String(form.varietyId)
|
|
|
);
|
|
|
+ const phenology = phenologyOptions.value.find(
|
|
|
+ (item) => String(item.id) === String(form.phenologyId)
|
|
|
+ );
|
|
|
|
|
|
emit("confirm", {
|
|
|
zone_name: zoneName,
|
|
|
category_id: form.categoryId,
|
|
|
category_name: category?.name || "",
|
|
|
variety_id: form.varietyId,
|
|
|
- variety_name: variety?.name || "",
|
|
|
+ variety_name: variety?.name || form.varietyId || "",
|
|
|
+ phenology_id: form.phenologyId,
|
|
|
+ phenology_name: phenology?.name || "",
|
|
|
+ phenology_code: phenology?.code || String(form.phenologyId || ""),
|
|
|
plant_date: form.plantDate,
|
|
|
});
|
|
|
emit("update:show", false);
|
|
|
@@ -348,6 +511,7 @@ const handleConfirm = () => {
|
|
|
.form-select {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
+ width: 100%;
|
|
|
}
|
|
|
|
|
|
:deep(.el-select__wrapper),
|