|
@@ -74,7 +74,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { computed, nextTick, onActivated, onMounted, ref } from "vue";
|
|
|
|
|
|
|
+import { computed, nextTick, onActivated, ref } from "vue";
|
|
|
import { useStore } from "vuex";
|
|
import { useStore } from "vuex";
|
|
|
import { useRouter } from "vue-router";
|
|
import { useRouter } from "vue-router";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
@@ -103,58 +103,16 @@ const formatDateYMD = (date = new Date()) =>
|
|
|
|
|
|
|
|
const filterDate = ref(formatDateYMD());
|
|
const filterDate = ref(formatDateYMD());
|
|
|
|
|
|
|
|
|
|
+const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
|
|
|
|
|
+const MAP_POINT_STORAGE_KEY = "GROWTH_REPORT_MAP_POINT";
|
|
|
|
|
+const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 假数据模板:wkt 相对中心点偏移(经度、纬度)
|
|
|
|
|
- * 后续可替换为接口,接口返回绝对坐标时可去掉 offset
|
|
|
|
|
|
|
+ * 假数据模板(已完成 tab 兜底):wkt 相对中心点偏移
|
|
|
*/
|
|
*/
|
|
|
-const TASK_MOCK_TEMPLATE = [
|
|
|
|
|
- {
|
|
|
|
|
- id: 1,
|
|
|
|
|
- status: "pending",
|
|
|
|
|
- title: "喷施杀菌剂",
|
|
|
|
|
- isHighRisk: false,
|
|
|
|
|
- isNormal: true,
|
|
|
|
|
- isAbnormal: false,
|
|
|
|
|
- isWeather: false,
|
|
|
|
|
- cornerTip: "待执行",
|
|
|
|
|
- analysisSummary: "近期高温高湿,蒂蛀虫风险上升",
|
|
|
|
|
- areaCode: "A-01 / 分区一",
|
|
|
|
|
- farmName: "喷施杀菌剂",
|
|
|
|
|
- executeDate: "08/06",
|
|
|
|
|
- offset: [-0.0018, 0.0012],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 2,
|
|
|
|
|
- status: "pending",
|
|
|
|
|
- title: "叶面追肥",
|
|
|
|
|
- isHighRisk: true,
|
|
|
|
|
- isNormal: false,
|
|
|
|
|
- isAbnormal: true,
|
|
|
|
|
- isWeather: false,
|
|
|
|
|
- cornerTip: "发现异常后机动执行",
|
|
|
|
|
- analysisSummary: "转色期养分需求增加,需补充钾肥",
|
|
|
|
|
- areaCode: "B-03 / 分区二",
|
|
|
|
|
- farmName: "叶面追肥",
|
|
|
|
|
- executeDate: "08/06",
|
|
|
|
|
- offset: [0.0016, 0.0008],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+const COMPLETED_MOCK_TEMPLATE = [
|
|
|
{
|
|
{
|
|
|
- id: 3,
|
|
|
|
|
- status: "pending",
|
|
|
|
|
- title: "灌溉补水",
|
|
|
|
|
- isHighRisk: true,
|
|
|
|
|
- isNormal: false,
|
|
|
|
|
- isAbnormal: false,
|
|
|
|
|
- isWeather: true,
|
|
|
|
|
- cornerTip: "建议执行",
|
|
|
|
|
- analysisSummary: "土壤墒情偏低,局部出现轻度干旱胁迫",
|
|
|
|
|
- areaCode: "C-02 / 分区三",
|
|
|
|
|
- farmName: "灌溉补水",
|
|
|
|
|
- executeDate: "08/06",
|
|
|
|
|
- offset: [-0.0006, -0.0014],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 4,
|
|
|
|
|
|
|
+ id: "mock-c-4",
|
|
|
status: "completed",
|
|
status: "completed",
|
|
|
title: "病虫害巡查",
|
|
title: "病虫害巡查",
|
|
|
isHighRisk: false,
|
|
isHighRisk: false,
|
|
@@ -164,13 +122,13 @@ const TASK_MOCK_TEMPLATE = [
|
|
|
isByMyself: true,
|
|
isByMyself: true,
|
|
|
cornerTip: "已完成",
|
|
cornerTip: "已完成",
|
|
|
analysisSummary: "新梢与果实表面未见明显异常斑点",
|
|
analysisSummary: "新梢与果实表面未见明显异常斑点",
|
|
|
- areaCode: "D-05 / 全园",
|
|
|
|
|
|
|
+ areaCode: "分区一",
|
|
|
farmName: "病虫害巡查",
|
|
farmName: "病虫害巡查",
|
|
|
executeDate: "08/05",
|
|
executeDate: "08/05",
|
|
|
offset: [0.0004, -0.0003],
|
|
offset: [0.0004, -0.0003],
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
- id: 5,
|
|
|
|
|
|
|
+ id: "mock-c-5",
|
|
|
status: "completed",
|
|
status: "completed",
|
|
|
title: "修剪整枝",
|
|
title: "修剪整枝",
|
|
|
isHighRisk: false,
|
|
isHighRisk: false,
|
|
@@ -179,53 +137,35 @@ const TASK_MOCK_TEMPLATE = [
|
|
|
isWeather: false,
|
|
isWeather: false,
|
|
|
cornerTip: "已完成",
|
|
cornerTip: "已完成",
|
|
|
analysisSummary: "已清理过密枝条,改善通风透光",
|
|
analysisSummary: "已清理过密枝条,改善通风透光",
|
|
|
- areaCode: "A-02 / 分区一",
|
|
|
|
|
|
|
+ areaCode: "分区一",
|
|
|
farmName: "修剪整枝",
|
|
farmName: "修剪整枝",
|
|
|
executeDate: "08/04",
|
|
executeDate: "08/04",
|
|
|
offset: [-0.0024, 0.0004],
|
|
offset: [-0.0024, 0.0004],
|
|
|
},
|
|
},
|
|
|
- {
|
|
|
|
|
- id: 6,
|
|
|
|
|
- status: "completed",
|
|
|
|
|
- title: "除草松土",
|
|
|
|
|
- isHighRisk: false,
|
|
|
|
|
- isNormal: false,
|
|
|
|
|
- isAbnormal: true,
|
|
|
|
|
- isWeather: false,
|
|
|
|
|
- cornerTip: "已完成",
|
|
|
|
|
- analysisSummary: "行间杂草已清除,土壤表层已疏松",
|
|
|
|
|
- areaCode: "B-01 / 分区二",
|
|
|
|
|
- farmName: "除草松土",
|
|
|
|
|
- executeDate: "08/03",
|
|
|
|
|
- offset: [0.002, -0.0009],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- id: 7,
|
|
|
|
|
- status: "completed",
|
|
|
|
|
- title: "防风加固",
|
|
|
|
|
- isHighRisk: false,
|
|
|
|
|
- isNormal: false,
|
|
|
|
|
- isAbnormal: false,
|
|
|
|
|
- isWeather: true,
|
|
|
|
|
- cornerTip: "已完成",
|
|
|
|
|
- analysisSummary: "台风过境前已完成支架加固与果袋检查",
|
|
|
|
|
- areaCode: "C-01 / 分区三",
|
|
|
|
|
- farmName: "防风加固",
|
|
|
|
|
- executeDate: "08/02",
|
|
|
|
|
- offset: [-0.0011, 0.0016],
|
|
|
|
|
- },
|
|
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
-/** 假数据:后续可替换为接口;status: pending 待执行 / completed 已完成 */
|
|
|
|
|
|
|
+/** status: pending 待执行 / completed 已完成 */
|
|
|
const taskList = ref([]);
|
|
const taskList = ref([]);
|
|
|
|
|
|
|
|
-/** 根据中心点生成附近任务点位 */
|
|
|
|
|
-const buildTaskListNearCenter = (centerWkt) => {
|
|
|
|
|
|
|
+function readEntryFarmData() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatExecuteDate(date = new Date()) {
|
|
|
|
|
+ return `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}`;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 根据中心点生成附近任务点位(已完成 mock) */
|
|
|
|
|
+const buildCompletedNearCenter = (centerWkt) => {
|
|
|
const coord = convertPointToArray(centerWkt);
|
|
const coord = convertPointToArray(centerWkt);
|
|
|
const lng = Number(coord?.[0]);
|
|
const lng = Number(coord?.[0]);
|
|
|
const lat = Number(coord?.[1]);
|
|
const lat = Number(coord?.[1]);
|
|
|
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return [];
|
|
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return [];
|
|
|
- return TASK_MOCK_TEMPLATE.map((item) => {
|
|
|
|
|
|
|
+ return COMPLETED_MOCK_TEMPLATE.map((item) => {
|
|
|
const [dx = 0, dy = 0] = item.offset || [];
|
|
const [dx = 0, dy = 0] = item.offset || [];
|
|
|
const { offset, ...rest } = item;
|
|
const { offset, ...rest } = item;
|
|
|
return {
|
|
return {
|
|
@@ -234,6 +174,98 @@ const buildTaskListNearCenter = (centerWkt) => {
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 接口农事 → 列表卡片
|
|
|
|
|
+ * @param {'standard' | 'warning'} type 标准 / 预警
|
|
|
|
|
+ */
|
|
|
|
|
+function mapAgronomyToTask(item, index, centerWkt, type = "standard") {
|
|
|
|
|
+ const coord = convertPointToArray(centerWkt);
|
|
|
|
|
+ const lng = Number(coord?.[0]);
|
|
|
|
|
+ const lat = Number(coord?.[1]);
|
|
|
|
|
+ const dx = ((index % 3) - 1) * 0.0012;
|
|
|
|
|
+ const dy = (Math.floor(index / 3) - 0.5) * 0.001;
|
|
|
|
|
+ const hasCoord = Number.isFinite(lng) && Number.isFinite(lat);
|
|
|
|
|
+ const title = item.fw_name || "";
|
|
|
|
|
+ const isWarning = type === "warning";
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: item.id ?? `${type}-${index}`,
|
|
|
|
|
+ status: "pending",
|
|
|
|
|
+ title,
|
|
|
|
|
+ farmName: title,
|
|
|
|
|
+ isHighRisk: isWarning,
|
|
|
|
|
+ isNormal: !isWarning,
|
|
|
|
|
+ isAbnormal: false,
|
|
|
|
|
+ isWeather: isWarning,
|
|
|
|
|
+ cornerTip: isWarning ? "建议执行" : "待执行",
|
|
|
|
|
+ analysisSummary: item.fw_explain_simple || item.fw_explain || "",
|
|
|
|
|
+ areaCode: item.crop_category_name || "分区一",
|
|
|
|
|
+ executeDate: formatExecuteDate(),
|
|
|
|
|
+ phenophaseCode: item.phenophase_code,
|
|
|
|
|
+ phenophaseName: item.phenophase_name,
|
|
|
|
|
+ fwExplain: item.fw_explain,
|
|
|
|
|
+ fwAtten: item.fw_atten,
|
|
|
|
|
+ fwParams: item.fw_params,
|
|
|
|
|
+ fwPrecrip: item.fw_precrip,
|
|
|
|
|
+ agronomyType: type,
|
|
|
|
|
+ wkt: hasCoord ? `POINT(${lng + dx} ${lat + dy})` : "",
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 拉取单类农事,失败返回空数组(不阻断另一类) */
|
|
|
|
|
+async function requestAgronomyList(apiFn, params, errorMsg) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await apiFn(params);
|
|
|
|
|
+ if (res?.code != null && res.code !== 200) {
|
|
|
|
|
+ ElMessage.error(res.msg || errorMsg);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ return Array.isArray(res?.data) ? res.data : [];
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ ElMessage.error(errorMsg);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 并行拉取标准农事 + 预警农事 */
|
|
|
|
|
+async function fetchAgronomyTasks(centerWkt) {
|
|
|
|
|
+ const entry = readEntryFarmData();
|
|
|
|
|
+ const cropType = entry?.crop;
|
|
|
|
|
+ const phenophaseCode = entry?.cropCode;
|
|
|
|
|
+ const riskName = entry?.weatherRisk;
|
|
|
|
|
+ if (!cropType || !phenophaseCode) {
|
|
|
|
|
+ taskList.value = buildCompletedNearCenter(centerWkt);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const baseParams = {
|
|
|
|
|
+ crop_type: cropType,
|
|
|
|
|
+ phenophase_code: phenophaseCode,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const standardPromise = requestAgronomyList(
|
|
|
|
|
+ VE_API.questionnaire.standardAgronomy,
|
|
|
|
|
+ baseParams,
|
|
|
|
|
+ "获取标准农事失败"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const warningPromise = riskName
|
|
|
|
|
+ ? requestAgronomyList(
|
|
|
|
|
+ VE_API.questionnaire.warningAgronomy,
|
|
|
|
|
+ { ...baseParams, risk_name: riskName },
|
|
|
|
|
+ "获取预警农事失败"
|
|
|
|
|
+ )
|
|
|
|
|
+ : Promise.resolve([]);
|
|
|
|
|
+
|
|
|
|
|
+ const [standardList, warningList] = await Promise.all([standardPromise, warningPromise]);
|
|
|
|
|
+ const pending = [
|
|
|
|
|
+ ...standardList.map((item, index) => mapAgronomyToTask(item, index, centerWkt, "standard")),
|
|
|
|
|
+ ...warningList.map((item, index) =>
|
|
|
|
|
+ mapAgronomyToTask(item, standardList.length + index, centerWkt, "warning")
|
|
|
|
|
+ ),
|
|
|
|
|
+ ];
|
|
|
|
|
+ taskList.value = [...pending, ...buildCompletedNearCenter(centerWkt)];
|
|
|
|
|
+}
|
|
|
const activeTab = ref("pending");
|
|
const activeTab = ref("pending");
|
|
|
const pendingCount = computed(() => taskList.value.filter((item) => item.status === "pending").length);
|
|
const pendingCount = computed(() => taskList.value.filter((item) => item.status === "pending").length);
|
|
|
const completedCount = computed(() => taskList.value.filter((item) => item.status === "completed").length);
|
|
const completedCount = computed(() => taskList.value.filter((item) => item.status === "completed").length);
|
|
@@ -297,10 +329,6 @@ const handleSurveyConfirm = () => {
|
|
|
|
|
|
|
|
const mapPoint = ref(null);
|
|
const mapPoint = ref(null);
|
|
|
|
|
|
|
|
-const DEFAULT_MAP_POINT = "POINT(113.6142086995688 23.585836479509055)";
|
|
|
|
|
-const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
|
|
|
|
|
-const MAP_POINT_STORAGE_KEY = "GROWTH_REPORT_MAP_POINT";
|
|
|
|
|
-
|
|
|
|
|
const isPointWkt = (value) => typeof value === "string" && /^POINT\s*\(/i.test(value.trim());
|
|
const isPointWkt = (value) => typeof value === "string" && /^POINT\s*\(/i.test(value.trim());
|
|
|
|
|
|
|
|
/** 与长势报告 / 农情档案一致:优先用户选中点位 */
|
|
/** 与长势报告 / 农情档案一致:优先用户选中点位 */
|
|
@@ -337,11 +365,8 @@ const resolveMapPoint = () => {
|
|
|
|
|
|
|
|
const getMapMarkerList = () => displayTaskList.value.filter((item) => item.wkt);
|
|
const getMapMarkerList = () => displayTaskList.value.filter((item) => item.wkt);
|
|
|
|
|
|
|
|
-const initWorkMap = () => {
|
|
|
|
|
|
|
+const refreshMapMarkers = () => {
|
|
|
if (!mapContainer.value) return;
|
|
if (!mapContainer.value) return;
|
|
|
- mapPoint.value = resolveMapPoint();
|
|
|
|
|
- // 假数据点位落在用户选中中心附近
|
|
|
|
|
- taskList.value = buildTaskListNearCenter(mapPoint.value);
|
|
|
|
|
if (!indexMap.kmap) {
|
|
if (!indexMap.kmap) {
|
|
|
indexMap.initMap(mapPoint.value, mapContainer.value, true);
|
|
indexMap.initMap(mapPoint.value, mapContainer.value, true);
|
|
|
} else {
|
|
} else {
|
|
@@ -352,39 +377,32 @@ const initWorkMap = () => {
|
|
|
indexMap.setMapPosition(mapPoint.value);
|
|
indexMap.setMapPosition(mapPoint.value);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- initWorkMap();
|
|
|
|
|
- });
|
|
|
|
|
-});
|
|
|
|
|
|
|
+/** keep-alive 下仅在 onActivated 拉取,避免与 onMounted 重复请求 */
|
|
|
|
|
+const loadWorkPage = async () => {
|
|
|
|
|
+ if (!mapContainer.value) return;
|
|
|
|
|
+ mapPoint.value = resolveMapPoint();
|
|
|
|
|
+ await fetchAgronomyTasks(mapPoint.value);
|
|
|
|
|
+ refreshMapMarkers();
|
|
|
|
|
+ if (mapContainer.value && indexMap.kmap?.map) {
|
|
|
|
|
+ const checkAndUpdateSize = () => {
|
|
|
|
|
+ const container = mapContainer.value;
|
|
|
|
|
+ if (!container) return;
|
|
|
|
|
+ const rect = container.getBoundingClientRect();
|
|
|
|
|
+ if (rect.width > 0 && rect.height > 0) {
|
|
|
|
|
+ indexMap.kmap.map.updateSize();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setTimeout(checkAndUpdateSize, 100);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ setTimeout(checkAndUpdateSize, 200);
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
onActivated(() => {
|
|
onActivated(() => {
|
|
|
nextTick(() => {
|
|
nextTick(() => {
|
|
|
- if (!indexMap.kmap) {
|
|
|
|
|
- initWorkMap();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- mapPoint.value = resolveMapPoint();
|
|
|
|
|
- taskList.value = buildTaskListNearCenter(mapPoint.value);
|
|
|
|
|
- indexMap.setMapPosition(mapPoint.value);
|
|
|
|
|
- indexMap.initData(getMapMarkerList(), "farmName", "wkt");
|
|
|
|
|
- indexMap.setMapPosition(mapPoint.value);
|
|
|
|
|
- if (mapContainer.value && indexMap.kmap.map) {
|
|
|
|
|
- const checkAndUpdateSize = () => {
|
|
|
|
|
- const container = mapContainer.value;
|
|
|
|
|
- if (!container) return;
|
|
|
|
|
- const rect = container.getBoundingClientRect();
|
|
|
|
|
- if (rect.width > 0 && rect.height > 0) {
|
|
|
|
|
- indexMap.kmap.map.updateSize();
|
|
|
|
|
- } else {
|
|
|
|
|
- setTimeout(checkAndUpdateSize, 100);
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
- setTimeout(checkAndUpdateSize, 200);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ loadWorkPage();
|
|
|
});
|
|
});
|
|
|
-});
|
|
|
|
|
-</script>
|
|
|
|
|
|
|
+});</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
.task-page {
|
|
.task-page {
|