wangsisi 10 часов назад
Родитель
Сommit
297a5a6fe1

+ 494 - 0
src/components/popup/farmInfoPopup copy.vue

@@ -0,0 +1,494 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        round
+        class="farm-info-popup"
+        :close-on-click-overlay="true"
+        teleport="body"
+    >
+        <!-- 第一步:农场信息填写 -->
+        <template v-if="currentStep === 1">
+            <!-- 标题 -->
+            <div class="popup-title">{{ $t('完善以下信息,生成智能种植方案') }}</div>
+
+            <!-- 表单区域 -->
+            <el-form ref="formRef" :model="formData" :rules="rules" class="farm-form">
+                <!-- 农场位置 -->
+                <el-form-item :label="$t('农场位置')" prop="address">
+                    <el-input v-model="formData.address" :placeholder="$t('请输入农场位置')" clearable />
+                </el-form-item>
+
+                <!-- 农场品种 -->
+                <el-form-item :label="$t('农场品种')" prop="speciesId">
+                    <el-select
+                        v-model="formData.speciesId"
+                        :placeholder="$t('请选择')"
+                        @change="handleSpecieChange"
+                    >
+                        <el-option
+                            v-for="item in specieList"
+                            :key="item.id"
+                            :label="item.name"
+                            :value="item.id"
+                        />
+                    </el-select>
+                </el-form-item>
+
+                <!-- 农场品类 -->
+                <el-form-item :label="$t('农场品类')" prop="typeIds">
+                    <el-select
+                        v-model="formData.typeIds"
+                        multiple
+                        collapse-tags
+                        :placeholder="$t('请选择')"
+                        :disabled="!formData.speciesId"
+                        @change="handleFruitsChange"
+                    >
+                        <el-option
+                            v-for="item in fruitsList"
+                            :key="item.id"
+                            :label="item.name"
+                            :value="item.id"
+                        />
+                    </el-select>
+                </el-form-item>
+
+                <!-- 农场亩数 -->
+                <el-form-item :label="$t('农场亩数')" prop="mu">
+                    <el-input v-model="formData.mu" :placeholder="$t('请输入')" type="number">
+                        <template #suffix>
+                            <span class="unit">{{ $t('亩') }}</span>
+                        </template>
+                    </el-input>
+                </el-form-item>
+
+                <!-- 农场名称 -->
+                <el-form-item :label="$t('农场名称')" prop="name">
+                    <el-input
+                        v-model="formData.name"
+                        :placeholder="$t('请输入农场名称')"
+                        clearable
+                        @input="handleNameInput"
+                    />
+                </el-form-item>
+            </el-form>
+        </template>
+
+        <!-- 第二步:物候期起始时间填写 -->
+        <template v-else>
+            <!-- 提示文字 -->
+            <div class="popup-title">{{ $t('为了精准匹配种植方案,完善物候信息') }}</div>
+
+            <!-- 物候期表单 -->
+            <el-form ref="phenologyFormRef" :model="phenologyData" label-width="92px" :rules="phenologyRules" class="farm-form">
+                <!-- 物候期选择器 -->
+                <el-form-item
+                    :label="$t('当下物候期')"
+                    prop="phenologyId"
+                >
+                    <el-select
+                        v-model="phenologyData.phenologyId"
+                        :placeholder="$t('选择物候期')"
+                        style="width: 100%"
+                        @change="handlePhenologyChange"
+                    >
+                        <el-option
+                            v-for="phenology in phenologyList"
+                            :key="phenology.phenologyId"
+                            :label="phenology.phenologyName"
+                            :value="phenology.phenologyId"
+                        />
+                    </el-select>
+                </el-form-item>
+
+                <!-- 日期选择器 -->
+                <el-form-item
+                    :label="$t('起始时间')"
+                    prop="phenologyStartDate"
+                >
+                    <el-date-picker
+                        v-model="phenologyData.phenologyStartDate"
+                        type="date"
+                        :placeholder="$t('选择时间')"
+                        format="YYYY-MM-DD"
+                        value-format="YYYY-MM-DD"
+                        style="width: 100%"
+                    />
+                </el-form-item>
+            </el-form>
+        </template>
+
+        <!-- 确认按钮 -->
+        <div class="btn-confirm" @click="handleConfirm">{{ $t('确认信息') }}</div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from "vant";
+import { computed, ref, watch, nextTick ,onMounted} from "vue";
+import { ElMessage } from "element-plus";
+import { convertPointToArray } from "@/utils/index";
+import { useStore } from "vuex";
+
+const store = useStore();
+
+const props = defineProps({
+    // 控制弹窗显示/隐藏
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    // 是否在点击遮罩层后关闭弹窗
+    closeOnClickOverlay: {
+        type: Boolean,
+        default: false,
+    },
+    // 初始表单数据
+    initialData: {
+        type: Object,
+        default: () => ({}),
+    },
+    expertMiniUserId: {
+        type: [String, Number],
+        default: "",
+    },
+    oldUser: {
+        type: Boolean,
+        default: false,
+    },
+});
+
+const city = ref("");
+const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
+function getLocationName(location) {
+    const params = {
+        key: MAP_KEY,
+        location,
+    };
+    VE_API.old_mini_map.location(params).then(({ result }) => {
+        const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
+        formData.value.address = add;
+        city.value = result.address_component?.city + result.address_component?.district || "";
+    });
+}
+
+onMounted(() => {
+    const arr = convertPointToArray(store.state.home.miniUserLocationPoint);
+    getLocationName(`${arr[1]},${arr[0]}`);
+    getSpecieList();
+});
+
+function getSpecieList() {
+    return VE_API.farm.fetchSpecieList({ point: store.state.home.miniUserLocationPoint }).then(({ data }) => {
+        specieList.value = data || [];
+        return data;
+    });
+}
+
+function getFruitsList(parentId) {
+    VE_API.farm.fruitsTypeItemList({ parentId }).then(({ data }) => {
+        fruitsList.value = data;
+    });
+}
+
+const emit = defineEmits(["update:show"]);
+
+// 处理v-model双向绑定
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit("update:show", value),
+});
+
+const formRef = ref(null);
+const phenologyFormRef = ref(null);
+
+// 当前步骤:1-填写农场信息,2-填写物候期时间
+const currentStep = ref(1);
+
+// 用户是否手动修改过农场名称
+const isNameEdited = ref(false);
+
+// 表单数据
+const formData = ref({
+    address: "",
+    speciesId: "",
+    typeIds: [],
+    mu: "",
+    name: "",
+});
+
+// 物候期列表
+const phenologyList = ref([]);
+
+// 物候期表单数据
+const phenologyData = ref({});
+
+const specieList = ref([]);
+const fruitsList = ref([]);
+
+// 规范化多选品种 typeId(支持字符串/数组),确保最终一定是数组
+const normalizeTypeId = (value) => {
+    if (Array.isArray(value)) return value;
+    if (value === null || value === undefined || value === "") return [];
+    return [value];
+};
+
+// 自定义验证器:验证农场品种(只校验 speciesId)
+const validateSpeciesId = (rule, value, callback) => {
+    if (!value) {
+        callback(new Error("请选择农场品种"));
+    } else {
+        callback();
+    }
+};
+
+// 自定义验证器:验证农场品类(只校验 typeIds)
+const validateTypeIds = (rule, value, callback) => {
+    const hasType =
+        Array.isArray(value)
+            ? value.length > 0
+            : !!value;
+    if (!hasType) {
+        callback(new Error("请选择农场品类"));
+    } else {
+        callback();
+    }
+};
+
+// 表单验证规则
+const rules = {
+    address: [{ required: true, message: "请输入农场位置", trigger: "blur" }],
+    speciesId: [{ required: true, validator: validateSpeciesId, trigger: "change" }],
+    typeIds: [{ required: true, validator: validateTypeIds, trigger: "change" }],
+    mu: [
+        { required: true, message: "请输入农场亩数", trigger: "blur" },
+        { pattern: /^\d+(\.\d+)?$/, message: "请输入有效的数字", trigger: "blur" },
+    ],
+    name: [{ required: true, message: "请输入农场名称", trigger: "blur" }],
+};
+
+// 物候期表单验证规则
+const phenologyRules = {
+    phenologyId: [{ required: true, message: "请选择物候期", trigger: "change" }],
+    phenologyStartDate: [{ required: true, message: "请选择起始时间", trigger: "change" }],
+};
+
+// 监听初始数据变化
+watch(
+    () => props.initialData,
+    (newData) => {
+        if (newData && Object.keys(newData).length > 0) {
+            Object.assign(formData.value, newData);
+            formData.value.typeIds = normalizeTypeId(formData.value.typeIds);
+        }
+    },
+    { immediate: true, deep: true }
+);
+
+// 无论选择几个品类,始终保证 formData.typeIds 为数组
+watch(
+    () => formData.value.typeIds,
+    (newVal) => {
+        // 仅在不是数组时进行规范化,避免死循环
+        if (!Array.isArray(newVal)) {
+            formData.value.typeIds = normalizeTypeId(newVal);
+        }
+    }
+);
+
+// 监听弹窗显示状态,重置表单
+watch(
+    () => props.show,
+    (newVal) => {
+        if (newVal) {
+            // 重置物候期列表和表单数据
+            phenologyList.value = [];
+            phenologyData.value = {};
+            // 重置名称编辑状态
+            isNameEdited.value = false;
+            // 弹窗打开时,如果有初始数据则使用,否则使用默认值
+            if (props.initialData && Object.keys(props.initialData).length > 0) {
+                Object.assign(formData.value, props.initialData);
+                formData.value.typeIds = normalizeTypeId(formData.value.typeIds);
+            } else {
+                formData.value = {
+                    address: "",
+                    speciesId: "",
+                    typeIds: [],
+                    mu: "",
+                    name: "",
+                };
+            }
+            // 清除验证状态
+            nextTick(() => {
+                formRef.value?.clearValidate();
+                phenologyFormRef.value?.clearValidate();
+            });
+
+            if(props.oldUser){
+                currentStep.value = 2;
+                getCurrentAndNextPhenology()
+            }else{
+                currentStep.value = 1;
+            }
+        }
+    }
+);
+
+// 品种1变化时,重置品种2并触发验证
+const handleSpecieChange = (val) => {
+    formData.value.typeIds = [];
+    const specie = specieList.value.find(item => item.id === val);
+    // 只有在用户没有手动修改名称时,才自动带出默认名称
+    if (specie && !isNameEdited.value) {
+        formData.value.name = city.value + specie.name + "农场";
+    }
+    getFruitsList(val);
+    // 触发品种验证
+    nextTick(() => {
+        formRef.value?.validateField("speciesId");
+    });
+};
+
+// 品种2变化时,触发验证
+const handleFruitsChange = () => {
+    nextTick(() => {
+        // 只校验当前字段(农场品类)
+        formRef.value?.validateField("typeIds");
+    });
+};
+
+// 农场名称输入时,标记为用户已手动修改
+const handleNameInput = () => {
+    isNameEdited.value = true;
+};
+
+// 确认信息
+const handleConfirm = async () => {
+    // 第一步:验证农场信息表单
+    if (currentStep.value === 1) {
+        if (!formRef.value) return;
+        try {
+            await formRef.value.validate();
+            
+            // 验证通过,获取物候期数据并切换到第二步
+            await getCurrentAndNextPhenology();
+            currentStep.value = 2;
+        } catch (error) {
+            console.log("表单验证失败", error);
+        }
+    } 
+    // 第二步:验证物候期表单并关闭弹窗
+    else {
+        if (!phenologyFormRef.value) return;
+        try {
+            await phenologyFormRef.value.validate();
+            // 验证通过,提交所有数据并关闭弹窗
+            const params = {
+                ...formData.value,
+                ...phenologyData.value,
+                wkt: store.state.home.miniUserLocationPoint,
+                expertMiniUserId: props.expertMiniUserId,
+                containerId: specieList.value.find(item => item.id === formData.value.speciesId)?.defaultContainerId,
+            }
+            if(!props.oldUser){
+                const { code, msg } = await VE_API.farm.saveFarm(params);
+                if (code === 0) {
+                    ElMessage.success("农场信息确认成功");
+                    emit("update:show", false);
+                } else {
+                    ElMessage.error(msg || '农场信息确认失败');
+                }
+            }else{
+                ElMessage.success("农场信息确认成功");
+                emit("update:show", false);
+            }
+        } catch (error) {
+            console.log("物候期表单验证失败", error);
+        }
+    }
+};
+
+// 获取当前日期(YYYY-MM-DD格式)
+const getTodayDate = () => {
+    const today = new Date();
+    const year = today.getFullYear();
+    const month = String(today.getMonth() + 1).padStart(2, "0");
+    const day = String(today.getDate()).padStart(2, "0");
+    return `${year}-${month}-${day}`;
+};
+
+// 物候期变化时,更新日期为对应物候期的 startDate
+const handlePhenologyChange = (phenologyId) => {
+    const selectedPhenology = phenologyList.value.find(item => item.phenologyId === phenologyId);
+    if (selectedPhenology && selectedPhenology.startDate) {
+        phenologyData.value.phenologyStartDate = selectedPhenology.startDate;
+    }
+};
+
+// 获取当前和下一个物候期
+const getCurrentAndNextPhenology = async () => {
+    try {
+        const { data } = await VE_API.home.getCurrentAndNextPhenology({ 
+            expertMiniUserId: props.expertMiniUserId,
+            containerId: specieList.value.find(item => item.id === formData.value.speciesId)?.defaultContainerId || '26',
+        });
+        if (data && Array.isArray(data)) {
+            phenologyList.value = data;
+            // 初始化物候期表单数据,日期使用第一个物候期的 startDate
+            const firstPhenology = data[0];
+            phenologyData.value = {
+                phenologyId: firstPhenology?.phenologyId || "",
+                phenologyStartDate: firstPhenology?.startDate || getTodayDate(),
+            };
+        }
+    } catch (error) {
+        console.error("获取物候期数据失败", error);
+        ElMessage.error("获取物候期数据失败");
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.farm-info-popup {
+    width: 100%;
+    padding: 20px 16px;
+    border-radius: 12px;
+
+    .popup-title {
+        font-size: 16px;
+        color: #121212;
+    }
+
+    .farm-form {
+        margin: 16px 0;
+        background: rgba(33, 153, 248, 0.05);
+        padding: 10px;
+        border-radius: 5px;
+        border: 1px solid rgba(33, 153, 248, 0.2);
+        :deep(.el-form-item__label) {
+            color: #1d2129;
+        }
+        .variety-select-wrap {
+            display: flex;
+            gap: 10px;
+            width: 100%;
+        }
+
+        .unit {
+            color: #666;
+            font-size: 14px;
+            padding-right: 8px;
+        }
+    }
+
+    .btn-confirm {
+        padding: 10px;
+        background: #2199f8;
+        color: #ffffff;
+        border-radius: 4px;
+        font-size: 16px;
+        text-align: center;
+    }
+}
+</style>

+ 277 - 443
src/components/popup/farmInfoPopup.vue

@@ -1,494 +1,328 @@
 <template>
-    <popup
-        v-model:show="showValue"
-        round
-        class="farm-info-popup"
-        :close-on-click-overlay="true"
-        teleport="body"
-    >
-        <!-- 第一步:农场信息填写 -->
-        <template v-if="currentStep === 1">
-            <!-- 标题 -->
-            <div class="popup-title">{{ $t('完善以下信息,生成智能种植方案') }}</div>
-
-            <!-- 表单区域 -->
-            <el-form ref="formRef" :model="formData" :rules="rules" class="farm-form">
-                <!-- 农场位置 -->
-                <el-form-item :label="$t('农场位置')" prop="address">
-                    <el-input v-model="formData.address" :placeholder="$t('请输入农场位置')" clearable />
-                </el-form-item>
-
-                <!-- 农场品种 -->
-                <el-form-item :label="$t('农场品种')" prop="speciesId">
-                    <el-select
-                        v-model="formData.speciesId"
-                        :placeholder="$t('请选择')"
-                        @change="handleSpecieChange"
-                    >
-                        <el-option
-                            v-for="item in specieList"
-                            :key="item.id"
-                            :label="item.name"
-                            :value="item.id"
-                        />
-                    </el-select>
-                </el-form-item>
-
-                <!-- 农场品类 -->
-                <el-form-item :label="$t('农场品类')" prop="typeIds">
-                    <el-select
-                        v-model="formData.typeIds"
-                        multiple
-                        collapse-tags
-                        :placeholder="$t('请选择')"
-                        :disabled="!formData.speciesId"
-                        @change="handleFruitsChange"
-                    >
-                        <el-option
-                            v-for="item in fruitsList"
-                            :key="item.id"
-                            :label="item.name"
-                            :value="item.id"
-                        />
-                    </el-select>
-                </el-form-item>
-
-                <!-- 农场亩数 -->
-                <el-form-item :label="$t('农场亩数')" prop="mu">
-                    <el-input v-model="formData.mu" :placeholder="$t('请输入')" type="number">
-                        <template #suffix>
-                            <span class="unit">{{ $t('亩') }}</span>
+    <Popup v-model:show="show" :overlay-style="{ zIndex: 9999 }" teleport="body" class="farm-info-popup" closeable>
+        <div class="popup-content-box">
+            <div class="popup-title">{{ $t('基本信息') }}</div>
+            <div class="popup-content">
+                <!-- <div class="map-box">
+                    <div class="map" ref="mapContainer"></div>
+                </div> -->
+                <cell-group inset class="cell-group">
+                    <field v-model="farmInfo.subjectName" :label="$t('农场名称')" />
+                    <field readonly :label="$t('农场面积')">
+                        <template #input>
+                            <span>{{ farmInfo.farmArea }}亩</span>
                         </template>
-                    </el-input>
-                </el-form-item>
-
-                <!-- 农场名称 -->
-                <el-form-item :label="$t('农场名称')" prop="name">
-                    <el-input
-                        v-model="formData.name"
-                        :placeholder="$t('请输入农场名称')"
-                        clearable
-                        @input="handleNameInput"
-                    />
-                </el-form-item>
-            </el-form>
-        </template>
-
-        <!-- 第二步:物候期起始时间填写 -->
-        <template v-else>
-            <!-- 提示文字 -->
-            <div class="popup-title">{{ $t('为了精准匹配种植方案,完善物候信息') }}</div>
-
-            <!-- 物候期表单 -->
-            <el-form ref="phenologyFormRef" :model="phenologyData" label-width="92px" :rules="phenologyRules" class="farm-form">
-                <!-- 物候期选择器 -->
-                <el-form-item
-                    :label="$t('当下物候期')"
-                    prop="phenologyId"
-                >
-                    <el-select
-                        v-model="phenologyData.phenologyId"
-                        :placeholder="$t('选择物候期')"
-                        style="width: 100%"
-                        @change="handlePhenologyChange"
-                    >
-                        <el-option
-                            v-for="phenology in phenologyList"
-                            :key="phenology.phenologyId"
-                            :label="phenology.phenologyName"
-                            :value="phenology.phenologyId"
-                        />
-                    </el-select>
-                </el-form-item>
-
-                <!-- 日期选择器 -->
-                <el-form-item
-                    :label="$t('起始时间')"
-                    prop="phenologyStartDate"
-                >
-                    <el-date-picker
-                        v-model="phenologyData.phenologyStartDate"
-                        type="date"
-                        :placeholder="$t('选择时间')"
-                        format="YYYY-MM-DD"
-                        value-format="YYYY-MM-DD"
-                        style="width: 100%"
+                    </field>
+                    <field readonly :label="$t('种植作物')" class="crop-field">
+                        <template #input>
+                            <template v-if="farmInfo.regionList && farmInfo.regionList.length">
+                                <template v-for="(item, index) in farmInfo.regionList" :key="index">
+                                    <span>{{ item.regionName }}</span>
+                                    <span v-if="index !== farmInfo.regionList.length - 1">、</span>
+                                </template>
+                            </template>
+                            <template v-else>
+                                <span>-</span>
+                            </template>
+                        </template>
+                    </field>
+                    <field v-model="farmInfo.contactName" :label="$t('联系人')" />
+                    <field
+                        ref="contactPhoneFieldRef"
+                        v-model="farmInfo.contactPhone"
+                        :label="$t('联系电话')"
+                        type="tel"
+                        maxlength="11"
+                        :rules="contactPhoneRules"
                     />
-                </el-form-item>
-            </el-form>
-        </template>
-
-        <!-- 确认按钮 -->
-        <div class="btn-confirm" @click="handleConfirm">{{ $t('确认信息') }}</div>
-    </popup>
+                    <field class="address-field" v-model="farmInfo.farmAddress" readonly :label="$t('农场位置')" />
+                    <!-- <field :label="$t('基地类别')" class="base-type-field">
+                        <template #input>
+                            <el-select
+                                v-model="farmInfo.baseType"
+                                :placeholder="$t('请选择')"
+                                class="base-type-select"
+                                autocomplete="off"
+                                teleported
+                                popper-class="farm-info-base-type-popper"
+                            >
+                                <el-option
+                                    v-for="opt in baseTypeOptions"
+                                    :key="opt.id"
+                                    :label="opt.name"
+                                    :value="opt.id"
+                                />
+                            </el-select>
+                        </template>
+                    </field> -->
+                </cell-group>
+            </div>
+            <div class="popup-footer">
+                <div class="footer-btn no-btn" @click="handleCancel">{{ $t('取消') }}</div>
+                <div class="footer-btn yes-btn" @click="handleEdit">{{ $t('确认修改') }}</div>
+            </div>
+        </div>
+    </Popup>
 </template>
 
 <script setup>
-import { Popup } from "vant";
-import { computed, ref, watch, nextTick ,onMounted} from "vue";
-import { ElMessage } from "element-plus";
-import { convertPointToArray } from "@/utils/index";
+import { Popup, Field, CellGroup, Checkbox } from "vant";
+import { ref, nextTick } from "vue";
+import { useRouter } from "vue-router";
+// import IndexMap from "../map/index.js";
 import { useStore } from "vuex";
+import { ElMessage } from "element-plus";
 
-const store = useStore();
-
-const props = defineProps({
-    // 控制弹窗显示/隐藏
-    show: {
-        type: Boolean,
-        default: false,
-    },
-    // 是否在点击遮罩层后关闭弹窗
-    closeOnClickOverlay: {
-        type: Boolean,
-        default: false,
-    },
-    // 初始表单数据
-    initialData: {
-        type: Object,
-        default: () => ({}),
-    },
-    expertMiniUserId: {
-        type: [String, Number],
-        default: "",
+/** 与 create_farm 联系电话规则一致 */
+const contactPhoneRules = [
+    { required: true, message: "请输入联系电话" },
+    {
+        pattern: /^1[3-9]\d{9}$/,
+        message: "请输入正确的手机号码",
     },
-    oldUser: {
-        type: Boolean,
-        default: false,
-    },
-});
-
-const city = ref("");
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
-function getLocationName(location) {
-    const params = {
-        key: MAP_KEY,
-        location,
-    };
-    VE_API.old_mini_map.location(params).then(({ result }) => {
-        const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
-        formData.value.address = add;
-        city.value = result.address_component?.city + result.address_component?.district || "";
-    });
-}
+];
 
-onMounted(() => {
-    const arr = convertPointToArray(store.state.home.miniUserLocationPoint);
-    getLocationName(`${arr[1]},${arr[0]}`);
-    getSpecieList();
-});
-
-function getSpecieList() {
-    return VE_API.farm.fetchSpecieList({ point: store.state.home.miniUserLocationPoint }).then(({ data }) => {
-        specieList.value = data || [];
+const baseTypeOptions = ref([]);
+function getBaseTypeList() {
+    return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
+        baseTypeOptions.value = data || [];
         return data;
     });
 }
 
-function getFruitsList(parentId) {
-    VE_API.farm.fruitsTypeItemList({ parentId }).then(({ data }) => {
-        fruitsList.value = data;
-    });
-}
-
-const emit = defineEmits(["update:show"]);
-
-// 处理v-model双向绑定
-const showValue = computed({
-    get: () => props.show,
-    set: (value) => emit("update:show", value),
+const props = defineProps({
+    farmId: {
+        type: [Number, String],
+        default: null,
+    },
 });
 
-const formRef = ref(null);
-const phenologyFormRef = ref(null);
-
-// 当前步骤:1-填写农场信息,2-填写物候期时间
-const currentStep = ref(1);
-
-// 用户是否手动修改过农场名称
-const isNameEdited = ref(false);
+const store = useStore();
+const router = useRouter();
+const show = ref(false);
+const mapContainer = ref(null);
+const contactPhoneFieldRef = ref(null);
+// const indexMap = new IndexMap();
+
+// 农场信息
+const farmInfo = ref();
+const handleShow = () => {
+    VE_API.basic_farm.fetchFarmSubjectDetail({ subjectId: props.farmId }).then((res) => {
+        if (res.code === 0) {
+            farmInfo.value = res.data;
+            getBaseTypeList();
+            show.value = true;
+            // nextTick(() => {
+            //     const point = farmInfo.value.farmLocation;
+            //     let geometryArr = [];
+
+            //     if (Array.isArray(farmInfo.value.regionList) && farmInfo.value.regionList.length > 0) {
+            //         farmInfo.value.regionList.forEach((item) => {
+            //             if (item?.geom) {
+            //                 geometryArr.push(item?.geom)
+            //             }
+            //         });
+            //     }
+
+            //     console.log('geometryArr', geometryArr);
+            //     // 如果地图已经初始化,则更新中心点和地块;否则初始化地图
+            //     if (indexMap.kmap) {
+            //         indexMap.updateCenter(point);
+            //         // 如果有地块数据,则添加地块
+            //         if (geometryArr && geometryArr.length > 0) {
+            //             indexMap.setAreaGeometry([geometryArr]);
+            //         }
+            //     } else {
+            //         indexMap.initMap(point, mapContainer.value);
+            //         // 初始化地图后,如果有地块数据,则添加地块
+            //         if (geometryArr && geometryArr.length > 0) {
+            //             indexMap.setAreaGeometry([geometryArr]);
+            //         }
+            //     }
+            // });
+        }
+    });
+};
 
-// 表单数据
-const formData = ref({
-    address: "",
-    speciesId: "",
-    typeIds: [],
-    mu: "",
-    name: "",
-});
+const emit = defineEmits(['success']);
 
-// 物候期列表
-const phenologyList = ref([]);
+const handleEdit = async () => {
+    const phoneErr = await contactPhoneFieldRef.value?.validate();
+    if (phoneErr?.message) {
+        return;
+    }
+    const paramsData = {
+        id: props.farmId,
+        name: farmInfo.value.subjectName,
+        fzr: farmInfo.value.contactName,
+        tel: farmInfo.value.contactPhone,
+        baseType: farmInfo.value.baseType,
+        expertMiniUserId: 81881,
+    }
+    VE_API.basic_farm.saveBasicFarmInfoByExpertV3(paramsData).then((res) => {
+        if (res.code === 0) {
+            ElMessage.success('修改成功');
+            show.value = false;
+            emit('success', {
+                id: props.farmId,
+                name: farmInfo.value.subjectName,
+            });
+        }
+    });
+    // setTimeout(() => {
+    //     show.value = false;
+    // }, 150);
+
+    // // 在跳转前,将当前农场的地块数据存储到store中
+    // if (farmInfo.value.geomWkt) {
+    //     const polygonData = {
+    //         geometryArr: [farmInfo.value.geomWkt],
+    //         mianji: farmInfo.value.mianji,
+    //         isConfirmed: true, // 编辑模式下标记为已确认
+    //     };
+    //     store.commit("home/SET_FARM_POLYGON", polygonData);
+    // }
+
+    // // 将农场数据存储到store中,供编辑页面使用
+    // store.commit("home/SET_EDIT_FARM_DATA", farmInfo.value);
+
+    // const from = props.showEditBtn ? "details" : "monitor";
+    // router.push(`/create_farm?type=edit&farmId=${props.farmId}&from=${from}`);
+};
 
-// 物候期表单数据
-const phenologyData = ref({});
+const handleCancel = () => {
+    show.value = false;
+};
 
-const specieList = ref([]);
-const fruitsList = ref([]);
+defineExpose({ handleShow });
+</script>
 
-// 规范化多选品种 typeId(支持字符串/数组),确保最终一定是数组
-const normalizeTypeId = (value) => {
-    if (Array.isArray(value)) return value;
-    if (value === null || value === undefined || value === "") return [];
-    return [value];
-};
+<style lang="scss" scoped>
+.farm-info-popup {
+    width: 100%;
+    border-radius: 8px;
+    z-index: 9999 !important;
+    overflow: hidden;
 
-// 自定义验证器:验证农场品种(只校验 speciesId)
-const validateSpeciesId = (rule, value, callback) => {
-    if (!value) {
-        callback(new Error("请选择农场品种"));
-    } else {
-        callback();
+    .popup-content-box {
+        background: url("@/assets/img/home/popup-mask.png") no-repeat center left / 100% 100%;
+        padding: 20px;
     }
-};
 
-// 自定义验证器:验证农场品类(只校验 typeIds)
-const validateTypeIds = (rule, value, callback) => {
-    const hasType =
-        Array.isArray(value)
-            ? value.length > 0
-            : !!value;
-    if (!hasType) {
-        callback(new Error("请选择农场品类"));
-    } else {
-        callback();
+    ::v-deep {
+        .van-popup__close-icon {
+            color: #000;
+        }
     }
-};
 
-// 表单验证规则
-const rules = {
-    address: [{ required: true, message: "请输入农场位置", trigger: "blur" }],
-    speciesId: [{ required: true, validator: validateSpeciesId, trigger: "change" }],
-    typeIds: [{ required: true, validator: validateTypeIds, trigger: "change" }],
-    mu: [
-        { required: true, message: "请输入农场亩数", trigger: "blur" },
-        { pattern: /^\d+(\.\d+)?$/, message: "请输入有效的数字", trigger: "blur" },
-    ],
-    name: [{ required: true, message: "请输入农场名称", trigger: "blur" }],
-};
+    .popup-title {
+        text-align: center;
+        font-size: 24px;
+        font-family: "PangMenZhengDao";
+    }
 
-// 物候期表单验证规则
-const phenologyRules = {
-    phenologyId: [{ required: true, message: "请选择物候期", trigger: "change" }],
-    phenologyStartDate: [{ required: true, message: "请选择起始时间", trigger: "change" }],
-};
+    .popup-content {
+        margin: 12px 0;
 
-// 监听初始数据变化
-watch(
-    () => props.initialData,
-    (newData) => {
-        if (newData && Object.keys(newData).length > 0) {
-            Object.assign(formData.value, newData);
-            formData.value.typeIds = normalizeTypeId(formData.value.typeIds);
-        }
-    },
-    { immediate: true, deep: true }
-);
-
-// 无论选择几个品类,始终保证 formData.typeIds 为数组
-watch(
-    () => formData.value.typeIds,
-    (newVal) => {
-        // 仅在不是数组时进行规范化,避免死循环
-        if (!Array.isArray(newVal)) {
-            formData.value.typeIds = normalizeTypeId(newVal);
-        }
-    }
-);
-
-// 监听弹窗显示状态,重置表单
-watch(
-    () => props.show,
-    (newVal) => {
-        if (newVal) {
-            // 重置物候期列表和表单数据
-            phenologyList.value = [];
-            phenologyData.value = {};
-            // 重置名称编辑状态
-            isNameEdited.value = false;
-            // 弹窗打开时,如果有初始数据则使用,否则使用默认值
-            if (props.initialData && Object.keys(props.initialData).length > 0) {
-                Object.assign(formData.value, props.initialData);
-                formData.value.typeIds = normalizeTypeId(formData.value.typeIds);
-            } else {
-                formData.value = {
-                    address: "",
-                    speciesId: "",
-                    typeIds: [],
-                    mu: "",
-                    name: "",
-                };
+        .map-box {
+            width: 100%;
+            height: 150px;
+            position: relative;
+
+            .map {
+                width: 100%;
+                height: 100%;
+                clip-path: inset(0px round 5px);
+                pointer-events: none;
             }
-            // 清除验证状态
-            nextTick(() => {
-                formRef.value?.clearValidate();
-                phenologyFormRef.value?.clearValidate();
-            });
 
-            if(props.oldUser){
-                currentStep.value = 2;
-                getCurrentAndNextPhenology()
-            }else{
-                currentStep.value = 1;
+            .map-text {
+                position: absolute;
+                right: 6px;
+                bottom: 8px;
+                font-size: 12px;
+                color: #ffffff;
+                padding: 8px 12px;
+                border-radius: 20px;
+                background: rgba(0, 0, 0, 0.5);
+                border: 1px solid rgba(255, 255, 255, 0.5);
             }
         }
-    }
-);
-
-// 品种1变化时,重置品种2并触发验证
-const handleSpecieChange = (val) => {
-    formData.value.typeIds = [];
-    const specie = specieList.value.find(item => item.id === val);
-    // 只有在用户没有手动修改名称时,才自动带出默认名称
-    if (specie && !isNameEdited.value) {
-        formData.value.name = city.value + specie.name + "农场";
-    }
-    getFruitsList(val);
-    // 触发品种验证
-    nextTick(() => {
-        formRef.value?.validateField("speciesId");
-    });
-};
 
-// 品种2变化时,触发验证
-const handleFruitsChange = () => {
-    nextTick(() => {
-        // 只校验当前字段(农场品类)
-        formRef.value?.validateField("typeIds");
-    });
-};
+        .cell-group {
+            margin: 12px 0 0;
 
-// 农场名称输入时,标记为用户已手动修改
-const handleNameInput = () => {
-    isNameEdited.value = true;
-};
+            .crop-field {
+                ::v-deep {
+                    .van-field__control--custom {
+                        flex-wrap: wrap;
+                    }
+                }
+            }
 
-// 确认信息
-const handleConfirm = async () => {
-    // 第一步:验证农场信息表单
-    if (currentStep.value === 1) {
-        if (!formRef.value) return;
-        try {
-            await formRef.value.validate();
-            
-            // 验证通过,获取物候期数据并切换到第二步
-            await getCurrentAndNextPhenology();
-            currentStep.value = 2;
-        } catch (error) {
-            console.log("表单验证失败", error);
-        }
-    } 
-    // 第二步:验证物候期表单并关闭弹窗
-    else {
-        if (!phenologyFormRef.value) return;
-        try {
-            await phenologyFormRef.value.validate();
-            // 验证通过,提交所有数据并关闭弹窗
-            const params = {
-                ...formData.value,
-                ...phenologyData.value,
-                wkt: store.state.home.miniUserLocationPoint,
-                expertMiniUserId: props.expertMiniUserId,
-                containerId: specieList.value.find(item => item.id === formData.value.speciesId)?.defaultContainerId,
+            .base-type-field {
+                ::v-deep {
+                    .el-select {
+                        width: 100%;
+                    }
+                    .el-select__wrapper {
+                        min-height: 24px;
+                        box-shadow: none;
+                        padding-left: 0;
+                    }
+                }
             }
-            if(!props.oldUser){
-                const { code, msg } = await VE_API.farm.saveFarm(params);
-                if (code === 0) {
-                    ElMessage.success("农场信息确认成功");
-                    emit("update:show", false);
-                } else {
-                    ElMessage.error(msg || '农场信息确认失败');
+
+            .address-field {
+                position: relative;
+
+                &::before {
+                    position: absolute;
+                    box-sizing: border-box;
+                    content: " ";
+                    pointer-events: none;
+                    right: 16px;
+                    bottom: 0;
+                    left: 16px;
+                    border-bottom: 1px solid #ebedf0;
+                    transform: scaleY(0.5);
                 }
-            }else{
-                ElMessage.success("农场信息确认成功");
-                emit("update:show", false);
             }
-        } catch (error) {
-            console.log("物候期表单验证失败", error);
         }
     }
-};
-
-// 获取当前日期(YYYY-MM-DD格式)
-const getTodayDate = () => {
-    const today = new Date();
-    const year = today.getFullYear();
-    const month = String(today.getMonth() + 1).padStart(2, "0");
-    const day = String(today.getDate()).padStart(2, "0");
-    return `${year}-${month}-${day}`;
-};
 
-// 物候期变化时,更新日期为对应物候期的 startDate
-const handlePhenologyChange = (phenologyId) => {
-    const selectedPhenology = phenologyList.value.find(item => item.phenologyId === phenologyId);
-    if (selectedPhenology && selectedPhenology.startDate) {
-        phenologyData.value.phenologyStartDate = selectedPhenology.startDate;
+    .checkbox {
+        pointer-events: none;
+        padding: 12px 14px;
     }
-};
-
-// 获取当前和下一个物候期
-const getCurrentAndNextPhenology = async () => {
-    try {
-        const { data } = await VE_API.home.getCurrentAndNextPhenology({ 
-            expertMiniUserId: props.expertMiniUserId,
-            containerId: specieList.value.find(item => item.id === formData.value.speciesId)?.defaultContainerId || '26',
-        });
-        if (data && Array.isArray(data)) {
-            phenologyList.value = data;
-            // 初始化物候期表单数据,日期使用第一个物候期的 startDate
-            const firstPhenology = data[0];
-            phenologyData.value = {
-                phenologyId: firstPhenology?.phenologyId || "",
-                phenologyStartDate: firstPhenology?.startDate || getTodayDate(),
-            };
-        }
-    } catch (error) {
-        console.error("获取物候期数据失败", error);
-        ElMessage.error("获取物候期数据失败");
-    }
-};
-</script>
-
-<style scoped lang="scss">
-.farm-info-popup {
-    width: 100%;
-    padding: 20px 16px;
-    border-radius: 12px;
 
-    .popup-title {
-        font-size: 16px;
-        color: #121212;
-    }
+    .popup-footer {
+        display: flex;
+        gap: 13px;
 
-    .farm-form {
-        margin: 16px 0;
-        background: rgba(33, 153, 248, 0.05);
-        padding: 10px;
-        border-radius: 5px;
-        border: 1px solid rgba(33, 153, 248, 0.2);
-        :deep(.el-form-item__label) {
-            color: #1d2129;
-        }
-        .variety-select-wrap {
-            display: flex;
-            gap: 10px;
-            width: 100%;
+        .footer-btn {
+            text-align: center;
+            flex: 1;
+            padding: 8px 0;
+            border-radius: 25px;
         }
 
-        .unit {
-            color: #666;
-            font-size: 14px;
-            padding-right: 8px;
+        .no-btn {
+            color: #666666;
+            border: 1px solid #999999;
         }
-    }
 
-    .btn-confirm {
-        padding: 10px;
-        background: #2199f8;
-        color: #ffffff;
-        border-radius: 4px;
-        font-size: 16px;
-        text-align: center;
+        .yes-btn {
+            background: #2199f8;
+            color: #fff;
+        }
     }
 }
 </style>
+
+<!-- 下拉挂载在 body,需非 scoped 才能盖住 van-popup(z-index: 9999) -->
+<style lang="scss">
+.farm-info-base-type-popper {
+    z-index: 10050 !important;
+}
+</style>

+ 3 - 2
src/components/weatherInfo.vue

@@ -12,6 +12,7 @@
                                 alt="">
                             <span class="current-name van-ellipsis">{{ farmName }}</span>
                             <el-icon
+                                @click="handleFarmInfo"
                                 v-if="activeGarden === 'current'"
                                 class="farm-edit-icon"
                             >
@@ -242,8 +243,8 @@ function getLocationName() {
             : result?.address + "";
     });
 }
-const handleAddFarm = () => {
-    router.push(`/create_farm?from=${props.from}&isReload=true`);
+const handleFarmInfo = () => {
+    router.push(`/farm_info?subjectId=${farmId.value}`);
 }
 
 // 获取天气数据

+ 7 - 0
src/router/globalRoutes.js

@@ -149,4 +149,11 @@ export default [
         meta: { showTabbar: true, keepAlive: true },
         component: () => import("@/views/old_mini/work_execute/index.vue"),
     },
+    // 农场信息
+    {
+        path: "/farm_info",
+        name: "FarmInfo",
+        meta: { keepAlive: true },
+        component: () => import("@/views/old_mini/farm_info/index.vue"),
+    },
 ];

+ 388 - 0
src/views/old_mini/farm_info/index.vue

@@ -0,0 +1,388 @@
+<template>
+    <custom-header :name="$t('农场基本信息')" bgColor="#f2f3f5"></custom-header>
+    <div class="farm-details-page">
+        <div class="info-box info-card">
+            <div class="section-header">
+                <div class="line-title">{{ t('基本信息') }}</div>
+                <div class="edit-btn-box">
+                    <div class="edit-btn" @click="handleAddVariety">{{ t('新增品种') }}</div>
+                    <div class="edit-btn" @click="handleEditFarmInfo">{{ t('编辑信息') }}</div>
+                </div>
+            </div>
+            <div class="info-list">
+                <div class="info-row">
+                    <span class="info-label">{{ t('农场名称:') }}</span>
+                    <span class="info-value">{{ farmInfo.subjectName }}</span>
+                </div>
+                <div class="info-row">
+                    <span class="info-label">{{ t('农场面积:') }}</span>
+                    <span class="info-value">{{ farmInfo.farmArea }}亩</span>
+                </div>
+                <div class="info-row">
+                    <span class="info-label">{{ t('农场位置:') }}</span>
+                    <span class="info-value">{{ farmInfo.farmAddress }}</span>
+                </div>
+            </div>
+        </div>
+        <div class="info-box info-card">
+            <div class="section-header">
+                <div class="line-title">{{ t('土壤性质') }}</div>
+                <div class="edit-btn" @click="handleEditSoil">{{ t('编辑信息') }}</div>
+            </div>
+            <div class="info-list grid-list">
+                <div class="grid-item">
+                    <div class="grid-value">{{ t('壤土') }}</div>
+                    <div class="grid-name">{{ t('质地') }}</div>
+                </div>
+                <div class="grid-item">
+                    <div class="grid-value">1%</div>
+                    <div class="grid-name">{{ t('有机质') }}</div>
+                </div>
+                <div class="grid-item">
+                    <div class="grid-value">5.5</div>
+                    <div class="grid-name">PH</div>
+                </div>
+            </div>
+        </div>
+        <div class="info-box info-card">
+            <div class="section-header">
+                <div class="line-title">{{ t('历史高发风险') }}</div>
+                <div class="edit-btn only-text" @click="handleEditFarmFacility">{{ t('查看更多') }}<el-icon><ArrowRight /></el-icon></div>
+            </div>
+            <div class="info-list">
+                <span class="text-label">{{ t('物候风险:') }}</span> 风险描述风险描述风险描述风险描述风险
+            </div>
+        </div>
+        <div class="info-box info-card">
+            <div class="section-header">
+                <div class="line-title">{{ t('种植类别') }}</div>
+            </div>
+            <div class="info-list">
+                <div class="info-row">
+                    <span class="info-label">{{ t('过往种植:') }}</span>
+                    <div class="info-value crop-tags">
+                        <span v-for="crop in farmInfo.regionList" :key="crop.regionId" class="crop-tag dark-tag">
+                            {{ crop.regionName }}
+                        </span>
+                        <span class="crop-tag border-tag" @click="handleAddVariety">{{ t('+新增品种') }}</span>
+                    </div>
+                </div>
+                <div class="info-row line-break">
+                    <div class="info-label">{{ t('当季作物:') }}</div>
+                    <div class="season-box">
+                        <div class="info-value crop-tags">
+                            <span class="crop-tag">
+                                桂味
+                            </span>
+                        </div>
+                        <div class="season-content">
+                            <div class="season-item">{{ t('桂味-上市时间:06.08') }}</div>
+                            <div class="season-item">{{ t('桂味-上市时间:06.08') }}</div>
+                        </div>
+                    </div>
+                    <div class="season-box">
+                        <div class="info-value crop-tags">
+                            <span class="crop-tag">
+                                桂味
+                            </span>
+                        </div>
+                        <div class="season-content">
+                            <div class="season-item">{{ t('桂味-上市时间:06.08') }}</div>
+                            <div class="season-item">{{ t('桂味-上市时间:06.08') }}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- <FarmInfoPopup ref="farmInfoPopupRef" :farmId="route.query.subjectId" @success="fetchFarmSubjectDetail" /> -->
+
+    <SoilPopup ref="soilPopupRef" />
+</template>
+
+<script setup>
+import { useI18n } from "@/i18n";
+const { t } = useI18n();
+import { ref, onMounted } from "vue";
+import customHeader from "@/components/customHeader.vue";
+import { useRouter, useRoute } from "vue-router";
+// import FarmInfoPopup from "@/components/popup/farmInfoPopup.vue";
+import SoilPopup from "./popup/soilPopup.vue";
+
+const router = useRouter();
+const route = useRoute();
+const farmInfo = ref({});
+
+onMounted(() => {
+    fetchFarmSubjectDetail();
+    fetchBasicFarmFormData();
+});
+
+function fetchFarmSubjectDetail() {
+    VE_API.basic_farm.fetchFarmSubjectDetail({ subjectId: route.query.subjectId }).then(({ data }) => {
+        farmInfo.value = data || {};
+    });
+}
+
+const basicFarmInfo = ref({});
+function fetchBasicFarmFormData() {
+    VE_API.basic_farm.fetchBasicFarmFormData({ subjectId: route.query.subjectId }).then(({ data, code }) => {
+        if (code === 0) {
+            basicFarmInfo.value = {
+                ...data,
+                irrigation: data.irrigationMethods.filter(item => item.selected),
+            }
+        }
+    });
+}
+
+const farmInfoPopupRef = ref(null);
+const handleEditFarmInfo = () => {
+    // // 回显地块:存到 polygonData,创建页会优先使用这里的数据
+    // if (data.geomWkt) {
+    //     const polygonData = {
+    //         geometryArr: [data.geomWkt],
+    //         mianji: data.mianji,
+    //         isConfirmed: true,
+    //     };
+    //     store.commit("home/SET_FARM_POLYGON", polygonData);
+    // } else {
+    //     store.commit("home/SET_FARM_POLYGON", null);
+    // }
+
+    // const params = {
+    //     ...farmInfo.value,
+    //     name: farmInfo.value.subjectName,
+    //     fzr: farmInfo.value.contactName,
+    //     tel: farmInfo.value.contactPhone,
+    //     mianji: farmInfo.value.farmArea,
+    //     address: farmInfo.value.farmAddress,
+    // };
+
+    // // 回显其他表单字段
+    // store.commit("home/SET_EDIT_FARM_DATA", params);
+
+    // // 带上 from=details,创建页提交/取消后能正确返回
+    // router.push(`/create_farm?type=edit&farmId=${route.query.subjectId}&from=details`);
+
+    // farmInfoPopupRef.value.handleShow();
+};
+
+const soilPopupRef = ref(null);
+const handleEditSoil = () => {
+    soilPopupRef.value.open();
+};
+
+const handleEditFarmFacility = () => {
+    router.push(`/prescription?subjectId=${route.query.subjectId}`);
+};
+
+const handleAddVariety = () => {
+    console.log('handleAddVariety');
+    // router.push(`/interaction?addVariety=true&subjectId=${route.query.subjectId}`);
+};
+</script>
+
+<style lang="scss" scoped>
+.farm-details-page {
+    background: #f2f3f5;
+    height: calc(100vh - 40px);
+    padding: 0 12px;
+    overflow: auto;
+
+    .line-title {
+        position: relative;
+        padding-left: 14px;
+        font-size: 16px;
+
+        &::before {
+            content: "";
+            position: absolute;
+            left: 5px;
+            top: 50%;
+            transform: translateY(-50%);
+            width: 4px;
+            height: 15px;
+            background: #2199f8;
+            border-radius: 20px;
+        }
+    }
+
+    .info-card{
+        margin-top: 12px;
+        background: #fff;
+        border-radius: 6px;
+        padding: 10px;
+    }
+
+    .info-box {
+        margin-top: 12px;
+
+        .section-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+        }
+
+        .edit-btn-box {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }
+
+        .edit-btn {
+            padding: 4px 12px;
+            font-size: 12px;
+            color: #86909c;
+            border-radius: 999px;
+            border: 0.5px solid #d0d3d8;
+            background-color: #ffffff;
+            &.only-text {
+                border: none;
+            }
+        }
+
+        .info-list {
+            margin-top: 10px;
+            margin-left: 5px;
+
+            .info-row {
+                display: flex;
+                align-items: flex-start;
+                margin-bottom: 6px;
+
+                &.line-break {
+                    display: block;
+                }
+
+                &.center-row {
+                    align-items: center;
+                }
+
+                .info-label {
+                    min-width: 70px;
+                    color: #4E5969;
+                }
+
+                .crop-tags {
+                    display: flex;
+                    align-items: center;
+                    flex-wrap: wrap;
+                    gap: 6px;
+
+                    .crop-tag {
+                        padding: 2px 8px;
+                        background: #E8F3FF;
+                        color: #2199f8;
+                        border-radius: 2px;
+                        font-size: 12px;
+                        height: 22px;
+                        box-sizing: border-box;
+                        &.dark-tag {
+                            background: #F8F8F8;
+                            color: #7F7F7F;
+                        }
+                        &.border-tag {
+                            border: 0.5px solid #2199F8;
+                        }
+                    }
+                }
+
+                .problem-tags {
+                    display: flex;
+                    flex-wrap: wrap;
+                    gap: 6px;
+
+                    .problem-tag {
+                        padding: 2px 8px;
+                        background: rgba(58, 173, 148, 0.1);
+                        color: #3AAD94;
+                        border-radius: 2px;
+                        font-size: 13px;
+                    }
+                }
+
+                .device-value {
+                    width: 100%;
+                }
+
+                .device-box {
+                    padding: 6px;
+                    border-radius: 4px;
+                    border: 0.5px solid rgba(33, 153, 248, 0.2);
+                    display: grid;
+                    grid-template-columns: repeat(2, minmax(0, 1fr));
+                    column-gap: 12px;
+                    row-gap: 4px;
+
+                    .device-item {
+                        display: flex;
+                        align-items: center;
+                        justify-content: space-between;
+                        padding: 4px 0;
+                        min-width: 0;
+                        gap: 8px;
+                        font-size: 13px;
+                        color: #1D2129;
+
+                        .device-count {
+                            padding: 2px 8px;
+                            background: #E8F3FF;
+                            color: #2199f8;
+                            border-radius: 2px;
+                        }
+                    }
+                }
+            }
+
+            .info-row-column {
+                display: flex;
+                flex-direction: column;
+                gap: 4px;
+            }
+        }
+
+        .season-box {
+            padding-top: 8px;
+            .season-content {
+                display: grid;
+                grid-template-columns: repeat(2, 1fr);
+                gap: 2px;
+                .season-item {
+                    padding-top: 6px;
+                }
+            }
+        }
+
+        .text-label {
+            color: #4E5969;
+        }
+
+        .grid-list {
+            display: grid;
+            grid-template-columns: repeat(3, 1fr);
+            gap: 6px;
+            .grid-item {
+                background: #F5F5F5;
+                border-radius: 5px;
+                padding: 3px;
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                justify-content: center;
+                height: 50px;
+                .grid-value {
+                    font-size: 16px;
+                    color: #1D2129;
+                    // line-height: 22px;
+                }
+                .grid-name {
+                    // line-height: 14px;
+                    font-size: 12px;
+                    color: rgba(78, 89, 105, 0.5);
+                }
+            }
+        }
+    }
+}
+</style>

+ 251 - 0
src/views/old_mini/farm_info/popup/soilPopup.vue

@@ -0,0 +1,251 @@
+<template>
+    <Popup class="soil-popup" round v-model:show="show">
+        <div class="soil-content">
+            <div class="section-title">{{ $t('请选择您的土壤类型') }}</div>
+            <div class="slider-wrap">
+                <Slider
+                    v-model="soilIndex"
+                    :min="0"
+                    :max="soilOptions.length - 1"
+                    :step="1"
+                    bar-height="2px"
+                    active-color="#D9D9D9"
+                    inactive-color="#D9D9D9"
+                >
+                    <template #button>
+                        <div class="slider-button">
+                            <span></span><span></span><span></span>
+                        </div>
+                    </template>
+                </Slider>
+                <div class="dot-row">
+                    <span
+                        v-for="(_, idx) in soilOptions"
+                        :key="'soil-dot-' + idx"
+                        class="dot"
+                        :class="{ active: idx === soilIndex }"
+                        @click="soilIndex = idx"
+                    />
+                </div>
+                <div class="label-row">
+                    <span
+                        v-for="(item, idx) in soilOptions"
+                        :key="'soil-label-' + item"
+                        class="label-item"
+                        :class="{ active: idx === soilIndex }"
+                        @click="soilIndex = idx"
+                    >
+                        {{ item }}
+                    </span>
+                </div>
+            </div>
+
+            <div class="section-title ph-title">{{ $t('调整pH值') }}</div>
+            <div class="slider-wrap">
+                <Slider
+                    v-model="phIndex"
+                    :min="0"
+                    :max="phOptions.length - 1"
+                    :step="1"
+                    bar-height="2px"
+                    active-color="#D9D9D9"
+                    inactive-color="#D9D9D9"
+                >
+                    <template #button>
+                        <div class="slider-button">
+                            <span></span><span></span><span></span>
+                        </div>
+                    </template>
+                </Slider>
+                <div class="dot-row">
+                    <span
+                        v-for="(_, idx) in phOptions"
+                        :key="'ph-dot-' + idx"
+                        class="dot"
+                        :class="{ active: idx === phIndex }"
+                        @click="phIndex = idx"
+                    />
+                </div>
+                <div class="label-row ph-label-row">
+                    <span
+                        v-for="(item, idx) in phOptions"
+                        :key="'ph-label-' + item"
+                        class="label-item"
+                        :class="{ active: idx === phIndex }"
+                        @click="phIndex = idx"
+                    >
+                        {{ item }}
+                    </span>
+                </div>
+            </div>
+        </div>
+        <div class="popup-button">
+            <div class="button-item cancel" @click="close">{{ $t('取消') }}</div>
+            <div class="button-item confirm" @click="handleConfirm">{{ $t('确认信息') }}</div>
+        </div>
+    </Popup>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue';
+import { Popup, Slider } from "vant";
+
+const show = ref(false);
+const soilOptions = ["沙土", "壤土", "粘土"];
+const phOptions = ["5", "5.5", "6", "6.5", "7", "7.5", "8"];
+const soilIndex = ref(1);
+const phIndex = ref(3);
+
+const emit = defineEmits(["confirm"]);
+
+const currentSoilType = computed(() => soilOptions[soilIndex.value] || soilOptions[0]);
+const currentPh = computed(() => Number(phOptions[phIndex.value] || phOptions[0]));
+
+const open = () => {
+    show.value = true;
+}
+
+const close = () => {
+    show.value = false;
+}
+
+const handleConfirm = () => {
+    emit("confirm", {
+        soilType: currentSoilType.value,
+        ph: currentPh.value,
+    });
+    close();
+};
+
+defineExpose({
+    open,
+    close,
+});
+</script>
+
+<style lang="scss" scoped>
+.soil-popup {
+    width: 90%;
+    padding: 20px 22px 20px 22px;
+    // background: linear-gradient(360deg, #FFFFFF 74.2%, #D1EBFF 100%);
+    background: linear-gradient(360deg, #FFFFFF 63.11%, #93CEFD 100%);
+    border-radius: 8px;
+    font-size: 16px;
+    
+    .soil-content {
+        padding: 4px 0 0;
+
+        .section-title {
+            font-size: 16px;
+            color: #000;
+            margin-bottom: 10px;
+        }
+
+        .ph-title {
+            margin-top: 18px;
+        }
+
+        .slider-wrap {
+            padding: 8px 4px 0;
+            ::v-deep {
+                .van-slider__button-wrapper {
+                    z-index: 10;
+                }
+            }
+        }
+
+        .slider-button {
+            width: 46px;
+            height: 26px;
+            border-radius: 28px;
+            background: #2199f8;
+            border: 2px solid rgba(255, 255, 255, 0.9);
+            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 4px;
+
+            span {
+                width: 2px;
+                height: 8px;
+                border-radius: 6px;
+                background: #fff;
+            }
+        }
+
+        .dot-row {
+            margin-top: -6px;
+            z-index: 2;
+            position: relative;
+            padding: 0 2px;
+            display: flex;
+            justify-content: space-between;
+
+            .dot {
+                width: 10px;
+                height: 10px;
+                box-sizing: border-box;
+                border-radius: 50%;
+                background: #CDCDCD;
+                border: 1.5px solid #fff;
+                cursor: pointer;
+                box-shadow: 0px 2px 3px 0px #00000012;
+            }
+
+            .dot.active {
+                border-color: #2199f8;
+            }
+        }
+
+        .label-row {
+            margin-top: 14px;
+            display: flex;
+            justify-content: space-between;
+
+            .label-item {
+                font-size: 14px;
+                color: #000;
+                cursor: pointer;
+            }
+
+            .label-item.active {
+                color: #2199f8;
+                font-weight: 500;
+            }
+        }
+
+        .ph-label-row {
+            .label-item {
+                min-width: 24px;
+                text-align: center;
+            }
+        }
+    }
+
+    .popup-button {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 16px;
+        gap: 12px;
+        .button-item {
+            flex: 1;
+            border-radius: 20px;
+            background: #2199F8;
+            color: #fff;
+            text-align: center;
+            height: 40px;
+            box-sizing: border-box;
+            line-height: 40px;
+        }
+
+        .cancel {
+            color: #000;
+            border: 1px solid rgba(0, 0, 0, 0.2);
+            background: #fff;
+            width: 102px;
+            flex: none;
+        }
+    }
+}
+</style>