Explorar el Código

设备管理中的经纬度,安装地址通过地图获取

zengy hace 4 semanas
padre
commit
f445aa4d20

+ 8 - 1
src/components/GisMapCanvas.vue

@@ -82,7 +82,14 @@ async function initMap() {
     mapInstance.centerAndZoom(new BMapGL.Point(lng, lat), props.zoom)
     mapInstance.centerAndZoom(new BMapGL.Point(lng, lat), props.zoom)
     mapInstance.enableScrollWheelZoom(true)
     mapInstance.enableScrollWheelZoom(true)
     mapInstance.setMapStyleV2({ styleId: props.mapStyleId })
     mapInstance.setMapStyleV2({ styleId: props.mapStyleId })
-    mapInstance.addEventListener('click', () => emit('map-click'))
+    mapInstance.addEventListener('click', (event) => {
+      const point = event?.latLng || event?.latlng || event?.point || event?.target?.point
+      const lng = typeof point?.lng === 'function' ? point.lng() : point?.lng
+      const lat = typeof point?.lat === 'function' ? point.lat() : point?.lat
+      if (point && Number.isFinite(Number(lng)) && Number.isFinite(Number(lat))) {
+        emit('map-click', { lng: Number(lng), lat: Number(lat) })
+      }
+    })
     mapInstance.addEventListener('moveend', renderScene)
     mapInstance.addEventListener('moveend', renderScene)
     mapInstance.addEventListener('zoomend', renderScene)
     mapInstance.addEventListener('zoomend', renderScene)
     renderScene()
     renderScene()

+ 17 - 0
src/utils/coordTransform.js

@@ -50,6 +50,23 @@ export function wgs84ToBd09(lng, lat) {
   return gcj02ToBd09(gcjLng, gcjLat)
   return gcj02ToBd09(gcjLng, gcjLat)
 }
 }
 
 
+/** 百度 BD-09 → WGS84,用于地图选点回填业务坐标 */
+export function bd09ToWgs84(lng, lat) {
+  const x = Number(lng) - 0.0065
+  const y = Number(lat) - 0.006
+  const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI)
+  const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI)
+  const gcjLng = z * Math.cos(theta)
+  const gcjLat = z * Math.sin(theta)
+  const [wgsLng, wgsLat] = wgs84ToGcj02Inverse(gcjLng, gcjLat)
+  return [wgsLng, wgsLat]
+}
+
+function wgs84ToGcj02Inverse(lng, lat) {
+  const [forwardLng, forwardLat] = wgs84ToGcj02(lng, lat)
+  return [lng * 2 - forwardLng, lat * 2 - forwardLat]
+}
+
 /** 转换管线数据中的坐标数组 */
 /** 转换管线数据中的坐标数组 */
 export function convertPipeline(pipeline) {
 export function convertPipeline(pipeline) {
   return {
   return {

+ 139 - 44
src/views/subSystem/pipeNetwork/pdeviceInform/index.vue

@@ -125,6 +125,7 @@
           </div>
           </div>
 
 
           <div class="card-code">{{ row.equipmentCode }}</div>
           <div class="card-code">{{ row.equipmentCode }}</div>
+          <div class="card-district">片区:{{ districtLabel(row.district) }}</div>
 
 
           <div class="card-bottom">
           <div class="card-bottom">
             <span class="type-tag">
             <span class="type-tag">
@@ -180,7 +181,7 @@
           <el-descriptions-item label="使用年限(年)">{{ selectedDetail.equipmentInfo.useLife ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="使用年限(年)">{{ selectedDetail.equipmentInfo.useLife ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="资产状态">{{ currentStatusMeta(selectedDetail.statusInfo?.currentStatus).text }}</el-descriptions-item>
           <el-descriptions-item label="资产状态">{{ currentStatusMeta(selectedDetail.statusInfo?.currentStatus).text }}</el-descriptions-item>
           <el-descriptions-item label="所属片区">{{ districtLabel(selectedDetail.equipmentInfo.district) }}</el-descriptions-item>
           <el-descriptions-item label="所属片区">{{ districtLabel(selectedDetail.equipmentInfo.district) }}</el-descriptions-item>
-          <el-descriptions-item label="设备位置" :span="2">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
+          <el-descriptions-item label="安装位置" :span="2">{{ selectedDetail.equipmentInfo.equipmentLocation || "-" }}</el-descriptions-item>
           <el-descriptions-item label="经度">{{ selectedDetail.equipmentInfo.longitude ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="经度">{{ selectedDetail.equipmentInfo.longitude ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="纬度">{{ selectedDetail.equipmentInfo.latitude ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="纬度">{{ selectedDetail.equipmentInfo.latitude ?? "-" }}</el-descriptions-item>
           <el-descriptions-item label="安装时间">{{ selectedDetail.equipmentInfo.installTime || "-" }}</el-descriptions-item>
           <el-descriptions-item label="安装时间">{{ selectedDetail.equipmentInfo.installTime || "-" }}</el-descriptions-item>
@@ -212,6 +213,7 @@
     <!-- 新增/修改弹窗 -->
     <!-- 新增/修改弹窗 -->
     <el-dialog :title="title" v-model="open" width="860px" append-to-body>
     <el-dialog :title="title" v-model="open" width="860px" append-to-body>
       <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
       <el-form ref="equipmentRef" :model="form" :rules="rules" label-width="110px">
+        <div class="form-section-title">基础信息</div>
         <el-row :gutter="16">
         <el-row :gutter="16">
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="设备编码" prop="equipmentCode">
             <el-form-item label="设备编码" prop="equipmentCode">
@@ -223,11 +225,6 @@
               <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
               <el-input v-model="form.equipmentName" placeholder="请输入设备名称" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="设备型号" prop="equipmentModel">
-              <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
-            </el-form-item>
-          </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="设备类型" prop="equipmentTypeId">
             <el-form-item label="设备类型" prop="equipmentTypeId">
               <el-tree-select
               <el-tree-select
@@ -242,6 +239,18 @@
               />
               />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属片区" prop="district">
+              <el-select v-model="form.district" placeholder="请选择所属片区" style="width: 100%">
+                <el-option v-for="item in districtOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="设备型号" prop="equipmentModel">
+              <el-input v-model="form.equipmentModel" placeholder="请输入设备型号" />
+            </el-form-item>
+          </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="制造商" prop="manufacturer">
             <el-form-item label="制造商" prop="manufacturer">
               <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
               <el-input v-model="form.manufacturer" placeholder="请输入制造商" />
@@ -249,39 +258,20 @@
           </el-col>
           </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="生产日期" prop="productionDate">
             <el-form-item label="生产日期" prop="productionDate">
-              <el-date-picker
-                v-model="form.productionDate"
-                type="date"
-                value-format="YYYY-MM-DD"
-                placeholder="请选择生产日期"
-                style="width: 100%"
-              />
+              <el-date-picker v-model="form.productionDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择生产日期" style="width: 100%" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
+        </el-row>
+
+        <div class="form-section-title">分类与资产</div>
+        <el-row :gutter="16">
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="资产状态" prop="currentStatus">
             <el-form-item label="资产状态" prop="currentStatus">
               <el-select v-model="form.currentStatus" placeholder="请选择资产状态" style="width: 100%">
               <el-select v-model="form.currentStatus" placeholder="请选择资产状态" style="width: 100%">
-                <el-option
-                  v-for="item in equipmentStatusOptions"
-                  :key="item.value"
-                  :label="item.label"
-                  :value="item.value"
-                />
+                <el-option v-for="item in equipmentStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
               </el-select>
               </el-select>
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="所属片区" prop="district">
-              <el-select v-model="form.district" clearable placeholder="请选择所属片区" style="width: 100%">
-                <el-option v-for="item in districtOptions" :key="item.value" :label="item.label" :value="item.value" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="24">
-            <el-form-item label="规格参数" prop="equipmentSpec">
-              <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
-            </el-form-item>
-          </el-col>
           <el-col :span="12">
           <el-col :span="12">
             <el-form-item label="资产价值(元)" prop="assetValue">
             <el-form-item label="资产价值(元)" prop="assetValue">
               <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
               <el-input-number v-model="form.assetValue" :precision="2" :step="100" style="width: 100%" />
@@ -293,18 +283,27 @@
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
           <el-col :span="24">
           <el-col :span="24">
-            <el-form-item label="设备位置" prop="equipmentLocation">
-              <el-input v-model="form.equipmentLocation" placeholder="请输入设备位置" />
+            <el-form-item label="规格参数" prop="equipmentSpec">
+              <el-input v-model="form.equipmentSpec" placeholder="请输入设备规格参数" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="经度" prop="longitude">
-              <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" style="width: 100%" />
+        </el-row>
+
+        <div class="form-section-title">安装位置</div>
+        <el-row :gutter="16">
+          <el-col :span="24">
+            <el-form-item label="安装位置" prop="equipmentLocation">
+              <el-input v-model="form.equipmentLocation" placeholder="请输入或通过地图选择安装位置" />
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
-          <el-col :span="12">
-            <el-form-item label="纬度" prop="latitude">
-              <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" style="width: 100%" />
+          <el-col :span="24">
+            <el-form-item label="地图坐标">
+              <div class="coordinate-row">
+                <el-input-number v-model="form.longitude" :precision="6" :step="0.000001" controls-position="right" placeholder="经度" />
+                <el-input-number v-model="form.latitude" :precision="6" :step="0.000001" controls-position="right" placeholder="纬度" />
+                <el-button type="primary" plain @click="openMapPicker">地图选点</el-button>
+              </div>
+              <div class="coordinate-tip">点击地图获取经纬度,也可以直接手工修改。</div>
             </el-form-item>
             </el-form-item>
           </el-col>
           </el-col>
           <el-col :span="12">
           <el-col :span="12">
@@ -347,11 +346,33 @@
         </div>
         </div>
       </template>
       </template>
     </el-dialog>
     </el-dialog>
+
+    <!-- 地图选点弹窗 -->
+    <el-dialog v-model="mapOpen" title="地图选点" width="760px" append-to-body>
+      <div class="map-picker">
+        <GisMapCanvas
+          :center="mapCenter"
+          :points="mapPoints"
+          :enable-clustering="false"
+          :show-marker-name="false"
+          @map-click="handleMapClick"
+        />
+      </div>
+      <div class="map-picker-coordinates">
+        当前坐标:经度 {{ form.longitude ?? "-" }},纬度 {{ form.latitude ?? "-" }}
+      </div>
+      <template #footer>
+        <el-button @click="mapOpen = false">关 闭</el-button>
+        <el-button type="primary" @click="mapOpen = false">使用当前坐标</el-button>
+      </template>
+    </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
 import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
 import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from "vue";
+import GisMapCanvas from "@/components/GisMapCanvas.vue";
+import { bd09ToWgs84 } from "@/utils/coordTransform";
 import { useRoute } from "vue-router";
 import { useRoute } from "vue-router";
 import {
 import {
   addEquipment,
   addEquipment,
@@ -376,7 +397,10 @@ const allTypeList = ref([]);
 const loading = ref(false);
 const loading = ref(false);
 const open = ref(false);
 const open = ref(false);
 const detailOpen = ref(false);
 const detailOpen = ref(false);
+const mapOpen = ref(false);
 const alarmOnly = ref(false);
 const alarmOnly = ref(false);
+const mapCenter = ref({ lng: 110.3965, lat: 28.4638 });
+const mapPoints = ref([]);
 const total = ref(0);
 const total = ref(0);
 const title = ref("");
 const title = ref("");
 const selectedDetail = ref({});
 const selectedDetail = ref({});
@@ -526,7 +550,8 @@ const data = reactive({
   rules: {
   rules: {
     equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
     equipmentCode: [{ required: true, message: "设备编码不能为空", trigger: "blur" }],
     equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
     equipmentName: [{ required: true, message: "设备名称不能为空", trigger: "blur" }],
-    equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }]
+    equipmentTypeId: [{ required: true, message: "设备类型不能为空", trigger: "change" }],
+    district: [{ required: true, message: "所属片区不能为空", trigger: "change" }]
   }
   }
 });
 });
 
 
@@ -594,6 +619,26 @@ function reset() {
   proxy.resetForm("equipmentRef");
   proxy.resetForm("equipmentRef");
 }
 }
 
 
+function openMapPicker() {
+  const longitude = Number(form.value.longitude);
+  const latitude = Number(form.value.latitude);
+  mapCenter.value = Number.isFinite(longitude) && Number.isFinite(latitude)
+    ? { lng: longitude, lat: latitude }
+    : { lng: 110.3965, lat: 28.4638 };
+  mapPoints.value = Number.isFinite(longitude) && Number.isFinite(latitude)
+    ? [{ lng: longitude, lat: latitude, name: form.value.equipmentName || "设备位置" }]
+    : [];
+  mapOpen.value = true;
+}
+
+function handleMapClick(point) {
+  if (!point) return;
+  const [longitude, latitude] = bd09ToWgs84(point.lng, point.lat);
+  form.value.longitude = Number(longitude.toFixed(6));
+  form.value.latitude = Number(latitude.toFixed(6));
+  mapPoints.value = [{ lng: form.value.longitude, lat: form.value.latitude, name: form.value.equipmentName || "设备位置" }];
+}
+
 function handleQuery() {
 function handleQuery() {
   queryParams.value.pageNum = 1;
   queryParams.value.pageNum = 1;
   getList();
   getList();
@@ -877,15 +922,18 @@ onMounted(() => {
 .grid {
 .grid {
   display: grid;
   display: grid;
   grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
   grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
-  gap: 12px;
-  min-height: 200px;
+  align-items: start;
+  gap: 10px;
+  min-height: 160px;
 }
 }
 
 
 .card {
 .card {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  gap: 10px;
-  padding: 14px;
+  gap: 6px;
+  align-self: start;
+  min-height: 0;
+  padding: 10px 12px;
   border-radius: 10px;
   border-radius: 10px;
   background: #fff;
   background: #fff;
   border: 1px solid #ebebed;
   border: 1px solid #ebebed;
@@ -979,6 +1027,14 @@ onMounted(() => {
   color: #9ca3af;
   color: #9ca3af;
 }
 }
 
 
+.card-district {
+  overflow: hidden;
+  color: #6b7280;
+  font-size: 12px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
 .card-bottom {
 .card-bottom {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -1075,6 +1131,45 @@ onMounted(() => {
   border: 1px solid #e6e6eb;
   border: 1px solid #e6e6eb;
 }
 }
 
 
+.form-section-title {
+  margin: 8px 0 12px;
+  padding-left: 10px;
+  border-left: 3px solid #014db2;
+  color: #1f2937;
+  font-size: 14px;
+  font-weight: 600;
+}
+
+.coordinate-row {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  width: 100%;
+}
+
+.coordinate-row :deep(.el-input-number) {
+  flex: 1;
+}
+
+.coordinate-tip {
+  margin-top: 6px;
+  color: #9ca3af;
+  font-size: 12px;
+}
+
+.map-picker {
+  height: 420px;
+  overflow: hidden;
+  border: 1px solid #e5e7eb;
+  border-radius: 6px;
+}
+
+.map-picker-coordinates {
+  margin-top: 10px;
+  color: #6b7280;
+  font-size: 13px;
+}
+
 .dlg-footer,
 .dlg-footer,
 .dialog-footer {
 .dialog-footer {
   display: flex;
   display: flex;