Merge branch 'dev' of http://47.109.205.240:3000/Web/daimp-front into dev
This commit is contained in:
		
						commit
						818d8c5b38
					
				| @ -12,6 +12,7 @@ declare module 'vue' { | ||||
|     'CenterMap copy': typeof import('./src/components/centerMap copy.vue')['default'] | ||||
|     CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default'] | ||||
|     Components: typeof import('./src/components/index.js')['default'] | ||||
|     copy: typeof import('./src/components/centerMap copy.vue')['default'] | ||||
|     CurrentTime: typeof import('./src/components/currentTime.vue')['default'] | ||||
|     CustomBack: typeof import('./src/components/customBack.vue')['default'] | ||||
|     CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default'] | ||||
|  | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 136 KiB | 
| @ -14,14 +14,9 @@ import { registerGlobalComponents } from './plugins/globalComponents'; | ||||
| import { registerElIcons } from './plugins/icon'; | ||||
| import { registerMicroApps } from './plugins/micro'; | ||||
| import { registerSplitpanes } from './plugins/splitpanes'; | ||||
| import VueTianditu from 'vue-tianditu'; | ||||
| import { map_config } from './config/map'; | ||||
| 
 | ||||
| const app = createApp(App); | ||||
| app.use(pinia).use(router).use(ElementPlus).use(Avue).use(VueTianditu, { | ||||
|   v: map_config.tianditu.version, | ||||
|   tk: map_config.tianditu.token, | ||||
| }); | ||||
| app.use(pinia).use(router).use(ElementPlus).use(Avue); | ||||
| registerGlobalComponents(app); | ||||
| registerElIcons(app); | ||||
| registerSplitpanes(app); | ||||
|  | ||||
| @ -41,16 +41,23 @@ export const constantRoutes = [ | ||||
|   ...inputSuppliesRoutes, | ||||
|   { | ||||
|     path: '/sub-government-affairs-service/output-products', | ||||
|     name: 'outputProducts', | ||||
|     name: 'OutputProducts', | ||||
|     component: Layout, | ||||
|     redirect: '/sub-government-affairs-service/output-products/index', | ||||
|     redirect: '/sub-government-affairs-service/output-products/output-info', | ||||
|     meta: { title: '产出品管理', icon: 'Box' }, | ||||
|     children: [ | ||||
|       // 产出品概览
 | ||||
|       { | ||||
|         path: '/sub-government-affairs-service/output-products/index', | ||||
|         component: () => import('@/views/outputProductsManage/index.vue'), | ||||
|         name: 'outputProductsIndex', | ||||
|         meta: { title: '产出品管理', icon: 'List' }, | ||||
|         path: '/sub-government-affairs-service/output-products/output-statistics', | ||||
|         component: () => import('@/views/output-products/output-statistics/index.vue'), | ||||
|         name: 'OutputStatistics', | ||||
|         meta: { title: '产出品概览', icon: 'Box' }, | ||||
|       }, | ||||
|       { | ||||
|         path: '/sub-government-affairs-service/output-products/output-info', | ||||
|         component: () => import('@/views/output-products/output-info/index.vue'), | ||||
|         name: 'OutputInfo', | ||||
|         meta: { title: '产出品信息', icon: 'List' }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|  | ||||
							
								
								
									
										17
									
								
								sub-government-affairs-service/src/utils/mapLoader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								sub-government-affairs-service/src/utils/mapLoader.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // MapLoader.js
 | ||||
| export function loadTiandituApi(config) { | ||||
|   return new Promise((resolve) => { | ||||
|     if (window.T) { | ||||
|       // 已经加载过,直接返回 T
 | ||||
|       resolve(window.T); | ||||
|       return; | ||||
|     } | ||||
|     const script = document.createElement('script'); | ||||
|     script.id = 'tianditu-script'; | ||||
|     script.src = `http://api.tianditu.gov.cn/api?v=${config.version}&tk=${config.token}`; | ||||
|     script.onload = () => { | ||||
|       resolve(window.T); | ||||
|     }; | ||||
|     document.head.appendChild(script); | ||||
|   }); | ||||
| } | ||||
| @ -1,5 +1,10 @@ | ||||
| <template> | ||||
|   <div class="custom-page"> | ||||
|     <div class="custom-search"> | ||||
|       <AreaCascader v-model:region-code="searchForm.regionCode" v-model:grid-id="searchForm.id" :width="600" /> | ||||
|       <el-button type="primary" @click="handleSearch"> 搜索 </el-button> | ||||
|       <el-button @click="resetSearch"> 重置 </el-button> | ||||
|     </div> | ||||
|     <avue-crud | ||||
|       ref="crudRef" | ||||
|       v-model:page="pageData" | ||||
| @ -10,13 +15,6 @@ | ||||
|       @current-change="handleCurrentChange" | ||||
|       @size-change="handleSizeChange" | ||||
|     > | ||||
|       <template #search> | ||||
|         <div class="custom-search"> | ||||
|           <AreaCascader v-model:region-code="searchForm.regionCode" v-model:grid-id="searchForm.id" :width="600" /> | ||||
|           <el-button type="primary" @click="handleSearch"> 搜索 </el-button> | ||||
|           <el-button @click="resetSearch"> 重置 </el-button> | ||||
|         </div> | ||||
|       </template> | ||||
|       <!-- <template #menu-left> | ||||
|         <el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button> | ||||
|       </template> --> | ||||
| @ -152,10 +150,10 @@ const filterObject = (obj) => { | ||||
| const crudData = ref([]); | ||||
| const crudOptions = reactive({ | ||||
|   ...CRUD_OPTIONS, | ||||
|   addBtn: false, | ||||
|   searchBtn: false, | ||||
|   emptyBtn: false, | ||||
|   menu: false, | ||||
|   header: false, | ||||
|   height: 'calc(100vh - 340px)', | ||||
|   selection: true, | ||||
|   column: [ | ||||
|     { label: '行政区划编码', prop: 'AreaCode', width: 150 }, | ||||
|     { label: '行政区划', prop: 'gridAreaName', width: 150 }, | ||||
| @ -0,0 +1,16 @@ | ||||
| <template> | ||||
|   <div class="custom-page" :style="`background-image: url(${getAssetsFile('images/output/output.png')})`"></div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { ref, watch, onMounted, computed } from 'vue'; | ||||
| import { getAssetsFile } from '@/utils'; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| .custom-page { | ||||
|   height: calc(100vh - 150px); | ||||
|   background-size: cover; | ||||
|   background-repeat: no-repeat; | ||||
| } | ||||
| </style> | ||||
| @ -1,88 +1,207 @@ | ||||
| <template> | ||||
|   <div class="custom-page"> | ||||
|     <h1>林地</h1> | ||||
|     <!-- 搜索 --> | ||||
|     <el-form :inline="true" :model="searchForm" class="search-bar"> | ||||
|       <el-form-item label="关键词"> | ||||
|         <el-input v-model="searchForm.name" placeholder="请输入关键词" clearable /> | ||||
|       </el-form-item> | ||||
|       <el-form-item lable=""> | ||||
|         <AreaCascader v-model:region-code="searchForm.regionCode" v-model:grid-id="searchForm.id" :width="600" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" @click="handleSearch"> 搜索 </el-button> | ||||
|         <el-button @click="resetSearch"> 重置 </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <!-- 四个固定 Tabs --> | ||||
|     <LandSearch :search="searchForm" @on-search="handleSearch" @on-reset="handleReset" /> | ||||
|     <el-tabs v-model="activeTab" @tab-click="handleTabChange"> | ||||
|       <!-- <el-tab-pane label="待提交" name="0" /> --> | ||||
|       <el-tab-pane label="待审核" name="1" /> | ||||
|       <el-tab-pane label="已通过" name="2" /> | ||||
|       <el-tab-pane label="已驳回" name="0" /> | ||||
|     </el-tabs> | ||||
|     <avue-crud | ||||
|       ref="crudRef" | ||||
|       v-model:page="pageData" | ||||
|       :data="crudData" | ||||
|       :option="crudOptions" | ||||
|       :table-loading="loading" | ||||
|       @current-change="handleCurrentChange" | ||||
|       @size-change="handleSizeChange" | ||||
|     > | ||||
|       <!-- <template #menu-left> | ||||
|         <el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button> | ||||
|       </template> --> | ||||
|       <template #menu="scope"> | ||||
|         <custom-table-operate :actions="crudOptions.actions" :data="scope" /> | ||||
|       </template> | ||||
|     </avue-crud> | ||||
|     <TableComponent | ||||
|       :loading="loading" | ||||
|       :columns="columns" | ||||
|       :table-data="tableData" | ||||
|       :current-page="pageData.currentPage" | ||||
|       :page-size="pageData.pageSize" | ||||
|       :total="pageData.total" | ||||
|       :show-pagination="true" | ||||
|       :show-border="true" | ||||
|       :show-sort="true" | ||||
|       style="height: calc(100vh - 320px)" | ||||
|       @page-change="handlePageChange" | ||||
|     /> | ||||
|     <el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true"> | ||||
|       <el-form ref="form" :model="formData" :rules="rules" :disabled="isReadonly" label-width="100px" class="form-item"> | ||||
|         <p class="form-title">填写网格信息</p> | ||||
|         <el-form-item label="网格名称" prop="gridName"> | ||||
|           <el-input v-model="formData.gridName" placeholder="请输入网格名称" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="所属行政区域" prop="gridAreaCode"> | ||||
|           <AreaSelect v-model="formData.gridAreaCode" :label="null" :emit-path="false" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="网格化地图" prop="scopeImg"> | ||||
|           <FileUploader v-model="formData.scopeImg" :limit="1" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="备注" prop="note"> | ||||
|           <el-input v-model="formData.note" placeholder="请输入备注" /> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <el-tabs v-model="activeFormTab" class="tabs-wrapper"> | ||||
|         <el-tab-pane label="土地基本信息" name="basic"> | ||||
|           <p class="form-title">基本信息</p> | ||||
|           <el-form ref="basicFormRef" :model="formData" :disabled="isReadonly" label-width="120px"> | ||||
|             <el-row :gutter="20"> | ||||
|               <el-col :span="12"> | ||||
|                 <el-form-item label="地块名称" prop="landName"> | ||||
|                   <el-input v-model="formData.landName" placeholder="请输入地块名称" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="土地类型" prop="landType"> | ||||
|                   <el-tree-select | ||||
|                     v-model="formData.landType" | ||||
|                     :data="landTypeOptions" | ||||
|                     :props="treeProps" | ||||
|                     placeholder="选择土地类型" | ||||
|                     clearable | ||||
|                     check-strictly | ||||
|                     :render-after-expand="false" | ||||
|                     @change="handleLandTypeChange" | ||||
|                   /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="具体位置" prop="address"> | ||||
|                   <el-input v-model="formData.address" placeholder="请输入具体位置" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="土壤类型" prop="soilTypeId"> | ||||
|                   <url-select | ||||
|                     v-model="formData.soilTypeId" | ||||
|                     placeholder="选择土壤类型" | ||||
|                     url="/land-resource/baseInfo/soilTypePage" | ||||
|                     :params="{ current: 1, size: 100 }" | ||||
|                     label-key="soilType" | ||||
|                     value-key="id" | ||||
|                     :clearable="true" | ||||
|                   /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="土地照片" prop="landUrl"> | ||||
|                   <FileUploader v-model="formData.landUrl" :limit="1" /> | ||||
|                 </el-form-item> | ||||
|               </el-col> | ||||
|               <el-col :span="12"> | ||||
|                 <el-form-item label="面积(亩)" prop="area"> | ||||
|                   <el-input-number v-model="formData.area" :min="0" :precision="2" :step="0.1" controls-position="right" style="width: 100%" /> | ||||
|                 </el-form-item> | ||||
| 
 | ||||
|                 <el-form-item label="" label-width="0" prop="gridId"> | ||||
|                   <AreaCascader v-model:region-code="formData.gridAreaCode" v-model:grid-id="formData.gridId" label="" :split-rows="true" /> | ||||
|                 </el-form-item> | ||||
| 
 | ||||
|                 <el-form-item label="土地范围" prop="scope"> | ||||
|                   <el-input v-model="formData.scope" placeholder="请输入土地范围" /> | ||||
|                   <!-- <Attrs v-model:attrs="formData.scope" type="add" accept="image/*" /> --> | ||||
|                 </el-form-item> | ||||
|               </el-col> | ||||
|             </el-row> | ||||
|           </el-form> | ||||
|         </el-tab-pane> | ||||
|         <el-tab-pane label="土地承包信息" name="property"> | ||||
|           <p class="form-title">承包信息</p> | ||||
|           <el-form ref="propertyFormRef" :model="formData" :disabled="isReadonly" label-width="150px"> | ||||
|             <el-row :gutter="20"> | ||||
|               <el-col :span="12"> | ||||
|                 <el-form-item label="土地承包经营权人" prop="propertyName"> | ||||
|                   <el-input v-model="formData.propertyName" placeholder="请输入产权人姓名" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="联系方式" prop="propertyPhone"> | ||||
|                   <el-input v-model="formData.propertyPhone" placeholder="请输入产权人联系方式" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="土地经营权证书编号" prop="landCode"> | ||||
|                   <el-input v-model="formData.landCode" placeholder="请输入产权编号" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item label="土地经营权证书" prop="propertyCertificateUrl"> | ||||
|                   <FileUploader v-model="formData.propertyCertificateUrl" :limit="1" /> | ||||
|                 </el-form-item> | ||||
|               </el-col> | ||||
|               <!-- <el-col :span="12"></el-col> --> | ||||
|             </el-row> | ||||
|           </el-form> | ||||
|         </el-tab-pane> | ||||
|         <el-tab-pane label="土地流转信息" name="use"> | ||||
|           <p class="form-title">流转信息</p> | ||||
|           <el-form ref="useForm" :model="formData" label-width="120px"> | ||||
|             <el-row> | ||||
|               <el-col :span="12"> | ||||
|                 <el-form-item label="是否土地流转" prop="landTransfer"> | ||||
|                   <el-radio-group v-model="formData.landTransfer" :disabled="isReadonly"> | ||||
|                     <el-radio label="1">是</el-radio> | ||||
|                     <el-radio label="0">否</el-radio> | ||||
|                   </el-radio-group> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item v-if="formData.landTransfer === '1'" label="土地受让方" prop="landUseName"> | ||||
|                   <el-input v-model="formData.landUseName" placeholder="请输入土地受让方" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item v-if="formData.landTransfer === '1'" label="联系电话" prop="landUsePhone"> | ||||
|                   <el-input v-model="formData.landUsePhone" placeholder="请输入土地受让方联系方式" /> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item v-if="formData.landTransfer === '1'" label="流转合同" prop="landCertificateUrl"> | ||||
|                   <FileUploader v-model="formData.landCertificateUrl" :limit="1" /> | ||||
|                 </el-form-item> | ||||
|               </el-col> | ||||
|             </el-row> | ||||
|           </el-form> | ||||
|         </el-tab-pane> | ||||
|       </el-tabs> | ||||
| 
 | ||||
|       <template #footer> | ||||
|         <div class="dialog-footer"> | ||||
|           <el-button @click="handleCancel">取消</el-button> | ||||
|           <el-button v-if="!isReadonly" type="primary" @click="handleSubmit()"> 保存 </el-button> | ||||
|         </div> | ||||
|         <span class="dialog-footer"> | ||||
|           <el-button @click="visible = false">取消</el-button> | ||||
|           <template v-if="!isReadonly"> | ||||
|             <el-button type="primary" @click="submitAll">修改</el-button> | ||||
|           </template> | ||||
|         </span> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| // --------------------------------------------------------------------- | ||||
| // avue-crud 通用代码 | ||||
| // --------------------------------------------------------------------- | ||||
| import { ref, reactive, watch, onMounted, computed, nextTick } from 'vue'; | ||||
| import { CRUD_OPTIONS } from '@/config'; | ||||
| import { ElMessage, ElMessageBox } from 'element-plus'; | ||||
| import { useUserStore } from '@/store/modules/user'; | ||||
| import { mockData } from './landData'; | ||||
| import { ref, onMounted } from 'vue'; | ||||
| import LandSearch from './components/LandSearch.vue'; | ||||
| import TableComponent from '@/components/tableComponent.vue'; | ||||
| 
 | ||||
| const UserStore = useUserStore(); | ||||
| const user = UserStore.getUserInfo(); | ||||
| console.log('admin 属性:', user.admin); | ||||
| const searchForm = ref({}); | ||||
| const handleSearch = (searchData) => { | ||||
|   // 使用搜索条件 | ||||
|   console.log(searchData); | ||||
| }; | ||||
| 
 | ||||
| const handleReset = () => { | ||||
|   // 重置后的逻辑 | ||||
| }; | ||||
| 
 | ||||
| const activeTab = ref('1'); | ||||
| const handleTabChange = ({ name }) => { | ||||
|   //   activeTab.value = name; | ||||
|   console.log('切换tab', activeTab.value); | ||||
|   searchForm.value.landStatus = Number(activeTab.value); | ||||
|   getData(); | ||||
| }; | ||||
| 
 | ||||
| const loading = ref(false); | ||||
| const tableData = ref([]); | ||||
| const pageData = ref({ | ||||
|   currentPage: 1, | ||||
|   pageSize: 10, | ||||
|   total: 0, | ||||
| }); | ||||
| const columns = ref([ | ||||
|   { label: '地块编号', prop: 'id', width: 160 }, | ||||
|   { label: '地块名称', prop: 'landName', width: 170 }, | ||||
|   { label: '面积', prop: 'area', formatter: (row, column, cellValue) => `${Number(cellValue).toFixed(2)} 亩` }, | ||||
|   { label: '土地分类', prop: 'landTypeName' }, | ||||
|   { label: '土壤类型', prop: 'soilTypeName' }, | ||||
|   { label: '所属行政区域', prop: 'fullRegionName', width: 160 }, | ||||
|   { label: '所属网格', prop: 'gridName', width: 90 }, | ||||
|   { label: '具体位置', prop: 'address', width: 160 }, | ||||
|   { label: '土地承包经营人', prop: 'propertyName' }, | ||||
|   { label: '联系电话', prop: 'propertyPhone' }, | ||||
|   { label: '土地经营权证书编号', prop: 'landCode', width: 160 }, | ||||
|   { label: '是否土地流转', prop: 'isTransfer' }, | ||||
|   { label: '土地受让方', prop: 'transferName' }, | ||||
|   { label: '受让方联系电话', prop: 'transferPhone', width: 100 }, | ||||
|   { label: '流转合同', prop: 'transferContract' }, | ||||
|   { label: '信息填报人', prop: 'fillName' }, | ||||
|   { label: '信息填报单位', prop: 'fillGroup' }, | ||||
|   { label: '信息填报时间', prop: 'fillTime' }, | ||||
|   // { label: '信息更新人', prop: 'updateName' }, | ||||
|   // { label: '信息更新单位', prop: 'updateGroup' }, | ||||
|   { label: '信息更新时间', prop: 'updateTime' }, | ||||
| ]); | ||||
| 
 | ||||
| const handlePageChange = ({ page, pageSize }) => { | ||||
|   pageData.value.currentPage = page; | ||||
|   pageData.value.pageSize = pageSize; | ||||
|   getData(); | ||||
| }; | ||||
| 
 | ||||
| const visible = ref(false); | ||||
| const isReadonly = ref(false); | ||||
| const dialogTitle = ref(); | ||||
| const activeTab = ref('1'); | ||||
| const activeFormTab = ref('basic'); | ||||
| const formData = ref({ | ||||
|   gridName: '', | ||||
|   gridAreaCode: '', | ||||
| @ -95,165 +214,9 @@ const resetForm = () => { | ||||
|   formData.value = { ...initialFormData }; | ||||
| }; | ||||
| 
 | ||||
| const pageData = ref({ | ||||
|   currentPage: 1, | ||||
|   pageSize: 10, | ||||
|   total: 0, | ||||
| }); | ||||
| const searchForm = ref({ | ||||
|   gridName: '', | ||||
|   keyword: '', | ||||
|   regionCode: '', | ||||
|   id: '', | ||||
|   status: -1, | ||||
| }); | ||||
| const initialSearchForm = { ...searchForm.value }; | ||||
| const resetSearch = () => { | ||||
|   searchForm.value = { ...initialSearchForm }; | ||||
| }; | ||||
| // 过滤对象,只保留有值的属性 | ||||
| const filterObject = (obj) => { | ||||
|   const newObj = {}; | ||||
|   Object.keys(obj).forEach((key) => { | ||||
|     const value = obj[key]; | ||||
|     // 检查值是否有效,排除空字符串、null 和 undefined | ||||
|     if (value !== '' && value !== null && value !== undefined) { | ||||
|       newObj[key] = value; | ||||
|     } | ||||
|   }); | ||||
|   return newObj; | ||||
| }; | ||||
| 
 | ||||
| const crudData = ref([]); | ||||
| const crudOptions = reactive({ | ||||
|   ...CRUD_OPTIONS, | ||||
|   addBtn: false, | ||||
|   header: false, | ||||
|   searchBtn: false, | ||||
|   emptyBtn: false, | ||||
|   refreshBtn: false, | ||||
|   height: 'calc(100vh - 360px)', | ||||
|   column: [ | ||||
|     { label: '地块编号', prop: 'id', width: 160 }, | ||||
|     { label: '地块名称', prop: 'landName', width: 170 }, | ||||
|     { label: '面积', prop: 'area', formatter: (row, column, cellValue) => `${Number(cellValue).toFixed(2)} 亩` }, | ||||
|     { label: '土地分类', prop: 'landTypeName' }, | ||||
|     { label: '土壤类型', prop: 'soilTypeName' }, | ||||
|     { label: '所属行政区域', prop: 'fullRegionName', width: 160 }, | ||||
|     { label: '所属网格', prop: 'gridName', width: 90 }, | ||||
|     { label: '具体位置', prop: 'address', width: 160 }, | ||||
|     { label: '土地承包经营人', prop: 'propertyName' }, | ||||
|     { label: '联系电话', prop: 'propertyPhone' }, | ||||
|     { label: '土地经营权证书编号', prop: 'landCode', width: 160 }, | ||||
|     { label: '是否土地流转', prop: 'isTransfer' }, | ||||
|     { label: '土地受让方', prop: 'transferName' }, | ||||
|     { label: '受让方联系电话', prop: 'transferPhone', width: 100 }, | ||||
|     { label: '流转合同', prop: 'transferContract' }, | ||||
|     { label: '信息填报人', prop: 'fillName' }, | ||||
|     { label: '信息填报单位', prop: 'fillGroup' }, | ||||
|     { label: '信息填报时间', prop: 'fillTime' }, | ||||
|     // { label: '信息更新人', prop: 'updateName' }, | ||||
|     // { label: '信息更新单位', prop: 'updateGroup' }, | ||||
|     { label: '信息更新时间', prop: 'updateTime' }, | ||||
|   ], | ||||
|   actions: [ | ||||
|     { | ||||
|       name: '查看', | ||||
|       icon: 'view', | ||||
|       event: ({ row }) => handleView(row), | ||||
|     }, | ||||
|     { | ||||
|       name: '编辑', | ||||
|       icon: 'edit', | ||||
|       event: ({ row }) => handleEdit(row), | ||||
|     }, | ||||
|     { | ||||
|       type: 'danger', | ||||
|       name: '删除', | ||||
|       icon: 'delete', | ||||
|       event: ({ row }) => handleDelete(row), | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
| const handleTabChange = ({ name }) => { | ||||
|   //   activeTab.value = name; | ||||
|   console.log('切换tab', activeTab.value); | ||||
|   getData(); | ||||
| }; | ||||
| const handleRefresh = async () => { | ||||
|   searchForm.value = { ...initialSearchForm }; | ||||
|   getData(); | ||||
| }; | ||||
| const handleCurrentChange = (val) => { | ||||
|   pageData.value.currentPage = val; | ||||
| }; | ||||
| const handleSizeChange = (val) => { | ||||
|   pageData.value.pageSize = val; | ||||
| }; | ||||
| const handleView = (row) => { | ||||
|   isReadonly.value = true; | ||||
|   formData.value = { ...row }; | ||||
|   dialogTitle.value = '查看网格'; | ||||
|   //   visible.value = true; | ||||
| }; | ||||
| const handleEdit = (row) => { | ||||
|   isReadonly.value = false; | ||||
|   formData.value = { ...row }; | ||||
|   dialogTitle.value = '编辑网格'; | ||||
|   //   visible.value = true; | ||||
| }; | ||||
| const handleDelete = async (row) => { | ||||
|   console.log('删除', row); | ||||
|   //   try { | ||||
|   //     await ElMessageBox.confirm('确认删除该网格吗?', '提示', { | ||||
|   //       confirmButtonText: '确定', | ||||
|   //       cancelButtonText: '取消', | ||||
|   //       type: 'warning', | ||||
|   //     }); | ||||
| 
 | ||||
|   //     const response = await deleteGrid(row.id); | ||||
| 
 | ||||
|   //     ElMessage.success('删除成功'); | ||||
|   //     getData(); | ||||
|   //   } catch (error) { | ||||
|   //     if (error === 'cancel') { | ||||
|   //       ElMessage.info('已取消删除'); | ||||
|   //     } else { | ||||
|   //       ElMessage.error('删除失败'); | ||||
|   //       console.error('删除异常:', error); | ||||
|   //     } | ||||
|   //   } | ||||
| }; | ||||
| 
 | ||||
| const handleSubmit = async () => { | ||||
|   console.log('提交表单:', formData.value); | ||||
|   try { | ||||
|     if (dialogTitle.value === '新增网格') { | ||||
|       await createGrid(formData.value); | ||||
|       ElMessage.success('新增成功'); | ||||
|       resetForm(); | ||||
|       visible.value = false; | ||||
|       getData(); | ||||
|     } else { | ||||
|       await updateGrid(formData.value); | ||||
|       ElMessage.success('更新成功'); | ||||
|       resetForm(); | ||||
|       visible.value = false; | ||||
|       getData(); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     ElMessage.error(error.message || '新增失败,请重试'); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // --------------------------------------------------------------------- | ||||
| // 业务代码 | ||||
| // --------------------------------------------------------------------- | ||||
| import { createGrid, updateGrid, deleteGrid, fetchGridList, getGridDetail, exportGrid } from '@/apis/landResourceManagement/gridManagement'; | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   getData(); | ||||
| }); | ||||
| import { mockData } from './landData'; | ||||
| import { createLand, deleteLand, editLand, fetchLandList, getLandById, approveLand } from '@/apis/landResourceManagement/landManagement'; | ||||
| import request from '@/utils/axios'; | ||||
| const getData = async () => { | ||||
|   //   const filteredParams = filterObject(searchForm.value); | ||||
|   //   const response = await fetchGridList(filteredParams); | ||||
| @ -261,69 +224,50 @@ const getData = async () => { | ||||
|   loading.value = true; | ||||
|   // 模拟接口延迟 | ||||
|   await new Promise((resolve) => setTimeout(resolve, 300)); | ||||
|   crudData.value = mockData | ||||
|   tableData.value = mockData | ||||
|     .filter((item) => item.status === activeTab.value) | ||||
|     .map((item) => ({ | ||||
|       ...item, | ||||
|       landTypeName: '林地', | ||||
|     })); | ||||
|   pageData.value.total = crudData.value.length; | ||||
|   pageData.value.total = tableData.value.length; | ||||
|   loading.value = false; | ||||
| }; | ||||
| 
 | ||||
| const handleAdd = () => { | ||||
|   console.log('handleAdd'); | ||||
|   resetForm(); | ||||
|   isReadonly.value = false; | ||||
|   dialogTitle.value = '新增网格'; | ||||
|   visible.value = true; | ||||
| const getLandDetail = async (id) => { | ||||
|   const response = await getLandById(id); | ||||
|   return response.data; | ||||
| }; | ||||
| 
 | ||||
| const handleSearch = () => { | ||||
| const landTypeOptions = ref([]); | ||||
| const treeProps = ref({ | ||||
|   value: 'id', | ||||
|   label: 'landType', | ||||
|   children: 'children', | ||||
|   // disabled: (data) => { | ||||
|   //   return data.children && data.children.length > 0; | ||||
|   // }, | ||||
| }); | ||||
| const fetchLandTypeData = async () => { | ||||
|   try { | ||||
|     const response = await request.get('/land-resource/baseInfo/landTree', { params: { status: '1' } }); | ||||
|     if (response.code === 200) { | ||||
|       landTypeOptions.value = response.data; | ||||
|     } | ||||
|   } catch (error) { | ||||
|     console.error('获取土地类型数据失败', error); | ||||
|   } | ||||
| }; | ||||
| onMounted(() => { | ||||
|   getData(); | ||||
| }; | ||||
| const handleCancel = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
|   fetchLandTypeData(); | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| .custom-page { | ||||
|   padding-bottom: 0px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| h1 { | ||||
|   font-size: 20px; | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .search-bar { | ||||
|   padding-left: 20px; | ||||
| } | ||||
| :deep(.el-dialog__body) { | ||||
|   padding: 20px; | ||||
|   height: calc(100vh - 300px); | ||||
|   overflow-y: auto; | ||||
| } | ||||
| .form-title { | ||||
|   font-size: 16px; | ||||
|   font-weight: 500; | ||||
|   margin: 30px 0; | ||||
|   color: #333333; | ||||
| } | ||||
| .form-item { | ||||
|   width: 500px; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| .dialog-footer { | ||||
|   text-align: center; | ||||
| } | ||||
| .custom-search { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding-left: 20px; | ||||
| 
 | ||||
|   .el-button { | ||||
|     margin-left: 12px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -0,0 +1,329 @@ | ||||
| <template> | ||||
|   <div class="custom-page"> | ||||
|     <h1>林地</h1> | ||||
|     <!-- 搜索 --> | ||||
|     <el-form :inline="true" :model="searchForm" class="search-bar"> | ||||
|       <el-form-item label="关键词"> | ||||
|         <el-input v-model="searchForm.name" placeholder="请输入关键词" clearable /> | ||||
|       </el-form-item> | ||||
|       <el-form-item lable=""> | ||||
|         <AreaCascader v-model:region-code="searchForm.regionCode" v-model:grid-id="searchForm.id" :width="600" /> | ||||
|       </el-form-item> | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" @click="handleSearch"> 搜索 </el-button> | ||||
|         <el-button @click="resetSearch"> 重置 </el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|     <!-- 四个固定 Tabs --> | ||||
|     <el-tabs v-model="activeTab" @tab-click="handleTabChange"> | ||||
|       <!-- <el-tab-pane label="待提交" name="0" /> --> | ||||
|       <el-tab-pane label="待审核" name="1" /> | ||||
|       <el-tab-pane label="已通过" name="2" /> | ||||
|       <el-tab-pane label="已驳回" name="0" /> | ||||
|     </el-tabs> | ||||
|     <avue-crud | ||||
|       ref="crudRef" | ||||
|       v-model:page="pageData" | ||||
|       :data="crudData" | ||||
|       :option="crudOptions" | ||||
|       :table-loading="loading" | ||||
|       @current-change="handleCurrentChange" | ||||
|       @size-change="handleSizeChange" | ||||
|     > | ||||
|       <!-- <template #menu-left> | ||||
|         <el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button> | ||||
|       </template> --> | ||||
|       <template #menu="scope"> | ||||
|         <custom-table-operate :actions="crudOptions.actions" :data="scope" /> | ||||
|       </template> | ||||
|     </avue-crud> | ||||
|     <el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true"> | ||||
|       <el-form ref="form" :model="formData" :rules="rules" :disabled="isReadonly" label-width="100px" class="form-item"> | ||||
|         <p class="form-title">填写网格信息</p> | ||||
|         <el-form-item label="网格名称" prop="gridName"> | ||||
|           <el-input v-model="formData.gridName" placeholder="请输入网格名称" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="所属行政区域" prop="gridAreaCode"> | ||||
|           <AreaSelect v-model="formData.gridAreaCode" :label="null" :emit-path="false" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="网格化地图" prop="scopeImg"> | ||||
|           <FileUploader v-model="formData.scopeImg" :limit="1" /> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="备注" prop="note"> | ||||
|           <el-input v-model="formData.note" placeholder="请输入备注" /> | ||||
|         </el-form-item> | ||||
|       </el-form> | ||||
|       <template #footer> | ||||
|         <div class="dialog-footer"> | ||||
|           <el-button @click="handleCancel">取消</el-button> | ||||
|           <el-button v-if="!isReadonly" type="primary" @click="handleSubmit()"> 保存 </el-button> | ||||
|         </div> | ||||
|       </template> | ||||
|     </el-dialog> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| // --------------------------------------------------------------------- | ||||
| // avue-crud 通用代码 | ||||
| // --------------------------------------------------------------------- | ||||
| import { ref, reactive, watch, onMounted, computed, nextTick } from 'vue'; | ||||
| import { CRUD_OPTIONS } from '@/config'; | ||||
| import { ElMessage, ElMessageBox } from 'element-plus'; | ||||
| import { useUserStore } from '@/store/modules/user'; | ||||
| import { mockData } from './landData'; | ||||
| 
 | ||||
| const UserStore = useUserStore(); | ||||
| const user = UserStore.getUserInfo(); | ||||
| console.log('admin 属性:', user.admin); | ||||
| 
 | ||||
| const loading = ref(false); | ||||
| 
 | ||||
| const visible = ref(false); | ||||
| const isReadonly = ref(false); | ||||
| const dialogTitle = ref(); | ||||
| const activeTab = ref('1'); | ||||
| const formData = ref({ | ||||
|   gridName: '', | ||||
|   gridAreaCode: '', | ||||
|   scope: '', | ||||
|   scopeImg: '', | ||||
|   note: '', | ||||
| }); | ||||
| const initialFormData = { ...formData.value }; | ||||
| const resetForm = () => { | ||||
|   formData.value = { ...initialFormData }; | ||||
| }; | ||||
| 
 | ||||
| const pageData = ref({ | ||||
|   currentPage: 1, | ||||
|   pageSize: 10, | ||||
|   total: 0, | ||||
| }); | ||||
| const searchForm = ref({ | ||||
|   gridName: '', | ||||
|   keyword: '', | ||||
|   regionCode: '', | ||||
|   id: '', | ||||
|   status: -1, | ||||
| }); | ||||
| const initialSearchForm = { ...searchForm.value }; | ||||
| const resetSearch = () => { | ||||
|   searchForm.value = { ...initialSearchForm }; | ||||
| }; | ||||
| // 过滤对象,只保留有值的属性 | ||||
| const filterObject = (obj) => { | ||||
|   const newObj = {}; | ||||
|   Object.keys(obj).forEach((key) => { | ||||
|     const value = obj[key]; | ||||
|     // 检查值是否有效,排除空字符串、null 和 undefined | ||||
|     if (value !== '' && value !== null && value !== undefined) { | ||||
|       newObj[key] = value; | ||||
|     } | ||||
|   }); | ||||
|   return newObj; | ||||
| }; | ||||
| 
 | ||||
| const crudData = ref([]); | ||||
| const crudOptions = reactive({ | ||||
|   ...CRUD_OPTIONS, | ||||
|   addBtn: false, | ||||
|   header: false, | ||||
|   searchBtn: false, | ||||
|   emptyBtn: false, | ||||
|   refreshBtn: false, | ||||
|   height: 'calc(100vh - 360px)', | ||||
|   column: [ | ||||
|     { label: '地块编号', prop: 'id', width: 160 }, | ||||
|     { label: '地块名称', prop: 'landName', width: 170 }, | ||||
|     { label: '面积', prop: 'area', formatter: (row, column, cellValue) => `${Number(cellValue).toFixed(2)} 亩` }, | ||||
|     { label: '土地分类', prop: 'landTypeName' }, | ||||
|     { label: '土壤类型', prop: 'soilTypeName' }, | ||||
|     { label: '所属行政区域', prop: 'fullRegionName', width: 160 }, | ||||
|     { label: '所属网格', prop: 'gridName', width: 90 }, | ||||
|     { label: '具体位置', prop: 'address', width: 160 }, | ||||
|     { label: '土地承包经营人', prop: 'propertyName' }, | ||||
|     { label: '联系电话', prop: 'propertyPhone' }, | ||||
|     { label: '土地经营权证书编号', prop: 'landCode', width: 160 }, | ||||
|     { label: '是否土地流转', prop: 'isTransfer' }, | ||||
|     { label: '土地受让方', prop: 'transferName' }, | ||||
|     { label: '受让方联系电话', prop: 'transferPhone', width: 100 }, | ||||
|     { label: '流转合同', prop: 'transferContract' }, | ||||
|     { label: '信息填报人', prop: 'fillName' }, | ||||
|     { label: '信息填报单位', prop: 'fillGroup' }, | ||||
|     { label: '信息填报时间', prop: 'fillTime' }, | ||||
|     // { label: '信息更新人', prop: 'updateName' }, | ||||
|     // { label: '信息更新单位', prop: 'updateGroup' }, | ||||
|     { label: '信息更新时间', prop: 'updateTime' }, | ||||
|   ], | ||||
|   actions: [ | ||||
|     { | ||||
|       name: '查看', | ||||
|       icon: 'view', | ||||
|       event: ({ row }) => handleView(row), | ||||
|     }, | ||||
|     { | ||||
|       name: '编辑', | ||||
|       icon: 'edit', | ||||
|       event: ({ row }) => handleEdit(row), | ||||
|     }, | ||||
|     { | ||||
|       type: 'danger', | ||||
|       name: '删除', | ||||
|       icon: 'delete', | ||||
|       event: ({ row }) => handleDelete(row), | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
| const handleTabChange = ({ name }) => { | ||||
|   //   activeTab.value = name; | ||||
|   console.log('切换tab', activeTab.value); | ||||
|   getData(); | ||||
| }; | ||||
| const handleRefresh = async () => { | ||||
|   searchForm.value = { ...initialSearchForm }; | ||||
|   getData(); | ||||
| }; | ||||
| const handleCurrentChange = (val) => { | ||||
|   pageData.value.currentPage = val; | ||||
| }; | ||||
| const handleSizeChange = (val) => { | ||||
|   pageData.value.pageSize = val; | ||||
| }; | ||||
| const handleView = (row) => { | ||||
|   isReadonly.value = true; | ||||
|   formData.value = { ...row }; | ||||
|   dialogTitle.value = '查看网格'; | ||||
|   //   visible.value = true; | ||||
| }; | ||||
| const handleEdit = (row) => { | ||||
|   isReadonly.value = false; | ||||
|   formData.value = { ...row }; | ||||
|   dialogTitle.value = '编辑网格'; | ||||
|   //   visible.value = true; | ||||
| }; | ||||
| const handleDelete = async (row) => { | ||||
|   console.log('删除', row); | ||||
|   //   try { | ||||
|   //     await ElMessageBox.confirm('确认删除该网格吗?', '提示', { | ||||
|   //       confirmButtonText: '确定', | ||||
|   //       cancelButtonText: '取消', | ||||
|   //       type: 'warning', | ||||
|   //     }); | ||||
| 
 | ||||
|   //     const response = await deleteGrid(row.id); | ||||
| 
 | ||||
|   //     ElMessage.success('删除成功'); | ||||
|   //     getData(); | ||||
|   //   } catch (error) { | ||||
|   //     if (error === 'cancel') { | ||||
|   //       ElMessage.info('已取消删除'); | ||||
|   //     } else { | ||||
|   //       ElMessage.error('删除失败'); | ||||
|   //       console.error('删除异常:', error); | ||||
|   //     } | ||||
|   //   } | ||||
| }; | ||||
| 
 | ||||
| const handleSubmit = async () => { | ||||
|   console.log('提交表单:', formData.value); | ||||
|   try { | ||||
|     if (dialogTitle.value === '新增网格') { | ||||
|       await createGrid(formData.value); | ||||
|       ElMessage.success('新增成功'); | ||||
|       resetForm(); | ||||
|       visible.value = false; | ||||
|       getData(); | ||||
|     } else { | ||||
|       await updateGrid(formData.value); | ||||
|       ElMessage.success('更新成功'); | ||||
|       resetForm(); | ||||
|       visible.value = false; | ||||
|       getData(); | ||||
|     } | ||||
|   } catch (error) { | ||||
|     ElMessage.error(error.message || '新增失败,请重试'); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // --------------------------------------------------------------------- | ||||
| // 业务代码 | ||||
| // --------------------------------------------------------------------- | ||||
| import { createGrid, updateGrid, deleteGrid, fetchGridList, getGridDetail, exportGrid } from '@/apis/landResourceManagement/gridManagement'; | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   getData(); | ||||
| }); | ||||
| const getData = async () => { | ||||
|   //   const filteredParams = filterObject(searchForm.value); | ||||
|   //   const response = await fetchGridList(filteredParams); | ||||
|   //   crudData.value = Array.isArray(response.data.records) ? response.data.records : []; | ||||
|   loading.value = true; | ||||
|   // 模拟接口延迟 | ||||
|   await new Promise((resolve) => setTimeout(resolve, 300)); | ||||
|   crudData.value = mockData | ||||
|     .filter((item) => item.status === activeTab.value) | ||||
|     .map((item) => ({ | ||||
|       ...item, | ||||
|       landTypeName: '林地', | ||||
|     })); | ||||
|   pageData.value.total = crudData.value.length; | ||||
|   loading.value = false; | ||||
| }; | ||||
| 
 | ||||
| const handleAdd = () => { | ||||
|   console.log('handleAdd'); | ||||
|   resetForm(); | ||||
|   isReadonly.value = false; | ||||
|   dialogTitle.value = '新增网格'; | ||||
|   visible.value = true; | ||||
| }; | ||||
| 
 | ||||
| const handleSearch = () => { | ||||
|   getData(); | ||||
| }; | ||||
| const handleCancel = () => { | ||||
|   visible.value = false; | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped lang="scss"> | ||||
| .custom-page { | ||||
|   padding-bottom: 0px; | ||||
| } | ||||
| h1 { | ||||
|   font-size: 20px; | ||||
|   font-weight: bold; | ||||
|   margin-bottom: 20px; | ||||
| } | ||||
| .search-bar { | ||||
|   padding-left: 20px; | ||||
| } | ||||
| :deep(.el-dialog__body) { | ||||
|   padding: 20px; | ||||
|   height: calc(100vh - 300px); | ||||
|   overflow-y: auto; | ||||
| } | ||||
| .form-title { | ||||
|   font-size: 16px; | ||||
|   font-weight: 500; | ||||
|   margin: 30px 0; | ||||
|   color: #333333; | ||||
| } | ||||
| .form-item { | ||||
|   width: 500px; | ||||
|   margin: 0 auto; | ||||
| } | ||||
| .dialog-footer { | ||||
|   text-align: center; | ||||
| } | ||||
| .custom-search { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   padding-left: 20px; | ||||
| 
 | ||||
|   .el-button { | ||||
|     margin-left: 12px; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
| @ -1,7 +0,0 @@ | ||||
| <template> | ||||
|   <LandCrud type="forest" /> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import LandCrud from './components/LandCrud.vue'; | ||||
| </script> | ||||
| @ -31,7 +31,7 @@ | ||||
|       @size-change="handleSizeChange" | ||||
|     > | ||||
|       <template #menu="scope"> | ||||
|         <custom-table-operate :actions="crudOptions.actions" :data="scope" /> | ||||
|         <custom-table-operate :actions="crudOptions.actions" :show="false" :data="scope" /> | ||||
|       </template> | ||||
|     </avue-crud> | ||||
|     <el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true"> | ||||
|  | ||||
| @ -1,47 +1,458 @@ | ||||
| <template> | ||||
|   <div class="mapDiv"> | ||||
|     <tdt-map ref="tdtMapRef" :center="state.center" :zoom="state.zoom" :style="{ height: mapHeight }"> | ||||
|       <!-- <tdt-tile-layer :layer-type="'img_w'"></tdt-tile-layer> --> | ||||
|       <!-- <tdt-tile-layer :layer-type="'cia_w'" :z-index="2"></tdt-tile-layer> --> | ||||
|       <tdt-tilelayer :url="state.img_w_url" :z-index="1"></tdt-tilelayer> | ||||
|       <tdt-tilelayer :url="state.cia_w_url" :z-index="2"></tdt-tilelayer> | ||||
|     </tdt-map> | ||||
|   <div class="custom-page"> | ||||
|     <!-- 左侧面板 --> | ||||
|     <div class="panel-left"> | ||||
|       <el-input v-model="searchTerm" placeholder="搜索地块名称" clearable style="margin-bottom: 15px"></el-input> | ||||
| 
 | ||||
|       <el-table :data="filteredFields" highlight-current-row style="width: 100%" @row-click="selectField"> | ||||
|         <el-table-column prop="name" label="名称"></el-table-column> | ||||
|         <el-table-column prop="area" label="面积(㎡)"> | ||||
|           <template #default="{ row }"> | ||||
|             {{ row.area.toFixed(2) }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table> | ||||
| 
 | ||||
|       <div class="stats">总面积: {{ totalArea.toFixed(2) }} ㎡</div> | ||||
| 
 | ||||
|       <div class="button-group"> | ||||
|         <el-button type="primary" @click="startDrawing">新增地块</el-button> | ||||
|         <el-button type="danger" :disabled="!selectedField" @click="removeField">删除地块</el-button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- 右侧地图区域 --> | ||||
|     <div class="panel-right"> | ||||
|       <div ref="mapContainer" class="map"></div> | ||||
| 
 | ||||
|       <div class="layer-switch"> | ||||
|         <el-radio-group v-model="layerType" size="small" @change="changeBaseLayer"> | ||||
|           <el-radio-button label="vector">矢量</el-radio-button> | ||||
|           <el-radio-button label="image">影像</el-radio-button> | ||||
|         </el-radio-group> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="field-layer-switch"> | ||||
|         <el-switch v-model="showFieldLayer" active-text="显示地块" @change="toggleFieldLayer"></el-switch> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="zoom-scale">缩放级别: {{ mapZoom }}, 比例尺: 1:{{ scale.toLocaleString() }}</div> | ||||
| 
 | ||||
|       <el-dialog v-model="drawDialogVisible" title="新增地块"> | ||||
|         <el-form> | ||||
|           <el-form-item label="地块名称"> | ||||
|             <el-input v-model="newFieldName"></el-input> | ||||
|           </el-form-item> | ||||
|           <el-form-item label="地块面积"> | ||||
|             <el-input :value="newFieldArea.toFixed(2) + ' ㎡'" readonly></el-input> | ||||
|           </el-form-item> | ||||
|         </el-form> | ||||
|         <template #footer> | ||||
|           <el-button @click="cancelDrawing">取消</el-button> | ||||
|           <el-button type="primary" @click="saveNewField">保存</el-button> | ||||
|         </template> | ||||
|       </el-dialog> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { reactive, onMounted, ref } from 'vue'; | ||||
| import { TdtMap, TdtTileLayer } from 'vue-tianditu'; | ||||
| import { ref, computed, onMounted, watch } from 'vue'; | ||||
| import { ElMessage } from 'element-plus'; | ||||
| import { map_config } from '@/config/map'; | ||||
| import { loadTiandituApi } from '@/utils/mapLoader'; | ||||
| 
 | ||||
| const key = map_config.tianditu.token; | ||||
| // 模拟接口服务(使用localStorage) | ||||
| const fieldService = { | ||||
|   getFields() { | ||||
|     const fields = localStorage.getItem('fields'); | ||||
|     return Promise.resolve(fields ? JSON.parse(fields) : []); | ||||
|   }, | ||||
|   createField(field) { | ||||
|     return this.getFields().then((fields) => { | ||||
|       field.id = Date.now(); | ||||
|       fields.push(field); | ||||
|       localStorage.setItem('fields', JSON.stringify(fields)); | ||||
|       return field; | ||||
|     }); | ||||
|   }, | ||||
|   deleteField(id) { | ||||
|     return this.getFields().then((fields) => { | ||||
|       const newFields = fields.filter((f) => f.id !== id); | ||||
|       localStorage.setItem('fields', JSON.stringify(newFields)); | ||||
|       return newFields; | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const state = reactive({ | ||||
|   center: [100.088, 23.883], | ||||
|   zoom: 14, | ||||
|   img_w_url: `http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${key}`, | ||||
|   cia_w_url: `http://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${key}`, | ||||
|   // 添加其他地图层URL | ||||
|   // ... | ||||
| const mapContainer = ref(null); | ||||
| const mapObj = ref(null); | ||||
| const T = ref(null); // 天地图API对象 | ||||
| 
 | ||||
| // 响应式状态 | ||||
| const fields = ref([]); | ||||
| const selectedField = ref(null); | ||||
| const searchTerm = ref(''); | ||||
| const newFieldName = ref(''); | ||||
| const drawDialogVisible = ref(false); | ||||
| const newFieldCoords = ref([]); | ||||
| const newFieldArea = ref(0); | ||||
| const layerType = ref('image'); // 默认影像地图 | ||||
| const showFieldLayer = ref(true); | ||||
| const mapZoom = ref(12); | ||||
| const scale = ref(0); | ||||
| 
 | ||||
| // 天地图图层 | ||||
| const baseLayers = { | ||||
|   vector: null, | ||||
|   vectorLabel: null, | ||||
|   image: null, | ||||
|   imageLabel: null, | ||||
| }; | ||||
| 
 | ||||
| // 覆盖物相关 | ||||
| const fieldPolygons = ref([]); // 存储所有地块多边形对象 | ||||
| const polygonTool = ref(null); | ||||
| 
 | ||||
| // 计算属性 | ||||
| const filteredFields = computed(() => { | ||||
|   const term = searchTerm.value.toLowerCase(); | ||||
|   return term ? fields.value.filter((f) => f.name.toLowerCase().includes(term)) : fields.value; | ||||
| }); | ||||
| 
 | ||||
| // 确保地图有明确的高度 | ||||
| const mapHeight = ref('100vh'); | ||||
| const totalArea = computed(() => { | ||||
|   return fields.value.reduce((sum, field) => sum + field.area, 0); | ||||
| }); | ||||
| 
 | ||||
| onMounted(() => { | ||||
|   console.log('地图组件已挂载'); | ||||
|   // 检查天地图API是否加载成功 | ||||
|   if (window.T) { | ||||
|     console.log('天地图API已加载:', window.T); | ||||
|   } else { | ||||
|     console.error('天地图API未加载'); | ||||
|   } | ||||
|   loadFields(); | ||||
|   loadTiandituApi(map_config.tianditu).then((tianditu) => { | ||||
|     T.value = tianditu; | ||||
|     initMap(); | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| // 初始化地图 | ||||
| const initMap = () => { | ||||
|   if (!T.value) return; | ||||
| 
 | ||||
|   // 创建地图实例 | ||||
|   const center = new T.value.LngLat(...map_config.tianditu.center); | ||||
|   mapObj.value = new T.value.Map(mapContainer.value, { | ||||
|     center: center, | ||||
|     zoom: map_config.tianditu.zoom, | ||||
|     minZoom: 3, | ||||
|     maxZoom: 18, | ||||
|   }); | ||||
| 
 | ||||
|   // 初始化图层 | ||||
|   initLayers(); | ||||
| 
 | ||||
|   // 添加控件 | ||||
|   mapObj.value.addControl(new T.value.Control.Zoom()); | ||||
|   mapObj.value.addControl(new T.value.Control.Scale()); | ||||
| 
 | ||||
|   // 事件监听 | ||||
|   mapObj.value.addEventListener('zoomend', updateScale); | ||||
|   mapObj.value.addEventListener('moveend', updateScale); | ||||
| 
 | ||||
|   // 初始比例尺 | ||||
|   updateScale(); | ||||
| }; | ||||
| 
 | ||||
| // 初始化图层 | ||||
| const initLayers = () => { | ||||
|   if (!T.value || !mapObj.value) return; | ||||
| 
 | ||||
|   // 创建矢量地图图层 | ||||
|   const vecLayer = new T.value.TileLayer( | ||||
|     `https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${map_config.tianditu.token}`, | ||||
|     { minZoom: 3, maxZoom: 18, zIndex: 1 } | ||||
|   ); | ||||
| 
 | ||||
|   const cvaLayer = new T.value.TileLayer( | ||||
|     `https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${map_config.tianditu.token}`, | ||||
|     { minZoom: 3, maxZoom: 18, zIndex: 2 } | ||||
|   ); | ||||
| 
 | ||||
|   // 创建影像地图图层 | ||||
|   const imgLayer = new T.value.TileLayer( | ||||
|     `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${map_config.tianditu.token}`, | ||||
|     { minZoom: 3, maxZoom: 18, zIndex: 1 } | ||||
|   ); | ||||
| 
 | ||||
|   const ciaLayer = new T.value.TileLayer( | ||||
|     `https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${map_config.tianditu.token}`, | ||||
|     { minZoom: 3, maxZoom: 18, zIndex: 2 } | ||||
|   ); | ||||
| 
 | ||||
|   // 存储图层引用 | ||||
|   baseLayers.vector = vecLayer; | ||||
|   baseLayers.vectorLabel = cvaLayer; | ||||
|   baseLayers.image = imgLayer; | ||||
|   baseLayers.imageLabel = ciaLayer; | ||||
| 
 | ||||
|   // 默认加载影像地图 | ||||
|   mapObj.value.addLayer(imgLayer); | ||||
|   mapObj.value.addLayer(ciaLayer); | ||||
| }; | ||||
| 
 | ||||
| // 更新比例尺 | ||||
| const updateScale = () => { | ||||
|   if (mapObj.value) { | ||||
|     mapZoom.value = mapObj.value.getZoom(); | ||||
|     scale.value = Math.round(mapObj.value.getScale()); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 切换底图函数 | ||||
| const changeBaseLayer = () => { | ||||
|   if (!mapObj.value) return; | ||||
| 
 | ||||
|   // 移除所有基础图层 | ||||
|   [baseLayers.vector, baseLayers.vectorLabel, baseLayers.image, baseLayers.imageLabel].forEach((layer) => { | ||||
|     mapObj.value.removeLayer(layer); | ||||
|   }); | ||||
| 
 | ||||
|   // 添加新图层组合 | ||||
|   if (layerType.value === 'vector') { | ||||
|     mapObj.value.addLayer(baseLayers.vector); | ||||
|     mapObj.value.addLayer(baseLayers.vectorLabel); | ||||
|   } else { | ||||
|     mapObj.value.addLayer(baseLayers.image); | ||||
|     mapObj.value.addLayer(baseLayers.imageLabel); | ||||
|   } | ||||
| }; | ||||
| // 加载地块数据 | ||||
| const loadFields = () => { | ||||
|   fieldService.getFields().then((data) => { | ||||
|     fields.value = data; | ||||
|     refreshOverlays(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 刷新覆盖物 | ||||
| const refreshOverlays = () => { | ||||
|   if (!mapObj.value || !T.value) return; | ||||
| 
 | ||||
|   // 清除所有地块多边形 | ||||
|   clearFieldOverlays(); | ||||
| 
 | ||||
|   if (!showFieldLayer.value) return; | ||||
| 
 | ||||
|   // 绘制所有地块 | ||||
|   fields.value.forEach((field) => { | ||||
|     const polygon = createPolygon(field, selectedField.value?.id === field.id); | ||||
|     mapObj.value.addOverLay(polygon); | ||||
|     fieldPolygons.value.push(polygon); | ||||
|   }); | ||||
| }; | ||||
| // 清除所有地块覆盖物 | ||||
| const clearFieldOverlays = () => { | ||||
|   fieldPolygons.value.forEach((polygon) => { | ||||
|     mapObj.value.removeOverLay(polygon); | ||||
|   }); | ||||
|   fieldPolygons.value = []; | ||||
| }; | ||||
| // 添加单个覆盖物 | ||||
| const addFieldToMap = (field) => { | ||||
|   const polygon = createPolygon(field); | ||||
|   mapObj.value.addOverLay(polygon); // 正确方法 | ||||
|   return polygon; | ||||
| }; | ||||
| 
 | ||||
| // 移除单个覆盖物 | ||||
| const removeFieldFromMap = (polygon) => { | ||||
|   mapObj.value.removeOverLay(polygon); // 正确方法 | ||||
| }; | ||||
| 
 | ||||
| // 清除所有覆盖物 | ||||
| const clearAllOverlays = () => { | ||||
|   mapObj.value.clearOverLays(); // 注意拼写 | ||||
| }; | ||||
| 
 | ||||
| // 创建多边形 | ||||
| const createPolygon = (field, isSelected = false) => { | ||||
|   const coords = field.geometry.coordinates[0].map((c) => new T.value.LngLat(c[0], c[1])); | ||||
|   return new T.value.Polygon(coords, { | ||||
|     color: isSelected ? '#ff0000' : '#1890ff', | ||||
|     weight: isSelected ? 3 : 2, | ||||
|     opacity: 0.8, | ||||
|     fillColor: isSelected ? '#ffa39e' : '#e6f7ff', | ||||
|     fillOpacity: 0.5, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 选择地块 | ||||
| const selectField = (field) => { | ||||
|   selectedField.value = field; | ||||
|   refreshOverlays(); | ||||
| 
 | ||||
|   if (mapObj.value && T.value) { | ||||
|     const coords = field.geometry.coordinates[0]; | ||||
|     const center = coords | ||||
|       .reduce( | ||||
|         (acc, coord) => { | ||||
|           return [acc[0] + coord[0], acc[1] + coord[1]]; | ||||
|         }, | ||||
|         [0, 0] | ||||
|       ) | ||||
|       .map((v) => v / coords.length); | ||||
| 
 | ||||
|     mapObj.value.panTo(new T.value.LngLat(center[0], center[1])); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 开始绘制 | ||||
| const startDrawing = () => { | ||||
|   if (!mapObj.value || !T.value) return; | ||||
| 
 | ||||
|   // 创建绘制工具 | ||||
|   polygonTool.value = new T.value.PolygonTool(mapObj.value, { | ||||
|     showLabel: true, | ||||
|     color: '#fa541c', | ||||
|     weight: 3, | ||||
|     opacity: 0.6, | ||||
|     fillColor: '#ffbb96', | ||||
|     fillOpacity: 0.4, | ||||
|   }); | ||||
| 
 | ||||
|   polygonTool.value.open(); | ||||
| 
 | ||||
|   polygonTool.value.addEventListener('draw', (e) => { | ||||
|     newFieldCoords.value = e.currentLnglats.map((lnglat) => [lnglat.lng, lnglat.lat]); | ||||
|     newFieldArea.value = e.currentArea; | ||||
|     drawDialogVisible.value = true; | ||||
|     polygonTool.value.close(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 保存新地块 | ||||
| const saveNewField = () => { | ||||
|   if (!newFieldName.value.trim()) { | ||||
|     ElMessage.error('请输入地块名称'); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const newField = { | ||||
|     id: Date.now(), | ||||
|     name: newFieldName.value, | ||||
|     area: newFieldArea.value, | ||||
|     geometry: { | ||||
|       type: 'Polygon', | ||||
|       coordinates: [newFieldCoords.value], | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   fieldService.createField(newField).then(() => { | ||||
|     ElMessage.success('地块创建成功'); | ||||
|     drawDialogVisible.value = false; | ||||
|     newFieldName.value = ''; | ||||
|     loadFields(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 取消绘制 | ||||
| const cancelDrawing = () => { | ||||
|   drawDialogVisible.value = false; | ||||
|   refreshOverlays(); | ||||
| }; | ||||
| 
 | ||||
| // 删除地块 | ||||
| const removeField = () => { | ||||
|   if (!selectedField.value) return; | ||||
| 
 | ||||
|   fieldService.deleteField(selectedField.value.id).then(() => { | ||||
|     ElMessage.success('地块已删除'); | ||||
|     selectedField.value = null; | ||||
|     loadFields(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 切换地块图层显示 | ||||
| const toggleFieldLayer = () => { | ||||
|   refreshOverlays(); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| .mapDiv { | ||||
| .custom-page { | ||||
|   display: flex; | ||||
|   padding: 0; | ||||
| } | ||||
| .panel-left { | ||||
|   width: 300px; | ||||
|   height: calc(100vh - 130px); | ||||
|   border-right: 1px solid #eee; | ||||
|   padding: 10px; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   background: #f8fafc; | ||||
| } | ||||
| .panel-right { | ||||
|   flex: 1; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .map { | ||||
|   width: 100%; | ||||
|   height: 100vh; /* 确保容器有明确高度 */ | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| .layer-switch { | ||||
|   position: absolute; | ||||
|   top: 15px; | ||||
|   right: 15px; | ||||
|   background: white; | ||||
|   padding: 8px; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
| } | ||||
| 
 | ||||
| .field-layer-switch { | ||||
|   position: absolute; | ||||
|   top: 60px; | ||||
|   right: 15px; | ||||
|   background: white; | ||||
|   padding: 8px; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 8px; | ||||
| } | ||||
| 
 | ||||
| .zoom-scale { | ||||
|   position: absolute; | ||||
|   bottom: 15px; | ||||
|   left: 50%; | ||||
|   transform: translateX(-50%); | ||||
|   background: rgba(255, 255, 255, 0.85); | ||||
|   padding: 6px 12px; | ||||
|   border-radius: 4px; | ||||
|   font-size: 14px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
| } | ||||
| 
 | ||||
| .stats { | ||||
|   padding: 12px; | ||||
|   margin: 10px 0; | ||||
|   background: #edf2ff; | ||||
|   border-radius: 4px; | ||||
|   font-weight: 500; | ||||
|   color: #364fc7; | ||||
| } | ||||
| 
 | ||||
| .button-group { | ||||
|   display: flex; | ||||
|   gap: 10px; | ||||
|   margin-top: 10px; | ||||
| } | ||||
| 
 | ||||
| .button-group > * { | ||||
|   flex: 1; | ||||
| } | ||||
| </style> | ||||
|  | ||||
| @ -1,401 +0,0 @@ | ||||
| <template> | ||||
|   <div class="field-management"> | ||||
|     <!-- 左侧面板 --> | ||||
|     <div class="panel left"> | ||||
|       <el-input v-model="searchTerm" placeholder="搜索地块名称" clearable style="margin-bottom: 15px"></el-input> | ||||
| 
 | ||||
|       <el-table :data="filteredFields" highlight-current-row style="width: 100%" height="calc(100vh - 300px)" @row-click="selectField"> | ||||
|         <el-table-column prop="name" label="名称"></el-table-column> | ||||
|         <el-table-column prop="area" label="面积(㎡)"> | ||||
|           <template #default="{ row }"> | ||||
|             {{ row.area.toFixed(2) }} | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </el-table> | ||||
| 
 | ||||
|       <div class="stats">总面积: {{ totalArea.toFixed(2) }} ㎡</div> | ||||
| 
 | ||||
|       <div class="button-group"> | ||||
|         <el-button type="primary" @click="startDrawing">新增地块</el-button> | ||||
|         <el-button type="danger" :disabled="!selectedField" @click="removeField">删除地块</el-button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <!-- 右侧地图区域 --> | ||||
|     <div class="panel right"> | ||||
|       <tdt-map class="map" :center="mapCenter" :zoom="mapZoom" @init="onMapInit"> </tdt-map> | ||||
| 
 | ||||
|       <div class="layer-switch"> | ||||
|         <el-radio-group v-model="layerType" size="small" @change="changeBaseLayer"> | ||||
|           <el-radio-button label="vector">矢量</el-radio-button> | ||||
|           <el-radio-button label="image">影像</el-radio-button> | ||||
|         </el-radio-group> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="field-layer-switch"> | ||||
|         <el-switch v-model="showFieldLayer" active-text="显示地块"></el-switch> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="zoom-scale">缩放级别: {{ mapZoom }}, 比例尺: 1:{{ scale.toLocaleString() }}</div> | ||||
| 
 | ||||
|       <el-dialog v-model="drawDialogVisible" title="新增地块"> | ||||
|         <el-form> | ||||
|           <el-form-item label="地块名称"> | ||||
|             <el-input v-model="newFieldName"></el-input> | ||||
|           </el-form-item> | ||||
|           <el-form-item label="地块面积"> | ||||
|             <el-input :value="newFieldArea.toFixed(2) + ' ㎡'" readonly></el-input> | ||||
|           </el-form-item> | ||||
|         </el-form> | ||||
|         <template #footer> | ||||
|           <el-button @click="cancelDrawing">取消</el-button> | ||||
|           <el-button type="primary" @click="saveNewField">保存</el-button> | ||||
|         </template> | ||||
|       </el-dialog> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { ref, computed, onMounted, watch } from 'vue'; | ||||
| import { TdtMap } from 'vue-tianditu'; | ||||
| import { ElMessage } from 'element-plus'; | ||||
| // import { map_config } from '../../config/map'; | ||||
| 
 | ||||
| // 模拟接口服务(使用localStorage) | ||||
| const fieldService = { | ||||
|   getFields() { | ||||
|     const fields = localStorage.getItem('fields'); | ||||
|     return Promise.resolve(fields ? JSON.parse(fields) : []); | ||||
|   }, | ||||
|   createField(field) { | ||||
|     return this.getFields().then((fields) => { | ||||
|       field.id = Date.now(); | ||||
|       fields.push(field); | ||||
|       localStorage.setItem('fields', JSON.stringify(fields)); | ||||
|       return field; | ||||
|     }); | ||||
|   }, | ||||
|   deleteField(id) { | ||||
|     return this.getFields().then((fields) => { | ||||
|       const newFields = fields.filter((f) => f.id !== id); | ||||
|       localStorage.setItem('fields', JSON.stringify(newFields)); | ||||
|       return newFields; | ||||
|     }); | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| // 响应式状态 | ||||
| const fields = ref([]); | ||||
| const selectedField = ref(null); | ||||
| const searchTerm = ref(''); | ||||
| const newFieldName = ref(''); | ||||
| const drawDialogVisible = ref(false); | ||||
| const newFieldCoords = ref([]); | ||||
| const newFieldArea = ref(0); | ||||
| const layerType = ref('vector'); | ||||
| const showFieldLayer = ref(true); | ||||
| const map = ref(null); | ||||
| const mapCenter = ref([100.088, 23.883]); | ||||
| const mapZoom = ref(12); | ||||
| const scale = ref(0); | ||||
| 
 | ||||
| // 天地图图层 | ||||
| const baseLayers = { | ||||
|   vector: null, | ||||
|   vectorLabel: null, | ||||
|   image: null, | ||||
|   imageLabel: null, | ||||
| }; | ||||
| 
 | ||||
| // 覆盖物图层 | ||||
| const overlayLayer = ref(null); | ||||
| 
 | ||||
| // 计算属性 | ||||
| const filteredFields = computed(() => { | ||||
|   const term = searchTerm.value.toLowerCase(); | ||||
|   return term ? fields.value.filter((f) => f.name.toLowerCase().includes(term)) : fields.value; | ||||
| }); | ||||
| 
 | ||||
| const totalArea = computed(() => { | ||||
|   return fields.value.reduce((sum, field) => sum + field.area, 0); | ||||
| }); | ||||
| 
 | ||||
| // 地图初始化 | ||||
| const onMapInit = (mapInstance) => { | ||||
|   map.value = mapInstance; | ||||
| 
 | ||||
|   // 创建图层 | ||||
|   baseLayers.vector = new T.TileLayer('vec'); | ||||
|   baseLayers.vectorLabel = new T.TileLayer('cva'); | ||||
|   baseLayers.image = new T.TileLayer('img'); | ||||
|   baseLayers.imageLabel = new T.TileLayer('cia'); | ||||
| 
 | ||||
|   // 添加默认图层 | ||||
|   map.value.addLayer(baseLayers.image); | ||||
|   map.value.addLayer(baseLayers.imageLabel); | ||||
| 
 | ||||
|   // 创建覆盖物图层 | ||||
|   overlayLayer.value = new T.Map.OverlayLayer(); | ||||
|   map.value.addLayer(overlayLayer.value); | ||||
| 
 | ||||
|   // 添加控件 | ||||
|   map.value.addControl(new T.Control.Zoom()); | ||||
|   map.value.addControl(new T.Control.Scale()); | ||||
| 
 | ||||
|   // 事件监听 | ||||
|   map.value.addEventListener('zoomend', updateScale); | ||||
|   map.value.addEventListener('moveend', updateScale); | ||||
| 
 | ||||
|   // 初始加载数据 | ||||
|   loadFields(); | ||||
| }; | ||||
| 
 | ||||
| // 更新比例尺 | ||||
| const updateScale = () => { | ||||
|   if (map.value) { | ||||
|     mapZoom.value = map.value.getZoom(); | ||||
|     scale.value = Math.round(map.value.getScale()); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 切换底图 | ||||
| const changeBaseLayer = () => { | ||||
|   if (!map.value) return; | ||||
| 
 | ||||
|   // 移除当前底图 | ||||
|   map.value.removeLayer(baseLayers.vector); | ||||
|   map.value.removeLayer(baseLayers.vectorLabel); | ||||
|   map.value.removeLayer(baseLayers.image); | ||||
|   map.value.removeLayer(baseLayers.imageLabel); | ||||
| 
 | ||||
|   // 添加新底图 | ||||
|   if (layerType.value === 'vector') { | ||||
|     map.value.addLayer(baseLayers.vector); | ||||
|     map.value.addLayer(baseLayers.vectorLabel); | ||||
|   } else { | ||||
|     map.value.addLayer(baseLayers.image); | ||||
|     map.value.addLayer(baseLayers.imageLabel); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 加载地块数据 | ||||
| const loadFields = () => { | ||||
|   fieldService.getFields().then((data) => { | ||||
|     fields.value = data; | ||||
|     refreshOverlays(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 刷新覆盖物 | ||||
| const refreshOverlays = () => { | ||||
|   if (!map.value || !overlayLayer.value) return; | ||||
| 
 | ||||
|   // 清除所有覆盖物 | ||||
|   overlayLayer.value.clearOverLays(); | ||||
| 
 | ||||
|   // 如果不显示地块图层则跳过绘制 | ||||
|   if (!showFieldLayer.value) return; | ||||
| 
 | ||||
|   // 绘制所有地块 | ||||
|   fields.value.forEach((field) => { | ||||
|     const isSelected = selectedField.value?.id === field.id; | ||||
|     const polygon = createPolygon(field, isSelected); | ||||
|     overlayLayer.value.addOverLay(polygon); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 创建多边形 | ||||
| const createPolygon = (field, isSelected = false) => { | ||||
|   const coords = field.geometry.coordinates[0].map((c) => new T.LngLat(c[0], c[1])); | ||||
|   return new T.Polygon(coords, { | ||||
|     color: isSelected ? '#ff0000' : '#1890ff', | ||||
|     weight: isSelected ? 3 : 2, | ||||
|     opacity: 0.8, | ||||
|     fillColor: isSelected ? '#ffa39e' : '#e6f7ff', | ||||
|     fillOpacity: 0.5, | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 选择地块 | ||||
| const selectField = (field) => { | ||||
|   selectedField.value = field; | ||||
|   refreshOverlays(); | ||||
| 
 | ||||
|   // 居中显示选中地块 | ||||
|   if (map.value) { | ||||
|     const coords = field.geometry.coordinates[0]; | ||||
|     const center = coords.reduce( | ||||
|       (acc, coord) => { | ||||
|         acc[0] += coord[0]; | ||||
|         acc[1] += coord[1]; | ||||
|         return acc; | ||||
|       }, | ||||
|       [0, 0] | ||||
|     ); | ||||
| 
 | ||||
|     center[0] /= coords.length; | ||||
|     center[1] /= coords.length; | ||||
| 
 | ||||
|     map.value.panTo(new T.LngLat(center[0], center[1])); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // 开始绘制 | ||||
| const startDrawing = () => { | ||||
|   if (!map.value) return; | ||||
| 
 | ||||
|   // 清除当前覆盖物 | ||||
|   refreshOverlays(); | ||||
| 
 | ||||
|   // 创建绘制工具 | ||||
|   const polygonTool = new T.PolygonTool(map.value, { | ||||
|     showLabel: true, | ||||
|     color: '#fa541c', | ||||
|     weight: 3, | ||||
|     opacity: 0.6, | ||||
|     fillColor: '#ffbb96', | ||||
|     fillOpacity: 0.4, | ||||
|   }); | ||||
| 
 | ||||
|   polygonTool.open(); | ||||
| 
 | ||||
|   // 绘制完成事件 | ||||
|   polygonTool.addEventListener('draw', (e) => { | ||||
|     newFieldCoords.value = e.currentLnglats.map((lnglat) => [lnglat.lng, lnglat.lat]); | ||||
|     newFieldArea.value = e.currentArea; | ||||
|     drawDialogVisible.value = true; | ||||
| 
 | ||||
|     // 清除临时图形 | ||||
|     polygonTool.close(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 保存新地块 | ||||
| const saveNewField = () => { | ||||
|   if (!newFieldName.value.trim()) { | ||||
|     ElMessage.error('请输入地块名称'); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   const newField = { | ||||
|     id: Date.now(), | ||||
|     name: newFieldName.value, | ||||
|     area: newFieldArea.value, | ||||
|     geometry: { | ||||
|       type: 'Polygon', | ||||
|       coordinates: [newFieldCoords.value], | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   fieldService.createField(newField).then(() => { | ||||
|     ElMessage.success('地块创建成功'); | ||||
|     drawDialogVisible.value = false; | ||||
|     newFieldName.value = ''; | ||||
|     loadFields(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 取消绘制 | ||||
| const cancelDrawing = () => { | ||||
|   drawDialogVisible.value = false; | ||||
|   refreshOverlays(); | ||||
| }; | ||||
| 
 | ||||
| // 删除地块 | ||||
| const removeField = () => { | ||||
|   if (!selectedField.value) return; | ||||
| 
 | ||||
|   fieldService.deleteField(selectedField.value.id).then(() => { | ||||
|     ElMessage.success('地块已删除'); | ||||
|     selectedField.value = null; | ||||
|     loadFields(); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| // 监听地块图层显示状态 | ||||
| watch(showFieldLayer, refreshOverlays); | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| .field-management { | ||||
|   display: flex; | ||||
|   height: calc(100vh - 160px); | ||||
| } | ||||
| 
 | ||||
| .panel.left { | ||||
|   width: 320px; | ||||
|   padding: 15px; | ||||
|   border-right: 1px solid #e4e7ed; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   background: #f8fafc; | ||||
| } | ||||
| 
 | ||||
| .panel.right { | ||||
|   flex: 1; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .map { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| 
 | ||||
| .layer-switch { | ||||
|   position: absolute; | ||||
|   top: 15px; | ||||
|   right: 15px; | ||||
|   background: white; | ||||
|   padding: 8px; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
| } | ||||
| 
 | ||||
| .field-layer-switch { | ||||
|   position: absolute; | ||||
|   top: 60px; | ||||
|   right: 15px; | ||||
|   background: white; | ||||
|   padding: 8px; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   gap: 8px; | ||||
| } | ||||
| 
 | ||||
| .zoom-scale { | ||||
|   position: absolute; | ||||
|   bottom: 15px; | ||||
|   left: 50%; | ||||
|   transform: translateX(-50%); | ||||
|   background: rgba(255, 255, 255, 0.85); | ||||
|   padding: 6px 12px; | ||||
|   border-radius: 4px; | ||||
|   font-size: 14px; | ||||
|   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); | ||||
|   z-index: 1000; | ||||
| } | ||||
| 
 | ||||
| .stats { | ||||
|   padding: 12px; | ||||
|   margin: 10px 0; | ||||
|   background: #edf2ff; | ||||
|   border-radius: 4px; | ||||
|   font-weight: 500; | ||||
|   color: #364fc7; | ||||
| } | ||||
| 
 | ||||
| .button-group { | ||||
|   display: flex; | ||||
|   gap: 10px; | ||||
|   margin-top: 10px; | ||||
| } | ||||
| 
 | ||||
| .button-group > * { | ||||
|   flex: 1; | ||||
| } | ||||
| </style> | ||||
| @ -1,24 +1,31 @@ | ||||
| <template> | ||||
|   <div class="land-search"> | ||||
|     <el-form :model="search" inline label-width="100px"> | ||||
|       <el-form-item label="地块名称"> | ||||
|         <el-input v-model="search.name" placeholder="请输入地块名称" clearable /> | ||||
|       </el-form-item> | ||||
|     <div class="search-row"> | ||||
|       <div class="search-item"> | ||||
|         <span class="search-label">关键词搜索:</span> | ||||
|         <el-input v-model="localSearch.keyWord" placeholder="请输入关键词" clearable class="search-input" /> | ||||
|       </div> | ||||
| 
 | ||||
|       <el-form-item label="所属区域"> | ||||
|         <AreaCascader v-model:region-code="search.regionCode" v-model:grid-id="search.id" :width="300" /> | ||||
|       </el-form-item> | ||||
|       <div class="search-item"> | ||||
|         <span class="search-label">地块名称:</span> | ||||
|         <el-input v-model="localSearch.name" placeholder="请输入地块名称" clearable class="search-input" /> | ||||
|       </div> | ||||
| 
 | ||||
|       <el-form-item> | ||||
|         <el-button type="primary" @click="onSearch">搜索</el-button> | ||||
|         <el-button @click="onReset">重置</el-button> | ||||
|       </el-form-item> | ||||
|     </el-form> | ||||
|       <div class="search-item"> | ||||
|         <span class="search-label">所属区域-网格:</span> | ||||
|         <AreaCascader v-model:region-code="localSearch.regionCode" v-model:grid-id="localSearch.id" :width="500" :label="null" /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div class="search-item"> | ||||
|         <el-button type="primary" @click="handleSearch">搜索</el-button> | ||||
|         <el-button @click="handleReset">重置</el-button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <script setup> | ||||
| import { reactive, watch, toRefs } from 'vue'; | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps({ | ||||
|   search: { | ||||
| @ -27,33 +34,46 @@ const props = defineProps({ | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits(['update:search', 'search', 'reset']); | ||||
| const emit = defineEmits(['on-search']); | ||||
| 
 | ||||
| const search = reactive({ ...props.search }); | ||||
| const localSearch = ref({ ...props.search }); | ||||
| 
 | ||||
| watch( | ||||
|   search, | ||||
|   (val) => { | ||||
|     emit('update:search', { ...val }); | ||||
|   }, | ||||
|   { deep: true } | ||||
| ); | ||||
| 
 | ||||
| const onSearch = () => { | ||||
|   emit('search'); | ||||
| const handleSearch = () => { | ||||
|   emit('on-search', { ...localSearch.value }); | ||||
| }; | ||||
| 
 | ||||
| const onReset = () => { | ||||
|   Object.keys(search).forEach((key) => { | ||||
|     search[key] = ''; | ||||
|   }); | ||||
|   emit('update:search', { ...search }); | ||||
|   emit('reset'); | ||||
| const handleReset = () => { | ||||
|   localSearch.value = {}; | ||||
|   handleSearch(); | ||||
| }; | ||||
| </script> | ||||
| 
 | ||||
| <style scoped> | ||||
| .land-search { | ||||
|   margin-bottom: 16px; | ||||
|   padding: 16px; | ||||
|   background: #fff; | ||||
|   border-radius: 4px; | ||||
| } | ||||
| 
 | ||||
| .search-row { | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
|   gap: 24px; | ||||
|   align-items: center; | ||||
| } | ||||
| 
 | ||||
| .search-item { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
| 
 | ||||
| .search-label { | ||||
|   font-size: 14px; | ||||
|   margin-right: 8px; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .search-input { | ||||
|   width: 200px; | ||||
| } | ||||
| </style> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user