Просмотр исходного кода

feat: 新增农场,长势记录详情

lxf 2 дней назад
Родитель
Сommit
477286d0df
34 измененных файлов с 3474 добавлено и 1337 удалено
  1. BIN
      src/assets/img/agricultural/card-bg.png
  2. BIN
      src/assets/img/agricultural/detail-bg.png
  3. BIN
      src/assets/img/agricultural/gaojin.png
  4. BIN
      src/assets/img/farm/advice-farm.png
  5. BIN
      src/assets/img/farm/farm.png
  6. BIN
      src/assets/img/farm/shield.png
  7. BIN
      src/assets/img/farm/warning-farm.png
  8. BIN
      src/assets/img/home/task.png
  9. BIN
      src/assets/img/map/current-point.png
  10. 156 94
      src/components/allGarden.vue
  11. 7 6
      src/components/customHeader.vue
  12. 53 3
      src/components/pageComponents/locationSearch.vue
  13. 21 5
      src/components/popup/restorePlantingPopup.vue
  14. 2 2
      src/i18n/messages.js
  15. 277 42
      src/views/old_mini/agri_file/components/addZoneInfoPopup.vue
  16. 3 2
      src/views/old_mini/agri_file/components/albumPanel.vue
  17. 440 277
      src/views/old_mini/agri_file/pages/abnormalTrack.vue
  18. 66 6
      src/views/old_mini/agri_file/pages/addZone.vue
  19. 6 7
      src/views/old_mini/agri_file/pages/albumMap.vue
  20. 366 244
      src/views/old_mini/agri_file/pages/growthTrack.vue
  21. 1 1
      src/views/old_mini/agri_file/pages/regionAlbums.vue
  22. 6 6
      src/views/old_mini/entry_information/components/baInformation.vue
  23. 446 0
      src/views/old_mini/entry_information/components/entryComplete.vue
  24. 324 69
      src/views/old_mini/entry_information/components/selectCategory.vue
  25. 115 29
      src/views/old_mini/entry_information/index.vue
  26. 4 0
      src/views/old_mini/entry_information/map/selectLocationMap.js
  27. 5 5
      src/views/old_mini/entry_information/selectLocation.vue
  28. 24 14
      src/views/old_mini/growth_report/components/CropAlertPanel.vue
  29. 93 11
      src/views/old_mini/recordDetails/map/mapManage.js
  30. 57 17
      src/views/old_mini/work_detail/components/agriMaterialTable.vue
  31. 576 306
      src/views/old_mini/work_detail/index.vue
  32. 67 41
      src/views/old_mini/work_execute/components/calendar.vue
  33. 55 37
      src/views/old_mini/work_execute/index.js
  34. 304 113
      src/views/old_mini/work_execute/index.vue

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


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


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


BIN
src/assets/img/farm/advice-farm.png


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


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


BIN
src/assets/img/farm/warning-farm.png


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


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


+ 156 - 94
src/components/allGarden.vue

@@ -6,7 +6,7 @@
             <div class="map-wrap">
                 <div class="map-container" ref="mapContainer"></div>
                 <div class="add-farm-btn" @click="handleAddFarm">
-                    <el-icon><Plus /></el-icon>
+                    <el-icon size="12"><Plus /></el-icon>
                     <span>{{ t("garden.addFarm") }}</span>
                 </div>
             </div>
@@ -17,7 +17,6 @@
                     class="filter-search"
                     :placeholder="t('weather.searchFarm')"
                     :prefix-icon="Search"
-                    style="width: 106px"
                     clearable
                 />
                 <div class="filter-select-wrap">
@@ -65,6 +64,7 @@
                                 <span v-if="item.categoryLabel" class="category-tag">{{ item.categoryLabel }}</span>
                             </div>
                             <div class="farm-address van-ellipsis">
+                                <el-icon><Location /></el-icon>
                                 {{ item.rawAddress || t("common.noAddress") }}
                             </div>
                         </div>
@@ -80,11 +80,16 @@
                             @click.stop="handleEnterFarm(item)"
                         >
                             {{ t("garden.enterFarm") }}
+                            <el-icon color="rgba(255, 255, 255, 0.6)"><ArrowRightBold /></el-icon>
                         </div>
                     </div>
 
                     <div class="phenology-box">
-                        {{ t("garden.currentPhenology") }}{{ item.phenologyText || t("garden.phenologyPlaceholder") }}
+                        {{ t("garden.currentPhenology") }}
+                        <span class="phenology-text">{{ item.phenologyText || t("garden.phenologyPlaceholder") }}</span>
+                        <div class="phenology-desc">
+                            待完善待完善待完善预计 11 月上旬第二次秋梢叶色转绿、叶片逐步老熟
+                        </div>
                     </div>
 
                     <div class="task-bar" v-if="item.taskName">
@@ -93,10 +98,10 @@
                             <span class="task-name van-ellipsis">{{ item.taskName || t("garden.taskNamePlaceholder") }}</span>
                             <span class="task-status">{{ item.taskStatus || t("garden.pendingExecute") }}</span>
                         </div>
-                        <!-- <div class="task-bar__forward" @click.stop="handleForward(item)">
-                            <Icon size="14" color="#2199F8" name="share" />
+                        <div class="task-bar__forward" @click.stop="handleForward(item)">
+                            <Icon size="14" color="#FFD489" name="share" />
                             <span>{{ t("garden.forward") }}</span>
-                        </div> -->
+                        </div>
                     </div>
                 </div>
 
@@ -107,7 +112,7 @@
         </div>
 
         <div class="invite-bar">
-            <div class="invite-btn" @click="handleInviteFarmer">
+            <div class="invite-btn secondary-btn" @click="handleInviteFarmer">
                 <img class="invite-btn-icon" src="@/assets/img/home/user-icon.png" alt="">
                 <span>{{ t("garden.inviteFarmer") }}</span>
             </div>
@@ -239,7 +244,8 @@ function refreshMapMarkers() {
 function ensureMap() {
     if (!mapContainer.value) return;
     if (!indexMap.kmap) {
-        indexMap.initMap(getDefaultMapPoint(), mapContainer.value, true);
+        // 农场列表跨省(如广东+湖北)时,默认 minZoom=6 在矮地图里 fit 后点全在视野外
+        indexMap.initMap(getDefaultMapPoint(), mapContainer.value, true, { minZoom: 3 });
     }
     refreshMapMarkers();
     nextTick(() => {
@@ -355,44 +361,49 @@ onActivated(() => {
     display: flex;
     flex-direction: column;
     height: 100vh;
-    background: #f5f5f5;
+    background: #141414;
     box-sizing: border-box;
 }
 
 .page-body {
+    background: url('@/assets/img/agricultural/page-bg.png') no-repeat top center;
+    background-size: 100% 250px;
     flex: 1;
     overflow: auto;
-    padding: 10px 12px 108px;
+    padding: 10px 12px 22px;
     box-sizing: border-box;
 }
 
 .map-wrap {
     position: relative;
-    border-radius: 8px;
+    border-radius: 10px;
     overflow: hidden;
-    background: #e8e8e8;
+    background: #1a191d;
+    border: 1px solid rgba(255, 255, 255, 0.06);
 
     .map-container {
         width: 100%;
         height: 162px;
-        clip-path: inset(0 round 8px);
+        clip-path: inset(0 round 10px);
     }
-    
+
     .add-farm-btn {
         position: absolute;
-        right: 10px;
-        bottom: 10px;
+        left: 8px;
+        top: 5px;
         z-index: 2;
         display: inline-flex;
         align-items: center;
         gap: 2px;
-        height: 28px;
-        padding: 0 10px;
-        border-radius: 14px;
-        background: #fff;
-        color: #2199f8;
+        height: 30px;
+        padding: 0 14px 0 10px;
+        border-radius: 24px;
+        background: rgba(7, 4, 0, 0.56);
+        border: 1px solid rgba(255, 212, 137, 0.5);
+        color: #FFD489;
         font-size: 12px;
-        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        box-sizing: border-box;
+        backdrop-filter: blur(4px);
     }
 }
 
@@ -401,7 +412,7 @@ onActivated(() => {
     align-items: center;
     justify-content: space-between;
     gap: 8px;
-    margin-top: 10px;
+    margin-top: 12px;
 
     .filter-select-wrap {
         display: flex;
@@ -410,43 +421,76 @@ onActivated(() => {
     }
 
     .filter-search {
-        // flex: 1;
         min-width: 0;
     }
 
     .filter-select {
-        width: 92px;
+        width: 94px;
         flex-shrink: 0;
     }
 
     :deep(.el-input__wrapper),
     :deep(.el-select__wrapper) {
-        min-height: 28px;
-        line-height: 28px;
-        height: 28px;
-        padding: 0 8px;
+        min-height: 36px;
+        line-height: 36px;
+        height: 36px;
+        padding: 0 10px;
         box-sizing: border-box;
-        border-radius: 4px;
-        box-shadow: 0 0 0 1px rgba(180, 180, 180, 0.4) inset;
+        border-radius: 6px;
+        background: rgba(255, 255, 255, 0.05);
+        box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08) inset !important;
+
+        &:hover {
+            box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2) inset !important;
+        }
+
+        &.is-focused {
+            box-shadow: 0 0 0 1px rgba(255, 212, 137, 0.45) inset !important;
+        }
+    }
+
+    :deep(.el-input__inner),
+    :deep(.el-select__selected-item),
+    :deep(.el-select__input) {
+        color: rgba(255, 255, 255, 0.9);
+        font-size: 12px;
+    }
+
+    :deep(.el-input__inner::placeholder),
+    :deep(.el-select__placeholder) {
+        color: rgba(255, 255, 255, 0.5);
+    }
+
+    :deep(.el-select__caret),
+    :deep(.el-input__prefix),
+    :deep(.el-input__suffix) {
+        color: rgba(255, 255, 255, 0.45);
+    }
+
+    :deep(.el-input__clear),
+    :deep(.el-select__clear) {
+        color: rgba(255, 255, 255, 0.45);
+        background: transparent;
     }
 }
 
 .farm-list {
-    margin-top: 10px;
+    margin-top: 12px;
 }
 
 .farm-card {
     margin-bottom: 10px;
-    padding: 10px 12px;
-    border-radius: 8px;
-    background: #fff;
+    padding: 12px;
+    border-radius: 12px;
+    background: linear-gradient(180deg, #1a191d 0%, #121216 100%);
+    border: 1px solid rgba(255, 255, 255, 0.08);
     box-sizing: border-box;
 
     &.is-current {
-        border: 1px solid #2199f8;
+        border-color: rgba(255, 212, 137, 0.3);
 
-        .farm-name {
-            color: #2199f8;
+        .phenology-box .phenology-text {
+            color: #ffd489;
         }
     }
 }
@@ -472,55 +516,71 @@ onActivated(() => {
 
 .farm-name {
     max-width: 180px;
-    font-size: 14px;
-    color: #1D2129;
-    font-weight: 500;
+    font-size: 16px;
+    color: #fff;
 }
 
 .category-tag {
     flex-shrink: 0;
-    height: 20px;
+    height: 18px;
     padding: 0 8px;
-    border-radius: 2px;
-    background: rgba(33, 153, 248, 0.1);
-    color: #2199f8;
+    border-radius: 4px;
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    background: rgba(255, 255, 255, 0.05);
+    color: rgba(255, 255, 255, 0.6);
     font-size: 12px;
-    line-height: 20px;
+    line-height: 18px;
+    box-sizing: border-box;
 }
 
 .farm-address {
-    margin-top: 2px;
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    margin-top: 4px;
     font-size: 12px;
-    color: rgba(32, 32, 32, 0.4);
+    color: rgba(255, 255, 255, 0.4);
 }
 
 .action-btn {
     flex-shrink: 0;
-    height: 28px;
-    padding: 0 10px;
-    border-radius: 4px;
+    height: 26px;
+    padding: 0 8px 0 11px;
+    border-radius: 22px;
     font-size: 12px;
-    line-height: 28px;
+    color: rgba(255, 255, 255, 0.85);
+    border: 1px solid rgba(255, 255, 255, 0.16);
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 2px;
 }
 
 .action-btn-current {
-    color: #fff;
-    background: #2199f8;
-}
-
-.action-btn-enter {
-    color: #fff;
-    background: #ff953d;
+    padding: 0 6px;
+    border-radius: 4px;
+    border-color: rgba(255, 212, 137, 0.16);
+    color: #FFD489;
+    background: rgba(255, 212, 137, 0.1);
 }
 
 .phenology-box {
     margin-top: 8px;
-    padding: 6px 8px;
-    border-radius: 4px;
-    background: rgba(160, 160, 160, 0.1);
+    padding: 9px 10px;
+    border-radius: 8px;
+    background: rgba(255, 255, 255, 0.05);
     font-size: 12px;
-    color: #767676;
+    color: rgba(255, 255, 255, 0.7);
     line-height: 18px;
+    .phenology-desc {
+        color: rgba(255, 255, 255, 0.7);
+    }
+    .phenology-text {
+        padding-left: 2px;
+        color: #FFFFFF;
+        font-size: 14px;
+    }
 }
 
 .task-bar {
@@ -528,16 +588,16 @@ onActivated(() => {
     align-items: center;
     justify-content: space-between;
     gap: 8px;
-    margin-top: 10px;
-    padding: 8px;
-    border-radius: 6px;
-    background: rgba(33, 153, 248, 0.1);
+    margin-top: 8px;
+    padding: 8px 10px;
+    border-radius: 10px;
+    background: rgba(255, 255, 255, 0.05);
 }
 
 .task-bar__left {
     display: flex;
     align-items: center;
-    gap: 6px;
+    gap: 8px;
     min-width: 0;
     flex: 1;
 }
@@ -552,18 +612,19 @@ onActivated(() => {
     max-width: 120px;
     font-weight: 500;
     font-size: 14px;
-    color: #000;
+    color: rgba(255, 255, 255, 0.9);
 }
 
 .task-status {
     flex-shrink: 0;
-    height: 20px;
+    height: 18px;
     padding: 0 6px;
-    border-radius: 2px;
-    background: #2199f8;
-    color: #fff;
+    border-radius: 4px;
+    background: rgba(255, 212, 137, 0.1);
+    color: #FFD489;
+    border: 1px solid rgba(255, 255, 255, 0.12);
     font-size: 12px;
-    line-height: 20px;
+    line-height: 18px;
 }
 
 .task-bar__forward {
@@ -571,49 +632,50 @@ onActivated(() => {
     align-items: center;
     gap: 4px;
     flex-shrink: 0;
-    color: #2199f8;
+    color: #FFD489;
     font-size: 14px;
 }
 
 .empty-tip {
     padding: 40px 0;
     text-align: center;
-    color: #86909c;
+    color: rgba(255, 255, 255, 0.4);
     font-size: 13px;
 }
 
 .invite-bar {
-    position: fixed;
-    left: 0;
-    right: 0;
-    bottom: 60px;
-    z-index: 20;
     display: flex;
     justify-content: center;
-    gap: 16px;
-    pointer-events: none;
+    gap: 12px;
+    padding: 20px 10px calc(30px + env(safe-area-inset-bottom));
+    width: 100%;
+    box-sizing: border-box;
 }
 
 .invite-btn {
-    pointer-events: auto;
     display: inline-flex;
     align-items: center;
     justify-content: center;
     box-sizing: border-box;
     gap: 6px;
-    min-width: 110px;
-    height: 40px;
-    padding: 0 16px;
-    border-radius: 20px;
-    background: linear-gradient(180deg, #72C1FF 0%, #2199F8 100%);
-    color: #fff;
-    font-size: 14px;
-    // box-shadow: 0 4px 12px rgba(33, 153, 248, 0.35);
-    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
+    flex: 1;
+    height: 44px;
+    border-radius: 28px;
+    background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+    color: #1A1408;
+    font-size: 16px;
+
+    &.secondary-btn {
+        background: #141416;
+        border: 1px solid rgba(255, 212, 137, 0.4);
+        color: #FFD489;
+    }
+
     .invite-btn-icon {
         width: 16px;
         height: 16px;
-        filter: brightness(0) invert(1);
+        filter: brightness(0) saturate(100%);
+        opacity: 0.85;
     }
 }
 </style>

+ 7 - 6
src/components/customHeader.vue

@@ -20,17 +20,17 @@ const props = defineProps({
     },
     bgColor:{
         type:String,
-        defalut:'#fff'
+        defalut:'#151516'
     },
     /** 返回/关闭图标颜色 */
     iconColor: {
         type: String,
-        default: "rgba(0, 0, 0, 0.9)",
+        default: "#fff",
     },
     /** 标题文字颜色 */
     titleColor: {
         type: String,
-        default: "inherit",
+        default: "#FFFFFF",
     },
     isGoBack:{
         type:Boolean,
@@ -65,7 +65,7 @@ const close = () => {
 
 <style lang="scss" scoped>
 .custom-header{
-    background: #fff;
+    background: #151516;
     width: 100%;
     height: 40px;
     font-size: 17px;
@@ -77,13 +77,14 @@ const close = () => {
     .back,.close{
         display: flex;
         align-items: center;
-        color: #656565;
+        color: #fff;
         position: absolute;
         left: 12px;
         z-index: 10;
     }
     .title{
-        font-weight: bold;
+        font-weight: 600;
+        font-size: 17px;
     }
 }
 </style>

+ 53 - 3
src/components/pageComponents/locationSearch.vue

@@ -98,8 +98,10 @@ const handleClear = () => {
 
 <style lang="scss">
 .location-search {
+    width: 100%;
+
     .el-select__wrapper {
-        background: rgba(0, 0, 0, 0.3);
+        background: rgba(0, 0, 0, 0.7);
         box-shadow: none;
         border-radius: 25px;
         border: 1px solid rgba(255, 255, 255, 0.4);
@@ -108,19 +110,67 @@ const handleClear = () => {
         color: rgba(255, 255, 255, 0.7);
     }
     .el-select__input {
-        color: rgba(255, 255, 255, 0.7);
+        color: rgba(255, 255, 255, 0.9);
+    }
+
+    .el-select__prefix,
+    .el-select__caret,
+    .el-select__clear {
+        color: rgba(255, 255, 255, 0.45);
     }
 }
+
 .location-search-popper {
+    background: #1c1c1c !important;
+    border-color: rgba(255, 255, 255, 0.12) !important;
+    border-radius: 10px !important;
+
+    .el-select-dropdown__wrap {
+        max-height: 280px;
+    }
+
     .el-select-dropdown__list {
         max-width: 96vw;
+        padding: 4px 0;
         overflow-x: auto;
     }
 
+    .el-select-dropdown__item {
+        display: flex;
+        align-items: center;
+        color: rgba(255, 255, 255, 0.85);
+        height: auto;
+        min-height: 40px;
+        line-height: 20px;
+        padding: 8px 12px;
+
+        &.is-hovering,
+        &:hover {
+            background: rgba(232, 197, 106, 0.12);
+        }
+
+        &.is-selected {
+            color: #e8c56a;
+            font-weight: 500;
+            background: rgba(232, 197, 106, 0.08);
+        }
+    }
+
     .sub-title {
         padding-left: 6px;
         font-size: 12px;
-        color: #ccc;
+        color: rgba(255, 255, 255, 0.4);
+        flex-shrink: 0;
+    }
+
+    .el-select-dropdown__empty,
+    .el-select-dropdown__loading {
+        color: rgba(255, 255, 255, 0.4);
+    }
+
+    .el-popper__arrow::before {
+        background: #1c1c1c !important;
+        border-color: rgba(255, 255, 255, 0.12) !important;
     }
 }
 </style>

+ 21 - 5
src/components/popup/restorePlantingPopup.vue

@@ -106,7 +106,7 @@
         </div>
     </popup>
 
-    <tip-popup class="restore-planting-success-popup" v-model:show="showSuccessPopup" :zIndex="20000" type="executeSuccess" text="物候期已确认" text2="正在生成专属农事方案" hideBtn>
+    <tip-popup class="restore-planting-success-popup" v-model:show="showSuccessPopup" :zIndex="20000" type="executeSuccess" text="您已提交成功" text2="诊断报告正在生成,请稍后" hideBtn>
             <template #footer>
                 <div class="bottom-btn-container">
                     <div class="bottom-btn primary-btn" @click="showSuccessPopup=false">完成</div>
@@ -717,16 +717,16 @@ linear-gradient(180deg, rgba(255, 212, 137, 0.36) 0%, rgba(255, 212, 137, 0) 12%
         padding: 0 25px;
         border-radius: 4px;
         font-size: 16px;
-        height: 40px;
-        line-height: 40px;
+        height: 44px;
+        line-height: 44px;
         box-sizing: border-box;
         font-weight: 500;
         text-align: center;
 
         &.primary-btn {
             flex: 1;
-            background: #2199f8;
-            color: #fff;
+            background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+            color: #1A1408;
         }
     }
 }
@@ -749,6 +749,22 @@ linear-gradient(180deg, rgba(255, 212, 137, 0.36) 0%, rgba(255, 212, 137, 0) 12%
 .restore-planting-success-popup {
     &.tip-popup {
         z-index: 20000 !important;
+        .tip-icon {
+            margin-bottom: 26px;
+        }
+        .tip-popup-content {
+            padding: 20px 25px 50px;
+        }
+        .tip-text.execute-success-text {
+            margin-bottom: 25px;
+        }
+        .tip-text.execute-success-text .execute-success-title {
+            font-size: 20px;
+        }
+        .tip-text.execute-success-text .execute-success-subtitle {
+            font-size: 14px;
+            color: rgba(255, 255, 255, 0.6);
+        }
     }
 }
 </style>

+ 2 - 2
src/i18n/messages.js

@@ -53,7 +53,7 @@ export default {
             enterFarm: "进入农场",
             enterFarmSuccess: "已切换农场",
             addFarm: "新增农场",
-            currentPhenology: "当前物候:",
+            currentPhenology: "当前物候",
             phenologyPlaceholder: "暂无物候信息",
             pendingExecute: "待执行",
             taskNamePlaceholder: "农事名称",
@@ -366,7 +366,7 @@ export default {
             fillSurvey: "填写调查问卷",
             fillSurveyDesc: "更方便为您分配农场任务",
             fillNow: "立即填写",
-            surveyEntryTitle: "录入信息",
+            surveyEntryTitle: "新建农场",
             surveyEntryHint: "请勾选您希望由专业服务团队",
             surveyEntryHint2: "帮您服务的农事类别",
             surveySearchPlaceholder: "请输入农事类别",

+ 277 - 42
src/views/old_mini/agri_file/components/addZoneInfoPopup.vue

@@ -8,6 +8,8 @@
         teleport="body"
     >
         <div class="add-zone-info-popup__content">
+            <div class="add-zone-info-popup__title">填写分区信息</div>
+
             <div class="form-block">
                 <div class="form-block__title">{{ t("agriFile.plantCrop") }}</div>
                 <div class="form-block__row">
@@ -17,6 +19,7 @@
                         :placeholder="t('agriFile.cropCategory')"
                         clearable
                         filterable
+                        popper-class="add-zone-info-select-popper"
                         :loading="categoryLoading"
                         @change="handleCategoryChange"
                     >
@@ -25,6 +28,7 @@
                             :key="item.id"
                             :label="item.name"
                             :value="item.id"
+                            :class="{ 'is-disabled': !isCropAllowed(item) }"
                         />
                     </el-select>
                     <el-select
@@ -33,6 +37,7 @@
                         :placeholder="t('agriFile.cropVariety')"
                         clearable
                         filterable
+                        popper-class="add-zone-info-select-popper"
                         :loading="varietyLoading"
                         :disabled="!form.categoryId"
                         @change="handleVarietyChange"
@@ -48,13 +53,25 @@
             </div>
 
             <div class="form-block">
+                <div class="form-block__title">{{ t("agriFile.regionName") }}</div>
+                <div class="form-input-wrap">
+                    <el-input
+                        v-model="form.zoneName"
+                        :placeholder="t('agriFile.regionNamePlaceholder')"
+                        maxlength="12"
+                    />
+                    <span class="form-input-wrap__count">{{ (form.zoneName || "").length }}/12</span>
+                </div>
+            </div>
+
+            <div class="form-block">
                 <div class="form-block__title">当前物候期</div>
                 <el-select
                     v-model="form.phenologyId"
                     class="form-select"
-                    placeholder="选择物候期"
+                    placeholder="选生育期"
                     placement="bottom-end"
-                    popper-class="variety-select-popper"
+                    popper-class="add-zone-info-select-popper"
                     :disabled="!form.categoryId"
                     clearable
                 >
@@ -66,6 +83,7 @@
                     />
                 </el-select>
             </div>
+
             <div class="form-block">
                 <div class="form-block__title">{{ startingPointQuestion }}</div>
                 <div class="form-date-wrap">
@@ -79,20 +97,12 @@
                         :editable="false"
                         :clearable="false"
                         :prefix-icon="null"
+                        popper-class="add-zone-info-date-popper"
                         style="width: 100%"
                     />
-                    <el-icon class="form-date-wrap__icon"><Clock /></el-icon>
                 </div>
             </div>
 
-            <div class="form-block">
-                <div class="form-block__title">{{ t("agriFile.regionName") }}</div>
-                <el-input
-                    v-model="form.zoneName"
-                    :placeholder="t('agriFile.regionNamePlaceholder')"
-                />
-            </div>
-
             <div
                 class="add-zone-info-popup__confirm"
                 :class="{ disabled: submitting }"
@@ -108,7 +118,7 @@
 import { computed, reactive, ref, watch } from "vue";
 import { Popup } from "vant";
 import { ElMessage } from "element-plus";
-import { Clock } from "@element-plus/icons-vue";
+import { ArrowDown, Clock } from "@element-plus/icons-vue";
 import { useI18n } from "@/i18n";
 
 const { t } = useI18n();
@@ -355,15 +365,22 @@ async function fetchInitialInteractOptionsWithoutZone() {
 
     await ensureCropMeta(form.categoryId);
 
-    const farm = getSelectedFarm();
+    // 物候期按「当前选中品类」请求,不能用农场主栽 crop_code(可能为空或与所选不一致)
     const params = {
-        crop_big_type: farm.crop_big_type || farm.crop_group || "",
-        crop_code: farm.category_code || "",
+        crop_big_type: cropMeta.crop_group || category.firstCrop || "",
+        crop_code: cropMeta.crop_code || category.code || "",
         crop_maturing: "P0",
         location: getDefaultPoint(),
         date: form.plantDate || new Date().toISOString().split("T")[0],
     };
-    if (!params.crop_code) return;
+    if (!params.crop_code) {
+        console.warn("[addZoneInfoPopup] skip interact: missing crop_code", {
+            categoryId: form.categoryId,
+            cropMeta: { ...cropMeta },
+            category,
+        });
+        return;
+    }
 
     const key = `${params.crop_code}|${params.location}|${params.date}`;
     if (interactFetchPromise && interactFetchKey === key) {
@@ -391,6 +408,9 @@ async function fetchInitialInteractOptionsWithoutZone() {
     return interactFetchPromise;
 }
 
+/** is_use=1 才可选;未开放仍展示,点选时提示 */
+const isCropAllowed = (item) => String(item?.is_use ?? "0") === "1";
+
 /** getCrops 可能返回对象(果树/大田分组)或数组,统一展平为下拉选项 */
 const flattenCropsToOptions = (data) => {
     const map = new Map();
@@ -403,6 +423,7 @@ const flattenCropsToOptions = (data) => {
             name,
             code: crop.crop_code || "",
             firstCrop: firstCrop || crop.crop_group || "",
+            is_use: crop.is_use ?? "0",
         });
     };
 
@@ -530,11 +551,25 @@ const handleCategoryChange = (cropId) => {
     form.varietyId = "";
     form.phenologyId = "";
     phenologyOptions.value = [];
+    varietyOptions.value = [];
     startingPointQuestion.value = t("agriFile.plantStartPoint");
-    fetchVarietyOptions(cropId);
+
+    if (cropId == null || cropId === "") {
+        applyAutoZoneName("");
+        return;
+    }
+
     const category = categoryOptions.value.find(
         (item) => String(item.id) === String(cropId)
     );
+    if (!isCropAllowed(category)) {
+        form.categoryId = "";
+        ElMessage.warning("该品种暂未开放");
+        applyAutoZoneName("");
+        return;
+    }
+
+    fetchVarietyOptions(cropId);
     applyAutoZoneName(category?.name || "");
 };
 
@@ -549,6 +584,13 @@ const handleConfirm = async () => {
         ElMessage.warning(t("agriFile.pleaseSelectCategory"));
         return;
     }
+    const selectedCategory = categoryOptions.value.find(
+        (item) => String(item.id) === String(form.categoryId)
+    );
+    if (!isCropAllowed(selectedCategory)) {
+        ElMessage.warning("该品种暂未开放");
+        return;
+    }
     if (!form.varietyId) {
         ElMessage.warning(t("agriFile.pleaseSelectVariety"));
         return;
@@ -642,19 +684,42 @@ const handleConfirm = async () => {
 <style scoped lang="scss">
 .add-zone-info-popup {
     width: 330px;
+    background: linear-gradient(0deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.42)),
+linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 12%);
+    padding: 1px;
+    border-radius: 16px;
+
+    overflow: visible;
 
     :deep(.van-popup__close-icon) {
-        color: #333;
-        font-size: 18px;
+        top: 16px;
+        right: 14px;
+        width: 24px;
+        height: 24px;
+        border-radius: 50%;
+        background: rgba(255, 255, 255, 0.12);
+        color: rgba(255, 255, 255, 0.85);
+        font-size: 14px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
     }
 
     &__content {
-        padding: 24px 16px 20px;
-        background: linear-gradient(360deg, #ffffff 74.2%, #d1ebff 100%);
+        padding: 20px 16px 18px;
+        background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
         border-radius: 16px;
         box-sizing: border-box;
     }
 
+    &__title {
+        margin-bottom: 20px;
+        padding-right: 28px;
+        font-size: 17px;
+        line-height: 24px;
+        color: #fff;
+    }
+
     .form-block {
         & + .form-block {
             margin-top: 16px;
@@ -662,9 +727,9 @@ const handleConfirm = async () => {
 
         &__title {
             margin-bottom: 10px;
-            font-size: 16px;
-            font-weight: 500;
-            color: #000;
+            font-size: 14px;
+            color: rgba(255, 255, 255, 0.85);
+            line-height: 20px;
         }
 
         &__row {
@@ -679,51 +744,221 @@ const handleConfirm = async () => {
         width: 100%;
     }
 
+    .form-input-wrap {
+        position: relative;
+
+        &__count {
+            position: absolute;
+            right: 12px;
+            bottom: 10px;
+            z-index: 1;
+            font-size: 12px;
+            line-height: 16px;
+            color: rgba(255, 255, 255, 0.35);
+            pointer-events: none;
+        }
+
+        :deep(.el-input__wrapper) {
+            padding-right: 48px;
+        }
+    }
+
     :deep(.el-select__wrapper),
     :deep(.el-input__wrapper) {
-        min-height: 40px;
-        border-radius: 4px;
-        box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset;
-        background: #fff;
+        min-height: 41px;
+        border-radius: 10px;
+        box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12) inset !important;
+        background: rgba(255, 255, 255, 0.04);
+        padding: 0 12px;
+
+        &:hover {
+            box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2) inset !important;
+        }
+
+        &.is-focused {
+            box-shadow: 0 0 0 1px rgba(255, 212, 137, 0.45) inset !important;
+        }
+
+        &.is-disabled {
+            color: rgba(255, 255, 255, 0.5);
+            background: rgba(255, 255, 255, 0.02);
+            box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06) inset !important;
+        }
     }
 
-    :deep(.el-date-editor.el-input) {
-        width: 100%;
+    :deep(.el-select__placeholder),
+    :deep(.el-input__inner::placeholder) {
+        color: rgba(255, 255, 255, 0.35);
+    }
+
+    :deep(.el-select__selected-item),
+    :deep(.el-select__placeholder.is-transparent),
+    :deep(.el-input__inner) {
+        color: rgba(255, 255, 255, 0.9);
+        font-size: 14px;
     }
 
-    :deep(.el-date-editor .el-input__wrapper) {
-        padding-left: 12px;
-        padding-right: 36px;
+    :deep(.el-select__placeholder.is-transparent) {
+        color: rgba(255, 255, 255, 0.5);
+        font-size: 14px;
+    }
+
+    :deep(.el-select__input) {
+        color: rgba(255, 255, 255, 0.9);
+    }
+
+    :deep(.el-select__caret),
+    :deep(.el-select__icon) {
+        color: rgba(255, 255, 255, 0.45);
+    }
+
+    :deep(.el-select__clear),
+    :deep(.el-input__clear) {
+        color: rgba(255, 255, 255, 0.35);
+        background: transparent;
+    }
+
+    :deep(.el-date-editor.el-input) {
+        width: 100%;
+        height: 41px;
     }
 
     .form-date-wrap {
         position: relative;
 
-        &__icon {
+        &__icon,
+        &__arrow {
             position: absolute;
-            right: 12px;
             top: 50%;
             transform: translateY(-50%);
-            color: rgba(0, 0, 0, 0.35);
+            color: rgba(255, 255, 255, 0.45);
             font-size: 16px;
             pointer-events: none;
             z-index: 1;
         }
+
+        &__icon {
+            left: 12px;
+        }
+
+        &__arrow {
+            right: 12px;
+            font-size: 14px;
+        }
     }
 
     &__confirm {
-        margin-top: 24px;
-        padding: 10px;
-        border-radius: 25px;
-        background: #2199f8;
-        color: #fff;
+        margin-top: 20px;
+        height: 44px;
+        line-height: 44px;
+        border-radius: 8px;
+        background: linear-gradient(135deg, #FFEBC9 0%, #FFCA6D 100%);
+        color: #1A1408;
         font-size: 16px;
         text-align: center;
+        box-sizing: border-box;
 
         &.disabled {
-            opacity: 0.6;
+            opacity: 0.55;
             pointer-events: none;
         }
     }
 }
 </style>
+
+<!-- 下拉/日期面板 teleport 到 body,需非 scoped -->
+<style lang="scss">
+.add-zone-info-select-popper {
+    background: #1c1c1c !important;
+    border-color: rgba(255, 255, 255, 0.12) !important;
+    // border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    border-radius: 10px !important;
+
+    .el-select-dropdown__wrap {
+        max-height: 220px;
+    }
+
+    .el-select-dropdown__item {
+        color: rgba(255, 255, 255, 0.85);
+        height: 40px;
+        line-height: 40px;
+
+        &.is-hovering,
+        &:hover {
+            background: rgba(232, 197, 106, 0.12);
+        }
+
+        &.is-selected {
+            color: #e8c56a;
+            font-weight: 500;
+            background: rgba(232, 197, 106, 0.08);
+        }
+
+        &.is-disabled {
+            color: rgba(255, 255, 255, 0.5) !important;
+            cursor: not-allowed;
+        }
+    }
+
+    .el-select-dropdown__empty {
+        color: rgba(255, 255, 255, 0.35);
+    }
+
+    .el-select-dropdown__loading {
+        color: rgba(255, 255, 255, 0.45);
+    }
+
+    .el-popper__arrow::before {
+        background: #1c1c1c !important;
+        border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    }
+}
+
+.add-zone-info-date-popper {
+    background: #1c1c1c !important;
+    border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    border-radius: 10px !important;
+
+    .el-picker-panel,
+    .el-date-picker,
+    .el-picker-panel__body-wrapper,
+    .el-date-table th,
+    .el-picker-panel__icon-btn {
+        background: #1c1c1c;
+        color: rgba(255, 255, 255, 0.85);
+        border-color: rgba(255, 255, 255, 0.08);
+    }
+
+    .el-date-picker__header-label,
+    .el-picker-panel__icon-btn {
+        color: rgba(255, 255, 255, 0.85);
+
+        &:hover {
+            color: #e8c56a;
+        }
+    }
+
+    .el-date-table td .el-date-table-cell__text {
+        color: rgba(255, 255, 255, 0.85);
+    }
+
+    .el-date-table td.available:hover .el-date-table-cell__text,
+    .el-date-table td.current:not(.disabled) .el-date-table-cell__text {
+        background: rgba(232, 197, 106, 0.9);
+        color: #1a1208;
+    }
+
+    .el-date-table td.today .el-date-table-cell__text {
+        color: #e8c56a;
+    }
+
+    .el-date-table td.disabled .el-date-table-cell__text {
+        color: rgba(255, 255, 255, 0.25);
+    }
+
+    .el-popper__arrow::before {
+        background: #1c1c1c !important;
+        border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    }
+}
+</style>

+ 3 - 2
src/views/old_mini/agri_file/components/albumPanel.vue

@@ -558,6 +558,7 @@ watch(
                 position: relative;
                 width: 100%;
                 height: 100%;
+                border-radius: 10px;
                 // aspect-ratio: 1;
 
                 img:not(.album-card__empty-icon) {
@@ -605,9 +606,9 @@ watch(
 
             &__cover-mask-bg {
                 position: absolute;
-                bottom: 0;
+                bottom: -1px;
                 left: 0;
-                width: 100%;
+                right: -1px;
                 height: 90px;
                 background: linear-gradient(360deg, #000000 0%, rgba(0, 0, 0, 0) 91.97%);
                 border-radius: 0 0 10px 10px;

+ 440 - 277
src/views/old_mini/agri_file/pages/abnormalTrack.vue

@@ -7,87 +7,135 @@
             :showClose="false"
             @goback="handleBack"
         />
-        <div class="abnormal-track-body" v-loading="loading">
+        <div class="abnormal-track-body" v-loading="loading" element-loading-background="rgba(15, 15, 15, 0.55)">
             <div class="abnormal-hero">
-                <div class="abnormal-hero__top">
-                    <div class="abnormal-hero__main">
-                        <div class="abnormal-hero__title-row">
-                            <span class="abnormal-hero__title">{{ theme }}</span>
-                            <span class="abnormal-hero__tag abnormal-hero__tag--level">
-                                <el-icon>
-                                    <BellFilled />
-                                </el-icon>
-                                {{ level }}
-                            </span>
-                            <span class="abnormal-hero__tag abnormal-hero__tag--zone">{{ zoneName }}</span>
-                        </div>
-                        <div class="abnormal-head-bottom">
-                          <div class="abnormal-hero__reason">{{ reason }}</div>
+                <div class="abnormal-hero__share" @click="handleShare">
+                    <Icon name="share" color="rgba(255,255,255,0.85)" />
+                    <span>{{ t("agriFile.forward") }}</span>
+                </div>
+                <div class="abnormal-mask"></div>
+                <div class="abnormal-top">
+                    <div class="abnormal-hero__tags">
+                        <span class="abnormal-hero__tag abnormal-hero__tag--level">
+                            <el-icon><BellFilled /></el-icon>
+                            {{ level }}
+                        </span>
+                        <span class="abnormal-hero__tag abnormal-hero__tag--zone">{{ zoneName }}</span>
+                    </div>
+                    <div class="abnormal-hero__title">{{ theme }}</div>
+                    <div class="abnormal-hero__reason">{{ reason }}</div>
+                </div>
+            </div>
 
-                          <div class="abnormal-hero__share" @click="handleShare">
-                                <Icon name="share" />
-                                <span>{{ t("agriFile.forward") }}</span>
+            <div class="abnormal-main">
+                <!-- 农情研判 -->
+                <div class="box-wrap stage-card analysis-card">
+                    <div class="work-info">
+                        <div class="section-title">
+                            <span class="title-bar"></span>
+                            {{ t("agriFile.agriAssessment") }}
+                        </div>
+                        <div class="analysis-card__desc">{{ assessmentDesc }}</div>
+                        <div class="analysis-card__stats">
+                            <div
+                                v-for="(item, index) in analysisStats"
+                                :key="index"
+                                class="analysis-card__stat"
+                            >
+                                <div
+                                    class="analysis-card__stat-value"
+                                    :class="{ highlight: item.highlight }"
+                                >
+                                    <template v-if="item.unit">
+                                        <span class="num">{{ item.value }}</span>
+                                        <span class="unit">{{ item.unit }}</span>
+                                    </template>
+                                    <template v-else>{{ item.value }}</template>
+                                </div>
+                                <div class="analysis-card__stat-label">{{ item.label }}</div>
                             </div>
                         </div>
                     </div>
                 </div>
-            </div>
-            <div class="abnormal-track-content">
+
+                <!-- 异常排查互动 -->
+                <div class="interact-section-head">
+                    <span class="interact-section-head__text">
+                        异常排查互动 · 共 {{ interactList.length }} 项
+                    </span>
+                    <span class="interact-section-head__line"></span>
+                </div>
+
                 <div
-                    class="situation-card__head"
                     v-for="(item, index) in interactList"
                     :key="item.fw_code || index"
+                    class="abnormal-item"
                 >
-                    <div class="situation-card__name">
-                        <div class="situation-card__title">
-                            <img
-                                class="situation-card__icon"
-                                src="@/assets/img/agricultural/gaojin.png"
-                                alt=""
-                            />
-                            <span>{{ item.abnormal_type }}</span>
-                        </div>
-                        <div class="situation-card__upload" @click="handleUploadClick">
-                            + {{ t("agriFile.uploadPhoto") }}
-                        </div>
+                    <div class="abnormal-item__title">
+                        <img
+                            class="abnormal-item__icon"
+                            src="@/assets/img/agricultural/gaojin.png"
+                            alt=""
+                        />
+                        <span>{{ item.abnormal_type || t("agriFile.rootAbnormal") }}</span>
                     </div>
-                    <!-- 互动问题 -->
-                    <div class="interact-qa">
-                        <div class="interact-qa__head">
-                            <span class="interact-qa__tag">{{ t("agriFile.interactQuestion") }}</span>
-                            <span class="interact-qa__question">{{ item.interact_problem }}</span>
-                        </div>
-                        <div class="interact-qa__actions">
-                            <div
-                                v-for="(opt, optIndex) in item.options"
-                                :key="`${item.fw_code || index}-${optIndex}`"
-                                class="interact-qa__option"
-                                :class="{ selected: selectedMap[item.fw_code || index] === optIndex }"
-                                @click="selectedMap[item.fw_code || index] = optIndex"
-                            >
-                                {{ opt }}
+
+                    <div class="box-wrap stage-card interact-card">
+                        <div class="work-info">
+                            <div class="interact-card__head">
+                                <div class="section-title">
+                                    <span class="title-bar"></span>
+                                    {{ t("agriFile.interactQuestion") }}
+                                </div>
+                                <div
+                                    class="interact-card__submit"
+                                    :class="{ disabled: selectedMap[item.fw_code || index] == null }"
+                                    @click="handleSubmit(item, index)"
+                                >
+                                    确认提交
+                                </div>
                             </div>
-                            <div
-                                class="interact-qa__submit"
-                                :class="{ disabled: selectedMap[item.fw_code || index] == null }"
-                                @click="handleSubmit(item, index)"
-                            >
-                                提交信息
+                            <div class="interact-card__qa">
+                                <div class="interact-card__question">{{ item.interact_problem }}</div>
+                                <div class="interact-card__options">
+                                    <div
+                                        v-for="(opt, optIndex) in item.options"
+                                        :key="`${item.fw_code || index}-${optIndex}`"
+                                        class="interact-option"
+                                        :class="{
+                                            selected: selectedMap[item.fw_code || index] === optIndex,
+                                        }"
+                                        @click="selectedMap[item.fw_code || index] = optIndex"
+                                    >
+                                        <span class="interact-option__text">{{ opt }}</span>
+                                    </div>
+                                </div>
                             </div>
                         </div>
                     </div>
-                    <div class="situation-card__assess">
-                        <div class="situation-card__assess-title">{{ t("agriFile.agriAssessment") }}</div>
-                        <div class="situation-card__assess-desc">{{ item.interaction_reason }}</div>
+                </div>
+
+                <!-- 巡园向导 -->
+                <div class="box-wrap stage-card guide-card">
+                    <div class="work-info">
+                        <div class="section-title">
+                            <img class="guide-icon" src="@/assets/img/farm/guide.png" alt="" />
+                            {{ t("agriFile.patrolGuide") }}
+                        </div>
+                        <photo-provider :photo-closable="true">
+                            <photo-consumer :src="guideImage">
+                                <img class="guide-card__img" :src="guideImage" alt="" />
+                            </photo-consumer>
+                        </photo-provider>
                     </div>
-                    <div class="situation-card__guide">
-                        <div class="guide-title">{{ t("agriFile.patrolGuide") }}</div>
-                        <div class="situation-card__guide-box">
-                            <photo-provider :photo-closable="true">
-                                <photo-consumer :src="resolveGuideImage(item.pic_url)">
-                                    <img :src="resolveGuideImage(item.pic_url)" alt="" />
-                                </photo-consumer>
-                            </photo-provider>
+
+                    <div class="guide-card__upload-wrap">
+                        <div class="guide-card__upload" @click="handleUploadClick">
+                            <img
+                                class="guide-card__upload-icon"
+                                src="@/assets/img/common/camera.png"
+                                alt=""
+                            />
                         </div>
                     </div>
                 </div>
@@ -129,6 +177,13 @@ const router = useRouter();
 // ---------- 常量 ----------
 const DEFAULT_GUIDE_IMAGE = require("@/assets/img/agricultural/patrol-guide.png");
 
+/** 农情研判底部指标:接口未接好前写死 */
+const analysisStats = [
+    { value: "126", unit: "mm", label: "累计降雨", highlight: false },
+    { value: "92", unit: "%", label: "土壤含水率", highlight: false },
+    { value: "52", unit: "h", label: "根部积水", highlight: true },
+];
+
 // ---------- 页面状态 ----------
 const loading = ref(false);
 const submitting = ref(false);
@@ -159,6 +214,11 @@ const theme = computed(
 const reason = computed(
     () => firstInteract.value?.interaction_theme || t("agriFile.interactReason")
 );
+/** 农情研判说明 */
+const assessmentDesc = computed(
+    () => firstInteract.value?.interaction_reason || t("agriFile.agriAssessmentDesc")
+);
+const guideImage = computed(() => resolveGuideImage(firstInteract.value?.pic_url));
 /** stress_info.level */
 const level = computed(() => {
     const lv = stressInfo.value?.level;
@@ -338,20 +398,6 @@ const handleUploadClick = () => {
         return;
     }
     showUploadPopup.value = true;
-    // 原异常逻辑:勾画后进病虫害选地块 / 直接进 pest_interact
-    // if (needDraw.value) {
-    //     router.push({
-    //         path: "/add_zone",
-    //         query: {
-    //             type: "farm",
-    //             targetUrl: "/pest_interact",
-    //             isAbnormal: "true",
-    //             ...(zoneId.value ? { trackId: zoneId.value } : {}),
-    //         },
-    //     });
-    //     return;
-    // }
-    // router.push({ path: "/pest_interact" });
 };
 
 const handleUploadConfirm = async ({ images, date } = {}) => {
@@ -441,281 +487,398 @@ onActivated(() => {
     width: 100%;
     height: 100vh;
     overflow: hidden;
-    background: #f5f5f5;
+    background: #0b0b0b;
+    color: #fff;
 
     .abnormal-track-body {
         flex: 1;
         overflow-y: auto;
         -webkit-overflow-scrolling: touch;
+        padding-bottom: 100px;
+        box-sizing: border-box;
+    }
 
-        .abnormal-hero {
-            padding: 18px 10px 38px;
-            background: #2199f8;
-            color: #fff;
-
-            &__top {
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-            }
+    .abnormal-hero {
+        position: relative;
+        padding: 86px 70px 10px 12px;
+        box-sizing: border-box;
+        z-index: 2;
+        min-height: 250px;
+        background:
+            linear-gradient(
+                180deg,
+                rgba(11, 11, 11, 0) 8.51%,
+                rgba(11, 11, 11, 0.416) 42.95%,
+                rgba(11, 11, 11, 0.468) 56.49%,
+                rgba(11, 11, 11, 0.52) 80.47%
+            ),
+            url("@/assets/img/agricultural/detail-bg.png") right bottom / cover no-repeat;
+
+        .abnormal-mask {
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            width: 100%;
+            height: 107px;
+            background: linear-gradient(
+                180deg,
+                rgba(11, 11, 11, 0) 8.47%,
+                rgba(11, 11, 11, 0.8) 26.93%,
+                rgba(11, 11, 11, 0.9) 37.74%,
+                #0b0b0b 56.89%
+            );
+        }
 
-            &__title-row {
-                display: flex;
-                align-items: center;
-                gap: 10px;
-            }
+        .abnormal-top {
+            position: relative;
+            z-index: 2;
+        }
 
-            &__title {
-                font-size: 22px;
-                font-family: "PangMenZhengDao";
-                margin-top: -5px;
-            }
+        &__share {
+            position: absolute;
+            top: 16px;
+            right: 16px;
+            z-index: 2;
+            display: inline-flex;
+            align-items: center;
+            gap: 4px;
+            height: 28px;
+            padding: 0 12px;
+            border-radius: 14px;
+            background: rgba(0, 0, 0, 0.45);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+            color: rgba(255, 255, 255, 0.85);
+            font-size: 12px;
+        }
 
-            .abnormal-head-bottom {
-                margin-top: 10px;
-                display: flex;
-                gap: 6px;
-                align-items: center;
-                justify-content: space-between;
-            }
+        &__tags {
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 8px;
+            margin-bottom: 12px;
+            padding-right: 88px;
+        }
 
-            &__reason {
-                padding: 4px 10px;
-                border-radius: 4px;
-                background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
-            }
+        &__tag {
+            display: inline-flex;
+            align-items: center;
+            gap: 3px;
+            height: 22px;
+            padding: 0 8px;
+            border-radius: 4px;
+            font-size: 12px;
+            line-height: 22px;
+            box-sizing: border-box;
+        }
 
-            &__tag {
-                flex: none;
-                display: inline-flex;
-                align-items: center;
-                gap: 4px;
-                padding: 1px 4px;
-                border-radius: 2px;
-                background: #fff;
-                font-size: 12px;
-            }
+        &__tag--level {
+            color: #fff;
+            background: #822422;
+            border: 1px solid rgba(208, 49, 46, 0.4);
+        }
 
-            &__tag--level {
-                color: #ff6a6a;
-            }
+        &__tag--zone {
+            color: rgba(255, 255, 255, 0.85);
+            background: rgba(0, 0, 0, 0.4);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+        }
 
-            &__tag--zone {
-                color: #2199f8;
-            }
+        &__title {
+            font-family: "PangMenZhengDao", sans-serif;
+            font-size: 26px;
+            line-height: 34px;
+            background: linear-gradient(180deg, #ffffff 0%, #fecd76 100%);
+            -webkit-background-clip: text;
+            background-clip: text;
+            color: transparent;
+            margin-bottom: 6px;
+        }
 
-            &__share {
-                flex: none;
-                display: flex;
-                align-items: center;
-                gap: 4px;
-                padding: 4px 14px;
-                border-radius: 25px;
-                background: rgba(19, 125, 209, 0.5);
-                color: #fff;
-            }
+        &__reason {
+            font-size: 12px;
+            line-height: 22px;
+            padding: 4px;
+            border-radius: 4px;
+            background: linear-gradient(90deg, rgba(255, 212, 137, 0.1) 0%, rgba(255, 212, 137, 0) 100%);
+            color: rgba(255, 255, 255, 1);
         }
     }
 
-    .guide-title {
+    .abnormal-main {
         position: relative;
-        z-index: 1;
-        display: inline-block;
-        font-size: 16px;
-        font-weight: 500;
+        z-index: 3;
+        margin-top: -38px;
+        padding: 0 12px 12px;
+        box-sizing: border-box;
+    }
 
-        &::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;
+    .box-wrap {
+        padding: 1px;
+        border-radius: 12px;
+        background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 72.15%);
+
+        .work-info {
+            background: linear-gradient(180deg, #1a191d 0%, #121216 100%);
+            border-radius: 12px;
+            padding: 12px 13px;
+            box-sizing: border-box;
         }
     }
 
-    .abnormal-track-content {
-        
-        position: relative;
-        z-index: 1;
-        margin: -24px 10px 12px;
+    .stage-card + .stage-card {
+        margin-top: 12px;
     }
 
-    .situation-card {
-        &__head {
-            position: relative;
-            z-index: 1;
-            margin: 0 10px 12px;
-            padding: 12px;
-            border-radius: 10px;
-            background: #fff;
+    .section-title {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        font-size: 16px;
+        font-weight: 500;
+        color: #f2e9d8;
+        margin-bottom: 10px;
 
-            &:first-child {
-                margin-top: -24px;
-            }
+        .title-bar {
+            width: 4px;
+            height: 16px;
+            border-radius: 2px;
+            background: #ffd489;
+            flex-shrink: 0;
         }
+    }
 
-        &__name {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            gap: 10px;
-            padding-bottom: 10px;
-            border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+    .analysis-card {
+        .analysis-card__desc {
+            font-size: 14px;
+            line-height: 22px;
+            color: rgba(255, 255, 255, 0.6);
+            margin-bottom: 10px;
         }
 
-        &__title {
+        .analysis-card__stats {
             display: flex;
-            align-items: center;
-            gap: 8px;
-            color: #2199f8;
-            font-size: 16px;
-            font-weight: 500;
+            align-items: stretch;
+            background: rgba(255, 255, 255, 0.05);
+            border-radius: 10px;
+            padding: 8px 0 6px;
         }
 
-        &__icon {
-            width: 18px;
-            height: 18px;
+        .analysis-card__stat {
+            flex: 1;
+            min-width: 0;
+            text-align: center;
+            position: relative;
+
+            & + .analysis-card__stat::before {
+                content: "";
+                position: absolute;
+                left: 0;
+                top: 50%;
+                transform: translateY(-50%);
+                width: 1px;
+                height: 32px;
+                background: rgba(255, 255, 255, 0.06);
+            }
         }
 
-        &__upload {
-            flex: none;
-            padding: 4px 12px;
-            border-radius: 4px;
-            background: #2199f8;
+        .analysis-card__stat-value {
+            font-size: 22px;
+            line-height: 27px;
             color: #fff;
-            font-size: 14px;
-            line-height: 20px;
-            cursor: pointer;
+            margin-bottom: 2px;
+            height: 27px;
+
+            &.highlight {
+                color: #ff9f5a;
+            }
+
+            .num {
+                font-size: 22px;
+                font-weight: 600;
+            }
+
+            .unit {
+                color: rgba(255, 255, 255, 0.5);
+                margin-left: 1px;
+                font-size: 12px;
+            }
         }
 
-        &__assess {
-            margin-top: 10px;
-            padding: 10px;
-            border-radius: 8px;
-            background: #f9f9f9;
+        .analysis-card__stat-label {
+            font-size: 12px;
+            line-height: 18px;
+            color: rgba(255, 255, 255, 0.5);
         }
+    }
+
+    .interact-section-head {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        margin: 16px 0 12px;
 
-        &__assess-title {
-            color: #4f4f4f;
-            font-size: 16px;
-            margin-top: -3px;
-            font-family: "PangMenZhengDao";
+        &__text {
+            flex-shrink: 0;
+            font-size: 13px;
+            line-height: 18px;
+            color: rgba(255, 255, 255, 0.45);
         }
 
-        &__assess-desc {
-            margin-top: 4px;
-            color: #9c9c9c;
+        &__line {
+            flex: 1;
+            height: 1px;
+            background: rgba(255, 255, 255, 0.08);
         }
+    }
 
-        &__guide {
-            margin-top: 10px;
-            padding: 10px;
-            border: 1px solid rgba(33, 153, 248, 0.2);
-            border-radius: 8px;
-            background: linear-gradient(360deg, #ffffff 90.33%, #abd8fc 116.61%, #2199f8 148.55%);
+    .abnormal-item {
+        & + .abnormal-item {
+            margin-top: 16px;
         }
 
-        &__guide-box {
-            margin-top: 12px;
+        &__title {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            margin-bottom: 10px;
+            font-size: 18px;
+            font-weight: 600;
+            color: #ffd489;
+        }
 
-            img {
-                display: block;
-                width: 100%;
-            }
+        &__icon {
+            width: 20px;
+            height: 20px;
+            object-fit: contain;
         }
     }
 
-    .interact-qa {
-        margin-top: 10px;
-        padding: 10px;
-        border-radius: 8px;
-        background: rgba(33, 153, 248, 0.1);
-
+    .interact-card {
         &__head {
             display: flex;
             align-items: center;
-            flex-wrap: wrap;
+            justify-content: space-between;
             gap: 8px;
+            margin-bottom: 4px;
+
+            .section-title {
+                margin-bottom: 0;
+            }
         }
 
-        &__tag {
-            flex: none;
-            padding: 2px 6px;
-            border-radius: 2px;
-            background: #2199f8;
-            color: #fff;
+        &__submit {
+            flex-shrink: 0;
+            height: 28px;
+            padding: 0 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(255, 212, 137, 0.45);
+            color: #ffd489;
             font-size: 12px;
-            line-height: 18px;
+            line-height: 26px;
+            text-align: center;
+            box-sizing: border-box;
+
+            &.disabled {
+                opacity: 0.35;
+            }
         }
 
         &__question {
-            flex: 1;
-            min-width: 0;
-            color: #333;
-            font-size: 14px;
-            font-weight: 500;
+            font-size: 15px;
+            font-weight: 600;
+            line-height: 22px;
+            color: #ffd489;
+            margin-bottom: 12px;
         }
 
-        &__actions {
+        &__options {
             display: flex;
             gap: 8px;
-            margin-top: 10px;
         }
 
-        &__option {
+        .interact-option {
             position: relative;
             flex: 1;
             min-width: 0;
-            height: 36px;
-            line-height: 36px;
-            border-radius: 6px;
-            background: #fff;
-            border: 1px solid transparent;
-            color: #666;
-            font-size: 14px;
-            text-align: center;
+            min-height: 44px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 10px;
+            background: rgba(255, 255, 255, 0.05);
+            border: 1px solid rgba(255, 255, 255, 0.08);
             box-sizing: border-box;
             cursor: pointer;
 
+            &__text {
+                display: block;
+                padding: 0 6px;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                font-size: 14px;
+                color: rgba(255, 255, 255, 0.7);
+            }
+
             &.selected {
-                border-color: #2199f8;
-                color: #2199f8;
-
-                &::after {
-                    content: "";
-                    position: absolute;
-                    top: -1px;
-                    right: -1px;
-                    width: 18px;
-                    height: 14px;
-                    background: url("@/assets/img/home/checked-bg-top.png") no-repeat bottom right / 18px 13px;
+                border-color: rgba(255, 212, 137, 0.55);
+                background: rgba(255, 212, 137, 0.1);
+
+                .interact-option__text {
+                    color: #ffd489;
                 }
             }
         }
+    }
 
-        &__submit {
-            flex: 1;
-            min-width: 0;
-            height: 36px;
-            line-height: 36px;
-            border-radius: 6px;
-            background: #2199f8;
-            color: #fff;
+    .guide-card {
+        position: relative;
+        margin-top: 12px;
+
+        &__img {
+            display: block;
+            width: 100%;
+            border-radius: 8px;
+        }
+
+        .section-title {
+            gap: 6px;
             font-size: 14px;
-            text-align: center;
-            cursor: pointer;
+            color: rgba(255, 255, 255, 0.6);
+        }
 
-            &.disabled {
-                opacity: 0.2;
-                cursor: not-allowed;
-            }
+        .guide-icon {
+            width: 14px;
+            height: 14px;
+            object-fit: contain;
+            filter: grayscale(100%);
+            -webkit-filter: grayscale(100%);
         }
     }
+
+    .guide-card__upload-wrap {
+        position: absolute;
+        right: 25px;
+        bottom: 27px;
+        z-index: 10;
+    }
+
+    .guide-card__upload {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 52px;
+        height: 52px;
+        border-radius: 50%;
+        background: linear-gradient(90deg, #ffebc9 0%, #ffca6d 100%);
+    }
+
+    .guide-card__upload-icon {
+        width: 18px;
+        height: 16px;
+        object-fit: contain;
+    }
 }
 
 .bottom-btn-container {
@@ -726,7 +889,7 @@ onActivated(() => {
 
     .bottom-btn {
         padding: 0 25px;
-        border-radius: 4px;
+        border-radius: 8px;
         font-size: 16px;
         height: 40px;
         line-height: 40px;
@@ -736,8 +899,8 @@ onActivated(() => {
 
         &.primary-btn {
             flex: 1;
-            background: #2199f8;
-            color: #fff;
+            background: linear-gradient(90deg, #ffebc9 0%, #ffca6d 100%);
+            color: #1a1408;
         }
     }
 }

+ 66 - 6
src/views/old_mini/agri_file/pages/addZone.vue

@@ -12,7 +12,13 @@
                 <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="map-tip center-text" v-show="!isFarmDrawMode">
+                <img class="map-tip__icon" src="@/assets/img/agricultural/edit.png" alt="">
+                <div class="map-tip__text">
+                    <div class="map-tip__highlight">建立您的专属作物档案,生成精准农事治疗方案</div>
+                    在地图上依次点击地块边界,回到起点即完成圈地
+                </div>
+            </div>
             <div class="locate-btn" @click="handleLocate">
                 <img class="locate-btn__icon" src="@/assets/img/home/current.png" alt="" />
             </div>
@@ -225,7 +231,26 @@ function getFarmMapLocation() {
     return DEFAULT_MAP_LOCATION;
 }
 
-const getFarmType = computed(() => getFarmData().crop_variety);
+/** localStorage 非响应式;进页/切农场后在 onActivated 里重读 */
+const farmTypeLabel = ref("");
+
+function resolveFarmTypeLabel(farm = getFarmData()) {
+    return (
+        farm.crop_variety ||
+        farm.categoryLabel ||
+        farm.crop_variety ||
+        farm.farm_variety ||
+        farm.crop_name ||
+        ""
+    );
+}
+
+function refreshFarmTypeLabel() {
+    farmTypeLabel.value = resolveFarmTypeLabel();
+}
+
+/** 模板兼容旧名 */
+const getFarmType = computed(() => farmTypeLabel.value);
 
 function getDefaultMapLocation() {
     return (
@@ -351,10 +376,17 @@ function resolveSubmitFarmId() {
 
 function resolveSubmitZoneId() {
     const farm = getFarmData();
-    return route.query.trackId || farm.zone_id || farm.zoneId || "";
+    return farm.zone_id || farm.zoneId || "";
+}
+
+function resolveSubmitRegionName() {
+    const farm = getFarmData();
+    return farm.zone_name || "";
 }
 
 const submittingZoneRange = ref(false);
+/** drawZoneRange 成功后的返回,上传照片跳转用 */
+const lastDrawZoneResult = ref(null);
 
 /** 巡园要点 needDraw 进入:填名称后提交分区范围 */
 const handleFarmNameConfirm = async (regionName) => {
@@ -386,6 +418,12 @@ const handleFarmNameConfirm = async (regionName) => {
             ElMessage.error(res?.msg || "提交失败,请稍后再试");
             return;
         }
+        const data = res?.data || {};
+        lastDrawZoneResult.value = {
+            farm_id: data.farm_id ?? farmId,
+            zone_id: data.zone_id ?? zoneId,
+            zone_name: data.zone_name || regionName,
+        };
     } catch (e) {
         console.warn("[addZone] drawZoneRange failed", e);
         ElMessage.error("提交失败,请稍后再试");
@@ -395,7 +433,7 @@ const handleFarmNameConfirm = async (regionName) => {
     }
 
     const geometryData = {
-        zone_name: regionName,
+        zone_name: lastDrawZoneResult.value?.zone_name || regionName,
         geometryArr: payload.geometryArr,
         mianji: payload.mianji,
         polygon,
@@ -453,9 +491,17 @@ const handleDrawSuccessClosed = () => {
     leaveAfterConfirm();
 };
 
-/** 勾画成功弹窗:上传作物照片(预留) */
+/** 勾画成功弹窗:上传作物照片,优先用 drawZoneRange 返回 */
 const handleUploadCropPhoto = () => {
-    // TODO: 跳转/打开作物照片上传
+    const result = lastDrawZoneResult.value || {};
+    router.replace({
+        path: "/region_albums",
+        query: {
+            farm_id: result.farm_id ?? resolveSubmitFarmId(),
+            zone_id: result.zone_id ?? resolveSubmitZoneId(),
+            name: result.zone_name || resolveSubmitRegionName(),
+        },
+    });
 };
 
 const handleComplete = () => {
@@ -471,10 +517,12 @@ function resetPageTransientState() {
     showZoneInfoPopup.value = false;
     drawnPolygon.value = "";
     submittingZoneRange.value = false;
+    lastDrawZoneResult.value = null;
 }
 
 onActivated(() => {
     resetPageTransientState();
+    refreshFarmTypeLabel();
     nextTick(() => initMapView());
 });
 
@@ -630,6 +678,18 @@ onDeactivated(() => {
         }
     }
 
+    .center-text {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        gap: 7px;
+        padding: 10px 12px;
+        .map-tip__text {
+            line-height: 20px;
+            color: rgba(255, 255, 255, 0.6);
+        }
+    }
+
     .locate-btn {
         position: absolute;
         right: 12px;

+ 6 - 7
src/views/old_mini/agri_file/pages/albumMap.vue

@@ -3,15 +3,15 @@
         <custom-header :name="t('agriFile.agriAlbum')" />
         <div class="album-map-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> -->
             <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
                 v-for="item in zoneList"
@@ -384,12 +384,11 @@ onBeforeUnmount(clearMapOverlays);
     width: 36px;
     height: 36px;
     border-radius: 8px;
-    background: #fff;
-    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    background: rgba(20, 20, 22, 0.8);
 
     &__icon {
-        width: 16px;
-        height: 18px;
+        width: 20px;
     }
 }
 </style>

+ 366 - 244
src/views/old_mini/agri_file/pages/growthTrack.vue

@@ -9,77 +9,111 @@
         />
         <div class="growth-track-body">
             <div class="growth-hero">
-                <div class="growth-hero__top">
-                    <div class="growth-hero__main">
-                        <div class="growth-hero__title-row">
-                            <span class="growth-hero__title">{{ theme }}</span>
-                            <span class="growth-hero__tag growth-hero__tag--level">
-                                <el-icon>
-                                    <BellFilled />
-                                </el-icon>
-                                {{ level }}
-                            </span>
-                            <span class="growth-hero__tag growth-hero__tag--zone">{{ zoneName }}</span>
-                        </div>
-                        <div class="growth-hero__reason">{{ reason }}</div>
-                    </div>
-                    <div class="growth-hero__share" @click="handleShare">
-                        <Icon name="share" />
-                        <span>{{ t("agriFile.forward") }}</span>
+                <div class="growth-hero__share" @click="handleShare">
+                    <Icon name="share" color="rgba(255,255,255,0.85)" />
+                    <span>{{ t("agriFile.forward") }}</span>
+                </div>
+                <div class="growth-mask"></div>
+                <div class="growth-top">
+                    <div class="growth-hero__tags">
+                        <span class="growth-hero__tag growth-hero__tag--level">
+                            <el-icon><BellFilled /></el-icon>
+                            {{ level }}
+                        </span>
+                        <span class="growth-hero__tag growth-hero__tag--zone">{{ zoneName }}</span>
                     </div>
+                    <div class="growth-hero__title">{{ theme }}</div>
+                    <div class="growth-hero__reason">{{ reason }}</div>
                 </div>
             </div>
-            <div class="growth-content">
-                <div class="growth-content__title">{{ t("agriFile.agriAssessment") }}</div>
-                <div class="growth-content__desc">{{ assessmentDesc }}</div>
-            </div>
-            <div class="interact-card">
-                <div class="interact-card__head">
-                    <div class="interact-card__tab">
-                        <img class="interact-card__star" src="@/assets/img/agricultural/start.png" alt="" />
-                        <span>{{ t("agriFile.interactQuestion") }}</span>
+
+            <div class="growth-main">
+                <div class="box-wrap stage-card analysis-card">
+                    <div class="work-info">
+                        <div class="section-title">
+                            <span class="title-bar"></span>
+                            {{ t("agriFile.agriAssessment") }}
+                        </div>
+                        <div class="analysis-card__desc">{{ assessmentDesc }}</div>
+                        <div class="analysis-card__stats">
+                            <div
+                                v-for="(item, index) in analysisStats"
+                                :key="index"
+                                class="analysis-card__stat"
+                            >
+                                <div class="analysis-card__stat-value" :class="{ highlight: item.highlight }">
+                                    <template v-if="item.unit">
+                                        <span class="num">{{ item.value }}</span>
+                                        <span class="unit">{{ item.unit }}</span>
+                                    </template>
+                                    <template v-else>{{ item.value }}</template>
+                                </div>
+                                <div class="analysis-card__stat-label">{{ item.label }}</div>
+                            </div>
+                        </div>
                     </div>
                 </div>
-                <div class="interact-card__qa">
-                    <div v-if="currentTask?.interact_feedback" class="interact-card__phenology">
-                        {{ currentTask.interact_feedback }}
-                    </div>
-                    <template v-else>
-                        <div class="interact-card__question">{{ interactProblem }}</div>
-                        <div class="interact-card__options">
+
+                <div class="box-wrap stage-card interact-card">
+                    <div class="work-info">
+                        <div class="interact-card__head">
+                            <div class="section-title">
+                                <span class="title-bar"></span>
+                                {{ t("agriFile.interactQuestion") }}
+                            </div>
                             <div
-                                v-for="item in interactOptions"
-                                :key="item.id"
-                                class="interact-option"
-                                :class="{ selected: selectedOptionId === item.id }"
-                                @click="selectedOptionId = item.id"
+                                v-if="!currentTask?.interact_feedback"
+                                class="interact-card__submit"
+                                @click="handleSubmit"
                             >
-                                <span class="interact-option__text">{{ item.name }}</span>
+                                {{ t("agriFile.submit") }}
                             </div>
                         </div>
-                        <div class="interact-card__submit" @click="handleSubmit">{{ t("agriFile.submit") }}</div>
-                    </template>
+                        <div class="interact-card__qa">
+                            <div v-if="currentTask?.interact_feedback" class="interact-card__phenology">
+                                {{ currentTask.interact_feedback }}
+                            </div>
+                            <template v-else>
+                                <div class="interact-card__question">{{ interactProblem }}</div>
+                                <div class="interact-card__options">
+                                    <div
+                                        v-for="item in interactOptions"
+                                        :key="item.id"
+                                        class="interact-option"
+                                        :class="{ selected: selectedOptionId === item.id }"
+                                        @click="selectedOptionId = item.id"
+                                    >
+                                        <span class="interact-option__text">{{ item.name }}</span>
+                                    </div>
+                                </div>
+                            </template>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="box-wrap stage-card guide-card">
+                    <div class="work-info">
+                        <div class="section-title">
+                            <img class="guide-icon" src="@/assets/img/farm/guide.png" alt="">
+                            {{ t("agriFile.patrolGuide") }}
+                        </div>
+                        <photo-provider :photo-closable="true">
+                            <photo-consumer :src="guideImage">
+                                <img class="guide-card__img" :src="guideImage" alt="" />
+                            </photo-consumer>
+                        </photo-provider>
+                    </div>
+                    
+                    <div class="guide-card__upload-wrap">
+                        <div v-if="uploadGuideVisible" class="upload-guide-bubble">
+                            <div class="upload-guide-bubble__content">上传照片,构建巡园档案</div>
+                            <span class="upload-guide-bubble__arrow"></span>
+                        </div>
+                        <div class="guide-card__upload" @click="handleUploadClick">
+                            <img class="guide-card__upload-icon" src="@/assets/img/common/camera.png" alt="" />
+                        </div>
+                    </div>
                 </div>
-            </div>
-            <div class="guide-card">
-                <div class="guide-title">{{ t("agriFile.patrolGuide") }}</div>
-                <photo-provider :photo-closable="true">
-                    <photo-consumer :src="guideImage">
-                        <img class="guide-card__img" :src="guideImage" alt="" />
-                    </photo-consumer>
-                </photo-provider>
-            </div>
-        </div>
-        <div class="guide-card__upload-wrap">
-            <div v-if="uploadGuideVisible" class="upload-guide-bubble">
-                <div class="upload-guide-bubble__content">上传照片,构建巡园档案</div>
-                <span class="upload-guide-bubble__arrow"></span>
-            </div>
-            <div class="guide-card__upload" @click="handleUploadClick">
-                <el-icon>
-                    <Plus />
-                </el-icon>
-                <span>{{ t("agriFile.uploadPhoto") }}</span>
             </div>
         </div>
         <album-upload-popup v-model:show="showUploadPopup" @confirm="handleUploadConfirm" />
@@ -161,6 +195,13 @@ const zoneName = computed(() => t("agriFile.zoneOne"));
 const assessmentDesc = computed(
     () => currentContent.value.interact_explain || t("agriFile.agriAssessmentDesc")
 );
+
+/** 农情研判底部指标:接口未接好前写死 */
+const analysisStats = [
+    { value: "60", unit: "%", label: "系统估算抽梢率", highlight: false },
+    { value: "5", unit: "天", label: "比去年慢", highlight: true },
+    { value: "3", unit: "天", label: "追肥窗口剩余", highlight: false },
+];
 const interactProblem = computed(
     () => currentContent.value.interact_problem || t("agriFile.interactQuestionText")
 );
@@ -387,173 +428,279 @@ onActivated(() => {
     width: 100%;
     height: 100vh;
     overflow: hidden;
-    background: #f5f5f5;
+    background: #0b0b0b;
+    color: #fff;
 
     .growth-track-body {
         flex: 1;
         overflow-y: auto;
         -webkit-overflow-scrolling: touch;
+        padding-bottom: 100px;
+        box-sizing: border-box;
+    }
 
-        .growth-hero {
-            padding: 18px 10px 38px;
-            background: #2199f8;
-            color: #fff;
+    .growth-hero {
+        position: relative;
+        padding: 86px 70px 10px 12px;
+        box-sizing: border-box;
+        z-index: 2;
+        min-height: 250px;
+        background:
+        linear-gradient(180deg, rgba(11, 11, 11, 0) 8.51%, rgba(11, 11, 11, 0.416) 42.95%, rgba(11, 11, 11, 0.468) 56.49%, rgba(11, 11, 11, 0.52) 80.47%),
+            url("@/assets/img/agricultural/detail-bg.png") right bottom / cover no-repeat;
 
-            &__top {
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-            }
+        .growth-mask {
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            width: 100%;
+            height: 107px;
+            background: linear-gradient(180deg, rgba(11, 11, 11, 0) 8.47%, rgba(11, 11, 11, 0.8) 26.93%, rgba(11, 11, 11, 0.9) 37.74%, #0B0B0B 56.89%);
+        }
+        .growth-top {
+            position: relative;
+            z-index: 2;
+        }
 
-            &__title-row {
-                display: flex;
-                align-items: center;
-                gap: 10px;
-            }
+        &__share {
+            position: absolute;
+            top: 16px;
+            right: 16px;
+            z-index: 2;
+            display: inline-flex;
+            align-items: center;
+            gap: 4px;
+            height: 28px;
+            padding: 0 12px;
+            border-radius: 14px;
+            background: rgba(0, 0, 0, 0.45);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+            color: rgba(255, 255, 255, 0.85);
+            font-size: 12px;
+        }
 
-            &__title {
-                font-size: 28px;
-                font-family: "PangMenZhengDao";
-                margin-top: -5px;
-            }
+        &__tags {
+            display: flex;
+            align-items: center;
+            flex-wrap: wrap;
+            gap: 8px;
+            margin-bottom: 12px;
+            padding-right: 88px;
+        }
 
-            &__reason {
-                margin-top: 10px;
-                padding: 4px 10px;
-                border-radius: 4px;
-                background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
-            }
+        &__tag {
+            display: inline-flex;
+            align-items: center;
+            gap: 3px;
+            height: 22px;
+            padding: 0 8px;
+            border-radius: 4px;
+            font-size: 12px;
+            line-height: 22px;
+            box-sizing: border-box;
+        }
 
-            &__tag {
-                display: inline-flex;
-                align-items: center;
-                gap: 4px;
-                padding: 1px 4px;
-                border-radius: 2px;
-                background: #fff;
-                font-size: 12px;
-            }
+        &__tag--level {
+            color: #fff;
+            background: #822422;
+            border: 1px solid rgba(208, 49, 46, 0.4);
+        }
 
-            &__tag--level {
-                color: #ff6a6a;
-            }
+        &__tag--zone {
+            color: rgba(255, 255, 255, 0.85);
+            background: rgba(0, 0, 0, 0.4);
+            border: 1px solid rgba(255, 255, 255, 0.12);
+        }
 
-            &__tag--zone {
-                color: #2199f8;
-            }
+        &__title {
+            font-family: "PangMenZhengDao", sans-serif;
+            font-size: 26px;
+            line-height: 34px;
+            background: linear-gradient(180deg, #FFFFFF 0%, #FECD76 100%);
+            -webkit-background-clip: text;
+            background-clip: text;
+            color: transparent;
+            margin-bottom: 6px;
+        }
 
-            &__share {
-                flex: none;
-                display: flex;
-                align-items: center;
-                gap: 4px;
-                padding: 4px 14px;
-                border-radius: 25px;
-                background: rgba(19, 125, 209, 0.5);
-                color: #fff;
-            }
+        &__reason {
+            font-size: 12px;
+            line-height: 22px;
+            padding: 4px;
+            border-radius: 4px;
+            background: linear-gradient(90deg, rgba(255, 212, 137, 0.1) 0%, rgba(255, 212, 137, 0) 100%);
+            color: rgba(255, 255, 255, 1);
         }
     }
 
-    .growth-content {
+    .growth-main {
         position: relative;
-        z-index: 1;
-        margin: -24px 10px 12px;
-        padding: 10px;
-        border-radius: 8px;
-        background: #fff;
+        z-index: 3;
+        margin-top: -38px;
+        padding: 0 12px 12px;
+        box-sizing: border-box;
+    }
 
-        &__title {
-            color: #4f4f4f;
-            font-size: 16px;
-            font-family: "PangMenZhengDao";
+    .box-wrap {
+        padding: 1px;
+        border-radius: 12px;
+        background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 72.15%);
+
+        .work-info {
+            background: linear-gradient(180deg, #1a191d 0%, #121216 100%);
+            border-radius: 12px;
+            padding: 12px 13px;
+            box-sizing: border-box;
         }
+    }
 
-        &__desc {
-            margin-top: 4px;
-            color: #9c9c9c;
-            font-size: 14px;
+    .stage-card + .stage-card {
+        margin-top: 12px;
+    }
+
+    .section-title {
+        display: flex;
+        align-items: center;
+        gap: 10px;
+        font-size: 16px;
+        font-weight: 500;
+        color: #f2e9d8;
+        margin-bottom: 10px;
+
+        .title-bar {
+            width: 4px;
+            height: 16px;
+            border-radius: 2px;
+            background: #ffd489;
+            flex-shrink: 0;
         }
     }
 
-    .interact-card {
-        margin: 0 10px 10px;
-        overflow: hidden;
-        border: 1px solid rgba(33, 153, 248, 0.5);
-        border-radius: 12px;
-        background: #fff;
+    .analysis-card {
+        .analysis-card__desc {
+            font-size: 14px;
+            line-height: 22px;
+            color: rgba(255, 255, 255, 0.6);
+            margin-bottom: 10px;
+        }
 
-        &__head {
+        .analysis-card__stats {
+            display: flex;
+            align-items: stretch;
+            background: rgba(255, 255, 255, 0.05);
+            border-radius: 10px;
+            padding: 8px 0 6px;
+        }
+
+        .analysis-card__stat {
+            flex: 1;
+            min-width: 0;
+            text-align: center;
             position: relative;
-            background: linear-gradient(180deg, #e5f4ff 0%, #ffffff 72%);
+
+            & + .analysis-card__stat::before {
+                content: "";
+                position: absolute;
+                left: 0;
+                top: 50%;
+                transform: translateY(-50%);
+                width: 1px;
+                height: 32px;
+                background: rgba(255, 255, 255, 0.06);
+            }
         }
 
-        &__tab {
-            display: inline-flex;
-            align-items: center;
-            gap: 5px;
-            height: 32px;
-            padding: 0 32px 10px 8px;
+        .analysis-card__stat-value {
+            font-size: 22px;
+            line-height: 27px;
             color: #fff;
-            font-size: 16px;
-            font-family: "PangMenZhengDao";
-            background: url("@/assets/img/agricultural/title-bg.png") no-repeat center / 100% 100%;
+            margin-bottom: 2px;
+            height: 27px;
 
-            span {
-                margin-top: -3px;
+            &.highlight {
+                color: #ff9f5a;
+            }
+
+            .num {
+                font-size: 22px;
+                font-weight: 600;
+            }
+
+            .unit {
+                color: rgba(255, 255, 255, 0.5);
+                margin-left: 1px;
+                font-size: 12px;
             }
         }
 
-        &__star {
-            width: 16px;
-            height: 14px;
+        .analysis-card__stat-label {
+            font-size: 12px;
+            line-height: 18px;
+            color: rgba(255, 255, 255, 0.5);
         }
+    }
 
-        &__qa {
-            margin: 10px;
-            padding: 10px;
-            border-radius: 8px;
-            background: rgba(33, 153, 248, 0.1);
+    .interact-card {
+        &__head {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 8px;
+            margin-bottom: 4px;
+
+            .section-title {
+                margin-bottom: 0;
+            }
+        }
+
+        &__submit {
+            flex-shrink: 0;
+            height: 28px;
+            padding: 0 12px;
+            border-radius: 14px;
+            border: 1px solid rgba(255, 212, 137, 0.45);
+            color: #ffd489;
+            font-size: 12px;
+            line-height: 26px;
+            text-align: center;
+            box-sizing: border-box;
         }
 
         &__question {
-            font-size: 14px;
-            font-weight: 500;
+            font-size: 15px;
+            font-weight: 600;
+            line-height: 22px;
+            color: #ffd489;
+            margin-bottom: 12px;
         }
 
         &__phenology {
-            color: #2199f8;
+            margin-top: 10px;
+            color: #ffd489;
             font-size: 14px;
-            line-height: 20px;
+            line-height: 22px;
+            background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+            padding: 10px 12px;
+            border-radius: 10px;
+            border: 1px solid rgba(255, 241, 217, 0.2);
         }
 
         &__options {
             display: flex;
             gap: 8px;
-            margin-top: 10px;
-        }
-
-        &__submit {
-            width: 120px;
-            margin: 12px auto 0;
-            padding: 8px 0;
-            border-radius: 25px;
-            background: #2199f8;
-            color: #fff;
-            font-size: 14px;
-            text-align: center;
         }
 
         .interact-option {
             position: relative;
             flex: 1;
             min-width: 0;
-            height: 36px;
-            line-height: 36px;
-            border-radius: 6px;
-            background: #fff;
-            border: 1px solid transparent;
-            text-align: center;
+            min-height: 44px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 10px;
+            background: rgba(255, 255, 255, 0.05);
+            border: 1px solid rgba(255, 255, 255, 0.08);
             box-sizing: border-box;
             cursor: pointer;
 
@@ -564,33 +711,46 @@ onActivated(() => {
                 text-overflow: ellipsis;
                 white-space: nowrap;
                 font-size: 14px;
-                color: #666;
+                color: rgba(255, 255, 255, 0.7);
             }
 
             &.selected {
-                border-color: #2199f8;
+                border-color: rgba(255, 212, 137, 0.55);
+                background: rgba(255, 212, 137, 0.1);
 
                 .interact-option__text {
-                    color: #2199f8;
-                }
-
-                &::after {
-                    content: "";
-                    position: absolute;
-                    top: -1px;
-                    right: -1px;
-                    width: 18px;
-                    height: 14px;
-                    background: url("@/assets/img/home/checked-bg-top.png") no-repeat bottom right / 18px 13px;
+                    color: #ffd489;
                 }
             }
         }
     }
 
+    .guide-card {
+        position: relative;
+        &__img {
+            display: block;
+            width: 100%;
+            border-radius: 8px;
+        }
+        .section-title {
+            gap: 6px;
+            font-size: 14px;
+            color: rgba(255, 255, 255, 0.6);
+        }
+        .guide-icon {
+            width: 14px;
+            height: 14px;
+            object-fit: contain;
+            filter: grayscale(100%);
+            /* 兼容旧版webkit浏览器 */
+            -webkit-filter: grayscale(100%);
+        }
+    }
+
     .guide-card__upload-wrap {
-        position: fixed;
-        right: 7px;
-        bottom: 94px;
+        position: absolute;
+        right: 25px;
+        bottom: 27px;
         z-index: 10;
     }
 
@@ -602,15 +762,15 @@ onActivated(() => {
         max-width: 200px;
         padding: 8px 12px;
         border-radius: 8px;
-        background: rgba(0, 0, 0, 0.8);
-        backdrop-filter: blur(4px);
+        background: rgba(24, 24, 24, 0.92);
+        border: 1px solid rgba(255, 212, 137, 0.2);
         box-sizing: border-box;
         pointer-events: none;
 
         &__content {
-            font-size: 14px;
+            font-size: 13px;
             line-height: 20px;
-            color: #fff;
+            color: rgba(255, 255, 255, 0.9);
             white-space: nowrap;
         }
 
@@ -622,62 +782,24 @@ onActivated(() => {
             height: 0;
             border-left: 6px solid transparent;
             border-right: 6px solid transparent;
-            border-top: 6px solid rgba(0, 0, 0, 0.8);
+            border-top: 6px solid rgba(24, 24, 24, 0.92);
         }
     }
 
     .guide-card__upload {
         display: flex;
-        flex-direction: column;
         align-items: center;
         justify-content: center;
-        width: 58px;
-        height: 58px;
+        width: 52px;
+        height: 52px;
         border-radius: 50%;
-        background: #2199f8;
-        color: #fff;
-        box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
-        font-size: 15px;
+        background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
     }
 
-    .guide-card__upload span {
-        margin-top: 3px;
-        font-size: 10px;
-    }
-
-    .guide-title {
-        position: relative;
-        z-index: 1;
-        display: inline-block;
-        font-size: 16px;
-        font-weight: 500;
-
-        &::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;
-        }
-    }
-
-    .guide-card {
-        position: relative;
-        margin: 10px;
-        padding: 10px;
-        border-radius: 8px;
-        background: #fff;
-
-        &__img {
-            display: block;
-            width: 100%;
-            margin-top: 8px;
-        }
+    .guide-card__upload-icon {
+        width: 18px;
+        height: 16px;
+        object-fit: contain;
     }
 }
 
@@ -689,7 +811,7 @@ onActivated(() => {
 
     .bottom-btn {
         padding: 0 25px;
-        border-radius: 4px;
+        border-radius: 8px;
         font-size: 16px;
         height: 40px;
         line-height: 40px;
@@ -699,8 +821,8 @@ onActivated(() => {
 
         &.primary-btn {
             flex: 1;
-            background: #2199f8;
-            color: #fff;
+            background: linear-gradient(90deg, #ffebc9 0%, #ffca6d 100%);
+            color: #1a1408;
         }
     }
 }

+ 1 - 1
src/views/old_mini/agri_file/pages/regionAlbums.vue

@@ -125,7 +125,7 @@ const heroCover = computed(() => {
 const heroStyle = computed(() => {
     if (!heroCover.value) {
         return {
-            background: "linear-gradient(180deg, #8ec8f5 0%, #5aadeb 55%, #3d9ae0 100%)",
+            background: "transparent",
         };
     }
     return {

+ 6 - 6
src/views/old_mini/entry_information/components/baInformation.vue

@@ -474,19 +474,19 @@ onBeforeUnmount(() => {
     }
 
     .search-link {
-        margin-top: 16px;
+        margin-top: 12px;
         display: flex;
         align-items: center;
         justify-content: center;
         gap: 6px;
-        font-size: 13px;
-        color: rgba(255, 255, 255, 0.45);
+        font-size: 14px;
+        color: rgba(255, 255, 255, 0.5);
         cursor: pointer;
 
         &__dot {
-            width: 12px;
-            height: 12px;
-            border: 1.5px solid rgba(255, 255, 255, 0.35);
+            width: 9px;
+            height: 9px;
+            border: 1.5px solid rgba(255, 255, 255, 0.5);
             border-radius: 50%;
             box-sizing: border-box;
         }

+ 446 - 0
src/views/old_mini/entry_information/components/entryComplete.vue

@@ -0,0 +1,446 @@
+<template>
+    <div class="entry-complete">
+        <div class="entry-complete__hero">
+            <img class="entry-complete__badge" src="@/assets/img/farm/right-light.png" alt="" />
+            <div class="entry-complete__hero-top">
+                <div class="entry-complete__title">建档完成</div>
+                <div class="entry-complete__subtitle">你的专属种植报告已生成</div>
+            </div>
+        </div>
+
+        <div class="entry-complete__card">
+            <div class="entry-complete__card-bg" aria-hidden="true"></div>
+            <div class="entry-complete__card-head">
+                <div class="entry-complete__card-title">专属种植报告</div>
+                <div class="entry-complete__status" :class="{ done: allDone }">
+                    <span v-if="!allDone" class="entry-complete__spinner"></span>
+                    <span v-else class="entry-complete__status-check">
+                        <el-icon><Select /></el-icon>
+                    </span>
+                    {{ allDone ? "" : "生成中" }}
+                </div>
+            </div>
+            <div class="entry-complete__tags">
+                <span v-if="locationLabel" class="entry-complete__tag">{{ locationLabel }}</span>
+                <span v-if="varietyLabel" class="entry-complete__tag">{{ varietyLabel }}</span>
+            </div>
+
+            <div class="entry-complete__steps">
+                <div
+                    v-for="(step, index) in steps"
+                    :key="step.key"
+                    class="entry-complete__step"
+                    :class="`is-${step.status}`"
+                >
+                    <div class="entry-complete__step-rail">
+                        <div class="entry-complete__step-icon">
+                            <span v-if="step.status === 'done'" class="icon-done">
+                                <el-icon><Select /></el-icon>
+                            </span>
+                            <span v-else-if="step.status === 'active'" class="icon-active"></span>
+                            <span v-else class="icon-pending"></span>
+                        </div>
+                        <div v-if="index < steps.length - 1" class="entry-complete__step-line"></div>
+                    </div>
+                    <div class="entry-complete__step-body">
+                        <div class="entry-complete__step-title">{{ step.title }}</div>
+                        <div class="entry-complete__step-desc">{{ step.desc }}</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="entry-complete__footer">
+            <div class="entry-complete__btn ghost" @click="emit('home')">看看首页</div>
+            <div class="entry-complete__btn primary" @click="emit('home')">
+                报告好了提醒我
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { computed, onBeforeUnmount, onMounted, ref } from "vue";
+import { ElMessage } from "element-plus";
+
+const props = defineProps({
+    /** 接口创建成功后为 true,驱动第一步完成 */
+    archiveReady: { type: Boolean, default: false },
+    locationLabel: { type: String, default: "" },
+    varietyLabel: { type: String, default: "" },
+});
+
+const emit = defineEmits(["home", "remind"]);
+
+const STEP_DEFS = [
+    {
+        key: "archive",
+        title: "农场档案已建立",
+        desc: "位置、作物、规模都已记好",
+    },
+    {
+        key: "phenology",
+        title: "物候起点已校准",
+        desc: "按 1 月中旬来白点,推算全年节点",
+    },
+    {
+        key: "weather",
+        title: "正在分析当地天气与土壤",
+        desc: "调取近 30 天天气和周边监测点数据",
+    },
+    {
+        key: "advice",
+        title: "生成风险预警与农事建议",
+        desc: "完成后提醒你",
+    },
+];
+
+/** 0 未开始;1~4 表示已完成到第几步(含进行中) */
+const progressIndex = ref(0);
+const timers = [];
+
+const steps = computed(() =>
+    STEP_DEFS.map((item, index) => {
+        let status = "pending";
+        if (index < progressIndex.value) status = "done";
+        else if (index === progressIndex.value) status = "active";
+        return { ...item, status };
+    })
+);
+
+const allDone = computed(() => progressIndex.value >= STEP_DEFS.length);
+
+function clearTimers() {
+    while (timers.length) {
+        clearTimeout(timers.pop());
+    }
+}
+
+/** 第一步跟接口;其后模拟推进 */
+function startFakeProgress() {
+    clearTimers();
+    // 档案步:接口成功则直接完成并进入下一步模拟
+    progressIndex.value = props.archiveReady ? 1 : 0;
+
+    if (!props.archiveReady) return;
+
+    const delays = [1200, 1800, 1600];
+    let acc = 0;
+    delays.forEach((ms, i) => {
+        acc += ms;
+        timers.push(
+            setTimeout(() => {
+                progressIndex.value = Math.min(2 + i, STEP_DEFS.length);
+            }, acc)
+        );
+    });
+}
+
+function handleRemind() {
+    if (allDone.value) {
+        // 离开完成页:由父级清 session 并重置到第 1 步后再跳转
+        emit("remind");
+        return;
+    }
+    ElMessage.success("报告生成后将提醒你");
+}
+
+onMounted(startFakeProgress);
+
+onBeforeUnmount(clearTimers);
+</script>
+
+<style scoped lang="scss">
+.entry-complete {
+    flex: 1;
+    min-height: 0;
+    display: flex;
+    flex-direction: column;
+    overflow: auto;
+    padding: 0 16px calc(20px + env(safe-area-inset-bottom));
+    box-sizing: border-box;
+    background: #0b0b0b url("@/assets/img/farm/light-bg.png") no-repeat top center / 100% auto;
+    color: #fff;
+}
+
+.entry-complete__hero {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    text-align: center;
+    position: relative;
+    .entry-complete__hero-top {
+        padding: 156px 0 22px 0;
+    }
+}
+
+.entry-complete__badge {
+    position: absolute;
+    top: 0;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 261px;
+    height: 202px;
+    object-fit: contain;
+}
+
+.entry-complete__title {
+    font-size: 28px;
+    font-family: "PangMenZhengDao";
+    line-height: 36px;
+    color: #FFFFFF;
+}
+
+.entry-complete__subtitle {
+    margin-top: 5px;
+    font-size: 14px;
+    line-height: 21px;
+    color: rgba(255, 255, 255, 0.6);
+}
+
+.entry-complete__card {
+    position: relative;
+    // flex: 1;
+    // min-height: 0;
+    padding: 10px;
+    border-radius: 12px;
+    border: 1px solid rgba(255, 212, 137, 0.16);
+    background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+    // overflow: hidden;
+    box-sizing: border-box;
+}
+
+.entry-complete__card-bg {
+    position: absolute;
+    inset: 0;
+    background: url("@/assets/img/farm/light-bg.png") no-repeat center top / 140% auto;
+    opacity: 0.35;
+    pointer-events: none;
+}
+
+.entry-complete__card-head,
+.entry-complete__tags,
+.entry-complete__steps {
+    position: relative;
+    z-index: 1;
+}
+
+.entry-complete__card-head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 10px;
+}
+
+.entry-complete__card-title {
+    font-size: 16px;
+    line-height: 24px;
+}
+
+.entry-complete__status {
+    flex-shrink: 0;
+    height: 21px;
+    padding: 0 6px;
+    border-radius: 13px;
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    font-size: 12px;
+    color: #e8c56a;
+    background: rgba(232, 197, 106, 0.12);
+    border: 1px solid rgba(232, 197, 106, 0.28);
+    box-sizing: border-box;
+
+    &.done {
+        padding: 0;
+        width: 21px;
+        text-align: center;
+        line-height: 21px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #5BDC94;
+        background: rgba(63, 160, 110, 0.3);
+        border-color: rgba(63, 160, 110, 0.6);
+        .entry-complete__status-check {
+            padding-top: 3px;
+        }
+    }
+}
+
+.entry-complete__spinner {
+    width: 12px;
+    height: 12px;
+    border-radius: 50%;
+    border: 1.5px solid rgba(232, 197, 106, 0.25);
+    border-top-color: #e8c56a;
+    animation: entry-spin 0.8s linear infinite;
+}
+
+.entry-complete__status-check {
+    font-size: 12px;
+    line-height: 1;
+}
+
+.entry-complete__tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
+    margin-top: 12px;
+}
+
+.entry-complete__tag {
+    height: 20px;
+    padding: 0 6px;
+    border-radius: 4px;
+    font-size: 12px;
+    line-height: 20px;
+    box-sizing: border-box;
+    color: rgba(255, 255, 255, 0.6);
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    background: rgba(255, 255, 255, 0.05);
+}
+
+.entry-complete__steps {
+    margin-top: 18px;
+}
+
+.entry-complete__step {
+    display: flex;
+    gap: 12px;
+    min-height: 64px;
+
+    &.is-pending {
+        .entry-complete__step-title,
+        .entry-complete__step-desc {
+            color: rgba(255, 255, 255, 0.35);
+        }
+    }
+
+    &.is-active {
+        .entry-complete__step-title {
+            color: #e8c56a;
+        }
+    }
+}
+
+.entry-complete__step-rail {
+    width: 22px;
+    flex-shrink: 0;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+}
+
+.entry-complete__step-icon {
+    width: 22px;
+    height: 22px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-sizing: border-box;
+}
+
+.icon-done {
+    width: 18px;
+    height: 18px;
+    border: 1px solid rgba(255, 255, 255, 0.4);
+    border-radius: 50%;
+    background: rgba(255, 255, 255, 0.08);
+    color: rgba(255, 255, 255, 0.8);
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.icon-active {
+    width: 18px;
+    height: 18px;
+    border-radius: 50%;
+    border: 1px solid #e8c56a;
+    box-shadow: 0 0 10px rgba(255, 212, 137, 0.08);
+    position: relative;
+
+    &::after {
+        content: "";
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        width: 5px;
+        height: 5px;
+        border-radius: 50%;
+        background: #FFD489;
+        transform: translate(-50%, -50%);
+    }
+}
+
+.icon-pending {
+    width: 17px;
+    height: 17px;
+    border-radius: 50%;
+    border: 1px dashed rgba(255, 255, 255, 0.2);
+    box-sizing: border-box;
+}
+
+.entry-complete__step-line {
+    flex: 1;
+    width: 0;
+    margin: 4px 0;
+    border-left: 1px solid rgba(255, 255, 255, 0.3);
+}
+
+.entry-complete__step-body {
+    flex: 1;
+    min-width: 0;
+    padding-bottom: 14px;
+}
+
+.entry-complete__step-title {
+    font-size: 14px;
+    line-height: 21px;
+    color: rgba(255, 255, 255, 0.8);
+}
+
+.entry-complete__step-desc {
+    font-size: 12px;
+    line-height: 18px;
+    color: rgba(255, 255, 255, 0.5);
+}
+
+.entry-complete__footer {
+    flex-shrink: 0;
+    display: flex;
+    gap: 12px;
+    margin-top: 18px;
+}
+
+.entry-complete__btn {
+    flex: 1;
+    height: 44px;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 15px;
+    font-weight: 600;
+    box-sizing: border-box;
+
+    &.ghost {
+        color: rgba(255, 255, 255, 0.9);
+        background: rgba(255, 255, 255, 0.08);
+        border: 1px solid rgba(255, 255, 255, 0.12);
+    }
+
+    &.primary {
+        color: #1a1208;
+        background: linear-gradient(135deg, #f5e6b8 0%, #e8c56a 50%, #d4a84b 100%);
+    }
+}
+
+@keyframes entry-spin {
+    to {
+        transform: rotate(360deg);
+    }
+}
+</style>

+ 324 - 69
src/views/old_mini/entry_information/components/selectCategory.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="select-crop">
-        <div class="select-crop__content" v-loading="loading">
+        <div class="select-crop__content" v-loading="loading" element-loading-background="rgba(15, 15, 15, 0.55)">
             <div class="page-header">
                 <div class="page-title">请选择您的作物</div>
                 <div class="page-subtitle">已自动匹配您当地的主要作物</div>
@@ -16,6 +16,9 @@
                     @click="handleSelectCrop(item)"
                 >
                     {{ item.name }}
+                    <span v-if="String(selectedCropId) === String(item.id)" class="chip-item__check">
+                        <el-icon><Select /></el-icon>
+                    </span>
                 </div>
             </div>
 
@@ -26,6 +29,7 @@
                 clearable
                 placeholder="搜索其他作物"
                 popper-class="entry-dark-select-popper"
+                @visible-change="handleCropSelectVisible"
                 @change="handleCropSearchSelect"
             >
                 <template #prefix>
@@ -36,8 +40,9 @@
                     :key="item.id"
                     :label="item.name"
                     :value="String(item.id)"
-                    :disabled="!isCropAllowed(item)"
+                    :class="{ 'is-disabled': !isCropAllowed(item) }"
                 />
+                    <!-- :disabled="!isCropAllowed(item)" -->
             </el-select>
 
             <!-- 品种 -->
@@ -46,7 +51,7 @@
                     <div class="section-title">{{ selectedCrop?.name || "" }}品种</div>
                 </div>
 
-                <div class="chip-grid variety-grid" v-loading="varietyLoading">
+                <div class="chip-grid variety-grid" v-loading="varietyLoading" element-loading-background="rgba(15, 15, 15, 0.55)">
                     <div
                         v-for="item in displayVarieties"
                         :key="item.id"
@@ -56,7 +61,7 @@
                     >
                         <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>
+                            <el-icon><Select /></el-icon>
                         </span>
                     </div>
                     <div
@@ -75,6 +80,7 @@
                     placeholder="搜索其他品种"
                     :disabled="!selectedCropId"
                     popper-class="entry-dark-select-popper"
+                    @visible-change="handleVarietySelectVisible"
                     @change="handleVarietySearchSelect"
                 >
                     <template #prefix>
@@ -92,17 +98,18 @@
 
         <div class="select-crop__footer">
             <div class="action-btn secondary" @click="emit('prev')">上一步</div>
-            <div class="action-btn primary" @click="handleNext">
-                {{ multiple ? "下一步" : "确认品种" }}
+            <div class="action-btn primary" :class="{ disabled: submitting }" @click="handleNext">
+                {{ primaryBtnText }}
             </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from "vue";
+import { computed, nextTick, onMounted, ref } from "vue";
 import { ElMessage } from "element-plus";
 import { Check, Search } from "@element-plus/icons-vue";
+import { selectNewlyCreatedFarm } from "@/utils/selectedFarm";
 
 // ---------------------------------------------------------------------------
 // Props / Emits
@@ -111,7 +118,7 @@ const props = defineProps({
     /** 农服多选品类;农户为单选品类+品种 */
     multiple: { type: Boolean, default: false },
 });
-const emit = defineEmits(["prev", "next"]);
+const emit = defineEmits(["prev", "next", "complete"]);
 
 // ---------------------------------------------------------------------------
 // 常量
@@ -121,6 +128,14 @@ 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 BA_FORM_KEY = "ENTRY_BA_FORM";
+const ADMIN_ID_KEY = "ENTRY_ADMIN_ID";
+const CROP_DETAIL_KEY = "ENTRY_CROP_DETAIL";
+const CROP_META_KEY = "ENTRY_CROP_META";
+const STEP_KEY = "ENTRY_INFORMATION_STEP";
+const INVITE_TYPE_KEY = "ENTRY_INVITE_TYPE";
+const MANUAL_VIEW_KEY = "ENTRY_MANUAL_VIEW";
+const EDIT_UID_KEY = "ENTRY_VARIETY_EDIT_UID";
 const META_KEYS = new Set(["city", "county", "county_code", "province"]);
 const FIRST_CROP_KEY_MAP = {
     果树: "fruit",
@@ -130,14 +145,17 @@ 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;
+/** 品类 chip:1 行 × 3 列 */
+const DEFAULT_CROP_VISIBLE = 3;
+/** 品种 chip:3 行 × 2 列 */
+const DEFAULT_VARIETY_VISIBLE = 6;
 
 // ---------------------------------------------------------------------------
 // 页面状态
 // ---------------------------------------------------------------------------
 const loading = ref(false);
 const varietyLoading = ref(false);
+const submitting = ref(false);
 /** 全量品类(扁平) */
 const allCrops = ref([]);
 /** 顶部展示的品类(选中项会置顶) */
@@ -145,12 +163,20 @@ const displayCrops = ref([]);
 const selectedCropId = ref("");
 /** 农服多选 */
 const selectedCropIds = ref(new Set());
+/** 仅「搜索其他作物」下拉选中后才有值,初始为空 */
 const cropSearchValue = ref("");
+/** 打开下拉前备份,关闭未选新项时恢复 */
+let cropSearchBackup = "";
+/** 打开/恢复时改 v-model 会触发 change,用此标记忽略 */
+let cropSelectSilent = false;
 
 const varietyList = ref([]);
 const displayVarieties = ref([]);
 const selectedVarietyId = ref("");
+/** 仅「搜索其他品种」下拉选中后才有值,初始为空 */
 const varietySearchValue = ref("");
+let varietySearchBackup = "";
+let varietySelectSilent = false;
 
 let varietyReqId = 0;
 
@@ -167,6 +193,12 @@ const cropSelectOptions = computed(() => allCrops.value);
 
 const varietySelectOptions = computed(() => varietyList.value);
 
+const primaryBtnText = computed(() => {
+    if (props.multiple) return "下一步";
+    if (submitting.value) return "提交中...";
+    return "确认品种";
+});
+
 const isCropSelected = (item) => {
     if (!item) return false;
     if (props.multiple) return selectedCropIds.value.has(String(item.id));
@@ -253,13 +285,35 @@ 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];
+    displayCrops.value = [crop, ...rest].slice(0, DEFAULT_CROP_VISIBLE);
     selectedCropId.value = crop.id;
     if (props.multiple) {
         selectedCropIds.value = new Set([id]);
     }
 }
 
+function setCropSearchValueSilent(value) {
+    cropSelectSilent = true;
+    cropSearchValue.value = value;
+    nextTick(() => {
+        cropSelectSilent = false;
+    });
+}
+
+/**
+ * 打开:清空以便搜索;关闭未选新项:恢复打开前下拉自身的值(不回填 chip 默认选中)
+ */
+const handleCropSelectVisible = (visible) => {
+    if (visible) {
+        cropSearchBackup = cropSearchValue.value || "";
+        setCropSearchValueSilent("");
+        return;
+    }
+    if (!cropSearchValue.value && cropSearchBackup) {
+        setCropSearchValueSilent(cropSearchBackup);
+    }
+};
+
 function getResidentPoint() {
     const saved = readJson(LOCATION_KEY);
     return (
@@ -313,10 +367,30 @@ 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];
+    displayVarieties.value = [variety, ...rest].slice(0, DEFAULT_VARIETY_VISIBLE);
     selectedVarietyId.value = variety.id;
 }
 
+function setVarietySearchValueSilent(value) {
+    varietySelectSilent = true;
+    varietySearchValue.value = value;
+    nextTick(() => {
+        varietySelectSilent = false;
+    });
+}
+
+/** 打开清空便于搜索;关闭未选新项:恢复打开前下拉自身的值 */
+const handleVarietySelectVisible = (visible) => {
+    if (visible) {
+        varietySearchBackup = varietySearchValue.value || "";
+        setVarietySearchValueSilent("");
+        return;
+    }
+    if (!varietySearchValue.value && varietySearchBackup) {
+        setVarietySearchValueSilent(varietySearchBackup);
+    }
+};
+
 function buildCategoryPayload(crop) {
     return {
         id: crop.id,
@@ -367,12 +441,14 @@ async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
         varietyList.value = [];
         displayVarieties.value = [];
         selectedVarietyId.value = "";
+        setVarietySearchValueSilent("");
         return;
     }
     const reqId = ++varietyReqId;
     varietyLoading.value = true;
     try {
-        const res = await VE_API.entry.getVarietiesByCrop({ crop_id: cropId });
+        // 品种 + crop_code 统一走 farmer_crop_detail
+        const res = await VE_API.entry.getMachines({ crop_id: cropId });
         if (reqId !== varietyReqId) return;
         if (res.code === 200 && res.data) {
             const data = res.data;
@@ -381,18 +457,16 @@ async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
                 crop.code = data.crop_code;
                 if (data.crop_group) crop.firstCrop = crop.firstCrop || data.crop_group;
             }
-            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 || "",
-                });
-            }
+            saveCropDetailFromApi({
+                crop_id: data.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];
+            displayVarieties.value = list.slice(0, DEFAULT_VARIETY_VISIBLE);
 
             const cached = readJson(VARIETY_LIST_KEY);
             const cachedItem = Array.isArray(cached)
@@ -405,11 +479,13 @@ async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
                 promoteVarietyToFront(list[0]);
             } else {
                 selectedVarietyId.value = "";
+                setVarietySearchValueSilent("");
             }
         } else {
             varietyList.value = [];
             displayVarieties.value = [];
             selectedVarietyId.value = "";
+            setVarietySearchValueSilent("");
             ElMessage.error(res.msg || "获取品种列表失败");
         }
     } catch {
@@ -417,6 +493,7 @@ async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
         varietyList.value = [];
         displayVarieties.value = [];
         selectedVarietyId.value = "";
+        setVarietySearchValueSilent("");
         ElMessage.error("获取品种列表失败,请稍后再试");
     } finally {
         if (reqId === varietyReqId) varietyLoading.value = false;
@@ -493,7 +570,7 @@ async function fetchCrops() {
 
 const handleSelectCrop = async (item) => {
     if (!isCropAllowed(item)) {
-        ElMessage.warning("该品种暂未开放");
+        ElMessage.warning("该作物暂未开放");
         return;
     }
     if (props.multiple) {
@@ -507,21 +584,32 @@ const handleSelectCrop = async (item) => {
     }
     if (String(selectedCropId.value) === String(item.id)) return;
     selectedCropId.value = item.id;
-    varietySearchValue.value = "";
+    // 点 chip 不写入搜索下拉;切换品类时清空品种搜索展示
+    setVarietySearchValueSilent("");
+    varietySearchBackup = "";
     await fetchVarietiesByCrop(item.id, { selectFirst: true });
 };
 
 const handleCropSearchSelect = async (value) => {
+    if (cropSelectSilent) return;
     if (value == null || value === "") return;
+
     const crop = allCrops.value.find((item) => String(item.id) === String(value));
-    cropSearchValue.value = "";
-    if (!crop) return;
+    if (!crop) {
+        setCropSearchValueSilent(cropSearchBackup || "");
+        return;
+    }
     if (!isCropAllowed(crop)) {
+        setCropSearchValueSilent(cropSearchBackup || "");
         ElMessage.warning("该品种暂未开放");
         return;
     }
+
     promoteCropToFront(crop);
-    varietySearchValue.value = "";
+    cropSearchValue.value = String(crop.id);
+    cropSearchBackup = String(crop.id);
+    setVarietySearchValueSilent("");
+    varietySearchBackup = "";
     if (!props.multiple) {
         await fetchVarietiesByCrop(crop.id, { selectFirst: true });
     }
@@ -533,14 +621,162 @@ const handleSelectVariety = (item) => {
 };
 
 const handleVarietySearchSelect = (value) => {
+    if (varietySelectSilent) return;
     if (value == null || value === "") return;
     const variety = varietyList.value.find((item) => String(item.id) === String(value));
-    varietySearchValue.value = "";
-    if (!variety) return;
+    if (!variety) {
+        setVarietySearchValueSilent(varietySearchBackup || "");
+        return;
+    }
     promoteVarietyToFront(variety);
+    varietySearchValue.value = String(variety.id);
+    varietySearchBackup = String(variety.id);
 };
 
-const handleNext = () => {
+function getUserId() {
+    const id = localStorage.getItem("MINI_USER_ID");
+    return id != null && id !== "" ? Number(id) : 0;
+}
+
+function getAdminId() {
+    const fromSession = sessionStorage.getItem(ADMIN_ID_KEY);
+    if (fromSession != null && fromSession !== "") return Number(fromSession);
+    return getUserId();
+}
+
+function getCropDetailMap() {
+    const cache = readJson(CROP_DETAIL_KEY);
+    return cache && typeof cache === "object" ? cache : {};
+}
+
+/** 农户确认品种后组装 addFarmInfo(不带设备) */
+function buildFarmPayload(crop, variety) {
+    const baForm = readJson(BA_FORM_KEY) || {};
+    const location = readJson(LOCATION_KEY);
+    const cropDetailMap = getCropDetailMap();
+    const detail = cropDetailMap[String(crop.id)] || {};
+    const farmLocation = location?.point || variety?.location || getResidentPoint();
+
+    return {
+        user_name: baForm.farmName || baForm.name,
+        tel: baForm.phone || "13000000000",
+        admin_id: getAdminId(),
+        user_id: getUserId(),
+        machine_id: [],
+        farm_location: farmLocation,
+        farm_address: location?.address || "",
+        crop_big_type: detail.crop_group || crop.firstCrop || "",
+        crops: [
+            {
+                crop_type: detail.crop_name || crop.name,
+                crop_code: detail.crop_code || crop.code || "",
+                crop_id: Number(crop.id),
+                variety_list: String(variety.varietyCode || variety.id),
+                phenophase_code: String(variety.phenophaseCode || variety.phenologyId || ""),
+            },
+        ],
+    };
+}
+
+function buildCompleteMeta(crop, variety) {
+    const location = readJson(LOCATION_KEY) || {};
+    const meta = readJson(CROP_META_KEY) || {};
+    return {
+        locationLabel: location.address || meta.county || meta.city || "",
+        varietyLabel: variety?.name || "",
+        cropName: crop?.name || "",
+        farmName: (readJson(BA_FORM_KEY) || {}).farmName || "",
+    };
+}
+
+function clearEntrySession() {
+    [
+        SESSION_KEY,
+        VARIETY_LIST_KEY,
+        EQUIPMENT_KEY,
+        TASK_KEY,
+        LOCATION_KEY,
+        BA_FORM_KEY,
+        ADMIN_ID_KEY,
+        CROP_DETAIL_KEY,
+        CROP_META_KEY,
+        STEP_KEY,
+        INVITE_TYPE_KEY,
+        MANUAL_VIEW_KEY,
+        EDIT_UID_KEY,
+    ].forEach((key) => sessionStorage.removeItem(key));
+}
+
+function pickFarmZoneIds(data) {
+    if (!data || typeof data !== "object") return null;
+    const farmId =
+        data.farm_id ?? data.farmId ?? data.farm?.farm_id ?? data.farm?.id ?? data.id;
+    const zoneId =
+        data.zone_id ??
+        data.zoneId ??
+        data.zone_ids?.[0] ??
+        data.zoneIds?.[0] ??
+        data.zone?.zone_id ??
+        data.zone?.id ??
+        data.default_zone_id ??
+        data.zones?.[0]?.zone_id ??
+        data.zones?.[0]?.id;
+    if (farmId == null || farmId === "" || zoneId == null || zoneId === "") return null;
+    return { farm_id: farmId, zone_id: zoneId };
+}
+
+function triggerReportApis(res, crop) {
+    const payload = res?.data && typeof res.data === "object" ? res.data : null;
+    const ids = pickFarmZoneIds(payload) || pickFarmZoneIds(res);
+    if (!ids) {
+        console.warn("generate_6module skipped: addFarmInfo 未返回 farm_id/zone_id", res);
+        return;
+    }
+    const meta = readJson(CROP_META_KEY) || {};
+    VE_API.report
+        .generate6Module({
+            farm_id: ids.farm_id,
+            zone_id: ids.zone_id,
+            region: meta.city || meta.county || "",
+            crop: crop?.name || "",
+        })
+        .catch(() => {});
+}
+
+/** 农户:确认品种后直接提交建档 */
+async function submitFarmerEntry(crop, variety) {
+    if (submitting.value) return;
+    const baForm = readJson(BA_FORM_KEY);
+    if (!baForm?.farmName && !baForm?.name) {
+        ElMessage.warning("请先完善农场信息");
+        return;
+    }
+
+    submitting.value = true;
+    const completeMeta = buildCompleteMeta(crop, variety);
+    try {
+        const params = buildFarmPayload(crop, variety);
+        const raw = await VE_API.entry.addFarmInfo(params);
+        const res = Array.isArray(raw) ? raw[0] : raw;
+        if (res?.code === 200) {
+            triggerReportApis(res, crop);
+            await selectNewlyCreatedFarm();
+            clearEntrySession();
+            emit("complete", { archiveReady: true, ...completeMeta });
+            return;
+        }
+        ElMessage.error(res?.msg || res?.message || "提交失败,请稍后再试");
+    } catch (error) {
+        console.error("entry submit failed", error);
+        ElMessage.error("提交失败");
+    } finally {
+        submitting.value = false;
+    }
+}
+
+const handleNext = async () => {
+    if (submitting.value) return;
+
     if (props.multiple) {
         const list = allCrops.value
             .filter((item) => selectedCropIds.value.has(String(item.id)) && isCropAllowed(item))
@@ -577,7 +813,7 @@ const handleNext = () => {
 
     writeJson(SESSION_KEY, [buildCategoryPayload(crop)]);
     writeJson(VARIETY_LIST_KEY, [buildVarietyPayload(crop, variety)]);
-    emit("next", { crop, variety });
+    await submitFarmerEntry(crop, variety);
 };
 
 onMounted(() => {
@@ -596,39 +832,38 @@ onMounted(() => {
     color: #fff;
 
     &__content {
-        flex: 1;
         min-height: 0;
         overflow-y: auto;
-        padding: 28px 16px 20px;
+        padding: 28px 24px 24px;
     }
 
     &__footer {
         flex-shrink: 0;
         display: flex;
         gap: 12px;
-        padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
+        padding: 0 24px calc(16px + env(safe-area-inset-bottom));
     }
 
     .page-header {
         margin-bottom: 18px;
 
         .page-title {
-            font-size: 24px;
-            font-weight: 600;
-            line-height: 34px;
+            font-size: 20px;
+            font-weight: 500;
+            line-height: 30px;
             color: #fff;
         }
 
         .page-subtitle {
-            margin-top: 6px;
-            font-size: 13px;
-            line-height: 20px;
-            color: rgba(255, 255, 255, 0.45);
+            margin-top: 2px;
+            font-size: 14px;
+            line-height: 21px;
+            color: rgba(255, 255, 255, 0.6);
         }
     }
 
     .section-header {
-        margin: 22px 0 12px;
+        margin: 22px 0 10px;
 
         .section-title {
             font-size: 18px;
@@ -642,37 +877,45 @@ onMounted(() => {
         display: grid;
         gap: 10px;
         margin-bottom: 12px;
+        overflow: hidden;
 
         &.crop-grid {
             grid-template-columns: repeat(3, 1fr);
+            /* 1 行:chip 65 + 不换行 */
+            max-height: 65px;
+            .chip-item {
+                height: 65px;
+                padding: 0;
+            }
         }
 
         &.variety-grid {
             grid-template-columns: repeat(2, 1fr);
             min-height: 44px;
+            /* 3 行:44*3 + gap 10*2 */
         }
     }
 
     .chip-item {
         position: relative;
-        height: 44px;
-        padding: 0 10px;
+        min-height: 55px;
+        padding: 0 10px 0 15px;
         border-radius: 10px;
         box-sizing: border-box;
         display: flex;
         align-items: center;
         justify-content: center;
         text-align: center;
-        font-size: 15px;
-        color: rgba(255, 255, 255, 0.85);
-        background: #2a2a2a;
-        border: 1px solid transparent;
+        font-size: 16px;
+        color: rgba(255, 255, 255, 1);
+        background: rgba(255, 255, 255, 0.05);
+        border: 1px solid rgba(255, 255, 255, 0.12);
         cursor: pointer;
 
         &.selected {
-            color: #e8c56a;
-            border-color: #e8c56a;
-            background: rgba(232, 197, 106, 0.08);
+            color: rgba(255, 212, 137, 1);
+            border-color: rgba(255, 212, 137, 1);
+            background: rgba(255, 212, 137, 0.1);
         }
 
         &.variety-item {
@@ -688,26 +931,29 @@ onMounted(() => {
                 text-align: left;
             }
 
-            .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;
-            }
-
             &.selected {
                 justify-content: space-between;
             }
         }
     }
 
+
+    .chip-item__check {
+        flex-shrink: 0;
+        width: 20px;
+        height: 20px;
+        margin-left: 10px;
+        border-radius: 50%;
+        background: rgba(255, 212, 137, 0.16);
+        border: 1px solid rgba(255, 212, 137, 1);
+        border-radius: 50%;
+        color: rgba(255, 212, 137, 1);
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 12px;
+    }
+
     .empty-tip {
         grid-column: 1 / -1;
         padding: 24px 0;
@@ -718,7 +964,6 @@ onMounted(() => {
 
     .search-select {
         width: 100%;
-        margin-bottom: 4px;
 
         :deep(.el-select__wrapper) {
             min-height: 44px;
@@ -774,6 +1019,11 @@ onMounted(() => {
             background: linear-gradient(135deg, #f5e6b8 0%, #e8c56a 50%, #d4a84b 100%);
             color: #1a1a1a;
         }
+
+        &.disabled {
+            opacity: 0.55;
+            pointer-events: none;
+        }
     }
 }
 </style>
@@ -781,10 +1031,10 @@ onMounted(() => {
 <style lang="scss">
 .entry-dark-select-popper {
     background: #1c1c1c !important;
-    border: 1px solid rgba(255, 255, 255, 0.12) !important;
+    border-color: rgba(255, 255, 255, 0.12) !important;
 
     .el-select-dropdown__item {
-        color: rgba(255, 255, 255, 0.85);
+        color: rgba(255, 255, 255, 0.9);
 
         &.is-hovering,
         &:hover {
@@ -795,6 +1045,11 @@ onMounted(() => {
             color: #e8c56a;
             font-weight: 500;
         }
+
+        &.is-disabled {
+            color: rgba(255, 255, 255, 0.5) !important;
+            cursor: not-allowed;
+        }
     }
 
     .el-popper__arrow::before {

+ 115 - 29
src/views/old_mini/entry_information/index.vue

@@ -1,34 +1,50 @@
 <template>
     <div class="entry-information-page" :class="{ 'is-dark-step': isDarkStep }">
         <custom-header
-            name="录入信息"
-            bgColor="#fff"
+            v-if="!showComplete"
+            name="新建农场"
             :isClose="hideBack"
             :showClose="false"
         ></custom-header>
         <div class="entry-information-body">
-            <service-information v-if="currentStep === 1 && isServiceEntry" @next="handleNext" />
-            <ba-information v-else-if="currentStep === 1" @next="handleNext" />
-            <select-category v-else-if="currentStep === 2" :multiple="isServiceEntry" @prev="handlePrev" @next="handleNext" />
-            <select-equipment
-                v-else-if="showEquipmentStep"
-                :key="equipmentScope || 'farmer'"
-                :is-service="isServiceEntry"
-                :is-last-step="equipmentIsLastStep"
-                :equipment-scope="equipmentScope"
-                :total-steps="totalSteps"
-                @prev="handlePrev"
-                @next="handleNext"
-                @confirm="handleConfirm"
-            />
-            <manual-farming-service
-                v-else-if="showManualStep"
-                :has-field="hasFieldSelected"
-                :has-fruit="hasFruitSelected"
-                @prev="handlePrev"
-                @next="handleNext"
-                @confirm="handleConfirm"
+            <entry-complete
+                v-if="showComplete"
+                :archive-ready="completeMeta.archiveReady"
+                :location-label="completeMeta.locationLabel"
+                :variety-label="completeMeta.varietyLabel"
+                @home="leaveCompleteToHome"
+                @remind="leaveCompleteToReport"
             />
+            <template v-else>
+                <service-information v-if="currentStep === 1 && isServiceEntry" @next="handleNext" />
+                <ba-information v-else-if="currentStep === 1" @next="handleNext" />
+                <select-category
+                    v-else-if="currentStep === 2"
+                    :multiple="isServiceEntry"
+                    @prev="handlePrev"
+                    @next="handleNext"
+                    @complete="handleEntryComplete"
+                />
+                <select-equipment
+                    v-else-if="showEquipmentStep"
+                    :key="equipmentScope || 'farmer'"
+                    :is-service="isServiceEntry"
+                    :is-last-step="equipmentIsLastStep"
+                    :equipment-scope="equipmentScope"
+                    :total-steps="totalSteps"
+                    @prev="handlePrev"
+                    @next="handleNext"
+                    @confirm="handleConfirm"
+                />
+                <manual-farming-service
+                    v-else-if="showManualStep"
+                    :has-field="hasFieldSelected"
+                    :has-fruit="hasFruitSelected"
+                    @prev="handlePrev"
+                    @next="handleNext"
+                    @confirm="handleConfirm"
+                />
+            </template>
         </div>
     </div>
 </template>
@@ -42,11 +58,26 @@ import serviceInformation from "./components/serviceInformation.vue";
 import selectCategory from "./components/selectCategory.vue";
 import selectEquipment from "./components/selectEquipment.vue";
 import manualFarmingService from "./components/manualFarmingService.vue";
+import entryComplete from "./components/entryComplete.vue";
 
 const STEP_KEY = "ENTRY_INFORMATION_STEP";
 const INVITE_TYPE_KEY = "ENTRY_INVITE_TYPE";
 const CATEGORY_SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
 const ADMIN_ID_KEY = "ENTRY_ADMIN_ID";
+/** 录入流程草稿键,离开完成页时一并清空 */
+const ENTRY_DRAFT_KEYS = [
+    "ENTRY_INFORMATION_STEP",
+    "ENTRY_SELECTED_CATEGORY",
+    "ENTRY_SELECTED_VARIETY_LIST",
+    "ENTRY_SELECTED_EQUIPMENT",
+    "ENTRY_SELECTED_FARM_TASKS",
+    "ENTRY_RESIDENT_LOCATION",
+    "ENTRY_BA_FORM",
+    "ENTRY_CROP_DETAIL",
+    "ENTRY_CROP_META",
+    "ENTRY_MANUAL_VIEW",
+    "ENTRY_VARIETY_EDIT_UID",
+];
 
 const router = useRouter();
 const route = useRoute();
@@ -80,24 +111,35 @@ function syncAdminIdFromQuery() {
 const currentStep = ref(readStep());
 const inviteType = ref(readInviteType());
 const isServiceEntry = computed(() => inviteType.value === "service");
-/** 农户录入:位置确认 / 作物品种页为深色 */
+/** 农户:位置 / 作物页 / 建档完成页为深色 */
+const showComplete = ref(false);
+const completeMeta = ref({
+    archiveReady: false,
+    locationLabel: "",
+    varietyLabel: "",
+});
 const isDarkStep = computed(
-    () => !isServiceEntry.value && (currentStep.value === 1 || currentStep.value === 2)
+    () =>
+        showComplete.value ||
+        (!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 3;
+    if (!isServiceEntry.value) return 2;
     return isBothCrops.value ? 6 : 4;
 });
 const maxStep = computed(() => {
-    if (!isServiceEntry.value) return 3;
+    if (!isServiceEntry.value) return 2;
     return isBothCrops.value ? 5 : 4;
 });
 const showEquipmentStep = computed(() => {
-    if (!isServiceEntry.value) return currentStep.value === 3;
+    if (!isServiceEntry.value) return false;
     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;
@@ -140,9 +182,32 @@ function clampStep() {
     }
 }
 
+function clearEntryDraft() {
+    ENTRY_DRAFT_KEYS.forEach((key) => sessionStorage.removeItem(key));
+}
+
+/** 离开完成页 / 再次进入:回到第 1 步 */
+function resetToFirstStep() {
+    clearEntryDraft();
+    showComplete.value = false;
+    completeMeta.value = {
+        archiveReady: false,
+        locationLabel: "",
+        varietyLabel: "",
+    };
+    currentStep.value = 1;
+    sessionStorage.setItem(STEP_KEY, "1");
+    refreshHasField();
+}
+
 function syncEntryState() {
     inviteType.value = readInviteType();
     syncAdminIdFromQuery();
+    // keep-alive 再次进入:若仍停在完成页,强制回第一步
+    if (showComplete.value) {
+        resetToFirstStep();
+        return;
+    }
     currentStep.value = readStep();
     refreshHasField();
     clampStep();
@@ -175,7 +240,28 @@ const handlePrev = () => {
     }
 };
 
+/** 农户确认品种并提交成功 → 建档完成页 */
+const handleEntryComplete = (meta = {}) => {
+    completeMeta.value = {
+        archiveReady: !!meta.archiveReady,
+        locationLabel: meta.locationLabel || "",
+        varietyLabel: meta.varietyLabel || "",
+    };
+    showComplete.value = true;
+};
+
+function leaveCompleteToHome() {
+    resetToFirstStep();
+    router.replace("/growth_report");
+}
+
+function leaveCompleteToReport() {
+    resetToFirstStep();
+    router.replace("/growth_report");
+}
+
 const handleConfirm = () => {
+    resetToFirstStep();
     router.replace("/growth_report");
 };
 </script>

+ 4 - 0
src/views/old_mini/entry_information/map/selectLocationMap.js

@@ -24,6 +24,9 @@ export const POINT_ICON = {
 class SelectLocationMap {
   constructor(options = {}) {
     const iconSrc = options.iconSrc || POINT_ICON.resident;
+    // 仅常驻点图标需要下移;种植点 / 当前点不偏移
+    const displacement =
+      iconSrc === POINT_ICON.resident ? [0, -150] : [0, 0];
     this.onMapClick = options.onMapClick || null;
     this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 10001, {
       style: () => {
@@ -32,6 +35,7 @@ class SelectLocationMap {
             src: iconSrc,
             scale: 0.45,
             anchor: [0.5, 1],
+            displacement,
           }),
         });
       },

+ 5 - 5
src/views/old_mini/entry_information/selectLocation.vue

@@ -11,7 +11,7 @@
             </div>
             <div class="map-container" ref="mapContainer"></div>
             <div class="locate-btn" @click="handleLocate">
-                <img class="locate-icon" src="@/assets/img/map/map-icon.png" alt="" />
+                <img class="locate-icon" src="@/assets/img/home/current.png" alt="" />
             </div>
         </div>
         <div class="custom-bottom-fixed-btns">
@@ -200,7 +200,7 @@ const handleSubmit = async () => {
             display: flex;
             align-items: center;
             justify-content: center;
-            background: #fff;
+            background: rgba(20, 20, 22, 0.8);
             border-radius: 8px;
             box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
 
@@ -213,7 +213,7 @@ const handleSubmit = async () => {
 
     .custom-bottom-fixed-btns {
         position: relative;
-        background: #fff;
+        background: #121214;
         box-shadow: 2px 2px 5px 0px rgba(0, 0, 0, 0.4);
 
         .bottom-btn {
@@ -225,8 +225,8 @@ const handleSubmit = async () => {
         }
 
         .primary-btn {
-            background: #2199f8;
-            color: #fff;
+            background: linear-gradient(90deg, #FFEBC9 0%, #FFCA6D 100%);
+            color: #000;
         }
     }
 }

+ 24 - 14
src/views/old_mini/growth_report/components/CropAlertPanel.vue

@@ -25,7 +25,7 @@
                                 v-model:visible="cropMenuVisible"
                                 placement="bottom"
                                 trigger="click"
-                                effect="light"
+                                effect="dark"
                                 :show-arrow="true"
                                 popper-class="crop-switch-tooltip"
                                 :teleported="true"
@@ -57,7 +57,7 @@
                                     </el-icon>
                                 </div>
                             </el-tooltip>
-                            <div v-if="guideVisible" class="crop-guide-bubble">
+                            <div v-if="guideVisible && !reportGenerating" class="crop-guide-bubble">
                                 <span class="crop-guide-bubble__arrow"></span>
                                 <div class="crop-guide-bubble__content">
                                     当下作物为<span class="crop-guide-bubble__crop">{{ currentCropName }}</span>,请录入拟种植的作物,之后变为对应作物的气象预警
@@ -1021,34 +1021,44 @@ defineExpose({ refreshAlerts: fetchAlertCards, refreshZones: fetchZones, reloadF
 <style lang="scss">
 .crop-switch-tooltip {
     z-index: 3000 !important;
-    padding: 8px 0 !important;
-    min-width: 120px;
-    border: none !important;
+    padding: 6px !important;
+    min-width: 128px;
+    border: 1px solid rgba(255, 255, 255, 0.12) !important;
     border-radius: 12px !important;
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12) !important;
-    background: #fff !important;
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45) !important;
+    background: #1c1c1c !important;
+    color: rgba(255, 255, 255, 0.9) !important;
 
     .el-popper__arrow::before {
-        background: #fff !important;
-        border: none !important;
+        background: #1c1c1c !important;
+        border: 1px solid rgba(255, 255, 255, 0.12) !important;
     }
 
     .crop-option-list {
         display: flex;
         flex-direction: column;
+        gap: 2px;
     }
 
     .crop-option-item {
-        padding: 10px 20px;
+        padding: 10px 14px;
+        border-radius: 8px;
         font-size: 14px;
         line-height: 20px;
-        color: #333;
+        color: rgba(255, 255, 255, 0.85);
         cursor: pointer;
+        transition: background 0.15s ease, color 0.15s ease;
+
+        &:hover,
+        &:active {
+            color: #e8c56a;
+            background: rgba(232, 197, 106, 0.12);
+        }
 
-        &:active,
         &.active {
-            color: #2199f8;
-            background: rgba(33, 153, 248, 0.06);
+            color: #e8c56a;
+            font-weight: 500;
+            background: rgba(232, 197, 106, 0.16);
         }
     }
 }

+ 93 - 11
src/views/old_mini/recordDetails/map/mapManage.js

@@ -31,6 +31,96 @@ const DEFAULT_ZONE_STYLE = {
   stroke: "#E03131",
 };
 
+/** 分区名称标签:黑金胶囊(v2:含 polygon→point geometry) */
+const ZONE_LABEL_CACHE = {};
+const ZONE_LABEL_CACHE_VER = "v2";
+const ZONE_LABEL_FONT = "13px sans-serif";
+const ZONE_LABEL_RADIUS = 20;
+
+function roundRectPath(ctx, x, y, width, height, radius) {
+  const r = Math.min(radius, width / 2, height / 2);
+  ctx.beginPath();
+  ctx.moveTo(x + r, y);
+  ctx.arcTo(x + width, y, x + width, y + height, r);
+  ctx.arcTo(x + width, y + height, x, y + height, r);
+  ctx.arcTo(x, y + height, x, y, r);
+  ctx.arcTo(x, y, x + width, y, r);
+  ctx.closePath();
+}
+
+/** Icon 只能画在 Point 上;用 extent 中心,兼容 Polygon / MultiPolygon */
+function getZoneLabelPoint(geometry) {
+  if (!geometry) return null;
+  try {
+    if (typeof geometry.getType === "function" && geometry.getType() === "Point") {
+      return geometry;
+    }
+    // Polygon 内部点更稳;其余(含 MultiPolygon)用包围盒中心,避免 API 不一致
+    if (
+      typeof geometry.getInteriorPoint === "function" &&
+      typeof geometry.getType === "function" &&
+      geometry.getType() === "Polygon"
+    ) {
+      return geometry.getInteriorPoint();
+    }
+    const extent = typeof geometry.getExtent === "function" ? geometry.getExtent() : null;
+    if (extent && extent.length >= 4) {
+      return new Point([(extent[0] + extent[2]) / 2, (extent[1] + extent[3]) / 2]);
+    }
+  } catch (e) {
+    console.warn("[MapManage] getZoneLabelPoint failed", e);
+  }
+  return null;
+}
+
+function createZoneNameLabelStyle(name) {
+  const text = String(name || "");
+  const cacheKey = `${ZONE_LABEL_CACHE_VER}:${text}`;
+  if (ZONE_LABEL_CACHE[cacheKey]) return ZONE_LABEL_CACHE[cacheKey];
+
+  const padX = 12;
+  const boxH = 28;
+  const measure = document.createElement("canvas").getContext("2d");
+  measure.font = ZONE_LABEL_FONT;
+  const textWidth = Math.ceil(measure.measureText(text).width);
+  const boxW = Math.max(textWidth + padX * 2, boxH);
+  const dpr = window.devicePixelRatio || 2;
+  const canvas = document.createElement("canvas");
+  canvas.width = Math.ceil((boxW + 4) * dpr);
+  canvas.height = Math.ceil((boxH + 4) * dpr);
+  const ctx = canvas.getContext("2d");
+  ctx.scale(dpr, dpr);
+  ctx.imageSmoothingEnabled = true;
+
+  const x = 2;
+  const y = 2;
+  roundRectPath(ctx, x, y, boxW, boxH, ZONE_LABEL_RADIUS);
+  ctx.fillStyle = "#181818";
+  ctx.fill();
+  ctx.lineWidth = 1;
+  ctx.strokeStyle = "rgba(232, 197, 106, 0.35)";
+  ctx.stroke();
+
+  ctx.font = ZONE_LABEL_FONT;
+  ctx.fillStyle = "#E8C56A";
+  ctx.textAlign = "center";
+  ctx.textBaseline = "middle";
+  ctx.fillText(text, x + boxW / 2, y + boxH / 2 - 0.5);
+
+  const style = new Style({
+    // 关键:多边形 Feature 上的 Icon 必须显式转成 Point,否则不渲染
+    geometry: (feature) => getZoneLabelPoint(feature.getGeometry()),
+    zIndex: 2,
+    image: new Icon({
+      src: canvas.toDataURL(),
+      scale: 1 / dpr,
+      anchor: [0.5, 0.5],
+    }),
+  });
+  ZONE_LABEL_CACHE[cacheKey] = style;
+  return style;
+}
+
 const VIEWPORT_INTERACTION_TYPES = [
   DragPan,
   MouseWheelZoom,
@@ -120,7 +210,7 @@ class MapManage {
     const name = String(feature.get("name") || "");
     const fill = feature.get("fill") || "rgba(76, 175, 80, 0.28)";
     const stroke = feature.get("stroke") || "#4CAF50";
-    return [
+    const styles = [
       new Style({
         fill: new Fill({ color: fill }),
         stroke: new Stroke({
@@ -128,17 +218,9 @@ class MapManage {
           width: 2,
         }),
       }),
-      new Style({
-        text: new Text({
-          text: name,
-          font: "13px sans-serif",
-          fill: new Fill({ color: "#1F1F1F" }),
-          backgroundFill: new Fill({ color: "#ffffff" }),
-          padding: [4, 8, 4, 8],
-          overflow: true,
-        }),
-      }),
     ];
+    if (name) styles.push(createZoneNameLabelStyle(name));
+    return styles;
   }
 
   createBoundaryStyle() {

+ 57 - 17
src/views/old_mini/work_detail/components/agriMaterialTable.vue

@@ -21,7 +21,12 @@
                 class="agri-material-table__col"
                 :class="`col-${col.key}`"
             >
-                {{ row[col.key] || "-" }}
+                <template v-if="col.key === 'dosage'">
+                    <span class="dosage" v-html="formatDosage(row.dosage)"></span>
+                </template>
+                <template v-else>
+                    {{ row[col.key] || "-" }}
+                </template>
             </div>
         </div>
         <div v-if="!rows.length" class="agri-material-table__empty">暂无数据</div>
@@ -42,14 +47,25 @@ defineProps({
         default: () => [],
     },
 });
+
+/** 数字加粗放大,单位保持较小字号 */
+function formatDosage(value) {
+    if (value == null || value === "") return "-";
+    const text = String(value);
+    const matched = text.match(/^([\d.]+)\s*(.*)$/);
+    if (!matched) return text;
+    const [, num, unit] = matched;
+    if (!unit) return `<span class="dosage__num">${num}</span>`;
+    return `<span class="dosage__num">${num}</span><span class="dosage__unit">${unit}</span>`;
+}
 </script>
 
 <style lang="scss" scoped>
 .agri-material-table {
-    border: 1px solid rgba(33, 153, 248, 0.35);
-    border-radius: 6px;
+    border: 1px solid rgba(255, 255, 255, 0.12);
+    border-radius: 12px;
     overflow: hidden;
-    background: #fff;
+    background: transparent;
 }
 
 .agri-material-table__header,
@@ -58,17 +74,20 @@ defineProps({
     align-items: stretch;
 }
 
+.agri-material-table__row + .agri-material-table__row {
+    border-top: 1px solid rgba(255, 255, 255, 0.06);
+}
+
 .agri-material-table__header {
-    background: rgba(33, 153, 248, 0.1);
-    color: #2199F8;
-    font-size: 13px;
-    font-weight: 500;
+    color: rgba(255, 255, 255, 0.5);
+    background: rgba(255, 255, 255, 0.05);
+    font-size: 12px;
+    font-weight: 400;
 }
 
 .agri-material-table__row {
-    color: #4F4F4F;
-    font-size: 13px;
-    border-top: 1px solid rgba(0, 0, 0, 0.06);
+    color: rgba(255, 255, 255, 0.7);
+    font-size: 12px;
 }
 
 .agri-material-table__col {
@@ -80,30 +99,51 @@ defineProps({
     box-sizing: border-box;
     word-break: break-word;
     line-height: 18px;
+    color: rgba(255, 255, 255, 0.7);
 
     &.col-effect {
-        width: 56px;
+        width: 52px;
         flex: none;
     }
     &.col-name {
-        flex: 1.1;
+        flex: 1.2;
         min-width: 0;
+        color: rgba(255, 255, 255, 1);
     }
     &.col-brand {
-        flex: 1;
+        flex: 0.85;
         min-width: 0;
     }
     &.col-dosage {
-        width: 72px;
+        width: 64px;
         flex: none;
     }
 }
 
+.dosage {
+    display: inline-flex;
+    align-items: baseline;
+    justify-content: center;
+    line-height: 1;
+
+    :deep(.dosage__num) {
+        font-size: 14px;
+        font-weight: 600;
+        color: #fff;
+    }
+
+    :deep(.dosage__unit) {
+        margin-left: 1px;
+        font-size: 11px;
+        font-weight: 600;
+        color: rgba(255, 255, 255, 0.7);
+    }
+}
+
 .agri-material-table__empty {
     padding: 16px 0;
     text-align: center;
-    color: #9C9C9C;
+    color: rgba(255, 255, 255, 0.35);
     font-size: 13px;
-    border-top: 1px solid rgba(0, 0, 0, 0.06);
 }
 </style>

Разница между файлами не показана из-за своего большого размера
+ 576 - 306
src/views/old_mini/work_detail/index.vue


+ 67 - 41
src/views/old_mini/work_execute/components/calendar.vue

@@ -1,30 +1,15 @@
 <template>
     <div class="calendar">
         <div class="header-wrap">
-            <!-- <div class="header-l">
-                <div class="top-l">
-                    <el-icon class="icon icon-l" color="#999999" size="11" @click="prevPeriod"><ArrowLeftBold /></el-icon>
-                </div>
-                <div class="top-c">
-                    <span class="header-text">
-                        {{ dateRange.start }} <span class="center-line">-</span> {{ dateRange.end }}
-                    </span>
-                </div>
-                <div class="top-r">
-                    <el-icon class="icon icon-r" color="#999999" size="11" @click="nextPeriod"><ArrowRightBold /></el-icon>
-                </div>
-            </div> -->
-            <!-- <div class="header-r">
-                <span class="line"></span>
-                高温预警
-            </div> -->
+            <div class="header-l">{{ weekTitle }}</div>
+            <div class="header-r">本周 {{ weekTodoCount }} 项待办</div>
         </div>
         <div class="days">
             <div
                 class="days-item"
                 v-for="(day, index) in calendarDays"
                 :key="index"
-                :class="[{ activeDay: activeDay === day.date, today: day.isToday && !day.solarTerm }]"
+                :class="[{ activeDay: activeDay === day.date, today: day.isToday }]"
                 @click="selectDate(day.date, day)"
             >
                 <div class="day-box">
@@ -35,13 +20,6 @@
                 <div v-if="day.solarTerm" class="solar-term">{{ day.solarTerm }}</div>
                 <div v-if="day.typeName?.lengthVal" class="type-num">{{ day.typeName.lengthVal }}</div>
             </div>
-
-            <!-- <div class="days-fixed">
-                <div>
-                    <el-icon><Calendar /></el-icon>
-                </div>
-                <div>日历</div>
-            </div> -->
         </div>
     </div>
 </template>
@@ -179,6 +157,30 @@ const dateRange = computed(() => {
     return { start, end };
 });
 
+/** 如「10月 · 第 2 周」,按窗口起始日所在月及当月周序 */
+const weekTitle = computed(() => {
+    const first = calendarDays.value[0];
+    if (!first) return "";
+    const d = new Date(`${first.date}T00:00:00`);
+    const month = d.getMonth() + 1;
+    const weekOfMonth = Math.ceil(d.getDate() / 7) || 1;
+    return `${month}月 · 第 ${weekOfMonth} 周`;
+});
+
+/** 本周窗口内待执行农事数量 */
+const weekTodoCount = computed(() => {
+    const dateSet = new Set(calendarDays.value.map((d) => d.date));
+    return taskListData.value.filter((task) => {
+        if (task.status === "completed") return false;
+        const dateStr =
+            task.executeDateFull ||
+            (task.expectedExecuteDate || task.executeDeadlineDate
+                ? formatDate(new Date(task.expectedExecuteDate || task.executeDeadlineDate))
+                : "");
+        return dateStr && dateSet.has(dateStr);
+    }).length;
+});
+
 function getAlignedStartDate(referenceDate) {
     const start = new Date(referenceDate);
     const dayOfWeek = start.getDay();
@@ -257,36 +259,58 @@ defineExpose({
 <style lang="scss" scoped>
 .calendar {
     width: 100%;
-    text-align: center;
+    padding: 14px 12px 12px;
     box-sizing: border-box;
+    border-radius: 16px;
+    background: rgba(255, 255, 255, 0.06);
+    border: 1px solid rgba(255, 255, 255, 0.06);
+    text-align: center;
 
     .header-wrap {
-        display: none;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 12px;
+        padding: 0 2px;
+
+        .header-l {
+            font-size: 14px;
+            line-height: 20px;
+            color: rgba(255, 255, 255, 0.92);
+        }
+
+        .header-r {
+            font-size: 12px;
+            line-height: 18px;
+            color: rgba(255, 255, 255, 0.5);
+        }
     }
 }
 
 .days {
     display: grid;
     grid-template-columns: repeat(7, 1fr);
+    gap: 6px;
     font-size: 12px;
     position: relative;
 
-    .days-item + .days-item {
-        margin-left: 6px;
-    }
-
     .days-item {
         cursor: pointer;
         position: relative;
 
         .day-box {
-            background: rgba(255, 255, 255, 0.06);
-            color: rgba(255, 255, 255, 0.55);
+            background: rgba(255, 255, 255, 0.05);
+            color: rgba(255, 255, 255, 0.5);
             border-radius: 10px;
-            padding: 8px 0 10px;
+            box-sizing: border-box;
+            padding: 5px 0;
+            height: 55px;
             position: relative;
             border: 1px solid transparent;
-            box-sizing: border-box;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
 
             .days-week {
                 font-size: 12px;
@@ -298,25 +322,25 @@ defineExpose({
             text-align: center;
             display: block;
             margin: 2px auto 0;
-            font-size: 15px;
+            font-size: 14px;
             line-height: 20px;
-            font-weight: 500;
-            color: rgba(255, 255, 255, 0.85);
+            color: rgba(255, 255, 255, 1);
         }
 
         .day-dot {
             display: block;
             width: 4px;
             height: 4px;
-            margin: 4px auto 0;
+            margin: 2px auto 0;
             border-radius: 50%;
-            background: rgba(255, 255, 255, 0.35);
+            background: rgba(255, 255, 255, 0.5);
         }
 
         .solar-term {
-            padding-top: 3px;
+            padding-top: 4px;
             color: rgba(255, 255, 255, 0.35);
             font-size: 11px;
+            line-height: 14px;
         }
 
         .type-num {
@@ -331,6 +355,7 @@ defineExpose({
             color: #1a1408;
             font-size: 10px;
             line-height: 16px;
+            font-weight: 600;
             background: #ffd489;
             border-radius: 5px;
         }
@@ -338,6 +363,7 @@ defineExpose({
         &.today .day-box {
             color: #ffd489;
             border-color: rgba(255, 212, 137, 0.55);
+            background: transparent;
 
             .days-one {
                 color: #ffd489;

+ 55 - 37
src/views/old_mini/work_execute/index.js

@@ -6,37 +6,25 @@ import Photo from "ol-ext/style/Photo";
 import { Icon, Stroke } from "ol/style.js";
 import { newPoint } from "@/utils/map";
 
+/** 对齐 albumPanel `.zone-marker__label` 视觉 */
 const LABEL_FONT = "12px sans-serif";
-const LABEL_PADDING = [4, 10, 4, 10];
-const LABEL_RADIUS = 18;
+const LABEL_PAD_X = 10;
+const LABEL_HEIGHT = 26;
+const LABEL_BORDER = 1;
+const LABEL_RADIUS = 25;
 const LABEL_DPR = window.devicePixelRatio || 2;
-const PHOTO_RESIZE = "?imageView2/1/w/300/interlace/1";
-const DEFAULT_PHOTO =
-  "https://birdseye-img-ali-cdn.sysuimars.com/sikong/fh_sync/71892baf-42b7-4d35-a763-2a420014d901/82d4e04b-df4e-4eef-b220-bd388f5f8299/media/71de4f44-235a-4274-ad5d-e5a0ce6f2abe/DJI_20260530085247_0222_V_code-ws0fsme6cx56.jpeg" + PHOTO_RESIZE;
-
-function getFeaturePhotoSrc(feature) {
-  // const image = feature.get("beforeImage") || feature.get("afterImage") || feature.get("coverImage");
-  // if (image?.cloud_filename) {
-  //   return config.base_img_url3 + image.cloud_filename + PHOTO_RESIZE;
-  // }
-  // const src = feature.get("mapPhotoUrl") || feature.get("coverImg") || feature.get("imgUrl");
-  // if (src) {
-  //   return src.startsWith("http") ? src : config.base_img_url3 + src + PHOTO_RESIZE;
-  // }
-  return DEFAULT_PHOTO;
-}
 
 function createPillLabel(text) {
-  const [paddingTop, paddingRight, paddingBottom, paddingLeft] = LABEL_PADDING;
   const canvas = document.createElement("canvas");
   const ctx = canvas.getContext("2d");
   const labelText = text || "";
   ctx.font = LABEL_FONT;
-  const metrics = ctx.measureText(labelText);
-  const textWidth = metrics.width;
-  const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent || 12;
-  const width = Math.ceil(textWidth + paddingLeft + paddingRight);
-  const height = Math.ceil(textHeight + paddingTop + paddingBottom);
+  const textWidth = ctx.measureText(labelText).width;
+  const innerW = Math.ceil(textWidth + LABEL_PAD_X * 2);
+  const innerH = LABEL_HEIGHT;
+  const width = innerW + LABEL_BORDER * 2;
+  const height = innerH + LABEL_BORDER * 2;
+  const radius = Math.min(LABEL_RADIUS, height / 2);
 
   canvas.width = Math.ceil(width * LABEL_DPR);
   canvas.height = Math.ceil(height * LABEL_DPR);
@@ -44,20 +32,33 @@ function createPillLabel(text) {
   ctx.imageSmoothingEnabled = true;
   ctx.imageSmoothingQuality = "high";
 
-  ctx.font = LABEL_FONT;
+  // 外圈:上浅下透明的白边(对应 label 渐变描边)
+  const borderGrad = ctx.createLinearGradient(0, 0, 0, height);
+  borderGrad.addColorStop(0, "rgba(255, 255, 255, 0.5)");
+  borderGrad.addColorStop(1, "rgba(255, 255, 255, 0)");
   ctx.beginPath();
-  ctx.roundRect(0, 0, width, height, Math.min(LABEL_RADIUS, height / 2));
-  // 深色胶囊标签,贴合页面深色主题
-  ctx.fillStyle = "rgba(12, 12, 12, 0.78)";
+  ctx.roundRect(0, 0, width, height, radius);
+  ctx.fillStyle = borderGrad;
   ctx.fill();
-  ctx.strokeStyle = "rgba(255, 255, 255, 0.12)";
-  ctx.lineWidth = 1;
-  ctx.stroke();
 
+  // 内芯:半透明深底(对应 label-text)
+  ctx.beginPath();
+  ctx.roundRect(
+    LABEL_BORDER,
+    LABEL_BORDER,
+    innerW,
+    innerH,
+    Math.max(radius - LABEL_BORDER, 0)
+  );
+  ctx.fillStyle = "#181818";
+  ctx.fill();
+
+  ctx.font = LABEL_FONT;
   ctx.fillStyle = "#FFFFFF";
   ctx.textAlign = "center";
   ctx.textBaseline = "middle";
-  ctx.fillText(labelText, Math.round(width / 2), Math.round(height / 2));
+  // 中文字体 middle 基线视觉偏下,略上移
+  ctx.fillText(labelText, Math.round(width / 2), Math.round(height / 2) - 1);
 
   return canvas.toDataURL();
 }
@@ -76,7 +77,7 @@ class IndexMap {
     this.borderStyle = new Style({
       image: new Icon({
         src: require("@/assets/img/home/garden-point.png"),
-        scale: 0.3,
+        scale: 0.5,
         onload: function () {
           that.gardenPointLayer.layer.changed();
         },
@@ -95,7 +96,7 @@ class IndexMap {
               src: createPillLabel(labelText),
               scale: 1 / LABEL_DPR,
               anchor: [0.5, 1],
-              displacement: [0, 28],
+              displacement: [0, 32],
             }),
           });
         }
@@ -105,12 +106,24 @@ class IndexMap {
     });
   }
 
-  initMap(location, target, isCapital) {
+  /**
+   * @param {string} location 初始中心点 WKT
+   * @param {HTMLElement} target 地图容器
+   * @param {boolean} isCapital 影像底图
+   * @param {{ minZoom?: number, maxZoom?: number }} [options]
+   *   全农场总览跨省时需降低 minZoom(默认 6 在 162px 高容器内放不下粤鄂跨度)
+   */
+  initMap(location, target, isCapital, options = {}) {
     let level = 16;
+    const minZoom = options.minZoom ?? 6;
+    const maxZoom = options.maxZoom ?? 22;
     let coordinate = util.wktCastGeom(location).getFirstCoordinate();
-    this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 6, 22, isCapital ? "img" : "vec");
+    this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, minZoom, maxZoom, isCapital ? "img" : "vec");
     let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
-    this.kmap.addXYZLayer(xyz2, { minZoom: 6, maxZoom: 22 }, 2);
+    this.kmap.addXYZLayer(xyz2, { minZoom, maxZoom }, 2);
+    // 图层可见级别需与地图一致,否则缩到跨省视野时点位仍被 minZoom 藏掉
+    this.gardenPointLayer.setMinZoom(minZoom);
+    this.gardenPointLayer.setMaxZoom(maxZoom);
     this.kmap.addLayer(this.gardenPointLayer.layer);
   }
 
@@ -127,7 +140,12 @@ class IndexMap {
             item.farmWorkName;
         this.gardenPointLayer.source.addFeature(newPoint(item, pointType));
       }
-      this.kmap.getView().fit(this.gardenPointLayer.source.getExtent(), { padding: [42, 20, 30, 20] });
+      const extent = this.gardenPointLayer.source.getExtent();
+      this.kmap.getView().fit(extent, {
+        padding: [36, 24, 36, 24],
+        maxZoom: 18,
+        duration: 0,
+      });
       const finalZoom = this.kmap.getView().getZoom();
       if (finalZoom > 18) {
         this.kmap.getView().setZoom(18);

+ 304 - 113
src/views/old_mini/work_execute/index.vue

@@ -1,5 +1,6 @@
 <template>
-    <div class="task-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+    <div class="task-page" v-loading="pageLoading" element-loading-background="rgba(15, 15, 15, 0.55)"
+        :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
         <farm-header class="task-page__header" />
         <div class="task-top">
             <div class="map-container" ref="mapContainer"></div>
@@ -8,13 +9,29 @@
             <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
         </div>
         <div class="task-list">
-            <div class="task-tabs">
-                <div class="tab-item" :class="{ active: activeTab === 'pending' }" @click="handleTabChange('pending')">
-                    待执行 {{ pendingCount }}
+            <div class="task-toolbar">
+                <div class="task-tabs">
+                    <div class="tab-item" :class="{ active: activeTab === 'pending' }"
+                        @click="handleTabChange('pending')">
+                        待执行 {{ pendingCount }}
+                    </div>
+                    <div class="tab-item" :class="{ active: activeTab === 'completed' }"
+                        @click="handleTabChange('completed')">
+                        已完成 {{ completedCount }}
+                    </div>
+                </div>
+                <div class="task-sort" :class="{ active: sortByUrgency }" @click="sortByUrgency = !sortByUrgency">
+                    <span class="task-sort__icon">
+                        <i /><i /><i />
+                    </span>
+                    <span>按紧急程度</span>
                 </div>
-                <div class="tab-item" :class="{ active: activeTab === 'completed' }"
-                    @click="handleTabChange('completed')">
-                    已完成 {{ completedCount }}
+            </div>
+
+            <div v-if="zoneList.length" class="zone-chips">
+                <div v-for="zone in zoneList" :key="zone.id" class="zone-chip"
+                    :class="{ active: String(activeZoneId) === String(zone.id) }" @click="handleZoneChange(zone.id)">
+                    {{ zone.name || "分区" }}
                 </div>
             </div>
             <div class="work-task-list">
@@ -33,6 +50,7 @@
                             <span class="task-card__title van-ellipsis">
                                 {{ item.executeDate + " " + item.title }}
                             </span>
+                            <span class="task-card__tag">高风险</span>
                         </div>
 
                         <div class="task-card__tags">
@@ -64,12 +82,17 @@
                         </div>
 
                         <div class="task-card__footer">
-                            <div class="footer-l">外包农事</div>
+                            <div class="footer-l">
+                                <img src="@/assets/img/farm/farm.png" alt="外包农事" class="outsource-icon">
+                                外包农事
+                            </div>
                             <div class="footer-r">
                                 <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
-                                    {{ t("workExecute.forward") }}
+                                    <Icon size="14" color="rgba(255, 255, 255, 0.6)" name="share" />
+                                    派发工人
                                 </div>
                                 <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
+                                    <img src="@/assets/img/farm/shield.png" alt="我已完成" class="complete-icon">
                                     我已完成
                                 </div>
                             </div>
@@ -105,6 +128,7 @@ import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
 import { useI18n } from "@/i18n";
 import { saveSelectedFarm } from "@/utils/selectedFarm";
 import wx from "weixin-js-sdk";
+import { Icon } from 'vant';
 
 
 // ---------- 常量 ----------
@@ -184,14 +208,27 @@ const filterDate = ref(getTodayDate()); // 当前选中日期
 const taskList = ref([]); // 已映射的农事卡片(归属接口 date)
 const mapCenterPoint = ref(null); // 接口返回的分区中心点 WKT
 const activeTab = ref("pending");
+const pageLoading = ref(false);
+const zoneList = ref([]);
+const activeZoneId = ref(null);
+/** 按紧急程度:高风险优先 */
+const sortByUrgency = ref(false);
 
 // ---------- 计算属性 ----------
 const dayTaskList = computed(() =>
-    taskList.value.filter((item) => item.executeDateFull === filterDate.value)
+    taskList.value.filter((item) => {
+        if (item.executeDateFull !== filterDate.value) return false;
+        if (activeZoneId.value == null || activeZoneId.value === "") return true;
+        return String(item.zoneId) === String(activeZoneId.value);
+    })
 );
 const pendingCount = computed(() => dayTaskList.value.filter((item) => item.status === "pending").length);
 const completedCount = computed(() => dayTaskList.value.filter((item) => item.status === "completed").length);
-const displayTaskList = computed(() => dayTaskList.value.filter((item) => item.status === activeTab.value));
+const displayTaskList = computed(() => {
+    const list = dayTaskList.value.filter((item) => item.status === activeTab.value);
+    if (!sortByUrgency.value) return list;
+    return [...list].sort((a, b) => Number(!!b.isHighRisk) - Number(!!a.isHighRisk));
+});
 
 /** 日历窗口:今天起连续 7 天 */
 function getCalendarDateRange() {
@@ -206,10 +243,10 @@ function getCalendarDateRange() {
     });
 }
 
-/** zone_id / farm_id / crop_type 取当前选中农场;trigger_date 为窗口内某一天 */
+/** zone_id 优先用分区筛选;否则退回农场缓存分区 */
 const resolveQueryParams = (triggerDate = getTodayDate()) => {
     const params = {
-        zone_id: getSelectedZoneId(),
+        zone_id: activeZoneId.value ?? getSelectedZoneId(),
         farm_id: getSelectedFarmId(),
         trigger_date: triggerDate,
     };
@@ -218,6 +255,39 @@ const resolveQueryParams = (triggerDate = getTodayDate()) => {
     return params;
 };
 
+async function fetchZoneList() {
+    const farmId = getSelectedFarmId();
+    if (farmId == null) {
+        zoneList.value = [];
+        activeZoneId.value = null;
+        return;
+    }
+    try {
+        const res = await VE_API.questionnaire.getZones({ farm_id: farmId });
+        const zones = Array.isArray(res?.data?.zones) ? res.data.zones : [];
+        zoneList.value = zones
+            .map((zone) => ({
+                id: zone.zone_id,
+                name: zone.zone_name || `分区${zone.zone_id ?? ""}`,
+            }))
+            .filter((zone) => zone.id != null && zone.id !== "");
+
+        const cachedZoneId = getSelectedZoneId();
+        const stillExists = zoneList.value.some(
+            (zone) => String(zone.id) === String(activeZoneId.value)
+        );
+        if (stillExists) return;
+        const preferred = zoneList.value.find(
+            (zone) => String(zone.id) === String(cachedZoneId)
+        );
+        activeZoneId.value = preferred?.id ?? zoneList.value[0]?.id ?? null;
+    } catch (e) {
+        console.warn("[work_execute] getZones failed", e);
+        zoneList.value = [];
+        activeZoneId.value = getSelectedZoneId();
+    }
+}
+
 const isCompletedStatus = (status) => COMPLETED_STATUS_RE.test(String(status || ""));
 
 const formatExecuteDate = (dateStr) => {
@@ -415,6 +485,18 @@ const handleTabChange = (key) => {
     activeTab.value = key;
 };
 
+const handleZoneChange = async (zoneId) => {
+    if (String(activeZoneId.value) === String(zoneId)) return;
+    activeZoneId.value = zoneId;
+    pageLoading.value = true;
+    try {
+        await fetchZoneFwTaskList();
+        nextTick(() => refreshMapMarkers());
+    } finally {
+        pageLoading.value = false;
+    }
+};
+
 /** 切日期:只过滤下方列表;地图与日历角标共用完整 7 天数据 */
 const handleDateSelect = (date) => {
     const next = date ?? getTodayDate();
@@ -526,31 +608,40 @@ const initMap = () => {
 
 /** keep-alive 下切农场 / 再次进入只走 onActivated,需在此拉列表 */
 const refreshPage = async () => {
-    await ensureSelectedFarm();
-    // 无农场时不打任务列表(弹窗由 checkFarmCalendarPopup 负责)
-    if (getSelectedFarmId() == null) {
-        taskList.value = [];
-        if (!indexMap.kmap) initMap();
-        return;
-    }
-    await fetchZoneFwTaskList();
-    if (!indexMap.kmap) {
-        initMap();
-        return;
-    }
-    if (!mapContainer.value || !indexMap.kmap.map) return;
-    const checkAndUpdateSize = () => {
-        const container = mapContainer.value;
-        if (!container) return;
-        const rect = container.getBoundingClientRect();
-        if (rect.width > 0 && rect.height > 0) {
-            indexMap.kmap.map.updateSize();
-            refreshMapMarkers();
-        } else {
-            setTimeout(checkAndUpdateSize, 100);
+    if (pageLoading.value) return;
+    pageLoading.value = true;
+    try {
+        await ensureSelectedFarm();
+        // 无农场时不打任务列表(弹窗由 checkFarmCalendarPopup 负责)
+        if (getSelectedFarmId() == null) {
+            taskList.value = [];
+            zoneList.value = [];
+            activeZoneId.value = null;
+            if (!indexMap.kmap) initMap();
+            return;
         }
-    };
-    setTimeout(checkAndUpdateSize, 200);
+        await fetchZoneList();
+        await fetchZoneFwTaskList();
+        if (!indexMap.kmap) {
+            initMap();
+            return;
+        }
+        if (!mapContainer.value || !indexMap.kmap.map) return;
+        const checkAndUpdateSize = () => {
+            const container = mapContainer.value;
+            if (!container) return;
+            const rect = container.getBoundingClientRect();
+            if (rect.width > 0 && rect.height > 0) {
+                indexMap.kmap.map.updateSize();
+                refreshMapMarkers();
+            } else {
+                setTimeout(checkAndUpdateSize, 100);
+            }
+        };
+        setTimeout(checkAndUpdateSize, 200);
+    } finally {
+        pageLoading.value = false;
+    }
 };
 
 onMounted(() => {
@@ -576,6 +667,12 @@ onDeactivated(() => {
     background: url('@/assets/img/agricultural/page-bg.png') no-repeat top center;
     background-size: 100% 250px;
 
+    ::v-deep {
+        .el-loading-spinner .path {
+            stroke: rgba(255, 212, 137, 0.8);
+        }
+    }
+
     .map-container {
         width: 100%;
         height: 162px;
@@ -596,80 +693,151 @@ onDeactivated(() => {
         padding: 0 12px 20px;
     }
 
+    .task-toolbar {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 12px;
+        margin: 4px 0 10px;
+    }
+
     .task-tabs {
         display: inline-flex;
         align-items: center;
         gap: 0;
-        margin: 4px 0 14px;
-        padding: 3px;
-        border-radius: 10px;
-        background: rgba(255, 255, 255, 0.06);
-        border: 1px solid rgba(255, 255, 255, 0.08);
+        padding: 4px;
+        border-radius: 6px;
+        background: rgba(255, 255, 255, 0.05);
 
         .tab-item {
-            min-width: 88px;
-            height: 32px;
+            min-width: 82px;
+            height: 30px;
             padding: 0 14px;
-            border-radius: 8px;
-            color: rgba(255, 255, 255, 0.45);
-            font-size: 13px;
-            line-height: 32px;
+            border-radius: 6px;
+            color: rgba(255, 255, 255, 0.5);
+            font-size: 14px;
+            line-height: 30px;
             text-align: center;
             cursor: pointer;
+            box-sizing: border-box;
 
             &.active {
                 color: #fff;
-                background: rgba(255, 255, 255, 0.12);
-                border: 1px solid rgba(255, 255, 255, 0.16);
-                font-weight: 500;
+                background: rgba(255, 255, 255, 0.1);
+                border: 1px solid rgba(255, 255, 255, 0.12);
+            }
+        }
+    }
+
+    .task-sort {
+        display: inline-flex;
+        align-items: center;
+        gap: 6px;
+        flex-shrink: 0;
+        font-size: 12px;
+        line-height: 18px;
+        color: rgba(255, 255, 255, 0.45);
+        cursor: pointer;
+
+        &.active {
+            color: #ffd489;
+        }
+
+        &__icon {
+            display: inline-flex;
+            flex-direction: column;
+            justify-content: center;
+            gap: 2px;
+            width: 12px;
+
+            i {
+                display: block;
+                height: 1.5px;
+                border-radius: 1px;
+                background: currentColor;
+
+                &:nth-child(1) {
+                    width: 100%;
+                }
+
+                &:nth-child(2) {
+                    width: 75%;
+                }
+
+                &:nth-child(3) {
+                    width: 50%;
+                }
             }
         }
     }
 
+    .zone-chips {
+        display: flex;
+        gap: 8px;
+        overflow-x: auto;
+        margin-bottom: 14px;
+        padding-bottom: 2px;
+        -webkit-overflow-scrolling: touch;
+
+        &::-webkit-scrollbar {
+            display: none;
+        }
+    }
+
+    .zone-chip {
+        flex-shrink: 0;
+        height: 28px;
+        padding: 0 12px;
+        border-radius: 4px;
+        font-size: 12px;
+        line-height: 28px;
+        color: rgba(255, 255, 255, 0.6);
+        background: rgba(255, 255, 255, 0.05);
+        backdrop-filter: blur(4px);
+        border: 1px solid transparent;
+        box-sizing: border-box;
+        cursor: pointer;
+
+        &.active {
+            background: rgba(255, 255, 255, 0.1);
+            border-color: rgba(255, 255, 255, 0.1);
+        }
+    }
+
     .work-task-list {
         display: flex;
         flex-direction: column;
-        gap: 12px;
+        gap: 16px;
     }
 
     .task-card {
         position: relative;
-        border-radius: 12px 13px 13px 12px;
-        // background: #1a1a1a;
-        // border: 1px solid rgba(255, 255, 255, 0.06);
         box-sizing: border-box;
         overflow: hidden;
         width: 100%;
-        padding-left: 1px;
-        background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 127.87%),
-linear-gradient(0deg, #FFD489, #FFD489);
-
-        .task-card__content {
-            padding: 14px 14px 12px;
-            border-right: 1px solid #1A191D;
-            // border-radius: 12px 0 0 12px;
-            border-radius: 12px 11px 11px 12px;
-            background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+        padding: 10px;
+        border-radius: 12px;
+        background: linear-gradient(180deg, #1A191D 0%, #121216 100%);
+        box-shadow: inset 1px 0 0 #FFD489;
+
+        &::after {
+            content: "";
+            position: absolute;
+            right: 0;
+            top: 0;
+            width: 110px;
+            height: 42px;
+            border-radius: 0 12px 0 0;
+            background: url('@/assets/img/agricultural/card-bg.png') no-repeat bottom left / 100% 100%;
         }
 
-        // &::before {
-        //     content: "";
-        //     position: absolute;
-        //     left: 0;
-        //     top: 0;
-        //     bottom: 0;
-        //     width: 1px;
-        //     border-radius: 0 12px 0 12px;
-        //     background: rgba(255, 212, 137, 0.75);
-        // }
-
-        &.is-weather::before {
-            background: rgba(64, 158, 255, 0.85);
+        &.is-weather {
+            box-shadow: inset 1px 0 0 #2199F8;
         }
 
-        &.is-abnormal::before,
-        &.is-risk::before {
-            background: rgba(232, 84, 84, 0.9);
+        &.is-abnormal,
+        &.is-risk {
+            box-shadow: inset 1px 0 0 #FF6A6A;
         }
 
         &__risk {
@@ -688,13 +856,25 @@ linear-gradient(0deg, #FFD489, #FFD489);
         &__header {
             display: flex;
             align-items: center;
+            justify-content: space-between;
             min-width: 0;
-            padding-right: 72px;
+
+            .task-card__tag {
+                height: 20px;
+                padding: 0 7px;
+                border: 1px solid rgba(208, 49, 46, 0.4);
+                background: #822422;
+                border-radius: 4px;
+                font-size: 12px;
+                line-height: 20px;
+                box-sizing: border-box;
+                color: #fff;
+            }
         }
 
         &__title {
             font-size: 16px;
-            font-weight: 600;
+            font-weight: 500;
             line-height: 22px;
             color: #fff;
         }
@@ -709,30 +889,30 @@ linear-gradient(0deg, #FFD489, #FFD489);
         .badge-tag {
             flex-shrink: 0;
             height: 20px;
-            padding: 0 8px;
-            border-radius: 6px;
+            padding: 0 7px;
+            border-radius: 4px;
             font-size: 12px;
-            line-height: 18px;
+            line-height: 20px;
             box-sizing: border-box;
             border: 1px solid transparent;
         }
 
         .normal-badge {
-            color: #ffd489;
-            border-color: rgba(255, 212, 137, 0.45);
-            background: transparent;
+            color: #FFD489;
+            border-color: rgba(255, 212, 137, 0.12);
+            background: rgba(255, 212, 137, 0.05);
         }
 
         .abnormal-badge {
-            color: #ff8b8b;
-            background: rgba(232, 84, 84, 0.16);
-            border-color: rgba(232, 84, 84, 0.28);
+            color: #FF6A6A;
+            background: rgba(255, 106, 106, 0.05);
+            border-color: rgba(255, 106, 106, 0.12);
         }
 
         .weather-badge {
-            color: #6eb6ff;
-            background: rgba(64, 158, 255, 0.16);
-            border-color: rgba(64, 158, 255, 0.28);
+            color: #2199F8;
+            background: rgba(33, 153, 248, 0.05);
+            border-color: rgba(33, 153, 248, 0.12);
         }
 
         .status-badge {
@@ -769,14 +949,14 @@ linear-gradient(0deg, #FFD489, #FFD489);
 
             &__label {
                 flex-shrink: 0;
-                width: 56px;
-                color: rgba(255, 255, 255, 0.4);
+                font-size: 12px;
+                color: rgba(255, 255, 255, 0.5);
             }
 
             &__value {
                 flex: 1;
                 min-width: 0;
-                color: rgba(255, 255, 255, 0.88);
+                color: rgba(255, 255, 255, 0.9);
             }
         }
 
@@ -785,13 +965,20 @@ linear-gradient(0deg, #FFD489, #FFD489);
             align-items: center;
             justify-content: space-between;
             gap: 12px;
-            margin-top: 14px;
-            padding-top: 12px;
-            border-top: 1px solid rgba(255, 255, 255, 0.08);
+            margin-top: 8px;
+            padding-top: 8px;
+            border-top: 1px solid rgba(255, 255, 255, 0.05);
 
             .footer-l {
                 font-size: 12px;
-                color: rgba(255, 255, 255, 0.4);
+                color: rgba(255, 255, 255, 0.5);
+                display: flex;
+                align-items: center;
+                gap: 6px;
+
+                .outsource-icon {
+                    width: 11px;
+                }
             }
 
             .footer-r {
@@ -802,25 +989,29 @@ linear-gradient(0deg, #FFD489, #FFD489);
         }
 
         .card-btn {
-            height: 32px;
-            border-radius: 8px;
+            height: 30px;
+            border-radius: 6px;
             text-align: center;
-            line-height: 32px;
+            line-height: 30px;
             font-size: 12px;
             box-sizing: border-box;
+            display: flex;
+            align-items: center;
+            gap: 4px;
+            padding: 0 10px;
+
+            .complete-icon {
+                width: 14px;
+            }
 
             &--ghost {
-                padding: 0 12px;
-                color: #fff;
-                background: rgba(255, 255, 255, 0.08);
-                border: 1px solid rgba(255, 255, 255, 0.12);
+                color: rgba(255, 255, 255, 0.6);
+                background: rgba(255, 255, 255, 0.05);
             }
 
             &--primary {
-                padding: 0 14px;
-                color: #1a1408;
-                background: linear-gradient(135deg, #ffebc9 0%, #ffca6d 100%);
-                border: none;
+                color: #FFD489;
+                background: rgba(255, 212, 137, 0.1);
             }
         }
 

Некоторые файлы не были показаны из-за большого количества измененных файлов