| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- <template>
- <div class="patrol-panel">
- <!-- 品种切换 -->
- <div class="variety-bar">
- <div class="variety-bar__tags">
- <div
- v-for="item in zoneList"
- :key="item.zone_id"
- class="variety-bar__tag"
- :class="{ active: item.zone_id === selectedZoneId }"
- @click="handleSelectZone(item)"
- >
- {{ item.zone_name }}
- </div>
- </div>
- <div class="variety-bar__add" @click="goAddVariety">
- <el-icon>
- <Plus />
- </el-icon>
- <span>新增品种</span>
- </div>
- </div>
- <div
- v-for="item in patrolList"
- :key="item.id"
- class="tracking-item"
- :class="`tracking-item--${item.theme}`"
- @click="goGrowthTrack(item)"
- >
- <div class="tracking-item__header">
- <div class="tracking-item__header-row">
- <div class="tracking-item__header-left">
- <span class="tracking-item__level">
- <el-icon><BellFilled /></el-icon>
- {{ item.level }}
- </span>
- <span class="tracking-item__title van-ellipsis">
- {{ item.title }}
- <span v-if="item.zone" class="tracking-item__zone"> ({{ item.zone }})</span>
- </span>
- </div>
- <div class="tracking-item__share" @click.stop="handleShare(item)">
- <Icon size="14" color="#2199F8" name="share" />
- <span>{{ t("agriFile.forward") }}</span>
- </div>
- </div>
- </div>
- <div class="tracking-item__body">
- <div class="tracking-item__action">
- <div class="tracking-item__action-main">
- <div class="tracking-item__icon">
- <img :src="item.icon" alt="" />
- <span class="tracking-item__reason">{{ item.subject }}</span>
- </div>
- <div class="tracking-item__issue">{{ item.issue }}</div>
- </div>
- <div class="tracking-item__btn" @click.stop="handleUploadClick(item)">
- <el-icon>
- <Plus />
- </el-icon>
- <span>{{ t("agriFile.uploadPhoto") }}</span>
- </div>
- </div>
- </div>
- </div>
- <div class="crop-archive">
- <div class="crop-archive__title">{{ t("agriFile.cropArchive") }}</div>
- <PhenologyTrackTimelineItem :list="cropArchiveList" />
- </div>
- <album-upload-popup v-model:show="showUploadPopup" @confirm="handleUploadConfirm" />
- <tip-popup v-model:show="showSuccessPopup" type="executeSuccess" text="您已上传成功" text2="请等待诊断报告生成" hideBtn>
- <template #footer>
- <div class="bottom-btn-container">
- <div class="bottom-btn primary-btn" @click="handleComplete">完成</div>
- </div>
- </template>
- </tip-popup>
- </div>
- </template>
- <script setup>
- import { onActivated, onMounted, ref } from "vue";
- import { useRouter } from "vue-router";
- import { Icon } from "vant";
- import { ElMessage } from "element-plus";
- import { Plus } from "@element-plus/icons-vue";
- import { useI18n } from "@/i18n";
- import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
- import albumUploadPopup from "./albumUploadPopup.vue";
- import tipPopup from "@/components/popup/tipPopup.vue";
- import wx from "weixin-js-sdk";
- const { t } = useI18n();
- const router = useRouter();
- const PATROL_ICON = require("@/assets/img/report/wh-icon.png");
- // ---------- 页面状态 ----------
- const zoneList = ref([]);
- const selectedZoneId = ref(null);
- const selectedZoneName = ref("");
- const patrolList = ref([
- {
- id: 1,
- type: "growth",
- theme: "blue",
- level: "",
- title: "",
- subject: "",
- issue: "",
- desc: "",
- icon: PATROL_ICON,
- },
- {
- id: 2,
- type: "abnormal",
- theme: "blue",
- level: "",
- title: "",
- subject: "",
- issue: "",
- icon: PATROL_ICON,
- },
- ]);
- const cropArchiveList = ref([]);
- const showUploadPopup = ref(false);
- const showSuccessPopup = ref(false);
- const uploadZoneId = ref("");
- // ---------- 工具函数 ----------
- /** 外部感知任务 → 巡园卡片(仅取第一条) */
- const mapInteractTaskToPatrol = (task) => {
- const content = task?.interact_content || {};
- return {
- id: task.zone_id || task.interact_code,
- type: "growth",
- theme: "blue",
- level: content.interact_level || "",
- title: t("agriFile.patrolGrowthTitle") || "",
- desc: content.interact_reason || "",
- issue: content.interact_reason || "",
- subject: content.interact_title || "",
- icon: PATROL_ICON,
- zoneId: task.zone_id,
- interactCode: task.interact_code,
- raw: task,
- };
- };
- const mapPhenoRecordToArchive = (item, index, zoneName) => ({
- id: [item.zone_id, item.enter_date, item.period_code, index]
- .filter((v) => v != null && v !== "")
- .join("_"),
- date: item.enter_date || "",
- zone_name: item.zone_name || zoneName || "",
- content: item.sentence || (item.period_name ? `到达 ${item.period_name}` : ""),
- period_code: item.period_code,
- period_name: item.period_name,
- shoot_type: item.shoot_type,
- zone_id: item.zone_id,
- crop_code: item.crop_code,
- });
- const getSelectedFarm = () => {
- try {
- return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
- } catch {
- return {};
- }
- };
- const 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;
- };
- /** 清空第一条上的感知任务残留,回退为 mock 文案 */
- const clearFirstPatrolInteract = () => {
- if (!patrolList.value.length) return;
- const item = patrolList.value[0];
- patrolList.value[0] = {
- ...item,
- id: 1,
- level: t("agriFile.riskLevel2"),
- title: t("agriFile.patrolGrowthTitle"),
- zone: selectedZoneName.value || t("agriFile.zoneOne"),
- subject: t("agriFile.patrolSubject"),
- issue: t("agriFile.patrolIssue"),
- desc: "",
- zoneId: undefined,
- interactCode: undefined,
- raw: undefined,
- };
- };
- // ---------- 业务逻辑:加载 ----------
- const applySelectedZone = (zone) => {
- selectedZoneId.value = zone?.zone_id ?? null;
- selectedZoneName.value = zone?.zone_name || "";
- };
- const fetchZones = async () => {
- const farmId = getSelectedFarmId();
- if (farmId == null) {
- zoneList.value = [];
- applySelectedZone(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;
- // 优先沿用当前选中;否则默认第一个
- const matched = zones.find((z) => z.zone_id === selectedZoneId.value);
- applySelectedZone(matched || zones[0] || null);
- } catch (e) {
- console.warn("[patrolPanel] getZones failed", e);
- zoneList.value = [];
- applySelectedZone(null);
- }
- };
- const fetchPatrolInteractTask = async () => {
- const zoneId = selectedZoneId.value;
- if (zoneId == null || zoneId === "") {
- clearFirstPatrolInteract();
- return;
- }
- try {
- const res = await VE_API.questionnaire.getExternalInteractTasks({ zone_id: zoneId });
- const tasks = res?.data?.interact_tasks;
- const first = Array.isArray(tasks) ? tasks[0] : null;
- if (!first) {
- clearFirstPatrolInteract();
- return;
- }
- const mapped = mapInteractTaskToPatrol(first);
- if (patrolList.value.length) {
- patrolList.value[0] = {
- ...patrolList.value[0],
- ...mapped,
- };
- } else {
- patrolList.value = [mapped];
- }
- } catch (e) {
- console.warn("[patrolPanel] getExternalInteractTasks failed", e);
- }
- };
- const fetchCropArchive = async () => {
- const farm = getSelectedFarm();
- const zoneId = selectedZoneId.value;
- if (zoneId == null || zoneId === "") {
- cropArchiveList.value = [];
- return;
- }
- const query = {
- farm_id: farm.farm_id ?? farm.id,
- zone_id: zoneId,
- crop_code: farm.category_code,
- };
- try {
- const res = await VE_API.entry.getPhenoRecord(query);
- if (res?.code !== 200) {
- cropArchiveList.value = [];
- return;
- }
- const list = Array.isArray(res.data) ? res.data : [];
- const fallbackZone = selectedZoneName.value || t("agriFile.zoneOne");
- cropArchiveList.value = list
- .map((item, index) => mapPhenoRecordToArchive(item, index, fallbackZone))
- .sort((a, b) => String(b.date).localeCompare(String(a.date)));
- } catch (e) {
- console.warn("[patrolPanel] getPhenoRecord failed", e);
- cropArchiveList.value = [];
- }
- };
- const fillMockLabels = () => {
- const albumName = selectedZoneName.value || t("agriFile.zoneOne");
- patrolList.value = patrolList.value.map((item, index) => {
- // 第一条由外部感知任务接口填充,不覆盖
- if (index === 0 && item.interactCode) return item;
- return {
- ...item,
- level: t("agriFile.riskLevel2"),
- title: t(index === 0 ? "agriFile.patrolGrowthTitle" : "agriFile.patrolAbnormalTitle"),
- zone: albumName,
- subject: t("agriFile.patrolSubject"),
- issue: t("agriFile.patrolIssue"),
- };
- });
- };
- /** 按当前选中品种刷新巡园卡片与作物档案 */
- const reloadBySelectedZone = () => {
- clearFirstPatrolInteract();
- fillMockLabels();
- fetchPatrolInteractTask();
- fetchCropArchive();
- };
- const loadPatrolData = async () => {
- // 先清掉上一农场感知任务,避免 fillMockLabels 因 interactCode 保留旧数据
- clearFirstPatrolInteract();
- await fetchZones();
- fillMockLabels();
- fetchPatrolInteractTask();
- fetchCropArchive();
- };
- // ---------- 事件处理 ----------
- const handleSelectZone = (zone) => {
- if (!zone || zone.zone_id === selectedZoneId.value) return;
- applySelectedZone(zone);
- reloadBySelectedZone();
- };
- const goAddVariety = () => {
- router.push({
- path: "/add_zone",
- query: { type: "region", isSuccessPopup: "0" },
- });
- };
- const goGrowthTrack = (item) => {
- router.push({
- path: "/growth_track",
- query: {
- id: item?.id,
- type: item?.type || "growth",
- pageName:
- item.type === "growth"
- ? t("agriFile.patrolGrowthTitle")
- : t("agriFile.patrolAbnormalTitle"),
- },
- });
- };
- const handleShare = (item) => {
- const query = {
- askInfo: { title: "转发巡园要点", content: "是否分享给好友" },
- shareText: item?.title || "速长窗口不追肥?后期发育慢人一步,直接掉队!",
- targetUrl: "growth_track",
- paramsPage: JSON.stringify({
- id: item?.id,
- type: item?.type || "growth",
- fromShare: 1,
- }),
- imageUrl: "https://birdseye-img.sysuimars.com/temp/field.png",
- };
- wx.miniProgram.navigateTo({
- url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
- });
- };
- const handleUploadClick = (item) => {
- uploadZoneId.value = item?.zoneId ?? item?.id ?? selectedZoneId.value ?? "";
- showUploadPopup.value = true;
- };
- const handleUploadConfirm = async ({ images, date } = {}) => {
- const cloud_url = Array.isArray(images) ? images : [];
- if (!cloud_url.length) {
- ElMessage.warning("请先上传照片");
- return;
- }
- const farm = getSelectedFarm();
- try {
- const res = await VE_API.questionnaire.uploadImages({
- cloud_url,
- farm_id: farm.farm_id ?? farm.id ?? "",
- zone_id: uploadZoneId.value || selectedZoneId.value || "",
- category_code: farm.category_code || "",
- zone_name: selectedZoneName.value || farm.variety_name || "",
- create_time: date,
- });
- if (res?.code === 200) {
- showSuccessPopup.value = true;
- return;
- }
- ElMessage.error(res?.msg || "上传失败,请稍后再试");
- } catch {
- ElMessage.error("上传失败,请稍后再试");
- }
- };
- const handleComplete = () => {
- showSuccessPopup.value = false;
- };
- // ---------- 生命周期 ----------
- onMounted(loadPatrolData);
- onActivated(loadPatrolData);
- defineExpose({
- fetchPatrolInteractTask,
- fetchCropArchive,
- loadPatrolData,
- });
- </script>
- <style lang="scss" scoped>
- .patrol-panel {
- .variety-bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- margin-bottom: 10px;
- &__tags {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 6px;
- flex: 1;
- min-width: 0;
- }
- &__tag {
- padding: 4px 16px;
- border-radius: 4px;
- background: #fff;
- color: #4e5969;
- font-size: 14px;
- &.active {
- background: #2199f8;
- color: #fff;
- }
- }
- &__add {
- flex: none;
- display: flex;
- align-items: center;
- gap: 2px;
- padding: 5px 10px;
- border-radius: 4px;
- border: 1px solid #2199f8;
- background: rgba(33, 153, 248, 0.1);
- color: #2199f8;
- font-size: 14px;
- }
- }
- .tracking-item {
- width: 100%;
- margin-bottom: 16px;
- box-shadow: 0px 4px 4px 0px rgba(33, 153, 248, 0.1);
- &:last-of-type {
- margin-bottom: 0;
- }
- &__header {
- position: relative;
- padding: 10px 10px 20px;
- border-radius: 10px 10px 0 0;
- background: linear-gradient(180deg, #86caff 0%, #2199f8 48.5%);
- }
- &__header-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- }
- &__header-left {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- &__level {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 1px 4px;
- border-radius: 2px;
- background: #fff;
- color: #ff6a6a;
- font-size: 12px;
- }
- &__title {
- color: #fff;
- font-size: 16px;
- font-weight: 500;
- }
- &__zone {
- font-size: 12px;
- }
- &__share {
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 2px 10px;
- border-radius: 25px;
- background: #fff;
- color: #2199f8;
- font-size: 12px;
- }
- &__body {
- position: relative;
- padding: 10px;
- margin-top: -8px;
- border-radius: 10px;
- background: #fff;
- &::before {
- content: "";
- position: absolute;
- top: -6px;
- left: 21%;
- width: 0;
- height: 0;
- border-left: 7px solid transparent;
- border-right: 7px solid transparent;
- border-bottom: 7px solid #fff;
- transform: translateX(-50%);
- }
- }
- &__action {
- display: flex;
- align-items: center;
- justify-content: space-between;
- border: 1px solid rgba(33, 153, 248, 0.2);
- border-radius: 6px;
- padding: 8px;
- }
- &__icon {
- display: flex;
- align-items: center;
- gap: 5px;
- img {
- width: 18px;
- height: 16px;
- }
- }
- &__reason {
- color: #2199f8;
- font-weight: 500;
- }
- &__issue {
- margin-top: 2px;
- color: #2199f8;
- font-size: 12px;
- }
- &__action-main {
- flex: 1;
- padding-right: 4px;
- }
- &__btn {
- flex: none;
- display: flex;
- align-items: center;
- gap: 2px;
- padding: 7px 10px;
- border-radius: 5px;
- background: #2199f8;
- color: #fff;
- font-size: 12px;
- }
- }
- .crop-archive {
- padding: 10px;
- border-radius: 10px;
- background: #fff;
- &__title {
- position: relative;
- display: inline-block;
- z-index: 1;
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 5px;
- &::after {
- content: "";
- position: absolute;
- left: 0;
- bottom: 2px;
- z-index: -1;
- width: 90px;
- height: 6px;
- border-radius: 25px;
- background: linear-gradient(90deg, #2199f8 0%, rgba(255, 255, 255, 0.37) 100%);
- opacity: 0.4;
- }
- }
- }
- }
- .bottom-btn-container {
- display: flex;
- justify-content: space-between;
- gap: 12px;
- width: 100%;
- .bottom-btn {
- padding: 0 25px;
- border-radius: 4px;
- font-size: 16px;
- height: 40px;
- line-height: 40px;
- box-sizing: border-box;
- font-weight: 500;
- text-align: center;
- &.primary-btn {
- flex: 1;
- background: #2199f8;
- color: #fff;
- }
- }
- }
- </style>
|