Bläddra i källkod

feat(water): add facility import and coordinates

Kazerin 4 dagar sedan
förälder
incheckning
214a59a9e8

+ 20 - 0
src/views/subSystem/basic/pipeInfo/index.vue

@@ -67,6 +67,9 @@
       <el-col :span="1.5">
         <el-button type="success" plain icon="Download" size="small" @click="handleExport" v-hasPermi="['waterSupply:waterPipe:export']">导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" plain icon="Upload" size="small" @click="importVisible = true" v-hasPermi="['waterSupply:waterPipe:import']">导入</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
@@ -84,6 +87,10 @@
       <el-table-column label="长度(m)" align="center" prop="pipeLength" width="100" />
       <el-table-column label="起点位置" align="center" prop="startPoint" :show-overflow-tooltip="true" min-width="140" />
       <el-table-column label="终点位置" align="center" prop="endPoint" :show-overflow-tooltip="true" min-width="140" />
+      <el-table-column label="起点经度" align="center" prop="startLongitude" width="120" />
+      <el-table-column label="起点纬度" align="center" prop="startLatitude" width="120" />
+      <el-table-column label="终点经度" align="center" prop="endLongitude" width="120" />
+      <el-table-column label="终点纬度" align="center" prop="endLatitude" width="120" />
       <el-table-column label="铺设年份" align="center" prop="layingYear" width="100" />
       <el-table-column label="设计压力(MPa)" align="center" prop="pressureRating" width="130" />
       <el-table-column label="状态" align="center" width="80">
@@ -174,6 +181,10 @@
               <el-input v-model="form.endPoint" placeholder="请输入终点位置" maxlength="200" />
             </el-form-item>
           </el-col>
+          <el-col :span="12"><el-form-item label="起点经度" prop="startLongitude"><el-input-number v-model="form.startLongitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="起点纬度" prop="startLatitude"><el-input-number v-model="form.startLatitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="终点经度" prop="endLongitude"><el-input-number v-model="form.endLongitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="终点纬度" prop="endLatitude"><el-input-number v-model="form.endLatitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
           <el-col :span="24">
             <el-form-item label="备注" prop="remark">
               <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" maxlength="500" />
@@ -204,6 +215,10 @@
         </el-descriptions-item>
         <el-descriptions-item label="起点位置" :span="2">{{ detailData.startPoint }}</el-descriptions-item>
         <el-descriptions-item label="终点位置" :span="2">{{ detailData.endPoint }}</el-descriptions-item>
+        <el-descriptions-item label="起点经度">{{ detailData.startLongitude }}</el-descriptions-item>
+        <el-descriptions-item label="起点纬度">{{ detailData.startLatitude }}</el-descriptions-item>
+        <el-descriptions-item label="终点经度">{{ detailData.endLongitude }}</el-descriptions-item>
+        <el-descriptions-item label="终点纬度">{{ detailData.endLatitude }}</el-descriptions-item>
         <el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
         <el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
@@ -215,6 +230,8 @@
       </template>
     </el-drawer>
 
+    <WaterFacilityImportDialog v-model="importVisible" title="管网信息" upload-url="/api/water/pipe/importData" template-url="/api/water/pipe/importTemplate" @imported="getList" />
+
     <!-- 回收站对话框 -->
     <el-dialog title="回收站" v-model:visible="recycleVisible" width="900px" append-to-body>
       <el-form :model="recycleQuery" ref="recycleForm" :inline="true" size="small">
@@ -262,9 +279,11 @@ import { getDicts } from '@/api/system/dict/data'
 import { saveAs } from 'file-saver'
 import { exportWaterPipe } from '@/api/pipeNetwork/waterSupply'
 import { buildWaterFacilityQuery, getRequestErrorMessage, normalizePipeStatus } from '@/utils/waterFacilityQuery'
+import WaterFacilityImportDialog from '@/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue'
 
 export default {
   name: 'WaterPipeInfo',
+  components: { WaterFacilityImportDialog },
   data() {
     return {
 
@@ -290,6 +309,7 @@ export default {
       dialogVisible: false,
       // 详情显示
       detailVisible: false,
+      importVisible: false,
       // 详情数据
       detailData: {},
       // 回收站显示

+ 14 - 0
src/views/subSystem/basic/pumpStation/index.vue

@@ -54,6 +54,9 @@
       <el-col :span="1.5">
         <el-button type="success" plain icon="Download" size="small" @click="handleExport" v-hasPermi="['waterSupply:waterPumpStation:export']">导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" plain icon="Upload" size="small" @click="importVisible = true" v-hasPermi="['waterSupply:waterPumpStation:import']">导入</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
@@ -63,6 +66,8 @@
       <el-table-column label="泵站编号" align="center" prop="stationCode" :show-overflow-tooltip="true" min-width="120" />
       <el-table-column label="泵站名称" align="center" prop="stationName" :show-overflow-tooltip="true" min-width="140" />
       <el-table-column label="泵站地址" align="center" prop="location" :show-overflow-tooltip="true" min-width="160" />
+      <el-table-column label="经度" align="center" prop="longitude" width="120" />
+      <el-table-column label="纬度" align="center" prop="latitude" width="120" />
       <el-table-column label="水泵数量" align="center" prop="pumpCount" width="90" />
       <el-table-column label="设计流量(m³/h)" align="center" prop="designFlow" width="130" />
       <el-table-column label="设计扬程(m)" align="center" prop="designHead" width="110" />
@@ -109,6 +114,8 @@
               <el-input v-model="form.location" placeholder="请输入泵站地址" maxlength="200" />
             </el-form-item>
           </el-col>
+          <el-col :span="12"><el-form-item label="经度" prop="longitude"><el-input-number v-model="form.longitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="纬度" prop="latitude"><el-input-number v-model="form.latitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
           <el-col :span="12">
             <el-form-item label="水泵数量" prop="pumpCount">
               <el-input-number v-model="form.pumpCount" :min="0" :precision="0" style="width: 100%" placeholder="请输入水泵数量" />
@@ -172,6 +179,8 @@
         <el-descriptions-item label="泵站编号">{{ detailData.stationCode }}</el-descriptions-item>
         <el-descriptions-item label="泵站名称">{{ detailData.stationName }}</el-descriptions-item>
         <el-descriptions-item label="泵站地址" :span="2">{{ detailData.location }}</el-descriptions-item>
+        <el-descriptions-item label="经度">{{ detailData.longitude }}</el-descriptions-item>
+        <el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
         <el-descriptions-item label="水泵数量">{{ detailData.pumpCount }}</el-descriptions-item>
         <el-descriptions-item label="设计流量(m³/h)">{{ detailData.designFlow }}</el-descriptions-item>
         <el-descriptions-item label="设计扬程(m)">{{ detailData.designHead }}</el-descriptions-item>
@@ -193,6 +202,8 @@
       </template>
     </el-drawer>
 
+    <WaterFacilityImportDialog v-model="importVisible" title="泵站信息" upload-url="/api/water/pumpStation/importData" template-url="/api/water/pumpStation/importTemplate" @imported="getList" />
+
     <!-- 回收站对话框 -->
     <el-dialog title="回收站" v-model="recycleVisible" width="900px" append-to-body>
       <el-form :model="recycleQuery" ref="recycleForm" :inline="true" size="small">
@@ -238,9 +249,11 @@ import { listPumpStation, getPumpStation, addPumpStation, updatePumpStation, del
 import { saveAs } from 'file-saver'
 import { exportWaterPumpStation } from '@/api/pipeNetwork/waterSupply'
 import { buildWaterFacilityQuery, getRequestErrorMessage } from '@/utils/waterFacilityQuery'
+import WaterFacilityImportDialog from '@/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue'
 
 export default {
   name: 'WaterPumpStation',
+  components: { WaterFacilityImportDialog },
   data() {
     return {
       loading: true,
@@ -254,6 +267,7 @@ export default {
       dialogTitle: '',
       dialogVisible: false,
       detailVisible: false,
+      importVisible: false,
       detailData: {},
       recycleVisible: false,
       recycleLoading: false,

+ 14 - 0
src/views/subSystem/basic/waterPlant/index.vue

@@ -53,6 +53,9 @@
       <el-col :span="1.5">
         <el-button type="success" plain icon="Download" size="small" @click="handleExport" v-hasPermi="['waterSupply:waterPlant:export']">导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" plain icon="Upload" size="small" @click="importVisible = true" v-hasPermi="['waterSupply:waterPlant:import']">导入</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
@@ -62,6 +65,8 @@
       <el-table-column label="水厂编号" align="center" prop="plantCode" :show-overflow-tooltip="true" min-width="120" />
       <el-table-column label="水厂名称" align="center" prop="plantName" :show-overflow-tooltip="true" min-width="140" />
       <el-table-column label="水厂地址" align="center" prop="location" :show-overflow-tooltip="true" min-width="160" />
+      <el-table-column label="经度" align="center" prop="longitude" width="120" />
+      <el-table-column label="纬度" align="center" prop="latitude" width="120" />
       <el-table-column label="水源类型" align="center" prop="waterSourceType" width="100" />
       <el-table-column label="设计能力(万m³/d)" align="center" prop="designCapacity" width="150" />
       <el-table-column label="实际能力(万m³/d)" align="center" prop="currentCapacity" width="150" />
@@ -109,6 +114,8 @@
               <el-input v-model="form.location" placeholder="请输入水厂地址" maxlength="200" />
             </el-form-item>
           </el-col>
+          <el-col :span="12"><el-form-item label="经度" prop="longitude"><el-input-number v-model="form.longitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="纬度" prop="latitude"><el-input-number v-model="form.latitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
           <el-col :span="12">
             <el-form-item label="水源类型" prop="waterSourceType">
               <el-input v-model="form.waterSourceType" placeholder="请输入水源类型" maxlength="50" />
@@ -176,6 +183,8 @@
         <el-descriptions-item label="水厂编号">{{ detailData.plantCode }}</el-descriptions-item>
         <el-descriptions-item label="水厂名称">{{ detailData.plantName }}</el-descriptions-item>
         <el-descriptions-item label="水厂地址" :span="2">{{ detailData.location }}</el-descriptions-item>
+        <el-descriptions-item label="经度">{{ detailData.longitude }}</el-descriptions-item>
+        <el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
         <el-descriptions-item label="水源类型">{{ detailData.waterSourceType }}</el-descriptions-item>
         <el-descriptions-item label="设计能力(万m³/d)">{{ detailData.designCapacity }}</el-descriptions-item>
         <el-descriptions-item label="实际能力(万m³/d)">{{ detailData.currentCapacity }}</el-descriptions-item>
@@ -198,6 +207,8 @@
       </template>
     </el-drawer>
 
+    <WaterFacilityImportDialog v-model="importVisible" title="水厂信息" upload-url="/api/water/plantInfo/importData" template-url="/api/water/plantInfo/importTemplate" @imported="getList" />
+
     <!-- 回收站对话框 -->
     <el-dialog title="回收站" v-model="recycleVisible" width="900px" append-to-body>
       <el-form :model="recycleQuery" ref="recycleForm" :inline="true" size="small">
@@ -243,9 +254,11 @@ import { listWaterPlant, getWaterPlant, addWaterPlant, updateWaterPlant, delWate
 import { saveAs } from 'file-saver'
 import { exportWaterPlant } from '@/api/pipeNetwork/waterSupply'
 import { buildWaterFacilityQuery, getRequestErrorMessage } from '@/utils/waterFacilityQuery'
+import WaterFacilityImportDialog from '@/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue'
 
 export default {
   name: 'WaterPlant',
+  components: { WaterFacilityImportDialog },
   data() {
     return {
       loading: true,
@@ -259,6 +272,7 @@ export default {
       dialogTitle: '',
       dialogVisible: false,
       detailVisible: false,
+      importVisible: false,
       detailData: {},
       recycleVisible: false,
       recycleLoading: false,

+ 14 - 0
src/views/subSystem/basic/waterSource/index.vue

@@ -53,6 +53,9 @@
       <el-col :span="1.5">
         <el-button type="success" plain icon="Download" size="small" @click="handleExport" v-hasPermi="['waterSupply:waterSource:export']">导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" plain icon="Upload" size="small" @click="importVisible = true" v-hasPermi="['waterSupply:waterSource:import']">导入</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
@@ -64,6 +67,8 @@
       <el-table-column label="水源地类型" align="center" prop="sourceType" width="100" />
       <el-table-column label="所属单位" align="center" prop="unit" :show-overflow-tooltip="true" min-width="120" />
       <el-table-column label="详细地址" align="center" prop="address" :show-overflow-tooltip="true" min-width="160" />
+      <el-table-column label="经度" align="center" prop="longitude" width="120" />
+      <el-table-column label="纬度" align="center" prop="latitude" width="120" />
       <el-table-column label="水源类型" align="center" prop="waterType" width="100" />
       <el-table-column label="保护等级" align="center" prop="protectionLevel" width="100" />
       <el-table-column label="水质等级" align="center" prop="waterQualityLevel" width="100" />
@@ -158,6 +163,8 @@
               </el-radio-group>
             </el-form-item>
           </el-col>
+          <el-col :span="12"><el-form-item label="经度" prop="longitude"><el-input-number v-model="form.longitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="纬度" prop="latitude"><el-input-number v-model="form.latitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
           <el-col :span="24">
             <el-form-item label="保护范围" prop="protectionScope">
               <el-input v-model="form.protectionScope" placeholder="请输入保护范围描述" maxlength="500" />
@@ -192,6 +199,8 @@
         <el-descriptions-item label="状态">
           <el-tag :type="statusTagType(detailData.status)" size="small">{{ statusLabel(detailData.status) }}</el-tag>
         </el-descriptions-item>
+        <el-descriptions-item label="经度">{{ detailData.longitude }}</el-descriptions-item>
+        <el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
         <el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
         <el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
@@ -203,6 +212,8 @@
       </template>
     </el-drawer>
 
+    <WaterFacilityImportDialog v-model="importVisible" title="水源地信息" upload-url="/api/water/sourceInfo/importData" template-url="/api/water/sourceInfo/importTemplate" @imported="getList" />
+
     <!-- 回收站对话框 -->
     <el-dialog title="回收站" v-model="recycleVisible" width="900px" append-to-body>
       <el-form :model="recycleQuery" ref="recycleForm" :inline="true" size="small">
@@ -248,9 +259,11 @@ import { listWaterSource, getWaterSource, addWaterSource, updateWaterSource, del
 import { saveAs } from 'file-saver'
 import { exportWaterSource } from '@/api/pipeNetwork/waterSupply'
 import { buildWaterFacilityQuery, getRequestErrorMessage } from '@/utils/waterFacilityQuery'
+import WaterFacilityImportDialog from '@/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue'
 
 export default {
   name: 'WaterSource',
+  components: { WaterFacilityImportDialog },
   data() {
     return {
       loading: true,
@@ -264,6 +277,7 @@ export default {
       dialogTitle: '',
       dialogVisible: false,
       detailVisible: false,
+      importVisible: false,
       detailData: {},
       recycleVisible: false,
       recycleLoading: false,

+ 14 - 0
src/views/subSystem/basic/waterUserInfo/index.vue

@@ -59,6 +59,9 @@
       <el-col :span="1.5">
         <el-button type="success" plain icon="Download" size="small" @click="handleExport" v-hasPermi="['waterSupply:waterUser:export']">导出</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button type="info" plain icon="Upload" size="small" @click="importVisible = true" v-hasPermi="['waterSupply:waterUser:import']">导入</el-button>
+      </el-col>
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
     </el-row>
 
@@ -69,6 +72,8 @@
       <el-table-column label="用水户名称" align="center" prop="userName" :show-overflow-tooltip="true" min-width="140" />
       <el-table-column label="用水户类型" align="center" prop="userType" width="100" />
       <el-table-column label="用水地址" align="center" prop="address" :show-overflow-tooltip="true" min-width="160" />
+      <el-table-column label="经度" align="center" prop="longitude" width="120" />
+      <el-table-column label="纬度" align="center" prop="latitude" width="120" />
       <el-table-column label="联系人" align="center" prop="contactPerson" width="100" />
       <el-table-column label="联系电话" align="center" prop="contactPhone" width="120" />
       <el-table-column label="水表编号" align="center" prop="meterNumber" width="120" />
@@ -133,6 +138,8 @@
               <el-input v-model="form.address" placeholder="请输入用水地址" maxlength="200" />
             </el-form-item>
           </el-col>
+          <el-col :span="12"><el-form-item label="经度" prop="longitude"><el-input-number v-model="form.longitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
+          <el-col :span="12"><el-form-item label="纬度" prop="latitude"><el-input-number v-model="form.latitude" :precision="8" :step="0.00000001" controls-position="right" style="width: 100%" /></el-form-item></el-col>
           <el-col :span="12">
             <el-form-item label="联系人" prop="contactPerson">
               <el-input v-model="form.contactPerson" placeholder="请输入联系人" maxlength="50" />
@@ -186,6 +193,8 @@
           <el-tag :type="statusTagType(detailData.status)" size="small">{{ statusLabel(detailData.status) }}</el-tag>
         </el-descriptions-item>
         <el-descriptions-item label="用水地址" :span="2">{{ detailData.address }}</el-descriptions-item>
+        <el-descriptions-item label="经度">{{ detailData.longitude }}</el-descriptions-item>
+        <el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
         <el-descriptions-item label="联系人">{{ detailData.contactPerson }}</el-descriptions-item>
         <el-descriptions-item label="联系电话">{{ detailData.contactPhone }}</el-descriptions-item>
         <el-descriptions-item label="水表编号">{{ detailData.meterNumber }}</el-descriptions-item>
@@ -203,6 +212,8 @@
       </template>
     </el-drawer>
 
+    <WaterFacilityImportDialog v-model="importVisible" title="用水户信息" upload-url="/api/water/userInfo/importData" template-url="/api/water/userInfo/importTemplate" @imported="getList" />
+
     <!-- 回收站对话框 -->
     <el-dialog title="回收站" v-model="recycleVisible" width="900px" append-to-body>
       <el-form :model="recycleQuery" ref="recycleForm" :inline="true" size="small">
@@ -249,9 +260,11 @@ import { listWaterUser, getWaterUser, addWaterUser, updateWaterUser, delWaterUse
 import { saveAs } from 'file-saver'
 import { exportWaterUser } from '@/api/pipeNetwork/waterSupply'
 import { buildWaterFacilityQuery, getRequestErrorMessage } from '@/utils/waterFacilityQuery'
+import WaterFacilityImportDialog from '@/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue'
 
 export default {
   name: 'WaterUser',
+  components: { WaterFacilityImportDialog },
   data() {
     return {
       loading: true,
@@ -265,6 +278,7 @@ export default {
       dialogTitle: '',
       dialogVisible: false,
       detailVisible: false,
+      importVisible: false,
       detailData: {},
       recycleVisible: false,
       recycleLoading: false,

+ 85 - 0
src/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue

@@ -0,0 +1,85 @@
+<template>
+  <el-dialog v-model="visible" :title="`${title}批量导入`" width="460px" append-to-body @closed="reset">
+    <el-upload
+      ref="uploadRef"
+      drag
+      :limit="1"
+      accept=".xlsx, .xls"
+      :headers="headers"
+      :action="actionUrl"
+      :disabled="isUploading"
+      :auto-upload="false"
+      :before-upload="validateFile"
+      :on-progress="onProgress"
+      :on-success="onSuccess"
+      :on-error="onError"
+    >
+      <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+      <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      <template #tip>
+        <div class="el-upload__tip">
+          <el-checkbox v-model="updateSupport">更新已存在的业务编码</el-checkbox>
+          <el-link type="primary" :underline="false" @click="downloadTemplate">下载模板</el-link>
+          <div>仅支持 xls、xlsx,业务编码为空的行将被跳过。</div>
+        </div>
+      </template>
+    </el-upload>
+    <template #footer>
+      <el-button type="primary" :loading="isUploading" @click="submit">开始导入</el-button>
+      <el-button @click="visible = false">取消</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import { getToken } from '@/utils/auth'
+import { download } from '@/utils/request'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+  title: { type: String, required: true },
+  uploadUrl: { type: String, required: true },
+  templateUrl: { type: String, required: true }
+})
+const emit = defineEmits(['update:modelValue', 'imported'])
+const uploadRef = ref()
+const updateSupport = ref(false)
+const isUploading = ref(false)
+const headers = { Authorization: `Bearer ${getToken()}` }
+const actionUrl = computed(() => `${import.meta.env.VITE_APP_BASE_API}${props.uploadUrl}?updateSupport=${updateSupport.value}`)
+const visible = computed({ get: () => props.modelValue, set: value => emit('update:modelValue', value) })
+
+function submit() { uploadRef.value?.submit() }
+function validateFile(file) {
+  if (!/\.(xlsx|xls)$/iu.test(file?.name || '')) {
+    ElMessage.error('仅支持 xls、xlsx 文件')
+    return false
+  }
+  return true
+}
+function onProgress() { isUploading.value = true }
+function onSuccess(response) {
+  isUploading.value = false
+  if (response?.code && response.code !== 200) {
+    ElMessage.error(response.msg || '导入失败,请检查文件格式')
+    return
+  }
+  ElMessage.success(response?.msg || '导入成功')
+  emit('imported')
+  visible.value = false
+}
+function onError(error) {
+  isUploading.value = false
+  ElMessage.error(error?.message || '导入失败,请检查文件格式')
+}
+function downloadTemplate() {
+  download(props.templateUrl, {}, `${props.title}导入模板.xlsx`)
+}
+function reset() {
+  updateSupport.value = false
+  isUploading.value = false
+  uploadRef.value?.clearFiles()
+}
+</script>

+ 58 - 0
tests/waterFacilityImportCoordinates.test.mjs

@@ -0,0 +1,58 @@
+import assert from 'node:assert/strict'
+import fs from 'node:fs'
+import test from 'node:test'
+
+const facilities = [
+  {
+    path: 'src/views/subSystem/basic/waterSource/index.vue',
+    fields: ['longitude', 'latitude']
+  },
+  {
+    path: 'src/views/subSystem/basic/waterPlant/index.vue',
+    fields: ['longitude', 'latitude']
+  },
+  {
+    path: 'src/views/subSystem/basic/pumpStation/index.vue',
+    fields: ['longitude', 'latitude']
+  },
+  {
+    path: 'src/views/subSystem/basic/pipeInfo/index.vue',
+    fields: ['startLongitude', 'startLatitude', 'endLongitude', 'endLatitude']
+  },
+  {
+    path: 'src/views/subSystem/basic/waterUserInfo/index.vue',
+    fields: ['longitude', 'latitude']
+  }
+]
+
+test('all T1 facility pages expose Excel import through the shared dialog', () => {
+  for (const facility of facilities) {
+    const source = fs.readFileSync(facility.path, 'utf8')
+    assert.match(source, /WaterFacilityImportDialog/u, facility.path)
+    assert.match(source, />导入<\/el-button>/u, facility.path)
+    assert.match(source, /@imported="getList"/u, facility.path)
+  }
+
+  const dialog = fs.readFileSync('src/views/subSystem/waterSupply/components/WaterFacilityImportDialog.vue', 'utf8')
+  assert.match(dialog, /accept="\.xlsx, \.xls"/u)
+  assert.match(dialog, /:before-upload="validateFile"/u)
+  assert.match(dialog, /function validateFile/u)
+  assert.match(dialog, /\(xlsx\|xls\)/u)
+  assert.match(dialog, /updateSupport/u)
+  assert.match(dialog, /下载模板/u)
+  assert.match(dialog, /VITE_APP_BASE_API/u)
+  assert.match(dialog, /download\(props\.templateUrl/u)
+  assert.match(dialog, /response\.code !== 200/u)
+})
+
+test('all T1 facility pages expose coordinates in list, edit drawer, and detail drawer', () => {
+  for (const facility of facilities) {
+    const source = fs.readFileSync(facility.path, 'utf8')
+    for (const field of facility.fields) {
+      const occurrences = source.match(new RegExp(field, 'gu')) || []
+      assert.ok(occurrences.length >= 3, `${facility.path} must expose ${field} for list, form, and detail`)
+      assert.match(source, new RegExp(`v-model="form\\.${field}"`, 'u'), `${facility.path} must edit ${field}`)
+      assert.match(source, new RegExp(`detailData\\.${field}`, 'u'), `${facility.path} must show ${field} in details`)
+    }
+  }
+})