日常提交
This commit is contained in:
parent
ef57aabc55
commit
0fb4c5772c
@ -12,6 +12,7 @@ declare module 'vue' {
|
|||||||
'CenterMap copy': typeof import('./src/components/centerMap copy.vue')['default']
|
'CenterMap copy': typeof import('./src/components/centerMap copy.vue')['default']
|
||||||
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
|
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
|
||||||
Components: typeof import('./src/components/index.js')['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']
|
CurrentTime: typeof import('./src/components/currentTime.vue')['default']
|
||||||
CustomBack: typeof import('./src/components/customBack.vue')['default']
|
CustomBack: typeof import('./src/components/customBack.vue')['default']
|
||||||
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.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 { registerElIcons } from './plugins/icon';
|
||||||
import { registerMicroApps } from './plugins/micro';
|
import { registerMicroApps } from './plugins/micro';
|
||||||
import { registerSplitpanes } from './plugins/splitpanes';
|
import { registerSplitpanes } from './plugins/splitpanes';
|
||||||
import VueTianditu from 'vue-tianditu';
|
|
||||||
import { map_config } from './config/map';
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(pinia).use(router).use(ElementPlus).use(Avue).use(VueTianditu, {
|
app.use(pinia).use(router).use(ElementPlus).use(Avue);
|
||||||
v: map_config.tianditu.version,
|
|
||||||
tk: map_config.tianditu.token,
|
|
||||||
});
|
|
||||||
registerGlobalComponents(app);
|
registerGlobalComponents(app);
|
||||||
registerElIcons(app);
|
registerElIcons(app);
|
||||||
registerSplitpanes(app);
|
registerSplitpanes(app);
|
||||||
|
@ -41,16 +41,23 @@ export const constantRoutes = [
|
|||||||
...inputSuppliesRoutes,
|
...inputSuppliesRoutes,
|
||||||
{
|
{
|
||||||
path: '/sub-government-affairs-service/output-products',
|
path: '/sub-government-affairs-service/output-products',
|
||||||
name: 'outputProducts',
|
name: 'OutputProducts',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/sub-government-affairs-service/output-products/index',
|
redirect: '/sub-government-affairs-service/output-products/output-info',
|
||||||
meta: { title: '产出品管理', icon: 'Box' },
|
meta: { title: '产出品管理', icon: 'Box' },
|
||||||
children: [
|
children: [
|
||||||
|
// 产出品概览
|
||||||
{
|
{
|
||||||
path: '/sub-government-affairs-service/output-products/index',
|
path: '/sub-government-affairs-service/output-products/output-statistics',
|
||||||
component: () => import('@/views/outputProductsManage/index.vue'),
|
component: () => import('@/views/output-products/output-statistics/index.vue'),
|
||||||
name: 'outputProductsIndex',
|
name: 'OutputStatistics',
|
||||||
meta: { title: '产出品管理', icon: 'List' },
|
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>
|
<template>
|
||||||
<div class="custom-page">
|
<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
|
<avue-crud
|
||||||
ref="crudRef"
|
ref="crudRef"
|
||||||
v-model:page="pageData"
|
v-model:page="pageData"
|
||||||
@ -10,13 +15,6 @@
|
|||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@size-change="handleSizeChange"
|
@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>
|
<!-- <template #menu-left>
|
||||||
<el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button>
|
<el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button>
|
||||||
</template> -->
|
</template> -->
|
||||||
@ -152,10 +150,10 @@ const filterObject = (obj) => {
|
|||||||
const crudData = ref([]);
|
const crudData = ref([]);
|
||||||
const crudOptions = reactive({
|
const crudOptions = reactive({
|
||||||
...CRUD_OPTIONS,
|
...CRUD_OPTIONS,
|
||||||
addBtn: false,
|
menu: false,
|
||||||
searchBtn: false,
|
header: false,
|
||||||
emptyBtn: false,
|
|
||||||
height: 'calc(100vh - 340px)',
|
height: 'calc(100vh - 340px)',
|
||||||
|
selection: true,
|
||||||
column: [
|
column: [
|
||||||
{ label: '行政区划编码', prop: 'AreaCode', width: 150 },
|
{ label: '行政区划编码', prop: 'AreaCode', width: 150 },
|
||||||
{ label: '行政区划', prop: 'gridAreaName', 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>
|
<template>
|
||||||
<div class="custom-page">
|
<div class="custom-page">
|
||||||
<h1>林地</h1>
|
<h1>林地</h1>
|
||||||
<!-- 搜索 -->
|
<LandSearch :search="searchForm" @on-search="handleSearch" @on-reset="handleReset" />
|
||||||
<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-tabs v-model="activeTab" @tab-click="handleTabChange">
|
||||||
<!-- <el-tab-pane label="待提交" name="0" /> -->
|
<!-- <el-tab-pane label="待提交" name="0" /> -->
|
||||||
<el-tab-pane label="待审核" name="1" />
|
<el-tab-pane label="待审核" name="1" />
|
||||||
<el-tab-pane label="已通过" name="2" />
|
<el-tab-pane label="已通过" name="2" />
|
||||||
<el-tab-pane label="已驳回" name="0" />
|
<el-tab-pane label="已驳回" name="0" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<avue-crud
|
<TableComponent
|
||||||
ref="crudRef"
|
:loading="loading"
|
||||||
v-model:page="pageData"
|
:columns="columns"
|
||||||
:data="crudData"
|
:table-data="tableData"
|
||||||
:option="crudOptions"
|
:current-page="pageData.currentPage"
|
||||||
:table-loading="loading"
|
:page-size="pageData.pageSize"
|
||||||
@current-change="handleCurrentChange"
|
:total="pageData.total"
|
||||||
@size-change="handleSizeChange"
|
:show-pagination="true"
|
||||||
>
|
:show-border="true"
|
||||||
<!-- <template #menu-left>
|
:show-sort="true"
|
||||||
<el-button type="primary" icon="Plus" @click="handleAdd">新增网格</el-button>
|
style="height: calc(100vh - 320px)"
|
||||||
</template> -->
|
@page-change="handlePageChange"
|
||||||
<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-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">
|
<el-tabs v-model="activeFormTab" class="tabs-wrapper">
|
||||||
<p class="form-title">填写网格信息</p>
|
<el-tab-pane label="土地基本信息" name="basic">
|
||||||
<el-form-item label="网格名称" prop="gridName">
|
<p class="form-title">基本信息</p>
|
||||||
<el-input v-model="formData.gridName" placeholder="请输入网格名称" />
|
<el-form ref="basicFormRef" :model="formData" :disabled="isReadonly" label-width="120px">
|
||||||
</el-form-item>
|
<el-row :gutter="20">
|
||||||
<el-form-item label="所属行政区域" prop="gridAreaCode">
|
<el-col :span="12">
|
||||||
<AreaSelect v-model="formData.gridAreaCode" :label="null" :emit-path="false" />
|
<el-form-item label="地块名称" prop="landName">
|
||||||
</el-form-item>
|
<el-input v-model="formData.landName" placeholder="请输入地块名称" />
|
||||||
<el-form-item label="网格化地图" prop="scopeImg">
|
</el-form-item>
|
||||||
<FileUploader v-model="formData.scopeImg" :limit="1" />
|
<el-form-item label="土地类型" prop="landType">
|
||||||
</el-form-item>
|
<el-tree-select
|
||||||
<el-form-item label="备注" prop="note">
|
v-model="formData.landType"
|
||||||
<el-input v-model="formData.note" placeholder="请输入备注" />
|
:data="landTypeOptions"
|
||||||
</el-form-item>
|
:props="treeProps"
|
||||||
</el-form>
|
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>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="handleCancel">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button v-if="!isReadonly" type="primary" @click="handleSubmit()"> 保存 </el-button>
|
<template v-if="!isReadonly">
|
||||||
</div>
|
<el-button type="primary" @click="submitAll">修改</el-button>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// ---------------------------------------------------------------------
|
import { ref, onMounted } from 'vue';
|
||||||
// avue-crud 通用代码
|
import LandSearch from './components/LandSearch.vue';
|
||||||
// ---------------------------------------------------------------------
|
import TableComponent from '@/components/tableComponent.vue';
|
||||||
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 searchForm = ref({});
|
||||||
const user = UserStore.getUserInfo();
|
const handleSearch = (searchData) => {
|
||||||
console.log('admin 属性:', user.admin);
|
// 使用搜索条件
|
||||||
|
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 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 visible = ref(false);
|
||||||
const isReadonly = ref(false);
|
const isReadonly = ref(false);
|
||||||
const dialogTitle = ref();
|
const dialogTitle = ref();
|
||||||
const activeTab = ref('1');
|
const activeFormTab = ref('basic');
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
gridName: '',
|
gridName: '',
|
||||||
gridAreaCode: '',
|
gridAreaCode: '',
|
||||||
@ -95,165 +214,9 @@ const resetForm = () => {
|
|||||||
formData.value = { ...initialFormData };
|
formData.value = { ...initialFormData };
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageData = ref({
|
import { mockData } from './landData';
|
||||||
currentPage: 1,
|
import { createLand, deleteLand, editLand, fetchLandList, getLandById, approveLand } from '@/apis/landResourceManagement/landManagement';
|
||||||
pageSize: 10,
|
import request from '@/utils/axios';
|
||||||
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 getData = async () => {
|
||||||
// const filteredParams = filterObject(searchForm.value);
|
// const filteredParams = filterObject(searchForm.value);
|
||||||
// const response = await fetchGridList(filteredParams);
|
// const response = await fetchGridList(filteredParams);
|
||||||
@ -261,69 +224,50 @@ const getData = async () => {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
// 模拟接口延迟
|
// 模拟接口延迟
|
||||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
crudData.value = mockData
|
tableData.value = mockData
|
||||||
.filter((item) => item.status === activeTab.value)
|
.filter((item) => item.status === activeTab.value)
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
landTypeName: '林地',
|
landTypeName: '林地',
|
||||||
}));
|
}));
|
||||||
pageData.value.total = crudData.value.length;
|
pageData.value.total = tableData.value.length;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
const getLandDetail = async (id) => {
|
||||||
const handleAdd = () => {
|
const response = await getLandById(id);
|
||||||
console.log('handleAdd');
|
return response.data;
|
||||||
resetForm();
|
|
||||||
isReadonly.value = false;
|
|
||||||
dialogTitle.value = '新增网格';
|
|
||||||
visible.value = true;
|
|
||||||
};
|
};
|
||||||
|
const landTypeOptions = ref([]);
|
||||||
const handleSearch = () => {
|
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();
|
getData();
|
||||||
};
|
fetchLandTypeData();
|
||||||
const handleCancel = () => {
|
});
|
||||||
visible.value = false;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.custom-page {
|
.custom-page {
|
||||||
padding-bottom: 0px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
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>
|
</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"
|
@size-change="handleSizeChange"
|
||||||
>
|
>
|
||||||
<template #menu="scope">
|
<template #menu="scope">
|
||||||
<custom-table-operate :actions="crudOptions.actions" :data="scope" />
|
<custom-table-operate :actions="crudOptions.actions" :show="false" :data="scope" />
|
||||||
</template>
|
</template>
|
||||||
</avue-crud>
|
</avue-crud>
|
||||||
<el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true">
|
<el-dialog :key="dialogTitle" v-model="visible" :title="dialogTitle" width="60%" align-center :draggable="true">
|
||||||
|
@ -1,47 +1,458 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mapDiv">
|
<div class="custom-page">
|
||||||
<tdt-map ref="tdtMapRef" :center="state.center" :zoom="state.zoom" :style="{ height: mapHeight }">
|
<!-- 左侧面板 -->
|
||||||
<!-- <tdt-tile-layer :layer-type="'img_w'"></tdt-tile-layer> -->
|
<div class="panel-left">
|
||||||
<!-- <tdt-tile-layer :layer-type="'cia_w'" :z-index="2"></tdt-tile-layer> -->
|
<el-input v-model="searchTerm" placeholder="搜索地块名称" clearable style="margin-bottom: 15px"></el-input>
|
||||||
<tdt-tilelayer :url="state.img_w_url" :z-index="1"></tdt-tilelayer>
|
|
||||||
<tdt-tilelayer :url="state.cia_w_url" :z-index="2"></tdt-tilelayer>
|
<el-table :data="filteredFields" highlight-current-row style="width: 100%" @row-click="selectField">
|
||||||
</tdt-map>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, onMounted, ref } from 'vue';
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
import { TdtMap, TdtTileLayer } from 'vue-tianditu';
|
import { ElMessage } from 'element-plus';
|
||||||
import { map_config } from '@/config/map';
|
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({
|
const mapContainer = ref(null);
|
||||||
center: [100.088, 23.883],
|
const mapObj = ref(null);
|
||||||
zoom: 14,
|
const T = ref(null); // 天地图API对象
|
||||||
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 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 totalArea = computed(() => {
|
||||||
const mapHeight = ref('100vh');
|
return fields.value.reduce((sum, field) => sum + field.area, 0);
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('地图组件已挂载');
|
loadFields();
|
||||||
// 检查天地图API是否加载成功
|
loadTiandituApi(map_config.tianditu).then((tianditu) => {
|
||||||
if (window.T) {
|
T.value = tianditu;
|
||||||
console.log('天地图API已加载:', window.T);
|
initMap();
|
||||||
} else {
|
});
|
||||||
console.error('天地图API未加载');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 初始化地图
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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%;
|
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>
|
</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>
|
<template>
|
||||||
<div class="land-search">
|
<div class="land-search">
|
||||||
<el-form :model="search" inline label-width="100px">
|
<div class="search-row">
|
||||||
<el-form-item label="地块名称">
|
<div class="search-item">
|
||||||
<el-input v-model="search.name" placeholder="请输入地块名称" clearable />
|
<span class="search-label">关键词搜索:</span>
|
||||||
</el-form-item>
|
<el-input v-model="localSearch.keyWord" placeholder="请输入关键词" clearable class="search-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-form-item label="所属区域">
|
<div class="search-item">
|
||||||
<AreaCascader v-model:region-code="search.regionCode" v-model:grid-id="search.id" :width="300" />
|
<span class="search-label">地块名称:</span>
|
||||||
</el-form-item>
|
<el-input v-model="localSearch.name" placeholder="请输入地块名称" clearable class="search-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-form-item>
|
<div class="search-item">
|
||||||
<el-button type="primary" @click="onSearch">搜索</el-button>
|
<span class="search-label">所属区域-网格:</span>
|
||||||
<el-button @click="onReset">重置</el-button>
|
<AreaCascader v-model:region-code="localSearch.regionCode" v-model:grid-id="localSearch.id" :width="500" :label="null" />
|
||||||
</el-form-item>
|
</div>
|
||||||
</el-form>
|
|
||||||
|
<div class="search-item">
|
||||||
|
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||||
|
<el-button @click="handleReset">重置</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch, toRefs } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
search: {
|
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(
|
const handleSearch = () => {
|
||||||
search,
|
emit('on-search', { ...localSearch.value });
|
||||||
(val) => {
|
|
||||||
emit('update:search', { ...val });
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSearch = () => {
|
|
||||||
emit('search');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const handleReset = () => {
|
||||||
Object.keys(search).forEach((key) => {
|
localSearch.value = {};
|
||||||
search[key] = '';
|
handleSearch();
|
||||||
});
|
|
||||||
emit('update:search', { ...search });
|
|
||||||
emit('reset');
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.land-search {
|
.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>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user