Ver código fonte

feat:添加勾选地块页面和弹窗

wangsisi 1 semana atrás
pai
commit
0eae23e086

+ 94 - 0
src/components/popup/saveRegionSuccessPopup.vue

@@ -0,0 +1,94 @@
+<template>
+    <popup v-model:show="showValue" round class="save-region-success-popup" :close-on-click-overlay="false"
+        teleport="body">
+        <img class="tip-icon" src="@/assets/img/home/right.png" alt="" />
+        <div class="title">
+            {{ title }}
+        </div>
+        <div class="actions">
+            <div class="btn btn-ghost" @click="handleKnow">我知道了</div>
+            <div class="btn btn-primary" @click="handleNext">勾选下一个品种</div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from "vant";
+import { computed } from "vue";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    /** 弹窗标题,例如:桂味 区域已保存成功 */
+    title: {
+        type: String,
+        default: "区域已保存成功",
+    },
+});
+
+const emit = defineEmits(["update:show", "know", "next"]);
+
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit("update:show", value),
+});
+
+const handleKnow = () => {
+    emit("know");
+    emit("update:show", false);
+};
+
+const handleNext = () => {
+    emit("next");
+    emit("update:show", false);
+};
+</script>
+
+<style scoped lang="scss">
+.save-region-success-popup {
+    width: 100%;
+    padding: 20px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+
+    .tip-icon {
+        width: 68px;
+        height: 68px;
+    }
+    .title {
+        font-size: 24px;
+        margin-bottom: 32px;
+        margin-top: 16px;
+    }
+
+    .actions {
+        display: flex;
+        justify-content: space-between;
+        gap: 10px;
+        width: 100%;
+        .btn {
+            height: 40px;
+            line-height: 40px;
+            border-radius: 25px;
+            text-align: center;
+        }
+    
+        .btn-ghost {
+            width: 100px;
+            color: #666666;
+            border: 1px solid rgba(153, 153, 153, 0.5);
+        }
+    
+        .btn-primary {
+            background: #2199f8;
+            color: #ffffff;
+            border: 1px solid #2199f8;
+            width: calc(100% - 110px);
+        }
+    }
+}
+</style>

+ 212 - 0
src/components/popup/selectRegionPopup.vue

@@ -0,0 +1,212 @@
+<template>
+    <popup v-model:show="showValue" round class="select-region-popup" :close-on-click-overlay="false" teleport="body">
+        <div class="image-wrapper">
+            <div class="map-container" ref="mapContainer"></div>
+        </div>
+        <div class="content">
+            <div class="title">{{ title }}</div>
+            <div class="sub-title">{{ subTitle }}</div>
+        </div>
+        <div class="actions">
+            <div class="btn btn-ghost" @click="handleSkip">跳过</div>
+            <div class="btn btn-primary" @click="handleConfirm">去勾选</div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from "vant";
+import { computed, ref, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
+import DrawRegionMap from "@/views/old_mini/interactionList/map/drawRegionMap.js";
+import * as util from "@/common/ol_common.js";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    image: {
+        type: String,
+        default: "",
+    },
+    title: {
+        type: String,
+        default: "勾选 区域",
+    },
+    subTitle: {
+        type: String,
+        default: "精准匹配农情信息,高效管理分区",
+    },
+    /** 地图中心点,WKT POINT 格式 */
+    centerPoint: {
+        type: String,
+        default: "POINT (113.6142086995688 23.585836479509055)",
+    },
+    /** 默认要回显的区域,多边形 WKT 数组 */
+    geometryArr: {
+        type: Array,
+        default: () => [],
+    },
+    /** 默认区域的面积文案(单位:亩,可选) */
+    areaText: {
+        type: [String, Number],
+        default: "",
+    },
+});
+
+const emit = defineEmits(["update:show", "skip", "confirm"]);
+
+const mapContainer = ref(null);
+const drawRegionMap = new DrawRegionMap();
+
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit("update:show", value),
+});
+
+const handleSkip = () => {
+    emit("skip");
+    emit("update:show", false);
+};
+
+const handleConfirm = () => {
+    emit("confirm");
+    emit("update:show", false);
+};
+
+const initMap = () => {
+    if (!mapContainer.value) return;
+    if (drawRegionMap.kmap) return;
+    // 在弹窗中以只读模式展示地图
+    drawRegionMap.initMap(props.centerPoint, mapContainer.value, false, true, false);
+    // 默认回显一个区域:优先使用外部传入的 geometryArr,没有则基于中心点生成一个小方形区域
+    let geometryArr = Array.isArray(props.geometryArr) && props.geometryArr.length > 0 ? props.geometryArr : [];
+    if (!geometryArr.length && props.centerPoint) {
+        try {
+            const match = props.centerPoint.match(/POINT\s*\(\s*([0-9.+-]+)\s+([0-9.+-]+)\s*\)/i);
+            if (match) {
+                const lng = Number(match[1]);
+                const lat = Number(match[2]);
+                const dLng = 0.0007;
+                const dLat = 0.0007;
+                const points = [
+                    [lng - dLng, lat - dLat],
+                    [lng - dLng, lat + dLat],
+                    [lng + dLng, lat + dLat],
+                    [lng + dLng, lat - dLat],
+                    [lng - dLng, lat - dLat],
+                ];
+                const polygonWkt = `POLYGON((${points.map(([x, y]) => `${x} ${y}`).join(",")}))`;
+                geometryArr = [polygonWkt];
+            }
+        } catch (e) {
+            // 解析失败则不回显默认区域
+        }
+    }
+    if (geometryArr.length) {
+        drawRegionMap.setAreaGeometry(geometryArr, true, props.areaText);
+    }
+
+    // 在区域中心落一个点位(使用与勾画页相同的图标)
+    try {
+        const geom = util.wktCastGeom(props.centerPoint);
+        if (geom && typeof geom.getFirstCoordinate === "function") {
+            const coord = geom.getFirstCoordinate(); // 地图投影坐标
+            drawRegionMap.setMapPoint(coord);
+        }
+    } catch (e) {
+        // 解析失败则忽略点位
+    }
+};
+
+const destroyMap = () => {
+    if (drawRegionMap && drawRegionMap.kmap && drawRegionMap.destroyMap) {
+        drawRegionMap.destroyMap();
+    }
+};
+
+onMounted(() => {
+    if (showValue.value) {
+        nextTick(() => {
+            initMap();
+        });
+    }
+});
+
+watch(
+    () => showValue.value,
+    (val) => {
+        if (val) {
+            nextTick(() => {
+                destroyMap();
+                initMap();
+            });
+        } else {
+            destroyMap();
+        }
+    }
+);
+
+onBeforeUnmount(() => {
+    destroyMap();
+});
+</script>
+
+<style scoped lang="scss">
+.select-region-popup {
+    width: 100%;
+    padding: 20px;
+
+    .image-wrapper {
+        width: 100%;
+        background: #ffffff;
+    }
+
+    .map-container {
+        width: 100%;
+        height: 180px;
+        border-radius: 8px;
+        overflow: hidden;
+    }
+
+    .content {
+        margin-top: 12px;
+        text-align: center;
+
+        .title {
+            font-size: 22px;
+            margin-bottom: 4px;
+        }
+
+        .sub-title {
+            font-size: 20px;
+        }
+    }
+
+    .actions {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 28px;
+        gap: 10px;
+        .btn {
+            height: 40px;
+            line-height: 40px;
+            border-radius: 25px;
+            text-align: center;
+        }
+
+        .btn-ghost {
+            width: 110px;
+            border: 1px solid rgba(153, 153, 153, 0.5);
+            color: #666666;
+        }
+
+        .btn-primary {
+            width: 162px;
+            background: #2199f8;
+            color: #ffffff;
+            border: 1px solid #2199f8;
+        }
+    }
+}
+</style>

+ 7 - 0
src/router/globalRoutes.js

@@ -473,4 +473,11 @@ export default [
         name: "FarmInfo",
         component: () => import("@/views/old_mini/monitor/subPages/farmInfo.vue"),
     },
+    // 勾画区域
+    {
+        path: "/draw_area",
+        name: "DrawArea",
+        meta: { keepAlive: true },
+        component: () => import("@/views/old_mini/monitor/subPages/darwArea.vue"),
+    },
 ];

+ 3 - 2
src/views/old_mini/create_farm/index.vue

@@ -33,9 +33,9 @@
                                         <div class="position-wrap">
                                             <el-input placeholder="农场位置" readonly v-model="ruleForm.address"
                                                 autocomplete="off" />
-                                            <div class="draw-btn" @click="toSubPage">
+                                            <!-- <div class="draw-btn" @click="toSubPage">
                                                 {{ hasDefaultPolygon ? "点击勾选地块" : "新增地块" }}
-                                            </div>
+                                            </div> -->
                                         </div>
                                     </el-form-item>
                                     <el-form-item label="种植作物" prop="speciesItem">
@@ -1199,6 +1199,7 @@ function handleMianjiInput(value) {
                 }
 
                 .position-wrap {
+                    width: 100%;
                     display: flex;
                     justify-content: space-between;
                     align-items: center;

+ 3 - 3
src/views/old_mini/interactionList/index.vue

@@ -141,11 +141,11 @@
         <div class="empty-data" v-if="!loading && listData.length === 0">暂无数据</div>
     </div>
 
-    <div class="custom-bottom-fixed-btns" :class="{ 'center-btn': false }">
+    <!-- <div class="custom-bottom-fixed-btns" :class="{ 'center-btn': false }">
         <div class="bottom-btn secondary-btn" @click="handleShowDroneConsultPopup">获取自动飞行航线</div>
         <div class="bottom-btn secondary-btn">邀请农情互动</div>
-        <!-- <div class="bottom-btn primary-btn" @click="handleSubmitAll">一键提交</div> -->
-    </div>
+        <div class="bottom-btn primary-btn" @click="handleSubmitAll">一键提交</div>
+    </div> -->
 
     <!-- 农场信息完善弹窗 -->
     <farm-info-popup :oldUser="oldUser" :expertMiniUserId="query.expertMiniUserId" v-model:show="showFarmInfoPopup" />

+ 59 - 5
src/views/old_mini/monitor/index.vue

@@ -10,7 +10,13 @@
         <div class="archives-time-line">
             <div class="archives-time-line-header">
                 <div class="line-title">作物档案</div>
-                <!-- <el-date-picker style="width: 110px" v-model="date" type="year" placeholder="全部日期" /> -->
+                <div class="header-right">
+                    <div class="add-variety-btn" @click="handleAddVariety">
+                        <el-icon size="12"><Plus /></el-icon>
+                        <span>新增品种</span>
+                    </div>
+                    <el-date-picker style="width: 110px" v-model="date" type="year" placeholder="全部日期" />
+                </div>
             </div>
             <!-- 品种选择 -->
             <div class="variety-tabs" v-if="varietyTabs.length > 0">
@@ -44,8 +50,24 @@
         </div>
     </div>
 
-    <tip-popup v-model:show="showFarmPopup" type="success" text="农场领取成功"
-        :overlay-style="{ 'backdrop-filter': 'blur(4px)' }" :closeOnClickOverlay="false" :zIndex="9999" />
+    <tip-popup
+        v-model:show="showFarmPopup"
+        type="success"
+        text="农场领取成功"
+        :overlay-style="{ 'backdrop-filter': 'blur(4px)' }"
+        :closeOnClickOverlay="false"
+        :zIndex="9999"
+    />
+
+    <!-- 勾选区域引导弹窗 -->
+    <select-region-popup
+        v-model:show="showSelectRegionPopup"
+        :image="selectRegionImage"
+        title="勾选 妃子笑 区域"
+        sub-title="精准匹配农情信息,高效管理分区"
+        @confirm="handleGoSelectRegion"
+        @skip="handleSkipSelectRegion"
+    />
 
     <!-- 农事执行弹窗 -->
     <agri-execute-popup v-model:show="showAgriExecutePopup" :popupData="agriExecuteData"
@@ -53,14 +75,14 @@
 </template>
 
 <script setup>
-import customHeader from "@/components/customHeader.vue";
 import { ref, computed, onActivated, onDeactivated, onMounted } from "vue";
+import customHeader from "@/components/customHeader.vue";
 import { useStore } from "vuex";
 import { Badge, List } from "vant";
 import weatherInfo from "@/components/weatherInfo.vue";
 import { useRouter, useRoute } from "vue-router";
-import farmInfoPopup from "../home/components/farmInfoPopup.vue";
 import tipPopup from "@/components/popup/tipPopup.vue";
+import selectRegionPopup from "@/components/popup/selectRegionPopup.vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import ArchivesFarmTimeLine from "@/components/pageComponents/ArchivesFarmTimeLine.vue";
 import agriExecutePopup from "@/components/popup/agriExecutePopup.vue";
@@ -92,6 +114,24 @@ const handleVarietyClick = (tab,index) => {
     farmIdData.value = tab.farmId;
 };
 
+const date = ref(new Date());
+
+const showSelectRegionPopup = ref(false);
+const selectRegionImage = ref("");
+
+const handleAddVariety = () => {
+    showSelectRegionPopup.value = true;
+};
+
+const handleSkipSelectRegion = () => {
+    showSelectRegionPopup.value = false;
+};
+
+const handleGoSelectRegion = () => {
+    showSelectRegionPopup.value = false;
+    router.push("/draw_area");
+};
+
 const showAgriExecutePopup = ref(false); // 农事执行弹窗
 const agriExecuteData = ref({});
 const handleAgriExecuted = () => {
@@ -367,6 +407,20 @@ const getReport = () => {
                     border-radius: 20px;
                 }
             }
+            .header-right {
+                display: flex;
+                align-items: center;
+                gap: 12px;
+                .add-variety-btn {
+                    display: flex;
+                    align-items: center;
+                    gap: 3px;
+                    padding: 4px 10px;
+                    border: 1px solid #DCDCDC;
+                    border-radius: 3px;
+                    background: #fff;
+                }
+            }
         }
 
         .variety-tabs {

+ 349 - 0
src/views/old_mini/monitor/subPages/darwArea.vue

@@ -0,0 +1,349 @@
+<template>
+    <div class="edit-map">
+        <custom-header :name="viewOnly ? '查看区域' : '勾选地块'"></custom-header>
+        <div class="variety-tabs" v-if="varietyTabs.length > 0">
+            <div
+                v-for="(v, index) in varietyTabs"
+                :key="index"
+                class="variety-tab"
+                :class="{ 'variety-tab--active': activeVariety === index }"
+                @click="handleVarietyClick(v, index)"
+            >
+                {{ v.regionName }}
+            </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 class="footer-back" @click="goBack">
+                    <img class="back-icon" src="@/assets/img/home/go-back.png" alt="" />
+                </div>
+                <div class="edit-map-footer-btn" v-if="!viewOnly">
+                    <div class="btn-reset" @click="resetPolygon">重置区域</div>
+                    <div class="btn-confirm" @click="confirm">确认</div>
+                </div>
+            </div>
+        </div>
+        <save-region-success-popup
+            v-if="!viewOnly"
+            v-model:show="showSuccessPopup"
+            :title="`${activeVarietyName} 区域已保存成功`"
+            @know="handleKnow"
+            @next="handleSelectNextVariety"
+        />
+    </div>
+</template>
+
+<script setup>
+import customHeader from "@/components/customHeader.vue";
+import SaveRegionSuccessPopup from "@/components/popup/saveRegionSuccessPopup.vue";
+import { ref, computed, onMounted, onActivated, onDeactivated } from "vue";
+import { useStore } from "vuex";
+import DrawRegionMap from "../../interactionList/map/drawRegionMap.js";
+import { useRouter, useRoute } from "vue-router";
+import { convertPointToArray } from "@/utils/index";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+const store = useStore();
+const router = useRouter();
+const route = useRoute();
+const mapContainer = ref(null);
+const drawRegionMap = new DrawRegionMap();
+
+const showSuccessPopup = ref(true);
+
+const type = ref(null);
+const varietyTabs = ref([]);
+const activeVariety = ref(0);
+const farmIdData = ref(null);
+const gardenId = ref(store.state.home.gardenId);
+
+const getVarietyTabs = async () => {
+    if (!gardenId.value) return;
+    try {
+        const res = await VE_API.monitor.listRegionsBySubjectId({
+            subjectId: gardenId.value,
+        });
+        varietyTabs.value = res.data || [];
+        if (varietyTabs.value.length > 0) {
+            handleVarietyClick(varietyTabs.value[0], 0);
+        }
+    } catch (error) {
+        console.error("获取主体分区列表失败:", error);
+    }
+};
+
+const handleVarietyClick = (tab, index) => {
+    activeVariety.value = index;
+    farmIdData.value = tab.farmId;
+};
+
+const activeVarietyName = computed(() => {
+    const current = varietyTabs.value[activeVariety.value];
+    return current?.regionName || "";
+});
+const viewOnly = computed(() => route.query.viewOnly === "1" || route.query.viewOnly === "true");
+
+onMounted(() => {
+    type.value = route.query.type;
+    const point = route.query.mapCenter || "POINT (113.6142086995688 23.585836479509055)";
+    const editable = !viewOnly.value;
+    const showPoint = !viewOnly.value;
+    drawRegionMap.initMap(point, mapContainer.value, editable, true, showPoint);
+
+    // 初始化品种 tabs
+    getVarietyTabs();
+
+    // 地图初始化之后(比如 initPreviewMap 里)
+    // const regions = [
+    //     {
+    //         geometry:
+    //             "MULTIPOLYGON(((113.61674040430906 23.586573370597367,113.61586610436014 23.585922976493354,113.61710291900188 23.58486741952544,113.61770000158238 23.585651090473736,113.61674040430906 23.586573370597367)))",
+    //         status: "unresolved", // 未解决(蓝色)
+    //     },
+    //     {
+    //         geometry:
+    //             "MULTIPOLYGON(((113.61516640298626 23.588441931082958,113.61445736699218 23.58799411906573,113.61572616841707 23.586954554834552,113.61642987338976 23.588180707433526,113.61516640298626 23.588441931082958)))",
+    //         status: "resolved", // 已解决(灰色)
+    //     },
+    // ];
+
+    // drawRegionMap.setStatusRegions(regions);
+});
+
+onActivated(() => {
+    const point = route.query.mapCenter || "POINT (113.6142086995688 23.585836479509055)";
+
+    // 从编辑态进入仅查看时,需重新初始化为不可编辑
+    if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
+        drawRegionMap.destroyMap();
+        drawRegionMap.initMap(point, mapContainer.value, false, true, false);
+    }
+
+    // 从仅查看进入勾画(编辑)时,需重新初始化为可编辑
+    if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
+        drawRegionMap.destroyMap();
+        drawRegionMap.initMap(point, mapContainer.value, true, true, true);
+    }
+
+    // 先绘制地块
+    const polygonData = route.query.polygonData;
+    const rawRangeWkt = route.query.rangeWkt;
+    const rangeWkt = rawRangeWkt ? decodeURIComponent(rawRangeWkt) : null;
+
+    if (rangeWkt) {
+        let regions = [];
+        try {
+            const parsed = JSON.parse(rangeWkt);
+            if (parsed && Array.isArray(parsed.geometryArr)) {
+                regions = parsed.geometryArr.map((item) => ({
+                    geometry: item,
+                    status: "unresolved",
+                    updatedTime: route.query.updatedTime,
+                }));
+            } else if (typeof rangeWkt === "string" && rangeWkt.trim().length > 10) {
+                regions = [{ geometry: rangeWkt.trim(), status: "unresolved", updatedTime: route.query.updatedTime }];
+            }
+        } catch (_) {
+            if (typeof rangeWkt === "string" && rangeWkt.trim().length > 10) {
+                regions = [{ geometry: rangeWkt.trim(), status: "unresolved", updatedTime: route.query.updatedTime }];
+            }
+        }
+        if (regions.length) {
+            drawRegionMap.setStatusRegions(regions);
+            if (viewOnly.value && drawRegionMap.fitAllRegions) {
+                drawRegionMap.fitAllRegions();
+            }
+        }
+    }
+    if (!viewOnly.value && polygonData) {
+        drawRegionMap.setAreaGeometry(JSON.parse(polygonData)?.geometryArr);
+    }
+
+    // 查看模式下已通过 fitAllRegions 适配;编辑模式再设置地图中心
+    if (!viewOnly.value) {
+        drawRegionMap.setMapPosition(convertPointToArray(point));
+    }
+});
+
+onDeactivated(() => {
+    drawRegionMap.clearLayer()
+})
+const goBack = () => {
+    // drawRegionMap.clearLayer()
+    router.back()
+};
+
+const resetPolygon = () => {
+    ElMessageBox.confirm(
+        "确认要重置当前地块吗?重置后将恢复到进入页面时的区域形状。",
+        "重置确认",
+        {
+            confirmButtonText: "确认重置",
+            cancelButtonText: "取消",
+            type: "warning",
+        }
+    )
+        .then(() => {
+            // 只清空当前可编辑多边形,不影响只读状态图层
+            if (drawRegionMap.kmap && drawRegionMap.kmap.polygonLayer && drawRegionMap.kmap.polygonLayer.source) {
+                drawRegionMap.kmap.polygonLayer.source.clear();
+            }
+
+            // 使用进入页面时路由上带的 polygonData 作为“初始形状”进行回显
+            const polygonDataStr = route.query.polygonData;
+            if (polygonDataStr) {
+                try {
+                    const parsed = JSON.parse(polygonDataStr);
+                    if (parsed && Array.isArray(parsed.geometryArr) && parsed.geometryArr.length) {
+                        drawRegionMap.setAreaGeometry(parsed.geometryArr);
+                    }
+                } catch (_) {
+                    // 解析失败则不做处理,仅相当于清空重画
+                }
+            }
+            ElMessage.success("地块已重置");
+        })
+        .catch(() => {
+            // 用户取消重置,不做任何操作
+        });
+};
+
+const confirm = () => {
+    // const polygonData = drawRegionMap.getAreaGeometry();
+    // sessionStorage.setItem("drawRegionPolygonData", JSON.stringify(polygonData));
+    showSuccessPopup.value = true;
+};
+
+const handleKnow = () => {
+    showSuccessPopup.value = false;
+    router.back();
+};
+
+const handleSelectNextVariety = () => {
+    if (varietyTabs.value.length === 0) {
+        handleKnow();
+        return;
+    }
+    const nextIndex = activeVariety.value + 1;
+    if (nextIndex < varietyTabs.value.length) {
+        const nextTab = varietyTabs.value[nextIndex];
+        handleVarietyClick(nextTab, nextIndex);
+        showSuccessPopup.value = false;
+        // 可根据业务需要在这里重置绘制区域
+        // drawRegionMap.clearLayer();
+    } else {
+        // 如果已经是最后一个品种,则直接返回上一页
+        handleKnow();
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.edit-map {
+    width: 100%;
+    height: 100vh;
+    overflow: hidden;
+
+    .variety-tabs {
+        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: #767676;
+            background: #fff;
+            border: 0.5px solid rgba(174, 174, 174, 0.8);
+            white-space: nowrap;
+        }
+
+        .variety-tab--active {
+            background: #2199F8;
+            color: #ffffff;
+        }
+    }
+
+    .edit-map-content {
+        width: 100%;
+        margin-top: 10px;
+        height: calc(100% - 58px);
+        position: relative;
+
+        .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: 110px;
+            left: 12px;
+            width: calc(100% - 24px);
+            display: flex;
+            flex-direction: column;
+            align-items: flex-end;
+
+            .footer-back {
+                padding: 6px 7px 9px;
+                background: #fff;
+                border-radius: 8px;
+                margin-bottom: 12px;
+
+                .back-icon {
+                    width: 20px;
+                    height: 18px;
+                }
+            }
+
+            .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;
+
+                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: #000;
+                    background-image: linear-gradient(180deg, #76c3ff 0%, #2199f8 100%);
+                    color: #fff;
+                }
+            }
+        }
+    }
+}
+</style>