lxf 4 дней назад
Родитель
Сommit
95742ffe2e
55 измененных файлов с 2773 добавлено и 1234 удалено
  1. BIN
      src/assets/img/agricultural/clock.png
  2. BIN
      src/assets/img/agricultural/edit.png
  3. BIN
      src/assets/img/agricultural/file-active.png
  4. BIN
      src/assets/img/agricultural/file.png
  5. BIN
      src/assets/img/agricultural/lightning.png
  6. BIN
      src/assets/img/agricultural/page-bg.png
  7. BIN
      src/assets/img/agricultural/pen-active.png
  8. BIN
      src/assets/img/agricultural/pen.png
  9. BIN
      src/assets/img/agricultural/period.png
  10. BIN
      src/assets/img/agricultural/pic-active.png
  11. BIN
      src/assets/img/agricultural/pic.png
  12. BIN
      src/assets/img/agricultural/report.png
  13. BIN
      src/assets/img/agricultural/soil.png
  14. BIN
      src/assets/img/agricultural/water.png
  15. BIN
      src/assets/img/common/camera.png
  16. BIN
      src/assets/img/farm/aura.png
  17. BIN
      src/assets/img/farm/right-light.png
  18. BIN
      src/assets/img/map/zhongzhi-point.png
  19. BIN
      src/assets/img/tab_bar/farm-active.png
  20. BIN
      src/assets/img/tab_bar/farm.png
  21. BIN
      src/assets/img/tab_bar/home.png
  22. BIN
      src/assets/img/tab_bar/mine-active.png
  23. BIN
      src/assets/img/tab_bar/mine.png
  24. BIN
      src/assets/img/tab_bar/report.png
  25. BIN
      src/assets/img/tab_bar/service-active.png
  26. BIN
      src/assets/img/tab_bar/service.png
  27. BIN
      src/assets/img/tab_bar/task.png
  28. BIN
      src/assets/img/tab_bar/tree.png
  29. BIN
      src/assets/img/tab_bar/user-active.png
  30. BIN
      src/assets/img/tab_bar/user.png
  31. BIN
      src/assets/img/type_icon/水稻.png
  32. BIN
      src/assets/img/type_icon/玉米.png
  33. BIN
      src/assets/img/type_icon/荔枝.png
  34. 13 4
      src/components/customHeader.vue
  35. 20 8
      src/components/farmHeader.vue
  36. 229 48
      src/components/pageComponents/PhenologyTrackTimelineItem.vue
  37. 3 3
      src/components/popup/diagnosisReportPopup.vue
  38. 80 54
      src/components/popup/tipPopup.vue
  39. 32 0
      src/utils/cropIcon.js
  40. 138 76
      src/utils/ol-map/Map.js
  41. 11 7
      src/views/old_mini/agri_file/components/albumPanel.vue
  42. 273 151
      src/views/old_mini/agri_file/components/patrolPanel.vue
  43. 618 70
      src/views/old_mini/agri_file/components/reportPanel.vue
  44. 45 27
      src/views/old_mini/agri_file/index.vue
  45. 172 32
      src/views/old_mini/agri_file/pages/addZone.vue
  46. 340 240
      src/views/old_mini/entry_information/components/baInformation.vue
  47. 627 468
      src/views/old_mini/entry_information/components/selectCategory.vue
  48. 6 5
      src/views/old_mini/entry_information/components/selectEquipment.vue
  49. 14 13
      src/views/old_mini/entry_information/index.vue
  50. 1 1
      src/views/old_mini/entry_information/map/selectLocationMap.js
  51. 2 10
      src/views/old_mini/growth_report/alert_detail.vue
  52. 4 12
      src/views/old_mini/growth_report/components/CropAlertPanel.vue
  53. 2 1
      src/views/old_mini/growth_report/index.vue
  54. 141 3
      src/views/old_mini/recordDetails/components/RegionNamePopup.vue
  55. 2 1
      src/views/old_mini/recordDetails/map/mapManage.js

BIN
src/assets/img/agricultural/clock.png


BIN
src/assets/img/agricultural/edit.png


BIN
src/assets/img/agricultural/file-active.png


BIN
src/assets/img/agricultural/file.png


BIN
src/assets/img/agricultural/lightning.png


BIN
src/assets/img/agricultural/page-bg.png


BIN
src/assets/img/agricultural/pen-active.png


BIN
src/assets/img/agricultural/pen.png


BIN
src/assets/img/agricultural/period.png


BIN
src/assets/img/agricultural/pic-active.png


BIN
src/assets/img/agricultural/pic.png


BIN
src/assets/img/agricultural/report.png


BIN
src/assets/img/agricultural/soil.png


BIN
src/assets/img/agricultural/water.png


BIN
src/assets/img/common/camera.png


BIN
src/assets/img/farm/aura.png


BIN
src/assets/img/farm/right-light.png


BIN
src/assets/img/map/zhongzhi-point.png


BIN
src/assets/img/tab_bar/farm-active.png


BIN
src/assets/img/tab_bar/farm.png


BIN
src/assets/img/tab_bar/home.png


BIN
src/assets/img/tab_bar/mine-active.png


BIN
src/assets/img/tab_bar/mine.png


BIN
src/assets/img/tab_bar/report.png


BIN
src/assets/img/tab_bar/service-active.png


BIN
src/assets/img/tab_bar/service.png


BIN
src/assets/img/tab_bar/task.png


BIN
src/assets/img/tab_bar/tree.png


BIN
src/assets/img/tab_bar/user-active.png


BIN
src/assets/img/tab_bar/user.png


BIN
src/assets/img/type_icon/水稻.png


BIN
src/assets/img/type_icon/玉米.png


BIN
src/assets/img/type_icon/荔枝.png


+ 13 - 4
src/components/customHeader.vue

@@ -1,17 +1,16 @@
 <template>
-    <div class="custom-header" :style="{background:bgColor}">
+    <div class="custom-header" :style="{ background: bgColor, color: titleColor }">
         <div class="back" @click="goback" v-if="!isClose">
-            <el-icon class="icon" color="rgba(0, 0, 0, 0.9)"><ArrowLeftBold /></el-icon>
+            <el-icon class="icon" :color="iconColor"><ArrowLeftBold /></el-icon>
         </div>
         <div class="close" @click="close" v-if="isClose && showClose">
-            <el-icon class="icon" color="rgba(0, 0, 0, 0.9)"><CloseBold /></el-icon>
+            <el-icon class="icon" :color="iconColor"><CloseBold /></el-icon>
         </div>
         <div class="title">{{name}}</div>
     </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
 import { useRouter} from "vue-router";
 const router = useRouter();
 const props = defineProps({
@@ -23,6 +22,16 @@ const props = defineProps({
         type:String,
         defalut:'#fff'
     },
+    /** 返回/关闭图标颜色 */
+    iconColor: {
+        type: String,
+        default: "rgba(0, 0, 0, 0.9)",
+    },
+    /** 标题文字颜色 */
+    titleColor: {
+        type: String,
+        default: "inherit",
+    },
     isGoBack:{
         type:Boolean,
         defalut:false

+ 20 - 8
src/components/farmHeader.vue

@@ -22,20 +22,24 @@ import { useRouter } from "vue-router";
 import { useStore } from "vuex";
 import { Switch } from "@element-plus/icons-vue";
 import { useI18n } from "@/i18n";
+import { DEFAULT_CROP_AVATAR, resolveCropIcon, resolveFarmCropName } from "@/utils/cropIcon";
 
 const { t } = useI18n();
 const router = useRouter();
 const store = useStore();
 
-const defaultAvatar = require("@/assets/img/common/logo.png");
 const farmName = ref("");
 const farmLocation = ref("");
-const circleUrl = ref(defaultAvatar);
+const circleUrl = ref(DEFAULT_CROP_AVATAR);
 
 const applyFarmInfo = (farm = {}) => {
     farmName.value = farm.farm_name || farm.name || t("agriFile.farmName");
     farmLocation.value = farm.farm_address || farm.rawAddress || farm.city || t("agriFile.farmLocation");
-    circleUrl.value = farm.cover || farm.image || farm.avatar || defaultAvatar;
+    // 优先按品种匹配 type_icon;无匹配再用封面 / 默认图
+    circleUrl.value = resolveCropIcon(
+        resolveFarmCropName(farm),
+        farm.cover || farm.image || farm.avatar || DEFAULT_CROP_AVATAR
+    );
 };
 
 const hasSelectedFarm = (farm) => {
@@ -143,13 +147,20 @@ defineExpose({ loadFarmInfo });
     }
 
     .farm-header__name {
-        font-size: 16px;
-        font-weight: 500;
+        font-size: 20px;
+        font-family: "PanMenZhengDao";
+        background: linear-gradient(180deg, #FFE2AE 0%, #FFD489 100%);
+        /* 裁切背景到文字 */
+        -webkit-background-clip: text;
+        background-clip: text;
+        /* 文字填充透明,露出渐变背景 */
+        -webkit-text-fill-color: transparent;
+        color: transparent;
     }
 
     .farm-header__location {
         margin-top: 2px;
-        color: rgba(0, 0, 0, 0.5);
+        color: #999999;
         font-size: 12px;
     }
 
@@ -160,8 +171,9 @@ defineExpose({ loadFarmInfo });
         flex-shrink: 0;
         padding: 6px 10px;
         border-radius: 16px;
-        background: #fff;
-        color: #2199f8;
+        border: 1px solid rgba(255, 212, 137, 0.1);
+        background: rgba(255, 255, 255, 0.1);
+        color: rgba(255, 255, 255, 0.6);
         cursor: pointer;
     }
 }

+ 229 - 48
src/components/pageComponents/PhenologyTrackTimelineItem.vue

@@ -1,33 +1,54 @@
 <template>
-    <template v-if="yearGroupedList.length">
-        <template v-for="yearGroup in yearGroupedList" :key="yearGroup.year || 'unknown'">
-            <div v-if="yearGroup.yearText" class="track-year">{{ yearGroup.yearText }}</div>
-            <div
-                class="phenology-track-item"
-                v-for="group in yearGroup.dateGroups"
-                :key="`${yearGroup.year}-${group.date}`"
-            >
-                <div class="track-axis">
-                    <span class="track-date-side">{{ group.date }}</span>
-                    <div class="track-line-wrap">
-                        <span class="track-dot"></span>
-                        <div class="track-line"></div>
+    <div class="phenology-track" :class="`phenology-track--${theme}`">
+        <template v-if="yearGroupedList.length">
+            <template v-for="yearGroup in yearGroupedList" :key="yearGroup.year || 'unknown'">
+                <div v-if="yearGroup.yearText" class="track-year">
+                    <div class="track-year__left">
+                        <span class="track-year__dot"></span>
+                        <span class="track-year__text">{{ yearGroup.yearText }}</span>
                     </div>
+                    <span class="track-year__count">{{ yearGroup.count }} 条记录</span>
                 </div>
-                <div class="track-cards">
-                    <div class="track-card" v-for="item in group.items" :key="item.id">
-                        <span v-if="item.zone_name" class="track-badge">{{ item.zone_name }}</span>
-                        <div v-if="item.content" class="track-content" v-html="highlightContent(item.content)"></div>
+                <div
+                    class="phenology-track-item"
+                    v-for="group in yearGroup.dateGroups"
+                    :key="`${yearGroup.year}-${group.date}`"
+                >
+                    <div class="track-axis">
+                        <span class="track-date-side">{{ group.date }}</span>
+                        <div class="track-line-wrap">
+                            <span class="track-dot"></span>
+                            <div class="track-line"></div>
+                        </div>
+                    </div>
+                    <div class="track-cards">
+                        <div
+                            class="track-card"
+                            :class="{ 'track-card--alert': item.isAlert }"
+                            v-for="item in group.items"
+                            :key="item.id"
+                        >
+                            <span v-if="item.zone_name" class="track-badge">{{ item.zone_name }}</span>
+                            <div
+                                v-if="item.content"
+                                class="track-content"
+                                :class="{ 'track-content--alert': item.isAlert }"
+                            >
+                                <el-icon v-if="item.isAlert" class="track-content__warn"><WarningFilled /></el-icon>
+                                <span v-html="highlightContent(item.content)"></span>
+                            </div>
+                        </div>
                     </div>
                 </div>
-            </div>
+            </template>
         </template>
-    </template>
-    <div v-else-if="dataLoaded" class="track-empty">{{ t("agriFile.noData") }}</div>
+        <div v-else-if="dataLoaded" class="track-empty">{{ t("agriFile.noData") }}</div>
+    </div>
 </template>
 
 <script setup>
 import { computed, onActivated, ref, watch } from "vue";
+import { WarningFilled } from "@element-plus/icons-vue";
 import { useI18n } from "@/i18n";
 
 const { t } = useI18n();
@@ -41,6 +62,11 @@ const props = defineProps({
         type: Array,
         default: null,
     },
+    /** light:档案详情等浅色页;dark:巡园面板暗色稿 */
+    theme: {
+        type: String,
+        default: "light",
+    },
 });
 
 const imageList = ref([]);
@@ -65,14 +91,20 @@ function formatDate(value) {
     return text;
 }
 
+function isAlertContent(text) {
+    return /异常|abnormal/i.test(String(text || ""));
+}
+
 function normalizeItem(item, index) {
     const rawDate = item.date || item.enter_date || item.latest_time || "";
+    const content = item.content || item.sentence || item.desc || item.text || "";
     return {
         id: item.id ?? index,
         year: extractYear(rawDate),
         date: formatDate(rawDate),
         zone_name: item.zone_name || item.zoneName || "",
-        content: item.content || item.sentence || item.desc || item.text || "",
+        content,
+        isAlert: isAlertContent(content),
     };
 }
 
@@ -92,6 +124,7 @@ const yearGroupedList = computed(() => {
                 yearText: year ? t("agriFile.yearLabel", { year }) : "",
                 dateGroups: [],
                 dateMap: new Map(),
+                count: 0,
             };
             yearMap.set(year, group);
             yearGroups.push(group);
@@ -105,9 +138,15 @@ const yearGroupedList = computed(() => {
             yearGroup.dateGroups.push(dateGroup);
         }
         yearGroup.dateMap.get(dateKey).items.push(normalized);
+        yearGroup.count += 1;
     });
 
-    return yearGroups.map(({ year, yearText, dateGroups }) => ({ year, yearText, dateGroups }));
+    return yearGroups.map(({ year, yearText, dateGroups, count }) => ({
+        year,
+        yearText,
+        dateGroups,
+        count,
+    }));
 });
 
 function highlightContent(text) {
@@ -115,7 +154,10 @@ function highlightContent(text) {
         .replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;");
-    return safe.replace(/(\d+(?:\.\d+)?%|异常|abnormality|abnormal)/gi, '<span class="track-hl">$1</span>');
+    return safe.replace(
+        /(\d+(?:\.\d+)?%|异常|abnormality|abnormal)/gi,
+        '<span class="track-hl">$1</span>'
+    );
 }
 
 const getFarmImages = async () => {
@@ -145,7 +187,7 @@ watch(
     () => {
         if (Array.isArray(props.list)) dataLoaded.value = true;
     },
-    { immediate: true },
+    { immediate: true }
 );
 
 onActivated(() => {
@@ -158,37 +200,85 @@ defineExpose({
 </script>
 
 <style scoped lang="scss">
-.track-year {
-    margin-bottom: 12px;
-    color: rgba(0, 0, 0, 0.4);
-}
+.phenology-track {
+    .track-year {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 8px;
+        margin-bottom: 12px;
+        color: rgba(0, 0, 0, 0.4);
+        font-size: 14px;
 
-.phenology-track-item + .track-year {
-    margin-top: 16px;
-}
+        &__left {
+            display: flex;
+            align-items: center;
+            gap: 6px;
+            min-width: 0;
+        }
 
-.track-empty {
-    padding: 24px 0;
-    text-align: center;
-    font-size: 14px;
-    color: rgba(60, 60, 60, 0.45);
-}
+        &__dot {
+            display: none;
+            width: 14px;
+            height: 14px;
+            border-radius: 50%;
+            border: 1px solid rgba(255, 212, 137, 0.3);
+            box-sizing: border-box;
+            position: relative;
+            flex-shrink: 0;
+
+            &::after {
+                content: "";
+                position: absolute;
+                left: 50%;
+                top: 50%;
+                width: 7px;
+                height: 7px;
+                border-radius: 50%;
+                background: #ffd489;
+                transform: translate(-50%, -50%);
+            }
+        }
 
-.phenology-track-item {
-    display: grid;
-    grid-template-columns: auto 1fr;
-    gap: 10px;
-    align-items: stretch;
+        &__count {
+            flex-shrink: 0;
+            font-size: 12px;
+            color: rgba(0, 0, 0, 0.4);
+        }
+    }
+
+    .phenology-track-item + .track-year {
+        margin-top: 16px;
+    }
+
+    .track-empty {
+        padding: 24px 0;
+        text-align: center;
+        font-size: 14px;
+        color: rgba(60, 60, 60, 0.45);
+    }
+
+    .phenology-track-item {
+        display: grid;
+        grid-template-columns: auto 1fr;
+        gap: 10px;
+        align-items: stretch;
+    }
 
     .track-axis {
         display: flex;
         flex-direction: column;
 
         .track-date-side {
-            padding: 2px 6px;
+            padding: 0 6px;
             border-radius: 2px;
-            background: #F6F6F6;
+            background: rgba(67, 67, 67, 0.3);
             font-size: 12px;
+            color: #B0A4A4;
+            text-align: center;
+            height: 21px;
+            line-height: 21px;
+            white-space: nowrap;
         }
 
         .track-line-wrap {
@@ -196,13 +286,13 @@ defineExpose({
             flex: 1;
             flex-direction: column;
             align-items: center;
-            margin: 4px 0 8px 0;
+            margin: 4px 0 8px;
 
             .track-dot {
                 width: 6px;
                 height: 6px;
                 border-radius: 50%;
-                background: rgba(29, 33, 41, 0.2);
+                background: rgba(255, 255, 255, 0.2);
                 z-index: 1;
             }
 
@@ -210,7 +300,7 @@ defineExpose({
                 flex: 1;
                 min-height: 12px;
                 margin-top: 2px;
-                border-left: 1px dashed rgba(29, 33, 41, 0.2);
+                border-left: 1px dashed rgba(255, 255, 255, 0.2);
             }
         }
     }
@@ -230,19 +320,110 @@ defineExpose({
         .track-badge {
             display: inline-block;
             padding: 0 5px;
-            margin-bottom: 6px;
+            margin-bottom: 4px;
             border-radius: 2px;
             background: #2199f8;
             color: #fff;
+            font-size: 12px;
+            line-height: 18px;
         }
 
         .track-content {
             color: rgba(60, 60, 60, 0.5);
+            font-size: 14px;
+            line-height: 21px;
+            word-break: break-all;
+
+            &--alert {
+                display: flex;
+                align-items: flex-start;
+                gap: 4px;
+            }
 
             :deep(.track-hl) {
                 color: #2199f8;
             }
         }
+
+        .track-content__warn {
+            display: none;
+            flex-shrink: 0;
+            margin-top: 2px;
+            font-size: 14px;
+        }
+    }
+
+    // ---------- 暗色稿(巡园面板) ----------
+    &--dark {
+        .track-year {
+            color: #ffd489;
+
+            &__dot {
+                display: inline-block;
+            }
+
+            &__count {
+                color: rgba(255, 255, 255, 0.35);
+            }
+        }
+
+        .track-empty {
+            color: rgba(255, 255, 255, 0.4);
+        }
+
+        .track-axis {
+            .track-date-side {
+                background: rgba(255, 255, 255, 0.08);
+                color: rgba(255, 255, 255, 0.45);
+                border-radius: 4px;
+            }
+
+            .track-line-wrap {
+                .track-dot {
+                    background: rgba(255, 255, 255, 0.25);
+                }
+
+                .track-line {
+                    border-left-color: rgba(255, 255, 255, 0.16);
+                }
+            }
+        }
+
+        .track-card {
+            padding: 10px 12px;
+            border-radius: 10px;
+            border: 1px solid rgba(255, 255, 255, 0.1);
+            background: #1c1c1e;
+
+            .track-badge {
+                background: rgba(255, 212, 137, 0.1);
+                color: rgba(255, 212, 137, 1);
+            }
+
+            .track-content {
+                color: rgba(255, 255, 255, 1);
+
+                :deep(.track-hl) {
+                    color: rgba(255, 212, 137, 1);
+                }
+
+                &--alert {
+                    padding: 8px 10px;
+                    border-radius: 6px;
+                    background: rgba(255, 77, 79, 0.18);
+                    color: rgba(255, 220, 220, 0.9);
+
+                    :deep(.track-hl) {
+                        color: #fff;
+                    }
+                }
+            }
+
+            .track-content__warn {
+                display: inline-flex;
+                color: #ff8f8f;
+            }
+        }
     }
 }
 </style>

+ 3 - 3
src/components/popup/diagnosisReportPopup.vue

@@ -144,17 +144,17 @@ defineExpose({
     &__image {
         display: block;
         width: 147px;
-        height: 132px;
+        // height: 132px;
         // margin: 0 auto 16px;
         position: absolute;
-        top: -74px;
+        top: -60px;
         left: 50%;
         transform: translateX(-50%);
     }
 
     &__title {
         font-size: 30px;
-        color: #010101;
+        color: #fff;
         line-height: 32px;
         div{
             font-family: "pangmenzhengdao";

+ 80 - 54
src/components/popup/tipPopup.vue

@@ -1,45 +1,42 @@
 <template>
-    <popup 
-        v-model:show="showValue" 
-        round 
-        class="tip-popup"
-        :overlay-style="overlayStyle"
-        :closeable="hasClose"
-        teleport="body"
-        :close-on-click-overlay="closeOnClickOverlay"
-        @click-overlay="handleClickOverlay"
-        @closed="handleClosed"
-        :z-index="zIndex"
-    >
-        <template v-if="type === 'create'">
-            <img class="tip-icon create-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
-            <div class="tip-text">
-                <div v-for="(line, index) in textLines" :key="index">{{ line }}</div>
+    <popup v-model:show="showValue" round class="tip-popup" :overlay-style="overlayStyle" :closeable="hasClose"
+        teleport="body" :close-on-click-overlay="closeOnClickOverlay" @click-overlay="handleClickOverlay"
+        @closed="handleClosed" :z-index="zIndex">
+        <div class="tip-popup-content">
+            <template v-if="type === 'create'">
+                <img class="tip-icon create-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
+                <div class="tip-text">
+                    <div v-for="(line, index) in textLines" :key="index">{{ line }}</div>
+                </div>
+            </template>
+            <template v-else-if="type === 'success'">
+                <div class="green-right">
+                    <el-icon size="28" color="#7FD8A6"><Select /></el-icon>
+                </div>
+                <!-- <img class="tip-icon success-icon" src="@/assets/img/home/right.png" alt="" /> -->
+                <slot name="default"></slot>
+                <div class="tip-text success-text" :class="{ pmzd: font, 'no-bottom': hideBtn }">
+                    {{ text }} <span class="highlight-text">{{ highlightText }}</span>
+                    <div class="text2">{{ text2 }}</div>
+                </div>
+            </template>
+            <template v-else-if="type === 'executeSuccess'">
+                <img class="tip-icon success-icon" src="@/assets/img/home/right.png" alt="" />
+                <div class="tip-text execute-success-text" :class="{ 'no-bottom': hideBtn && !noticeText }">
+                    <div class="execute-success-title">{{ text }}</div>
+                    <div v-if="text2" class="execute-success-subtitle">{{ text2 }}</div>
+                </div>
+                <div v-if="noticeText" class="tip-notice">{{ noticeText }}</div>
+            </template>
+            <template v-else>
+                <img class="tip-icon success-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
+                <div class="tip-text success-text">{{ text }} <span class="highlight-text">{{ highlightText }}</span> {{
+                    text2 }}</div>
+            </template>
+            <slot name="footer"></slot>
+            <div class="tip-btn" v-if="!hideBtn" @click.stop="handleBtnClick">
+                {{ buttonText || (type === "create" ? "去创建农场" : "我知道了") }}
             </div>
-        </template>
-        <template v-else-if="type === 'success'">
-            <img class="tip-icon success-icon" src="@/assets/img/home/right.png" alt="" />
-            <slot name="default"></slot>
-            <div class="tip-text success-text" :class="{pmzd:font, 'no-bottom': hideBtn}">
-                {{ text }} <span class="highlight-text">{{ highlightText }}</span>
-                <div class="text2">{{ text2 }}</div>
-            </div>
-        </template>
-        <template v-else-if="type === 'executeSuccess'">
-            <img class="tip-icon success-icon" src="@/assets/img/home/right.png" alt="" />
-            <div class="tip-text execute-success-text" :class="{ 'no-bottom': hideBtn && !noticeText }">
-                <div class="execute-success-title">{{ text }}</div>
-                <div v-if="text2" class="execute-success-subtitle">{{ text2 }}</div>
-            </div>
-            <div v-if="noticeText" class="tip-notice">{{ noticeText }}</div>
-        </template>
-        <template v-else>
-            <img class="tip-icon success-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
-            <div class="tip-text success-text">{{ text }} <span class="highlight-text">{{ highlightText }}</span> {{ text2 }}</div>
-        </template>
-        <slot name="footer"></slot>
-        <div class="tip-btn" v-if="!hideBtn" @click.stop="handleBtnClick">
-            {{ buttonText || (type === "create" ? "去创建农场" : "我知道了") }}
         </div>
     </popup>
 </template>
@@ -105,8 +102,8 @@ const props = defineProps({
         default: 2003,
     },
     font: {
-        type:Boolean,
-        default:false
+        type: Boolean,
+        default: false
     },
     hideBtn: {
         type: Boolean,
@@ -135,14 +132,14 @@ const textLines = computed(() => {
 
 const handleBtnClick = () => {
     emit("confirm");
-    if(props.buttonText !== "分享微信"){
+    if (props.buttonText !== "分享微信") {
         emit("update:show", false);
     }
 };
 
 const handleClickOverlay = () => {
     emit("handleClickOverlay");
-    if(props.closeOnClickOverlay){
+    if (props.closeOnClickOverlay) {
         emit("update:show", false);
     }
 };
@@ -156,44 +153,74 @@ const handleClosed = () => {
 .tip-popup {
     z-index: 10000 !important;
     width: 100%;
-    padding: 28px 20px 20px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
+    border-radius: 16px;
+    padding: 1px;
     color: #fff;
-    border: 1px solid rgba(255, 255, 255, 0.1);
-    background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+    background: linear-gradient(0deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)),
+linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 12%);
+
+    .tip-popup-content {
+        background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+        border-radius: 16px;
+        width: 100%;
+        box-sizing: border-box;
+        padding: 20px 16px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+
+    }
 
     .tip-icon {
         margin-bottom: 12px;
+
         &.create-icon {
             width: 40px;
             height: 40px;
         }
+
         &.success-icon {
             width: 68px;
             height: 68px;
         }
     }
+
+    .green-right {
+        width: 64px;
+        height: 64px;
+        background: rgba(30, 74, 51, 0.6);
+        border: 1px solid rgba(63, 160, 110, 0.5);
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-bottom: 24px;
+    }
+
     .tip-text {
         font-size: 18px;
         font-weight: 500;
         margin-bottom: 32px;
         text-align: center;
+
         &.no-bottom {
             margin-bottom: 6px;
         }
+
         &.success-text {
             font-size: 24px;
             font-weight: 400;
         }
+
         .highlight-text {
             color: #FFD489;
         }
-        &.pmzd{
+
+        &.pmzd {
             font-family: 'PangMenZhengDao';
         }
+
         .text2 {
             padding-top: 10px;
             font-size: 20px;
@@ -231,7 +258,7 @@ const handleClosed = () => {
         width: 100%;
         box-sizing: border-box;
         padding: 8px;
-        border-radius: 25px;
+        border-radius: 8px;
         font-size: 16px;
         background: linear-gradient(135deg, #FFEBC9 0%, #FFCA6D 100%);
         color: #1A1408;
@@ -240,4 +267,3 @@ const handleClosed = () => {
     }
 }
 </style>
-

+ 32 - 0
src/utils/cropIcon.js

@@ -0,0 +1,32 @@
+/** 品类名 → type_icon;多处头像 / 封面共用 */
+
+export const DEFAULT_CROP_AVATAR =
+    "https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png";
+
+export const CROP_ICON_MAP = {
+    水稻: require("@/assets/img/type_icon/水稻.png"),
+    玉米: require("@/assets/img/type_icon/玉米.png"),
+    荔枝: require("@/assets/img/type_icon/荔枝.png"),
+};
+
+/**
+ * 按作物名取品类图标
+ * @param {string} cropName
+ * @param {string} [fallback=DEFAULT_CROP_AVATAR]
+ */
+export function resolveCropIcon(cropName, fallback = DEFAULT_CROP_AVATAR) {
+    const name = String(cropName || "").trim();
+    if (name && CROP_ICON_MAP[name]) return CROP_ICON_MAP[name];
+    return fallback || DEFAULT_CROP_AVATAR;
+}
+
+/** 从农场对象取品种展示名 */
+export function resolveFarmCropName(farm = {}) {
+    return (
+        farm.categoryLabel ||
+        farm.variety_name ||
+        farm.crop_name ||
+        farm.farm_variety ||
+        ""
+    );
+}

+ 138 - 76
src/utils/ol-map/Map.js

@@ -175,14 +175,20 @@ class Map {
 		})
 	}
 
-	initDraw(callback) {
+	initDraw(callback, options = {}) {
 		const vm = this
-		this.draw = new Draw({
+		const drawOptions = {
 			type: 'MultiPolygon',
 			source: this.polygonLayer.source,
-			free: true,
-			style: vm.drawStyleFunc
-		})
+			style: (feature) => vm.drawStyleFunc(feature),
+		};
+		if (options.snapTolerance != null) {
+			drawOptions.snapTolerance = options.snapTolerance;
+		}
+		if (options.maxPoints != null && options.maxPoints > 0) {
+			drawOptions.maxPoints = options.maxPoints;
+		}
+		this.draw = new Draw(drawOptions)
 		this.draw.setActive(false)
 		this.map.addInteraction(this.draw);
 		this.draw.on("drawend", callback)
@@ -192,9 +198,11 @@ class Map {
 	}
 
 	modifyDraw(callback) {
+		const vm = this;
 		this.modify = new Modify({
 			source: this.polygonLayer.source,
 			pixelTolerance: 10, //设置吸附像素值
+			style: (feature) => vm.drawStyleFunc(feature),
 		})
 		this.map.addInteraction(this.modify);
 		this.modify.on("modifyend", callback)
@@ -208,39 +216,56 @@ class Map {
 		// 允许通过 Map.drawStyleColors 全局控制勾画时的颜色(未设置时仍使用原来的蓝色)
 		const lineColor = (Map.drawStyleColors && Map.drawStyleColors.line) || '#2199F8';
 		const vertexColor = (Map.drawStyleColors && Map.drawStyleColors.vertex) || '#2199F8';
-		for (let i = 0; i < coord.length - 1; i++) {
-			if (i % 2) {
-				styles.push(
-					new Style({
-						geometry: new Point(coord[i]),
-						image: new Circle({
-							radius: 4,
-							fill: new Fill({
-								color: vertexColor
-							}),
-							stroke: new Stroke({
-								color: '#fff',
-								width: 1
-							})
-						})
-					})
-				);
-			} else {
-				styles.push(
-					new Style({
-						geometry: new Point(coord[i]),
-						image: new Circle({
-							radius: 6,
-							fill: new Fill({
-								color: '#fff'
-							}),
+		if (type === 'Point') {
+			styles.push(
+				new Style({
+					geometry: new Point(coord),
+					image: new Circle({
+						radius: 4,
+						fill: new Fill({
+							color: vertexColor
+						}),
+						stroke: new Stroke({
+							color: '#fff',
+							width: 1
 						})
 					})
-				);
-			}
+				})
+			);
 		}
 		if (type === 'LineString') {
 			for (let i = 0; i < coord.length - 1; i++) {
+				if (i % 2) {
+					styles.push(
+						new Style({
+							geometry: new Point(coord[i]),
+							image: new Circle({
+								radius: 4,
+								fill: new Fill({
+									color: vertexColor
+								}),
+								stroke: new Stroke({
+									color: '#fff',
+									width: 1
+								})
+							})
+						})
+					);
+				} else {
+					styles.push(
+						new Style({
+							geometry: new Point(coord[i]),
+							image: new Circle({
+								radius: 6,
+								fill: new Fill({
+									color: '#fff'
+								}),
+							})
+						})
+					);
+				}
+			}
+			for (let i = 0; i < coord.length - 1; i++) {
 				styles.push(
 					new Style({
 						geometry: new LineString([coord[i], coord[i + 1]]),
@@ -251,67 +276,104 @@ class Map {
 					})
 				);
 			}
+		} else if (type === 'Polygon' || type === 'MultiPolygon') {
+			// 勾画中只画开口折线:去掉 OL 自动补的「末点→起点」闭合边,也不填充
+			const ring = type === 'Polygon' ? coord[0] : coord[0]?.[0];
+			if (!Array.isArray(ring) || ring.length < 2) return styles;
+			const last = ring[ring.length - 1];
+			const first = ring[0];
+			const closed =
+				last &&
+				first &&
+				last[0] === first[0] &&
+				last[1] === first[1];
+			const openRing = closed ? ring.slice(0, -1) : ring;
+			for (let i = 0; i < openRing.length; i++) {
+				if (i % 2) {
+					styles.push(
+						new Style({
+							geometry: new Point(openRing[i]),
+							image: new Circle({
+								radius: 4,
+								fill: new Fill({ color: vertexColor }),
+								stroke: new Stroke({ color: '#fff', width: 1 }),
+							}),
+						})
+					);
+				} else {
+					styles.push(
+						new Style({
+							geometry: new Point(openRing[i]),
+							image: new Circle({
+								radius: 6,
+								fill: new Fill({ color: '#fff' }),
+							}),
+						})
+					);
+				}
+			}
+			for (let i = 0; i < openRing.length - 1; i++) {
+				styles.push(
+					new Style({
+						geometry: new LineString([openRing[i], openRing[i + 1]]),
+						stroke: new Stroke({
+							color: lineColor,
+							width: 2,
+						}),
+					})
+				);
+			}
 		}
 		return styles;
 	}
 
 	polygonStyle(feature) {
 		const styles = [];
+		const strokeColor = (Map.drawStyleColors && Map.drawStyleColors.stroke) || '#2199F8';
+		const vertexColor = (Map.drawStyleColors && Map.drawStyleColors.vertex) || strokeColor;
+		const fillColor = (Map.drawStyleColors && Map.drawStyleColors.fill) || [0, 0, 0, 0.5];
+		const hideArea = !!(Map.drawStyleColors && Map.drawStyleColors.hideArea);
 		const coord = feature.getGeometry().getCoordinates()[0];
 		for (let i = 0; i < coord[0].length - 1; i++) {
-			if (i % 2) {
-				styles.push(
-					new Style({
-						geometry: new Point(coord[0][i]),
-						image: new Circle({
-							radius: 4,
-							fill: new Fill({
-								color: '#2199F8'
-							}),
-							stroke: new Stroke({
-								color: '#fff',
-								width: 1
-							})
-						})
-					})
-				);
-			} else {
-				styles.push(
-					new Style({
-						geometry: new Point(coord[0][i]),
-						image: new Circle({
-							radius: 6,
-							fill: new Fill({
-								color: '#fff'
-							})
+			styles.push(
+				new Style({
+					geometry: new Point(coord[0][i]),
+					image: new Circle({
+						radius: 5,
+						fill: new Fill({
+							color: vertexColor
+						}),
+						stroke: new Stroke({
+							color: vertexColor,
+							width: 1
 						})
 					})
-				);
-			}
+				})
+			);
 		}
 		let fillStyle = new Style({
 			fill: new Fill({
-				color: [0, 0, 0, 0.5]
+				color: fillColor
 			}),
 			stroke: new Stroke({
-				color: '#2199F8',
+				color: strokeColor,
 				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;
-		let areaValStyle = new Style({
-			text: new Text({
-				font: "16px sans-serif",
-				text: area.toFixed(2) + "亩",
-				// offsetX: 28,
-				// offsetY: -100,
-				fill: new Fill({ color: "#fff" }), // 字体颜色
-			}),
-		})
-		styles.push(fillStyle,areaValStyle);
+		styles.push(fillStyle);
+		if (!hideArea) {
+			let geom = feature.getGeometry().clone()
+			geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"))
+			let area = getArea(geom)
+			area = (area + area / 2) / 1000;
+			styles.push(new Style({
+				text: new Text({
+					font: "16px sans-serif",
+					text: area.toFixed(2) + "亩",
+					fill: new Fill({ color: "#fff" }),
+				}),
+			}));
+		}
 		return styles;
 	}
 

+ 11 - 7
src/views/old_mini/agri_file/components/albumPanel.vue

@@ -383,16 +383,17 @@ watch(
         cursor: pointer;
 
         .mask-content {
-            background: rgba(255, 255, 255, 0.2);
-            backdrop-filter: blur(4px);
-            padding: 8px 10px;
-            border-radius: 4px;
-            width: fit-content;
+            width: calc(100% - 24px);
+            margin: 0 auto;
+            background: rgba(0, 0, 0, 0.57);
+            border: 1px solid rgba(255, 255, 255, 0.2);
+            padding: 12px 0;
+            border-radius: 12px;
             display: flex;
             flex-direction: column;
             align-items: center;
             justify-content: center;
-            gap: 12px;
+            gap: 10px;
             color: #fff;
             pointer-events: none;
 
@@ -404,9 +405,12 @@ watch(
             &__btn {
                 padding: 0 24px;
                 height: 32px;
+                color: rgba(255, 212, 137, 1);
                 line-height: 32px;
+                border: 1px solid rgba(255, 212, 137, 1);
+                background: rgba(26, 20, 10, 0.4);
                 border-radius: 24px;
-                background: #2199f8;
+                box-sizing: border-box;
             }
         }
     }

+ 273 - 151
src/views/old_mini/agri_file/components/patrolPanel.vue

@@ -14,7 +14,7 @@
                 </div>
             </div>
             <div class="variety-bar__add" @click="goAddVariety">
-                <el-icon>
+                <el-icon size="10">
                     <Plus />
                 </el-icon>
                 <span>新增品种</span>
@@ -24,48 +24,64 @@
             v-for="item in patrolList"
             :key="item.id"
             class="tracking-item"
-            :class="`tracking-item--${item.theme}`"
             @click="goGrowthTrack(item)"
         >
             <div class="tracking-item__header">
-                <div class="tracking-item__header-row">
-                    <div class="tracking-item__header-left">
-                        <span class="tracking-item__level">
-                            <el-icon><BellFilled /></el-icon>
-                            {{ item.level }}
-                        </span>
-                        <span class="tracking-item__title van-ellipsis">
-                            {{ item.title }}
-                            <span v-if="item.zone" class="tracking-item__zone"> ({{ item.zone }})</span>
-                        </span>
-                    </div>
-                    <div class="tracking-item__share" @click.stop="handleShare(item)">
-                        <Icon size="14" color="#2199F8" name="share" />
-                        <span>{{ t("agriFile.forward") }}</span>
-                    </div>
+                <div class="tracking-item__header-left">
+                    <img
+                        class="tracking-item__header-icon"
+                        :src="item.headerIcon || periodIcon"
+                        alt=""
+                    />
+                    <span class="tracking-item__title van-ellipsis">
+                        {{ item.title }}
+                    </span>
+                </div>
+                <div v-if="item.level" class="tracking-item__level">
+                    <el-icon><WarningFilled /></el-icon>
+                    <span>{{ item.level }}</span>
                 </div>
             </div>
             <div class="tracking-item__body">
-                <div class="tracking-item__action">
-                    <div class="tracking-item__action-main">
-                        <div class="tracking-item__icon">
-                            <img :src="item.icon" alt="" />
-                            <span class="tracking-item__reason">{{ item.subject }}</span>
-                        </div>
-                        <div class="tracking-item__issue">{{ item.issue }}</div>
+                <img class="tracking-item__thumb" :src="item.icon" alt="" />
+                <div class="tracking-item__content">
+                    <div class="tracking-item__subject van-ellipsis">{{ item.subject }}</div>
+                    <div class="tracking-item__issue">{{ item.issue || item.desc }}</div>
+                    <div
+                        class="tracking-item__urgency"
+                        :class="`tracking-item__urgency--${item.urgencyTone || 'danger'}`"
+                    >
+                        <img
+                            v-if="item.urgencyIcon"
+                            class="tracking-item__urgency-icon"
+                            :src="item.urgencyIcon"
+                            alt=""
+                        />
+                        <el-icon v-else><Clock /></el-icon>
+                        <span>{{ item.urgencyText || "建议 24 小时内处理" }}</span>
+                    </div>
+                </div>
+            </div>
+            <div class="tracking-item__footer">
+                <div class="tracking-item__actions">
+                    <div class="tracking-item__action" @click.stop="handleShare(item)">
+                        <Icon size="14" color="rgba(255,255,255,0.75)" name="share" />
+                        <span>{{ t("agriFile.forward") }}</span>
                     </div>
-                    <div class="tracking-item__btn" @click.stop="handleUploadClick(item)">
-                        <el-icon>
-                            <Plus />
-                        </el-icon>
-                        <span>{{ t("agriFile.uploadPhoto") }}</span>
+                    <span class="tracking-item__divider"></span>
+                    <div class="tracking-item__action" @click.stop="handleUploadClick(item)">
+                        <el-icon><Camera /></el-icon>
+                        <span>上传巡园照片</span>
                     </div>
                 </div>
+                <div class="tracking-item__detail-btn" @click.stop="goGrowthTrack(item)">
+                    进入详情 <el-icon><ArrowRightBold /></el-icon>
+                </div>
             </div>
         </div>
         <div class="crop-archive">
-            <div class="crop-archive__title">{{ t("agriFile.cropArchive") }}</div>
-            <PhenologyTrackTimelineItem :list="cropArchiveList" />
+            <!-- <div class="crop-archive__title">{{ t("agriFile.cropArchive") }}</div> -->
+            <PhenologyTrackTimelineItem theme="dark" :list="cropArchiveList" />
         </div>
         <album-upload-popup v-model:show="showUploadPopup" @confirm="handleUploadConfirm" />
         <tip-popup v-model:show="showSuccessPopup" type="executeSuccess" text="您已上传成功" text2="请等待诊断报告生成" hideBtn>
@@ -83,7 +99,7 @@ import { onActivated, onMounted, ref } from "vue";
 import { useRouter } from "vue-router";
 import { Icon } from "vant";
 import { ElMessage } from "element-plus";
-import { Plus } from "@element-plus/icons-vue";
+import { Camera, Clock, Plus, WarningFilled } from "@element-plus/icons-vue";
 import { useI18n } from "@/i18n";
 import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
 import albumUploadPopup from "./albumUploadPopup.vue";
@@ -93,7 +109,31 @@ import wx from "weixin-js-sdk";
 const { t } = useI18n();
 const router = useRouter();
 
-const PATROL_ICON = require("@/assets/img/report/wh-icon.png");
+const PATROL_ICON = require("@/assets/img/common/empty-bg.png");
+const periodIcon = require("@/assets/img/agricultural/period.png");
+const lightningIcon = require("@/assets/img/agricultural/lightning.png");
+const soilIcon = require("@/assets/img/agricultural/soil.png");
+/** 长势:最佳窗口(资源文件名 clock,实际为日历) */
+const calendarUrgencyIcon = require("@/assets/img/agricultural/clock.png");
+/** 土壤:含水率提示 */
+const waterUrgencyIcon = require("@/assets/img/agricultural/water.png");
+
+/** 土壤根系感知:暂无接口,先写死占位 */
+const buildSoilPatrolMock = () => ({
+    id: "soil-mock",
+    type: "soil",
+    theme: "blue",
+    level: "一级",
+    title: "土壤根系感知",
+    subject: "连阴雨清沟排水",
+    issue: "连阴雨泡得芽点发软,不清沟排水,连阴雨泡得芽点发软,不清沟排水,",
+    desc: "",
+    urgencyText: "土壤含水率偏高 · 已持续 5 天",
+    urgencyTone: "warn",
+    urgencyIcon: waterUrgencyIcon,
+    headerIcon: soilIcon,
+    icon: PATROL_ICON,
+});
 
 // ---------- 页面状态 ----------
 const zoneList = ref([]);
@@ -109,8 +149,13 @@ const patrolList = ref([
         subject: "",
         issue: "",
         desc: "",
+        urgencyText: "最佳窗口 10/14 - 10/20",
+        urgencyTone: "success",
+        urgencyIcon: calendarUrgencyIcon,
+        headerIcon: periodIcon,
         icon: PATROL_ICON,
     },
+    buildSoilPatrolMock(),
 ]);
 const cropArchiveList = ref([]);
 const showUploadPopup = ref(false);
@@ -142,6 +187,10 @@ const mapInteractTaskToPatrol = (task) => {
         desc: content.interact_reason || "",
         issue: content.interact_reason || "",
         subject: content.interact_title || "",
+        urgencyText: "最佳窗口 10/14 - 10/20",
+        urgencyTone: "success",
+        urgencyIcon: calendarUrgencyIcon,
+        headerIcon: periodIcon,
         icon: PATROL_ICON,
         zoneId: task.zone_id,
         interactCode: task.interact_code,
@@ -160,6 +209,10 @@ const mapAbnormalInteractToPatrol = (info, stressInfo) => {
         title: t("agriFile.patrolAbnormalTitle") || "",
         subject: info?.interaction_title || "",
         issue: info?.interaction_theme || "",
+        urgencyText: "建议 24 小时内处理",
+        urgencyTone: "danger",
+        urgencyIcon: "",
+        headerIcon: lightningIcon,
         icon: PATROL_ICON,
         zone: selectedZoneName.value || "",
         zoneId: selectedZoneId.value,
@@ -207,12 +260,17 @@ const clearFirstPatrolInteract = () => {
     patrolList.value[0] = {
         ...item,
         id: 1,
+        type: "growth",
         level: t("agriFile.riskLevel2"),
         title: t("agriFile.patrolGrowthTitle"),
         zone: selectedZoneName.value || t("agriFile.zoneOne"),
         subject: t("agriFile.patrolSubject"),
         issue: t("agriFile.patrolIssue"),
         desc: "",
+        urgencyText: "最佳窗口 10/14 - 10/20",
+        urgencyTone: "success",
+        urgencyIcon: calendarUrgencyIcon,
+        headerIcon: periodIcon,
         zoneId: undefined,
         interactCode: undefined,
         raw: undefined,
@@ -225,9 +283,17 @@ const isEmptyAbInfo = (abInfo) => {
     return Object.keys(abInfo).length === 0;
 };
 
-/** 移除异常卡片(无数据时不占位) */
+/** 保证土壤卡片始终在列表末尾(第 3 项;暂写死,后续接接口) */
+const ensureSoilPatrolMock = () => {
+    const mock = buildSoilPatrolMock();
+    const rest = patrolList.value.filter((item) => item.type !== "soil");
+    patrolList.value = [...rest, mock];
+};
+
+/** 移除异常卡片(无数据时不占位),并保持土壤在末尾 */
 const clearAbnormalPatrol = () => {
     patrolList.value = patrolList.value.filter((item) => item.type !== "abnormal");
+    ensureSoilPatrolMock();
 };
 
 // ---------- 业务逻辑:加载 ----------
@@ -331,11 +397,19 @@ const fetchAbnormalInteract = async () => {
                 ...mapped,
             };
         } else {
-            patrolList.value.push(mapped);
+            // 插在土壤之前,保证顺序:长势 → 异常 → 土壤
+            const soilIdx = patrolList.value.findIndex((item) => item.type === "soil");
+            if (soilIdx >= 0) {
+                patrolList.value.splice(soilIdx, 0, mapped);
+            } else {
+                patrolList.value.push(mapped);
+            }
         }
+        ensureSoilPatrolMock();
     } catch (e) {
         console.warn("[patrolPanel] getAbnormalInteract failed", e);
         clearAbnormalPatrol();
+        ensureSoilPatrolMock();
     }
 };
 
@@ -371,19 +445,27 @@ const fetchCropArchive = async () => {
 const fillMockLabels = () => {
     const albumName = selectedZoneName.value || t("agriFile.zoneOne");
     patrolList.value = patrolList.value.map((item, index) => {
+        // 土壤卡片写死占位,不走通用 mock
+        if (item.type === "soil") return { ...buildSoilPatrolMock(), zone: albumName };
         // 第一条由外部感知任务接口填充,不覆盖
         if (index === 0 && item.interactCode) return item;
         // 异常卡片由 getAbnormalInteract 填充,不覆盖
         if (item.type === "abnormal" && item.fwCode) return item;
+        const isGrowth = item.type === "growth" || index === 0;
         return {
             ...item,
             level: t("agriFile.riskLevel2"),
-            title: t(index === 0 ? "agriFile.patrolGrowthTitle" : "agriFile.patrolAbnormalTitle"),
+            title: t(isGrowth ? "agriFile.patrolGrowthTitle" : "agriFile.patrolAbnormalTitle"),
             zone: albumName,
             subject: t("agriFile.patrolSubject"),
             issue: t("agriFile.patrolIssue"),
+            urgencyText: isGrowth ? "最佳窗口 10/14 - 10/20" : "建议 24 小时内处理",
+            urgencyTone: isGrowth ? "success" : "danger",
+            urgencyIcon: isGrowth ? calendarUrgencyIcon : "",
+            headerIcon: isGrowth ? periodIcon : lightningIcon,
         };
     });
+    ensureSoilPatrolMock();
 };
 
 /** 按当前选中品种刷新巡园卡片与作物档案 */
@@ -505,18 +587,19 @@ defineExpose({
 
 <style lang="scss" scoped>
 .patrol-panel {
+    padding-top: 8px;
     .variety-bar {
         display: flex;
         align-items: center;
         justify-content: space-between;
-        gap: 8px;
-        margin-bottom: 10px;
+        gap: 12px;
+        margin-bottom: 12px;
 
         &__tags {
             display: flex;
             align-items: center;
             flex-wrap: nowrap;
-            gap: 6px;
+            gap: 16px;
             flex: 1;
             min-width: 0;
             overflow-x: auto;
@@ -529,17 +612,29 @@ defineExpose({
         }
 
         &__tag {
+            position: relative;
             flex: none;
             white-space: nowrap;
-            padding: 4px 16px;
-            border-radius: 4px;
-            background: #fff;
-            color: #4e5969;
+            padding: 4px 0 8px;
+            color: rgba(255, 255, 255, 0.35);
             font-size: 14px;
+            line-height: 20px;
 
             &.active {
-                background: #2199f8;
-                color: #fff;
+                color: #ffd489;
+                font-weight: 500;
+
+                &::after {
+                    content: "";
+                    position: absolute;
+                    left: 50%;
+                    bottom: 0;
+                    transform: translateX(-50%);
+                    width: 100%;
+                    height: 2px;
+                    border-radius: 2px;
+                    background: #ffd489;
+                }
             }
         }
 
@@ -548,172 +643,199 @@ defineExpose({
             display: flex;
             align-items: center;
             gap: 2px;
-            padding: 5px 10px;
-            border-radius: 4px;
-            border: 1px solid #2199f8;
-            background: rgba(33, 153, 248, 0.1);
-            color: #2199f8;
-            font-size: 14px;
+            height: 28px;
+            padding: 0 10px;
+            border-radius: 14px;
+            border: 1px solid rgba(255, 255, 255, 0.1);
+            background: rgba(255, 255, 255, 0.06);
+            color: rgba(255, 255, 255, 0.8);
+            font-size: 12px;
+            box-sizing: border-box;
         }
     }
 
     .tracking-item {
         width: 100%;
-        margin-bottom: 16px;
-        box-shadow: 0px 4px 4px 0px rgba(33, 153, 248, 0.1);
+        margin-bottom: 12px;
+        padding: 10px 8px 8px 12px;
+        border-radius: 12px;
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+        box-sizing: border-box;
 
         &: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;
+            gap: 8px;
+            padding-bottom: 10px;
+            border-bottom: 1px solid rgba(255, 255, 255, 0.08);
         }
 
         &__header-left {
             display: flex;
             align-items: center;
-            gap: 5px;
+            gap: 6px;
+            min-width: 0;
+        }
+
+        &__header-icon {
+            width: 16px;
+            height: 16px;
+            flex-shrink: 0;
+            object-fit: contain;
+        }
+
+        &__title {
+            color: rgba(255, 255, 255, 0.9);
+            font-size: 14px;
+            font-weight: 500;
+            line-height: 20px;
         }
 
         &__level {
+            flex-shrink: 0;
             display: flex;
             align-items: center;
-            gap: 4px;
-            padding: 0 4px;
-            border-radius: 2px;
-            background: #fff;
-            color: #ff6a6a;
+            gap: 3px;
+            height: 22px;
+            padding: 0 8px;
+            border-radius: 11px;
+            background: rgba(126, 35, 33, 0.2);
+            border: 1px solid rgba(201, 53, 50, 0.41);
+            color: #FFA1A0;
             font-size: 12px;
-            height: 20px;
-            line-height: 20px;
+            box-sizing: border-box;
+
+            .el-icon {
+                font-size: 12px;
+            }
         }
 
-        &__title {
+        &__body {
+            display: flex;
+            gap: 10px;
+            padding: 12px 0;
+        }
+
+        &__thumb {
+            width: 96px;
+            height: 96px;
+            flex-shrink: 0;
+            border-radius: 8px;
+            object-fit: cover;
+            background: rgba(255, 255, 255, 0.06);
+        }
+
+        &__content {
+            flex: 1;
+            min-width: 0;
+            display: flex;
+            flex-direction: column;
+            justify-content: space-around;
+            gap: 4px;
+        }
+
+        &__subject {
             color: #fff;
             font-size: 16px;
-            font-weight: 500;
+            line-height: 24px;
         }
 
-        &__zone {
+        &__issue {
+            color: rgba(255, 255, 255, 0.6);
             font-size: 12px;
+            line-height: 18px;
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+            -webkit-line-clamp: 2;
+            overflow: hidden;
+            word-break: break-all;
         }
 
-        &__share {
+        &__urgency {
             display: flex;
             align-items: center;
             gap: 4px;
-            padding: 2px 10px;
-            border-radius: 25px;
-            background: #fff;
-            color: #2199f8;
+            margin-top: 2px;
             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%);
+            .el-icon {
+                font-size: 13px;
+            }
+
+            &--success {
+                color: #85a53e;
+            }
+
+            &--danger {
+                color: #ff8f8f;
+            }
+
+            &--warn {
+                color: #ffd489;
             }
         }
 
-        &__action {
+        &__urgency-icon {
+            width: 14px;
+            height: 14px;
+            flex-shrink: 0;
+            object-fit: contain;
+        }
+
+        &__footer {
             display: flex;
             align-items: center;
             justify-content: space-between;
-            border: 1px solid rgba(33, 153, 248, 0.2);
-            border-radius: 6px;
-            padding: 8px;
+            gap: 8px;
         }
 
-        &__icon {
+        &__actions {
             display: flex;
             align-items: center;
-            gap: 5px;
-
-            img {
-                width: 18px;
-                height: 16px;
-            }
-        }
-
-        &__reason {
-            color: #2199f8;
-            font-weight: 500;
+            gap: 10px;
+            min-width: 0;
         }
 
-        &__issue {
-            margin-top: 2px;
-            color: #2199f8;
+        &__action {
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            color: rgba(255, 255, 255, 0.75);
             font-size: 12px;
+            line-height: 16px;
+            white-space: nowrap;
+
+            .el-icon {
+                font-size: 14px;
+            }
         }
 
-        &__action-main {
-            flex: 1;
-            padding-right: 4px;
+        &__divider {
+            width: 1px;
+            height: 12px;
+            background: rgba(255, 255, 255, 0.16);
+            flex-shrink: 0;
         }
 
-        &__btn {
-            flex: none;
+        &__detail-btn {
+            flex-shrink: 0;
+            // height: 32px;
+            // line-height: 32px;
+            padding: 7px 10px 8px 10px;
             display: flex;
             align-items: center;
-            gap: 2px;
-            padding: 7px 10px;
-            border-radius: 5px;
-            background: #2199f8;
-            color: #fff;
+            justify-content: center;
+            border-radius: 24px;
+            background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+            color: #000;
             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;
-            }
         }
     }
 }

+ 618 - 70
src/views/old_mini/agri_file/components/reportPanel.vue

@@ -1,38 +1,178 @@
 <template>
     <div class="report-panel">
-        <div v-for="item in reportList" :key="item.id" class="report-card" @click="handleReportClick(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 class="report-section">
+            <div class="report-section__title">最新报告</div>
+
+            <div
+                v-if="latestReport"
+                class="latest-card"
+                @click="handleReportClick(latestReport)"
+            >
+                <div class="latest-card__header">
+                    <div class="latest-card__header-left">
+                        <div class="latest-card__type-icon">
+                            <el-icon><Search /></el-icon>
+                        </div>
+                        <div class="latest-card__type-meta">
+                            <div class="latest-card__type-name">{{ latestReport.title || "—" }}</div>
+                            <div class="latest-card__date">{{ latestReport.dateText }}</div>
+                        </div>
+                    </div>
+                    <span class="risk-badge">待完善</span>
+                </div>
+
+                <div class="latest-card__subject">东区 · 待完善待完善</div>
+
+                <div class="latest-card__conclusion">
+                    <div class="latest-card__conclusion-label">核心结论</div>
+                    <div class="latest-card__conclusion-text">
+                        {{ latestReport.desc || "—" }}
+                    </div>
+                </div>
+
+                <div class="latest-card__evidence">
+                    <div class="latest-card__avatars">
+                        <img
+                            v-for="(src, idx) in mockPhotoAvatars"
+                            :key="idx"
+                            class="latest-card__avatar"
+                            :src="src"
+                            alt=""
+                        />
+                        <span class="latest-card__avatar-more">+3</span>
+                    </div>
+                    <div class="latest-card__evidence-text">
+                        基于 <em>6</em> 张待完善待完善 · <em>4</em> 项待完善
+                    </div>
+                </div>
+
+                <div class="latest-card__footer">
+                    <div class="latest-card__share" @click.stop="handleShare(latestReport)">
+                        <Icon size="14" color="rgba(255,255,255,0.75)" name="share" />
+                        <span>转发</span>
+                    </div>
+                    <div class="latest-card__detail" @click.stop="handleReportClick(latestReport)">
+                        查看完整报告 <el-icon><ArrowRightBold /></el-icon>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 报告生成中:暂写死 -->
+            <div class="generating-card">
+                <div class="generating-card__spinner"></div>
+                <div class="generating-card__text">
+                    <div class="generating-card__title">报告生成中</div>
+                    <div class="generating-card__desc">
+                        东区2号 · 已上传 <span class="generating-card__desc-count">3</span> 张巡园照片
+                    </div>
+                </div>
             </div>
-            <div class="report-card__body">
-                <div class="report-card__title">{{ item.title }}</div>
-                <div class="report-card__desc">{{ item.desc }}</div>
+        </div>
+
+        <!-- 历史报告 -->
+        <div class="report-section report-section--history">
+            <div class="report-section__head">
+                <div class="report-section__title">历史报告</div>
+                <div class="report-section__meta">
+                    共 <span class="report-section__meta-count">{{ historyReports.length }}</span> 份 · 按时间排序
+                </div>
+            </div>
+
+            <div v-if="!historyReports.length" class="report-empty">暂无历史报告</div>
+
+            <div
+                v-for="monthGroup in historyMonthGroups"
+                :key="monthGroup.key"
+                class="history-month"
+            >
+                <div class="history-month__label">
+                    <span>{{ monthGroup.label }}</span>
+                    <i class="history-month__line"></i>
+                </div>
+
+                <div
+                    v-for="item in monthGroup.items"
+                    :key="item.id"
+                    class="history-card"
+                    @click="handleReportClick(item)"
+                >
+                    <div class="history-card-wrap">
+                        <img class="history-card__cover" :src="item.cover" alt="" />
+                        <div class="history-card__body">
+                            <div class="history-card__top">
+                                <div class="history-card__type">
+                                    <el-icon><WarningFilled /></el-icon>
+                                    <span>{{ resolveTypeLabel(item.type) }}</span>
+                                </div>
+                                <span class="risk-badge">待完善</span>
+                            </div>
+                            <div class="history-card__title van-ellipsis">
+                                {{ item.title || "—" }}
+                            </div>
+                            <div class="history-card__desc van-ellipsis">
+                                {{ item.desc || "—" }}
+                            </div>
+                            <div class="history-card__line"></div>
+                            <div class="history-card__bottom">
+                                <span class="history-card__time">
+                                    <el-icon><Clock /></el-icon>
+                                    {{ item.dateText }}
+                                </span>
+                                <el-icon class="history-card__arrow"><ArrowRightBold /></el-icon>
+                            </div>
+                        </div>
+                    </div>
+                </div>
             </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { onActivated, onMounted, ref } from "vue";
-import { useI18n } from "@/i18n";
+import { computed, onActivated, onMounted, ref } from "vue";
 import { useRouter } from "vue-router";
+import { Icon } from "vant";
+import { ArrowRight, Clock, Search, WarningFilled } from "@element-plus/icons-vue";
 import { base_img_url2 } from "@/api/config";
+import wx from "weixin-js-sdk";
 
 const router = useRouter();
-const { t } = useI18n();
 
-// ---------- 常量 ----------
 const defaultCover = require("@/assets/img/common/sd-1.jpg");
+const emptyPhoto = require("@/assets/img/common/empty-bg.png");
+/** 证据区头像占位 */
+const mockPhotoAvatars = [emptyPhoto, emptyPhoto];
+
+const TYPE_LABEL_MAP = {
+    warning: "预警报告",
+    stress: "胁迫报告",
+    diagnosis: "巡园诊断报告",
+    patrol: "巡园诊断报告",
+};
 
-// ---------- 页面状态 ----------
 const reportList = ref([]);
 
-// ---------- 工具函数 ----------
+const latestReport = computed(() => reportList.value[0] || null);
+const historyReports = computed(() => reportList.value.slice(1));
+
+/** 历史按月分组,key 用 YYYY-MM */
+const historyMonthGroups = computed(() => {
+    const map = new Map();
+    historyReports.value.forEach((item) => {
+        const key = item.monthKey || "unknown";
+        if (!map.has(key)) {
+            map.set(key, {
+                key,
+                label: item.monthLabel || "其他",
+                items: [],
+            });
+        }
+        map.get(key).items.push(item);
+    });
+    return Array.from(map.values());
+});
+
 const getSelectedFarm = () => {
     try {
         return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
@@ -41,7 +181,6 @@ const getSelectedFarm = () => {
     }
 };
 
-/** cover_url 可能是文件名或完整 URL */
 const resolveCoverUrl = (path) => {
     if (!path) return defaultCover;
     const text = String(path).trim();
@@ -50,14 +189,53 @@ const resolveCoverUrl = (path) => {
     return base_img_url2 + text.replace(/^\//, "");
 };
 
-const mapReportItem = (item, index) => ({
-    id: item.id ?? `${item.time || ""}-${index}`,
-    title: item.title || "",
-    type: item.type || "",
-    desc: item.content || "",
-    date: item.time || "",
-    cover: resolveCoverUrl(item.cover_url),
-});
+const resolveTypeLabel = (type) => {
+    const key = String(type || "").toLowerCase();
+    return TYPE_LABEL_MAP[key] || type || "报告";
+};
+
+/** 解析接口 time → 展示用日期 / 月份 */
+const parseReportTime = (raw) => {
+    const text = String(raw || "").trim();
+    const iso = text.match(/(\d{4})-(\d{2})-(\d{2})/);
+    if (iso) {
+        return {
+            dateText: `${iso[2]}/${iso[3]}`,
+            monthKey: `${iso[1]}-${iso[2]}`,
+            monthLabel: `${Number(iso[2])} 月`,
+        };
+    }
+    const slash = text.match(/(\d{1,2})\/(\d{1,2})/);
+    if (slash) {
+        const mm = slash[1].padStart(2, "0");
+        const dd = slash[2].padStart(2, "0");
+        return {
+            dateText: `${mm}/${dd}`,
+            monthKey: mm,
+            monthLabel: `${Number(mm)} 月`,
+        };
+    }
+    return {
+        dateText: text || "—",
+        monthKey: "unknown",
+        monthLabel: "其他",
+    };
+};
+
+const mapReportItem = (item, index) => {
+    const timeInfo = parseReportTime(item.time);
+    return {
+        id: item.id ?? `${item.time || ""}-${index}`,
+        title: item.title || "",
+        type: item.type || "",
+        desc: item.content || "",
+        date: item.time || "",
+        dateText: timeInfo.dateText,
+        monthKey: timeInfo.monthKey,
+        monthLabel: timeInfo.monthLabel,
+        cover: resolveCoverUrl(item.cover_url),
+    };
+};
 
 const handleReportClick = (item) => {
     router.push({
@@ -69,7 +247,23 @@ const handleReportClick = (item) => {
     });
 };
 
-// ---------- 业务逻辑:加载 ----------
+const handleShare = (item) => {
+    const query = {
+        askInfo: { title: "转发报告", content: "是否分享给好友" },
+        shareText: item?.title || "报告",
+        targetUrl: "alert_detail",
+        paramsPage: JSON.stringify({
+            title: item?.title || "具体预警",
+            type: item?.type || "warning",
+            fromShare: 1,
+        }),
+        imageUrl: item?.cover || "https://birdseye-img.sysuimars.com/temp/field.png",
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
+};
+
 const fetchReportList = async () => {
     const farm = getSelectedFarm();
     const farmId = farm.farm_id ?? farm.id;
@@ -90,82 +284,436 @@ const fetchReportList = async () => {
     }
 };
 
-// ---------- 生命周期 ----------
 onMounted(fetchReportList);
 onActivated(fetchReportList);
 </script>
 
 <style lang="scss" scoped>
 .report-panel {
-    .report-card {
-        margin-bottom: 10px;
-        border-radius: 8px;
-        background: #fff;
+    .report-section--history {
+        margin-top: 10px;
+    }
+    .report-section {
+        & + & {
+            margin-top: 20px;
+        }
 
-        &:last-child {
-            margin-bottom: 0;
+        &__title {
+            margin-bottom: 10px;
+            font-size: 16px;
+            font-weight: 500;
+            color: #fff;
         }
 
-        &__cover {
-            position: relative;
-            width: 100%;
-            height: 158px;
-
-            img {
-                display: block;
-                width: 100%;
-                height: 100%;
-                object-fit: cover;
-                border-radius: 5px;
+        &__head {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 8px;
+            margin-bottom: 10px;
+
+            .report-section__title {
+                margin-bottom: 0;
             }
         }
 
+        &__meta {
+            flex-shrink: 0;
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.5);
+            .report-section__meta-count {
+                color: rgba(255, 255, 255, 0.9);
+                font-weight: 600;
+            }
+        }
+    }
+
+    .risk-badge {
+        flex-shrink: 0;
+        height: 20px;
+        background: rgba(107, 52, 18, 0.4);
+        padding: 0 6px;
+        border: 1px solid rgba(224, 122, 46, 0.4);
+        border-radius: 4px;
+        color: rgba(255, 159, 90, 1);
+        font-size: 12px;
+        box-sizing: border-box;
+        line-height: 20px;
+    }
+
+    .report-empty {
+        padding: 20px 0;
+        text-align: center;
+        font-size: 13px;
+        color: rgba(255, 255, 255, 0.35);
+    }
+
+    // ---------- 最新报告卡片 ----------
+    .latest-card {
+        padding: 12px;
+        border-radius: 12px;
+        border: 1px solid rgba(255, 212, 137, 0.16);
+        background: radial-gradient(80% 80% at 90% 0%, rgba(255, 212, 137, 0.12) 0%, rgba(255, 212, 137, 0) 100%);
+        box-sizing: border-box;
+
+        &__header {
+            display: flex;
+            align-items: flex-start;
+            justify-content: space-between;
+            gap: 8px;
+        }
+
+        &__header-left {
+            display: flex;
+            align-items: center;
+            gap: 11px;
+            min-width: 0;
+        }
+
+        &__type-icon {
+            width: 35px;
+            height: 35px;
+            border-radius: 8px;
+            background: rgba(255, 255, 255, 0.05);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+            color: rgba(255, 255, 255, 0.8);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-shrink: 0;
+            font-size: 18px;
+        }
+
+        &__type-meta {
+            min-width: 0;
+        }
+
+        &__type-name {
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.6);
+            line-height: 18px;
+        }
+
         &__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: 12px;
+            font-weight: 600;
+            color: rgba(255, 255, 255, 0.9);
+            line-height: 16px;
+        }
+
+        &__subject {
+            margin-top: 12px;
             font-size: 16px;
-            border-radius: 5px 0 5px 0;
+            color: #fff;
+            line-height: 24px;
+        }
+
+        &__conclusion {
+            margin-top: 7px;
+            padding: 7px 14px;
+            border-radius: 12px;
+            background: rgba(255, 255, 255, 0.05);
+            box-sizing: border-box;
+            position: relative;
+            &::after {
+                content: "";
+                position: absolute;
+                left: 0;
+                top: 12px;
+                width: 2px;
+                height: calc(100% - 24px);
+                background: rgba(255, 255, 255, 0.3);
+            }
+        }
+
+        &__conclusion-label {
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.5);
+            line-height: 18px;
+        }
+
+        &__conclusion-text {
+            margin-top: 4px;
+            font-size: 14px;
+            line-height: 20px;
+            color: rgba(255, 255, 255, 1);
+            word-break: break-all;
+            display: -webkit-box;
+            -webkit-box-orient: vertical;
+            -webkit-line-clamp: 3;
+            overflow: hidden;
+        }
+
+        &__evidence {
+            margin-top: 12px;
+            display: flex;
+            align-items: center;
+            gap: 12px;
         }
 
-        &__status {
-            position: absolute;
-            top: 10px;
-            right: 0;
+        &__avatars {
             display: flex;
             align-items: center;
+            flex-shrink: 0;
             gap: 4px;
-            padding: 2px 13px;
-            border-radius: 20px 0 0 20px;
-            background: #ff5454;
-            color: #fff;
+        }
+
+        &__avatar {
+            width: 36px;
+            height: 36px;
+            border-radius: 8px;
+            object-fit: cover;
+            // margin-left: -6px;
+            background: #2a2a2c;
+
+            &:first-child {
+                margin-left: 0;
+            }
+        }
+
+        &__avatar-more {
+            width: 36px;
+            height: 36px;
+            border-radius: 8px;
+            background: linear-gradient(135deg, #22211F 0%, #121214 100%);
+            color: rgba(255, 255, 255, 0.6);
             font-size: 12px;
+            font-weight: 600;
+            display: flex;
+            align-items: center;
+            justify-content: center;
         }
 
-        &__dot {
-            width: 6px;
-            height: 6px;
+        &__evidence-text {
+            min-width: 0;
+            font-size: 12px;
+            line-height: 16px;
+            color: rgba(255, 255, 255, 0.4);
+
+            em {
+                font-style: normal;
+                color: #fff;
+                font-weight: 500;
+            }
+        }
+
+        &__footer {
+            margin-top: 12px;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }
+
+        &__share {
+            flex-shrink: 0;
+            height: 32px;
+            padding: 0 23px;
+            border-radius: 8px;
+            background: rgba(255, 255, 255, 0.05);
+            color: rgba(255, 255, 255, 0.8);
+            font-size: 12px;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            box-sizing: border-box;
+        }
+
+        &__detail {
+            flex: 1;
+            height: 32px;
+            border-radius: 8px;
+            background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+            color: #1a1408;
+            font-size: 14px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 4px;
+        }
+    }
+
+    // ---------- 报告生成中 ----------
+    .generating-card {
+        margin-top: 10px;
+        padding: 14px;
+        border-radius: 12px;
+        border: 1px dashed rgba(255, 255, 255, 0.2);
+        background: rgba(255, 255, 255, 0.03);
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        box-sizing: border-box;
+
+        &__spinner {
+            width: 24px;
+            height: 24px;
+            flex-shrink: 0;
             border-radius: 50%;
-            background: #fff;
+            border: 2px solid rgba(255, 255, 255, 0.12);
+            border-top-color: rgba(255, 212, 137, 1);
+            animation: report-spin 0.9s linear infinite;
+            box-sizing: border-box;
         }
 
-        &__body {
+        &__text {
+            min-width: 0;
+        }
+
+        &__title {
+            font-size: 14px;
+            font-weight: 500;
+            color: #fff;
+            line-height: 20px;
+        }
+
+        &__desc {
+            margin-top: 2px;
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.4);
+            line-height: 16px;
+
+            .generating-card__desc-count {
+                font-style: normal;
+                color: #fff;
+                font-weight: 600;
+            }
+        }
+    }
+
+    @keyframes report-spin {
+        to {
+            transform: rotate(360deg);
+        }
+    }
+    .history-card + .history-card {
+        margin-top: 12px;
+    }
+    .history-month + .history-month {
+        margin-top: 14px;
+    }
+
+    // ---------- 历史报告 ----------
+    .history-month {
+
+        &__label {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-bottom: 8px;
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.5);
+        }
+
+        &__line {
+            flex: 1;
+            height: 1px;
+            background: rgba(255, 255, 255, 0.08);
+        }
+    }
+
+    .history-card {
+        padding: 1px;
+        border-radius: 12px;
+        background: linear-gradient(0deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)),
+linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 25%);
+        &-wrap {
+            display: flex;
+            gap: 10px;
             padding: 10px;
-            margin-top: 4px;
+            background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+            border-radius: 12px;
+        }
+
+        & + & {
+            margin-top: 8px;
+        }
+
+        &__cover {
+            width: 82px;
+            height: 93px;
+            flex-shrink: 0;
+            border-radius: 8px;
+            object-fit: cover;
+            background: rgba(255, 255, 255, 0.06);
+        }
+
+        &__body {
+            flex: 1;
+            min-width: 0;
+            display: flex;
+            flex-direction: column;
+        }
+
+        &__top {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 6px;
+        }
+
+        &__type {
+            display: flex;
+            align-items: center;
+            gap: 3px;
+            min-width: 0;
+            font-size: 12px;
+            border-radius: 4px;
+            padding: 0 6px;
+            height: 19px;
+            background: rgba(255, 255, 255, 0.05);
+            color: rgba(255, 255, 255, 0.6);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+
+            .el-icon {
+                font-size: 12px;
+                flex-shrink: 0;
+            }
         }
 
         &__title {
+            margin-top: 6px;
             font-size: 16px;
-            font-weight: 500;
+            color: #fff;
+            line-height: 24px;
         }
 
         &__desc {
-            margin-top: 4px;
-            color: #9e9b9b;
+            margin-top: 2px;
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.6);
+            line-height: 18px;
+        }
+
+        &__line {
+            margin: 4px 0;
+            height: 1px;
+            width: 100%;
+            background: rgba(255, 255, 255, 0.06);
+        }
+
+        &__bottom {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 6px;
+        }
+
+        &__time {
+            display: flex;
+            align-items: center;
+            gap: 5px;
+            font-size: 12px;
+            font-weight: 600;
+            color: rgba(255, 255, 255, 0.6);
+
+            .el-icon {
+                font-size: 12px;
+            }
+        }
+
+        &__arrow {
+            font-size: 12px;
+            color: rgba(255, 255, 255, 0.4);
         }
     }
 }

+ 45 - 27
src/views/old_mini/agri_file/index.vue

@@ -1,5 +1,6 @@
 <template>
     <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+        <!-- <div class="agri-file-page__header" /> -->
         <farm-header />
         <div class="nav-cards">
             <div
@@ -13,8 +14,9 @@
                     :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>
+                    <img v-show="activeNav !== item.key" class="nav-card__icon" :src="item.icon" alt="" />
+                    <img v-show="activeNav === item.key" class="nav-card__icon" :src="item.activeIcon" alt="" />
                 </div>
                 <div v-if="item.key === 'report' && guideVisible && !noFarms" class="report-guide-bubble">
                     <span class="report-guide-bubble__arrow"></span>
@@ -107,17 +109,20 @@ const navCards = [
     {
         key: "patrol",
         labelKey: "agriFile.patrolRecord",
-        icon: require("@/assets/img/agricultural/icon-1.png"),
+        icon: require("@/assets/img/agricultural/pen.png"),
+        activeIcon: require("@/assets/img/agricultural/pen-active.png"),
     },
     {
         key: "album",
         labelKey: "agriFile.agriAlbum",
-        icon: require("@/assets/img/agricultural/icon-2.png"),
+        icon: require("@/assets/img/agricultural/pic.png"),
+        activeIcon: require("@/assets/img/agricultural/pic-active.png"),
     },
     {
         key: "report",
         labelKey: "agriFile.diagnosisReport",
-        icon: require("@/assets/img/agricultural/icon-3.png"),
+        icon: require("@/assets/img/agricultural/file.png"),
+        activeIcon: require("@/assets/img/agricultural/file-active.png"),
     },
 ];
 
@@ -325,18 +330,31 @@ onBeforeUnmount(() => {
     width: 100%;
     height: 100%;
     overflow: hidden;
-    background: #141414;
+    background: url('@/assets/img/agricultural/page-bg.png') no-repeat top center;
+    background-size: 100% 250px;
+
+    .agri-file-page__header {
+        position: absolute;
+        z-index: -1;
+        top: 0;
+        background: url('@/assets/img/agricultural/page-bg.png') no-repeat center center;
+        background-size: 100% 100%;
+        width: 100%;
+        height: 250px;
+    }
 
     .nav-cards {
         position: relative;
         z-index: 2;
         display: flex;
         justify-content: space-around;
+        gap: 8px;
         padding: 10px 10px;
-        margin-bottom: 10px;
+        // margin-bottom: 10px;
 
         .nav-card-wrap {
             position: relative;
+            flex: 1;
 
             &--report {
                 z-index: 5;
@@ -346,31 +364,33 @@ onBeforeUnmount(() => {
         .nav-card {
             position: relative;
             display: flex;
-            flex-direction: column;
             align-items: center;
-            justify-content: center;
             border-radius: 8px;
-            background: #fff;
-            width: 90px;
-            height: 64px;
-            box-shadow: 0px 4px 4px 0px #0000000d;
+            background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+            border: 1px solid rgba(255, 255, 255, 0.1);
+            height: 43px;
+            font-size: 14px;
+            font-weight: 500;
+            box-sizing: border-box;
 
             .nav-card__icon {
-                width: 26px;
-                height: 26px;
-                margin-bottom: 4px;
+                width: 38px;
+                // height: 29px;
+                border-radius: 0 0 8px 0;
+                position: absolute;
+                bottom: 0;
+                right: 0;
+            }
+            .nav-card__label {
+                color: #A4A4A4;
+                padding-left: 12px;
             }
 
             &.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);
-                }
+                background: linear-gradient(180deg, #FFEBC9 0%, #FFCA6D 100%);
 
                 .nav-card__label {
-                    color: #fff;
+                    color: #292929;
                 }
 
                 &::after {
@@ -381,7 +401,7 @@ onBeforeUnmount(() => {
                     transform: translateX(-50%);
                     border-left: 6px solid transparent;
                     border-right: 6px solid transparent;
-                    border-top: 6px solid #2199f8;
+                    border-top: 6px solid #FFCA6D;
                     z-index: 3;
                 }
             }
@@ -430,10 +450,8 @@ onBeforeUnmount(() => {
     .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;
+        background: #141414;
+        padding: 8px 12px 12px;
 
         &::-webkit-scrollbar {
             display: none;

+ 172 - 32
src/views/old_mini/agri_file/pages/addZone.vue

@@ -3,23 +3,30 @@
         <custom-header :name="isFarmDrawMode ? t('agriFile.drawFarmArea') : t('agriFile.addZone')" />
         <div class="add-zone-content">
             <div class="map-container" ref="mapContainer"></div>
-            <div class="search-bar">
+            <!-- <div class="search-bar">
                 <location-search class="search-bar__search" :user-location="userLocation"
                     @change="handleLocationChange" />
-            </div>
-            <div class="map-tip normal-text" v-show="isFarmDrawMode">请勾画 <span class="map-tip__highlight">{{ getFarmType }}</span>
+            </div> -->
+            <div class="map-page-mask"></div>
+            <div class="map-tip normal-text" v-show="isFarmDrawMode">
+                <img class="map-tip__icon" src="@/assets/img/agricultural/edit.png" alt="">
+                请勾画 <span class="map-tip__highlight">{{ getFarmType }}</span>
                 的种植区域</div>
             <div class="map-tip" v-show="!isFarmDrawMode">{{ t("agriFile.selectZoneHint") }}</div>
             <div class="locate-btn" @click="handleLocate">
-                <img class="locate-btn__icon" src="@/assets/img/map/map-icon.png" alt="" />
+                <img class="locate-btn__icon" src="@/assets/img/home/current.png" alt="" />
             </div>
         </div>
-        <div class="custom-bottom-fixed-btns">
-            <div class="bottom-btn secondary-btn" @click="handleClearDraw">{{ t("agriFile.cancelSelect") }}</div>
+        <div class="custom-bottom-fixed-btns add-zone-bottom-btns">
+            <div class="bottom-btn secondary-btn" @click="handleClearDraw">
+                <el-icon><RefreshLeft /></el-icon>
+                <span>{{ t("agriFile.cancelSelect") }}</span>
+            </div>
             <div class="bottom-btn primary-btn" @click="handleConfirmDraw">{{ t("agriFile.confirmArea") }}</div>
         </div>
         <RegionNamePopup
             v-model:show="showRegionNamePopup"
+            theme="dark"
             :default-name="getFarmType"
             @confirm="handleFarmNameConfirm"
         />
@@ -40,10 +47,24 @@
         <tip-popup
             v-model:show="showDrawSuccessPopup"
             type="success"
-            text="区域已勾画完成"
-            hideBtn
-            @closedPopup="handleDrawSuccessClosed"
-        />
+            text="区域已勾画成功"
+            buttonText="完成"
+            class="draw-dark"
+            :close-on-click-overlay="false"
+            @confirm="handleDrawSuccessClosed"
+        >
+            <template #footer>
+                <div class="draw-success-upload" @click="handleUploadCropPhoto">
+                    <div class="draw-success-upload__text">
+                        <div class="draw-success-upload__title">可上传作物照片</div>
+                        <div class="draw-success-upload__desc">持续记录这块地的长势情况</div>
+                    </div>
+                    <div class="draw-success-upload__camera">
+                        <img class="draw-success-upload__camera__icon" src="@/assets/img/common/camera.png" alt="">
+                    </div>
+                </div>
+            </template>
+        </tip-popup>
     </div>
 </template>
 
@@ -53,10 +74,11 @@ import { useRoute, useRouter } from "vue-router";
 import { useStore } from "vuex";
 import { ElMessage } from "element-plus";
 import customHeader from "@/components/customHeader.vue";
-import locationSearch from "@/components/pageComponents/locationSearch.vue";
+// import locationSearch from "@/components/pageComponents/locationSearch.vue";
 import RegionNamePopup from "@/views/old_mini/recordDetails/components/RegionNamePopup.vue";
 import addZoneInfoPopup from "../components/addZoneInfoPopup.vue";
 import MapManage from "@/views/old_mini/recordDetails/map/mapManage.js";
+import { Map as KMapMap } from "@/utils/ol-map/KMap";
 import { POINT_ICON } from "@/views/old_mini/entry_information/map/selectLocationMap.js";
 import * as util from "@/common/ol_common.js";
 import { useI18n } from "@/i18n";
@@ -84,10 +106,12 @@ const showZoneInfoPopup = ref(false);
 const drawnPolygon = ref("");
 
 const DEFAULT_MAP_LOCATION = "POINT(113.6142086995688 23.585836479509055)";
+/** 勾画区:深色半透明填充 + 浅金描边(与种植点位图标一致) */
+const DRAW_GOLD = "#F5E1B9";
 const ZONE_STYLE = {
-    fill: "rgba(33, 153, 248, 0.35)",
-    fillSelected: "rgba(33, 153, 248, 0.45)",
-    stroke: "#2199F8",
+    fill: "rgba(0, 0, 0, 0.55)",
+    fillSelected: "rgba(0, 0, 0, 0.62)",
+    stroke: DRAW_GOLD,
 };
 
 /** 已有分区种植范围样式 */
@@ -96,6 +120,20 @@ const EXISTING_ZONE_STYLE = {
     stroke: "rgba(200, 200, 200, 0.55)",
 };
 
+function applyAgriDrawStyles() {
+    KMapMap.drawStyleColors = {
+        line: DRAW_GOLD,
+        vertex: DRAW_GOLD,
+        fill: [0, 0, 0, 0.55],
+        stroke: DRAW_GOLD,
+        hideArea: true,
+    };
+}
+
+function clearAgriDrawStyles() {
+    KMapMap.drawStyleColors = null;
+}
+
 const existingVarietyZones = ref([]);
 
 function mapExistingZoneItem(zone) {
@@ -213,6 +251,7 @@ function getMapLocation() {
 async function initMapView() {
     if (!mapContainer.value) return;
     mapManage.destroyMap();
+    applyAgriDrawStyles();
     mapManage.initMap(getMapLocation(), mapContainer.value, {
         editable: true,
         zoneStyle: ZONE_STYLE,
@@ -414,6 +453,11 @@ const handleDrawSuccessClosed = () => {
     leaveAfterConfirm();
 };
 
+/** 勾画成功弹窗:上传作物照片(预留) */
+const handleUploadCropPhoto = () => {
+    // TODO: 跳转/打开作物照片上传
+};
+
 const handleComplete = () => {
     showExecuteSuccessPopup.value = false;
     leaveAfterZoneCreated();
@@ -436,11 +480,58 @@ onActivated(() => {
 
 onDeactivated(() => {
     resetPageTransientState();
+    clearAgriDrawStyles();
     mapManage.destroyMap();
 });
 </script>
 
 <style lang="scss" scoped>
+.draw-success-upload {
+    width: 100%;
+    box-sizing: border-box;
+    margin-bottom: 13px;
+    padding: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 12px;
+    border-radius: 10px;
+    background: rgba(255, 212, 137, 0.08);
+    border: 1px solid rgba(255, 212, 137, 0.16);
+    cursor: pointer;
+
+    &__text {
+        min-width: 0;
+        flex: 1;
+    }
+
+    &__title {
+        font-size: 14px;
+        color: #ffd489;
+        line-height: 22px;
+    }
+
+    &__desc {
+        font-size: 12px;
+        color: rgba(255, 255, 255, 0.6);
+        line-height: 18px;
+    }
+
+    &__camera {
+        flex-shrink: 0;
+        width: 32px;
+        height: 32px;
+        border-radius: 50%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+        &__icon {
+            width: 12px;
+        }
+    }
+}
+
 .add-zone-page {
     width: 100%;
     height: 100vh;
@@ -497,26 +588,45 @@ onDeactivated(() => {
             }
         }
     }
+    .map-page-mask {
+        pointer-events: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 200px;
+        background: linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0) 100%);
+        z-index: 1;
+    }
 
     .map-tip {
+        pointer-events: none;
         position: absolute;
-        top: 64px;
+        top: 16px;
         left: 50%;
         z-index: 2;
         transform: translateX(-50%);
         padding: 8px 30px;
-        border-radius: 25px;
-        background: rgba(0, 0, 0, 0.5);
-        color: #7CC5FF;
+        border-radius: 10px;
+        background: rgba(20, 20, 22, 0.8);
+        color: rgba(255, 212, 137, 1);
         font-size: 12px;
         width: max-content;
+        border: 1px solid rgba(255, 212, 137, 0.16);
 
         &.normal-text {
             color: #fff;
+            display: flex;
+            align-items: center;
+            gap: 6px;
         }
 
         &__highlight {
-            color: #2199F8;
+            color: rgba(255, 212, 137, 1);
+        }
+
+        &__icon {
+            width: 14px;
         }
     }
 
@@ -528,35 +638,56 @@ onDeactivated(() => {
         display: flex;
         align-items: center;
         justify-content: center;
-        width: 36px;
-        height: 36px;
-        border-radius: 8px;
-        background: #fff;
-        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+        width: 48px;
+        height: 48px;
+        border-radius: 10px;
+        border: 1px solid rgba(255, 255, 255, 0.12);
+        background: rgba(20, 20, 22, 0.8);
 
         &__icon {
-            width: 16px;
-            height: 18px;
+            width: 20px;
         }
     }
 }
 
-.custom-bottom-fixed-btns {
+.add-zone-bottom-btns.custom-bottom-fixed-btns {
     position: relative;
     justify-content: space-between;
+    gap: 10px;
+    background: rgba(18, 18, 20, 1);
+    box-shadow: none;
 
     .bottom-btn {
-        padding: 10px 20px;
-        border-radius: 25px;
+        height: 44px;
+        padding: 0 16px;
+        box-sizing: border-box;
+        font-size: 15px;
+        font-weight: 500;
+        display: flex;
+        align-items: center;
+        justify-content: center;
     }
 
     .secondary-btn {
-        color: #666;
-        border: 1px solid rgba(153, 153, 153, 0.5);
+        flex: 0 0 auto;
+        min-width: 110px;
+        gap: 6px;
+        border-radius: 10px;
+        color: rgba(255, 255, 255, 0.85);
+        background: rgba(255, 255, 255, 0.05);
+        border: 1px solid rgba(255, 255, 255, 0.18);
+
+        &__icon {
+            flex-shrink: 0;
+        }
     }
 
     .primary-btn {
-        background: #2199f8;
+        flex: 1;
+        border: none;
+        border-radius: 10px;
+        color: rgba(0, 0, 0, 1);
+        background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
     }
 }
 
@@ -587,3 +718,12 @@ onDeactivated(() => {
     }
 }
 </style>
+
+
+<style lang="scss">
+.draw-dark {
+    &.tip-popup .tip-text {
+        margin-bottom: 13px;
+    }
+}
+</style>

+ 340 - 240
src/views/old_mini/entry_information/components/baInformation.vue

@@ -2,100 +2,95 @@
     <div class="ba-information">
         <div class="ba-information__content">
             <div class="page-header">
-                <div class="page-title">完善个人信息</div>
+                <div class="page-title">你的农场名称叫什么?</div>
+                <div class="farm-name-input">
+                    <input
+                        v-model="farmName"
+                        class="farm-name-input__field"
+                        type="text"
+                        maxlength="30"
+                        placeholder="农场名称农场名称"
+                    />
+                    <div
+                        v-if="farmName"
+                        class="farm-name-input__clear"
+                        @click="farmName = ''"
+                    >
+                        ×
+                    </div>
+                </div>
             </div>
 
-            <div class="info-card">
-                <div class="section-title">
-                    <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
-                    <span>基本信息</span>
-                </div>
+            <div class="page-header location-header">
+                <div class="page-title">这是你的农场位置吗?</div>
+                <div class="page-subtitle">已自动定位到你附近,点击地图可修改点位</div>
+            </div>
 
-                <el-form
-                    ref="formRef"
-                    :model="form"
-                    :rules="rules"
-                    label-position="left"
-                    label-width="72px"
-                    class="info-form"
-                    require-asterisk-position="right"
-                >
-                    <el-form-item label="姓名" prop="name">
-                        <el-input
-                            v-model="form.name"
-                            placeholder="请输入名字"
-                            maxlength="20"
-                            clearable
-                        />
-                    </el-form-item>
-                    <el-form-item label="手机号" prop="phone">
-                        <el-input
-                            v-model="form.phone"
-                            placeholder="请输入手机号"
-                            maxlength="11"
-                            clearable
-                            type="tel"
-                            inputmode="numeric"
-                            @input="onPhoneInput"
-                        />
-                    </el-form-item>
-                    <el-form-item label="常驻点位" prop="location" class="location-form-item">
-                        <div class="location-block" @click="goSelectLocation">
-                            <div class="map-preview">
-                                <div class="map-preview__map" ref="previewMapRef"></div>
-                                <div v-if="!form.location" class="map-preview__mask">点击地图选择常驻点位</div>
-                            </div>
-                        </div>
-                    </el-form-item>
-                </el-form>
+            <div class="map-card">
+                <div class="map-card__map" ref="previewMapRef"></div>
+                <div class="map-card__locate" @click.stop="handleRelocate">
+                    <img class="locate-crosshair__icon" src="@/assets/img/home/current.png" alt="">
+                </div>
+                <div class="map-card__address">
+                    <el-icon size="18" color="rgba(255, 255, 255, 0.6)"><Location /></el-icon>
+                    <div class="address-text">
+                        <div class="address-name">{{ locationAddress || "定位中..." }}</div>
+                        <div class="address-meta">已定位 · 精度约 10 米</div>
+                    </div>
+                </div>
             </div>
         </div>
 
-        <div class="custom-bottom-fixed-btns">
-            <div class="bottom-btn primary-btn" @click="handleNext">下一步 (1/4)</div>
+        <div class="ba-information__footer">
+            <div class="action-btns">
+                <div class="action-btn secondary" @click="handleRelocate">重新定位</div>
+                <div class="action-btn primary" @click="handleNext">确认信息</div>
+            </div>
+            <div class="search-link" @click="goSelectLocation">
+                <span class="search-link__dot" />
+                <span>位置不对?搜索地址</span>
+            </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { nextTick, onActivated, onBeforeUnmount, onMounted, reactive, ref } from "vue";
+import { nextTick, onActivated, onBeforeUnmount, onMounted, ref } from "vue";
 import { useRouter } from "vue-router";
 import { useStore } from "vuex";
-import SelectLocationMap from "../map/selectLocationMap.js";
+import { ElMessage } from "element-plus";
+import { convertPointToArray } from "@/utils/index";
+import SelectLocationMap, { POINT_ICON } from "../map/selectLocationMap.js";
 
+// ---------------------------------------------------------------------------
+// Props / Emits
+// ---------------------------------------------------------------------------
+const emit = defineEmits(["next"]);
+
+// ---------------------------------------------------------------------------
+// 常量
+// ---------------------------------------------------------------------------
 const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
 const FORM_KEY = "ENTRY_BA_FORM";
+const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
 
-const emit = defineEmits(["next"]);
+// ---------------------------------------------------------------------------
+// 页面状态
+// ---------------------------------------------------------------------------
 const router = useRouter();
 const store = useStore();
-
-const formRef = ref(null);
 const previewMapRef = ref(null);
+const farmName = ref("");
+const locationAddress = ref("");
+const locationPoint = ref("");
+const locationAdcode = ref("");
 let previewMap = null;
-const form = reactive({
-    name: "",
-    phone: "",
-    location: "",
-});
-
-const rules = {
-    name: [
-        { required: true, message: "请输入名字", trigger: "blur" },
-        { min: 1, max: 20, message: "姓名长度不超过20个字", trigger: "blur" },
-    ],
-    phone: [
-        { required: true, message: "请输入手机号", trigger: "blur" },
-        {
-            pattern: /^1[3-9]\d{9}$/,
-            message: "请输入正确的手机号码",
-            trigger: "blur",
-        },
-    ],
-    location: [{ required: true, message: "请选择常驻点位", trigger: "change" }],
-};
+let addressReqId = 0;
 
+// ---------------------------------------------------------------------------
+// 工具函数
+// ---------------------------------------------------------------------------
 function getDefaultPoint() {
     return (
         localStorage.getItem("MINI_USER_LOCATION_POINT") ||
@@ -113,91 +108,167 @@ function readJson(key) {
     }
 }
 
-/** 手机号仅保留数字,最多 11 位(type=number 时 maxlength 不生效) */
-function onPhoneInput(val) {
-    form.phone = String(val ?? "").replace(/\D/g, "").slice(0, 11);
+function toPointWkt(coordinate) {
+    return `POINT(${Number(coordinate[0])} ${Number(coordinate[1])})`;
 }
 
-function saveFormDraft() {
+function saveLocation(point, coordinate, address = "", adcode = "") {
+    locationPoint.value = point;
+    if (address) locationAddress.value = address;
+    if (adcode) locationAdcode.value = adcode;
     sessionStorage.setItem(
-        FORM_KEY,
-        JSON.stringify({ name: form.name, phone: form.phone })
+        LOCATION_KEY,
+        JSON.stringify({
+            point,
+            coordinate: coordinate?.length
+                ? [Number(coordinate[0]), Number(coordinate[1])]
+                : convertPointToArray(point).map(Number),
+            adcode: locationAdcode.value || "",
+            address: locationAddress.value || "",
+        })
     );
 }
 
-function restoreFormDraft() {
-    const draft = readJson(FORM_KEY);
-    if (!draft) return;
-    if (draft.name != null) form.name = draft.name;
-    if (draft.phone != null) form.phone = String(draft.phone).replace(/\D/g, "").slice(0, 11);
+/** 逆地理:根据坐标刷新地址文案 */
+async function fetchLocationAddress(point) {
+    const coordinate = convertPointToArray(point);
+    if (!coordinate?.length) return;
+    const reqId = ++addressReqId;
+    try {
+        const { result } = await VE_API.old_mini_map.location({
+            key: MAP_KEY,
+            location: `${coordinate[1]},${coordinate[0]}`,
+        });
+        if (reqId !== addressReqId) return;
+        const address =
+            result?.formatted_addresses?.recommend ||
+            result?.address ||
+            (result?.address_component
+                ? `${result.address_component.city || ""}${result.address_component.district || ""}${result.address_component.street || ""}`
+                : "");
+        const adcode = result?.ad_info?.adcode;
+        locationAddress.value = address || locationAddress.value;
+        locationAdcode.value = adcode != null && adcode !== "" ? String(adcode) : locationAdcode.value;
+        saveLocation(point, coordinate, locationAddress.value, locationAdcode.value);
+    } catch {
+        // 逆地理失败时保留已有文案
+    }
+}
+
+function onMapClick(coordinate) {
+    if (!coordinate?.length) return;
+    const point = toPointWkt(coordinate);
+    locationAddress.value = "定位中...";
+    locationAdcode.value = "";
+    saveLocation(point, coordinate);
+    fetchLocationAddress(point);
 }
 
-function utilWktToCoordinate(point) {
-    const match = String(point).match(/POINT\s*\(([\d.\-]+)\s+([\d.\-]+)\)/i);
-    if (!match) return null;
-    return [Number(match[1]), Number(match[2])];
+function refreshMapSize() {
+    nextTick(() => {
+        previewMap?.kmap?.map?.updateSize?.();
+        setTimeout(() => previewMap?.kmap?.map?.updateSize?.(), 120);
+    });
 }
 
-function initPreviewMap(point, showPoint = !!form.location) {
+function initPreviewMap(point) {
     if (!previewMapRef.value || !point) return;
-    const previewPadding = [50, 0, 10, 0];
-    const coordinate = utilWktToCoordinate(point);
     if (!previewMap) {
-        previewMap = new SelectLocationMap();
+        previewMap = new SelectLocationMap({
+            iconSrc: POINT_ICON.current,
+            onMapClick,
+        });
         previewMap.initMap(point, previewMapRef.value, {
-            enableClick: false,
-            padding: previewPadding,
-            showPoint,
+            enableClick: true,
+            showPoint: true,
         });
+        refreshMapSize();
         return;
     }
     if (previewMap.kmap) {
-        if (coordinate) {
-            if (showPoint) {
-                previewMap.showPoint(coordinate);
-            } else {
-                previewMap.hidePoint();
-                previewMap.setMapPosition(coordinate, false);
-            }
+        const coordinate = convertPointToArray(point).map(Number);
+        if (coordinate?.length) {
+            previewMap.showPoint(coordinate);
         }
+        refreshMapSize();
         return;
     }
     previewMap.initMap(point, previewMapRef.value, {
-        enableClick: false,
-        padding: previewPadding,
-        showPoint,
+        enableClick: true,
+        showPoint: true,
+    });
+    refreshMapSize();
+}
+
+function applyPoint(point, address = "", adcode = "") {
+    if (!point) return;
+    locationPoint.value = point;
+    if (address) locationAddress.value = address;
+    if (adcode) locationAdcode.value = String(adcode);
+    nextTick(() => {
+        initPreviewMap(point);
+        if (!address) fetchLocationAddress(point);
+        else saveLocation(point, convertPointToArray(point), address, adcode);
     });
 }
 
 function syncLocationFromSession() {
     const saved = readJson(LOCATION_KEY);
     if (saved?.point) {
-        form.location = saved.point;
-        formRef.value?.clearValidate("location");
-        nextTick(() => initPreviewMap(saved.point, true));
+        applyPoint(saved.point, saved.address || "", saved.adcode || "");
         return;
     }
-    nextTick(() => initPreviewMap(getDefaultPoint(), false));
+    applyPoint(getDefaultPoint());
+}
+
+function restoreFormDraft() {
+    const draft = readJson(FORM_KEY);
+    if (!draft) return;
+    if (draft.farmName != null) farmName.value = String(draft.farmName);
+}
+
+function saveFormDraft() {
+    const draft = readJson(FORM_KEY) || {};
+    sessionStorage.setItem(
+        FORM_KEY,
+        JSON.stringify({
+            ...draft,
+            farmName: farmName.value.trim(),
+        })
+    );
 }
 
+// ---------------------------------------------------------------------------
+// 事件处理 / 生命周期
+// ---------------------------------------------------------------------------
+const handleRelocate = () => {
+    applyPoint(getDefaultPoint());
+};
+
 const goSelectLocation = () => {
     saveFormDraft();
-    const mapCenter = form.location || getDefaultPoint();
+    const mapCenter = locationPoint.value || getDefaultPoint();
     router.push({
         path: "/entry_select_location",
         query: { mapCenter },
     });
 };
 
-const handleNext = async () => {
-    if (!formRef.value) return;
-    try {
-        await formRef.value.validate();
-        saveFormDraft();
-        emit("next", { ...form });
-    } catch {
-        // 校验未通过
+const handleNext = () => {
+    const name = farmName.value.trim();
+    if (!name) {
+        ElMessage.warning("请输入农场名称");
+        return;
     }
+    const point = locationPoint.value || getDefaultPoint();
+    if (!point) {
+        ElMessage.warning("请选择农场位置");
+        return;
+    }
+    const coordinate = convertPointToArray(point).map(Number);
+    saveLocation(point, coordinate, locationAddress.value, locationAdcode.value);
+    saveFormDraft();
+    emit("next", { farmName: name, location: point, address: locationAddress.value });
 };
 
 onMounted(() => {
@@ -222,173 +293,202 @@ onBeforeUnmount(() => {
     display: flex;
     flex-direction: column;
     overflow: hidden;
-    padding-bottom: 80px;
+    background: #0f0f0f;
+    color: #fff;
 
     &__content {
-        flex: 1;
+        // flex: 1;
         overflow-y: auto;
-        padding: 8px 16px 20px;
+        display: flex;
+        flex-direction: column;
+        padding: 28px 16px 0;
+        min-height: 0;
+    }
+
+    &__footer {
+        flex-shrink: 0;
+        padding: 24px 24px calc(16px + env(safe-area-inset-bottom));
     }
 
     .page-header {
-        padding: 8px 4px 16px;
+        margin-bottom: 15px;
+
+        &.location-header {
+            margin-bottom: 15px;
+        }
 
         .page-title {
-            font-size: 26px;
-            color: #005599;
-            font-family: "PangMenZhengDao";
-            line-height: 36px;
+            font-size: 20px;
+            line-height: 30px;
+            color: #fff;
         }
 
         .page-subtitle {
-            margin-top: 4px;
+            margin-top: 2px;
             font-size: 14px;
-            color: rgba(46, 46, 46, 0.4);
             line-height: 20px;
+            color: rgba(255, 255, 255, 0.6);
         }
     }
 
-    .info-card {
-        background: #fff;
-        border-radius: 12px;
-        padding: 16px 14px 8px;
-        box-shadow: 0 2px 12px rgba(33, 153, 248, 0.08);
-    }
-
-    .section-title {
+    .farm-name-input {
+        margin-top: 16px;
+        position: relative;
         display: flex;
         align-items: center;
-        gap: 8px;
-        margin-bottom: 8px;
-        font-size: 16px;
-        font-weight: 600;
-        color: #1a1a1a;
+        height: 46px;
+        padding: 0 14px;
+        border-radius: 12px;
+        background: rgba(255, 255, 255, 0.05);
+        border: 1px solid rgba(255, 255, 255, 0.4);
+        box-sizing: border-box;
+
+        &__field {
+            flex: 1;
+            min-width: 0;
+            height: 100%;
+            border: none;
+            outline: none;
+            background: transparent;
+            color: #fff;
+            font-size: 15px;
+            line-height: 22px;
+
+            &::placeholder {
+                color: rgba(255, 255, 255, 0.35);
+            }
+        }
 
-        .title-icon {
-            width: 14px;
-            height: 14px;
+        &__clear {
             flex-shrink: 0;
-            object-fit: contain;
+            width: 20px;
+            height: 20px;
+            margin-left: 8px;
+            border-radius: 50%;
+            background: rgba(255, 255, 255, 0.28);
+            color: #0f0f0f;
+            font-size: 14px;
+            line-height: 18px;
+            text-align: center;
+            cursor: pointer;
         }
     }
 
-    .info-form {
-        :deep(.el-form-item) {
-            margin-bottom: 0;
-            padding: 10px 0;
-        }
+    .map-card {
+        position: relative;
+        // flex: 1;
+        min-height: 280px;
+        height: 310px;
+        border-radius: 16px;
+        overflow: hidden;
+        background: #1a1a1a;
 
-        :deep(.el-form-item__label) {
-            color: #1a1a1a;
-            font-size: 15px;
-            font-weight: 400;
-            padding: 0;
-            line-height: 32px;
-            height: 32px;
+        &__map {
+            width: 100%;
+            // height: 100%;
+            height: 310px;
         }
 
-        :deep(.el-form-item__content) {
-            justify-content: flex-end;
-            line-height: 32px;
+        &__locate {
+            position: absolute;
+            top: 16px;
+            right: 12px;
+            z-index: 2;
+            width: 31px;
+            height: 31px;
+            border-radius: 10px;
+            border: 1px solid rgba(255, 255, 255, 0.12);
+            background: rgba(20, 20, 20, 0.8);
+            display: flex;
+            align-items: center;
+            justify-content: center;
         }
 
-        :deep(.el-form-item__error) {
-            padding-top: 4px;
-            text-align: right;
+        .locate-crosshair__icon {
+            width: 18px;
         }
 
-        :deep(.el-input__wrapper) {
-            box-shadow: none;
-            background: transparent;
-            padding: 0;
-
-            .el-input__inner {
-                text-align: right;
-                color: #1a1a1a;
-                font-size: 15px;
-
-                &::placeholder {
-                    color: rgba(0, 0, 0, 0.25);
-                }
-            }
+        &__address {
+            position: absolute;
+            left: 12px;
+            right: 12px;
+            bottom: 16px;
+            z-index: 2;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            padding: 10px 14px;
+            border-radius: 12px;
+            border: 1px solid rgba(255, 255, 255, 0.12);
+            background: rgba(20, 20, 22, 0.8);
+            backdrop-filter: blur(6px);
         }
 
-        .location-form-item {
-            display: block;
-
-            :deep(.el-form-item__label) {
-                float: none;
-                display: block;
-                text-align: left;
-                width: auto !important;
-                margin-bottom: 10px;
-                height: auto;
-                line-height: 22px;
-            }
+        .address-text {
+            min-width: 0;
+            flex: 1;
+        }
 
-            :deep(.el-form-item__content) {
-                margin-left: 0 !important;
-                line-height: normal;
-                justify-content: flex-start;
-            }
+        .address-name {
+            font-size: 16px;
+            line-height: 24px;
+            color: #fff;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
 
-            :deep(.el-form-item__error) {
-                text-align: left;
-                padding-top: 6px;
-            }
+        .address-meta {
+            margin-top: 2px;
+            font-size: 12px;
+            line-height: 18px;
+            color: rgba(255, 255, 255, 0.45);
         }
     }
 
-    .location-block {
-        width: 100%;
-
-        .map-preview {
-            position: relative;
-            width: 100%;
-            height: 140px;
-            border-radius: 8px;
-            overflow: hidden;
-            background: linear-gradient(135deg, #e8eef3 0%, #d4dde6 100%);
+    .action-btns {
+        display: flex;
+        gap: 12px;
+    }
 
-            &__map {
-                width: 100%;
-                height: 100%;
-                pointer-events: none;
-            }
+    .action-btn {
+        flex: 1;
+        height: 48px;
+        border-radius: 12px;
+        font-size: 16px;
+        font-weight: 500;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
 
-            &__mask {
-                position: absolute;
-                left: 0;
-                top: 0;
-                z-index: 2;
-                padding: 0 12px;
-                height: 30px;
-                line-height: 30px;
-                background: rgba(0, 0, 0, 0.4);
-                color: #fff;
-                font-size: 14px;
-                text-align: center;
-                width: fit-content;
-                border-radius: 8px 0 8px 0;
-            }
+        &.secondary {
+            background: #2a2a2a;
+            color: #fff;
         }
-    }
-
-    .custom-bottom-fixed-btns {
-        background: #fff;
-        box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.4);
 
-        .bottom-btn {
-            padding: 0 30px;
-            height: 40px;
-            line-height: 40px;
-            font-size: 14px;
-            border-radius: 25px;
+        &.primary {
+            background: linear-gradient(135deg, #f5e6b8 0%, #e8c56a 50%, #d4a84b 100%);
+            color: #1a1a1a;
         }
+    }
 
-        .primary-btn {
-            background: #2199f8;
-            color: #fff;
+    .search-link {
+        margin-top: 16px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        gap: 6px;
+        font-size: 13px;
+        color: rgba(255, 255, 255, 0.45);
+        cursor: pointer;
+
+        &__dot {
+            width: 12px;
+            height: 12px;
+            border: 1.5px solid rgba(255, 255, 255, 0.35);
+            border-radius: 50%;
+            box-sizing: border-box;
         }
     }
 }

+ 627 - 468
src/views/old_mini/entry_information/components/selectCategory.vue

@@ -1,83 +1,127 @@
 <template>
-    <div class="select-category">
-        <div class="select-category__content">
+    <div class="select-crop">
+        <div class="select-crop__content" v-loading="loading">
             <div class="page-header">
-                <div class="page-title">完善种植品类</div>
-                <div class="page-title">精准匹配农机服务<span class="page-title-tip">({{ multiple ? "多选" : "单选" }})</span></div>
+                <div class="page-title">请选择您的作物</div>
+                <div class="page-subtitle">已自动匹配您当地的主要作物</div>
             </div>
 
-            <div class="major-tabs">
+            <!-- 品类 -->
+            <div class="chip-grid crop-grid">
                 <div
-                    v-for="tab in majorTabs"
-                    :key="tab.key"
-                    class="major-tab"
-                    :class="{ active: activeTabKey === tab.key }"
-                    @click="activeTabKey = tab.key"
+                    v-for="item in displayCrops"
+                    :key="item.id"
+                    class="chip-item"
+                    :class="{ selected: isCropSelected(item) }"
+                    @click="handleSelectCrop(item)"
                 >
-                    {{ tab.label }}
+                    {{ item.name }}
                 </div>
             </div>
 
-            <div class="search-bar">
-                <el-icon class="search-icon"><Search /></el-icon>
-                <input
-                    v-model="searchKeyword"
-                    class="search-input"
-                    type="text"
-                    placeholder="请输入品类名称"
-                    @keyup.enter="handleSearch"
+            <el-select
+                v-model="cropSearchValue"
+                class="search-select"
+                filterable
+                clearable
+                placeholder="搜索其他作物"
+                popper-class="entry-dark-select-popper"
+                @change="handleCropSearchSelect"
+            >
+                <template #prefix>
+                    <el-icon class="search-prefix-icon"><Search /></el-icon>
+                </template>
+                <el-option
+                    v-for="item in cropSelectOptions"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="String(item.id)"
+                    :disabled="!isCropAllowed(item)"
                 />
-                <div class="search-btn" @click="handleSearch">搜索</div>
-            </div>
+            </el-select>
 
-            <div class="category-list" v-loading="loading">
-                <div
-                    v-for="group in displayGroups"
-                    :key="group.name"
-                    class="category-card"
-                >
-                    <div class="section-title">
-                        <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
-                        <span>{{ group.name }}</span>
+            <!-- 品种 -->
+            <template v-if="!multiple">
+                <div class="section-header">
+                    <div class="section-title">{{ selectedCrop?.name || "" }}品种</div>
+                </div>
+
+                <div class="chip-grid variety-grid" v-loading="varietyLoading">
+                    <div
+                        v-for="item in displayVarieties"
+                        :key="item.id"
+                        class="chip-item variety-item"
+                        :class="{ selected: String(selectedVarietyId) === String(item.id) }"
+                        @click="handleSelectVariety(item)"
+                    >
+                        <span class="chip-item__text">{{ item.name }}</span>
+                        <span v-if="String(selectedVarietyId) === String(item.id)" class="chip-item__check">
+                            <el-icon><Check /></el-icon>
+                        </span>
                     </div>
-                    <div class="tag-group add-tag-group">
-                        <div
-                            v-for="item in group.items"
-                            :key="item.id"
-                            class="tag-item"
-                            :class="{
-                                selected: item.selected,
-                                disabled: !isCropAllowed(item),
-                            }"
-                            @click="handleSelect(item)"
-                        >
-                            <span class="text">{{ item.name }}</span>
-                        </div>
+                    <div
+                        v-if="!varietyLoading && selectedCropId && !displayVarieties.length"
+                        class="empty-tip"
+                    >
+                        暂无品种
                     </div>
                 </div>
-                <div v-if="!loading && !displayGroups.length" class="empty-tip">暂无匹配品类</div>
-            </div>
+
+                <el-select
+                    v-model="varietySearchValue"
+                    class="search-select"
+                    filterable
+                    clearable
+                    placeholder="搜索其他品种"
+                    :disabled="!selectedCropId"
+                    popper-class="entry-dark-select-popper"
+                    @change="handleVarietySearchSelect"
+                >
+                    <template #prefix>
+                        <el-icon class="search-prefix-icon"><Search /></el-icon>
+                    </template>
+                    <el-option
+                        v-for="item in varietySelectOptions"
+                        :key="item.id"
+                        :label="item.name"
+                        :value="String(item.id)"
+                    />
+                </el-select>
+            </template>
         </div>
 
-        <div class="custom-bottom-fixed-btns">
-            <div class="bottom-btn secondary-btn" @click="emit('prev')">上一步</div>
-            <div class="bottom-btn primary-btn" @click="handleNext">{{ nextBtnText }}</div>
+        <div class="select-crop__footer">
+            <div class="action-btn secondary" @click="emit('prev')">上一步</div>
+            <div class="action-btn primary" @click="handleNext">
+                {{ multiple ? "下一步" : "确认品种" }}
+            </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { computed, onMounted, ref, watch } from "vue";
+import { computed, onMounted, ref } from "vue";
 import { ElMessage } from "element-plus";
-import { Search } from "@element-plus/icons-vue";
+import { Check, Search } from "@element-plus/icons-vue";
 
+// ---------------------------------------------------------------------------
+// Props / Emits
+// ---------------------------------------------------------------------------
+const props = defineProps({
+    /** 农服多选品类;农户为单选品类+品种 */
+    multiple: { type: Boolean, default: false },
+});
+const emit = defineEmits(["prev", "next"]);
+
+// ---------------------------------------------------------------------------
+// 常量
+// ---------------------------------------------------------------------------
 const SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
 const VARIETY_LIST_KEY = "ENTRY_SELECTED_VARIETY_LIST";
 const EQUIPMENT_KEY = "ENTRY_SELECTED_EQUIPMENT";
 const TASK_KEY = "ENTRY_SELECTED_FARM_TASKS";
 const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
 const META_KEYS = new Set(["city", "county", "county_code", "province"]);
-/** 与下游步骤约定一致的 majorKey */
 const FIRST_CROP_KEY_MAP = {
     果树: "fruit",
     大田: "field",
@@ -86,107 +130,75 @@ const TAB_ORDER = ["果树", "大田"];
 const DEFAULT_COUNTY_CODE = "440113";
 const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
 const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
+/** 顶部默认展示的品类数量 */
+const DEFAULT_CROP_VISIBLE = 6;
 
-/** 接口 is_use:'1' 开放,'0' 未开放;会话缓存无该字段时按当前列表 crop_id 解析 */
-const isCropAllowed = (item) => {
-    if (item?.is_use != null && item.is_use !== "") {
-        return String(item.is_use) === "1";
-    }
-    const id = item?.id;
-    if (id == null) return false;
-    for (const tab of majorTabs.value) {
-        for (const group of tab.groups || []) {
-            const found = (group.items || []).find((i) => String(i.id) === String(id));
-            if (found) return String(found.is_use ?? "0") === "1";
-        }
-    }
-    return false;
-};
-
-const props = defineProps({
-    multiple: { type: Boolean, default: false },
-});
-
-const emit = defineEmits(["prev", "next"]);
-
-const majorTabs = ref([]);
-const activeTabKey = ref("");
-const searchKeyword = ref("");
-const appliedKeyword = ref("");
+// ---------------------------------------------------------------------------
+// 页面状态
+// ---------------------------------------------------------------------------
 const loading = ref(false);
-
-const activeTab = computed(() =>
-    majorTabs.value.find((tab) => tab.key === activeTabKey.value) || majorTabs.value[0]
+const varietyLoading = ref(false);
+/** 全量品类(扁平) */
+const allCrops = ref([]);
+/** 顶部展示的品类(选中项会置顶) */
+const displayCrops = ref([]);
+const selectedCropId = ref("");
+/** 农服多选 */
+const selectedCropIds = ref(new Set());
+const cropSearchValue = ref("");
+
+const varietyList = ref([]);
+const displayVarieties = ref([]);
+const selectedVarietyId = ref("");
+const varietySearchValue = ref("");
+
+let varietyReqId = 0;
+
+// ---------------------------------------------------------------------------
+// 计算属性
+// ---------------------------------------------------------------------------
+const selectedCrop = computed(() =>
+    allCrops.value.find((item) => String(item.id) === String(selectedCropId.value)) ||
+    displayCrops.value.find((item) => String(item.id) === String(selectedCropId.value)) ||
+    null
 );
 
-const displayGroups = computed(() => {
-    const groups = activeTab.value?.groups || [];
-    const keyword = (appliedKeyword.value || "").trim();
-    if (!keyword) return groups;
-    return groups
-        .map((group) => ({
-            ...group,
-            items: group.items.filter((item) => item.name.includes(keyword)),
-        }))
-        .filter((group) => group.items.length);
-});
+const cropSelectOptions = computed(() => allCrops.value);
 
-const selectedItems = computed(() => {
-    const list = [];
-    majorTabs.value.forEach((tab) => {
-        tab.groups.forEach((group) => {
-            group.items.forEach((item) => {
-                if (item.selected) {
-                    list.push({
-                        id: item.id,
-                        name: item.name,
-                        code: item.code || "",
-                        groupName: group.name,
-                        majorKey: tab.key,
-                        majorLabel: tab.label,
-                        firstCrop: tab.firstCrop,
-                    });
-                }
-            });
-        });
-    });
-    return list;
-});
-
-const nextBtnText = computed(() => {
-    if (!props.multiple) return "下一步 (2/4)";
-    const hasField = selectedItems.value.some(
-        (item) => item.firstCrop === "大田" || item.majorKey === "field"
-    );
-    const hasFruit = selectedItems.value.some(
-        (item) => item.firstCrop === "果树" || item.majorKey === "fruit"
-    );
-    const total = hasField && hasFruit ? 6 : 4;
-    return `下一步 (2/${total})`;
-});
+const varietySelectOptions = computed(() => varietyList.value);
 
-const mapCropGroups = (list) => {
-    if (!Array.isArray(list)) return [];
-    return list
-        .map((group) => ({
-            name: group.crop_type || "",
-            hasAdvantage: !!group.has_advantage,
-            items: (group.items || []).map((crop) => ({
-                id: crop.crop_id,
-                name: crop.crop_name,
-                code: crop.crop_code || "",
-                is_use: crop.is_use ?? "0",
-                isAdvantage: !!crop.is_advantage,
-                advantageRank: crop.advantage_rank,
-                selected: false,
-            })),
-        }))
-        .filter((group) => group.name && group.items.length);
+const isCropSelected = (item) => {
+    if (!item) return false;
+    if (props.multiple) return selectedCropIds.value.has(String(item.id));
+    return String(selectedCropId.value) === String(item.id);
 };
 
-const resolveTabKey = (firstCrop) => FIRST_CROP_KEY_MAP[firstCrop] || firstCrop;
+// ---------------------------------------------------------------------------
+// 工具函数
+// ---------------------------------------------------------------------------
+const isCropAllowed = (item) => String(item?.is_use ?? "0") === "1";
+
+function readJson(key) {
+    try {
+        const raw = sessionStorage.getItem(key);
+        return raw ? JSON.parse(raw) : null;
+    } catch {
+        return null;
+    }
+}
+
+function writeJson(key, value) {
+    sessionStorage.setItem(key, JSON.stringify(value));
+}
+
+function clearDownstreamDrafts() {
+    sessionStorage.removeItem(VARIETY_LIST_KEY);
+    sessionStorage.removeItem(EQUIPMENT_KEY);
+    sessionStorage.removeItem(TASK_KEY);
+    sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
+}
 
-const buildTabsFromData = (data) => {
+function flattenCropsFromData(data) {
     if (!data || typeof data !== "object") return [];
     const cropKeys = Object.keys(data).filter(
         (key) => !META_KEYS.has(key) && Array.isArray(data[key])
@@ -199,218 +211,374 @@ const buildTabsFromData = (data) => {
         if (bi === -1) return -1;
         return ai - bi;
     });
-    return cropKeys.map((firstCrop) => ({
-        key: resolveTabKey(firstCrop),
-        label: `${firstCrop}类`,
-        firstCrop,
-        groups: mapCropGroups(data[firstCrop]),
-    }));
-};
-
-const cropTypeRank = (item) => {
-    if (item?.firstCrop === "大田" || item?.majorKey === "field") return 0;
-    if (item?.firstCrop === "果树" || item?.majorKey === "fruit") return 1;
-    return 2;
-};
-
-const sortFieldFirst = (list) =>
-    [...(list || [])].sort((a, b) => cropTypeRank(a) - cropTypeRank(b));
-
-const getCategorySignature = (list) =>
-    [...(list || [])].map((item) => String(item.id)).sort().join(",");
 
-const clearDownstreamDrafts = () => {
-    sessionStorage.removeItem(VARIETY_LIST_KEY);
-    sessionStorage.removeItem(EQUIPMENT_KEY);
-    sessionStorage.removeItem(TASK_KEY);
-    sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
-};
-
-const getCachedSelectedItems = () => {
-    try {
-        const raw = sessionStorage.getItem(SESSION_KEY);
-        if (!raw) return [];
-        const list = JSON.parse(raw);
-        if (!Array.isArray(list)) return [];
-        // 历史缓存可能含未开放品类,进页时过滤掉
-        const allowed = list.filter((item) => isCropAllowed(item));
-        return props.multiple ? allowed : allowed.slice(0, 1);
-    } catch {
-        return [];
-    }
-};
-
-const getSelectedIds = () => getCachedSelectedItems().map((item) => String(item.id));
-
-const clearAllSelection = () => {
-    majorTabs.value.forEach((tab) => {
-        tab.groups.forEach((group) => {
-            group.items.forEach((item) => {
-                item.selected = false;
+    const list = [];
+    const seen = new Set();
+    cropKeys.forEach((firstCrop) => {
+        const majorKey = FIRST_CROP_KEY_MAP[firstCrop] || firstCrop;
+        (data[firstCrop] || []).forEach((group) => {
+            (group.items || []).forEach((crop) => {
+                const id = crop.crop_id;
+                if (id == null || seen.has(String(id))) return;
+                seen.add(String(id));
+                list.push({
+                    id,
+                    name: crop.crop_name || "",
+                    code: crop.crop_code || "",
+                    is_use: crop.is_use ?? "0",
+                    isAdvantage: !!crop.is_advantage,
+                    advantageRank: crop.advantage_rank,
+                    groupName: group.crop_type || "",
+                    firstCrop,
+                    majorKey,
+                    majorLabel: `${firstCrop}类`,
+                });
             });
         });
     });
-};
+    return list;
+}
 
-const applyCachedSelection = () => {
-    const selectedIds = new Set(getSelectedIds());
-    if (!selectedIds.size) return;
-    majorTabs.value.forEach((tab) => {
-        tab.groups.forEach((group) => {
-            group.items.forEach((item) => {
-                item.selected = selectedIds.has(String(item.id));
-            });
-        });
-    });
-};
+function buildInitialDisplayCrops(list) {
+    const allowed = list.filter(isCropAllowed);
+    const advantage = allowed
+        .filter((item) => item.isAdvantage)
+        .sort((a, b) => Number(a.advantageRank || 99) - Number(b.advantageRank || 99));
+    const base = advantage.length ? advantage : allowed;
+    return base.slice(0, DEFAULT_CROP_VISIBLE);
+}
 
-const persistSelection = () => {
-    const list = props.multiple
-        ? sortFieldFirst(selectedItems.value)
-        : selectedItems.value.slice(0, 1);
-    if (list.length) {
-        sessionStorage.setItem(SESSION_KEY, JSON.stringify(list));
-    } else {
-        sessionStorage.removeItem(SESSION_KEY);
+/** 将某品类置顶到展示列表并选中 */
+function promoteCropToFront(crop) {
+    if (!crop) return;
+    const id = String(crop.id);
+    const rest = displayCrops.value.filter((item) => String(item.id) !== id);
+    displayCrops.value = [crop, ...rest];
+    selectedCropId.value = crop.id;
+    if (props.multiple) {
+        selectedCropIds.value = new Set([id]);
     }
-};
+}
 
-const readResidentLocation = () => {
-    try {
-        const raw = sessionStorage.getItem(LOCATION_KEY);
-        return raw ? JSON.parse(raw) : null;
-    } catch {
-        return null;
-    }
-};
+function getResidentPoint() {
+    const saved = readJson(LOCATION_KEY);
+    return (
+        saved?.point ||
+        localStorage.getItem("MINI_USER_LOCATION_POINT") ||
+        DEFAULT_POINT
+    );
+}
 
-const parsePointCoordinate = (point) => {
+async function fetchAdcodeByPoint(point) {
     const match = String(point || "").match(/POINT\s*\(([\d.\-]+)\s+([\d.\-]+)\)/i);
-    if (!match) return null;
-    return [Number(match[1]), Number(match[2])];
-};
-
-const fetchAdcodeByPoint = async (point) => {
-    const coordinate = parsePointCoordinate(point);
-    if (!coordinate) return "";
+    if (!match) return "";
     try {
         const { result } = await VE_API.old_mini_map.location({
             key: MAP_KEY,
-            location: `${coordinate[1]},${coordinate[0]}`,
+            location: `${match[2]},${match[1]}`,
         });
         const adcode = result?.ad_info?.adcode;
         return adcode != null && adcode !== "" ? String(adcode) : "";
     } catch {
         return "";
     }
-};
+}
 
-const resolveCountyCode = async () => {
-    const saved = readResidentLocation();
+async function resolveCountyCode() {
+    const saved = readJson(LOCATION_KEY);
     if (saved?.adcode) return String(saved.adcode);
-
-    const point =
-        saved?.point ||
-        localStorage.getItem("MINI_USER_LOCATION_POINT") ||
-        DEFAULT_POINT;
+    const point = saved?.point || getResidentPoint();
     const adcode = await fetchAdcodeByPoint(point);
     if (adcode && saved?.point) {
         try {
-            sessionStorage.setItem(
-                LOCATION_KEY,
-                JSON.stringify({ ...saved, adcode })
-            );
+            writeJson(LOCATION_KEY, { ...saved, adcode });
         } catch {
             // ignore
         }
     }
     return adcode || DEFAULT_COUNTY_CODE;
-};
+}
 
-const fetchCrops = async () => {
-    loading.value = true;
+function mapVarieties(list) {
+    return (list || [])
+        .map((item) => {
+            if (typeof item === "string") return { id: item, name: item };
+            const name = item?.name || "";
+            return { id: item?.id ?? name, name };
+        })
+        .filter((item) => item.name);
+}
+
+function promoteVarietyToFront(variety) {
+    if (!variety) return;
+    const id = String(variety.id);
+    const rest = displayVarieties.value.filter((item) => String(item.id) !== id);
+    displayVarieties.value = [variety, ...rest];
+    selectedVarietyId.value = variety.id;
+}
+
+function buildCategoryPayload(crop) {
+    return {
+        id: crop.id,
+        name: crop.name,
+        code: crop.code || "",
+        groupName: crop.groupName || "",
+        majorKey: crop.majorKey,
+        majorLabel: crop.majorLabel,
+        firstCrop: crop.firstCrop,
+    };
+}
+
+function buildVarietyPayload(crop, variety) {
+    return {
+        uid: `sel-${crop.id}-${variety.id}`,
+        id: variety.id,
+        name: variety.name,
+        categoryId: crop.id,
+        categoryName: crop.name,
+        cropCode: crop.code || "",
+        firstCrop: crop.firstCrop || "",
+        phenologyId: "",
+        phenophaseCode: "",
+        startTime: "",
+        area: "",
+        location: getResidentPoint(),
+    };
+}
+
+function saveCropDetailFromApi(data) {
+    if (data?.crop_id == null || data?.crop_id === "") return;
+    const cache = readJson("ENTRY_CROP_DETAIL") || {};
+    cache[String(data.crop_id)] = {
+        crop_id: data.crop_id,
+        crop_code: data.crop_code || "",
+        crop_group: data.crop_group || "",
+        crop_name: data.crop_name || "",
+        crop_type: data.crop_type || "",
+    };
+    writeJson("ENTRY_CROP_DETAIL", cache);
+}
+
+// ---------------------------------------------------------------------------
+// 业务逻辑
+// ---------------------------------------------------------------------------
+async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
+    if (cropId == null || cropId === "") {
+        varietyList.value = [];
+        displayVarieties.value = [];
+        selectedVarietyId.value = "";
+        return;
+    }
+    const reqId = ++varietyReqId;
+    varietyLoading.value = true;
     try {
-        const countyCode = await resolveCountyCode();
-        const res = await VE_API.entry.getCrops({
-            county_code: countyCode,
-        });
-        if (res.code === 200) {
-            const data = res.data || {};
-            try {
-                sessionStorage.setItem(
-                    "ENTRY_CROP_META",
-                    JSON.stringify({
-                        city: data.city || "",
-                        county: data.county || "",
-                        province: data.province || "",
-                        county_code: data.county_code || countyCode,
-                    })
-                );
-            } catch {
-                // ignore
+        const res = await VE_API.entry.getVarietiesByCrop({ crop_id: cropId });
+        if (reqId !== varietyReqId) return;
+        if (res.code === 200 && res.data) {
+            const data = res.data;
+            const crop = selectedCrop.value;
+            if (crop && data.crop_code) {
+                crop.code = data.crop_code;
+                if (data.crop_group) crop.firstCrop = crop.firstCrop || data.crop_group;
             }
-            const tabs = buildTabsFromData(data);
-            majorTabs.value = tabs;
-            if (!tabs.some((tab) => tab.key === activeTabKey.value)) {
-                activeTabKey.value = tabs[0]?.key || "";
+            if (crop && data.crop_code) {
+                saveCropDetailFromApi({
+                    crop_id: cropId,
+                    crop_code: data.crop_code,
+                    crop_group: data.crop_group || crop.firstCrop || "",
+                    crop_name: data.crop_name || crop.name,
+                    crop_type: data.crop_type || crop.groupName || "",
+                });
+            }
+            const list = mapVarieties(data.varieties);
+            varietyList.value = list;
+            displayVarieties.value = [...list];
+
+            const cached = readJson(VARIETY_LIST_KEY);
+            const cachedItem = Array.isArray(cached)
+                ? cached.find((row) => String(row.categoryId) === String(cropId))
+                : null;
+            if (cachedItem?.id && list.some((v) => String(v.id) === String(cachedItem.id))) {
+                const found = list.find((v) => String(v.id) === String(cachedItem.id));
+                promoteVarietyToFront(found);
+            } else if (selectFirst && list.length) {
+                promoteVarietyToFront(list[0]);
+            } else {
+                selectedVarietyId.value = "";
             }
-            applyCachedSelection();
         } else {
-            majorTabs.value = [];
-            activeTabKey.value = "";
+            varietyList.value = [];
+            displayVarieties.value = [];
+            selectedVarietyId.value = "";
+            ElMessage.error(res.msg || "获取品种列表失败");
+        }
+    } catch {
+        if (reqId !== varietyReqId) return;
+        varietyList.value = [];
+        displayVarieties.value = [];
+        selectedVarietyId.value = "";
+        ElMessage.error("获取品种列表失败,请稍后再试");
+    } finally {
+        if (reqId === varietyReqId) varietyLoading.value = false;
+    }
+}
+
+async function fetchCrops() {
+    loading.value = true;
+    try {
+        const countyCode = await resolveCountyCode();
+        const res = await VE_API.entry.getCrops({ county_code: countyCode });
+        if (res.code !== 200) {
+            allCrops.value = [];
+            displayCrops.value = [];
             ElMessage.error(res.msg || "获取种植品类失败");
+            return;
+        }
+        const data = res.data || {};
+        try {
+            writeJson("ENTRY_CROP_META", {
+                city: data.city || "",
+                county: data.county || "",
+                province: data.province || "",
+                county_code: data.county_code || countyCode,
+            });
+        } catch {
+            // ignore
+        }
+
+        const list = flattenCropsFromData(data);
+        allCrops.value = list;
+        let initial = buildInitialDisplayCrops(list);
+
+        const cached = readJson(SESSION_KEY);
+        const cachedList = Array.isArray(cached) ? cached : [];
+        if (props.multiple) {
+            const ids = new Set(
+                cachedList.filter((item) => isCropAllowed(item)).map((item) => String(item.id))
+            );
+            selectedCropIds.value = ids;
+            // 缓存选中项置顶展示
+            const promoted = cachedList
+                .map((c) => list.find((item) => String(item.id) === String(c.id)))
+                .filter(Boolean);
+            const rest = initial.filter((item) => !ids.has(String(item.id)));
+            displayCrops.value = [...promoted, ...rest].slice(0, Math.max(DEFAULT_CROP_VISIBLE, promoted.length));
+            selectedCropId.value = displayCrops.value[0]?.id || "";
+        } else {
+            const cachedOne = cachedList[0];
+            const found = cachedOne
+                ? list.find((item) => String(item.id) === String(cachedOne.id) && isCropAllowed(item))
+                : null;
+            if (found) {
+                const rest = initial.filter((item) => String(item.id) !== String(found.id));
+                displayCrops.value = [found, ...rest].slice(0, DEFAULT_CROP_VISIBLE);
+                selectedCropId.value = found.id;
+            } else {
+                displayCrops.value = initial;
+                const first = initial.find(isCropAllowed) || initial[0];
+                if (first) selectedCropId.value = first.id;
+            }
+            if (selectedCropId.value) {
+                await fetchVarietiesByCrop(selectedCropId.value, { selectFirst: true });
+            }
         }
     } catch {
-        majorTabs.value = [];
-        activeTabKey.value = "";
+        allCrops.value = [];
+        displayCrops.value = [];
         ElMessage.error("获取种植品类失败,请稍后再试");
     } finally {
         loading.value = false;
     }
-};
-
-const handleSearch = () => {
-    appliedKeyword.value = searchKeyword.value.trim();
-};
+}
 
-const handleSelect = (item) => {
+const handleSelectCrop = async (item) => {
     if (!isCropAllowed(item)) {
         ElMessage.warning("该品种暂未开放");
         return;
     }
     if (props.multiple) {
-        item.selected = !item.selected;
-        persistSelection();
+        const id = String(item.id);
+        const next = new Set(selectedCropIds.value);
+        if (next.has(id)) next.delete(id);
+        else next.add(id);
+        selectedCropIds.value = next;
+        selectedCropId.value = item.id;
         return;
     }
-    const wasSelected = item.selected;
-    clearAllSelection();
-    item.selected = !wasSelected;
-    persistSelection();
+    if (String(selectedCropId.value) === String(item.id)) return;
+    selectedCropId.value = item.id;
+    varietySearchValue.value = "";
+    await fetchVarietiesByCrop(item.id, { selectFirst: true });
 };
 
-const handleNext = () => {
-    const list = (props.multiple
-        ? sortFieldFirst(selectedItems.value)
-        : selectedItems.value.slice(0, 1)
-    ).filter((item) => isCropAllowed(item));
-    if (!list.length) {
-        ElMessage.warning(props.multiple ? "请选择种植品类" : "请选择一个种植品类");
+const handleCropSearchSelect = async (value) => {
+    if (value == null || value === "") return;
+    const crop = allCrops.value.find((item) => String(item.id) === String(value));
+    cropSearchValue.value = "";
+    if (!crop) return;
+    if (!isCropAllowed(crop)) {
+        ElMessage.warning("该品种暂未开放");
         return;
     }
-    const prevSignature = getCategorySignature(getCachedSelectedItems());
-    const nextSignature = getCategorySignature(list);
-    if (prevSignature !== nextSignature) {
-        clearDownstreamDrafts();
+    promoteCropToFront(crop);
+    varietySearchValue.value = "";
+    if (!props.multiple) {
+        await fetchVarietiesByCrop(crop.id, { selectFirst: true });
     }
-    sessionStorage.setItem(SESSION_KEY, JSON.stringify(list));
-    emit("next", list);
 };
 
-watch(activeTabKey, () => {
-    appliedKeyword.value = "";
-    searchKeyword.value = "";
-});
+const handleSelectVariety = (item) => {
+    if (!item) return;
+    selectedVarietyId.value = item.id;
+};
+
+const handleVarietySearchSelect = (value) => {
+    if (value == null || value === "") return;
+    const variety = varietyList.value.find((item) => String(item.id) === String(value));
+    varietySearchValue.value = "";
+    if (!variety) return;
+    promoteVarietyToFront(variety);
+};
+
+const handleNext = () => {
+    if (props.multiple) {
+        const list = allCrops.value
+            .filter((item) => selectedCropIds.value.has(String(item.id)) && isCropAllowed(item))
+            .map(buildCategoryPayload);
+        if (!list.length) {
+            ElMessage.warning("请选择种植品类");
+            return;
+        }
+        const prev = readJson(SESSION_KEY);
+        const prevSig = (Array.isArray(prev) ? prev : []).map((i) => String(i.id)).sort().join(",");
+        const nextSig = list.map((i) => String(i.id)).sort().join(",");
+        if (prevSig !== nextSig) clearDownstreamDrafts();
+        writeJson(SESSION_KEY, list);
+        emit("next", list);
+        return;
+    }
+
+    const crop = selectedCrop.value;
+    if (!crop || !isCropAllowed(crop)) {
+        ElMessage.warning("请选择一个种植品类");
+        return;
+    }
+    const variety =
+        displayVarieties.value.find((item) => String(item.id) === String(selectedVarietyId.value)) ||
+        varietyList.value.find((item) => String(item.id) === String(selectedVarietyId.value));
+    if (!variety) {
+        ElMessage.warning("请选择品种");
+        return;
+    }
+
+    const prev = readJson(SESSION_KEY);
+    const prevId = Array.isArray(prev) && prev[0] ? String(prev[0].id) : "";
+    if (prevId !== String(crop.id)) clearDownstreamDrafts();
+
+    writeJson(SESSION_KEY, [buildCategoryPayload(crop)]);
+    writeJson(VARIETY_LIST_KEY, [buildVarietyPayload(crop, variety)]);
+    emit("next", { crop, variety });
+};
 
 onMounted(() => {
     fetchCrops();
@@ -418,229 +586,220 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
-.select-category {
-    height: calc(100% - 80px);
+.select-crop {
+    flex: 1;
+    min-height: 0;
     display: flex;
     flex-direction: column;
-    overflow: auto;
+    overflow: hidden;
+    background: #0f0f0f;
+    color: #fff;
 
     &__content {
         flex: 1;
+        min-height: 0;
         overflow-y: auto;
-        padding: 8px 10px 20px;
+        padding: 28px 16px 20px;
+    }
+
+    &__footer {
+        flex-shrink: 0;
+        display: flex;
+        gap: 12px;
+        padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
     }
 
     .page-header {
-        padding: 8px 0 20px;
+        margin-bottom: 18px;
 
         .page-title {
-            font-size: 26px;
-            color: #005599;
-            font-family: "PangMenZhengDao";
-            line-height: 30px;
-            .page-title-tip {
-                font-size: 20px;
-            }
+            font-size: 24px;
+            font-weight: 600;
+            line-height: 34px;
+            color: #fff;
         }
 
         .page-subtitle {
-            font-size: 14px;
-            color: rgba(46, 46, 46, 0.4);
+            margin-top: 6px;
+            font-size: 13px;
             line-height: 20px;
+            color: rgba(255, 255, 255, 0.45);
         }
     }
 
-    .major-tabs {
-        display: flex;
+    .section-header {
+        margin: 22px 0 12px;
+
+        .section-title {
+            font-size: 18px;
+            font-weight: 600;
+            line-height: 26px;
+            color: #fff;
+        }
+    }
+
+    .chip-grid {
+        display: grid;
         gap: 10px;
         margin-bottom: 12px;
 
-        .major-tab {
-            flex: 1;
-            height: 36px;
-            line-height: 36px;
-            text-align: center;
-            border-radius: 8px;
-            font-size: 15px;
-            color: rgba(0, 0, 0, 0.55);
-            background: #fff;
-            cursor: pointer;
-
-            &.active {
-                color: #fff;
-                background: #2199f8;
-                font-weight: 500;
-            }
+        &.crop-grid {
+            grid-template-columns: repeat(3, 1fr);
+        }
+
+        &.variety-grid {
+            grid-template-columns: repeat(2, 1fr);
+            min-height: 44px;
         }
     }
 
-    .search-bar {
+    .chip-item {
+        position: relative;
+        height: 44px;
+        padding: 0 10px;
+        border-radius: 10px;
+        box-sizing: border-box;
         display: flex;
         align-items: center;
-        height: 36px;
-        padding: 0 12px;
-        margin-bottom: 10px;
-        background: rgba(255, 255, 255, 0.5);
-        border: 1px solid rgba(33, 153, 248, 0.5);
-        border-radius: 6px;
-
-        .search-icon {
-            color: rgba(0, 0, 0, 0.35);
-            font-size: 16px;
-            margin-right: 6px;
+        justify-content: center;
+        text-align: center;
+        font-size: 15px;
+        color: rgba(255, 255, 255, 0.85);
+        background: #2a2a2a;
+        border: 1px solid transparent;
+        cursor: pointer;
+
+        &.selected {
+            color: #e8c56a;
+            border-color: #e8c56a;
+            background: rgba(232, 197, 106, 0.08);
         }
 
-        .search-input {
-            flex: 1;
-            border: none;
-            outline: none;
-            background: transparent;
-            font-size: 14px;
-            color: #333;
+        &.variety-item {
+            justify-content: space-between;
+            padding: 0 12px;
+
+            .chip-item__text {
+                flex: 1;
+                min-width: 0;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                text-align: left;
+            }
 
-            &::placeholder {
-                color: rgba(0, 0, 0, 0.3);
+            .chip-item__check {
+                flex-shrink: 0;
+                width: 20px;
+                height: 20px;
+                margin-left: 8px;
+                border-radius: 50%;
+                background: #e8c56a;
+                color: #1a1a1a;
+                display: inline-flex;
+                align-items: center;
+                justify-content: center;
+                font-size: 12px;
             }
-        }
 
-        .search-btn {
-            flex-shrink: 0;
-            padding-left: 10px;
-            color: #2199f8;
-            font-size: 14px;
-            cursor: pointer;
+            &.selected {
+                justify-content: space-between;
+            }
         }
     }
 
-    .category-list {
-        .category-card {
-            background: #fff;
+    .empty-tip {
+        grid-column: 1 / -1;
+        padding: 24px 0;
+        text-align: center;
+        color: rgba(255, 255, 255, 0.35);
+        font-size: 14px;
+    }
+
+    .search-select {
+        width: 100%;
+        margin-bottom: 4px;
+
+        :deep(.el-select__wrapper) {
+            min-height: 44px;
+            padding: 0 12px;
             border-radius: 12px;
-            padding: 14px 12px 16px;
-            margin-bottom: 12px;
+            background: #1c1c1c;
+            box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12) inset;
         }
 
-        .section-title {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-            margin-bottom: 4px;
-            font-size: 16px;
-            font-weight: 600;
-            color: #1a1a1a;
+        :deep(.el-select__wrapper.is-focused) {
+            box-shadow: 0 0 0 1px rgba(232, 197, 106, 0.55) inset;
+        }
 
-            .title-icon {
-                width: 15px;
-                height: 15px;
-                object-fit: contain;
-                flex-shrink: 0;
-            }
+        :deep(.el-select__placeholder) {
+            color: rgba(255, 255, 255, 0.35);
         }
 
-        .empty-tip {
-            padding: 40px 0;
-            text-align: center;
-            color: rgba(0, 0, 0, 0.35);
-            font-size: 14px;
+        :deep(.el-select__selected-item),
+        :deep(.el-select__input) {
+            color: #fff;
         }
-    }
 
-    .tag-group {
-        display: grid;
-        grid-template-columns: repeat(4, 1fr);
-        gap: 0 8px;
-        font-size: 14px;
+        :deep(.el-select__caret) {
+            color: rgba(255, 255, 255, 0.55);
+        }
 
-        .tag-item {
-            margin-top: 10px;
-            position: relative;
-            border-radius: 6px;
-            box-sizing: border-box;
-            height: 36px;
-            text-align: center;
-            line-height: 36px;
-            cursor: pointer;
-            transition: all 0.3s;
-            color: #000000;
-            background: rgba(241, 241, 241, 0.12);
-            border: 1px solid #ebebeb;
-
-            .text {
-                display: inline-flex;
-                align-items: center;
-            }
+        :deep(.el-select__prefix) {
+            color: rgba(255, 255, 255, 0.45);
+        }
+    }
 
-            &.selected {
-                border: 1px solid #2199f8;
-                background: #e8f5ff;
-                color: #2199f8;
-
-                &::after {
-                    content: "";
-                    position: absolute;
-                    z-index: 9;
-                    top: -1px;
-                    right: -1px;
-                    width: 18px;
-                    height: 14px;
-                    background: url("@/assets/img/home/checked-bg-top.png") no-repeat bottom right / 18px 13px;
-                }
-            }
+    .search-prefix-icon {
+        font-size: 16px;
+    }
 
-            &.disabled {
-                color: rgba(0, 0, 0, 0.25);
-                cursor: not-allowed;
-            }
+    .action-btn {
+        flex: 1;
+        height: 48px;
+        border-radius: 12px;
+        font-size: 16px;
+        font-weight: 500;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+
+        &.secondary {
+            background: #2a2a2a;
+            color: #fff;
         }
 
-        &.add-tag-group {
-            .tag-item {
-                color: #000000;
-                background: rgba(241, 241, 241, 0.12);
-                border: 1px solid #ebebeb;
-
-                &.selected {
-                    border: 1px solid #2199f8;
-                    background: #e8f5ff;
-                    color: #2199f8;
-                }
-
-                &.disabled {
-                    color: rgba(0, 0, 0, 0.25);
-                }
-            }
+        &.primary {
+            background: linear-gradient(135deg, #f5e6b8 0%, #e8c56a 50%, #d4a84b 100%);
+            color: #1a1a1a;
         }
     }
+}
+</style>
 
-    .custom-bottom-fixed-btns {
-        display: flex;
-        justify-content: space-between;
-        align-items: baseline;
-        padding: 10px 12px 0 12px;
-        height: 80px;
-        background: #fff;
-        box-sizing: border-box;
-        box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.4);
-
-        .bottom-btn {
-            padding: 0 30px;
-            height: 40px;
-            line-height: 40px;
-            font-size: 14px;
-            border-radius: 25px;
-            text-align: center;
-        }
+<style lang="scss">
+.entry-dark-select-popper {
+    background: #1c1c1c !important;
+    border: 1px solid rgba(255, 255, 255, 0.12) !important;
+
+    .el-select-dropdown__item {
+        color: rgba(255, 255, 255, 0.85);
 
-        .secondary-btn {
-            color: #2199f8;
-            background: #fff;
-            border: 1px solid #2199f8;
+        &.is-hovering,
+        &:hover {
+            background: rgba(232, 197, 106, 0.12);
         }
 
-        .primary-btn {
-            background: #2199f8;
-            color: #fff;
+        &.is-selected {
+            color: #e8c56a;
+            font-weight: 500;
         }
     }
+
+    .el-popper__arrow::before {
+        background: #1c1c1c !important;
+        border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    }
 }
 </style>

+ 6 - 5
src/views/old_mini/entry_information/components/selectEquipment.vue

@@ -170,7 +170,7 @@ const allEquipmentItems = computed(() =>
 const primaryBtnText = computed(() => {
     if (submitting.value) return "提交中...";
     if (props.isLastStep) return "提交信息";
-    const total = props.isService ? props.totalSteps : 4;
+    const total = props.isService ? props.totalSteps : props.totalSteps || 3;
     return `下一步 (3/${total})`;
 });
 
@@ -529,8 +529,8 @@ function buildFarmPayload(includeEquipment = true) {
         "";
 
     const payload = {
-        user_name: baForm.name,
-        tel: baForm.phone,
+        user_name: baForm.farmName || baForm.name,
+        tel: baForm.phone || "",
         // admin_id: 94494,
         admin_id: getAdminId(),
         user_id: getUserId(),
@@ -576,6 +576,7 @@ function buildReportParams() {
         crop: detail.crop_name || first?.categoryName || "",
         variety: first?.name || "",
         tel: baForm.phone || "",
+        farmName: baForm.farmName || baForm.name || "",
     };
 }
 
@@ -653,8 +654,8 @@ async function submitEntry(includeEquipment = true) {
     }
 
     const baForm = readJson(SESSION.BA_FORM);
-    if (!baForm?.name || !baForm?.phone) {
-        ElMessage.warning("请先完善个人信息");
+    if (!baForm?.farmName && !baForm?.name) {
+        ElMessage.warning("请先完善农场信息");
         return;
     }
 

+ 14 - 13
src/views/old_mini/entry_information/index.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="entry-information-page">
+    <div class="entry-information-page" :class="{ 'is-dark-step': isDarkStep }">
         <custom-header
             name="录入信息"
             bgColor="#fff"
@@ -21,12 +21,6 @@
                 @next="handleNext"
                 @confirm="handleConfirm"
             />
-            <select-variety
-                v-else-if="showVarietyStep"
-                :multiple="isServiceEntry"
-                @prev="handlePrev"
-                @next="handleNext"
-            />
             <manual-farming-service
                 v-else-if="showManualStep"
                 :has-field="hasFieldSelected"
@@ -46,7 +40,6 @@ import customHeader from "@/components/customHeader.vue";
 import baInformation from "./components/baInformation.vue";
 import serviceInformation from "./components/serviceInformation.vue";
 import selectCategory from "./components/selectCategory.vue";
-import selectVariety from "./components/selectVariety.vue";
 import selectEquipment from "./components/selectEquipment.vue";
 import manualFarmingService from "./components/manualFarmingService.vue";
 
@@ -87,19 +80,24 @@ function syncAdminIdFromQuery() {
 const currentStep = ref(readStep());
 const inviteType = ref(readInviteType());
 const isServiceEntry = computed(() => inviteType.value === "service");
+/** 农户录入:位置确认 / 作物品种页为深色 */
+const isDarkStep = computed(
+    () => !isServiceEntry.value && (currentStep.value === 1 || currentStep.value === 2)
+);
 const hasFieldSelected = ref(false);
 const hasFruitSelected = ref(false);
 const isBothCrops = computed(() => hasFieldSelected.value && hasFruitSelected.value);
+/** 农户:位置 → 作物+品种 → 设备(共 3 步) */
 const totalSteps = computed(() => {
-    if (!isServiceEntry.value) return 4;
+    if (!isServiceEntry.value) return 3;
     return isBothCrops.value ? 6 : 4;
 });
 const maxStep = computed(() => {
-    if (!isServiceEntry.value) return 4;
+    if (!isServiceEntry.value) return 3;
     return isBothCrops.value ? 5 : 4;
 });
 const showEquipmentStep = computed(() => {
-    if (!isServiceEntry.value) return currentStep.value === 4;
+    if (!isServiceEntry.value) return currentStep.value === 3;
     if (hasFieldSelected.value && currentStep.value === 3) return true;
     if (isBothCrops.value && currentStep.value === 5) return true;
     if (hasFruitSelected.value && !hasFieldSelected.value && currentStep.value === 4) return true;
@@ -114,7 +112,6 @@ const equipmentIsLastStep = computed(() => {
     if (!isServiceEntry.value) return true;
     return equipmentScope.value === "fruit";
 });
-const showVarietyStep = computed(() => !isServiceEntry.value && currentStep.value === 3);
 const showManualStep = computed(() => {
     if (!isServiceEntry.value) return false;
     if (hasFieldSelected.value) return currentStep.value === 4;
@@ -164,7 +161,7 @@ watch(
 
 const handleNext = () => {
     refreshHasField();
-    if (hasFieldSelected.value && currentStep.value === 3) {
+    if (isServiceEntry.value && hasFieldSelected.value && currentStep.value === 3) {
         sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
     }
     if (currentStep.value < maxStep.value) {
@@ -189,6 +186,10 @@ const handleConfirm = () => {
     background: linear-gradient(270deg, rgba(218, 195, 255, 0.59) 0%, #E6F2FF 0.01%, #8FC5FE 100%);
     display: flex;
     flex-direction: column;
+
+    &.is-dark-step {
+        background: #0f0f0f;
+    }
 }
 
 .entry-information-body {

+ 1 - 1
src/views/old_mini/entry_information/map/selectLocationMap.js

@@ -25,7 +25,7 @@ class SelectLocationMap {
   constructor(options = {}) {
     const iconSrc = options.iconSrc || POINT_ICON.resident;
     this.onMapClick = options.onMapClick || null;
-    this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
+    this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 10001, {
       style: () => {
         return new Style({
           image: new Icon({

+ 2 - 10
src/views/old_mini/growth_report/alert_detail.vue

@@ -85,6 +85,7 @@
 import { computed, onMounted, ref } from "vue";
 import { useRoute } from "vue-router";
 import customHeader from "@/components/customHeader.vue";
+import { resolveCropIcon } from "@/utils/cropIcon";
 
 const route = useRoute();
 
@@ -97,17 +98,8 @@ const phenologyIcon = require("@/assets/img/common/header-icon-3.png");
 const weatherIcon = require("@/assets/img/common/header-icon-1.png");
 const soilIcon = require("@/assets/img/common/header-icon-2.png");
 const cropCover = require("@/assets/img/home/plant.png");
-/** 当前作物名 → 品类图标(与 CropAlertPanel 一致) */
-const CROP_ICON_MAP = {
-    水稻: require("@/assets/img/type_icon/水稻.png"),
-    玉米: require("@/assets/img/type_icon/玉米.png"),
-    荔枝: require("@/assets/img/type_icon/荔枝.png"),
-};
 
-const resolveCropCover = (cropName) => {
-    const name = String(cropName || "").trim();
-    return (name && CROP_ICON_MAP[name]) || cropCover;
-};
+const resolveCropCover = (cropName) => resolveCropIcon(cropName, cropCover);
 
 const detailType = computed(() =>
     String(route.query.type || "") === DETAIL_TYPE.STRESS

+ 4 - 12
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -118,6 +118,7 @@ import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch
 import { useStore } from "vuex";
 import { useI18n } from "@/i18n";
 import { useRouter } from "vue-router";
+import { DEFAULT_CROP_AVATAR, resolveCropIcon } from "@/utils/cropIcon";
 
 const router = useRouter();
 const store = useStore();
@@ -165,7 +166,6 @@ const emit = defineEmits(["viewDetail", "heightChange", "zoneChange"]);
 
 const weatherIcon = require("@/assets/img/report/icon-weather.png");
 const stressIcon = require("@/assets/img/report/icon-warning.png");
-const DEFAULT_CROP_AVATAR = "https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png";
 /** 卡片占位文案(接口字段待补) */
 const PLACEHOLDER = {
     riskLevel: "待完善",
@@ -173,12 +173,6 @@ const PLACEHOLDER = {
     metricUnit: "待完善",
     timeText: "待完善待完善待完善",
 };
-/** 当前作物名 → 品类图标 */
-const CROP_ICON_MAP = {
-    水稻: require("@/assets/img/type_icon/水稻.png"),
-    玉米: require("@/assets/img/type_icon/玉米.png"),
-    荔枝: require("@/assets/img/type_icon/荔枝.png"),
-};
 
 /** 面板 bottom:取 tabBar 顶边距视口底的距离,避免 offsetHeight 偏差留缝 */
 const resolveTabBarHeight = () => {
@@ -200,11 +194,9 @@ const currentCropName = ref(props.cropName || "");
 /** 无农场时来自点位预警接口的 crop_name */
 const pointCropName = ref("");
 /** 按当前作物名匹配 type_icon,未命中用默认图 */
-const resolvedCropAvatar = computed(() => {
-    const name = String(currentCropName.value || "").trim();
-    if (name && CROP_ICON_MAP[name]) return CROP_ICON_MAP[name];
-    return props.cropAvatar || DEFAULT_CROP_AVATAR;
-});
+const resolvedCropAvatar = computed(() =>
+    resolveCropIcon(currentCropName.value, props.cropAvatar || DEFAULT_CROP_AVATAR)
+);
 const cropMenuVisible = ref(false);
 const guideVisible = ref(false);
 

+ 2 - 1
src/views/old_mini/growth_report/index.vue

@@ -1042,7 +1042,8 @@ onActivated(() => {
         gap: 12px;
         padding: 4px;
         border-radius: 10px;
-        background: rgba(20, 20, 22, 0.8);
+        // background: rgba(20, 20, 22, 0.8);
+        background: #1b1b1d;
         box-sizing: border-box;
         border: 1px solid rgba(255, 255, 255, 0.12);
 

+ 141 - 3
src/views/old_mini/recordDetails/components/RegionNamePopup.vue

@@ -4,18 +4,23 @@
         round
         closeable
         class="region-name-popup"
+        :class="{ 'region-name-popup--dark': theme === 'dark' }"
+        :position="theme === 'dark' ? 'center' : 'bottom'"
         :close-on-click-overlay="false"
         teleport="body"
     >
         <div class="region-name-popup__content">
-            <div class="region-name-popup__title">{{ $t('区域名称') }}</div>
+            <div class="region-name-popup__title">{{ titleText }}</div>
+            <div v-if="fieldLabelText" class="region-name-popup__label">{{ fieldLabelText }}</div>
             <el-input
                 v-model="regionName"
                 size="large"
-                :placeholder="$t('请输入区域名称')"
+                :placeholder="placeholderText"
+                maxlength="12"
+                show-word-limit
             />
             <div class="region-name-popup__confirm" @click="handleConfirm">
-                {{ $t('确认区域') }}
+                {{ confirmTextLabel }}
             </div>
         </div>
     </popup>
@@ -36,6 +41,29 @@ const props = defineProps({
         type: String,
         default: "",
     },
+    /** default:浅蓝白底;dark:农事档案深色金配色 */
+    theme: {
+        type: String,
+        default: "default",
+    },
+    /** 弹窗主标题;空则走默认文案 */
+    title: {
+        type: String,
+        default: "",
+    },
+    /** 输入框上方字段名;dark 主题默认「区域名称」 */
+    fieldLabel: {
+        type: String,
+        default: "",
+    },
+    confirmText: {
+        type: String,
+        default: "",
+    },
+    maxlength: {
+        type: Number,
+        default: 12,
+    },
 });
 
 const emit = defineEmits(["update:show", "confirm"]);
@@ -47,6 +75,23 @@ const showValue = computed({
     set: (value) => emit("update:show", value),
 });
 
+const titleText = computed(() => {
+    if (props.title) return props.title;
+    return props.theme === "dark" ? "填写分区信息" : "区域名称";
+});
+
+const fieldLabelText = computed(() => {
+    if (props.fieldLabel) return props.fieldLabel;
+    return props.theme === "dark" ? "区域名称" : "";
+});
+
+const placeholderText = computed(() => "请输入区域名称");
+
+const confirmTextLabel = computed(() => {
+    if (props.confirmText) return props.confirmText;
+    return props.theme === "dark" ? "确认信息" : "确认区域";
+});
+
 watch(
     () => props.show,
     (val) => {
@@ -86,6 +131,13 @@ const handleConfirm = () => {
     &__title {
         font-size: 16px;
         margin-bottom: 12px;
+        color: #000;
+    }
+
+    &__label {
+        margin-bottom: 8px;
+        font-size: 14px;
+        color: #333;
     }
 
     :deep(.el-input__wrapper) {
@@ -102,5 +154,91 @@ const handleConfirm = () => {
         font-size: 16px;
         text-align: center;
     }
+
+    &--dark {
+        width: 90%;
+        overflow: visible;
+        padding: 1px;
+        // background: transparent !important;
+        background: linear-gradient(0deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12)),
+linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 12%);
+
+        :deep(.van-popup__close-icon) {
+            top: 20px;
+            right: 16px;
+            width: 24px;
+            height: 24px;
+            border-radius: 50%;
+            background: rgba(255, 255, 255, 0.06);
+            color: rgba(255, 255, 255, 0.7);
+            font-size: 14px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+
+        .region-name-popup__content {
+            padding: 20px 16px 20px;
+            background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+            border-radius: 16px;
+        }
+
+        .region-name-popup__title {
+            margin-bottom: 20px;
+            font-size: 17px;
+            color: rgba(255, 212, 137, 1);
+        }
+
+        .region-name-popup__label {
+            margin-bottom: 10px;
+            font-size: 14px;
+            color: rgba(255, 255, 255, 0.9);
+        }
+
+        :deep(.el-input) {
+            --el-input-text-color: #fff;
+            --el-input-bg-color: transparent;
+            --el-input-border-color: rgba(255, 255, 255, 0.18);
+            --el-input-hover-border-color: rgba(255, 255, 255, 0.28);
+            --el-input-focus-border-color: #ffca6d;
+            --el-input-placeholder-color: rgba(255, 255, 255, 0.35);
+            --el-fill-color-blank: transparent;
+        }
+
+        :deep(.el-input__wrapper) {
+            padding: 0 10px;
+            min-height: 40px;
+            border-radius: 10px;
+            background: rgba(255, 255, 255, 0.05);
+            box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12) inset;
+        }
+
+        :deep(.el-input__inner) {
+            height: 40px;
+            color: #fff;
+        }
+
+        :deep(.el-input__count) {
+            background: transparent;
+            color: rgba(255, 255, 255, 0.35);
+            font-size: 12px;
+        }
+
+        :deep(.el-input__count-inner) {
+            background: transparent;
+            color: rgba(255, 255, 255, 0.35);
+        }
+
+        .region-name-popup__confirm {
+            margin-top: 20px;
+            height: 44px;
+            padding: 0;
+            line-height: 44px;
+            border-radius: 8px;
+            background: linear-gradient(135deg, #FFEBC9 0%, #FFCA6D 100%);
+            color: rgba(26, 20, 8, 1);
+            font-size: 16px;
+        }
+    }
 }
 </style>

+ 2 - 1
src/views/old_mini/recordDetails/map/mapManage.js

@@ -176,6 +176,7 @@ class MapManage {
       onDrawOverlap,
       singleDraw = false,
       onDrawLimit,
+      maxPoints,
     } = options;
     this.zoneStyle = zoneStyle ? { ...DEFAULT_ZONE_STYLE, ...zoneStyle } : { ...DEFAULT_ZONE_STYLE };
     this.editable = editable;
@@ -204,7 +205,7 @@ class MapManage {
           this.handleDrawEndOverlap(e.feature);
         }
         this.syncSingleDrawState();
-      });
+      }, { maxPoints });
       this.kmap.modifyDraw();
       this.setupOverlapConstraintListeners();
       this.setupSingleDrawListeners();