| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652 |
- <template>
- <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
- <farm-header />
- <div class="nav-cards">
- <div v-for="item in navCards" :key="item.key" class="nav-card-wrap"
- :class="{ 'nav-card-wrap--report': item.key === 'report' }">
- <div class="nav-card" :class="{ 'is-active': activeNav === item.key }" @click="handleNavClick(item)">
- <img class="nav-card__icon" :src="item.icon" alt="" />
- <span class="nav-card__label">{{ t(item.labelKey) }}</span>
- </div>
- <!-- <div v-if="item.key === 'report' && guideVisible" class="report-guide-bubble">
- <span class="report-guide-bubble__arrow"></span>
- <div class="report-guide-bubble__content">
- {{ t("agriFile.diagnosisReportGuidePrefix") }}
- <span class="report-guide-bubble__link" @click.stop="handleGuideUpload">
- {{ t("agriFile.uploadPhoto") }}
- </span>
- {{ t("agriFile.diagnosisReportGuideSuffix") }}
- </div>
- </div> -->
- </div>
- </div>
- <div class="content-panel">
- <div v-show="activeNav === 'patrol'">
- <div v-for="(item, index) in patrolList" :key="index" class="tracking-item tracking-item--blue"
- @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>
- {{ t("agriFile.riskLevel2") }}
- </span>
- <span class="tracking-item__title van-ellipsis">
- {{
- item.type === "abnormal"
- ? t("agriFile.patrolAbnormalTitle")
- : t("agriFile.patrolGrowthTitle")
- }}
- <span v-if="item.crop_category_name" class="tracking-item__zone">
- ({{ item.crop_category_name }})
- </span>
- <span v-else-if="item.zone" class="tracking-item__zone">
- ({{ item.zone }})
- </span>
- </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="patrolIcon" alt="" />
- <span class="tracking-item__reason">{{ item.interaction_title || item.interaction_problem }}</span>
- </div>
- <div class="tracking-item__issue">{{ item.interaction_reason }}</div>
- </div>
- </div>
- </div>
- </div>
- <div class="crop-archive">
- <div class="crop-archive__title">{{ t("agriFile.cropArchive") }}</div>
- <PhenologyTrackTimelineItem :list="cropArchiveList" />
- </div>
- </div>
- <div v-show="activeNav === 'album'">
- <div class="map-card" @click="goAlbumMap">
- <div ref="mapContainer" class="map-container"></div>
- <!-- <div v-if="noFarmRange" class="map-mask" @click.stop="goAddZone('farm')">
- <div class="mask-content">
- <div class="mask-content__title">勾画种植区域,关联作物照片</div>
- <div class="mask-content__btn">去勾选</div>
- </div>
- </div> -->
- <!-- <div v-else class="add-zone-btn" @click.stop="goAddZone('region')">
- <el-icon>
- <Plus />
- </el-icon>
- <span>{{ t("agriFile.addZone") }}</span>
- </div> -->
- <!-- 地图标记由 Overlay 托管 DOM,勿用 Vue v-for 渲染,否则 KeepAlive 更新会报 __vnode -->
- </div>
- <div class="album-grid" v-if="!noFarmRange">
- <!-- <div class="album-add" @click.stop="goAddZone('region')">
- <el-icon class="album-add__icon">
- <Plus />
- </el-icon>
- <span>{{ t("agriFile.addAlbum") }}</span>
- </div> -->
- <div v-for="item in albumList" :key="item.id" class="album-card"
- :class="{ 'is-empty': !item.count }">
- <div class="album-card__cover" @click.stop="goAlbumDetail(item)">
- <img v-if="item.count && item.cover" :src="item.cover" alt="" />
- <img v-else class="album-card__empty-icon" src="@/assets/img/agricultural/photo.png"
- alt="" />
- <span class="album-card__count">{{ t("agriFile.photoCount", { count: item.count }) }}</span>
- <!-- <div class="album-card__add" @click.stop="openUploadPopup">
- <el-icon>
- <Plus />
- </el-icon>
- </div> -->
- </div>
- <div class="album-card__name">{{ item.name }}</div>
- </div>
- </div>
- </div>
- <div v-show="activeNav === 'report'">
- <div v-for="item in reportList" :key="item.id" class="report-card" @click="goReportDetail(item)">
- <div class="report-card__cover">
- <img :src="item.cover" alt="" />
- <!-- <span class="report-card__date">{{ item.date }}</span> -->
- <!-- <span class="report-card__status">
- <i class="report-card__dot"></i>
- {{ t("agriFile.reportUpdate") }}
- </span> -->
- </div>
- <div class="report-card__body">
- <div class="report-card__head">
- <div class="report-card__title">{{ item.title }}</div>
- <div v-if="item.date" class="report-card__day">{{ item.date }}</div>
- </div>
- <div class="report-card__desc">{{ item.desc }}</div>
- </div>
- </div>
- <!-- 气象报告:weather_report_list -->
- <div
- v-for="item in weatherReportList"
- :key="item.id"
- class="report-card"
- @click="goReportDetail(item)"
- >
- <div class="report-card__cover">
- <img :src="item.cover" alt="" />
- </div>
- <div class="report-card__body">
- <div class="report-card__head">
- <div class="report-card__title">{{ item.title }}</div>
- <div v-if="item.date" class="report-card__day">{{ item.date }}</div>
- </div>
- <div class="report-card__desc">{{ item.desc }}</div>
- </div>
- </div>
- </div>
- </div>
- <album-upload-popup v-model:show="showUploadPopup" />
- <complete-farm-info-popup v-model:show="showCompleteFarmPopup" @confirm="goCompleteFarmInfo" />
- <!-- 诊断报告弹窗 -->
- <diagnosis-report-popup ref="diagnosisReportPopupRef" />
- <!-- 恢复种植弹窗:确认已到达后触发诊断报告弹窗检查 -->
- <restore-planting-popup @phenology-reached="onPhenologyReached" />
- <!-- 代管弹窗:后续由接口控制是否展示 -->
- <proxy-auth-popup v-model:show="showProxyAuthPopup" :service-name="proxyServiceName" @reject="handleProxyReject"
- @allow="handleProxyAllow" />
- <!-- 申请更换物候期弹窗:后续由接口控制是否展示 -->
- <phenology-update-popup v-model:show="showPhenologyUpdatePopup" :service-name="phenologyUpdateInfo.serviceName"
- :current-stage="phenologyUpdateInfo.currentStage" :target-stage="phenologyUpdateInfo.targetStage"
- :current-image="phenologyUpdateInfo.currentImage" :farm-location="phenologyUpdateInfo.farmLocation"
- @reject="handlePhenologyUpdateReject" @confirm="handlePhenologyUpdateConfirm" />
- </div>
- </template>
- <script setup>
- import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
- import { useStore } from "vuex";
- import { useRouter } from "vue-router";
- import Overlay from "ol/Overlay";
- import { useI18n } from "@/i18n";
- import { Icon } from 'vant';
- import FileMap from "./fileMap";
- import * as util from "@/common/ol_common.js";
- import farmHeader from "@/components/farmHeader.vue";
- import albumUploadPopup from "./components/albumUploadPopup.vue";
- import completeFarmInfoPopup from "@/components/popup/completeFarmInfoPopup.vue";
- import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
- import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
- import proxyAuthPopup from "@/components/popup/proxyAuthPopup.vue";
- import phenologyUpdatePopup from "@/components/popup/phenologyUpdatePopup.vue";
- import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
- import eventBus from "@/api/eventBus";
- import wx from "weixin-js-sdk";
- import { base_img_url2 } from "@/api/config";
- const { t } = useI18n();
- const store = useStore();
- const router = useRouter();
- const tabBarHeight = computed(() => store.state.home.tabBarHeight);
- const diagnosisReportPopupRef = ref(null);
- /** 与 growthTrack 异常巡检约定:成功后刷新列表(弹窗已由 App 全局承接) */
- const INSPECT_SUCCESS_EVENT = "inspect-upload-success";
- function onInspectSuccess() {
- fetchReportList();
- fetchWeatherReportList();
- fetchCropArchiveList();
- }
- /** 恢复种植确认「已到达」后,再检查是否弹出诊断报告 */
- const onPhenologyReached = () => {
- diagnosisReportPopupRef.value?.checkShouldShow?.();
- };
- const DIAGNOSIS_GUIDE_STORAGE_KEY = "AGRI_FILE_DIAGNOSIS_REPORT_GUIDE";
- const ENTRY_FARM_DATA_KEY = "ENTRY_FARM_DATA";
- const CROP_ARCHIVE_KEY = "CROP_ARCHIVE_RECORDS";
- /** crop_question 物候配图,诊断报告封面复用 */
- const CROP_QUESTION_PIC_KEY = "CROP_QUESTION_PIC_URL";
- const guideVisible = ref(false);
- const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
- const activeNav = ref("patrol");
- const mapContainer = ref(null);
- const fileMap = new FileMap();
- const mapOverlays = [];
- const noFarmRange = ref(false);
- const navCards = [
- {
- key: "patrol",
- labelKey: "agriFile.patrolRecord",
- icon: require("@/assets/img/agricultural/icon-1.png"),
- },
- {
- key: "album",
- labelKey: "agriFile.agriAlbum",
- icon: require("@/assets/img/agricultural/icon-2.png"),
- },
- {
- key: "report",
- labelKey: "agriFile.diagnosisReport",
- icon: require("@/assets/img/agricultural/icon-3.png"),
- },
- ];
- /** 读取用户新增农场时选中的点位 */
- function readEntryFarmCoordinate() {
- try {
- const data = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
- if (Array.isArray(data?.coordinate) && data.coordinate.length === 2) {
- return [Number(data.coordinate[0]), Number(data.coordinate[1])];
- }
- const point =
- data?.point ||
- localStorage.getItem("GROWTH_REPORT_MAP_POINT") ||
- localStorage.getItem("selectedFarmPoint");
- if (typeof point === "string" && /^POINT\s*\(/i.test(point.trim())) {
- return util.wktCastGeom(point).getFirstCoordinate();
- }
- } catch {
- // ignore
- }
- return null;
- }
- function readEntryFarmData() {
- try {
- return JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
- } catch {
- return null;
- }
- }
- /** 用户录入时选择的作物品类名 */
- function resolveSelectedCropName() {
- return readEntryFarmData()?.crop || "";
- }
- const zoneList = ref([]);
- const albumList = ref([]);
- function resolveCropQuestionImageUrl(path) {
- if (!path) return "";
- if (/^https?:\/\//i.test(path)) return path;
- return base_img_url2 + String(path).replace(/^\//, "");
- }
- function readStoredCropQuestionPic() {
- return localStorage.getItem(CROP_QUESTION_PIC_KEY) || "";
- }
- /** 拉取 crop_question 配图并缓存,诊断报告封面复用 */
- async function fetchCropQuestionAlbumPic() {
- const entry = readEntryFarmData();
- const crop = entry?.crop;
- const code = entry?.cropCode;
- if (!crop || !code) return;
- try {
- const res = await VE_API.questionnaire.phenologyProblem({
- crop_type: crop,
- phenophase_code: String(code),
- code: String(code),
- });
- if (res?.code !== 200 || !res.data) return;
- const url = resolveCropQuestionImageUrl(
- res.data.phenophase_pic_url || res.data.interact_pic_url
- );
- if (url) localStorage.setItem(CROP_QUESTION_PIC_KEY, url);
- } catch {
- // ignore
- }
- }
- function resolveGroupPoint(group) {
- const lng = Number(group?.longitude);
- const lat = Number(group?.latitude);
- if (Number.isFinite(lng) && Number.isFinite(lat)) return [lng, lat];
- return readEntryFarmCoordinate() || DEFAULT_CENTER;
- }
- function buildEmptyAlbum() {
- const coordinate = readEntryFarmCoordinate() || DEFAULT_CENTER;
- return {
- id: "album-empty",
- name: resolveSelectedCropName(),
- count: 0,
- cover: "",
- longitude: Number(coordinate[0]),
- latitude: Number(coordinate[1]),
- };
- }
- /** 组内展示名:优先分区名,否则用户品类 */
- function resolveGroupAlbumName(images, cropName) {
- const zoneName = String(images.find((img) => img?.zone_name)?.zone_name || "").trim();
- return zoneName || cropName || "";
- }
- /**
- * query_tmp_image:data.groups 为按点位分组的二维结构
- * groups[i].images[j] → cloud_url / zone_name;每组自带经纬度与 image_count
- */
- function mapTmpImageAlbum(data) {
- const cropName = resolveSelectedCropName();
- const rawGroups = Array.isArray(data?.groups) ? data.groups : [];
- const albums = [];
- const zones = [];
- rawGroups.forEach((group, index) => {
- const images = Array.isArray(group?.images)
- ? group.images.filter((img) => img?.cloud_url)
- : [];
- if (!images.length) return;
- const [lng, lat] = resolveGroupPoint(group);
- const name = resolveGroupAlbumName(images, cropName);
- const count = Number(group.image_count) || images.length;
- const cover = images[0].cloud_url;
- const id = `${images[0]?.farm_id || "album"}-${index}`;
- const item = {
- id,
- groupIndex: index,
- name,
- count,
- cover,
- longitude: lng,
- latitude: lat,
- };
- albums.push(item);
- zones.push({ ...item, id: `album-point-${index}` });
- });
- if (!albums.length) {
- return { albums: [buildEmptyAlbum()], zones: [] };
- }
- return { albums, zones };
- }
- let tmpAlbumTask = null;
- /** 农情相册:query_tmp_image,手机号与诊断报告同一来源 */
- async function fetchTmpAlbumImages() {
- if (tmpAlbumTask) return tmpAlbumTask;
- tmpAlbumTask = (async () => {
- const tel = resolveUserTel();
- let data = null;
- if (tel) {
- try {
- data = unwrapApiData(await VE_API.questionnaire.queryTmpImage({ tel }));
- } catch {
- data = null;
- }
- }
- const mapped = mapTmpImageAlbum(data);
- albumList.value = mapped.albums;
- zoneList.value = mapped.zones;
- })().finally(() => {
- tmpAlbumTask = null;
- });
- return tmpAlbumTask;
- }
- const reportCover = require("@/assets/img/common/sd-1.jpg");
- /** 诊断报告列表:风险报告 / 胁迫报告 / 异常农情研判报告 */
- const reportList = ref([]);
- /** 气象报告列表:weather_report_list */
- const weatherReportList = ref([]);
- function resolveReportCover() {
- return readStoredCropQuestionPic() || reportCover;
- }
- function resolveUserTel() {
- const entry = readEntryFarmData();
- if (entry?.phone) return String(entry.phone).trim();
- try {
- const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
- return String(userInfo.tel || userInfo.phone || userInfo.mobile || "").trim();
- } catch {
- return "";
- }
- }
- function unwrapApiData(raw) {
- const res = Array.isArray(raw) ? raw[0] : raw;
- if (Number(res?.code) !== 200 || !res.data) return null;
- return Array.isArray(res.data) ? res.data[0] : res.data;
- }
- /**
- * risk_report / stress_report:兼容
- * - 新:{ current_time, reports: [...] }
- * - 旧:data 为报告对象或数组
- */
- function unwrapRiskStressEnvelope(raw) {
- const res = Array.isArray(raw) ? raw[0] : raw;
- if (Number(res?.code) !== 200 || !res.data) {
- return { report: null, currentTime: "" };
- }
- const data = res.data;
- if (Array.isArray(data)) {
- return { report: data[0] || null, currentTime: "" };
- }
- if (Array.isArray(data.reports)) {
- return {
- report: data.reports[0] || null,
- currentTime: data.current_time || "",
- };
- }
- return {
- report: data,
- currentTime: data.current_time || "",
- };
- }
- function pickReportDate(data) {
- return data?.create_time || data?.update_time || data?.date || data?.report_date || "";
- }
- /** 日期字段 → 仅年月日;兼容 2026-09-11 / Wed, 23 Sep 2026 09:42:00 GMT */
- function formatReportDay(value) {
- if (!value) return "";
- const text = String(value).trim();
- const matched = text.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (matched) return `${matched[1]}-${matched[2]}-${matched[3]}`;
- const d = new Date(text);
- if (Number.isNaN(d.getTime())) return "";
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
- }
- /** 异常研判报告:取 summary 首段正文作卡片简介 */
- function pickAbnormalReportDesc(summary) {
- const text = String(summary || "").trim();
- if (!text) return "";
- return (
- text
- .split(/\n+/)
- .map((line) => line.trim())
- .find((line) => line && !/^[一二三四五六七八九十]+[、..]/.test(line)) || text
- );
- }
- /** 拼装诊断报告 tab:risk_report / stress_report / query_abnormal_report */
- async function fetchReportList() {
- const tel = resolveUserTel();
- const cover = resolveReportCover();
- const entry = readEntryFarmData();
- const empty = (id) => ({ id, title: "", desc: "", date: "", cover });
- if (!tel) {
- reportList.value = [empty(1), empty(2), empty(3)];
- return;
- }
- const [riskRaw, stressRaw, abnormalRaw] = await Promise.all([
- VE_API.questionnaire.riskReport({ tel }).catch(() => null),
- VE_API.questionnaire.stressReport({ tel }).catch(() => null),
- VE_API.questionnaire.queryAbnormalReport({ tel }).catch(() => null),
- ]);
- const { report: riskData, currentTime: riskTime } = unwrapRiskStressEnvelope(riskRaw);
- const { report: stressData, currentTime: stressTime } = unwrapRiskStressEnvelope(stressRaw);
- // query_abnormal_report:data.reports / data.report 均为报告数组
- const abnormalRes = Array.isArray(abnormalRaw) ? abnormalRaw[0] : abnormalRaw;
- const abnormalRawList =
- Number(abnormalRes?.code) === 200
- ? abnormalRes?.data?.reports ?? abnormalRes?.data?.report
- : null;
- const abnormalReports = Array.isArray(abnormalRawList)
- ? abnormalRawList
- : abnormalRawList && typeof abnormalRawList === "object"
- ? [abnormalRawList]
- : [];
- const baseList = [
- {
- id: 1,
- type: "risk",
- title: riskData
- ? `未来预警: ${riskData.risk_name || "预警"}`
- : "",
- // 卡片简介:explain_simple
- desc: riskData?.explain_simple || "",
- // 日期:外层 current_time
- date: formatReportDay(riskTime),
- cover,
- },
- {
- id: 2,
- type: "stress",
- title: stressData
- ? `当下胁迫: ${stressData.stress_name || "胁迫"}`
- : "",
- // 卡片简介:explain_simple
- desc: stressData?.explain_simple || "",
- // 日期:外层 current_time
- date: formatReportDay(stressTime),
- cover,
- },
- ].filter((item) => item.title || item.desc);
- const abnormalList = abnormalReports
- .filter((item) => item && (item.id != null || item.analysis || item.explain_simple))
- .map((item) => ({
- id: `abnormal-${item.id}`,
- reportId: item.id,
- type: "abnormal_report",
- title: item.title || "异常农情诊断报告",
- // 卡片简介:analysis[].desc
- desc: item.explain_simple || "",
- // cover_url 相对路径需拼 base_img_url2
- cover: resolveCropQuestionImageUrl(item.cover_url) || cover,
- // 异常报告日期:calc_date → 年月日
- date: formatReportDay(item.calc_date),
- }));
- reportList.value = [...baseList, ...abnormalList];
- }
- /** 气象报告列表:weather_report_list → 卡片展示在诊断报告下方 */
- async function fetchWeatherReportList() {
- const cover = resolveReportCover();
- try {
- const raw = await VE_API.questionnaire.weatherReportList({}).catch(() => null);
- const res = Array.isArray(raw) ? raw[0] : raw;
- if (Number(res?.code) !== 200) {
- weatherReportList.value = [];
- return;
- }
- const reports = Array.isArray(res?.data?.reports) ? res.data.reports : [];
- weatherReportList.value = reports
- .filter((item) => item && (item.id != null || item.explain_simple || item.risk_name))
- .map((item) => ({
- id: `weather-${item.id}`,
- reportId: item.id,
- type: "weather",
- title: item.risk_name
- ? `历史气象报告: ${item.risk_name}`
- : item.crop_name
- ? `历史气象报告: ${item.crop_name}`
- : "历史气象报告",
- desc: item.explain_simple || item.interact_explain || "",
- date: formatReportDay(item.report_date),
- // cover_url 相对路径需拼 base_img_url2
- cover: resolveCropQuestionImageUrl(item.cover_url) || cover,
- }));
- } catch {
- weatherReportList.value = [];
- }
- }
- const showUploadPopup = ref(false);
- const showCompleteFarmPopup = ref(false);
- // 后续由接口控制是否展示代管授权弹窗
- const showProxyAuthPopup = ref(false);
- const proxyServiceName = ref("农服名称");
- const handleProxyReject = () => {
- // TODO: 调用拒绝代管接口
- };
- const handleProxyAllow = () => {
- // TODO: 调用允许代管接口
- };
- // 后续由接口控制是否展示申请更换物候期弹窗
- const showPhenologyUpdatePopup = ref(false);
- const phenologyUpdateInfo = ref({
- serviceName: "某某农服",
- currentStage: "花穗期",
- targetStage: "小果期",
- varietyName: "荔枝-桂味",
- farmLocation: "位置位置位置",
- });
- const handlePhenologyUpdateReject = () => {
- // TODO: 调用暂不更新接口
- };
- const handlePhenologyUpdateConfirm = () => {
- // TODO: 调用确认更新接口
- };
- const patrolIcon = require("@/assets/img/report/wh-icon.png");
- /** 巡园要点列表:接口 data 原样 push,字段在模板自行对接 */
- const patrolList = ref([]);
- const cropArchiveList = ref([]);
- // function resolveUserTel() {
- // try {
- // const entry = JSON.parse(localStorage.getItem(ENTRY_FARM_DATA_KEY) || "null");
- // if (entry?.phone) return String(entry.phone).trim();
- // } catch {
- // // ignore
- // }
- // try {
- // const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
- // return String(userInfo.tel || userInfo.phone || userInfo.mobile || "").trim();
- // } catch {
- // return "";
- // }
- // }
- function formatPhenophaseDate(value) {
- if (!value) return "";
- const text = String(value).trim();
- const iso = text.match(/(\d{4})-(\d{2})-(\d{2})/);
- if (iso) return `${iso[1]}-${iso[2]}-${iso[3]}`;
- const d = new Date(text);
- if (Number.isNaN(d.getTime())) return "";
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
- }
- /** get_phenophase_info → 作物档案时间轴 */
- function mapPhenophaseInfoToArchive(list) {
- const cropName = resolveSelectedCropName();
- const rows = Array.isArray(list) ? list : [];
- const filtered = cropName
- ? rows.filter((item) => !item?.crop_category_name || item.crop_category_name === cropName)
- : rows;
- const source = filtered.length ? filtered : rows;
- return source
- .map((item, index) => ({
- id: item?.phenophase_code
- ? `${item.phenophase_code}-${item.start_time || index}`
- : `pheno-${index}`,
- date: formatPhenophaseDate(item?.start_time) || formatPhenophaseDate(item?.sentence),
- zone_name: item?.crop_category_name || cropName || "",
- content: item?.sentence || item?.phenophase_name || "",
- phenophase_code: item?.phenophase_code,
- phenophase_name: item?.phenophase_name,
- }))
- .filter((item) => item.date && item.content)
- .sort((a, b) => String(b.date).localeCompare(String(a.date)));
- }
- function readCropArchiveAnswers() {
- try {
- const list = JSON.parse(localStorage.getItem(CROP_ARCHIVE_KEY) || "[]");
- return Array.isArray(list) ? list : [];
- } catch {
- return [];
- }
- }
- async function fetchCropArchiveList() {
- const tel = resolveUserTel();
- if (!tel) {
- cropArchiveList.value = readCropArchiveAnswers();
- return;
- }
- try {
- const res = await VE_API.questionnaire.getPhenophaseInfo({ tel });
- if (res?.code !== 200) {
- cropArchiveList.value = readCropArchiveAnswers();
- return;
- }
- const apiList = mapPhenophaseInfoToArchive(res.data);
- cropArchiveList.value = [...readCropArchiveAnswers(), ...apiList];
- } catch {
- cropArchiveList.value = readCropArchiveAnswers();
- }
- }
- const getFarmCenter = () => {
- const entryCoordinate = readEntryFarmCoordinate();
- if (entryCoordinate) return entryCoordinate;
- 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 util.wktCastGeom(wkt).getFirstCoordinate();
- }
- } catch {
- // ignore
- }
- return DEFAULT_CENTER;
- };
- /** 手动创建标记节点,避免 OL Overlay 挪走 Vue vnode 对应的 DOM */
- function createZoneMarkerEl(zone) {
- const root = document.createElement("div");
- root.className = "zone-marker";
- const label = document.createElement("div");
- label.className = "zone-marker__label";
- label.textContent = zone.name || "";
- const thumb = document.createElement("div");
- thumb.className = "zone-marker__thumb";
- const img = document.createElement("img");
- img.src = zone.cover || "";
- img.alt = "";
- const badge = document.createElement("span");
- badge.className = "zone-marker__badge";
- badge.textContent = String(zone.count ?? 0);
- thumb.append(img, badge);
- const arrow = document.createElement("div");
- arrow.className = "zone-marker__arrow";
- root.append(label, thumb, arrow);
- return root;
- }
- const clearMapOverlays = () => {
- if (!fileMap.kmap?.map) {
- mapOverlays.length = 0;
- return;
- }
- mapOverlays.forEach((overlay) => {
- fileMap.kmap.map.removeOverlay(overlay);
- const el = overlay.getElement?.();
- el?.remove?.();
- });
- mapOverlays.length = 0;
- };
- const bindZoneOverlays = () => {
- if (!fileMap.kmap?.map) return;
- clearMapOverlays();
- zoneList.value.forEach((zone) => {
- if (zone.longitude == null || zone.latitude == null) return;
- const overlay = new Overlay({
- element: createZoneMarkerEl(zone),
- position: [zone.longitude, zone.latitude],
- positioning: "bottom-center",
- stopEvent: false,
- });
- fileMap.kmap.map.addOverlay(overlay);
- mapOverlays.push(overlay);
- });
- };
- const focusAlbumPoint = () => {
- const point = zoneList.value[0];
- if (!point || point.longitude == null || point.latitude == null || !fileMap.kmap?.getView) return;
- fileMap.kmap.getView().animate({
- center: [Number(point.longitude), Number(point.latitude)],
- zoom: 16,
- duration: 0,
- });
- };
- const initAlbumMap = async () => {
- await nextTick();
- if (!mapContainer.value) return;
- const center = getFarmCenter();
- fileMap.initMap(`POINT(${center[0]} ${center[1]})`, mapContainer.value);
- await nextTick();
- if (noFarmRange.value) {
- zoneList.value = [];
- albumList.value = [];
- return;
- }
- await fetchTmpAlbumImages();
- await nextTick();
- bindZoneOverlays();
- focusAlbumPoint();
- fileMap.kmap?.map?.updateSize?.();
- };
- const fillMockLabels = () => {
- reportList.value = reportList.value.map((item) => ({
- ...item,
- title: t("agriFile.reportTitle"),
- desc: t("agriFile.reportDesc"),
- date: t("agriFile.reportDate"),
- }));
- };
- /**
- * 长势跟踪巡园要点:parse_interact
- */
- async function fetchPatrolGrowthInteract() {
- const entry = readEntryFarmData();
- const cropCategoryName = entry?.crop;
- const phenophaseCode = entry?.cropCode;
- const riskName = entry?.weatherRisk;
- if (!cropCategoryName || !phenophaseCode || !riskName) return;
- try {
- const res = await VE_API.questionnaire.parseInteract({
- crop_category_name: cropCategoryName,
- phenophase_code: String(phenophaseCode),
- risk_name: riskName,
- });
- if (res.code !== 200) return;
- const data = res.data;
- if (Array.isArray(data)) {
- patrolList.value.push(...data);
- } else if (data) {
- patrolList.value.push(data);
- }
- } catch {
- // ignore
- }
- }
- /**
- * 异常态势巡园要点:parse_anormal
- */
- async function fetchPatrolAbnormalInteract() {
- const entry = readEntryFarmData();
- const cropCategoryName = entry?.crop;
- const phenophaseCode = entry?.cropCode;
- const riskName = entry?.weatherStress || entry?.weatherRisk;
- if (!cropCategoryName || !phenophaseCode || !riskName) return;
- try {
- const res = await VE_API.questionnaire.abnormalContent({
- crop_category_name: cropCategoryName,
- phenophase_code: String(phenophaseCode),
- risk_name: riskName,
- });
- if (res?.code !== 200 || !res.data) return;
- const list = Array.isArray(res.data) ? res.data : [res.data];
- list.forEach((item) => {
- if (!item || typeof item !== "object") return;
- patrolList.value.push({
- ...item,
- type: "abnormal",
- crop_category_name: item.crop_category_name || cropCategoryName,
- // 卡片标题 / 描述:兼容异常接口字段
- interaction_problem:
- item.interaction_problem || item.interaction_title || item.interaction_theme || "",
- interaction_reason:
- item.interaction_reason || item.interaction_theme || item.inspection_guide || "",
- });
- });
- } catch {
- // ignore
- }
- }
- async function loadPatrolContent() {
- fillMockLabels();
- patrolList.value = [];
- // 先长势跟踪,后异常态势
- await fetchPatrolGrowthInteract();
- await fetchPatrolAbnormalInteract();
- await fetchCropQuestionAlbumPic();
- await fetchTmpAlbumImages();
- await fetchReportList();
- await fetchWeatherReportList();
- await fetchCropArchiveList();
- await nextTick();
- bindZoneOverlays();
- }
- const goAddZone = (type) => {
- router.push(
- {
- path: "/add_zone",
- query: {
- type,
- isSuccessPopup: type === 'farm' ? '1' : '0',
- },
- }
- );
- };
- const goAlbumMap = () => {
- router.push("/album_map");
- };
- const goGrowthTrack = (item) => {
- router.push({
- path: "/growth_track",
- query: {
- type: item?.type || "growth",
- pageName: item.type === "abnormal"
- ? t("agriFile.patrolAbnormalTitle")
- : t("agriFile.patrolGrowthTitle"),
- },
- });
- };
- /** 诊断报告:风险/胁迫/异常研判进详情页,种植诊断进报告页 */
- const goReportDetail = (item) => {
- if (!item) return;
- if (item.type === "diagnosis") {
- router.push({
- path: "/diagnosis_report",
- query: {
- id: item.reportId || item.id || "",
- },
- });
- return;
- }
- router.push({
- path: "/alert_detail",
- query: {
- title: item.title || "具体预警",
- detailType: item.type || "",
- id: item.reportId || item.id || "",
- },
- });
- };
- 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 needDraw = ref(true);
- const openUploadPopup = () => {
- showUploadPopup.value = true;
- };
- /** 与 growthTrack 上传一致:需勾画 → add_zone;异常 → pest_interact;否则弹上传 */
- const handleUploadClick = (item) => {
- const isAbnormal = item?.type === "abnormal";
- if (needDraw.value) {
- router.push({
- path: "/add_zone",
- query: {
- type: "farm",
- targetUrl: isAbnormal ? "/pest_interact" : "/growth_track",
- ...(isAbnormal ? { isAbnormal: "true" } : {}),
- ...(item?.id != null ? { trackId: item.id } : {}),
- ...(item?.type ? { trackType: item.type } : {}),
- },
- });
- return;
- }
- if (isAbnormal) {
- router.push({ path: "/pest_interact" });
- return;
- }
- showUploadPopup.value = true;
- };
- const dismissGuide = () => {
- if (!guideVisible.value) return;
- guideVisible.value = false;
- localStorage.setItem(DIAGNOSIS_GUIDE_STORAGE_KEY, "1");
- };
- const tryShowGuide = () => {
- if (localStorage.getItem(DIAGNOSIS_GUIDE_STORAGE_KEY)) return;
- guideVisible.value = true;
- };
- const handleGuideUpload = () => {
- dismissGuide();
- openUploadPopup();
- };
- const handleNavClick = (item) => {
- if (item.key === "report") dismissGuide();
- activeNav.value = item.key;
- };
- const handleOutsideClick = (event) => {
- if (!guideVisible.value) return;
- const target = event.target;
- if (
- target?.closest?.(".report-guide-bubble") ||
- target?.closest?.(".nav-card-wrap--report .nav-card")
- ) {
- return;
- }
- dismissGuide();
- };
- const goCompleteFarmInfo = () => {
- router.push("/entry_information");
- };
- const goAlbumDetail = (item) => {
- router.push({
- path: "/region_albums",
- query: {
- id: item?.id,
- name: item?.name || "",
- groupIndex: item?.groupIndex != null ? String(item.groupIndex) : "",
- noImage: item?.count ? "false" : "true",
- },
- });
- };
- onMounted(() => {
- initAlbumMap();
- tryShowGuide();
- eventBus.on(INSPECT_SUCCESS_EVENT, onInspectSuccess);
- document.addEventListener("click", handleOutsideClick, true);
- });
- onActivated(() => {
- loadPatrolContent();
- initAlbumMap();
- tryShowGuide();
- });
- onBeforeUnmount(() => {
- eventBus.off(INSPECT_SUCCESS_EVENT, onInspectSuccess);
- clearMapOverlays();
- document.removeEventListener("click", handleOutsideClick, true);
- });
- watch(activeNav, (key) => {
- if (key === "album") {
- initAlbumMap();
- }
- });
- </script>
- <style lang="scss" scoped>
- .agri-file-page {
- position: relative;
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- overflow: hidden;
- background: linear-gradient(180deg, #D1EBFF 0%, #FFFFFF 100%);
- .nav-cards {
- position: relative;
- z-index: 2;
- display: flex;
- justify-content: space-around;
- padding: 10px 6px;
- margin-bottom: 10px;
- gap: 6px;
- .nav-card-wrap {
- position: relative;
- width: 100%;
- &--report {
- z-index: 5;
- }
- }
- .nav-card {
- position: relative;
- display: flex;
- // flex-direction: column;
- align-items: center;
- justify-content: center;
- border-radius: 8px;
- background: #fff;
- // width: 90px;
- gap: 4px;
- height: 40px;
- box-shadow: 0px 4px 4px 0px #0000000D;
- .nav-card__icon {
- width: 26px;
- height: 26px;
- // margin-bottom: 4px;
- }
- &.is-active {
- background: linear-gradient(180deg, #5BB8FF 0%, #2199F8 100%);
- box-shadow: 0 4px 10px rgba(33, 153, 248, 0.28);
- .nav-card__icon {
- filter: brightness(0) invert(1);
- }
- .nav-card__label {
- color: #fff;
- }
- &::after {
- content: "";
- position: absolute;
- left: 50%;
- bottom: -5px;
- transform: translateX(-50%);
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-top: 6px solid #2199F8;
- z-index: 3;
- }
- }
- }
- .report-guide-bubble {
- position: absolute;
- top: calc(100% + 10px);
- right: 22px;
- width: 216px;
- padding: 6px 10px;
- border-radius: 6px;
- background: rgba(0, 0, 0, 0.8);
- backdrop-filter: blur(4px);
- box-sizing: border-box;
- z-index: 20;
- pointer-events: auto;
- &__arrow {
- position: absolute;
- top: -5px;
- right: 16px;
- width: 0;
- height: 0;
- border-left: 6px solid transparent;
- border-right: 6px solid transparent;
- border-bottom: 6px solid rgba(0, 0, 0, 0.8);
- }
- &__content {
- font-size: 14px;
- line-height: 22px;
- color: #fff;
- word-break: break-all;
- text-align: justify;
- }
- &__link {
- padding: 0 2px;
- color: #2199f8;
- cursor: pointer;
- }
- }
- }
- .content-panel {
- flex: 1;
- overflow-y: auto;
- background: linear-gradient(360deg, rgba(229, 243, 255, 0.55) 0%, rgba(255, 255, 255, 0.55) 100%);
- border-radius: 20px 20px 0 0;
- border: 1px solid #FFFFFF;
- padding: 18px 10px;
- &::-webkit-scrollbar {
- display: none;
- }
- .map-card {
- position: relative;
- height: 210px;
- }
- .map-container {
- width: 100%;
- height: 100%;
- clip-path: inset(0px round 8px);
- }
- .map-mask {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 5;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- pointer-events: auto;
- cursor: pointer;
- .mask-content {
- background: rgba(255, 255, 255, 0.2);
- backdrop-filter: blur(4px);
- padding: 8px 10px;
- border-radius: 4px;
- width: fit-content;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 12px;
- color: #fff;
- pointer-events: none;
- &__title {
- font-size: 18px;
- font-family: "PangMenZhengDao";
- }
- &__btn {
- padding: 0 24px;
- height: 32px;
- line-height: 32px;
- border-radius: 24px;
- background: #2199F8;
- }
- }
- }
- .add-zone-btn {
- position: absolute;
- right: 5px;
- bottom: 10px;
- z-index: 5;
- display: flex;
- align-items: center;
- gap: 2px;
- padding: 5px 10px;
- border-radius: 4px;
- background: #fff;
- color: #2199F8;
- font-weight: 500;
- }
- .album-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 16px;
- margin-top: 16px;
- box-sizing: border-box;
- .album-add {
- align-self: start;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- width: 100%;
- aspect-ratio: 1;
- border-radius: 5px;
- background: #FFFFFF;
- color: #969494;
- font-size: 16px;
- &__icon {
- margin-bottom: 6px;
- font-size: 20px;
- }
- }
- .album-card {
- width: 100%;
- &.is-empty {
- .album-card__cover {
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- border-radius: 8px;
- background: linear-gradient(180deg, #E0F1FF 0%, #C2E4FF 100%);
- }
- }
- &__cover {
- position: relative;
- width: 100%;
- aspect-ratio: 1;
- img:not(.album-card__empty-icon) {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 6px;
- }
- }
- &__empty-icon {
- width: 50px;
- height: 50px;
- }
- &__count {
- position: absolute;
- top: 1px;
- right: 0;
- padding: 1px 5px;
- border-radius: 0 5px 0 5px;
- background: rgba(0, 0, 0, 0.4);
- backdrop-filter: blur(4px);
- color: #fff;
- }
- &__add {
- position: absolute;
- right: 8px;
- bottom: 8px;
- z-index: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 44px;
- height: 44px;
- border-radius: 50%;
- background: #2199F8;
- color: #fff;
- font-size: 20px;
- box-shadow: 0 2px 8px rgba(33, 153, 248, 0.35);
- }
- &__name {
- margin-top: 10px;
- color: #1F1F1F;
- }
- }
- }
- .report-card {
- margin-bottom: 10px;
- border-radius: 8px;
- background: #fff;
- &:last-child {
- margin-bottom: 0;
- }
- &__cover {
- position: relative;
- width: 100%;
- height: 158px;
- img {
- display: block;
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 5px;
- }
- }
- &__date {
- position: absolute;
- top: 0;
- padding: 5px 12px;
- border-radius: 4px;
- background: rgba(0, 0, 0, 0.45);
- backdrop-filter: blur(4px);
- color: #fff;
- font-size: 16px;
- border-radius: 5px 0 5px 0;
- }
- &__status {
- position: absolute;
- top: 10px;
- right: 0;
- display: flex;
- align-items: center;
- gap: 4px;
- padding: 2px 13px;
- border-radius: 20px 0 0 20px;
- background: #FF5454;
- color: #fff;
- font-size: 12px;
- }
- &__dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: #fff;
- }
- &__body {
- padding: 10px;
- margin-top: 4px;
- }
- &__head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 8px;
- }
- &__title {
- flex: 1;
- min-width: 0;
- font-size: 16px;
- font-weight: 500;
- }
- &__day {
- flex: none;
- color: #9E9B9B;
- font-size: 14px;
- white-space: nowrap;
- }
- &__desc {
- margin-top: 4px;
- color: #9E9B9B;
- }
- }
- .tracking-item {
- width: 100%;
- margin-bottom: 16px;
- box-shadow: 0px 4px 4px 0px rgba(33, 153, 248, 0.1);
- background: linear-gradient(180deg, #86CAFF 0%, #2199F8 49%);
- border-radius: 10px;
- &: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;
- }
- }
- }
- }
- }
- </style>
- <!-- Overlay 移出 Vue 树后无 scoped attr,标记样式需非 scoped -->
- <style lang="scss">
- .zone-marker {
- display: flex;
- flex-direction: column;
- align-items: center;
- pointer-events: none;
- &__label {
- padding: 3px 14px;
- border-radius: 25px;
- background: #fff;
- color: #0a0a0a;
- font-size: 12px;
- }
- &__thumb {
- position: relative;
- width: 42px;
- height: 42px;
- margin-top: 8px;
- img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border: 2px solid #fff;
- border-radius: 6px;
- box-sizing: border-box;
- }
- }
- &__badge {
- position: absolute;
- top: -6px;
- right: -6px;
- min-width: 18px;
- height: 18px;
- line-height: 16px;
- border-radius: 50%;
- background: #fff;
- color: #0a0a0a;
- font-size: 12px;
- text-align: center;
- }
- &__arrow {
- width: 0;
- height: 0;
- border-left: 5px solid transparent;
- border-right: 5px solid transparent;
- border-top: 6px solid #fff;
- }
- }
- </style>
|