添加小区 功能

This commit is contained in:
aaron 2025-01-08 09:03:37 +08:00
parent fd51a431bb
commit 8930e13f3f
5 changed files with 452 additions and 11 deletions

View File

@ -6,6 +6,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>DM Admin</title>
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<script type="text/javascript">
window._AMapSecurityConfig = {
securityJsCode: '93527b49270ba2142f47f0407da7c0d6'
}
</script>
</head>
<body>
<div id="app"></div>

View File

@ -16,4 +16,13 @@ export function getBuildingList(params) {
method: 'get',
params
})
}
// 添加新小区
export function createCommunity(data) {
return request({
url: '/api/community/',
method: 'post',
data
})
}

View File

@ -16,7 +16,9 @@ import {
Divider,
Tag,
Modal,
Select
Select,
InputNumber,
AutoComplete
} from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
@ -39,5 +41,7 @@ app.use(Divider)
app.use(Tag)
app.use(Modal)
app.use(Select)
app.use(InputNumber)
app.use(AutoComplete)
app.mount('#app')

View File

@ -1,20 +1,37 @@
export function initAMap() {
return new Promise((resolve, reject) => {
if (window.AMap) {
resolve(window.AMap);
// 如果已经加载过,直接加载插件
window.AMap.plugin(['AMap.PlaceSearch', 'AMap.AutoComplete'], () => {
resolve(window.AMap);
});
return;
}
// 加载高德地图脚本
const script = document.createElement('script');
script.type = 'text/javascript';
script.async = true;
script.src = `https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f`;
script.onerror = reject;
script.onload = () => {
script.src = `https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f&plugin=AMap.PlaceSearch,AMap.AutoComplete&callback=initAMapCallback`;
// 创建回调函数
window.initAMapCallback = () => {
resolve(window.AMap);
};
script.onerror = () => {
reject(new Error('加载地图失败'));
};
document.head.appendChild(script);
});
}
// 添加地图工具函数
export function createMap(container, options = {}) {
return new window.AMap.Map(container, {
zoom: 13,
viewMode: '2D',
...options
});
}

View File

@ -2,6 +2,7 @@
<div class="community-list">
<div class="table-header">
<h1>小区列表</h1>
<a-button type="primary" @click="showAddModal">添加小区</a-button>
</div>
<a-table
@ -36,14 +37,93 @@
>
<div id="map-container" style="height: 500px;"></div>
</a-modal>
<a-modal
v-model:visible="addModalVisible"
title="添加小区"
@ok="handleAdd"
@cancel="handleCancel"
:confirmLoading="confirmLoading"
width="680px"
>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" :loading="confirmLoading" @click="handleAdd">
保存
</a-button>
</a-space>
</template>
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="地址搜索">
<a-auto-complete
v-model:value="searchAddress"
:options="searchOptions"
placeholder="输入地址搜索"
@change="handleSearch"
@select="handleSelect"
:loading="searchLoading"
allow-clear
>
<template #option="{ value: val, label }">
<div>{{ label }}</div>
</template>
</a-auto-complete>
</a-form-item>
<a-form-item label=" " :colon="false">
<div class="map-container">
<div id="add-map-container" style="height: 240px;"></div>
</div>
</a-form-item>
<a-form-item label="小区名称" name="name" required>
<a-input v-model:value="formState.name" placeholder="请输入小区名称" />
</a-form-item>
<a-form-item label="详细地址" name="address" required>
<a-input v-model:value="formState.address" placeholder="请输入详细地址" />
</a-form-item>
<a-form-item label="经纬度" required>
<a-input-group compact>
<a-input-number
v-model:value="formState.longitude"
:min="-180"
:max="180"
:controls="false"
disabled
style="width: 50%"
placeholder="经度"
/>
<a-input-number
v-model:value="formState.latitude"
:min="-90"
:max="90"
:controls="false"
disabled
style="width: 50%"
placeholder="纬度"
/>
</a-input-group>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, nextTick } from 'vue'
import { message, Tag } from 'ant-design-vue'
import { getCommunityList } from '@/api/community'
import { initAMap } from '@/utils/amap'
import { getCommunityList, createCommunity } from '@/api/community'
import { initAMap, createMap } from '@/utils/amap'
import dayjs from 'dayjs'
export default defineComponent({
@ -200,6 +280,203 @@ export default defineComponent({
mapVisible.value = false
}
//
const addModalVisible = ref(false)
const confirmLoading = ref(false)
const searchLoading = ref(false)
const searchAddress = ref('')
const searchOptions = ref([])
const addMap = ref(null)
const addMarker = ref(null)
const formRef = ref(null)
const formState = ref({
name: '',
address: '',
longitude: null,
latitude: null
})
const rules = {
name: [{ required: true, message: '请输入小区名称' }],
address: [{ required: true, message: '请输入详细地址' }],
longitude: [{ required: true, message: '请选择位置获取经度' }],
latitude: [{ required: true, message: '请选择位置获取纬度' }],
}
//
const showAddModal = async () => {
addModalVisible.value = true
await nextTick()
initAddMap()
}
//
const updateMarkerPosition = (lng, lat, name = '') => {
if (!addMap.value) return
formState.value.longitude = lng
formState.value.latitude = lat
//
if (name && !formState.value.name) {
formState.value.name = name
}
if (addMarker.value) {
addMarker.value.setPosition([lng, lat])
} else {
const AMap = window.AMap
addMarker.value = new AMap.Marker({
position: [lng, lat],
map: addMap.value
})
}
}
//
const initAddMap = async () => {
try {
await initAMap();
if (!addMap.value) {
addMap.value = createMap('add-map-container', {
center: [116.397428, 39.90923]
});
//
addMap.value.on('click', (e) => {
const { lng, lat } = e.lnglat;
// 使
const geocoder = new window.AMap.Geocoder();
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const address = result.regeocode;
formState.value.address = address.formattedAddress;
// 使POI
if (!formState.value.name && address.pois && address.pois.length > 0) {
formState.value.name = address.pois[0].name;
}
updateMarkerPosition(lng, lat);
}
});
});
}
} catch (error) {
console.error('初始化地图失败:', error);
message.error('初始化地图失败,请刷新重试');
}
};
//
const handleSearch = async (value) => {
if (!value || value.length < 2) {
searchOptions.value = [];
return;
}
try {
searchLoading.value = true;
await initAMap(); //
const autoComplete = new window.AMap.AutoComplete({
city: '全国'
});
const result = await new Promise((resolve, reject) => {
autoComplete.search(value, (status, result) => {
if (status === 'complete') {
resolve(result);
} else {
reject(new Error('搜索失败'));
}
});
});
searchOptions.value = result.tips.map(tip => ({
value: tip.name,
label: `${tip.name} (${tip.district})`,
location: tip.location,
address: tip.address || tip.district
}));
} catch (error) {
console.error('搜索地址失败:', error);
searchOptions.value = [];
} finally {
searchLoading.value = false;
}
};
//
const handleSelect = (value, option) => {
const selectedOption = searchOptions.value.find(opt => opt.value === value);
if (selectedOption) {
//
formState.value.address = selectedOption.address;
formState.value.name = selectedOption.value; // 使
if (selectedOption.location) {
updateMarkerPosition(
selectedOption.location.lng,
selectedOption.location.lat,
selectedOption.value
);
addMap.value.setCenter([selectedOption.location.lng, selectedOption.location.lat]);
} else {
// 使 PlaceSearch
const placeSearch = new window.AMap.PlaceSearch({
city: '全国'
});
placeSearch.search(value, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const poi = result.poiList.pois[0];
formState.value.address = poi.address;
updateMarkerPosition(
poi.location.lng,
poi.location.lat,
poi.name
);
addMap.value.setCenter([poi.location.lng, poi.location.lat]);
}
});
}
}
};
//
const handleAdd = () => {
formRef.value.validate().then(async () => {
try {
confirmLoading.value = true
const res = await createCommunity(formState.value)
if (res.code === 200) {
message.success('添加成功')
addModalVisible.value = false
fetchData() //
} else {
message.error(res.message || '添加失败')
}
} catch (error) {
console.error('添加小区失败:', error)
message.error('添加失败')
} finally {
confirmLoading.value = false
}
})
}
//
const handleCancel = () => {
formRef.value?.resetFields()
addModalVisible.value = false
searchAddress.value = ''
if (addMarker.value) {
addMarker.value.setMap(null)
addMarker.value = null
}
}
onMounted(() => {
fetchData()
})
@ -215,7 +492,20 @@ export default defineComponent({
closeMap,
getStatusText,
getStatusColor,
formatDateTime
formatDateTime,
addModalVisible,
confirmLoading,
searchLoading,
searchAddress,
formState,
formRef,
rules,
showAddModal,
handleAdd,
handleCancel,
handleSearch,
searchOptions,
handleSelect
}
}
})
@ -236,4 +526,120 @@ export default defineComponent({
:deep(.ant-table-content) {
overflow-x: auto;
}
:deep(.ant-modal-body) {
padding: 24px;
max-width: 100%;
}
:deep(.ant-form) {
max-width: 600px;
margin: 0 auto;
}
:deep(.ant-form-item) {
margin-bottom: 16px;
}
:deep(.ant-input),
:deep(.ant-input-number),
:deep(.ant-auto-complete) {
width: 100% !important;
}
:deep(.ant-input-number-input) {
height: 32px;
}
:deep(.ant-form-item-control-input) {
width: 100%;
}
:deep(.ant-col) {
padding-right: 12px;
padding-left: 12px;
}
:deep(.ant-select-item-option-content) {
white-space: normal;
word-break: break-all;
}
:deep(.ant-row) {
margin-bottom: 0 !important;
}
:deep(.ant-form-item) {
margin-bottom: 16px;
}
:deep(.ant-col) {
padding-right: 8px;
padding-left: 8px;
}
/* 地图区域样式 */
.map-section {
margin-bottom: 24px;
padding: 16px;
background: #fafafa;
border-radius: 4px;
}
.search-item {
margin-bottom: 16px;
}
.map-container {
border: 1px solid #f0f0f0;
border-radius: 2px;
overflow: hidden;
}
:deep(.ant-form-item-label) {
text-align: right;
}
:deep(.ant-form-item) {
margin-bottom: 24px;
}
:deep(.ant-input),
:deep(.ant-input-number),
:deep(.ant-auto-complete) {
width: 100%;
}
:deep(.ant-input-group) {
display: flex;
}
:deep(.ant-input-group .ant-input-number) {
border-radius: 0;
&:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
&:last-child {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}
:deep(.ant-form) {
max-width: 600px;
margin: 0 auto;
}
:deep(.ant-modal-body) {
padding: 24px;
}
:deep(.ant-modal-footer) {
text-align: right;
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
}
</style>