|
@@ -43,7 +43,6 @@
|
|
|
ref="ruleFormRef"
|
|
|
:model="ruleForm"
|
|
|
:rules="rules"
|
|
|
- label-width="auto"
|
|
|
class="demo-ruleForm"
|
|
|
>
|
|
|
<el-form-item label="农场位置" prop="address">
|
|
@@ -57,17 +56,6 @@
|
|
|
<div class="draw-btn" @click="toSubPage">点击勾选地块</div>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="农场面积" prop="mianji">
|
|
|
- <div class="area-box">
|
|
|
- <el-input
|
|
|
- placeholder="勾选地块获得农场面积"
|
|
|
- v-model="ruleForm.mianji"
|
|
|
- autocomplete="off"
|
|
|
- style="width: fit-content"
|
|
|
- />
|
|
|
- <div class="unit">亩</div>
|
|
|
- </div>
|
|
|
- </el-form-item>
|
|
|
<el-form-item label="种植作物" prop="speciesItem">
|
|
|
<div class="select-wrap">
|
|
|
<el-select
|
|
@@ -97,6 +85,41 @@
|
|
|
</el-select>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
+ <el-form-item label="联系人" prop="userName">
|
|
|
+ <div class="area-box">
|
|
|
+ <el-input
|
|
|
+ placeholder="请输入联系人姓名"
|
|
|
+ v-model="ruleForm.userName"
|
|
|
+ autocomplete="off"
|
|
|
+ style="width: fit-content"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="联系电话" prop="phone">
|
|
|
+ <div class="area-box">
|
|
|
+ <el-input
|
|
|
+ placeholder="请输入联系人电话"
|
|
|
+ v-model="ruleForm.phone"
|
|
|
+ autocomplete="off"
|
|
|
+ style="width: fit-content"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="农场面积" prop="mianji">
|
|
|
+ <div class="area-box">
|
|
|
+ <el-input
|
|
|
+ :placeholder="isFromEditMap ? '勾选地块获得农场面积' : '请输入亩数'"
|
|
|
+ v-model="ruleForm.mianji"
|
|
|
+ :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="请输入您的农场名称"
|
|
@@ -137,12 +160,118 @@ const store = useStore();
|
|
|
|
|
|
const indexMap = new IndexMap();
|
|
|
const mapContainer = ref(null);
|
|
|
+
|
|
|
+// 标记是否从编辑地图页面确认返回
|
|
|
+const isFromEditMap = 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; // 初始化时可以手动输入
|
|
|
+
|
|
|
centerPoint.value = store.state.home.miniUserLocationPoint;
|
|
|
const arr = convertPointToArray(centerPoint.value);
|
|
|
getLocationName(`${arr[1]},${arr[0]}`);
|
|
|
indexMap.initMap(centerPoint.value, mapContainer.value);
|
|
|
|
|
|
+ // 初始化时绘制边长200米的正方形地块
|
|
|
+ const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
|
|
|
+ const geometryData = {
|
|
|
+ geometryArr: [squareData.wkt],
|
|
|
+ mianji: squareData.area, // 自动计算的面积(亩)
|
|
|
+ }
|
|
|
+ indexMap.setAreaGeometry(geometryData?.geometryArr);
|
|
|
+
|
|
|
+ // 保存到状态中(只保存地块几何数据,不回显面积)
|
|
|
+ polygonArr.value = geometryData?.geometryArr;
|
|
|
+ // 清空面积输入框,等待用户输入
|
|
|
+ ruleForm.mianji = '';
|
|
|
+
|
|
|
getSpecieList();
|
|
|
});
|
|
|
|
|
@@ -150,19 +279,54 @@ const polygonArr = ref(null);
|
|
|
const paramsType = ref(null);
|
|
|
onActivated(() => {
|
|
|
paramsType.value = route.query.type;
|
|
|
+
|
|
|
+ // 如果是从 home 页面进入,重置所有数据
|
|
|
if (route.query.isFromHome) {
|
|
|
+ // 清除旧的地块数据
|
|
|
+ store.commit("home/SET_FARM_POLYGON", null);
|
|
|
+ isFromEditMap.value = false; // 从home进入,可以手动输入
|
|
|
+
|
|
|
centerPoint.value = store.state.home.miniUserLocationPoint;
|
|
|
const arr = convertPointToArray(centerPoint.value);
|
|
|
getLocationName(`${arr[1]},${arr[0]}`);
|
|
|
indexMap.setMapPosition(arr);
|
|
|
+
|
|
|
+ indexMap.clearLayer();
|
|
|
+
|
|
|
+ // 重新生成默认地块
|
|
|
+ const squareData = generateSquarePolygonBySideLength(arr, 200); // 200米边长
|
|
|
+ const geometryData = {
|
|
|
+ geometryArr: [squareData.wkt],
|
|
|
+ mianji: squareData.area,
|
|
|
+ }
|
|
|
+ indexMap.setAreaGeometry(geometryData?.geometryArr);
|
|
|
+ polygonArr.value = geometryData?.geometryArr;
|
|
|
+ // 清空面积输入框,等待用户输入
|
|
|
+ ruleForm.mianji = '';
|
|
|
+
|
|
|
+ return; // 直接返回,不执行下面的逻辑
|
|
|
}
|
|
|
+
|
|
|
indexMap.clearLayer();
|
|
|
- // 绘制勾画范围
|
|
|
+ // 绘制勾画范围(从edit_map返回的情况)
|
|
|
const polygonData = store.state.home.polygonData;
|
|
|
if (polygonData) {
|
|
|
+ // 用户从edit_map返回
|
|
|
+ // 根据 isConfirmed 判断是否锁定输入框
|
|
|
+ if (polygonData.isConfirmed) {
|
|
|
+ // 用户点击了"确认"按钮,锁定输入框并显示精确面积
|
|
|
+ isFromEditMap.value = true;
|
|
|
+ ruleForm.mianji = polygonData.mianji;
|
|
|
+ } else {
|
|
|
+ // 用户点击了"取消",不锁定输入框,面积保持原样
|
|
|
+ isFromEditMap.value = false;
|
|
|
+ // 面积输入框保持之前的值,不更新
|
|
|
+ }
|
|
|
indexMap.setAreaGeometry(polygonData?.geometryArr);
|
|
|
polygonArr.value = polygonData.geometryArr;
|
|
|
- ruleForm.mianji = polygonData.mianji;
|
|
|
+ } else if (centerPoint.value && polygonArr.value) {
|
|
|
+ // 没有新的编辑数据,保持当前地块
|
|
|
+ indexMap.setAreaGeometry(polygonArr.value);
|
|
|
}
|
|
|
});
|
|
|
|
|
@@ -221,50 +385,91 @@ const ruleForm = reactive({
|
|
|
speciesItem: null,
|
|
|
phenologyId: "",
|
|
|
name: "",
|
|
|
+ userName: "",
|
|
|
+ phone: "",
|
|
|
});
|
|
|
+// 自定义验证规则:验证面积必须是大于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" }],
|
|
|
- mianji: [{ required: true, message: "请勾选地块获得农场面积", trigger: "blur" }],
|
|
|
+ mianji: [
|
|
|
+ { required: true, message: "请输入农场面积", trigger: "blur" },
|
|
|
+ { validator: validateMianji, trigger: ["blur", "change"] }
|
|
|
+ ],
|
|
|
speciesItem: [{ required: true, message: "请选择品类", trigger: "blur" }],
|
|
|
phenologyId: [{ required: true, message: "请选择物候期", trigger: "blur" }],
|
|
|
name: [{ required: true, message: "请输入您的农场名称", trigger: "blur" }],
|
|
|
+ userName: [{ required: true, message: "请输入联系人姓名", trigger: "blur" }],
|
|
|
+ phone: [{ required: true, message: "请输入联系人电话", trigger: "blur" }],
|
|
|
});
|
|
|
|
|
|
const submitForm = (formEl) => {
|
|
|
- if (!formEl) return;
|
|
|
- formEl.validate((valid) => {
|
|
|
- if (valid) {
|
|
|
- const params = {
|
|
|
- ...ruleForm,
|
|
|
- wkt: centerPoint.value,
|
|
|
- speciesId: ruleForm.speciesItem?.id,
|
|
|
- containerId: ruleForm.speciesItem?.defaultContainerId,
|
|
|
- geom: polygonArr.value,
|
|
|
- };
|
|
|
- VE_API.farm.saveFarm(params).then((res) => {
|
|
|
- ElMessage.success("创建成功");
|
|
|
- // 重置表单
|
|
|
- ruleFormRef.value.resetFields();
|
|
|
- store.commit("home/SET_FARM_POLYGON", null);
|
|
|
- router.replace("/home?reload=true");
|
|
|
- });
|
|
|
- console.log("submit!", params);
|
|
|
- } else {
|
|
|
- console.log("error submit!");
|
|
|
- }
|
|
|
- });
|
|
|
+ router.replace("/home?reload=true&showSuccess=true");
|
|
|
+ // if (!formEl) return;
|
|
|
+ // formEl.validate((valid) => {
|
|
|
+ // if (valid) {
|
|
|
+ // const params = {
|
|
|
+ // ...ruleForm,
|
|
|
+ // wkt: centerPoint.value,
|
|
|
+ // speciesId: ruleForm.speciesItem?.id,
|
|
|
+ // containerId: ruleForm.speciesItem?.defaultContainerId,
|
|
|
+ // geom: polygonArr.value,
|
|
|
+ // };
|
|
|
+ // VE_API.farm.saveFarm(params).then((res) => {
|
|
|
+ // ElMessage.success("创建成功");
|
|
|
+ // // 重置表单和地块数据
|
|
|
+ // ruleFormRef.value.resetFields();
|
|
|
+ // store.commit("home/SET_FARM_POLYGON", null);
|
|
|
+ // polygonArr.value = null;
|
|
|
+ // isFromEditMap.value = false;
|
|
|
+ // // 返回首页并显示成功弹窗
|
|
|
+ // router.replace("/home?reload=true&showSuccess=true");
|
|
|
+ // });
|
|
|
+ // console.log("submit!", params);
|
|
|
+ // } else {
|
|
|
+ // console.log("error submit!");
|
|
|
+ // }
|
|
|
+ // });
|
|
|
};
|
|
|
|
|
|
const resetForm = (formEl) => {
|
|
|
if (!formEl) return;
|
|
|
formEl.resetFields();
|
|
|
+ // 清除地块数据
|
|
|
store.commit("home/SET_FARM_POLYGON", null);
|
|
|
+ polygonArr.value = null;
|
|
|
+ isFromEditMap.value = false;
|
|
|
router.replace("/home?reload=true");
|
|
|
};
|
|
|
|
|
|
const centerPoint = ref(null);
|
|
|
|
|
|
function toSubPage() {
|
|
|
+ // 如果存在默认地块数据,保存到store中以便在编辑页面回显
|
|
|
+ // 注意:isConfirmed 设置为 false,表示只是为了回显,还未确认
|
|
|
+ if (polygonArr.value) {
|
|
|
+ const polygonData = {
|
|
|
+ geometryArr: polygonArr.value,
|
|
|
+ mianji: ruleForm.mianji || '', // 保存用户输入的面积,如果没有输入则为空
|
|
|
+ isConfirmed: false, // 标记:还未从编辑页面确认返回
|
|
|
+ };
|
|
|
+ store.commit("home/SET_FARM_POLYGON", polygonData);
|
|
|
+ }
|
|
|
+
|
|
|
router.push(
|
|
|
`/edit_map?mapCenter=${centerPoint.value}&pointName=${ruleForm.address}&pointAddress=${pointAddress.value}`
|
|
|
);
|
|
@@ -317,6 +522,100 @@ function getPhenology(containerId) {
|
|
|
function backgToHome() {
|
|
|
router.replace("/home")
|
|
|
}
|
|
|
+
|
|
|
+// 处理面积按键输入 - 只允许数字和小数点
|
|
|
+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.mianji || '';
|
|
|
+ if (char === '.' && currentValue.includes('.')) {
|
|
|
+ event.preventDefault();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 不允许以小数点开头
|
|
|
+ if (char === '.' && !currentValue) {
|
|
|
+ event.preventDefault();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 处理面积输入
|
|
|
+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.mianji = 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;
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|