| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916 |
- <template>
- <div class="edit-map">
- <custom-header @goback="goBack" :isGoBack="true" :name="viewOnly ? '查看区域' : '勾选地块'"></custom-header>
- <div class="region-type-tabs">
- <div v-for="item in regionTypeTabs" :key="item.code" class="region-type-tab"
- :class="{ 'region-type-tab--active': activeRegionType === item.code }"
- @click="handleRegionTypeClick(item)">
- {{ item.name }}
- </div>
- </div>
- <div class="variety-tabs" v-if="varietyTabs.length > 0 && activeRegionType !== 'DORMANCY'">
- <div v-for="(v, index) in varietyTabs" :key="index" class="variety-tab"
- :class="{ 'variety-tab--active': activeVariety === index }" @click="handleVarietyClick(v, index)">
- {{ v.regionName || v.problemZoneTypeName }}
- </div>
- </div>
- <div class="edit-map-content">
- <div class="edit-map-tip" v-if="!viewOnly">操作提示:拖动圆点,即可调整地块边界</div>
- <div class="map-container" ref="mapContainer"></div>
- <div class="edit-map-footer">
- <div v-if="viewOnly" class="footer-back" @click="deletePolygon">
- <img class="back-icon" src="@/assets/img/map/delete.png" alt="" />
- </div>
- <div class="edit-map-footer-btn" :class="{ 'confirm-btn-box': viewOnly }">
- <template v-if="!viewOnly">
- <div class="btn-reset" @click="resetPolygon">重置区域</div>
- <div class="btn-confirm" @click="openConfirmDrawTypePopup">确认区域</div>
- </template>
- <div v-else class="btn-confirm" @click="handleEditRegion">编辑区域</div>
- </div>
- </div>
- </div>
- <tip-popup v-model:show="showTipPopup" type="success" text="您的农情报告已生成" text2="请查看" buttonText="点击查看"
- @confirm="handleTipConfirm" />
- <confirm-draw-type-popup ref="confirmDrawTypePopupRef" @confirm="handleConfirmDrawType" />
- </div>
- </template>
- <script setup>
- import customHeader from "@/components/customHeader.vue";
- import { ref, computed, watch, onActivated, onDeactivated, nextTick } from "vue";
- import DrawRegionMap from "../../interactionList/map/drawRegionMap.js";
- import { Map as KMapMap } from "@/utils/ol-map/KMap";
- import { useRouter, useRoute } from "vue-router";
- import { convertPointToArray } from "@/utils/index";
- import { ElMessage, ElMessageBox } from "element-plus";
- import TipPopup from "@/components/popup/tipPopup.vue";
- import ConfirmDrawTypePopup from "@/components/popup/confirmDrawTypePopup.vue";
- import Style from "ol/style/Style";
- import { Fill, Stroke, Circle, Text } from "ol/style.js";
- import { Point, Polygon, MultiPolygon } from "ol/geom";
- import WKT from "ol/format/WKT.js";
- import { unByKey } from "ol/Observable.js";
- import * as proj from "ol/proj";
- import { getArea } from "ol/sphere.js";
- const router = useRouter();
- const route = useRoute();
- const mapContainer = ref(null);
- const drawRegionMap = new DrawRegionMap();
- /** OpenLayers 地图在容器尺寸变化后不会自动重算 canvas,需手动 updateSize */
- const resizeMapToContainer = () => {
- nextTick(() => {
- requestAnimationFrame(() => {
- drawRegionMap.kmap?.map?.updateSize?.();
- });
- });
- };
- // handleVarietyClick 内部有延迟渲染(setTimeout),切换查看/编辑或重建地图时必须取消,避免旧回调在新实例上再次 addFeature 造成叠加
- const pendingGeomRenderTimer = ref(null);
- const clearPendingGeomRenderTimer = () => {
- if (pendingGeomRenderTimer.value) {
- clearTimeout(pendingGeomRenderTimer.value);
- pendingGeomRenderTimer.value = null;
- }
- };
- const confirmDrawTypePopupRef = ref(null);
- const selectedDrawTypeMeta = ref({
- type: "",
- category: "",
- remark: "",
- });
- // 每个品种(tab)对应的地块数据草稿:key 为 tab index,value 为 { geomArr, geom }(与当前大类 Tab 一致时同步)
- const regionsDraftByIndex = ref({});
- /** 按弹窗确认的「大类 + 小类」存几何与备注,避免与地图当前选中 Tab 不一致 */
- const regionsDraftByKey = ref({});
- const submitting = ref(false);
- // 仅在首次进入页面时根据路由 varietyId 定位一次,避免后续刷新覆盖当前流程进度
- const hasAppliedInitialVariety = ref(false);
- const selectedDeleteFeature = ref(null);
- const deleteSelectClickKey = ref(null);
- const selectedDeleteFeatureUid = ref("");
- const selectedDeleteFeatureOriginalStyle = ref(null);
- const type = ref(null);
- const varietyTabs = ref([]);
- const activeVariety = ref(0);
- const regionGeom = ref(null);
- /** 切换「大类」Tab 时子类列表完全替换,regionsDraftByIndex 按下标缓存会与新区块列表错位;保存成功后也应以接口为准 */
- const clearDraftIndexOnly = () => {
- regionsDraftByIndex.value = {};
- };
- const clearAllRegionDrafts = () => {
- regionsDraftByKey.value = {};
- regionsDraftByIndex.value = {};
- };
- const clearDeleteSelectionState = () => {
- if (selectedDeleteFeature.value) {
- selectedDeleteFeature.value.setStyle(selectedDeleteFeatureOriginalStyle.value ?? undefined);
- selectedDeleteFeature.value = null;
- }
- selectedDeleteFeatureUid.value = "";
- selectedDeleteFeatureOriginalStyle.value = null;
- if (deleteSelectClickKey.value) {
- unByKey(deleteSelectClickKey.value);
- deleteSelectClickKey.value = null;
- }
- };
- const getDeleteHighlightStyle = () => {
- const styleKind = getCanonicalRegionTypeForStyles();
- if (styleKind === "variety") {
- return {
- fillColor: "rgba(24, 170, 139, 0.5)",
- strokeColor: "#18AA8B",
- };
- }
- if (styleKind === "ABNORMAL") {
- if (activeVariety.value < 2) {
- return {
- fillColor: "rgba(224, 49, 49, 0.5)",
- strokeColor: "#E03131",
- }
- } else {
- return {
- fillColor: "rgba(255, 115, 0, 0.5)",
- strokeColor: "#FF7300",
- };
- }
- }
- if (styleKind === "ENVIRONMENT") {
- return {
- fillColor: "rgba(255, 199, 102, 0.5)",
- strokeColor: "#FDCF7F",
- };
- }
- // 其余类型先给默认占位色,后续可按产品视觉再调整
- return {
- fillColor: "rgba(229, 229, 229, 0.5)",
- strokeColor: "#A6A6A6",
- };
- };
- const highlightDeleteSelectedFeature = (feature) => {
- if (!feature) return;
- if (selectedDeleteFeature.value === feature) return;
- if (selectedDeleteFeature.value && selectedDeleteFeature.value !== feature) {
- selectedDeleteFeature.value.setStyle(selectedDeleteFeatureOriginalStyle.value ?? undefined);
- }
- selectedDeleteFeatureOriginalStyle.value = feature.getStyle ? feature.getStyle() : null;
- selectedDeleteFeature.value = feature;
- selectedDeleteFeatureUid.value = String(feature?.ol_uid || feature?.getId?.() || "");
- const { fillColor, strokeColor } = getDeleteHighlightStyle();
- // 删除选择态:按当前类型动态高亮(单选)
- feature.setStyle(
- new Style({
- fill: new Fill({ color: fillColor }),
- stroke: new Stroke({ color: strokeColor, width: 2 }),
- })
- );
- };
- const isFeatureLockedForDelete = (feature) => {
- if (activeRegionType.value === "variety") return false;
- const growth = getAbnormalGrowthOverlayMeta(feature);
- const status = String(growth?.handleStatus || "");
- return status === "2" || status === "3";
- };
- const enableDeleteSelectionMode = () => {
- const map = drawRegionMap.kmap?.map;
- const polygonLayer = drawRegionMap.kmap?.polygonLayer?.layer;
- if (!map || !polygonLayer || deleteSelectClickKey.value) return;
- deleteSelectClickKey.value = map.on("singleclick", (evt) => {
- let hitFeature = null;
- map.forEachFeatureAtPixel(
- evt.pixel,
- (feature) => {
- hitFeature = feature;
- return true;
- },
- {
- layerFilter: (layer) => layer === polygonLayer,
- }
- );
- if (hitFeature) {
- if (isFeatureLockedForDelete(hitFeature)) {
- ElMessage.warning("该地块不可删除");
- return;
- }
- highlightDeleteSelectedFeature(hitFeature);
- }
- });
- };
- const handleRegionTypeClick = (item) => {
- clearDeleteSelectionState();
- const prevMajor = activeRegionType.value;
- if (item.code !== prevMajor) {
- clearDraftIndexOnly();
- }
- activeRegionType.value = item.code;
- activeVariety.value = 0;
- if (item.code === "variety") {
- varietyTabs.value = regionInfo.value.regionList || [];
- } else {
- varietyTabs.value = item.children;
- }
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- if (drawRegionMap.kmap) {
- applyRegionStyles();
- }
- };
- // 删除当前地块
- const deletePolygon = () => {
- const features = drawRegionMap.kmap?.polygonLayer?.source?.getFeatures?.() || [];
- const deletableFeatures = features.filter((f) => !isFeatureLockedForDelete(f));
- let matchedFeature = null;
- if (features.length === 0) {
- clearDeleteSelectionState();
- ElMessage.warning("当前没有可删除的地块");
- return;
- }
- if (features.length === 1 && deletableFeatures.length === 0) {
- clearDeleteSelectionState();
- ElMessage.warning("当前地块不可删除");
- return;
- }
- if (features.length > 1) {
- if (deletableFeatures.length === 0) {
- clearDeleteSelectionState();
- ElMessage.warning("当前地块不可删除");
- return;
- }
- if (!selectedDeleteFeature.value) {
- enableDeleteSelectionMode();
- ElMessage.warning("当前有多个地块,请先选择地块");
- return;
- }
- matchedFeature = features.find((f) => {
- if (f === selectedDeleteFeature.value) return true;
- const uid = String(f?.ol_uid || f?.getId?.() || "");
- return uid && uid === selectedDeleteFeatureUid.value;
- });
- if (!matchedFeature) {
- selectedDeleteFeature.value = null;
- selectedDeleteFeatureUid.value = "";
- ElMessage.warning("请选择地块后再删除");
- return;
- }
- if (isFeatureLockedForDelete(matchedFeature)) {
- clearDeleteSelectionState();
- ElMessage.warning("该地块不可删除");
- return;
- }
- } else {
- clearDeleteSelectionState();
- }
- ElMessageBox.confirm(
- "确认删除当前地块吗?删除后可以重新勾画。",
- "删除确认",
- {
- confirmButtonText: "确认删除",
- cancelButtonText: "取消",
- type: "warning",
- }
- ).then(async () => {
- if (selectedDeleteFeature.value && features.length > 1) {
- drawRegionMap.kmap?.polygonLayer?.source?.removeFeature?.(matchedFeature || selectedDeleteFeature.value);
- clearDeleteSelectionState();
- } else {
- drawRegionMap.deleteCurrentPolygon();
- }
- const polygonData = drawRegionMap.getAreaGeometry?.();
- const geometryArr = polygonData?.geometryArr || [];
- try {
- const ok = await submitCurrentTabGeometryChange(geometryArr);
- if (!ok) {
- ElMessage.error("删除后保存失败");
- return;
- }
- await fetchRegionInfo();
- clearAllRegionDrafts();
- if (activeRegionType.value === "variety") {
- varietyTabs.value = regionInfo.value?.regionList || [];
- } else {
- const group = (regionTypeTabs.value || []).find(
- (x) => String(x?.code) === String(activeRegionType.value)
- );
- varietyTabs.value = group?.children || [];
- }
- const nextIndex = Math.min(activeVariety.value, Math.max(varietyTabs.value.length - 1, 0));
- activeVariety.value = nextIndex;
- if (varietyTabs.value.length > 0 && varietyTabs.value[nextIndex]) {
- handleVarietyClick(varietyTabs.value[nextIndex], nextIndex);
- } else {
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- drawRegionMap.fitAllRegions?.();
- }
- ElMessage.success("地块已删除");
- } catch (_) {
- ElMessage.error("删除后保存失败");
- }
- });
- };
- /** 取 Polygon / MultiPolygon 的外环坐标(地图投影),用于顶点样式 */
- const getOuterRingCoordinates = (geometry) => {
- if (!geometry || typeof geometry.getType !== "function") return [];
- const type = geometry.getType();
- const c = geometry.getCoordinates();
- if (!c || !c.length) return [];
- if (type === "Polygon") return c[0] || [];
- if (type === "MultiPolygon") return c[0]?.[0] || [];
- return [];
- };
- const createPolygonStyleFunc = (fillColor, strokeColor) => {
- return (feature) => {
- const growth = getAbnormalGrowthOverlayMeta(feature);
- const isTreatingDisease =
- String(growth?.handleStatus || "") === "2" && !String(growth?.badgeText || "").includes("长势");
- const isTreatingGrowth =
- String(growth?.handleStatus || "") === "2" && String(growth?.badgeText || "").includes("长势");
- const isControlledDisease = String(growth?.handleStatus || "") === "3";
- const currentFillColor = isControlledDisease
- ? SLEEP_ZONE_FILL_COLOR
- : isTreatingGrowth
- ? "rgba(255, 159, 102, 0.3)"
- : isTreatingDisease
- ? DISEASE_TREATING_FILL_COLOR
- : fillColor;
- const currentStrokeColor = isControlledDisease
- ? SLEEP_ZONE_STROKE_COLOR
- : isTreatingGrowth
- ? "rgba(255, 94, 0, 0.3)"
- : isTreatingDisease
- ? DISEASE_TREATING_STROKE_COLOR
- : strokeColor;
- const styles = [];
- const ring = getOuterRingCoordinates(feature.getGeometry());
- const ringLen = ring.length;
- const vertexCount = ringLen > 1 ? ringLen - 1 : ringLen;
- for (let i = 0; i < vertexCount; i++) {
- if (i % 2) {
- styles.push(
- new Style({
- geometry: new Point(ring[i]),
- image: new Circle({
- radius: 4,
- fill: new Fill({
- color: currentStrokeColor,
- }),
- stroke: new Stroke({
- color: "#fff",
- width: 1,
- }),
- }),
- })
- );
- } else {
- styles.push(
- new Style({
- geometry: new Point(ring[i]),
- image: new Circle({
- radius: 6,
- fill: new Fill({
- color: "#fff",
- }),
- }),
- })
- );
- }
- }
- const fillStyle = new Style({
- fill: new Fill({
- color: currentFillColor,
- }),
- stroke: new Stroke({
- color: currentStrokeColor,
- width: 2,
- }),
- });
- let geom = feature.getGeometry().clone();
- geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
- let area = getArea(geom);
- area = (area + area / 2) / 1000;
- if (growth) {
- styles.push(
- new Style({
- text: new Text({
- text: growth.badgeText,
- font: "bold 13px sans-serif",
- fill: new Fill({ color: growth.badgeTextColor || "#ffffff" }),
- backgroundFill: new Fill({ color: growth.badgeBackground || ABNORMAL_BADGE_BG_GROWTH }),
- backgroundStroke: growth.badgeBorderColor
- ? new Stroke({ color: growth.badgeBorderColor, width: 1 })
- : undefined,
- padding: [4, 10, 4, 10],
- offsetY: -40,
- }),
- }),
- new Style({
- text: new Text({
- text: `发现时间:${growth.discoveryDate}`,
- font: "12px sans-serif",
- fill: new Fill({ color: "#ffffff" }),
- offsetY: -16,
- }),
- })
- );
- }
- const areaValStyle = new Style({
- text: new Text({
- font: "16px sans-serif",
- text: area.toFixed(2) + "亩",
- fill: new Fill({ color: "#fff" }),
- offsetY: growth ? 14 : 0,
- }),
- });
- styles.push(fillStyle, areaValStyle);
- return styles;
- };
- };
- const applyRegionStyles = () => {
- const kmap = drawRegionMap.kmap;
- if (!kmap) return;
- let lineColor = "#2199F8";
- let vertexColor = "#2199F8";
- let fillColor = [0, 0, 0, 0.5];
- let strokeColor = "#2199F8";
- const styleKind = getCanonicalRegionTypeForStyles();
- if (styleKind === "variety") {
- lineColor = "#18AA8B";
- vertexColor = "#18AA8B";
- fillColor = [0, 57, 44, 0.5];
- strokeColor = "#18AA8B";
- } else if (styleKind === "ABNORMAL") {
- if (activeVariety.value < 2) {
- lineColor = "#E03131";
- vertexColor = "#E03131";
- fillColor = [100, 0, 0, 0.5];
- strokeColor = "#E03131";
- } else {
- lineColor = "#FF7300";
- vertexColor = "#FF7300";
- fillColor = [124, 46, 0, 0.5];
- strokeColor = "#FF7300";
- }
- } else if (styleKind === "ENVIRONMENT") {
- lineColor = "#FDCF7F";
- vertexColor = "#FDCF7F";
- fillColor = [151, 96, 0, 0.5];
- strokeColor = "#FDCF7F";
- } else {
- lineColor = "#A6A6A6";
- vertexColor = "#A6A6A6";
- fillColor = [166, 166, 166, 0.25];
- strokeColor = "#A6A6A6";
- }
- // 勾画进行中:通过 Map.drawStyleColors 影响底层 drawStyleFunc 颜色
- KMapMap.drawStyleColors = {
- line: lineColor,
- vertex: vertexColor,
- // 只读回显时也复用当前大类颜色(避免查看态全部变绿)
- fill: fillColor,
- stroke: strokeColor,
- };
- kmap.polygonStyle = createPolygonStyleFunc(fillColor, strokeColor);
- if (kmap.polygonLayer?.layer && typeof kmap.polygonLayer.layer.setStyle === "function") {
- kmap.polygonLayer.layer.setStyle(kmap.polygonStyle);
- }
- };
- const isValidGeom = (geom) => {
- if (typeof geom !== "string") return false;
- const normalized = geom.trim();
- if (!normalized) return false;
- if (["[]", "{}", "null", "undefined", '""'].includes(normalized)) return false;
- return true;
- };
- /** 与 drawRegionMap / setAreaGeometry 一致:WKT 按 WGS84 解析 */
- const WGS84_WKT_OPTS = Object.freeze({
- dataProjection: "EPSG:4326",
- featureProjection: "EPSG:4326",
- });
- const wktGeomFormat = new WKT();
- /** Polygon / MultiPolygon 各自对应一组环坐标(MultiPolygon 为多块) */
- const polygonCoordSetsFromGeometry = (geometry) => {
- if (!geometry || typeof geometry.getType !== "function") return [];
- const type = geometry.getType();
- if (type === "Polygon") return [geometry.getCoordinates()];
- if (type === "MultiPolygon") return geometry.getCoordinates();
- return [];
- };
- const mergePolygonWktsForApi = (wktArr) => {
- if (!Array.isArray(wktArr) || wktArr.length === 0) return "";
- const trimmed = wktArr.map((x) => String(x).trim()).filter(isValidGeom);
- if (trimmed.length === 0) return "";
- if (trimmed.length === 1) return trimmed[0];
- const coordSets = [];
- for (const w of trimmed) {
- try {
- const g = wktGeomFormat.readGeometry(w, WGS84_WKT_OPTS);
- coordSets.push(...polygonCoordSetsFromGeometry(g));
- } catch (_) {
- /* 单条解析失败则跳过 */
- }
- }
- if (coordSets.length === 0) return trimmed[0];
- if (coordSets.length === 1) {
- try {
- return wktGeomFormat.writeGeometry(new Polygon(coordSets[0]), WGS84_WKT_OPTS);
- } catch (_) {
- return trimmed[0];
- }
- }
- return wktGeomFormat.writeGeometry(new MultiPolygon(coordSets), WGS84_WKT_OPTS);
- };
- const flattenWktToPolygonWktArray = (wkt) => {
- if (!isValidGeom(wkt)) return [];
- const s = String(wkt).trim();
- try {
- const g = wktGeomFormat.readGeometry(s, WGS84_WKT_OPTS);
- const sets = polygonCoordSetsFromGeometry(g);
- if (sets.length === 0) return [s];
- return sets.map((rings) => wktGeomFormat.writeGeometry(new Polygon(rings), WGS84_WKT_OPTS));
- } catch (_) {
- return [s];
- }
- };
- const flattenGeomWktList = (arr) => {
- if (!Array.isArray(arr)) return [];
- const out = [];
- for (const item of arr) {
- if (!isValidGeom(item)) continue;
- out.push(...flattenWktToPolygonWktArray(String(item).trim()));
- }
- return out;
- };
- /** 地图回显:展开 MULTIPOLYGON 为多个 POLYGON WKT,并去掉无效项 */
- const flattenGeomWktListForMap = (arr) => flattenGeomWktList(arr).filter(isValidGeom);
- const formatAreaTextFromGeomArr = (geomWktArr) => {
- if (!Array.isArray(geomWktArr) || geomWktArr.length === 0) return "";
- let totalArea = 0;
- for (const wkt of geomWktArr) {
- if (!isValidGeom(wkt)) continue;
- try {
- const geometry = wktGeomFormat.readGeometry(String(wkt).trim(), WGS84_WKT_OPTS);
- geometry.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
- let area = getArea(geometry);
- area = (area + area / 2) / 1000;
- totalArea += Number(area) || 0;
- } catch (_) {
- /* 单条解析失败则跳过 */
- }
- }
- return totalArea > 0 ? `${totalArea.toFixed(2)}亩` : "";
- };
- // 从 tab.geomItems[{geomWkt}] 中提取 WKT 数组(兼容单块/多块)
- const getGeomArrFromGeomItems = (tab) => {
- const items = tab?.geomItems;
- if (!Array.isArray(items) || items.length === 0) return [];
- return items
- .map((x) => x?.geomWkt)
- .filter((x) => x !== undefined && x !== null)
- .map((x) => String(x).trim())
- .filter((x) => isValidGeom(x));
- };
- const isDiseaseAbnormalTab = (tab) => {
- const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
- return name.includes("病害") || name.includes("虫害");
- };
- const isGrowthAbnormalTab = (tab) => {
- const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
- return name.includes("过慢") || name.includes("过快");
- };
- const hasLockedStatusItems = (tab) => {
- const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
- return items.some((item) => {
- const status = String(item?.handleStatus ?? "");
- return status === "2" || status === "3";
- });
- };
- const isLockedAbnormalTab = (tab) =>
- isDiseaseAbnormalTab(tab) || isGrowthAbnormalTab(tab) || hasLockedStatusItems(tab);
- const isLockedAbnormalItem = (tab, item) => {
- const status = String(item?.handleStatus ?? "");
- if (!isLockedAbnormalTab(tab)) return false;
- return status === "2" || status === "3";
- };
- const formatDateToYmdDot = (raw) => {
- const sourceDate = raw ? new Date(raw) : new Date();
- const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
- const y = resolvedDate.getFullYear();
- const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
- const d = String(resolvedDate.getDate()).padStart(2, "0");
- return `${y}.${m}.${d}`;
- };
- const formatDateToYmdDash = (raw) => {
- const sourceDate = raw ? new Date(raw) : new Date();
- const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
- const y = resolvedDate.getFullYear();
- const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
- const d = String(resolvedDate.getDate()).padStart(2, "0");
- return `${y}-${m}-${d}`;
- };
- const getLockedDiseaseRegions = (tab) => {
- if (!isLockedAbnormalTab(tab)) return [];
- const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
- const out = [];
- items.forEach((item) => {
- if (!isLockedAbnormalItem(tab, item)) return;
- const status = String(item?.handleStatus ?? "");
- const polygons = flattenWktToPolygonWktArray(item?.geomWkt).filter(isValidGeom);
- polygons.forEach((geometry) => {
- const name = (tab?.problemZoneTypeName || tab?.regionName || "").toString();
- const isGrowth = name.includes("过慢") || name.includes("过快");
- const prefix = name.includes("虫害")
- ? "虫害"
- : name.includes("病害")
- ? "病害"
- : name.includes("过快")
- ? "长势过快"
- : "长势过慢";
- out.push({
- geometry,
- displayMode: "lockedDisease",
- handleStatus: status,
- lockStyleType: isGrowth ? "growthTreating" : status === "3" ? "controlled" : "diseaseTreating",
- label: status === "3" ? `${prefix}已控制` : `${prefix}治疗中`,
- updatedTime: formatDateToYmdDot(item?.createTime),
- });
- });
- });
- return out;
- };
- const getEditableGeomArrForCurrentTab = (tab, rawGeomArr) => {
- if (
- !tab ||
- !isLockedAbnormalTab(tab) ||
- viewOnly.value ||
- activeRegionType.value === "variety"
- ) {
- return flattenGeomWktListForMap(rawGeomArr);
- }
- const key = draftKeyFromParts(activeRegionType.value, tab);
- const draft = key ? regionsDraftByKey.value[key] : null;
- const draftItems = Array.isArray(draft?.geomItems) ? draft.geomItems : [];
- const items = draftItems.length > 0 ? draftItems : Array.isArray(tab?.geomItems) ? tab.geomItems : [];
- const editable = [];
- items.forEach((item) => {
- if (isLockedAbnormalItem(tab, item)) return;
- editable.push(...flattenWktToPolygonWktArray(item?.geomWkt));
- });
- const editableGeomArr = flattenGeomWktListForMap(editable);
- if (editableGeomArr.length > 0) return editableGeomArr;
- // 当前小类存在 2/3 锁定地块时,编辑图层保持空,避免把锁定地块误加到可编辑层
- const hasLockedItems = items.some((item) => isLockedAbnormalItem(tab, item));
- if (hasLockedItems) return [];
- // 仅当无锁定地块且没有 geomItems 时,兜底使用纯几何草稿(例如新勾画未落库)
- const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value);
- if (isValidGeom(draftGeom)) {
- return flattenGeomWktListForMap([String(draftGeom).trim()]);
- }
- return editableGeomArr;
- };
- // 兜底防线:若锁定地块意外进入可编辑图层,按 handleStatus(2/3) 强制移除,避免只读层与可编辑层叠加
- const purgeLockedFeaturesFromEditableLayer = (tab) => {
- if (!isLockedAbnormalTab(tab)) return;
- const source = drawRegionMap.kmap?.polygonLayer?.source;
- if (!source) return;
- const features = source.getFeatures?.() || [];
- features.forEach((feature) => {
- const geomItem = getGeomItemByFeature(tab, feature);
- const status = String(geomItem?.handleStatus ?? "");
- if (status === "2" || status === "3") {
- source.removeFeature?.(feature);
- }
- });
- };
- /** 非品种区提交:按当前地块顺序生成 geomItems(已有项更新,新项追加) */
- const buildGeomItemsFromGeometryArr = (tab, geometryArr, remarkOverride) => {
- const geomArr = flattenGeomWktListForMap(geometryArr || []);
- const existingItems = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
- const nowDate = new Date();
- const y = nowDate.getFullYear();
- const m = String(nowDate.getMonth() + 1).padStart(2, "0");
- const d = String(nowDate.getDate()).padStart(2, "0");
- const defaultCreateTime = `${y}-${m}-${d}`;
- return geomArr.map((geomWkt, idx) => {
- const base = existingItems[idx] && typeof existingItems[idx] === "object" ? existingItems[idx] : {};
- const baseRemark = base?.remark != null ? String(base.remark) : "";
- const finalRemark = remarkOverride != null ? String(remarkOverride) : baseRemark;
- const createTime =
- base?.createTime != null && String(base.createTime).trim() !== ""
- ? String(base.createTime)
- : defaultCreateTime;
- const handleStatus =
- base?.handleStatus != null && String(base.handleStatus).trim() !== ""
- ? base.handleStatus
- : 1;
- return {
- ...base,
- geomWkt,
- remark: finalRemark,
- createTime,
- handleStatus,
- };
- });
- };
- /** 病害编辑态提交:2/3 保持只读原样,1 与新勾画项按当前可编辑地块更新/追加 */
- const buildDiseaseGeomItemsForSubmit = (tab, editableGeometryArr, remarkOverride) => {
- const items = Array.isArray(tab?.geomItems) ? tab.geomItems : [];
- const lockedItems = items
- .filter((item) => {
- const status = String(item?.handleStatus ?? "");
- return status === "2" || status === "3";
- })
- .map((item) => ({
- ...item,
- geomWkt: isValidGeom(item?.geomWkt) ? String(item.geomWkt).trim() : "",
- remark: item?.remark != null ? String(item.remark) : "",
- }))
- .filter((item) => isValidGeom(item.geomWkt));
- const editableBaseItems = items.filter((item) => {
- const status = String(item?.handleStatus ?? "");
- return status === "1" || status === "";
- });
- const geomArr = flattenGeomWktListForMap(editableGeometryArr || []);
- const editableItems = geomArr.map((geomWkt, idx) => {
- const base =
- editableBaseItems[idx] && typeof editableBaseItems[idx] === "object"
- ? editableBaseItems[idx]
- : {};
- const baseRemark = base?.remark != null ? String(base.remark) : "";
- const finalRemark = remarkOverride != null ? String(remarkOverride) : baseRemark;
- const createTime =
- base?.createTime != null && String(base.createTime).trim() !== ""
- ? String(base.createTime)
- : formatDateToYmdDot();
- const handleStatus =
- base?.handleStatus != null && String(base.handleStatus).trim() !== ""
- ? String(base.handleStatus)
- : 1;
- return {
- ...base,
- geomWkt,
- remark: finalRemark,
- createTime: formatDateToYmdDash(createTime),
- handleStatus,
- };
- });
- return [...lockedItems, ...editableItems];
- };
- const draftKeyFromParts = (majorType, tabLike) => {
- if (!majorType || !tabLike || typeof tabLike !== "object") return "";
- const t = tabLike;
- const id = t.regionId ?? t.problemZoneTypeId ?? t.typeId ?? t.id;
- if (id != null && String(id).trim() !== "") {
- return `${majorType}:${String(id).trim()}`;
- }
- const nm = (t.regionName || t.problemZoneTypeName || "").toString().trim();
- return nm ? `${majorType}:name:${nm}` : "";
- };
- const getDraftGeomForTab = (majorType, tab, index) => {
- const k = draftKeyFromParts(majorType, tab);
- const byKey = k ? regionsDraftByKey.value[k] : null;
- if (byKey?.geom) return byKey.geom;
- return regionsDraftByIndex.value[index]?.geom;
- };
- const resolveInitialVarietyIndex = (regionList) => {
- const queryVarietyId = route.query?.varietyId;
- if (!queryVarietyId || !Array.isArray(regionList) || regionList.length === 0) return 0;
- const queryId = String(queryVarietyId).trim();
- const matchedIndex = regionList.findIndex((item) => {
- const candidateIds = [item?.typeId, item?.varietyId, item?.id]
- .filter((v) => v !== undefined && v !== null)
- .map((v) => String(v).trim());
- return candidateIds.includes(queryId);
- });
- return matchedIndex >= 0 ? matchedIndex : 0;
- };
- const getReadonlyVarietyRegions = (activeIndex) => {
- if (!Array.isArray(varietyTabs.value) || varietyTabs.value.length === 0) return [];
- const readonlyRegions = [];
- for (let i = 0; i < varietyTabs.value.length; i++) {
- if (i === activeIndex) continue;
- const tab = varietyTabs.value[i];
- if (!tab) continue;
- const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, i);
- const geometry = draftGeom || tab.geom;
- if (!isValidGeom(geometry)) continue;
- readonlyRegions.push({
- geometry,
- label: tab.regionName || "",
- displayMode: "readonlyVariety",
- });
- }
- return readonlyRegions;
- };
- const getAllVarietyReadonlyRegions = () => {
- const list = regionInfo.value?.regionList || [];
- if (!Array.isArray(list) || list.length === 0) return [];
- const regions = [];
- for (let i = 0; i < list.length; i++) {
- const tab = list[i];
- if (!tab) continue;
- const draftGeom = getDraftGeomForTab("variety", tab, i);
- const geometry = draftGeom || tab.geom;
- if (!isValidGeom(geometry)) continue;
- regions.push({
- geometry,
- label: tab.regionName || "",
- displayMode: "readonlyVariety",
- });
- }
- return regions;
- };
- const renderReadonlyVarietyRegions = (activeIndex) => {
- if (drawRegionMap && typeof drawRegionMap.setStatusRegions === "function") {
- drawRegionMap.setStatusRegions(getReadonlyVarietyRegions(activeIndex));
- }
- };
- /**
- * 仅渲染「品种区」所有已有地块(只读),用于在其它问题区查看/编辑时作为底图参考。
- * 需求:只显示品种区地块,其它问题区地块不显示,且不可编辑。
- */
- const renderAllVarietyRegionsReadonlyOnly = () => {
- if (!drawRegionMap || typeof drawRegionMap.setStatusRegions !== "function") return;
- const regions = getAllVarietyReadonlyRegions();
- if (regions.length === 0) {
- drawRegionMap.setStatusRegions([]);
- return;
- }
- drawRegionMap.setStatusRegions(regions);
- };
- /**
- * 用户“闭环(drawend)”后,把当前绘制结果自动写入草稿,避免切换 tab 时被清掉。
- * 注意:getAreaGeometry 可能包含多块,这里以“最新一块”为准(最后一个 feature)
- */
- const syncClosedDrawToDraft = () => {
- const tab = varietyTabs.value?.[activeVariety.value];
- if (!tab) return;
- const polygonData = drawRegionMap.getAreaGeometry?.();
- const geometryArr = polygonData?.geometryArr;
- if (!Array.isArray(geometryArr) || geometryArr.length === 0) return;
- const geom = String(geometryArr[geometryArr.length - 1] || "").trim();
- if (!isValidGeom(geom)) return;
- const key = draftKeyFromParts(activeRegionType.value, tab);
- if (key) {
- const prev = regionsDraftByKey.value[key] || {};
- regionsDraftByKey.value[key] = {
- ...prev,
- geomArr: [geom],
- geom,
- };
- }
- regionsDraftByIndex.value[activeVariety.value] = { geomArr: [geom], geom };
- regionGeom.value = geom;
- };
- const handleVarietyClick = (tab, index) => {
- clearDeleteSelectionState();
- // 取消上一次延迟渲染,避免快速切换 tab / 切换编辑态时重复追加要素
- clearPendingGeomRenderTimer();
- // 若存在“未闭环”的绘制草图(Draw 的 sketch 在 overlay 上),切换时需要清掉;已闭环的要素会走 drawend 写入草稿
- const draw = drawRegionMap.kmap?.draw;
- const hasUnclosedSketch = !!(draw && (draw.sketchFeature_ || draw.sketchCoords_ || draw.sketchLine_));
- if (hasUnclosedSketch) {
- drawRegionMap.abortOngoingDrawSketch?.();
- }
- activeVariety.value = index;
- // 取值优先级:
- // 1) 草稿(regionsDraftByKey / regionsDraftByIndex)
- // 2) 接口返回的 tab.geomItems[].geomWkt(你给的结构)
- // 3) 兜底 tab.geom(兼容旧结构)
- const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, index);
- const rawGeomArr =
- isValidGeom(draftGeom)
- ? [String(draftGeom).trim()]
- : (() => {
- const fromItems = getGeomArrFromGeomItems(tab);
- if (fromItems.length) return fromItems;
- return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
- })();
- const geomArr = getEditableGeomArrForCurrentTab(tab, rawGeomArr);
- // 保留一份“当前选中地块”的原始字符串形态给其它逻辑使用
- regionGeom.value = geomArr.length > 1 ? JSON.stringify(geomArr) : geomArr[0] || "";
- // 地图尚未初始化时,仅更新状态,不做图层绘制,避免首次进入重复叠加
- if (!drawRegionMap.kmap) return;
- // 确保当前大类颜色样式生效(非品种区:黄色/红色/灰色;品种区:绿色)
- applyRegionStyles();
- // 非品种区:底图显示品种区(只读绿色)+ 当前小类(按当前大类颜色渲染)
- // 品种区:显示其他品种只读 + 当前品种可编辑
- if (activeRegionType.value !== "variety") {
- if (isLockedAbnormalTab(tab)) {
- drawRegionMap.setStatusRegions([
- ...getAllVarietyReadonlyRegions(),
- ...getLockedDiseaseRegions(tab),
- ]);
- // 查看态也需要继续渲染当前小类地块(handleStatus=1),
- // 2/3 锁定地块仍由 setStatusRegions 只读层承载,避免被遗漏
- } else {
- renderAllVarietyRegionsReadonlyOnly();
- }
- } else {
- renderReadonlyVarietyRegions(index);
- }
- // 切换时先清空当前可编辑图层,避免叠加
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- if (geomArr.length > 0) {
- pendingGeomRenderTimer.value = setTimeout(() => {
- // 再兜底清一次,避免异步回调在 destroy/init 后仍执行导致重复追加
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- const viewOnlySubCategoryLabel =
- route.query.type === "viewOnly" && getCanonicalRegionTypeForStyles() !== "ABNORMAL"
- ? (tab.regionName || tab.problemZoneTypeName || "").toString().trim()
- : "";
- const currentAreaText = formatAreaTextFromGeomArr(geomArr);
- const viewOnlySubCategoryLabelWithArea =
- viewOnlySubCategoryLabel && currentAreaText
- ? `${viewOnlySubCategoryLabel}\n${currentAreaText}`
- : viewOnlySubCategoryLabel;
- drawRegionMap.setAreaGeometry(
- geomArr,
- false,
- undefined,
- undefined,
- getAbnormalGrowthOverlayMeta,
- viewOnlySubCategoryLabelWithArea || undefined
- );
- if (!viewOnly.value && activeRegionType.value !== "variety") {
- purgeLockedFeaturesFromEditableLayer(tab);
- }
- // 有当前小类地块:仅 fit 当前可编辑层,避免被其它只读参考地块拉远
- drawRegionMap.fitView?.();
- }, 50);
- } else {
- // 当前小类无地块:视野包含品种区只读底图(与其它参考地块联合范围)
- pendingGeomRenderTimer.value = setTimeout(() => {
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- drawRegionMap.fitAllRegions?.();
- }, 50);
- }
- };
- const viewOnly = computed(() => route.query.type === "viewOnly");
- const point = ref(null);
- const regionTypeTabs = ref([]);
- const activeRegionType = ref("variety");
- const regionInfo = ref([]);
- async function fetchRegionInfo() {
- const { data } = await VE_API.basic_farm.fetchRegionInfo({ subjectId: route.query.subjectId });
- if (data && data.length > 0) {
- regionInfo.value = data[0] || [];
- regionTypeTabs.value = regionInfo.value.problemZoneList || [];
- regionTypeTabs.value.unshift({ name: "品种区", code: "variety" });
- if (data[0]?.regionList?.length) {
- if (!hasAppliedInitialVariety.value && route.query?.varietyId) {
- activeVariety.value = resolveInitialVarietyIndex(data[0]?.regionList);
- hasAppliedInitialVariety.value = true;
- }
- point.value = data[0].point;
- varietyTabs.value = regionInfo.value.regionList || [];
- }
- }
- }
- /** 样式用的大类:与接口 tab.code 解耦(避免 ABNORMAL / 数字 code 等导致勾画色落到默认灰) */
- const getCanonicalRegionTypeForStyles = () => {
- const raw = activeRegionType.value;
- if (raw === "variety") return "variety";
- const tabs = regionTypeTabs.value || [];
- const item = tabs.find((t) => String(t?.code) === String(raw));
- if (item) {
- const kind = item.code;
- if (kind) return kind;
- }
- return "SLEEP";
- };
- const ABNORMAL_BADGE_BG_DISEASE_PEST = "#E32A28";
- const ABNORMAL_BADGE_BG_GROWTH = "#F76F00";
- const DISEASE_TREATING_FILL_COLOR = "rgba(191, 91, 91, 0.36)";
- const DISEASE_TREATING_STROKE_COLOR = "rgba(224, 49, 49, 0.3)";
- const SLEEP_ZONE_STROKE_COLOR = "#A6A6A6";
- const SLEEP_ZONE_FILL_COLOR = [166, 166, 166, 0.25];
- const getGeomItemByFeature = (tab, feature) => {
- if (!tab || !feature) return null;
- const items = Array.isArray(tab.geomItems) ? tab.geomItems : [];
- if (!items.length) return null;
- let featureWkt = "";
- try {
- featureWkt = wktGeomFormat.writeGeometry(feature.getGeometry(), WGS84_WKT_OPTS);
- } catch (_) {
- featureWkt = "";
- }
- if (!isValidGeom(featureWkt)) return null;
- const normalizeWktKey = (wkt) => String(wkt || "").replace(/\s+/g, "").trim();
- const normalizedFeatureWkt = normalizeWktKey(featureWkt);
- for (const item of items) {
- const polygons = flattenWktToPolygonWktArray(item?.geomWkt);
- if (polygons.some((w) => normalizeWktKey(w) === normalizedFeatureWkt)) {
- return item;
- }
- }
- // 兜底:仅在要素数量与 geomItems 数量一致时按索引映射,避免病害锁定态错位
- const features = drawRegionMap.kmap?.polygonLayer?.source?.getFeatures?.() || [];
- if (features.length === items.length) {
- const featureIndex = features.findIndex((f) => f === feature);
- if (featureIndex >= 0 && featureIndex < items.length) {
- return items[featureIndex];
- }
- }
- return null;
- };
- /** 异常区小类(长势/病害/虫害等)闭合地块后在多边形内展示标签与发现日期(查看态 setAreaGeometry 同步使用) */
- const getAbnormalGrowthOverlayMeta = (feature) => {
- if (getCanonicalRegionTypeForStyles() !== "ABNORMAL") return null;
- const tab = varietyTabs.value?.[activeVariety.value];
- if (!tab) return null;
- const geomItem = getGeomItemByFeature(tab, feature);
- const name = (tab.problemZoneTypeName || tab.regionName || "").toString();
- const handleStatus = String(geomItem?.handleStatus || "");
- let badgeText = "";
- let badgeBackground = ABNORMAL_BADGE_BG_GROWTH;
- let badgeTextColor = "#ffffff";
- let badgeBorderColor = "";
- if (name.includes("病害") || name.includes("虫害")) {
- // 病害/虫害:严格按当前地块对应 geomItem;未命中(新勾画)按“新增”展示,避免误用旧地块状态
- const currentGeomItem = geomItem || null;
- const currentHandleStatus = String(currentGeomItem?.handleStatus || "");
- const currentCreateTime = currentGeomItem?.createTime;
- const prefix = name.includes("虫害") ? "虫害" : "病害";
- if (!currentGeomItem) {
- badgeText = `新增${prefix}`;
- badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
- } else if (currentHandleStatus === "2") {
- badgeText = `${prefix}治疗中`;
- badgeTextColor = ABNORMAL_BADGE_BG_DISEASE_PEST;
- badgeBackground = "#fff";
- badgeBorderColor = ABNORMAL_BADGE_BG_DISEASE_PEST;
- } else if (currentHandleStatus == "3") {
- badgeText = `${prefix}已控制`;
- // 已控制态与休眠区视觉保持一致
- badgeBackground = SLEEP_ZONE_STROKE_COLOR;
- } else {
- badgeText = `新增${prefix}`;
- badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
- }
- const rawCreateTime = currentCreateTime || tab?.createTime;
- const sourceDate = rawCreateTime ? new Date(rawCreateTime) : new Date();
- const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
- const y = resolvedDate.getFullYear();
- const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
- const d = String(resolvedDate.getDate()).padStart(2, "0");
- return {
- badgeText,
- discoveryDate: `${y}.${m}.${d}`,
- badgeBackground,
- badgeTextColor,
- badgeBorderColor,
- handleStatus: currentHandleStatus,
- };
- } else if (name.includes("过慢")) {
- if (handleStatus === "2") {
- badgeText = "长势过慢治疗中";
- badgeTextColor = ABNORMAL_BADGE_BG_GROWTH;
- badgeBackground = "#fff";
- badgeBorderColor = ABNORMAL_BADGE_BG_GROWTH;
- } else {
- badgeText = "新增长势过慢";
- }
- } else if (name.includes("过快")) {
- if (handleStatus === "2") {
- badgeText = "长势过快治疗中";
- badgeTextColor = ABNORMAL_BADGE_BG_GROWTH;
- badgeBackground = "#fff";
- badgeBorderColor = ABNORMAL_BADGE_BG_GROWTH;
- } else {
- badgeText = "新增长势过快";
- }
- } else {
- return null;
- }
- const rawCreateTime = geomItem?.createTime || tab?.createTime;
- const sourceDate = rawCreateTime ? new Date(rawCreateTime) : new Date();
- const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
- const y = resolvedDate.getFullYear();
- const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
- const d = String(resolvedDate.getDate()).padStart(2, "0");
- return {
- badgeText,
- discoveryDate: `${y}.${m}.${d}`,
- badgeBackground,
- badgeTextColor,
- badgeBorderColor,
- handleStatus,
- };
- };
- const findProblemZoneGroupByDrawType = (majorType) => {
- if (!majorType || majorType === "variety") return null;
- const list = regionInfo.value?.problemZoneList || [];
- return (
- list.find((x) => x.code === majorType) ||
- list.find((x) => String(x?.code) === String(majorType))
- );
- };
- const getTabsForMajorType = (majorType) => {
- if (majorType === "variety") {
- return regionInfo.value?.regionList || [];
- }
- const g = findProblemZoneGroupByDrawType(majorType);
- return g?.children || [];
- };
- const findTabIndexForCategory = (tabs, category) => {
- if (!Array.isArray(tabs) || !category || typeof category !== "object") return -1;
- const fields = ["regionId", "problemZoneTypeId", "typeId", "id"];
- for (let i = 0; i < tabs.length; i++) {
- const t = tabs[i];
- for (const f of fields) {
- const cv = category[f];
- const tv = t[f];
- if (cv != null && cv !== "" && tv != null && String(cv) === String(tv)) return i;
- }
- }
- const cname = (category.regionName || category.problemZoneTypeName || "").toString().trim();
- if (cname) {
- const idx = tabs.findIndex((t) => {
- const tn = (t.regionName || t.problemZoneTypeName || "").toString().trim();
- return tn === cname;
- });
- if (idx >= 0) return idx;
- }
- return -1;
- };
- /** 仅提交本次有勾画草稿(regionsDraftByKey)的品种区,不带未勾画项 */
- const buildRegionListForSubmit = () => {
- const list = regionInfo.value?.regionList || [];
- const out = [];
- list.forEach((tab) => {
- const k = draftKeyFromParts("variety", tab);
- const d = k ? regionsDraftByKey.value[k] : null;
- const geom = d?.geom != null ? String(d.geom).trim() : "";
- if (!isValidGeom(geom)) return;
- out.push({
- regionId: tab.regionId,
- typeId: tab.typeId,
- regionName: tab.regionName,
- geom,
- });
- });
- return out;
- };
- /** 仅包含有勾画草稿的问题分区子项;无任何子项勾画的分组整组不传 */
- const buildProblemZoneListForSubmit = () => {
- const raw = regionInfo.value?.problemZoneList || [];
- const subjectId = route.query.subjectId;
- const groups = [];
- raw.forEach((group) => {
- const canonical = group.code;
- const children = (group.children || [])
- .map((child) => {
- if (!canonical) return null;
- const k = draftKeyFromParts(canonical, child);
- const d = k ? regionsDraftByKey.value[k] : null;
- let geomItems = Array.isArray(d?.geomItems)
- ? d.geomItems
- : [];
- geomItems = geomItems
- .map((x) => {
- const geomWkt = x?.geomWkt != null ? String(x.geomWkt).trim() : "";
- if (!isValidGeom(geomWkt)) return null;
- return {
- ...x,
- geomWkt,
- remark: x?.remark != null ? String(x.remark) : "",
- };
- })
- .filter(Boolean);
- if (geomItems.length === 0) {
- const geomWkt = d?.geom != null ? String(d.geom).trim() : "";
- if (!isValidGeom(geomWkt)) return null;
- const remark =
- d && "remark" in d && d.remark != null ? String(d.remark) : "";
- geomItems = [{ geomWkt, remark }];
- }
- return {
- farmSubjectId: subjectId,
- problemZoneTypeId: child.problemZoneTypeId ?? child.typeId ?? "",
- problemZoneTypeName: child.problemZoneTypeName ?? child.regionName ?? "",
- parentName: group.name ?? "",
- parentCode: group.code ?? "",
- geomItems,
- };
- })
- .filter(Boolean);
- if (children.length === 0) return;
- groups.push({
- name: group.name,
- code: group.code,
- children,
- });
- });
- return groups;
- };
- onActivated(async () => {
- activeVariety.value = 0;
- hasAppliedInitialVariety.value = false;
- clearPendingGeomRenderTimer();
- // keep-alive 场景下再次进入前先销毁旧地图实例,避免重复 init 导致图层状态错位
- if (drawRegionMap.kmap) {
- drawRegionMap.abortOngoingDrawSketch?.();
- drawRegionMap.destroyMap?.();
- }
- type.value = route.query.type;
- const editable = !viewOnly.value;
- await fetchRegionInfo();
- drawRegionMap.initMap(point.value, mapContainer.value, editable, true, true, syncClosedDrawToDraft);
- applyRegionStyles();
- // 首次进入时,fetch 阶段可能先于地图初始化触发了 tab 渲染;
- // 这里在地图可用后再补渲一次,确保“其他品种只读地块”能显示出来
- if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- }
- // 从编辑态进入仅查看时,需重新初始化为不可编辑
- if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
- drawRegionMap.destroyMap();
- drawRegionMap.initMap(point.value, mapContainer.value, false, true, false, syncClosedDrawToDraft);
- applyRegionStyles();
- if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- }
- }
- // 从仅查看进入勾画(编辑)时,需重新初始化为可编辑
- if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
- drawRegionMap.destroyMap();
- drawRegionMap.initMap(point.value, mapContainer.value, true, true, true, syncClosedDrawToDraft);
- applyRegionStyles();
- if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- }
- }
- // 查看模式下已通过 fitAllRegions 适配;编辑模式再设置地图中心
- if (!viewOnly.value) {
- drawRegionMap.setMapPosition(convertPointToArray(point.value));
- }
- resizeMapToContainer();
- });
- watch(
- () => [varietyTabs.value.length, activeRegionType.value, activeVariety.value],
- () => {
- if (drawRegionMap.kmap?.map) resizeMapToContainer();
- }
- );
- onDeactivated(() => {
- clearDeleteSelectionState();
- activeVariety.value = 0;
- activeRegionType.value = "variety";
- clearPendingGeomRenderTimer();
- // 离开页面时中止未完成绘制并销毁地图,确保下次进入是干净实例
- drawRegionMap.abortOngoingDrawSketch?.();
- drawRegionMap.destroyMap?.();
- })
- const goBack = () => {
- if (route.query.targetUrl) {
- router.replace(route.query.targetUrl);
- return;
- }
- router.go(-1);
- };
- const resetPolygon = () => {
- clearDeleteSelectionState();
- ElMessageBox.confirm(
- "确认重置当前地块吗?重置后将恢复为初始状态",
- "重置确认",
- {
- confirmButtonText: "确认重置",
- cancelButtonText: "取消",
- type: "warning",
- }
- )
- .then(() => {
- // 先取消进行中的勾画草图(未完成时线条在 Draw 的 overlay 上,清 source 去不掉)
- drawRegionMap.abortOngoingDrawSketch();
- // 只清空当前可编辑多边形,不影响只读状态图层
- if (drawRegionMap.kmap && drawRegionMap.kmap.polygonLayer && drawRegionMap.kmap.polygonLayer.source) {
- drawRegionMap.kmap.polygonLayer.source.clear();
- }
- // 清空当前 tab 草稿,重置应回到“进入时初始化值”
- const currentTab = varietyTabs.value?.[activeVariety.value];
- const currentKey = draftKeyFromParts(activeRegionType.value, currentTab);
- if (currentKey && regionsDraftByKey.value[currentKey]) {
- delete regionsDraftByKey.value[currentKey];
- }
- if (regionsDraftByIndex.value[activeVariety.value]) {
- delete regionsDraftByIndex.value[activeVariety.value];
- }
- // 当前品类有初始化地块则回显;没有则保持空地图
- const initialGeomArrRaw = (() => {
- const fromItems = getGeomArrFromGeomItems(currentTab);
- if (fromItems.length) return fromItems;
- return isValidGeom(currentTab?.geom) ? [String(currentTab.geom).trim()] : [];
- })();
- const initialGeomArr = getEditableGeomArrForCurrentTab(currentTab, initialGeomArrRaw);
- if (
- !viewOnly.value &&
- activeRegionType.value !== "variety" &&
- isLockedAbnormalTab(currentTab)
- ) {
- drawRegionMap.setStatusRegions([
- ...getAllVarietyReadonlyRegions(),
- ...getLockedDiseaseRegions(currentTab),
- ]);
- }
- if (initialGeomArr.length > 0) {
- drawRegionMap.setAreaGeometry(
- initialGeomArr,
- false,
- undefined,
- undefined,
- getAbnormalGrowthOverlayMeta
- );
- regionGeom.value =
- initialGeomArr.length > 1 ? JSON.stringify(initialGeomArr) : initialGeomArr[0];
- } else {
- regionGeom.value = "";
- drawRegionMap.fitAllRegions?.();
- }
- ElMessage.success("地块已重置");
- })
- .catch(() => {
- // 用户取消重置,不做任何操作
- });
- };
- const submitRegions = async () => {
- if (submitting.value) return;
- const regionList = buildRegionListForSubmit();
- const problemZoneList = buildProblemZoneListForSubmit();
- if (regionList.length === 0 && problemZoneList.length === 0) {
- ElMessage.warning("请先勾画地块后再确认");
- return false;
- }
- submitting.value = true;
- try {
- const params = {
- subjectId: route.query.subjectId,
- regionList,
- problemZoneList,
- };
- const res = await VE_API.basic_farm.ediRegionZone(params);
- if (res?.code === 0) {
- if (selectedDrawTypeMeta.value.type === 'ABNORMAL') {
- let problemZoneId = null;
- if (params.problemZoneList.length) {
- res.data[0].problemZoneList.forEach(element => {
- if (element.code === selectedDrawTypeMeta.value.type) {
- element.children.forEach(item => {
- if (item.problemZoneTypeName === selectedDrawTypeMeta.value.category.problemZoneTypeName) {
- problemZoneId = item.id;
- }
- });
- }
- });
- }
- VE_API.basic_farm.saveProblemZoneImage({
- problemZoneId,
- imagePathList: selectedDrawTypeMeta.value.images,
- })
- }
- return true;
- }
- ElMessage.error(res?.msg || "保存失败");
- return false;
- } catch (e) {
- ElMessage.error("保存失败");
- return false;
- } finally {
- submitting.value = false;
- }
- };
- const submitCurrentTabGeometryChange = async (geomWktOrArr) => {
- const tab = varietyTabs.value?.[activeVariety.value];
- if (!tab) return false;
- const geometryArr = Array.isArray(geomWktOrArr)
- ? geomWktOrArr
- : isValidGeom(geomWktOrArr)
- ? [String(geomWktOrArr).trim()]
- : [];
- const normalizedGeom = mergePolygonWktsForApi(geometryArr);
- const params = {
- subjectId: route.query.subjectId,
- regionList: [],
- problemZoneList: [],
- };
- if (activeRegionType.value === "variety") {
- params.regionList = [
- {
- regionId: tab.regionId,
- typeId: tab.typeId,
- regionName: tab.regionName,
- geom: normalizedGeom,
- },
- ];
- } else {
- const group = findProblemZoneGroupByDrawType(activeRegionType.value);
- if (!group) return false;
- const geomItems = isLockedAbnormalTab(tab)
- ? buildDiseaseGeomItemsForSubmit(tab, geometryArr)
- : buildGeomItemsFromGeometryArr(tab, geometryArr);
- params.problemZoneList = [
- {
- name: group.name,
- code: group.code,
- children: [
- {
- farmSubjectId: route.query.subjectId,
- problemZoneTypeId: tab.problemZoneTypeId ?? tab.typeId ?? "",
- problemZoneTypeName: tab.problemZoneTypeName ?? tab.regionName ?? "",
- parentName: group.name ?? "",
- parentCode: group.code ?? "",
- geomItems,
- },
- ],
- },
- ];
- }
- const res = await VE_API.basic_farm.ediRegionZone(params);
- return res?.code === 0;
- };
- /** 以弹窗 confirm 传入的 drawMeta(大类 type + 小类 category + remark)为准写入草稿并提交 */
- const confirmArea = async (drawMeta) => {
- const meta =
- drawMeta && drawMeta.type != null
- ? drawMeta
- : selectedDrawTypeMeta.value;
- if (!meta?.type || meta.category == null) {
- ElMessage.warning("请先在弹窗中选择勾画类型与类别");
- return;
- }
- const polygonData = drawRegionMap.getAreaGeometry?.();
- const geometryArr = polygonData?.geometryArr;
- if (!Array.isArray(geometryArr) || geometryArr.length === 0) {
- ElMessage.warning("请先勾画地块后再确认");
- return;
- }
- const normalizedGeomArr = flattenGeomWktListForMap(geometryArr);
- if (!Array.isArray(normalizedGeomArr) || normalizedGeomArr.length === 0) {
- ElMessage.warning("地块几何无效,请重新勾画");
- return;
- }
- const key = draftKeyFromParts(meta.type, meta.category);
- if (!key) {
- ElMessage.warning("无法识别勾画类别,请重新选择");
- return;
- }
- const geom = mergePolygonWktsForApi(normalizedGeomArr);
- const currentTab = varietyTabs.value?.[activeVariety.value];
- const nextDraft = {
- geomArr: normalizedGeomArr,
- geom,
- remark: meta.remark != null ? String(meta.remark) : "",
- };
- if (meta.type !== "variety") {
- nextDraft.geomItems = isLockedAbnormalTab(currentTab)
- ? buildDiseaseGeomItemsForSubmit(currentTab, normalizedGeomArr, meta.remark)
- : buildGeomItemsFromGeometryArr(currentTab, normalizedGeomArr, meta.remark);
- }
- regionsDraftByKey.value[key] = nextDraft;
- const tabList = getTabsForMajorType(meta.type);
- const idx = findTabIndexForCategory(tabList, meta.category);
- if (activeRegionType.value === meta.type && idx >= 0) {
- regionsDraftByIndex.value[idx] = { geomArr: normalizedGeomArr, geom };
- }
- const ok = await submitRegions();
- const rid = meta.category?.regionId;
- if (rid != null && rid !== "") {
- try {
- await VE_API.basic_farm.updateLastViewTime({ regionId: rid });
- } catch (_) {
- /* 非品种区可能无 regionId,忽略 */
- }
- }
- if (!ok) return;
- // 保存成功后:不跳走,刷新当前页并回显最新数据,并切换为查看态
- try {
- await fetchRegionInfo();
- // 接口已是最新几何与 id,清空草稿避免旧 key/下标回退遮挡 geomItems
- clearAllRegionDrafts();
- // 恢复当前大类下的子类 tab(fetchRegionInfo 默认会填充品种区)
- if (activeRegionType.value === "variety") {
- varietyTabs.value = regionInfo.value?.regionList || [];
- } else {
- const group = (regionTypeTabs.value || []).find(
- (x) => x?.code === activeRegionType.value
- );
- varietyTabs.value = group?.children || [];
- }
- // 更新路由为查看态,确保按钮/头部状态同步
- try {
- router.replace({ query: { ...route.query, type: "viewOnly" } });
- } catch (_) {
- // 路由更新失败不影响回显
- }
- // 重新初始化地图(非编辑/查看模式),确保回显形状正确
- if (drawRegionMap.kmap) {
- drawRegionMap.destroyMap?.();
- }
- drawRegionMap.initMap(point.value, mapContainer.value, false, true, false);
- applyRegionStyles();
- if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- }
- drawRegionMap.fitAllRegions?.();
- } catch (_) {
- // 刷新失败不阻断用户继续操作,保持当前页面状态
- }
- if (route.query.showTipPopup) {
- showTipPopup.value = true;
- return;
- }
- };
- const openConfirmDrawTypePopup = () => {
- const polygonData = drawRegionMap.getAreaGeometry?.();
- const geometryArr = polygonData?.geometryArr;
- if (!Array.isArray(geometryArr) || geometryArr.length === 0) {
- ElMessage.warning("请先勾画地块后再确认");
- return;
- }
- const remark = varietyTabs?.value?.[activeVariety.value]?.geomItems?.[0]?.remark;
- confirmDrawTypePopupRef.value.openPopup({
- regionInfo: regionInfo.value,
- activeRegionType: activeRegionType.value,
- activeVarietyIndex: activeVariety.value,
- remark: activeRegionType.value === 'variety' ? '' : remark,
- });
- };
- const handleConfirmDrawType = async (payload) => {
- selectedDrawTypeMeta.value = payload;
- await confirmArea(payload);
- };
- const handleEditRegion = async () => {
- clearDeleteSelectionState();
- // 从查看态切换到可勾画编辑态:移除查看标记(所有大类共用)
- const nextQuery = { ...route.query };
- delete nextQuery.type;
- delete nextQuery.viewOnly;
- clearPendingGeomRenderTimer();
- await router.replace({ query: nextQuery });
- nextTick(() => {
- if (drawRegionMap.kmap) {
- drawRegionMap.abortOngoingDrawSketch?.();
- drawRegionMap.destroyMap();
- }
- // 若当前不在品种区(如环境/异常/休眠),点击“编辑”进入当前问题区的编辑态:
- // - 地图可编辑(仅编辑当前选中的问题区子项)
- // - 只显示「品种区」地块作为底图参考(只读)
- // - 不显示其它问题区地块
- if (activeRegionType.value !== "variety") {
- drawRegionMap.initMap(point.value, mapContainer.value, true, true, true, syncClosedDrawToDraft);
- applyRegionStyles();
- // 清空当前可编辑图层,避免残留
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- const tab = varietyTabs.value?.[activeVariety.value];
- if (isLockedAbnormalTab(tab)) {
- drawRegionMap.setStatusRegions([
- ...getAllVarietyReadonlyRegions(),
- ...getLockedDiseaseRegions(tab),
- ]);
- } else {
- renderAllVarietyRegionsReadonlyOnly();
- }
- // 进入编辑时:缩放到当前要编辑的地块
- // 异常锁定小类(2/3)进入编辑态时,禁止从 tab.geom / draftGeom 回填到可编辑层,避免与只读层重叠
- const currentGeomArrRaw = isLockedAbnormalTab(tab)
- ? getGeomArrFromGeomItems(tab)
- : (() => {
- const currentGeomStr = tab
- ? (getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value) || tab.geom)
- : "";
- return isValidGeom(currentGeomStr)
- ? [String(currentGeomStr).trim()]
- : getGeomArrFromGeomItems(tab);
- })();
- const currentGeomArr = getEditableGeomArrForCurrentTab(tab, currentGeomArrRaw);
- regionGeom.value =
- currentGeomArr.length > 1 ? JSON.stringify(currentGeomArr) : currentGeomArr[0] || "";
- if (currentGeomArr.length > 0) {
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- drawRegionMap.setAreaGeometry(
- currentGeomArr,
- true,
- undefined,
- undefined,
- getAbnormalGrowthOverlayMeta
- );
- purgeLockedFeaturesFromEditableLayer(tab);
- } else {
- // 仍自适应到品种区底图
- drawRegionMap.fitAllRegions?.();
- }
- return;
- }
- drawRegionMap.initMap(point.value, mapContainer.value, true, true, true, syncClosedDrawToDraft);
- applyRegionStyles();
- // 切到编辑态后:统一走一遍当前 tab 的点击逻辑,确保所有有地块的品种都能显示
- if (varietyTabs.value.length && varietyTabs.value[activeVariety.value]) {
- // 进入编辑时:让当前品种地块显示且缩放到当前编辑地块
- handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
- const tab = varietyTabs.value[activeVariety.value];
- const draftGeom = getDraftGeomForTab(activeRegionType.value, tab, activeVariety.value);
- const rawGeomArr =
- isValidGeom(draftGeom)
- ? [String(draftGeom).trim()]
- : (() => {
- const fromItems = getGeomArrFromGeomItems(tab);
- if (fromItems.length) return fromItems;
- return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
- })();
- const geomArr = flattenGeomWktListForMap(rawGeomArr);
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- if (geomArr.length > 0) {
- drawRegionMap.setAreaGeometry(
- geomArr,
- true,
- undefined,
- undefined,
- getAbnormalGrowthOverlayMeta
- );
- }
- } else {
- renderReadonlyVarietyRegions(activeVariety.value);
- const fallbackGeom = regionGeom.value;
- if (isValidGeom(fallbackGeom)) {
- drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
- drawRegionMap.setAreaGeometry(
- [fallbackGeom],
- true,
- undefined,
- undefined,
- getAbnormalGrowthOverlayMeta
- );
- }
- }
- // 编辑态下:保持缩放在当前要编辑地块,不再强制 fitAllRegions 覆盖
- });
- };
- const showTipPopup = ref(false);
- const handleTipConfirm = () => {
- const query = {
- hideInteraction: "true",
- };
- if (route.query.addVarietyCount) {
- query.addVarietyCount = String(route.query.addVarietyCount);
- }
- router.push({ path: "/growth_report", query });
- }
- </script>
- <style lang="scss" scoped>
- .edit-map {
- width: 100%;
- height: 100vh;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- &> :first-child {
- flex-shrink: 0;
- }
- .region-type-tabs {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- background: #f4f4f4;
- margin: 10px 10px 0;
- padding: 3px;
- border-radius: 4px;
- box-sizing: border-box;
- .region-type-tab {
- flex: 1;
- text-align: center;
- padding: 5px 0;
- color: #767676;
- }
- .region-type-tab--active {
- background: #ffffff;
- border-radius: 4px;
- color: #0D0D0D;
- }
- }
- .variety-tabs {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 10px 12px 0;
- overflow-x: auto;
- overflow-y: hidden;
- flex-wrap: nowrap;
- -webkit-overflow-scrolling: touch;
- .variety-tab {
- padding: 4px 12px;
- border-radius: 2px;
- color: #575757;
- background: #F4F4F4;
- border: 1px solid transparent;
- white-space: nowrap;
- }
- .variety-tab--active {
- background: rgba(33, 153, 248, 0.1);
- color: #2199F8;
- border: 1px solid #2199F8;
- }
- }
- .edit-map-content {
- width: 100%;
- margin-top: 10px;
- flex: 1;
- min-height: 0;
- position: relative;
- box-sizing: border-box;
- /* 底部「重置 / 确认」等为 position:fixed,占高约 68px,地图区域需预留避免被盖住 */
- padding-bottom: 68px;
- .edit-map-tip {
- position: absolute;
- top: 23px;
- left: calc(50% - 256px / 2);
- z-index: 1;
- font-size: 12px;
- color: #fff;
- padding: 9px 20px;
- background: rgba(0, 0, 0, 0.5);
- border-radius: 20px;
- }
- .map-container {
- width: 100%;
- height: 100%;
- }
- .edit-map-footer {
- position: absolute;
- bottom: 85px;
- left: 12px;
- width: calc(100% - 24px);
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- .footer-back {
- padding: 4px 5px 7px;
- background: #fff;
- border-radius: 8px;
- margin-bottom: 12px;
- .back-icon {
- width: 23px;
- height: 23px;
- }
- }
- .edit-map-footer-btn {
- display: flex;
- justify-content: space-between;
- align-items: center;
- width: 100%;
- position: fixed;
- bottom: 0;
- left: 0;
- background: #fff;
- padding: 10px 12px 20px 12px;
- box-sizing: border-box;
- &.confirm-btn-box {
- justify-content: center;
- }
- div {
- width: 100px;
- text-align: center;
- color: #666666;
- font-size: 14px;
- padding: 8px 0;
- border-radius: 25px;
- border: 0.5px solid rgba(153, 153, 153, 0.5);
- }
- .btn-confirm {
- background: #2199F8;
- color: #fff;
- border: 1px solid #2199F8;
- }
- }
- }
- }
- }
- </style>
|