| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033 |
- <template>
- <div class="task-page" v-loading="pageLoading" element-loading-background="rgba(15, 15, 15, 0.55)"
- :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
- <farm-header class="task-page__header" />
- <div class="task-top">
- <div class="map-container" ref="mapContainer"></div>
- </div>
- <div class="calendar-container">
- <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
- </div>
- <div class="task-list">
- <div class="task-toolbar">
- <div class="task-tabs">
- <div class="tab-item" :class="{ active: activeTab === 'pending' }"
- @click="handleTabChange('pending')">
- 待执行 {{ pendingCount }}
- </div>
- <div class="tab-item" :class="{ active: activeTab === 'completed' }"
- @click="handleTabChange('completed')">
- 已完成 {{ completedCount }}
- </div>
- </div>
- <div class="task-sort" :class="{ active: sortByUrgency }" @click="sortByUrgency = !sortByUrgency">
- <span class="task-sort__icon">
- <i /><i /><i />
- </span>
- <span>按紧急程度</span>
- </div>
- </div>
- <div v-if="zoneList.length" class="zone-chips">
- <div v-for="zone in zoneList" :key="zone.id" class="zone-chip"
- :class="{ active: String(activeZoneId) === String(zone.id) }" @click="handleZoneChange(zone.id)">
- {{ zone.name || "分区" }}
- </div>
- </div>
- <div class="work-task-list">
- <div v-for="item in displayTaskList" :key="item.id" class="task-card" :class="{
- 'is-risk': item.isHighRisk,
- 'is-weather': item.isWeather,
- 'is-abnormal': item.isAbnormal,
- 'is-normal': item.isNormal,
- }" @click.stop="handleViewDetail(item)">
- <div class="task-card__content">
- <div v-if="item.isHighRisk" class="task-card__risk">
- {{ t("workExecute.highRisk") }}
- </div>
- <div class="task-card__header">
- <span class="task-card__title van-ellipsis">
- {{ item.executeDate + " " + item.title }}
- </span>
- <span class="task-card__tag">高风险</span>
- </div>
- <div class="task-card__tags">
- <span v-if="item.isNormal" class="badge-tag normal-badge">标准类</span>
- <span v-if="item.isAbnormal" class="badge-tag abnormal-badge">异常类</span>
- <span v-if="item.isWeather" class="badge-tag weather-badge">气象类</span>
- <span v-if="item.status === 'pending' && item.cornerTip" class="badge-tag status-badge">
- {{ item.cornerTip }}
- </span>
- <span v-if="item.status === 'completed' && item.cornerTip"
- class="badge-tag status-badge is-done" :class="{ 'is-myself': item.isByMyself }">
- {{ item.cornerTip }}
- </span>
- </div>
- <div class="task-card__body">
- <div class="info-row">
- <span class="info-row__label">农情研判</span>
- <span class="info-row__value">{{ item.analysisSummary || "--" }}</span>
- </div>
- <div class="info-row">
- <span class="info-row__label">执行区域</span>
- <span class="info-row__value">{{ item.areaCode || "--" }}</span>
- </div>
- <div v-if="item.fw_trigger" class="info-row">
- <span class="info-row__label">触发条件</span>
- <span class="info-row__value">{{ item.fw_trigger }}</span>
- </div>
- </div>
- <div class="task-card__footer">
- <div class="footer-l">
- <img src="@/assets/img/farm/farm.png" alt="外包农事" class="outsource-icon">
- 外包农事
- </div>
- <div class="footer-r">
- <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
- <Icon size="14" color="rgba(255, 255, 255, 0.6)" name="share" />
- 派发工人
- </div>
- <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
- <img src="@/assets/img/farm/shield.png" alt="我已完成" class="complete-icon">
- 我已完成
- </div>
- </div>
- </div>
- </div>
- </div>
- <div v-if="!displayTaskList.length" class="empty-tip">{{ t("workExecute.noData") }}</div>
- </div>
- </div>
- <farm-calendar-popup v-model:show="showFarmCalendarPopup" @confirm="goCompleteFarmInfo" />
- <survey-ask-popup v-model:show="showSurveyAskPopup" @confirm="handleSurveyConfirm" />
- <!-- 诊断报告弹窗:组件内接口判断 exists=2 且本地未展示过 -->
- <diagnosis-report-popup />
- <!-- 恢复种植弹窗 -->
- <restore-planting-popup />
- </div>
- </template>
- <script setup>
- import { computed, nextTick, onActivated, onDeactivated, onMounted, ref } from "vue";
- import { useStore } from "vuex";
- import { useRouter } from "vue-router";
- import IndexMap from "./index";
- import customCalendar from "./components/calendar.vue";
- import farmHeader from "@/components/farmHeader.vue";
- import farmCalendarPopup from "@/components/popup/farmCalendarPopup.vue";
- import surveyAskPopup from "@/components/popup/surveyAskPopup.vue";
- import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
- import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
- import { useI18n } from "@/i18n";
- import { saveSelectedFarm } from "@/utils/selectedFarm";
- import wx from "weixin-js-sdk";
- import { Icon } from 'vant';
- // ---------- 常量 ----------
- /** 任务类型:fw_tasks 标准农事 / weather_work 气象农事 / abnormal_fw_tasks 异常农事 */
- const TASK_KIND = {
- NORMAL: "normal",
- WEATHER: "weather",
- ABNORMAL: "abnormal",
- };
- /** 方案文案优先取人工,接口里标准任务是字符串,气象/异常是按方案拆开的对象 */
- const SCHEME_TEXT_KEYS = ["人工方案", "机械方案", "人工", "机械"];
- /** 列表进详情:整条任务缓存,详情页不再打 fw_task_details */
- const FW_TASK_CACHE_KEY = "WORK_EXECUTE_FW_TASK_DETAIL";
- const COMPLETED_STATUS_RE = /已完成|已执行|已认证|本人完成/;
- const { t } = useI18n();
- const store = useStore();
- const router = useRouter();
- const indexMap = new IndexMap();
- const mapContainer = ref(null);
- const calendarRef = ref(null);
- const showFarmCalendarPopup = ref(false);
- /** 调查问卷弹窗:后续由接口决定是否展示 */
- const showSurveyAskPopup = ref(false);
- const tabBarHeight = computed(() => store.state.home.tabBarHeight);
- // ---------- 工具函数 ----------
- function getMiniUserId() {
- return store.state.home.miniUserId || localStorage.getItem("MINI_USER_ID");
- }
- function getTodayDate() {
- const now = new Date();
- const y = now.getFullYear();
- const m = String(now.getMonth() + 1).padStart(2, "0");
- const d = String(now.getDate()).padStart(2, "0");
- return `${y}-${m}-${d}`;
- }
- function getSelectedFarm() {
- try {
- return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
- } catch {
- return {};
- }
- }
- function getSelectedFarmId() {
- const stored = localStorage.getItem("selectedFarmId");
- if (stored != null && stored !== "") return Number(stored);
- const farm = getSelectedFarm();
- const id = farm.farm_id ?? farm.id;
- return id != null && id !== "" ? Number(id) : null;
- }
- function getSelectedZoneId() {
- const farm = getSelectedFarm();
- const id = farm.zone_id ?? farm.zoneId;
- return id != null && id !== "" ? Number(id) : null;
- }
- /** crop_type 取作物编码,如 LCH;各入口字段不统一 */
- function getSelectedCropType() {
- const farm = getSelectedFarm();
- return (
- farm.category_code ||
- farm.crop_code ||
- farm.crop_type ||
- farm.farm_category ||
- farm.farm_variety ||
- ""
- );
- }
- // ---------- 页面状态 ----------
- const filterDate = ref(getTodayDate()); // 当前选中日期
- const taskList = ref([]); // 已映射的农事卡片(归属接口 date)
- const mapCenterPoint = ref(null); // 接口返回的分区中心点 WKT
- const activeTab = ref("pending");
- const pageLoading = ref(false);
- const zoneList = ref([]);
- const activeZoneId = ref(null);
- /** 按紧急程度:高风险优先 */
- const sortByUrgency = ref(false);
- // ---------- 计算属性 ----------
- const dayTaskList = computed(() =>
- taskList.value.filter((item) => {
- if (item.executeDateFull !== filterDate.value) return false;
- if (activeZoneId.value == null || activeZoneId.value === "") return true;
- return String(item.zoneId) === String(activeZoneId.value);
- })
- );
- const pendingCount = computed(() => dayTaskList.value.filter((item) => item.status === "pending").length);
- const completedCount = computed(() => dayTaskList.value.filter((item) => item.status === "completed").length);
- const displayTaskList = computed(() => {
- const list = dayTaskList.value.filter((item) => item.status === activeTab.value);
- if (!sortByUrgency.value) return list;
- return [...list].sort((a, b) => Number(!!b.isHighRisk) - Number(!!a.isHighRisk));
- });
- /** 日历窗口:今天起连续 7 天 */
- function getCalendarDateRange() {
- const start = new Date();
- return Array.from({ length: 7 }, (_, i) => {
- const d = new Date(start);
- d.setDate(start.getDate() + i);
- const y = d.getFullYear();
- const m = String(d.getMonth() + 1).padStart(2, "0");
- const day = String(d.getDate()).padStart(2, "0");
- return `${y}-${m}-${day}`;
- });
- }
- /** zone_id 优先用分区筛选;否则退回农场缓存分区 */
- const resolveQueryParams = (triggerDate = getTodayDate()) => {
- const params = {
- zone_id: activeZoneId.value ?? getSelectedZoneId(),
- farm_id: getSelectedFarmId(),
- trigger_date: triggerDate,
- };
- const cropType = getSelectedCropType();
- if (cropType) params.crop_type = cropType;
- return params;
- };
- async function fetchZoneList() {
- const farmId = getSelectedFarmId();
- if (farmId == null) {
- zoneList.value = [];
- activeZoneId.value = null;
- return;
- }
- try {
- const res = await VE_API.questionnaire.getZones({ farm_id: farmId });
- const zones = Array.isArray(res?.data?.zones) ? res.data.zones : [];
- zoneList.value = zones
- .map((zone) => ({
- id: zone.zone_id,
- name: zone.zone_name || `分区${zone.zone_id ?? ""}`,
- }))
- .filter((zone) => zone.id != null && zone.id !== "");
- const cachedZoneId = getSelectedZoneId();
- const stillExists = zoneList.value.some(
- (zone) => String(zone.id) === String(activeZoneId.value)
- );
- if (stillExists) return;
- const preferred = zoneList.value.find(
- (zone) => String(zone.id) === String(cachedZoneId)
- );
- activeZoneId.value = preferred?.id ?? zoneList.value[0]?.id ?? null;
- } catch (e) {
- console.warn("[work_execute] getZones failed", e);
- zoneList.value = [];
- activeZoneId.value = getSelectedZoneId();
- }
- }
- const isCompletedStatus = (status) => COMPLETED_STATUS_RE.test(String(status || ""));
- const formatExecuteDate = (dateStr) => {
- if (!dateStr) return "";
- return String(dateStr).replace(/^\d{4}-(\d{2})-(\d{2})$/, "$1/$2");
- };
- const buildPointWkt = (lng, lat, index = 0) => {
- if (lng == null || lat == null || Number.isNaN(Number(lng)) || Number.isNaN(Number(lat))) return "";
- const offset = (index % 6) * 0.00025;
- return `POINT(${Number(lng) + offset} ${Number(lat) + offset * 0.6})`;
- };
- /** 名称/说明可能是字符串,也可能是 { 人工方案, 机械方案 } / { 人工, 机械 } */
- const pickSchemeText = (value) => {
- if (value == null || value === "") return "";
- if (typeof value === "string" || typeof value === "number") return String(value);
- if (typeof value !== "object") return "";
- for (const key of SCHEME_TEXT_KEYS) {
- if (value[key]) return String(value[key]);
- }
- const first = Object.values(value).find((item) => item != null && item !== "");
- return first != null ? String(first) : "";
- };
- /** 单条任务映射为卡片;kind 区分标准 / 气象 / 异常 */
- const mapTaskItem = (task, index, { dateStr, lat, lng, zoneId, kind }) => {
- const executeDateFull = task.date || dateStr;
- const isWeather = kind === TASK_KIND.WEATHER;
- const isAbnormal = kind === TASK_KIND.ABNORMAL;
- const statusText = pickSchemeText(task.fw_status || task.status_text);
- const completed = isCompletedStatus(statusText);
- const prefix = isAbnormal ? "abnormal" : isWeather ? "weather" : "fw";
- const title = pickSchemeText(task.fw_name);
- const trigger = pickSchemeText(task.fw_trigger) || (isAbnormal ? task.abnormal_type || "" : "");
- return {
- id: `${prefix}_${task.manual_id || task.mechanical_id || pickSchemeText(task.fw_code) || index}_${executeDateFull}_${index}`,
- status: completed ? "completed" : "pending",
- title,
- isHighRisk: false,
- isNormal: kind === TASK_KIND.NORMAL,
- isAbnormal,
- isWeather,
- cornerTip: statusText || (completed ? "已完成" : "待执行"),
- analysisSummary:
- pickSchemeText(task.fw_explain_simple) ||
- pickSchemeText(task.fw_simple_explain) ||
- pickSchemeText(task.fw_explain),
- fw_trigger: trigger,
- areaCode: zoneId ? `分区 ${zoneId}` : "",
- farmName: title,
- executeDate: formatExecuteDate(executeDateFull),
- executeDateFull,
- zoneId,
- manualId: task.manual_id,
- mechanicalId: task.mechanical_id,
- latitude: lat,
- longitude: lng,
- wkt: buildPointWkt(lng, lat, index),
- // 详情页直接消费原始列表项,避免再请求 fw_task_details
- rawTask: task,
- };
- };
- /** 合并三类农事;无单条日期时归到本次请求日 */
- const mapZoneTaskList = (data, requestDate) => {
- const dateStr = requestDate || resolveRangeStart(data);
- const lat = data.latitude ?? 22.970229;
- const lng = data.longitude ?? 113.746646;
- const zoneId = data.zone_id ?? getSelectedZoneId();
- const ctx = { dateStr, lat, lng, zoneId };
- const mapList = (raw, kind, offset) =>
- (Array.isArray(raw) ? raw : [])
- .filter(Boolean)
- .map((task, index) => mapTaskItem(task, offset + index, { ...ctx, kind }));
- const normalList = mapList(data.fw_tasks, TASK_KIND.NORMAL, 0);
- const weatherList = mapList(data.weather_work, TASK_KIND.WEATHER, normalList.length);
- const abnormalList = mapList(
- data.abnormal_fw_tasks,
- TASK_KIND.ABNORMAL,
- normalList.length + weatherList.length
- );
- return { list: [...normalList, ...weatherList, ...abnormalList], lat, lng, dateStr, zoneId };
- };
- const resolveRangeStart = (data) => {
- if (data?.start_date) return data.start_date;
- if (data?.date) return data.date;
- return getTodayDate();
- };
- // ---------- 业务逻辑 ----------
- /** 只同步选中态和农事角标,不改日历起始日(窗口固定为今天起 7 天) */
- const syncCalendar = () => {
- nextTick(() => {
- calendarRef.value?.selectDateByValue?.(filterDate.value);
- calendarRef.value?.setSolarTerm?.(taskList.value);
- });
- };
- /** 无果园信息时展示「完善农场信息 / 专属农事日历」弹窗 */
- async function checkFarmCalendarPopup() {
- const id = getMiniUserId();
- if (!id) {
- showFarmCalendarPopup.value = false;
- return;
- }
- try {
- const res = await VE_API.questionnaire.getFarms({ id });
- const list = Array.isArray(res?.data) ? res.data : [];
- showFarmCalendarPopup.value = list.length === 0;
- } catch (e) {
- console.warn("[work_execute] getFarms failed", e);
- showFarmCalendarPopup.value = false;
- }
- }
- /** 无选中农场时写入列表首项,保证 zone_fw_task_list 有 farm_id */
- async function ensureSelectedFarm() {
- if (getSelectedFarmId() != null) return true;
- const id = getMiniUserId();
- if (!id) return false;
- try {
- const res = await VE_API.questionnaire.getFarms({ id });
- const list = Array.isArray(res?.data) ? res.data : [];
- return list[0] ? saveSelectedFarm(list[0]) : false;
- } catch (e) {
- console.warn("[work_execute] ensureSelectedFarm failed", e);
- return false;
- }
- }
- const refreshMapMarkers = () => {
- if (!indexMap.kmap) return;
- indexMap.initData(getMapMarkerList(), "farmName", "wkt");
- };
- /** 与日历一致:展示 7 天窗口内全部待执行农事(列表仍按选中日过滤) */
- const getMapMarkerList = () =>
- taskList.value.filter((item) => item.status === "pending" && item.wkt);
- /**
- * 按日历 7 天逐日请求后合并(接口按 trigger_date 返回当日任务)。
- * 切换日期只本地过滤列表,地图仍用完整 7 天点位。
- */
- const fetchZoneFwTaskList = async () => {
- try {
- const dates = getCalendarDateRange();
- const settled = await Promise.all(
- dates.map((triggerDate) =>
- VE_API.questionnaire
- .getZoneFwTaskList(resolveQueryParams(triggerDate))
- .then((res) => ({ triggerDate, data: res?.data || null }))
- .catch((e) => {
- console.warn("[work_execute] zone_fw_task_list failed", triggerDate, e);
- return { triggerDate, data: null };
- })
- )
- );
- const merged = [];
- const seen = new Set();
- let lat = null;
- let lng = null;
- for (const { triggerDate, data } of settled) {
- if (!data) continue;
- const mapped = mapZoneTaskList(data, triggerDate);
- if (lat == null && mapped.lat != null) lat = mapped.lat;
- if (lng == null && mapped.lng != null) lng = mapped.lng;
- for (const item of mapped.list) {
- if (seen.has(item.id)) continue;
- seen.add(item.id);
- merged.push(item);
- }
- }
- mapCenterPoint.value =
- lat != null && lng != null ? `POINT(${lng} ${lat})` : null;
- taskList.value = merged;
- if (!merged.length) {
- calendarRef.value?.selectDateByValue?.(filterDate.value);
- calendarRef.value?.setSolarTerm?.([]);
- } else {
- syncCalendar();
- }
- nextTick(() => refreshMapMarkers());
- } catch (e) {
- console.error("获取农事任务失败", e);
- taskList.value = [];
- refreshMapMarkers();
- }
- };
- // ---------- 事件处理 ----------
- const handleTabChange = (key) => {
- if (activeTab.value === key) return;
- activeTab.value = key;
- };
- const handleZoneChange = async (zoneId) => {
- if (String(activeZoneId.value) === String(zoneId)) return;
- activeZoneId.value = zoneId;
- pageLoading.value = true;
- try {
- await fetchZoneFwTaskList();
- nextTick(() => refreshMapMarkers());
- } finally {
- pageLoading.value = false;
- }
- };
- /** 切日期:只过滤下方列表;地图与日历角标共用完整 7 天数据 */
- const handleDateSelect = (date) => {
- const next = date ?? getTodayDate();
- if (filterDate.value === next) return;
- filterDate.value = next;
- };
- const handleForward = (item) => {
- const workName = item?.title || item?.farmName || "农事详情";
- const query = {
- askInfo: { title: "转发农事", content: "是否分享给好友" },
- shareText: `${workName} 已触发,请在最佳农时内及时执行`,
- targetUrl: "work_detail",
- paramsPage: JSON.stringify({
- ...buildFwTaskQuery(item),
- fromShare: 1,
- }),
- imageUrl: "https://birdseye-img.sysuimars.com/birdseye-look-mini/work_img.png",
- };
- wx.miniProgram.navigateTo({
- url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
- });
- };
- const buildFwTaskQuery = (item) => ({
- zone_id: item?.zoneId ?? getSelectedZoneId(),
- date: item?.executeDateFull || getTodayDate(),
- manual_id: item?.manualId,
- mechanical_id: item?.mechanicalId,
- statusVal: item?.status,
- lat: item?.latitude,
- lng: item?.longitude,
- fw_name: item?.title || item?.farmName || "",
- isNormal: item?.isNormal ? 1 : 0,
- isAbnormal: item?.isAbnormal ? 1 : 0,
- isWeather: item?.isWeather ? 1 : 0,
- isHighRisk: item?.isHighRisk ? 1 : 0,
- fromZoneList: 1,
- });
- /** 写入详情页缓存:原始接口字段 + 列表侧元信息 */
- const saveFwTaskCache = (item) => {
- if (!item?.rawTask) return;
- sessionStorage.setItem(
- FW_TASK_CACHE_KEY,
- JSON.stringify({
- raw: item.rawTask,
- meta: {
- zone_id: item.zoneId ?? getSelectedZoneId(),
- date: item.executeDateFull || getTodayDate(),
- lat: item.latitude,
- lng: item.longitude,
- statusVal: item.status,
- statusText: item.cornerTip || "",
- fw_name: item.title || item.farmName || "",
- isNormal: item.isNormal ? 1 : 0,
- isAbnormal: item.isAbnormal ? 1 : 0,
- isWeather: item.isWeather ? 1 : 0,
- isHighRisk: item.isHighRisk ? 1 : 0,
- },
- })
- );
- };
- const handleViewDetail = (item) => {
- saveFwTaskCache(item);
- router.push({
- path: "/work_detail",
- query: buildFwTaskQuery(item),
- });
- };
- const goCompleteFarmInfo = () => {
- router.push("/entry_information");
- };
- const handleSurveyConfirm = () => {
- showSurveyAskPopup.value = false;
- router.push("/survey_entry");
- };
- /** 当前选中农场的 farm_location(POINT) */
- function resolveSelectedFarmLocationPoint() {
- const farm = getSelectedFarm();
- const candidates = [
- farm.farm_location,
- farm.wkt,
- farm.geom_wkt,
- localStorage.getItem("selectedFarmPoint"),
- ];
- for (const item of candidates) {
- if (typeof item === "string" && /^POINT\s*\(/i.test(item.trim())) {
- return item.trim();
- }
- }
- return "";
- }
- const initMap = () => {
- if (!mapContainer.value) return;
- // 中心点:接口分区中心 → 选中农场 farm_location → 登录定位 → 兜底
- const fallback = store.state.home.miniUserLocationPoint || "POINT(113.746646 22.970229)";
- const point = mapCenterPoint.value || resolveSelectedFarmLocationPoint() || fallback;
- if (!indexMap.kmap) {
- indexMap.initMap(point, mapContainer.value, true);
- }
- refreshMapMarkers();
- };
- /** keep-alive 下切农场 / 再次进入只走 onActivated,需在此拉列表 */
- const refreshPage = async () => {
- if (pageLoading.value) return;
- pageLoading.value = true;
- try {
- await ensureSelectedFarm();
- // 无农场时不打任务列表(弹窗由 checkFarmCalendarPopup 负责)
- if (getSelectedFarmId() == null) {
- taskList.value = [];
- zoneList.value = [];
- activeZoneId.value = null;
- if (!indexMap.kmap) initMap();
- return;
- }
- await fetchZoneList();
- await fetchZoneFwTaskList();
- if (!indexMap.kmap) {
- initMap();
- return;
- }
- if (!mapContainer.value || !indexMap.kmap.map) return;
- const checkAndUpdateSize = () => {
- const container = mapContainer.value;
- if (!container) return;
- const rect = container.getBoundingClientRect();
- if (rect.width > 0 && rect.height > 0) {
- indexMap.kmap.map.updateSize();
- refreshMapMarkers();
- } else {
- setTimeout(checkAndUpdateSize, 100);
- }
- };
- setTimeout(checkAndUpdateSize, 200);
- } finally {
- pageLoading.value = false;
- }
- };
- onMounted(() => {
- checkFarmCalendarPopup();
- nextTick(refreshPage);
- });
- onActivated(() => {
- checkFarmCalendarPopup();
- nextTick(refreshPage);
- });
- onDeactivated(() => {
- showFarmCalendarPopup.value = false;
- });
- </script>
- <style lang="scss" scoped>
- .task-page {
- width: 100%;
- height: calc(100vh - 50px);
- overflow: auto;
- box-sizing: border-box;
- background: url('@/assets/img/agricultural/page-bg.png') no-repeat top center;
- background-size: 100% 250px;
- ::v-deep {
- .el-loading-spinner .path {
- stroke: rgba(255, 212, 137, 0.8);
- }
- }
- .map-container {
- width: 100%;
- height: 162px;
- border-radius: 12px;
- overflow: hidden;
- background: #1a1a1a;
- }
- .calendar-container {
- padding: 10px 12px 8px;
- }
- .task-top {
- padding: 10px 12px 0;
- }
- .task-list {
- padding: 0 12px 20px;
- }
- .task-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin: 4px 0 10px;
- }
- .task-tabs {
- display: inline-flex;
- align-items: center;
- gap: 0;
- padding: 4px;
- border-radius: 6px;
- background: rgba(255, 255, 255, 0.05);
- .tab-item {
- min-width: 82px;
- height: 30px;
- padding: 0 14px;
- border-radius: 6px;
- color: rgba(255, 255, 255, 0.5);
- font-size: 14px;
- line-height: 30px;
- text-align: center;
- cursor: pointer;
- box-sizing: border-box;
- &.active {
- color: #fff;
- background: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.12);
- }
- }
- }
- .task-sort {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- flex-shrink: 0;
- font-size: 12px;
- line-height: 18px;
- color: rgba(255, 255, 255, 0.45);
- cursor: pointer;
- &.active {
- color: #ffd489;
- }
- &__icon {
- display: inline-flex;
- flex-direction: column;
- justify-content: center;
- gap: 2px;
- width: 12px;
- i {
- display: block;
- height: 1.5px;
- border-radius: 1px;
- background: currentColor;
- &:nth-child(1) {
- width: 100%;
- }
- &:nth-child(2) {
- width: 75%;
- }
- &:nth-child(3) {
- width: 50%;
- }
- }
- }
- }
- .zone-chips {
- display: flex;
- gap: 8px;
- overflow-x: auto;
- margin-bottom: 14px;
- padding-bottom: 2px;
- -webkit-overflow-scrolling: touch;
- &::-webkit-scrollbar {
- display: none;
- }
- }
- .zone-chip {
- flex-shrink: 0;
- height: 28px;
- padding: 0 12px;
- border-radius: 4px;
- font-size: 12px;
- line-height: 28px;
- color: rgba(255, 255, 255, 0.6);
- background: rgba(255, 255, 255, 0.05);
- backdrop-filter: blur(4px);
- border: 1px solid transparent;
- box-sizing: border-box;
- cursor: pointer;
- &.active {
- background: rgba(255, 255, 255, 0.1);
- border-color: rgba(255, 255, 255, 0.1);
- }
- }
- .work-task-list {
- display: flex;
- flex-direction: column;
- gap: 16px;
- }
- .task-card {
- position: relative;
- box-sizing: border-box;
- overflow: hidden;
- width: 100%;
- padding: 10px;
- border-radius: 12px;
- background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
- box-shadow: inset 1px 0 0 #FFD489;
- &::after {
- content: "";
- position: absolute;
- right: 0;
- top: 0;
- width: 110px;
- height: 42px;
- border-radius: 0 12px 0 0;
- background: url('@/assets/img/agricultural/card-bg.png') no-repeat bottom left / 100% 100%;
- }
- &.is-weather {
- box-shadow: inset 1px 0 0 #2199F8;
- }
- &.is-abnormal,
- &.is-risk {
- box-shadow: inset 1px 0 0 #FF6A6A;
- }
- &__risk {
- position: absolute;
- top: 12px;
- right: 12px;
- height: 22px;
- padding: 0 8px;
- border-radius: 6px;
- background: #c93c3c;
- color: #fff;
- font-size: 12px;
- line-height: 22px;
- }
- &__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- min-width: 0;
- .task-card__tag {
- height: 20px;
- padding: 0 7px;
- border: 1px solid rgba(208, 49, 46, 0.4);
- background: #822422;
- border-radius: 4px;
- font-size: 12px;
- line-height: 20px;
- box-sizing: border-box;
- color: #fff;
- }
- }
- &__title {
- font-size: 16px;
- font-weight: 500;
- line-height: 22px;
- color: #fff;
- }
- &__tags {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- margin-top: 8px;
- }
- .badge-tag {
- flex-shrink: 0;
- height: 20px;
- padding: 0 7px;
- border-radius: 4px;
- font-size: 12px;
- line-height: 20px;
- box-sizing: border-box;
- border: 1px solid transparent;
- }
- .normal-badge {
- color: #FFD489;
- border-color: rgba(255, 212, 137, 0.12);
- background: rgba(255, 212, 137, 0.05);
- }
- .abnormal-badge {
- color: #FF6A6A;
- background: rgba(255, 106, 106, 0.05);
- border-color: rgba(255, 106, 106, 0.12);
- }
- .weather-badge {
- color: #2199F8;
- background: rgba(33, 153, 248, 0.05);
- border-color: rgba(33, 153, 248, 0.12);
- }
- .status-badge {
- color: #1a1408;
- background: #ffd489;
- border-color: #ffd489;
- &.is-done {
- color: rgba(255, 255, 255, 0.65);
- background: rgba(255, 255, 255, 0.1);
- border-color: transparent;
- }
- &.is-myself {
- color: #7dff9a;
- background: rgba(74, 191, 50, 0.16);
- }
- }
- &__body {
- margin-top: 12px;
- }
- .info-row {
- display: flex;
- align-items: flex-start;
- gap: 12px;
- font-size: 13px;
- line-height: 20px;
- &+.info-row {
- margin-top: 8px;
- }
- &__label {
- flex-shrink: 0;
- font-size: 12px;
- color: rgba(255, 255, 255, 0.5);
- }
- &__value {
- flex: 1;
- min-width: 0;
- color: rgba(255, 255, 255, 0.9);
- }
- }
- &__footer {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-top: 8px;
- padding-top: 8px;
- border-top: 1px solid rgba(255, 255, 255, 0.05);
- .footer-l {
- font-size: 12px;
- color: rgba(255, 255, 255, 0.5);
- display: flex;
- align-items: center;
- gap: 6px;
- .outsource-icon {
- width: 11px;
- }
- }
- .footer-r {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- }
- .card-btn {
- height: 30px;
- border-radius: 6px;
- text-align: center;
- line-height: 30px;
- font-size: 12px;
- box-sizing: border-box;
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 0 10px;
- .complete-icon {
- width: 14px;
- }
- &--ghost {
- color: rgba(255, 255, 255, 0.6);
- background: rgba(255, 255, 255, 0.05);
- }
- &--primary {
- color: #FFD489;
- background: rgba(255, 212, 137, 0.1);
- }
- }
- &.is-abnormal .card-btn--primary,
- &.is-risk .card-btn--primary {
- color: #ffd489;
- background: transparent;
- border: 1px solid rgba(255, 212, 137, 0.45);
- }
- }
- .empty-tip {
- padding: 40px 0;
- text-align: center;
- color: rgba(255, 255, 255, 0.35);
- font-size: 14px;
- }
- }
- </style>
|