Преглед изворни кода

feat:修改新增农场逻辑

wangsisi пре 1 дан
родитељ
комит
1bf13831ca

+ 7 - 0
src/router/globalRoutes.js

@@ -508,4 +508,11 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/work_detail/index.vue"),
     },
+    // 农事详情
+    {
+        path: "/select_crop",
+        name: "SelectCrop",
+        meta: { keepAlive: true },
+        component: () => import("@/views/old_mini/create_farm/selectCrop.vue"),
+    },
 ];

+ 0 - 1354
src/views/old_mini/create_farm/index copy 2.vue

@@ -1,1354 +0,0 @@
-<template>
-    <div class="create-farm">
-        <custom-header :name="paramsType === 'client' ? '新增用户' : paramsType === 'edit' ? '编辑农场' : '创建农场'"
-            :isGoBack="true" @goback="backgToHome"></custom-header>
-        <!-- 地图 -->
-        <div class="map-container" ref="mapContainer"></div>
-        <div class="farm-content">
-            <div class="top-mask"></div>
-            <div class="farm-filter">
-                <el-select v-model="locationVal" filterable remote reserve-keyword placeholder="搜索位置"
-                    :remote-method="remoteMethod" :loading="loading" @change="handleSearchRes"
-                    popper-class="location-search-popper">
-                    <el-option v-for="(item, index) in locationOptions.list" :key="index" :label="item.title"
-                        :value="{ value: item.point, item }">
-                        <span>{{ item.title }}</span>
-                        <span class="sub-title">{{ item.province }}{{ item.city }}{{ item.district || '' }}</span>
-                    </el-option>
-                </el-select>
-            </div>
-
-            <!-- 创建 -->
-            <div class="create-wrap">
-                <div class="create-box">
-                    <div class="box-content">
-                        <div class="create-title">
-                            <img class="title-icon" src="@/assets/img/home/create-icon.png" alt="" />
-                            {{ paramsType === "client" ? "新增用户" : paramsType === "edit" ? "编辑农场" : "创建农场" }}
-                        </div>
-                        <div class="create-content">
-                            <div class="create-from">
-                                <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="demo-ruleForm">
-                                    <el-form-item label="农场位置" prop="address">
-                                        <div class="position-wrap">
-                                            <el-input placeholder="农场位置" readonly v-model="ruleForm.address"
-                                                autocomplete="off" />
-                                            <!-- <div class="draw-btn" @click="toSubPage">
-                                                {{ hasDefaultPolygon ? "点击勾选地块" : "新增地块" }}
-                                            </div> -->
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="种植作物" prop="speciesItem">
-                                        <div class="select-wrap client-wrap">
-                                            <el-select v-model="ruleForm.speciesItem" filterable class="select-item specie-select"
-                                                multiple :max-collapse-tags="3" collapse-tags placeholder="种植作物(可多选)" @change="changeSpecie">
-                                                <el-option v-for="(item, index) in specieList" :key="index"
-                                                    :label="item.name" :value="{ value: item.id, ...item }" />
-                                            </el-select>
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="联系人" prop="fzr">
-                                        <div class="area-box">
-                                            <el-input placeholder="请输入联系人姓名" v-model="ruleForm.fzr" autocomplete="off"
-                                                style="width: fit-content" />
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="联系电话" prop="tel">
-                                        <div class="area-box">
-                                            <el-input placeholder="请输入联系人电话" type="number" v-model="ruleForm.tel" autocomplete="off"
-                                                style="width: fit-content" />
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="农场面积" prop="mu">
-                                        <div class="area-box">
-                                            <el-input :placeholder="isFromEditMap ? '勾选地块获得农场面积' : '请输入农场的真实面积'
-                                                " v-model="ruleForm.mu" :readonly="isFromEditMap" type="text"
-                                                autocomplete="off" style="width: fit-content" @input="handleMianjiInput"
-                                                @keypress="handleMianjiKeypress" />
-                                            <div class="unit">亩</div>
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="农场名称" prop="name">
-                                        <el-input placeholder="请输入您的农场名称" v-model="ruleForm.name" autocomplete="off"
-                                            @input="handleFarmNameInput" />
-                                    </el-form-item>
-                                    <el-form-item label="基地类别" prop="baseType" class="select-wrap client-wrap">
-                                        <el-select placeholder="请选择" v-model="ruleForm.baseType" autocomplete="off">
-                                            <el-option v-for="(item, index) in baseTypeList" :key="index"
-                                                :label="item.name" :value="item.id" />
-                                        </el-select>
-                                    </el-form-item>
-                                </el-form>
-                            </div>
-                            <div class="create-btn">
-                                <div class="btn-item sencond-btn" @click="resetForm(ruleFormRef)">取消</div>
-                                <div class="btn-item primary-btn" @click="submitForm(ruleFormRef)">
-                                    {{
-                                        paramsType === "client"
-                                            ? "添加"
-                                            : paramsType === "edit"
-                                                ? "确认修改"
-                                                : "立即创建"
-                                    }}
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-    <!-- 农情采集成功弹窗 -->
-    <tip-popup
-        v-model:show="showSuccessPopup"
-        type="success"
-        text="农情采集成功"
-        @confirm="handlePopupConfirm"
-        @handleClickOverlay="handlePopupConfirm"
-    />
-    <!-- 成熟收获时间弹窗 -->
-    <harvest-time-popup
-        v-model:show="showHarvestPopup"
-        :crops="harvestCrops"
-        @confirm="handleHarvestConfirm"
-    />
-</template>
-
-<script setup>
-import customHeader from "@/components/customHeader.vue";
-import IndexMap from "./map/index.js";
-import { useRoute, useRouter } from "vue-router";
-import { mapLocation } from "./map/index.js";
-import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated } from "vue";
-import { useStore } from "vuex";
-import { convertPointToArray } from "@/utils/index";
-import { ElMessage } from "element-plus";
-import { Checkbox } from "vant";
-import tipPopup from "@/components/popup/tipPopup.vue";
-import harvestTimePopup from "@/components/popup/harvestTimePopup.vue";
-import wx from "weixin-js-sdk";
-import { transformFromGCJToWGS, transformFromWGSToGCJ } from "@/utils/WSCoordinate.js";
-
-const route = useRoute();
-const router = useRouter();
-const store = useStore();
-
-const indexMap = new IndexMap();
-const mapContainer = ref(null);
-
-// 农情采集成功弹窗
-const showSuccessPopup = ref(false);
-// 成熟收获时间弹窗
-const showHarvestPopup = ref(false);
-const harvestCrops = ref([]);
-const pendingCreateParams = ref(null);
-
-const handlePopupConfirm = () => {
-    showSuccessPopup.value = false;
-    router.replace(`/home`);
-};
-
-// 标记是否从编辑地图页面确认返回
-const isFromEditMap = ref(false);
-
-// 标记是否已创建默认地块
-const hasDefaultPolygon = ref(false);
-
-// 标记用户是否手动修改过农场名称
-const isFarmNameManuallyModified = ref(false);
-
-/**
- * 根据中心点生成指定边长的正方形地块WKT
- * @param {Array} center - 中心点坐标 [lng, lat]
- * @param {Number} sideLength - 正方形边长(米)
- * @returns {Object} 包含WKT字符串和面积(亩)的对象
- */
-function generateSquarePolygonBySideLength(center, sideLength) {
-    // 确保输入是数字类型
-    const lng = parseFloat(center[0]);
-    const lat = parseFloat(center[1]);
-
-    // 半边长(米)
-    const halfSide = sideLength / 2;
-
-    // 纬度方向:1度约等于111000米
-    const latDelta = halfSide / 111000;
-
-    // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
-    const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
-
-    // 计算四个顶点(逆时针顺序)
-    const topLeft = [lng - lngDelta, lat + latDelta];
-    const topRight = [lng + lngDelta, lat + latDelta];
-    const bottomRight = [lng + lngDelta, lat - latDelta];
-    const bottomLeft = [lng - lngDelta, lat - latDelta];
-
-    // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
-    // 明确使用join来格式化坐标点
-    const formatPoint = (point) => `${point[0]} ${point[1]}`;
-    const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
-
-    // 注意:MULTIPOLYGON 和括号之间需要有一个空格
-    const wkt = `MULTIPOLYGON (((${coordinates})))`;
-
-    // 计算面积(亩): 边长200米 = 40,000平方米 ≈ 60亩
-    const areaInSquareMeters = sideLength * sideLength;
-    const areaInMu = (areaInSquareMeters / 666.67).toFixed(2);
-
-    return { wkt, area: areaInMu };
-}
-
-/**
- * 根据中心点和亩数生成正方形地块WKT
- * @param {Array} center - 中心点坐标 [lng, lat]
- * @param {Number} mu - 面积(亩)
- * @returns {Object} 包含WKT字符串和面积(亩)的对象
- */
-function generateSquarePolygonByMu(center, mu) {
-    // 确保输入是数字类型
-    const lng = parseFloat(center[0]);
-    const lat = parseFloat(center[1]);
-
-    // 1亩 ≈ 666.67平方米
-    const areaInSquareMeters = mu * 666.67;
-    // 正方形边长(米)
-    const sideLength = Math.sqrt(areaInSquareMeters);
-    // 半边长(米)
-    const halfSide = sideLength / 2;
-
-    // 纬度方向:1度约等于111000米
-    const latDelta = halfSide / 111000;
-
-    // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
-    const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
-
-    // 计算四个顶点(逆时针顺序)
-    const topLeft = [lng - lngDelta, lat + latDelta];
-    const topRight = [lng + lngDelta, lat + latDelta];
-    const bottomRight = [lng + lngDelta, lat - latDelta];
-    const bottomLeft = [lng - lngDelta, lat - latDelta];
-
-    // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
-    const formatPoint = (point) => `${point[0]} ${point[1]}`;
-    const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
-
-    // 注意:MULTIPOLYGON 和括号之间需要有一个空格
-    const wkt = `MULTIPOLYGON (((${coordinates})))`;
-
-    return { wkt, area: parseFloat(mu).toFixed(2) };
-}
-
-onMounted(() => {
-    // 清除上一次的地块数据,确保每次进入都是全新状态
-    store.commit("home/SET_FARM_POLYGON", null);
-    isFromEditMap.value = false; // 初始化时可以手动输入
-    hasDefaultPolygon.value = false; // 初始化时没有默认地块
-
-    centerPoint.value = store.state.home.miniUserLocationPoint;
-    // const arr = convertPointToArray(centerPoint.value);
-    // getLocationName(`${arr[1]},${arr[0]}`);
-    indexMap.initMap(centerPoint.value, mapContainer.value);
-
-    // 不再初始化时绘制默认地块,等待用户点击"新增地块"按钮
-    // 清空地块和面积数据
-    polygonArr.value = null;
-    ruleForm.mu = "";
-
-    // 如果是编辑模式,等待品类列表加载完成后再回填数据
-    if (route.query.type === "edit" && store.state.home.editFarmData) {
-        getSpecieList().then(() => {
-            populateEditData();
-        });
-    } else {
-        getSpecieList();
-    }
-    getBaseTypeList();
-});
-
-const polygonArr = ref(null);
-const paramsType = ref(null);
-onActivated(() => {
-    const centerPointVal = sessionStorage.getItem('centerPoint') ? JSON.parse(sessionStorage.getItem('centerPoint')) : null;
-    paramsType.value = route.query.type;
-
-    centerPoint.value = centerPointVal || store.state.home.miniUserLocationPoint;
-    const arr = convertPointToArray(centerPoint.value);
-    getLocationName(`${arr[1]},${arr[0]}`);
-    // 仅在携带 isReload 标记、且不是编辑/小程序回流场景时,认为是一次全新创建,重置表单和地块,
-    // 避免破坏原有自动生成农场名称等逻辑
-    if (route.query.isReload && paramsType.value !== "edit" && !route.query.miniJson) {
-        // 重置表单字段到初始值
-        ruleFormRef.value && ruleFormRef.value.resetFields();
-        // 重置与地块绘制相关的内部状态
-        polygonArr.value = null;
-        isFromEditMap.value = false;
-        hasDefaultPolygon.value = false;
-        // 重置农场名称手动修改标记,允许自动生成农场名称
-        isFarmNameManuallyModified.value = false;
-        // 清空上一次地块缓存
-        store.commit("home/SET_FARM_POLYGON", null);
-        getSpecieList();
-    }
-    // 确保地图已初始化,使用 nextTick 等待 DOM 更新
-    nextTick(() => {
-        // 检查地图实例是否已初始化
-        if (!indexMap.kmap) {
-            // 如果地图未初始化,重新初始化
-            if (mapContainer.value) {
-                centerPoint.value = store.state.home.miniUserLocationPoint;
-                const arr = convertPointToArray(centerPoint.value);
-                indexMap.initMap(centerPoint.value, mapContainer.value);
-            }
-            // 等待地图初始化完成后再继续
-            setTimeout(() => {
-                handleMapUpdate();
-            }, 100);
-            return;
-        }
-        handleMapUpdate();
-    });
-});
-
-onDeactivated(() => {
-    sessionStorage.setItem('centerPoint', JSON.stringify(centerPoint.value));
-});
-
-// 处理地图更新的逻辑
-function handleMapUpdate() {
-    if (route.query.isReload) {
-        // 清除旧的地块数据
-        store.commit("home/SET_FARM_POLYGON", null);
-        isFromEditMap.value = false; // 从home进入,可以手动输入
-        hasDefaultPolygon.value = false; // 重置默认地块状态
-
-        centerPoint.value = store.state.home.miniUserLocationPoint;
-        const arr = convertPointToArray(centerPoint.value);
-        getLocationName(`${arr[1]},${arr[0]}`);
-        indexMap.setMapPosition(arr);
-
-        indexMap.clearLayer();
-
-        // 不再自动生成默认地块,等待用户点击"新增地块"
-        polygonArr.value = null;
-        ruleForm.mu = "";
-        ruleForm.defaultFarm = 0;
-
-        return; // 直接返回,不执行下面的逻辑
-    }
-
-    indexMap.clearLayer();
-    // 绘制勾画范围(从edit_map返回的情况)
-    const polygonData = store.state.home.polygonData;
-
-    // 优先处理从编辑地图页面返回的数据
-    if (polygonData) {
-        // 检查地块数据是否有效(数组存在且不为空)
-        const hasValidGeometry =
-            polygonData.geometryArr && Array.isArray(polygonData.geometryArr) && polygonData.geometryArr.length > 0;
-
-        if (hasValidGeometry) {
-            // 用户从edit_map返回,且有有效的地块数据
-            // 根据 isConfirmed 判断是否锁定输入框
-            if (polygonData.isConfirmed) {
-                // 用户点击了"确认"按钮,锁定输入框并显示精确面积
-                isFromEditMap.value = true;
-                ruleForm.mu = polygonData.mianji;
-            } else {
-                // 用户点击了"取消",不锁定输入框,面积保持原样
-                isFromEditMap.value = false;
-                // 面积输入框保持之前的值,不更新
-            }
-            // 使用 nextTick 确保地图渲染完成后再设置地块
-            nextTick(() => {
-                indexMap.setAreaGeometry(polygonData.geometryArr);
-            });
-            polygonArr.value = polygonData.geometryArr;
-            // 有地块数据时,标记已创建默认地块
-            hasDefaultPolygon.value = true;
-        } else {
-            // 用户在编辑页面删除了所有地块
-            // 重置所有状态
-            polygonArr.value = null;
-            ruleForm.mu = "";
-            isFromEditMap.value = false;
-            hasDefaultPolygon.value = false;
-            ruleForm.defaultFarm = 0;
-        }
-    } else if (route.query.type === "edit" && store.state.home.editFarmData) {
-        // 如果是编辑模式且没有从编辑地图返回的数据,回填原始编辑数据
-        populateEditData();
-    } else if (centerPoint.value && polygonArr.value) {
-        // 没有新的编辑数据,保持当前地块
-        // 同样需要检查数据有效性
-        if (Array.isArray(polygonArr.value) && polygonArr.value.length > 0) {
-            nextTick(() => {
-                indexMap.setAreaGeometry(polygonArr.value);
-            });
-            // 保持 hasDefaultPolygon 状态
-        }
-    }
-}
-
-// 搜索
-const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
-
-const locationVal = ref(null);
-const locationOptions = reactive({
-    list: [],
-});
-const loading = ref(false);
-const remoteMethod = async (keyword) => {
-    if (keyword) {
-        locationOptions.list = [];
-        loading.value = true;
-        const params = {
-            key: MAP_KEY,
-            keyword,
-            location: route.query.userLocation || "113.61702297075017,23.584863449735067",
-        };
-        await VE_API.old_mini_map.getCtiyList({ word: keyword }).then(({ data }) => {
-            if (data && data.length) {
-                data.forEach((item) => {
-                    item.point = item.location.lat + "," + item.location.lng;
-                    locationOptions.list.push(item);
-                });
-            }
-        });
-        VE_API.old_mini_map.search(params).then(({ data }) => {
-            loading.value = false;
-            data.forEach((item) => {
-                item.point = item.location.lat + "," + item.location.lng;
-                locationOptions.list.push(item);
-            });
-        });
-    } else {
-        locationOptions.list = [];
-    }
-};
-
-const handleSearchRes = (v) => {
-    const parts = v.value.split(",");
-    let { latitude, longitude } = transformFromGCJToWGS(parseFloat(parts[0]), parseFloat(parts[1]));
-    const coordinateArray = [longitude, latitude];
-    indexMap.setMapPosition(coordinateArray);
-    centerPoint.value = `POINT (${coordinateArray[0]} ${coordinateArray[1]})`;
-    ruleForm.address = v.item?.title || v.item?.address;
-    pointAddress.value = v.item?.province + v.item?.city + (v.item?.district || '');
-    // 更新farmCity以便后续更新农场名称
-    farmCity.value = v.item?.city + (v.item?.district || "");
-    // 地址修改后,如果满足条件则自动更新农场名称
-    updateFarmNameIfNeeded();
-    getSpecieList();
-};
-
-// 表单
-const ruleFormRef = ref(null);
-const ruleForm = reactive({
-    address: "",
-    mu: "",
-    // 多选品类:数组
-    speciesItem: [],
-    name: "",
-    fzr: "",
-    tel: "",
-    baseType: null,
-    defaultFarm: 0, // 0:否 1:是
-});
-// 自定义验证规则:验证面积必须是大于0的数字
-const validateMianji = (rule, value, callback) => {
-    if (!value) {
-        callback(new Error("请输入农场面积"));
-    } else {
-        const num = parseFloat(value);
-        if (isNaN(num)) {
-            callback(new Error("面积必须是数字"));
-        } else if (num <= 0) {
-            callback(new Error("面积必须大于0"));
-        } else {
-            callback();
-        }
-    }
-};
-
-const rules = reactive({
-    address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
-    mu: [
-        { required: true, message: "请输入农场面积", trigger: "blur" },
-        { validator: validateMianji, trigger: ["blur", "change"] },
-    ],
-    speciesItem: [{ required: true, message: "请选择种植作物", trigger: ["blur", "change"] }],
-    name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
-    fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
-    tel: [
-        { required: true, message: "请输入联系人电话", trigger: ["blur", "change"] },
-        {
-            pattern: /^1[3-9]\d{9}$/,
-            message: "请输入正确的手机号码",
-            trigger: ["blur", "change"],
-        },
-    ],
-    defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
-    baseType: [{ required: true, message: "请选择基地类别", trigger: "blur" }],
-});
-
-const curRole = localStorage.getItem("SET_USER_CUR_ROLE");
-
-// 成熟收获时间弹窗确认
-const handleHarvestConfirm = (list) => {
-    if (!pendingCreateParams.value) return;
-    const { params, mainSpecies } = pendingCreateParams.value;
-
-    // 将成熟时间附加到参数中(可按需要调整字段名)
-    const harvestInfo = (list || []).map((item) => ({
-        name: item.name,
-        month: item.month,
-        day: item.day,
-    }));
-
-    // 处理 geom 参数,如果是数组需要序列化
-    const queryParams = {
-        ...params,
-        ...route.query,
-        harvestInfo: JSON.stringify(harvestInfo),
-    };
-    if (Array.isArray(queryParams.geom)) {
-        queryParams.geom = JSON.stringify(queryParams.geom);
-    }
-    if (queryParams.speciesContainer && typeof queryParams.speciesContainer === "object") {
-        queryParams.speciesContainer = JSON.stringify(queryParams.speciesContainer);
-    }
-    delete queryParams.speciesItem;
-    queryParams.speciesName = mainSpecies?.name;
-
-    showHarvestPopup.value = false;
-
-    setTimeout(() => {
-        router.push({
-            path: "/prescription",
-            query: queryParams,
-        });
-    }, 500);
-
-    pendingCreateParams.value = null;
-};
-
-const submitForm = (formEl) => {
-    if (!formEl) return;
-    formEl.validate((valid) => {
-        if (valid) {
-            const speciesList = Array.isArray(ruleForm.speciesItem) ? ruleForm.speciesItem : (ruleForm.speciesItem ? [ruleForm.speciesItem] : []);
-            const mainSpecies = speciesList[0];
-            const speciesContainer = speciesList.map((item) => ({
-                speciesId: item?.id ?? null,
-                containerId: item?.defaultContainerId ?? null,
-            }));
-            const params = {
-                ...ruleForm,
-                wkt: centerPoint.value,
-                speciesContainer,
-                agriculturalCreate: route.query.type === "client" ? 1 : 0,
-                // 编辑时geom不是数组,新增时是数组
-                geom:
-                    route.query.type === "edit"
-                        ? polygonArr.value && polygonArr.value.length > 0
-                            ? polygonArr.value[0]
-                            : null
-                        : polygonArr.value,
-            };
-
-            // 如果是编辑模式,添加农场ID
-            if (route.query.type === "edit" && route.query.farmId) {
-                params.id = route.query.farmId;
-            }
-
-            // let pageData = null;
-            // if (route.query.miniJson) {
-            //     const json = JSON.parse(route.query.miniJson);
-            //     console.log('json', json);
-            //     // pageData = JSON.parse(json.paramsPage);
-            // }
-
-            if (route.query.type !== "edit") {
-                // 创建模式下先弹出成熟收获时间弹窗
-                harvestCrops.value = speciesList.map((item) => ({
-                    name: item.name || "",
-                    month: "",
-                    day: "",
-                }));
-                pendingCreateParams.value = {
-                    params,
-                    mainSpecies,
-                };
-                showHarvestPopup.value = true;
-                return;
-            }
-
-            // const apiCall = route.query.type === "edit" ? VE_API.basic_farm.saveBasicFarmInfoByExpertV3({...params, expertMiniUserId: '81881'}) : VE_API.farm.saveFarm(params);
-
-            // apiCall.then((res) => {
-            //     if (res.code === 0) {
-            //         ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
-            //         // 重置表单和地块数据
-            //         ruleFormRef.value.resetFields();
-            //         store.commit("home/SET_FARM_POLYGON", null);
-            //         store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
-            //         polygonArr.value = null;
-            //         isFromEditMap.value = false;
-
-            //         if (route.query.type !== "edit" && curRole == 0) {
-            //             localStorage.setItem("selectedFarmId", res.data.id);
-            //             localStorage.setItem("selectedFarmName", res.data.name);
-            //         }
-
-            //         // 根据来源页面决定跳转目标
-            //         const fromPage = route.query.from;
-            //         if (fromPage && fromPage !== "details") {
-            //             // 如果是从monitor页面来的
-            //             router.replace(`/${fromPage}`);
-            //         } else if (fromPage === "details") {
-            //             router.go(-1);
-            //         } else {
-            //             if (route.query.miniJson) {
-            //                 const json = JSON.parse(route.query.miniJson);
-            //                 //上传图片
-            //                 VE_API.ali
-            //                     .uploadImg({
-            //                         farmId: res.data.id,
-            //                         images: json.images,
-            //                         uploadDate: formatDate(new Date()),
-            //                     })
-            //                     .then(({ code, msg }) => {
-            //                         if (code === 0) {
-            //                             showSuccessPopup.value = true;
-            //                         } else {
-            //                             ElMessage.error(msg);
-            //                         }
-            //                     });
-            //             } else {
-            //                 router.replace(`/growth_report`);
-            //             }
-            //         }
-            //     } else {
-            //         ElMessage.error(res.msg);
-            //     }
-            // });
-        }
-    });
-};
-
-const resetForm = (formEl) => {
-    if (!formEl) return;
-    formEl.resetFields();
-    // 清除地块数据
-    store.commit("home/SET_FARM_POLYGON", null);
-    polygonArr.value = null;
-    isFromEditMap.value = false;
-    hasDefaultPolygon.value = false; // 重置默认地块状态
-
-    // 根据来源页面决定返回目标
-    const fromPage = route.query.from;
-    if (fromPage && fromPage !== "details") {
-        router.replace(`/${fromPage}`);
-        return;
-    }
-    router.go(-1);
-};
-
-const centerPoint = ref(null);
-
-function toSubPage() {
-    // 如果还没有默认地块,先创建默认地块
-    if (!hasDefaultPolygon.value) {
-        if (centerPoint.value) {
-            const arr = convertPointToArray(centerPoint.value);
-            const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
-            const geometryData = {
-                geometryArr: [squareData.wkt],
-                mianji: squareData.area,
-            };
-
-            // 绘制默认地块
-            indexMap.setAreaGeometry(geometryData.geometryArr);
-
-            // 保存地块数据
-            polygonArr.value = geometryData.geometryArr;
-
-            // 标记已创建默认地块
-            hasDefaultPolygon.value = true;
-
-            // 不跳转,停留在当前页面
-            return;
-        }
-    }
-
-    // 如果已有默认地块,则跳转到编辑页面
-    // 保存到store中以便在编辑页面回显
-    if (polygonArr.value) {
-        const polygonData = {
-            geometryArr: polygonArr.value,
-            mianji: ruleForm.mu || "", // 保存用户输入的面积,如果没有输入则为空
-            isConfirmed: false, // 标记:还未从编辑页面确认返回
-        };
-        store.commit("home/SET_FARM_POLYGON", polygonData);
-    }
-
-    router.push(
-        `/edit_map?mapCenter=${centerPoint.value}&pointName=${ruleForm.address}&pointAddress=${pointAddress.value}&from=${route.query.from}&type=${route.query.type}`
-    );
-}
-
-const pointAddress = ref(null);
-const farmCity = ref(null);
-function getLocationName(location) {
-    const params = {
-        key: MAP_KEY,
-        location,
-    };
-    VE_API.old_mini_map.location(params).then(({ result }) => {
-        // locationVal.value = result.formatted_addresses.recommend;
-        const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
-        ruleForm.address = add;
-        pointAddress.value = result.address;
-        farmCity.value = result.address_component?.city + (result.address_component?.district || "");
-        // 地址修改后,如果满足条件则自动更新农场名称
-        updateFarmNameIfNeeded();
-    });
-}
-
-watch(
-    () => mapLocation.data,
-    (newValue, oldValue) => {
-        if (newValue) {
-            let { latitude, longitude } = transformFromWGSToGCJ(newValue[1], newValue[0]);
-            centerPoint.value = `POINT (${newValue[0]} ${newValue[1]})`;
-            getLocationName(`${latitude},${longitude}`);
-        }
-    }
-);
-
-const specieList = ref([]);
-function getSpecieList() {
-    return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
-        const list = Array.isArray(data) ? data : [];
-        // 只保留名称包含“水稻”或“荔枝”的品类
-        const litchiList = list.filter(
-            (item) => item?.name && (item.name.includes("水稻") || item.name.includes("荔枝"))
-        );
-        specieList.value = litchiList;
-        // 返回空列表时,重置已选品类,避免保留上一次默认项
-        if (litchiList.length === 0) {
-            ruleForm.speciesItem = [];
-            return litchiList;
-        }
-        // //列表有值时,默认选中第一项
-        // if (litchiList.length > 0) {
-        //     const first = { value: litchiList[0].id, ...litchiList[0] };
-        //     ruleForm.speciesItem = [first];
-        //     // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
-        //     changeSpecie(first);
-        // }
-        return litchiList;
-    });
-}
-
-const baseTypeList = ref([]);
-function getBaseTypeList() {
-    return VE_API.farm.fetchBaseTypeList().then(({ data }) => {
-        baseTypeList.value = data || [];
-        return data;
-    });
-}
-
-async function changeSpecie(v) {
-    const current = Array.isArray(v) ? v[v.length - 1] : v;
-    // 选择后立即更新该字段的校验状态,避免错误提示残留
-    nextTick(() => {
-        if (!ruleFormRef.value) return;
-        if (Array.isArray(ruleForm.speciesItem) && ruleForm.speciesItem.length > 0) {
-            ruleFormRef.value.clearValidate(["speciesItem"]);
-            return;
-        }
-        ruleFormRef.value.validateField("speciesItem");
-    });
-    // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
-        ruleForm.name = farmCity.value + current.name + "农场";
-    }
-}
-
-/**
- * 格式化日期为 YYYY-MM-DD 格式
- * @param {Date} date - 日期对象
- * @returns {string} 格式化后的日期字符串
- */
-function formatDate(date) {
-    const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, "0");
-    const day = String(date.getDate()).padStart(2, "0");
-    return `${year}-${month}-${day}`;
-}
-
-function backgToHome() {
-    ruleFormRef.value?.resetFields();
-
-    // 根据来源页面决定返回目标
-    const fromPage = route.query?.from;
-    if (route.query.miniJson) {
-        const json = JSON.parse(route.query.miniJson);
-        if (json.isMini) {
-            const dropdownGardenItem = ref({
-                organId: json.farmId,
-                periodId: json.periodId,
-                name: json.name,
-                page: "create_farm",
-                showFarmSelect: true,
-                images: json.images,
-            });
-            wx.miniProgram.reLaunch({
-                url: `/pages/subPages/new_recognize/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
-            });
-        }
-        
-        json?.from && router.replace(`/${json.from}`);
-        // const paramsPage = JSON.parse(json.paramsPage);
-        // if (paramsPage.isFarmer) {
-        //     router.replace('/home');
-        //     return;
-        // }
-    } else {
-        if (fromPage && fromPage !== "details") {
-            if (route.query?.type === "farmer") {
-                router.go(-1);
-            } else {
-                router.replace(`/${fromPage}`);
-            }
-            return;
-        } else {
-            router.go(-1);
-        }
-    }
-}
-
-// 处理面积按键输入 - 只允许数字和小数点
-function handleMianjiKeypress(event) {
-    // 如果是从编辑地图返回的,不允许手动修改
-    if (isFromEditMap.value) {
-        event.preventDefault();
-        return;
-    }
-
-    const charCode = event.which ? event.which : event.keyCode;
-    const char = String.fromCharCode(charCode);
-
-    // 允许数字 (0-9) 和小数点 (.)
-    if (!/^[0-9.]$/.test(char)) {
-        event.preventDefault();
-        return;
-    }
-
-    // 如果已经有小数点,不允许再输入小数点
-    const currentValue = ruleForm.mu || "";
-    if (char === "." && currentValue.includes(".")) {
-        event.preventDefault();
-        return;
-    }
-
-    // 不允许以小数点开头
-    if (char === "." && !currentValue) {
-        event.preventDefault();
-        return;
-    }
-}
-
-// 处理农场名称输入
-function handleFarmNameInput() {
-    // 标记用户已手动修改过农场名称
-    isFarmNameManuallyModified.value = true;
-}
-
-// 根据地址更新农场名称(如果满足条件)
-function updateFarmNameIfNeeded() {
-    // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
-    const mainSpecies = Array.isArray(ruleForm.speciesItem)
-        ? ruleForm.speciesItem[0]
-        : ruleForm.speciesItem;
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
-        ruleForm.name = farmCity.value + mainSpecies.name + "农场";
-    }
-}
-
-// 回填编辑数据
-function populateEditData() {
-    const editData = store.state.home.editFarmData;
-    if (!editData) {
-        return;
-    }
-
-    // 回填基本信息
-    ruleForm.name = editData.name || "";
-    ruleForm.fzr = editData.fzr || "";
-    ruleForm.tel = editData.tel || "";
-    ruleForm.baseType = editData.baseType || null;
-    ruleForm.defaultFarm = editData.defaultOption || 0;
-    ruleForm.mu = editData.mianji || "";
-    ruleForm.address = editData.address || "";
-
-    // 设置地图中心点
-    if (editData.pointWkt) {
-        centerPoint.value = editData.pointWkt;
-        const arr = convertPointToArray(editData.pointWkt);
-        indexMap.setMapPosition(arr);
-    }
-
-    // 处理地块数据
-    if (editData.geomWkt) {
-        polygonArr.value = [editData.geomWkt];
-        indexMap.setAreaGeometry([editData.geomWkt]);
-        hasDefaultPolygon.value = true;
-        isFromEditMap.value = true; // 编辑模式下锁定面积输入
-    }
-
-    // 处理作物数据(兼容多选)
-    if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
-        const selected = [];
-        const speciesIdSet = new Set();
-        editData.regionList.forEach((region) => {
-            const targetId = region.speciesId;
-            if (targetId == null) return;
-            // 去重:同一个 speciesId 只保留一条
-            const speciesKey = String(targetId);
-            if (speciesIdSet.has(speciesKey)) return;
-            const match = specieList.value.find((s) => String(s.id) === String(targetId));
-            if (match) {
-                speciesIdSet.add(speciesKey);
-                selected.push({
-                    value: match.id,
-                    ...match,
-                });
-            }
-        });
-        if (selected.length > 0) {
-            ruleForm.speciesItem = selected;
-        }
-    } else if (editData.speciesId && editData.speciesName) {
-        // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
-        const species = {
-            value: editData.speciesId,
-            id: editData.speciesId,
-            name: editData.speciesName,
-            defaultContainerId: editData.containerId,
-        };
-        ruleForm.speciesItem = [species];
-    }
-
-    // 设置地址信息
-    if (editData.district) {
-        try {
-            const districtInfo = JSON.parse(editData.district);
-            pointAddress.value = districtInfo.province + districtInfo.city + (districtInfo?.district || '');
-        } catch (e) {
-            console.warn("解析地址信息失败:", e);
-        }
-    }
-}
-
-// 处理面积输入
-let mianjiInputTimer = null;
-function handleMianjiInput(value) {
-    // 如果是从编辑地图返回的,不允许手动修改
-    if (isFromEditMap.value) {
-        return;
-    }
-
-    // 过滤非法字符,只保留数字和小数点
-    let filteredValue = value.replace(/[^\d.]/g, "");
-
-    // 确保只有一个小数点
-    const parts = filteredValue.split(".");
-    if (parts.length > 2) {
-        filteredValue = parts[0] + "." + parts.slice(1).join("");
-    }
-
-    // 限制小数点后最多2位
-    if (parts.length === 2 && parts[1].length > 2) {
-        filteredValue = parts[0] + "." + parts[1].substring(0, 2);
-    }
-
-    // 更新输入框的值(如果被过滤了)
-    if (filteredValue !== value) {
-        ruleForm.mu = filteredValue;
-        return;
-    }
-
-    // 清除之前的定时器
-    if (mianjiInputTimer) {
-        clearTimeout(mianjiInputTimer);
-    }
-
-    // 防抖处理,用户停止输入500ms后再更新地块
-    mianjiInputTimer = setTimeout(() => {
-        const mu = parseFloat(filteredValue);
-
-        // 验证输入的有效性
-        if (!mu || isNaN(mu) || mu <= 0) {
-            return;
-        }
-
-        // 根据亩数重新生成地块
-        if (centerPoint.value) {
-            const arr = convertPointToArray(centerPoint.value);
-            const squareData = generateSquarePolygonByMu(arr, mu);
-
-            const geometryData = {
-                geometryArr: [squareData.wkt],
-                mianji: squareData.area,
-            };
-
-            // 清除旧地块
-            indexMap.clearLayer();
-            // 绘制新地块
-            indexMap.setAreaGeometry(geometryData.geometryArr);
-
-            // 更新状态
-            polygonArr.value = geometryData.geometryArr;
-            // 标记已创建默认地块
-            hasDefaultPolygon.value = true;
-        }
-    }, 500);
-}
-</script>
-
-<style lang="scss" scoped>
-::v-deep {
-    .el-form-item--default {
-        margin-bottom: 20px;
-    }
-}
-
-.create-farm {
-    position: relative;
-    width: 100%;
-    height: 100vh;
-    overflow: hidden;
-
-    .map-container {
-        width: 100%;
-        height: calc(100% - 320px);
-    }
-
-    .checkbox {
-        padding: 0 12px 6px;
-        font-size: 15px;
-    }
-
-    .farm-content {
-        position: absolute;
-        top: 40px;
-        left: 0;
-        width: 100%;
-        height: calc(100% - 40px);
-        pointer-events: none;
-        z-index: 2;
-
-        .top-mask {
-            height: 100px;
-            position: absolute;
-            z-index: 2;
-            top: 0;
-            left: 0;
-            width: 100%;
-            background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
-        }
-    }
-
-    .farm-filter {
-        pointer-events: all;
-        margin: 12px;
-        position: relative;
-        z-index: 10;
-        background: rgba(0, 0, 0, 0.3);
-        border-radius: 20px;
-        border: 1px solid rgba(255, 255, 255, 0.4);
-
-        &::before {
-            content: "";
-            position: absolute;
-            left: 12px;
-            top: 9px;
-            width: 14px;
-            height: 14px;
-            background: url("@/assets/img/home/search.png") no-repeat center center / 100% 100%;
-        }
-
-        ::v-deep {
-            .el-select__wrapper {
-                background: none;
-                box-shadow: none;
-                padding-left: 34px;
-                font-size: 12px;
-
-                .el-select__selected-item,
-                .el-select__placeholder,
-                .el-select__input {
-                    color: rgba(255, 255, 255, 0.6);
-
-                    &.is-transparent {
-                        color: #ccc;
-                        font-size: 12px;
-                    }
-                }
-            }
-
-            .el-select {
-                transition: all 0.3s;
-
-                .el-input.is-focus .el-input__wrapper {
-                    box-shadow: none !important;
-                }
-            }
-
-            .el-input {
-                .el-input__wrapper {
-                    background: none;
-                    box-shadow: none;
-                    padding-left: 18px;
-                    font-size: 11px;
-
-                    .el-input__inner {
-                        color: rgba(255, 255, 255, 0.6);
-                    }
-
-                    &.is-focus {
-                        .el-input__inner {
-                            color: #ccc;
-                            font-size: 11px;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    .create-wrap {
-        position: absolute;
-        bottom: 0px;
-        left: 0;
-        width: 100%;
-        background: linear-gradient(180deg, transparent 0%, #f5f7fb 20%);
-    }
-
-    .create-box {
-        pointer-events: all;
-        margin: 0 12px 10px 12px;
-        width: calc(100% - 24px);
-        background: #e0f1fe;
-        border-radius: 14px;
-
-        .box-content {
-            position: relative;
-
-            &::after {
-                position: absolute;
-                right: 10px;
-                top: 2px;
-                content: "";
-                width: 79px;
-                height: 72px;
-                background: url("@/assets/img/home/creat-bg.png") no-repeat center / 100% 100%;
-            }
-        }
-
-        .create-title {
-            display: flex;
-            align-items: center;
-            padding: 12px 6px 12px 12px;
-            color: #0089f5;
-            font-size: 18px;
-            font-weight: bold;
-
-            .title-icon {
-                width: 18px;
-                padding-right: 10px;
-            }
-        }
-
-        .create-content {
-            background: #fff;
-            border-radius: 14px;
-            padding: 12px;
-            position: relative;
-            z-index: 2;
-
-            .create-from {
-
-                .select-wrap {
-                    display: flex;
-
-                    // &.specie-wrap {
-                    //     flex: 1;
-                    //     gap: 12px;
-
-                    //     .specie-select {
-                    //         min-width: 56px;
-                    //         width: 56px;
-                    //     }
-                    // }
-
-                    // width: 90%;
-                    
-                    ::v-deep {
-                        .el-input__wrapper {
-                            background: none;
-                            box-shadow: none;
-                        }
-
-                        .el-input__inner {
-                            font-size: 14px;
-                            color: rgba(0, 0, 0, 0.5);
-                        }
-
-                        .el-select__wrapper {
-                            background: none;
-                            box-shadow: none;
-                            gap: 2px;
-                            padding: 4px 2px;
-                            justify-content: center;
-                        }
-
-                        // .el-select__selection {
-                        //     flex: none;
-                        //     width: min-content;
-                        // }
-
-                        .el-select__placeholder {
-                            color: #000;
-                            position: static;
-                            transform: none;
-                            width: fit-content;
-                        }
-                    }
-
-                    &.client-wrap {
-                        ::v-deep {
-                            .el-select__wrapper {
-                                justify-content: flex-start;
-                            }
-                        }
-                    }
-
-                    // .select-item {
-                    //     min-width: 80px;
-                    //     margin-left: 10px;
-                    // }
-                }
-
-                ::v-deep {
-                    .el-form-item__label {
-                        color: #000;
-                    }
-
-                    .el-form-item__error {
-                        top: 117%;
-                    }
-
-                    .el-form-item {
-                        position: relative;
-
-                        &::after {
-                            content: "";
-                            position: absolute;
-                            left: 60px;
-                            bottom: -5px;
-                            width: calc(100% - 60px);
-                            height: 1px;
-                            background: rgba(0, 0, 0, 0.08);
-                        }
-                    }
-
-                    .el-input__wrapper {
-                        box-shadow: none;
-                        padding: 1px 6px;
-                    }
-                }
-
-                .area-box {
-                    display: flex;
-                    align-items: center;
-                    width: 100%;
-
-                    .unit {
-                        padding-right: 10px;
-                    }
-                }
-
-                .position-wrap {
-                    width: 100%;
-                    display: flex;
-                    justify-content: space-between;
-                    align-items: center;
-
-                    .draw-btn {
-                        flex: none;
-                        padding: 0 12px;
-                        height: 30px;
-                        line-height: 30px;
-                        box-sizing: border-box;
-                        color: #2199f8;
-                        border: 1px solid #2199f8;
-                        background: rgba(33, 153, 248, 0.1);
-                        border-radius: 20px;
-                        font-size: 12px;
-                    }
-                }
-            }
-
-            .create-btn {
-                display: flex;
-                align-items: center;
-                width: 100%;
-                padding-top: 10px;
-
-                .btn-item {
-                    flex: 1;
-                    text-align: center;
-                    padding: 0 11px;
-                    height: 40px;
-                    line-height: 40px;
-                    border-radius: 34px;
-                    font-size: 16px;
-                    box-sizing: border-box;
-
-                    &.sencond-btn {
-                        border: 1px solid rgba(153, 153, 153, 0.5);
-                        color: #666666;
-                    }
-
-                    &.primary-btn {
-                        background: linear-gradient(180deg, #76c3ff, #2199f8);
-                        color: #fff;
-                    }
-                }
-
-                .btn-item+.btn-item {
-                    margin-left: 5px;
-                }
-            }
-        }
-    }
-}
-</style>
-
-<style lang="scss">
-.location-search-popper {
-    .el-select-dropdown__list {
-        max-width: 96vw;
-        overflow-x: auto;
-    }
-
-    .sub-title {
-        padding-left: 6px;
-        font-size: 12px;
-        color: #ccc;
-    }
-}
-</style>

+ 168 - 195
src/views/old_mini/create_farm/index copy.vue

@@ -39,17 +39,12 @@
                                         </div>
                                     </el-form-item>
                                     <el-form-item label="种植作物" prop="speciesItem">
-                                        <div class="select-wrap specie-wrap">
-                                            <el-select @change="changeSpecie" class="select-item specie-select"
-                                                v-model="ruleForm.speciesItem" placeholder="品类">
+                                        <div class="select-wrap">
+                                            <el-select v-model="ruleForm.speciesItem" filterable class="select-item specie-select"
+                                                multiple :max-collapse-tags="3" collapse-tags placeholder="种植作物(可多选)" @change="changeSpecie">
                                                 <el-option v-for="(item, index) in specieList" :key="index"
                                                     :label="item.name" :value="{ value: item.id, ...item }" />
                                             </el-select>
-                                            <el-select v-model="ruleForm.typeIds" multiple collapse-tags
-                                                placeholder="品种(可多选)" class="period-select select-item period-select">
-                                                <el-option v-for="(item, index) in fruitsList" :key="index"
-                                                    :label="item.name" :value="item.id" />
-                                            </el-select>
                                         </div>
                                     </el-form-item>
                                     <el-form-item label="联系人" prop="fzr">
@@ -60,7 +55,7 @@
                                     </el-form-item>
                                     <el-form-item label="联系电话" prop="tel">
                                         <div class="area-box">
-                                            <el-input placeholder="请输入联系人电话" v-model="ruleForm.tel" autocomplete="off"
+                                            <el-input placeholder="请输入联系人电话" type="number" v-model="ruleForm.tel" autocomplete="off"
                                                 style="width: fit-content" />
                                         </div>
                                     </el-form-item>
@@ -73,33 +68,16 @@
                                             <div class="unit">亩</div>
                                         </div>
                                     </el-form-item>
-                                    <el-form-item v-if="paramsType !== 'farmer'" label="客户类型" prop="userType"
-                                        class="select-wrap client-wrap">
-                                        <el-select class="select-item" v-model="ruleForm.userType" placeholder="请选择">
-                                            <el-option label="普通用户" :value="1" />
-                                            <el-option label="托管用户" :value="2" />
-                                            <el-option label="优质客户" :value="3" />
-                                        </el-select>
-                                    </el-form-item>
                                     <el-form-item label="农场名称" prop="name">
                                         <el-input placeholder="请输入您的农场名称" v-model="ruleForm.name" autocomplete="off"
                                             @input="handleFarmNameInput" />
                                     </el-form-item>
-                                    <el-form-item v-if="paramsType === 'farmer'" label="基地类别" prop="baseType"
-                                        class="select-wrap client-wrap">
+                                    <!-- <el-form-item label="基地类别" prop="baseType" class="select-wrap client-wrap">
                                         <el-select placeholder="请选择" v-model="ruleForm.baseType" autocomplete="off">
                                             <el-option v-for="(item, index) in baseTypeList" :key="index"
                                                 :label="item.name" :value="item.id" />
                                         </el-select>
-                                    </el-form-item>
-                                    <!-- <Checkbox
-                                        v-if="paramsType !== 'client' && curRole == 0"
-                                        class="checkbox"
-                                        icon-size="18px"
-                                        shape="square"
-                                        v-model="ruleForm.defaultFarm"
-                                        >是否勾选为默认农场</Checkbox
-                                    > -->
+                                    </el-form-item> -->
                                 </el-form>
                             </div>
                             <div class="create-btn">
@@ -130,7 +108,7 @@ import customHeader from "@/components/customHeader.vue";
 import IndexMap from "./map/index.js";
 import { useRoute, useRouter } from "vue-router";
 import { mapLocation } from "./map/index.js";
-import { onMounted, ref, reactive, watch, onActivated, nextTick,onDeactivated } from "vue";
+import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated } from "vue";
 import { useStore } from "vuex";
 import { convertPointToArray } from "@/utils/index";
 import { ElMessage } from "element-plus";
@@ -259,7 +237,6 @@ onMounted(() => {
     // 清空地块和面积数据
     polygonArr.value = null;
     ruleForm.mu = "";
-    ruleForm.typeIds = [];
 
     // 如果是编辑模式,等待品类列表加载完成后再回填数据
     if (route.query.type === "edit" && store.state.home.editFarmData) {
@@ -294,6 +271,7 @@ onActivated(() => {
         isFarmNameManuallyModified.value = false;
         // 清空上一次地块缓存
         store.commit("home/SET_FARM_POLYGON", null);
+        getSpecieList();
     }
     // 确保地图已初始化,使用 nextTick 等待 DOM 更新
     nextTick(() => {
@@ -311,7 +289,6 @@ onActivated(() => {
             }, 100);
             return;
         }
-
         handleMapUpdate();
     });
 });
@@ -327,6 +304,7 @@ function handleMapUpdate() {
         store.commit("home/SET_FARM_POLYGON", null);
         isFromEditMap.value = false; // 从home进入,可以手动输入
         hasDefaultPolygon.value = false; // 重置默认地块状态
+        locationVal.value = null;
 
         centerPoint.value = store.state.home.miniUserLocationPoint;
         const arr = convertPointToArray(centerPoint.value);
@@ -338,7 +316,6 @@ function handleMapUpdate() {
         // 不再自动生成默认地块,等待用户点击"新增地块"
         polygonArr.value = null;
         ruleForm.mu = "";
-        ruleForm.typeIds = [];
         ruleForm.defaultFarm = 0;
 
         return; // 直接返回,不执行下面的逻辑
@@ -454,12 +431,11 @@ const ruleFormRef = ref(null);
 const ruleForm = reactive({
     address: "",
     mu: "",
-    speciesItem: null,
-    typeIds: [],
+    // 多选品类:数组
+    speciesItem: [],
     name: "",
     fzr: "",
     tel: "",
-    userType: 1,
     baseType: null,
     defaultFarm: 0, // 0:否 1:是
 });
@@ -481,13 +457,11 @@ const validateMianji = (rule, value, callback) => {
 
 const rules = reactive({
     address: [{ required: true, message: "请选择农场位置", trigger: "blur" }],
-    userType: [{ required: true, message: "请选择客户类型", trigger: "blur" }],
     mu: [
         { required: true, message: "请输入农场面积", trigger: "blur" },
         { validator: validateMianji, trigger: ["blur", "change"] },
     ],
-    speciesItem: [{ required: true, message: "请选择品类", trigger: "blur" }],
-    typeIds: [{ required: true, message: "请选择品种", trigger: "blur" }],
+    speciesItem: [{ required: true, message: "请选择种植作物", trigger: ["blur", "change"] }],
     name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
     fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
     tel: [
@@ -508,11 +482,16 @@ const submitForm = (formEl) => {
     if (!formEl) return;
     formEl.validate((valid) => {
         if (valid) {
+            const speciesList = Array.isArray(ruleForm.speciesItem) ? ruleForm.speciesItem : (ruleForm.speciesItem ? [ruleForm.speciesItem] : []);
+            const mainSpecies = speciesList[0];
+            const speciesContainer = speciesList.map((item) => ({
+                speciesId: item?.id ?? null,
+                containerId: item?.defaultContainerId ?? null,
+            }));
             const params = {
                 ...ruleForm,
                 wkt: centerPoint.value,
-                speciesId: ruleForm.speciesItem?.id,
-                containerId: ruleForm.speciesItem?.defaultContainerId,
+                speciesContainer,
                 agriculturalCreate: route.query.type === "client" ? 1 : 0,
                 // 编辑时geom不是数组,新增时是数组
                 geom:
@@ -525,33 +504,36 @@ const submitForm = (formEl) => {
 
             // 如果是编辑模式,添加农场ID
             if (route.query.type === "edit" && route.query.farmId) {
-                params.farmId = route.query.farmId;
+                params.id = route.query.farmId;
             }
 
-            if (route.query.type === "client" || route.query.type === "farmer") {
+            // let pageData = null;
+            // if (route.query.miniJson) {
+            //     const json = JSON.parse(route.query.miniJson);
+            //     console.log('json', json);
+            //     // pageData = JSON.parse(json.paramsPage);
+            // }
+
+            if (route.query.type !== "edit") {
                 // 处理 geom 参数,如果是数组需要序列化
                 const queryParams = {
                     ...params,
                     ...route.query,
                 };
-
                 // 如果 geom 是数组,需要序列化为 JSON 字符串
                 if (Array.isArray(queryParams.geom)) {
                     queryParams.geom = JSON.stringify(queryParams.geom);
                 }
-                delete queryParams.speciesItem;
-                queryParams.speciesName = ruleForm.speciesItem?.name;
-                // 根据 typeIds 获取对应的名字
-                if (ruleForm.typeIds && ruleForm.typeIds.length > 0 && fruitsList.value.length > 0) {
-                    queryParams.typeNames = JSON.stringify(ruleForm.typeIds
-                        .map(id => {
-                            const typeItem = fruitsList.value.find(item => item.id === id);
-                            return typeItem ? typeItem.name : null;
-                        })
-                        .filter(name => name !== null));
+                // speciesContainer 为对象时需序列化,否则 query 传递后无法正确解析
+                if (queryParams.speciesContainer && typeof queryParams.speciesContainer === 'object') {
+                    queryParams.speciesContainer = JSON.stringify(queryParams.speciesContainer);
                 }
-                console.log('queryParams', queryParams.typeNames)
+                delete queryParams.speciesItem;
+                queryParams.speciesName = mainSpecies?.name;
 
+                // if (pageData?.type === 'add') {
+                //     queryParams.invite = true;
+                // }
                 router.push({
                     path: "/prescription",
                     query: queryParams,
@@ -559,66 +541,55 @@ const submitForm = (formEl) => {
                 return;
             }
 
-            // if(route.query.type === "farmer") {
-            //     VE_API.farm.saveFarm({...params, expertMiniUserId: route.query.expertMiniUserId}).then((res) => {
-            //         if (res.code === 0) {
+            // const apiCall = route.query.type === "edit" ? VE_API.basic_farm.saveBasicFarmInfoByExpertV3({...params, expertMiniUserId: '81881'}) : VE_API.farm.saveFarm(params);
+
+            // apiCall.then((res) => {
+            //     if (res.code === 0) {
+            //         ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
+            //         // 重置表单和地块数据
+            //         ruleFormRef.value.resetFields();
+            //         store.commit("home/SET_FARM_POLYGON", null);
+            //         store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
+            //         polygonArr.value = null;
+            //         isFromEditMap.value = false;
+
+            //         if (route.query.type !== "edit" && curRole == 0) {
+            //             localStorage.setItem("selectedFarmId", res.data.id);
+            //             localStorage.setItem("selectedFarmName", res.data.name);
+            //         }
+
+            //         // 根据来源页面决定跳转目标
+            //         const fromPage = route.query.from;
+            //         if (fromPage && fromPage !== "details") {
+            //             // 如果是从monitor页面来的
+            //             router.replace(`/${fromPage}`);
+            //         } else if (fromPage === "details") {
             //             router.go(-1);
             //         } else {
-            //             ElMessage.error(res.msg || '创建失败');
+            //             if (route.query.miniJson) {
+            //                 const json = JSON.parse(route.query.miniJson);
+            //                 //上传图片
+            //                 VE_API.ali
+            //                     .uploadImg({
+            //                         farmId: res.data.id,
+            //                         images: json.images,
+            //                         uploadDate: formatDate(new Date()),
+            //                     })
+            //                     .then(({ code, msg }) => {
+            //                         if (code === 0) {
+            //                             showSuccessPopup.value = true;
+            //                         } else {
+            //                             ElMessage.error(msg);
+            //                         }
+            //                     });
+            //             } else {
+            //                 router.replace(`/growth_report`);
+            //             }
             //         }
-            //     });
-            //     return;
-            // }
-
-            const apiCall = route.query.type === "edit" ? VE_API.farm.updateFarm(params) : VE_API.farm.saveFarm(params);
-
-            apiCall.then((res) => {
-                if (res.code === 0) {
-                    ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
-                    // 重置表单和地块数据
-                    ruleFormRef.value.resetFields();
-                    store.commit("home/SET_FARM_POLYGON", null);
-                    store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
-                    polygonArr.value = null;
-                    isFromEditMap.value = false;
-
-                    if (route.query.type !== "edit" && curRole == 0) {
-                        localStorage.setItem("selectedFarmId", res.data.id);
-                        localStorage.setItem("selectedFarmName", res.data.name);
-                    }
-
-                    // 根据来源页面决定跳转目标
-                    const fromPage = route.query.from;
-                    if (fromPage && fromPage !== "details") {
-                        // 如果是从monitor页面来的
-                        router.replace(`/${fromPage}`);
-                    } else if (fromPage === "details") {
-                        router.go(-1);
-                    } else {
-                        if (route.query.miniJson) {
-                            const json = JSON.parse(route.query.miniJson);
-                            //上传图片
-                            VE_API.ali
-                                .uploadImg({
-                                    farmId: res.data.id,
-                                    images: json.images,
-                                    uploadDate: formatDate(new Date()),
-                                })
-                                .then(({ code, msg }) => {
-                                    if (code === 0) {
-                                        showSuccessPopup.value = true;
-                                    } else {
-                                        ElMessage.error(msg);
-                                    }
-                                });
-                        } else {
-                            router.replace(`/home`);
-                        }
-                    }
-                } else {
-                    ElMessage.error(res.msg);
-                }
-            });
+            //     } else {
+            //         ElMessage.error(res.msg);
+            //     }
+            // });
         }
     });
 };
@@ -717,16 +688,23 @@ const specieList = ref([]);
 function getSpecieList() {
     return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
         const list = Array.isArray(data) ? data : [];
-        // 只保留名称包含“荔枝”的品类
-        const litchiList = list.filter((item) => item?.name && item.name.includes("荔枝"));
+        // 只保留名称包含“水稻”或“荔枝”的品类
+        const litchiList = list.filter(
+            (item) => item?.name && (item.name.includes("水稻") || item.name.includes("荔枝")|| item.name.includes("大豆")|| item.name.includes("茶叶"))
+        );
         specieList.value = litchiList;
-        // 非编辑模式且当前未选择品类时,默认选中第一项
-        if (route.query.type !== "edit" && !ruleForm.speciesItem && litchiList.length > 0) {
-            const first = { value: litchiList[0].id, ...litchiList[0] };
-            ruleForm.speciesItem = first;
-            // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
-            changeSpecie(first);
+        // 返回空列表时,重置已选品类,避免保留上一次默认项
+        if (litchiList.length === 0) {
+            ruleForm.speciesItem = [];
+            return litchiList;
         }
+        // //列表有值时,默认选中第一项
+        // if (litchiList.length > 0) {
+        //     const first = { value: litchiList[0].id, ...litchiList[0] };
+        //     ruleForm.speciesItem = [first];
+        //     // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
+        //     changeSpecie(first);
+        // }
         return litchiList;
     });
 }
@@ -740,36 +718,22 @@ function getBaseTypeList() {
 }
 
 async function changeSpecie(v) {
-    // const data = await checkExistsEnabledScheme(v.defaultContainerId);
-    // if (!data) {
-    //     ElMessage.warning("该品类暂无可用方案,请选择其他品类");
-    //     ruleForm.speciesItem = "";
-    //     ruleForm.typeIds = [];
-    //     fruitsList.value = [];
-    //     return;
-    // }
-    getFruitsTypeItemList(v.id);
-    // 清空品种选择
-    ruleForm.typeIds = [];
+    const current = Array.isArray(v) ? v[v.length - 1] : v;
+    // 选择后立即更新该字段的校验状态,避免错误提示残留
+    nextTick(() => {
+        if (!ruleFormRef.value) return;
+        if (Array.isArray(ruleForm.speciesItem) && ruleForm.speciesItem.length > 0) {
+            ruleFormRef.value.clearValidate(["speciesItem"]);
+            return;
+        }
+        ruleFormRef.value.validateField("speciesItem");
+    });
     // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value) {
-        ruleForm.name = farmCity.value + v.name + "农场";
+    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
+        ruleForm.name = farmCity.value + current.name + "农场";
     }
 }
 
-//判断是否存在可用方案
-async function checkExistsEnabledScheme(containerId) {
-    const { data } = await VE_API.home.existsEnabledScheme({ containerId });
-    return data;
-}
-
-const fruitsList = ref([]);
-function getFruitsTypeItemList(parentId) {
-    VE_API.farm.fruitsTypeItemList({ parentId }).then(({ data }) => {
-        fruitsList.value = data;
-    });
-}
-
 /**
  * 格式化日期为 YYYY-MM-DD 格式
  * @param {Date} date - 日期对象
@@ -786,7 +750,7 @@ function backgToHome() {
     ruleFormRef.value?.resetFields();
 
     // 根据来源页面决定返回目标
-    const fromPage = route.query.from;
+    const fromPage = route.query?.from;
     if (route.query.miniJson) {
         const json = JSON.parse(route.query.miniJson);
         if (json.isMini) {
@@ -802,14 +766,16 @@ function backgToHome() {
                 url: `/pages/subPages/new_recognize/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
             });
         }
-        const paramsPage = JSON.parse(json.paramsPage);
-        if(paramsPage.isFarmer){
-            router.replace('/home');
-            return;
-        }
+
+        json?.from && router.replace(`/${json.from}`);
+        // const paramsPage = JSON.parse(json.paramsPage);
+        // if (paramsPage.isFarmer) {
+        //     router.replace('/home');
+        //     return;
+        // }
     } else {
         if (fromPage && fromPage !== "details") {
-            if (route.query.type === "farmer") {
+            if (route.query?.type === "farmer") {
                 router.go(-1);
             } else {
                 router.replace(`/${fromPage}`);
@@ -861,8 +827,11 @@ function handleFarmNameInput() {
 // 根据地址更新农场名称(如果满足条件)
 function updateFarmNameIfNeeded() {
     // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && ruleForm.speciesItem && farmCity.value) {
-        ruleForm.name = farmCity.value + ruleForm.speciesItem.name + "农场";
+    const mainSpecies = Array.isArray(ruleForm.speciesItem)
+        ? ruleForm.speciesItem[0]
+        : ruleForm.speciesItem;
+    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
+        ruleForm.name = farmCity.value + mainSpecies.name + "农场";
     }
 }
 
@@ -897,23 +866,37 @@ function populateEditData() {
         isFromEditMap.value = true; // 编辑模式下锁定面积输入
     }
 
-    // 处理作物数据
-    if (editData.speciesId && editData.speciesName) {
-        // 设置品类 - 需要匹配模板中的数据结构
-        ruleForm.speciesItem = {
+    // 处理作物数据(兼容多选)
+    if (Array.isArray(editData.regionList) && editData.regionList.length > 0) {
+        const selected = [];
+        const speciesIdSet = new Set();
+        editData.regionList.forEach((region) => {
+            const targetId = region.speciesId;
+            if (targetId == null) return;
+            // 去重:同一个 speciesId 只保留一条
+            const speciesKey = String(targetId);
+            if (speciesIdSet.has(speciesKey)) return;
+            const match = specieList.value.find((s) => String(s.id) === String(targetId));
+            if (match) {
+                speciesIdSet.add(speciesKey);
+                selected.push({
+                    value: match.id,
+                    ...match,
+                });
+            }
+        });
+        if (selected.length > 0) {
+            ruleForm.speciesItem = selected;
+        }
+    } else if (editData.speciesId && editData.speciesName) {
+        // 兼容旧数据:只有单个品类字段时,按原逻辑回显一个
+        const species = {
             value: editData.speciesId,
             id: editData.speciesId,
             name: editData.speciesName,
             defaultContainerId: editData.containerId,
         };
-
-        // 获取品种列表
-        getFruitsTypeItemList(editData.speciesId);
-
-        // 设置品种
-        if (editData.typeIds) {
-            ruleForm.typeIds = editData.typeIds;
-        }
+        ruleForm.speciesItem = [species];
     }
 
     // 设置地址信息
@@ -1155,26 +1138,21 @@ function handleMianjiInput(value) {
             z-index: 2;
 
             .create-from {
-                
+
                 .select-wrap {
                     display: flex;
-                    &.specie-wrap {
-                        flex: 1;
-                        gap: 12px;
-                        .specie-select {
-                            min-width: 56px;
-                            width: 56px;
-                        }
-                        .period-select {
-                            ::v-deep {
-                                .el-select__selection {
-                                    flex: 1;
-                                }
-                            }
-                        }
-                    }
 
-                    // width: 86%;
+                    // &.specie-wrap {
+                    //     flex: 1;
+                    //     gap: 12px;
+
+                    //     .specie-select {
+                    //         min-width: 56px;
+                    //         width: 56px;
+                    //     }
+                    // }
+
+                    width: 100%;
                     ::v-deep {
                         .el-input__wrapper {
                             background: none;
@@ -1194,10 +1172,10 @@ function handleMianjiInput(value) {
                             justify-content: center;
                         }
 
-                        .el-select__selection {
-                            flex: none;
-                            width: fit-content;
-                        }
+                        // .el-select__selection {
+                        //     flex: none;
+                        //     width: min-content;
+                        // }
 
                         .el-select__placeholder {
                             color: #000;
@@ -1216,15 +1194,9 @@ function handleMianjiInput(value) {
                     }
 
                     // .select-item {
-                    //     width: fit-content;
+                    //     min-width: 80px;
+                    //     margin-left: 10px;
                     // }
-                    .period-select {
-                        margin-left: 6px;
-                    }
-
-                    .select-item {
-                        min-width: 76px;
-                    }
                 }
 
                 ::v-deep {
@@ -1267,6 +1239,7 @@ function handleMianjiInput(value) {
                 }
 
                 .position-wrap {
+                    width: 100%;
                     display: flex;
                     justify-content: space-between;
                     align-items: center;

+ 33 - 354
src/views/old_mini/create_farm/index.vue

@@ -33,30 +33,14 @@
                                         <div class="position-wrap">
                                             <el-input placeholder="农场位置" readonly v-model="ruleForm.address"
                                                 autocomplete="off" />
-                                            <div class="draw-btn" @click="toSubPage">
-                                                {{ hasDefaultPolygon ? "点击勾选地块" : "新增地块" }}
-                                            </div>
                                         </div>
                                     </el-form-item>
-                                    <el-form-item label="种植作物" prop="speciesItem">
+                                    <el-form-item label="种植作物">
                                         <div class="select-wrap">
-                                            <el-select v-model="ruleForm.speciesItem" filterable class="select-item specie-select"
-                                                multiple :max-collapse-tags="3" collapse-tags placeholder="种植作物(可多选)" @change="changeSpecie">
-                                                <el-option v-for="(item, index) in specieList" :key="index"
-                                                    :label="item.name" :value="{ value: item.id, ...item }" />
-                                            </el-select>
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="联系人" prop="fzr">
-                                        <div class="area-box">
-                                            <el-input placeholder="请输入联系人姓名" v-model="ruleForm.fzr" autocomplete="off"
-                                                style="width: fit-content" />
-                                        </div>
-                                    </el-form-item>
-                                    <el-form-item label="联系电话" prop="tel">
-                                        <div class="area-box">
-                                            <el-input placeholder="请输入联系人电话" type="number" v-model="ruleForm.tel" autocomplete="off"
-                                                style="width: fit-content" />
+                                            <div class="species-entry" @click="handleGoSelectCrop">
+                                                <span class="species-entry__text">点击选择作物</span>
+                                                <el-icon><ArrowRight /></el-icon>
+                                            </div>
                                         </div>
                                     </el-form-item>
                                     <el-form-item label="农场面积" prop="mu">
@@ -72,12 +56,6 @@
                                         <el-input placeholder="请输入您的农场名称" v-model="ruleForm.name" autocomplete="off"
                                             @input="handleFarmNameInput" />
                                     </el-form-item>
-                                    <!-- <el-form-item label="基地类别" prop="baseType" class="select-wrap client-wrap">
-                                        <el-select placeholder="请选择" v-model="ruleForm.baseType" autocomplete="off">
-                                            <el-option v-for="(item, index) in baseTypeList" :key="index"
-                                                :label="item.name" :value="item.id" />
-                                        </el-select>
-                                    </el-form-item> -->
                                 </el-form>
                             </div>
                             <div class="create-btn">
@@ -108,11 +86,9 @@ import customHeader from "@/components/customHeader.vue";
 import IndexMap from "./map/index.js";
 import { useRoute, useRouter } from "vue-router";
 import { mapLocation } from "./map/index.js";
-import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated } from "vue";
+import { onMounted, ref, reactive, watch, onActivated, nextTick, onDeactivated, computed } from "vue";
 import { useStore } from "vuex";
 import { convertPointToArray } from "@/utils/index";
-import { ElMessage } from "element-plus";
-import { Checkbox } from "vant";
 import tipPopup from "@/components/popup/tipPopup.vue";
 import wx from "weixin-js-sdk";
 import { transformFromGCJToWGS, transformFromWGSToGCJ } from "@/utils/WSCoordinate.js";
@@ -135,54 +111,10 @@ const handlePopupConfirm = () => {
 // 标记是否从编辑地图页面确认返回
 const isFromEditMap = ref(false);
 
-// 标记是否已创建默认地块
-const hasDefaultPolygon = ref(false);
-
 // 标记用户是否手动修改过农场名称
 const isFarmNameManuallyModified = ref(false);
 
 /**
- * 根据中心点生成指定边长的正方形地块WKT
- * @param {Array} center - 中心点坐标 [lng, lat]
- * @param {Number} sideLength - 正方形边长(米)
- * @returns {Object} 包含WKT字符串和面积(亩)的对象
- */
-function generateSquarePolygonBySideLength(center, sideLength) {
-    // 确保输入是数字类型
-    const lng = parseFloat(center[0]);
-    const lat = parseFloat(center[1]);
-
-    // 半边长(米)
-    const halfSide = sideLength / 2;
-
-    // 纬度方向:1度约等于111000米
-    const latDelta = halfSide / 111000;
-
-    // 经度方向:需要根据纬度调整,1度约等于111000 * cos(lat)米
-    const lngDelta = halfSide / (111000 * Math.cos((lat * Math.PI) / 180));
-
-    // 计算四个顶点(逆时针顺序)
-    const topLeft = [lng - lngDelta, lat + latDelta];
-    const topRight = [lng + lngDelta, lat + latDelta];
-    const bottomRight = [lng + lngDelta, lat - latDelta];
-    const bottomLeft = [lng - lngDelta, lat - latDelta];
-
-    // 生成MULTIPOLYGON格式的WKT(需要闭合,所以第一个点要重复)
-    // 明确使用join来格式化坐标点
-    const formatPoint = (point) => `${point[0]} ${point[1]}`;
-    const coordinates = [topLeft, topRight, bottomRight, bottomLeft, topLeft].map(formatPoint).join(", ");
-
-    // 注意:MULTIPOLYGON 和括号之间需要有一个空格
-    const wkt = `MULTIPOLYGON (((${coordinates})))`;
-
-    // 计算面积(亩): 边长200米 = 40,000平方米 ≈ 60亩
-    const areaInSquareMeters = sideLength * sideLength;
-    const areaInMu = (areaInSquareMeters / 666.67).toFixed(2);
-
-    return { wkt, area: areaInMu };
-}
-
-/**
  * 根据中心点和亩数生成正方形地块WKT
  * @param {Array} center - 中心点坐标 [lng, lat]
  * @param {Number} mu - 面积(亩)
@@ -226,11 +158,8 @@ onMounted(() => {
     // 清除上一次的地块数据,确保每次进入都是全新状态
     store.commit("home/SET_FARM_POLYGON", null);
     isFromEditMap.value = false; // 初始化时可以手动输入
-    hasDefaultPolygon.value = false; // 初始化时没有默认地块
 
     centerPoint.value = store.state.home.miniUserLocationPoint;
-    // const arr = convertPointToArray(centerPoint.value);
-    // getLocationName(`${arr[1]},${arr[0]}`);
     indexMap.initMap(centerPoint.value, mapContainer.value);
 
     // 不再初始化时绘制默认地块,等待用户点击"新增地块"按钮
@@ -238,13 +167,8 @@ onMounted(() => {
     polygonArr.value = null;
     ruleForm.mu = "";
 
-    // 如果是编辑模式,等待品类列表加载完成后再回填数据
     if (route.query.type === "edit" && store.state.home.editFarmData) {
-        getSpecieList().then(() => {
-            populateEditData();
-        });
-    } else {
-        getSpecieList();
+        populateEditData();
     }
     getBaseTypeList();
 });
@@ -266,12 +190,10 @@ onActivated(() => {
         // 重置与地块绘制相关的内部状态
         polygonArr.value = null;
         isFromEditMap.value = false;
-        hasDefaultPolygon.value = false;
         // 重置农场名称手动修改标记,允许自动生成农场名称
         isFarmNameManuallyModified.value = false;
         // 清空上一次地块缓存
         store.commit("home/SET_FARM_POLYGON", null);
-        getSpecieList();
     }
     // 确保地图已初始化,使用 nextTick 等待 DOM 更新
     nextTick(() => {
@@ -303,7 +225,6 @@ function handleMapUpdate() {
         // 清除旧的地块数据
         store.commit("home/SET_FARM_POLYGON", null);
         isFromEditMap.value = false; // 从home进入,可以手动输入
-        hasDefaultPolygon.value = false; // 重置默认地块状态
         locationVal.value = null;
 
         centerPoint.value = store.state.home.miniUserLocationPoint;
@@ -348,15 +269,12 @@ function handleMapUpdate() {
                 indexMap.setAreaGeometry(polygonData.geometryArr);
             });
             polygonArr.value = polygonData.geometryArr;
-            // 有地块数据时,标记已创建默认地块
-            hasDefaultPolygon.value = true;
         } else {
             // 用户在编辑页面删除了所有地块
             // 重置所有状态
             polygonArr.value = null;
             ruleForm.mu = "";
             isFromEditMap.value = false;
-            hasDefaultPolygon.value = false;
             ruleForm.defaultFarm = 0;
         }
     } else if (route.query.type === "edit" && store.state.home.editFarmData) {
@@ -369,7 +287,6 @@ function handleMapUpdate() {
             nextTick(() => {
                 indexMap.setAreaGeometry(polygonArr.value);
             });
-            // 保持 hasDefaultPolygon 状态
         }
     }
 }
@@ -423,7 +340,6 @@ const handleSearchRes = (v) => {
     farmCity.value = v.item?.city + (v.item?.district || "");
     // 地址修改后,如果满足条件则自动更新农场名称
     updateFarmNameIfNeeded();
-    getSpecieList();
 };
 
 // 表单
@@ -461,7 +377,6 @@ const rules = reactive({
         { required: true, message: "请输入农场面积", trigger: "blur" },
         { validator: validateMianji, trigger: ["blur", "change"] },
     ],
-    speciesItem: [{ required: true, message: "请选择种植作物", trigger: ["blur", "change"] }],
     name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
     fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
     tel: [
@@ -476,8 +391,6 @@ const rules = reactive({
     baseType: [{ required: true, message: "请选择基地类别", trigger: "blur" }],
 });
 
-const curRole = localStorage.getItem("SET_USER_CUR_ROLE");
-
 const submitForm = (formEl) => {
     if (!formEl) return;
     formEl.validate((valid) => {
@@ -507,13 +420,6 @@ const submitForm = (formEl) => {
                 params.id = route.query.farmId;
             }
 
-            // let pageData = null;
-            // if (route.query.miniJson) {
-            //     const json = JSON.parse(route.query.miniJson);
-            //     console.log('json', json);
-            //     // pageData = JSON.parse(json.paramsPage);
-            // }
-
             if (route.query.type !== "edit") {
                 // 处理 geom 参数,如果是数组需要序列化
                 const queryParams = {
@@ -531,65 +437,12 @@ const submitForm = (formEl) => {
                 delete queryParams.speciesItem;
                 queryParams.speciesName = mainSpecies?.name;
 
-                // if (pageData?.type === 'add') {
-                //     queryParams.invite = true;
-                // }
                 router.push({
                     path: "/prescription",
                     query: queryParams,
                 });
                 return;
             }
-
-            // const apiCall = route.query.type === "edit" ? VE_API.basic_farm.saveBasicFarmInfoByExpertV3({...params, expertMiniUserId: '81881'}) : VE_API.farm.saveFarm(params);
-
-            // apiCall.then((res) => {
-            //     if (res.code === 0) {
-            //         ElMessage.success(route.query.type === "edit" ? "修改成功" : "创建成功");
-            //         // 重置表单和地块数据
-            //         ruleFormRef.value.resetFields();
-            //         store.commit("home/SET_FARM_POLYGON", null);
-            //         store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
-            //         polygonArr.value = null;
-            //         isFromEditMap.value = false;
-
-            //         if (route.query.type !== "edit" && curRole == 0) {
-            //             localStorage.setItem("selectedFarmId", res.data.id);
-            //             localStorage.setItem("selectedFarmName", res.data.name);
-            //         }
-
-            //         // 根据来源页面决定跳转目标
-            //         const fromPage = route.query.from;
-            //         if (fromPage && fromPage !== "details") {
-            //             // 如果是从monitor页面来的
-            //             router.replace(`/${fromPage}`);
-            //         } else if (fromPage === "details") {
-            //             router.go(-1);
-            //         } else {
-            //             if (route.query.miniJson) {
-            //                 const json = JSON.parse(route.query.miniJson);
-            //                 //上传图片
-            //                 VE_API.ali
-            //                     .uploadImg({
-            //                         farmId: res.data.id,
-            //                         images: json.images,
-            //                         uploadDate: formatDate(new Date()),
-            //                     })
-            //                     .then(({ code, msg }) => {
-            //                         if (code === 0) {
-            //                             showSuccessPopup.value = true;
-            //                         } else {
-            //                             ElMessage.error(msg);
-            //                         }
-            //                     });
-            //             } else {
-            //                 router.replace(`/growth_report`);
-            //             }
-            //         }
-            //     } else {
-            //         ElMessage.error(res.msg);
-            //     }
-            // });
         }
     });
 };
@@ -601,12 +454,10 @@ const resetForm = (formEl) => {
     store.commit("home/SET_FARM_POLYGON", null);
     polygonArr.value = null;
     isFromEditMap.value = false;
-    hasDefaultPolygon.value = false; // 重置默认地块状态
 
     // 根据来源页面决定返回目标
-    const fromPage = route.query.from;
-    if (fromPage && fromPage !== "details") {
-        router.replace(`/${fromPage}`);
+    if (route.query.from && route.query.from !== "details") {
+        router.replace(`/${route.query.from}`);
         return;
     }
     router.go(-1);
@@ -614,45 +465,8 @@ const resetForm = (formEl) => {
 
 const centerPoint = ref(null);
 
-function toSubPage() {
-    // 如果还没有默认地块,先创建默认地块
-    if (!hasDefaultPolygon.value) {
-        if (centerPoint.value) {
-            const arr = convertPointToArray(centerPoint.value);
-            const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
-            const geometryData = {
-                geometryArr: [squareData.wkt],
-                mianji: squareData.area,
-            };
-
-            // 绘制默认地块
-            indexMap.setAreaGeometry(geometryData.geometryArr);
-
-            // 保存地块数据
-            polygonArr.value = geometryData.geometryArr;
-
-            // 标记已创建默认地块
-            hasDefaultPolygon.value = true;
-
-            // 不跳转,停留在当前页面
-            return;
-        }
-    }
-
-    // 如果已有默认地块,则跳转到编辑页面
-    // 保存到store中以便在编辑页面回显
-    if (polygonArr.value) {
-        const polygonData = {
-            geometryArr: polygonArr.value,
-            mianji: ruleForm.mu || "", // 保存用户输入的面积,如果没有输入则为空
-            isConfirmed: false, // 标记:还未从编辑页面确认返回
-        };
-        store.commit("home/SET_FARM_POLYGON", polygonData);
-    }
-
-    router.push(
-        `/edit_map?mapCenter=${centerPoint.value}&pointName=${ruleForm.address}&pointAddress=${pointAddress.value}&from=${route.query.from}&type=${route.query.type}`
-    );
+function handleGoSelectCrop() {
+    router.push(`/select_crop`);
 }
 
 const pointAddress = ref(null);
@@ -663,7 +477,6 @@ function getLocationName(location) {
         location,
     };
     VE_API.old_mini_map.location(params).then(({ result }) => {
-        // locationVal.value = result.formatted_addresses.recommend;
         const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
         ruleForm.address = add;
         pointAddress.value = result.address;
@@ -673,41 +486,13 @@ function getLocationName(location) {
     });
 }
 
-watch(
-    () => mapLocation.data,
-    (newValue, oldValue) => {
-        if (newValue) {
-            let { latitude, longitude } = transformFromWGSToGCJ(newValue[1], newValue[0]);
-            centerPoint.value = `POINT (${newValue[0]} ${newValue[1]})`;
-            getLocationName(`${latitude},${longitude}`);
-        }
+watch(() => mapLocation.data, (newValue) => {
+    if (newValue) {
+        let { latitude, longitude } = transformFromWGSToGCJ(newValue[1], newValue[0]);
+        centerPoint.value = `POINT (${newValue[0]} ${newValue[1]})`;
+        getLocationName(`${latitude},${longitude}`);
     }
-);
-
-const specieList = ref([]);
-function getSpecieList() {
-    return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
-        const list = Array.isArray(data) ? data : [];
-        // 只保留名称包含“水稻”或“荔枝”的品类
-        const litchiList = list.filter(
-            (item) => item?.name && (item.name.includes("水稻") || item.name.includes("荔枝")|| item.name.includes("大豆")|| item.name.includes("茶叶"))
-        );
-        specieList.value = litchiList;
-        // 返回空列表时,重置已选品类,避免保留上一次默认项
-        if (litchiList.length === 0) {
-            ruleForm.speciesItem = [];
-            return litchiList;
-        }
-        // //列表有值时,默认选中第一项
-        // if (litchiList.length > 0) {
-        //     const first = { value: litchiList[0].id, ...litchiList[0] };
-        //     ruleForm.speciesItem = [first];
-        //     // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
-        //     changeSpecie(first);
-        // }
-        return litchiList;
-    });
-}
+});
 
 const baseTypeList = ref([]);
 function getBaseTypeList() {
@@ -717,35 +502,6 @@ function getBaseTypeList() {
     });
 }
 
-async function changeSpecie(v) {
-    const current = Array.isArray(v) ? v[v.length - 1] : v;
-    // 选择后立即更新该字段的校验状态,避免错误提示残留
-    nextTick(() => {
-        if (!ruleFormRef.value) return;
-        if (Array.isArray(ruleForm.speciesItem) && ruleForm.speciesItem.length > 0) {
-            ruleFormRef.value.clearValidate(["speciesItem"]);
-            return;
-        }
-        ruleFormRef.value.validateField("speciesItem");
-    });
-    // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
-        ruleForm.name = farmCity.value + current.name + "农场";
-    }
-}
-
-/**
- * 格式化日期为 YYYY-MM-DD 格式
- * @param {Date} date - 日期对象
- * @returns {string} 格式化后的日期字符串
- */
-function formatDate(date) {
-    const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, "0");
-    const day = String(date.getDate()).padStart(2, "0");
-    return `${year}-${month}-${day}`;
-}
-
 function backgToHome() {
     ruleFormRef.value?.resetFields();
 
@@ -768,11 +524,6 @@ function backgToHome() {
         }
 
         json?.from && router.replace(`/${json.from}`);
-        // const paramsPage = JSON.parse(json.paramsPage);
-        // if (paramsPage.isFarmer) {
-        //     router.replace('/home');
-        //     return;
-        // }
     } else {
         if (fromPage && fromPage !== "details") {
             if (route.query?.type === "farmer") {
@@ -826,12 +577,9 @@ function handleFarmNameInput() {
 
 // 根据地址更新农场名称(如果满足条件)
 function updateFarmNameIfNeeded() {
-    // 只有在创建模式下且用户没有手动修改过农场名称且已选择品类时,才自动更新农场名称
-    const mainSpecies = Array.isArray(ruleForm.speciesItem)
-        ? ruleForm.speciesItem[0]
-        : ruleForm.speciesItem;
-    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && mainSpecies && farmCity.value) {
-        ruleForm.name = farmCity.value + mainSpecies.name + "农场";
+    // 只有在创建模式下且用户没有手动修改过农场名称时,才自动更新农场名称
+    if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && !ruleForm.name) {
+        ruleForm.name = farmCity.value + "农场";
     }
 }
 
@@ -862,7 +610,6 @@ function populateEditData() {
     if (editData.geomWkt) {
         polygonArr.value = [editData.geomWkt];
         indexMap.setAreaGeometry([editData.geomWkt]);
-        hasDefaultPolygon.value = true;
         isFromEditMap.value = true; // 编辑模式下锁定面积输入
     }
 
@@ -876,14 +623,13 @@ function populateEditData() {
             // 去重:同一个 speciesId 只保留一条
             const speciesKey = String(targetId);
             if (speciesIdSet.has(speciesKey)) return;
-            const match = specieList.value.find((s) => String(s.id) === String(targetId));
-            if (match) {
-                speciesIdSet.add(speciesKey);
-                selected.push({
-                    value: match.id,
-                    ...match,
-                });
-            }
+            speciesIdSet.add(speciesKey);
+            selected.push({
+                value: targetId,
+                id: targetId,
+                name: region.regionName || region.speciesName || "",
+                defaultContainerId: region.containerId ?? null,
+            });
         });
         if (selected.length > 0) {
             ruleForm.speciesItem = selected;
@@ -969,8 +715,6 @@ function handleMianjiInput(value) {
 
             // 更新状态
             polygonArr.value = geometryData.geometryArr;
-            // 标记已创建默认地块
-            hasDefaultPolygon.value = true;
         }
     }, 500);
 }
@@ -994,11 +738,6 @@ function handleMianjiInput(value) {
         height: calc(100% - 320px);
     }
 
-    .checkbox {
-        padding: 0 12px 6px;
-        font-size: 15px;
-    }
-
     .farm-content {
         position: absolute;
         top: 40px;
@@ -1141,62 +880,15 @@ function handleMianjiInput(value) {
 
                 .select-wrap {
                     display: flex;
-
-                    // &.specie-wrap {
-                    //     flex: 1;
-                    //     gap: 12px;
-
-                    //     .specie-select {
-                    //         min-width: 56px;
-                    //         width: 56px;
-                    //     }
-                    // }
-
                     width: 100%;
-                    ::v-deep {
-                        .el-input__wrapper {
-                            background: none;
-                            box-shadow: none;
-                        }
-
-                        .el-input__inner {
-                            font-size: 14px;
-                            color: rgba(0, 0, 0, 0.5);
-                        }
 
-                        .el-select__wrapper {
-                            background: none;
-                            box-shadow: none;
-                            gap: 2px;
-                            padding: 4px 2px;
-                            justify-content: center;
-                        }
-
-                        // .el-select__selection {
-                        //     flex: none;
-                        //     width: min-content;
-                        // }
-
-                        .el-select__placeholder {
-                            color: #000;
-                            position: static;
-                            transform: none;
-                            width: fit-content;
-                        }
-                    }
-
-                    &.client-wrap {
-                        ::v-deep {
-                            .el-select__wrapper {
-                                justify-content: flex-start;
-                            }
-                        }
+                    .species-entry {
+                        display: inline-flex;
+                        align-items: center;
+                        gap: 2px;
+                        color: #2199f8;
+                        cursor: pointer;
                     }
-
-                    // .select-item {
-                    //     min-width: 80px;
-                    //     margin-left: 10px;
-                    // }
                 }
 
                 ::v-deep {
@@ -1243,19 +935,6 @@ function handleMianjiInput(value) {
                     display: flex;
                     justify-content: space-between;
                     align-items: center;
-
-                    .draw-btn {
-                        flex: none;
-                        padding: 0 12px;
-                        height: 30px;
-                        line-height: 30px;
-                        box-sizing: border-box;
-                        color: #2199f8;
-                        border: 1px solid #2199f8;
-                        background: rgba(33, 153, 248, 0.1);
-                        border-radius: 20px;
-                        font-size: 12px;
-                    }
                 }
             }
 

+ 544 - 0
src/views/old_mini/create_farm/selectCrop.vue

@@ -0,0 +1,544 @@
+<template>
+    <div class="select-crop-page">
+        <custom-header name="选择作物"></custom-header>
+        <div class="page-body">
+            <div class="top-tabs">
+                <div v-for="tab in topTabs" :key="tab.id" class="tab-item" :class="{ active: activeTopTab === tab.id }"
+                    @click="scrollToTab(tab.id)">
+                    {{ tab.name }}
+                </div>
+            </div>
+
+            <div ref="categoryScrollEl" class="category-sections">
+                <div v-for="tab in topTabs" :key="tab.id" :id="tabAnchorId(tab.id)" class="tab-anchor-block">
+                    <template v-for="section in cropSource[tab.id] || []" :key="sectionKey(tab.id, section.id)">
+                        <div class="category-section">
+                            <div class="section-header">
+                                <div class="section-title">
+                                    <span class="title-bar"></span>
+                                    <span>{{ section.name }}</span>
+                                </div>
+                                <el-input v-model="searchMap[sectionKey(tab.id, section.id)]" class="search-wrap"
+                                    placeholder="搜索品类" :prefix-icon="Search" />
+                            </div>
+
+                            <div class="crop-grid">
+                                <div v-for="crop in getVisibleCrops(tab.id, section)" :key="crop.id" class="crop-item"
+                                    :class="{ selected: isSelected(crop.id) }"
+                                    @click="handleCropClick(tab.id, section, crop.id)">
+                                    <span>{{ crop.name }}</span>
+                                    <span v-if="isSelected(crop.id)" class="selected-mark">
+                                        <el-icon><Select /></el-icon>
+                                    </span>
+                                </div>
+                            </div>
+
+                            <div class="expand-trigger" @click="toggleExpand(tab.id, section.id)">
+                                {{ expandedSections[sectionKey(tab.id, section.id)] ? "收起" : "点击展开更多" }}
+                            </div>
+                        </div>
+                    </template>
+                </div>
+            </div>
+        </div>
+
+        <div class="custom-bottom-fixed-btns">
+            <div class="bottom-btn primary-btn" @click="handleConfirm">确认</div>
+        </div>
+    </div>
+    <popup v-model:show="showPopup" class="crop-popup" round>
+        <div class="popup-title">请选择具体品种</div>
+        <div class="popup-body">
+            <div v-if="popupSection" class="category-section">
+                <div class="section-header">
+                    <div class="section-title">
+                        <span class="title-bar"></span>
+                        <span>{{ popupSection.name }}</span>
+                    </div>
+                    <el-input v-model="popupSearchKeyword" class="search-wrap" placeholder="搜索品种" :prefix-icon="Search" />
+                </div>
+                <div class="crop-grid">
+                    <div v-for="crop in popupVisibleCrops" :key="crop.id" class="crop-item"
+                        :class="{ selected: isSelected(crop.id) }" @click="toggleCrop(crop.id)">
+                        <span>{{ crop.name }}</span>
+                        <span v-if="isSelected(crop.id)" class="selected-mark">
+                            <el-icon><Select /></el-icon>
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <div class="switch-btn" @click="handleSwitch">换一换</div>
+            <div class="popup-actions">
+                <div class="action-btn cancel-btn" @click="clearSelection">取消选中</div>
+                <div class="action-btn confirm-btn" @click="handlePopupConfirm">确认</div>
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import CustomHeader from "@/components/customHeader.vue";
+import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
+import { Popup } from "vant";
+import { Search, Select } from "@element-plus/icons-vue";
+
+const SECTION_VISIBLE_COUNT = 9;
+const POPUP_PAGE_SIZE = 12;
+const TAB_SCROLL_MARGIN = 8;
+const TAB_SCROLL_LEAD = 16;
+const PROGRAMMATIC_SCROLL_UNLOCK_MS = 750;
+
+const topTabs = [
+    { id: "tree", name: "类别" },
+    { id: "grain", name: "类别" },
+    { id: "bean", name: "类别" },
+    { id: "other", name: "类别" },
+];
+
+const cropSource = {
+    tree: [
+        {
+            id: "s1",
+            name: "类别",
+            crops: Array.from({ length: 10 }, (_, idx) => ({
+                id: `tree-s1-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+        {
+            id: "s2",
+            name: "类别",
+            crops: Array.from({ length: 10 }, (_, idx) => ({
+                id: `tree-s2-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+        {
+            id: "s3",
+            name: "类别",
+            crops: Array.from({ length: 10 }, (_, idx) => ({
+                id: `tree-s3-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+    ],
+    grain: [
+        {
+            id: "s1",
+            name: "类别",
+            crops: Array.from({ length: 9 }, (_, idx) => ({
+                id: `grain-s1-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+    ],
+    bean: [
+        {
+            id: "s1",
+            name: "类别",
+            crops: Array.from({ length: 9 }, (_, idx) => ({
+                id: `bean-s1-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+    ],
+    other: [
+        {
+            id: "s1",
+            name: "类别",
+            crops: Array.from({ length: 9 }, (_, idx) => ({
+                id: `other-s1-${idx + 1}`,
+                name: "品类",
+            })),
+        },
+    ],
+};
+
+const activeTopTab = ref(topTabs[0].id);
+const selectedCropIds = ref(new Set());
+const expandedSections = ref({});
+const searchMap = ref({});
+const showPopup = ref(false);
+const popupSearchKeyword = ref("");
+const popupPage = ref(0);
+const categoryScrollEl = ref(null);
+/** 点击 Tab 触发 smooth 滚动期间为 true,避免与滚动联动抢 active */
+const tabScrollProgrammatic = ref(false);
+let scrollRaf = 0;
+/** 点击 Tab 触发的 smooth 滚动:清理旧监听器/定时器,避免连点或 scrollend 误触发导致高亮乱跳 */
+let progScrollEndHandler = null;
+let progUnlockTimer = null;
+
+const clearProgrammaticScrollWatchers = (root) => {
+    if (progScrollEndHandler && root) {
+        root.removeEventListener("scrollend", progScrollEndHandler);
+    }
+    progScrollEndHandler = null;
+    if (progUnlockTimer) {
+        window.clearTimeout(progUnlockTimer);
+        progUnlockTimer = null;
+    }
+    /** 取消未完成的点击滚动时释放锁,避免连点清掉定时器后无法跟手滚动 */
+    tabScrollProgrammatic.value = false;
+};
+/** 弹窗展示的数据:来自用户点击的 tab + 类别块 */
+const popupContext = ref({ tabId: null, sectionId: null });
+
+const sectionKey = (tabId, sectionId) => `${tabId}-${sectionId}`;
+const tabAnchorId = (tabId) => `crop-tab-anchor-${tabId}`;
+const getTabAnchorEl = (root, tabId) => root?.querySelector(`#${tabAnchorId(tabId)}`);
+const filterByKeyword = (list, keyword) => {
+    const normalizedKeyword = (keyword || "").trim();
+    if (!normalizedKeyword) return list;
+    return list.filter((item) => item.name.includes(normalizedKeyword));
+};
+const sliceByPage = (list, page, pageSize) => {
+    const start = page * pageSize;
+    return list.slice(start, start + pageSize);
+};
+const toggleSetItem = (sourceSet, item) => {
+    const next = new Set(sourceSet);
+    if (next.has(item)) {
+        next.delete(item);
+    } else {
+        next.add(item);
+    }
+    return next;
+};
+
+const popupSection = computed(() => {
+    const { tabId, sectionId } = popupContext.value;
+    if (!tabId || !sectionId) return null;
+    const sections = cropSource[tabId] || [];
+    return sections.find((s) => s.id === sectionId) || null;
+});
+
+/** 锚点顶部相对滚动容器内容区的纵向位置(与 scrollTop 同坐标系) */
+const anchorContentTop = (root, el) => {
+    const elRect = el.getBoundingClientRect();
+    const rootRect = root.getBoundingClientRect();
+    return root.scrollTop + (elRect.top - rootRect.top);
+};
+
+/** 根据滚动位置更新顶部 Tab 高亮(与点击 scrollToTab 共用 activeTopTab) */
+const syncActiveTopTabFromScroll = () => {
+    const root = categoryScrollEl.value;
+    if (!root) return;
+    const st = root.scrollTop;
+    let nextId = topTabs[0].id;
+    for (const tab of topTabs) {
+        const el = getTabAnchorEl(root, tab.id);
+        if (!el) continue;
+        if (anchorContentTop(root, el) <= st + TAB_SCROLL_LEAD) {
+            nextId = tab.id;
+        }
+    }
+    if (activeTopTab.value !== nextId) {
+        activeTopTab.value = nextId;
+    }
+};
+
+const scheduleSyncFromScroll = () => {
+    if (tabScrollProgrammatic.value) return;
+    if (scrollRaf) return;
+    scrollRaf = requestAnimationFrame(() => {
+        scrollRaf = 0;
+        syncActiveTopTabFromScroll();
+    });
+};
+
+const scrollToTab = (tabId) => {
+    activeTopTab.value = tabId;
+    nextTick(() => {
+        const root = categoryScrollEl.value;
+        const target = getTabAnchorEl(root, tabId);
+        if (!root || !target) return;
+        clearProgrammaticScrollWatchers(root);
+        tabScrollProgrammatic.value = true;
+        let unlocked = false;
+        const unlock = () => {
+            if (unlocked) return;
+            unlocked = true;
+            clearProgrammaticScrollWatchers(root);
+            /** 以点击目标为准,避免动画结束瞬间用 scroll 反算出现中间 Tab */
+            activeTopTab.value = tabId;
+        };
+        progScrollEndHandler = () => unlock();
+        root.addEventListener("scrollend", progScrollEndHandler);
+        /** 只滚内容区,避免 scrollIntoView 带动外层滚动;与 .tab-anchor-block 的 scroll-margin-top 一致 */
+        const top = anchorContentTop(root, target);
+        root.scrollTo({ top: Math.max(0, top - TAB_SCROLL_MARGIN), behavior: "smooth" });
+        progUnlockTimer = window.setTimeout(unlock, PROGRAMMATIC_SCROLL_UNLOCK_MS);
+    });
+};
+
+const bindCategoryScroll = () => {
+    const root = categoryScrollEl.value;
+    root?.addEventListener("scroll", scheduleSyncFromScroll, { passive: true });
+};
+
+const unbindCategoryScroll = () => {
+    const root = categoryScrollEl.value;
+    root?.removeEventListener("scroll", scheduleSyncFromScroll);
+    if (scrollRaf) {
+        cancelAnimationFrame(scrollRaf);
+        scrollRaf = 0;
+    }
+};
+
+onMounted(() => {
+    bindCategoryScroll();
+    nextTick(() => syncActiveTopTabFromScroll());
+});
+
+onBeforeUnmount(() => {
+    clearProgrammaticScrollWatchers(categoryScrollEl.value);
+    unbindCategoryScroll();
+});
+
+const getFilteredCrops = (tabId, section) => {
+    const key = sectionKey(tabId, section.id);
+    return filterByKeyword(section.crops, searchMap.value[key]);
+};
+
+const getVisibleCrops = (tabId, section) => {
+    const list = getFilteredCrops(tabId, section);
+    const key = sectionKey(tabId, section.id);
+    return expandedSections.value[key] ? list : list.slice(0, SECTION_VISIBLE_COUNT);
+};
+
+const toggleExpand = (tabId, sectionId) => {
+    const key = sectionKey(tabId, sectionId);
+    expandedSections.value[key] = !expandedSections.value[key];
+};
+
+const isSelected = (cropId) => selectedCropIds.value.has(cropId);
+
+const toggleCrop = (cropId) => {
+    selectedCropIds.value = toggleSetItem(selectedCropIds.value, cropId);
+};
+
+/** 主列表选品类:选中并打开弹窗(带当前 tab / 类别上下文) */
+const handleCropClick = (tabId, section, cropId) => {
+    const alreadySelected = isSelected(cropId);
+    selectedCropIds.value = toggleSetItem(selectedCropIds.value, cropId);
+    if (!alreadySelected) {
+        popupContext.value = { tabId, sectionId: section.id };
+        popupSearchKeyword.value = "";
+        popupPage.value = 0;
+        showPopup.value = true;
+    }
+};
+
+const handleConfirm = () => {
+    history.back();
+};
+
+const popupFilteredCrops = computed(() => {
+    if (!popupSection.value) return [];
+    return filterByKeyword(popupSection.value.crops, popupSearchKeyword.value);
+});
+
+const popupVisibleCrops = computed(() => {
+    return sliceByPage(popupFilteredCrops.value, popupPage.value, POPUP_PAGE_SIZE);
+});
+
+const handleSwitch = () => {
+    if (!popupFilteredCrops.value.length) return;
+    const totalPage = Math.ceil(popupFilteredCrops.value.length / POPUP_PAGE_SIZE);
+    popupPage.value = (popupPage.value + 1) % totalPage;
+};
+
+const clearSelection = () => {
+    selectedCropIds.value = new Set();
+};
+
+const handlePopupConfirm = () => {
+    showPopup.value = false;
+};
+</script>
+
+<style scoped lang="scss">
+.category-section {
+    background: #fff;
+    border-radius: 6px;
+    padding: 10px 6px;
+    margin-bottom: 10px;
+}
+
+.section-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 10px;
+
+    .section-title {
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+        gap: 6px;
+
+        .title-bar {
+            width: 3px;
+            height: 14px;
+            border-radius: 4px;
+            background: #2f9cf4;
+        }
+    }
+
+    .search-wrap {
+        width: 150px;
+
+        :deep(.el-input__wrapper) {
+            height: 32px;
+            border-radius: 20px;
+            box-shadow: 0 0 0 1px #ebedf0 inset;
+            padding: 0 10px;
+        }
+
+        :deep(.el-input__prefix-inner) {
+            color: #b8bec8;
+            font-size: 16px;
+        }
+
+        :deep(.el-input__inner) {
+            font-size: 14px;
+            color: #666;
+        }
+    }
+}
+
+.crop-grid {
+    display: grid;
+    grid-template-columns: repeat(3, minmax(0, 1fr));
+    gap: 10px;
+
+    .crop-item {
+        border: 1px solid #ebebeb;
+        border-radius: 4px;
+        text-align: center;
+        position: relative;
+        padding: 10px;
+
+        &.selected {
+            border-color: #2f9cf4;
+            color: #2f9cf4;
+            background: rgba(33, 153, 248, 0.1);
+        }
+    }
+
+    .selected-mark {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        width: 20px;
+        height: 14px;
+        border-radius: 4px 0 4px 0;
+        background: #2f9cf4;
+        color: #fff;
+        font-size: 12px;
+        line-height: 18px;
+        text-align: center;
+    }
+}
+
+.select-crop-page {
+    height: calc(100vh - 80px);
+    background: #f2f4f5;
+
+    .page-body {
+        padding: 14px 10px;
+        height: calc(100% - 100px);
+
+        .top-tabs {
+            display: grid;
+            grid-template-columns: repeat(4, 1fr);
+            gap: 10px;
+            margin-bottom: 10px;
+
+            .tab-item {
+                border-radius: 2px;
+                padding: 6px;
+                background: #fff;
+                border: 1px solid transparent;
+                color: #858585;
+                text-align: center;
+
+                &.active {
+                    background: #2f9cf4;
+                    color: #fff;
+                }
+            }
+        }
+
+        .category-sections {
+            overflow: auto;
+            height: 100%;
+
+            .tab-anchor-block {
+                scroll-margin-top: 8px;
+            }
+    
+            .expand-trigger {
+                text-align: center;
+                color: rgba(0, 0, 0, 0.4);
+                margin-top: 14px;
+            }
+        }
+
+        .custom-bottom-fixed-btns {
+            .bottom-btn {
+                width: 130px;
+                box-sizing: border-box;
+                background: #2f9cf4;
+            }
+        }
+    }
+}
+
+.crop-popup {
+    width: 100%;
+    background: linear-gradient(0deg, #FFFFFF 79.37%, #93CEFD 108.08%);
+    padding: 20px 10px;
+    .popup-title {
+        text-align: center;
+        font-size: 20px;
+    }
+    .popup-body {
+        margin-top: 12px;
+        .switch-btn {
+            border-radius: 25px;
+            background: rgba(238, 238, 238, 0.6);
+            color: rgba(0, 0, 0, 0.56);
+            padding: 6px 0;
+            width: 114px;
+            text-align: center;
+            margin: 0 auto;
+        }
+        .popup-actions {
+            display: flex;
+            gap: 10px;
+            margin-top: 18px;
+            .action-btn {
+                flex: 1;
+                border-radius: 25px;
+                font-size: 16px;
+                text-align: center;
+                color: #000;
+                padding: 8px;
+                background: #fff;
+            }
+            
+            .cancel-btn {
+                border: 1px solid rgba(0, 0, 0, 0.2);
+            }
+            
+            .confirm-btn {
+                background: #2f9cf4;
+                color: #fff;
+            }
+        }
+    }
+}
+
+</style>