dman-web-admin/src/views/community/CommunityList.vue
2025-01-08 09:03:37 +08:00

645 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="community-list">
<div class="table-header">
<h1>小区列表</h1>
<a-button type="primary" @click="showAddModal">添加小区</a-button>
</div>
<a-table
:columns="columns"
:data-source="tableData"
:pagination="pagination"
:loading="loading"
@change="handleTableChange"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-if="column.key === 'location'">
<a @click="showMap(record)">查看位置</a>
</template>
<template v-if="column.key === 'create_time'">
{{ formatDateTime(record.create_time) }}
</template>
</template>
</a-table>
<a-modal
v-model:visible="mapVisible"
title="小区位置"
:footer="null"
width="800px"
@cancel="closeMap"
>
<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, createCommunity } from '@/api/community'
import { initAMap, createMap } from '@/utils/amap'
import dayjs from 'dayjs'
export default defineComponent({
components: {
[Tag.name]: Tag
},
setup() {
const loading = ref(false)
const tableData = ref([])
const mapVisible = ref(false)
const currentMap = ref(null)
const currentMarker = ref(null)
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showTotal: (total) => `${total} 条记录`
})
// 获取状态显示文本
const getStatusText = (status) => {
const statusMap = {
'OPENING': '运营中',
'UNOPEN': '未启用'
}
return statusMap[status] || status
}
// 获取状态标签颜色
const getStatusColor = (status) => {
const colorMap = {
'OPENING': 'green',
'UNOPEN': 'orange'
}
return colorMap[status] || 'default'
}
// 格式化日期时间
const formatDateTime = (value) => {
if (!value) return ''
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
}
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
align: 'center',
},
{
title: '小区名称',
dataIndex: 'name',
key: 'name',
width: 150,
},
{
title: '地址',
dataIndex: 'address',
key: 'address',
width: 200,
},
{
title: '位置',
key: 'location',
width: 100,
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
align: 'center',
}
]
// 获取小区列表数据
const fetchData = async () => {
try {
loading.value = true
const params = {
skip: (pagination.value.current - 1) * pagination.value.pageSize,
limit: pagination.value.pageSize
}
const res = await getCommunityList(params)
if (res.code === 200) {
tableData.value = res.data.items
pagination.value.total = res.data.total
} else {
message.error(res.message || '获取数据失败')
}
} catch (error) {
console.error('获取小区列表失败:', error)
message.error('获取数据失败')
} finally {
loading.value = false
}
}
// 表格变化处理
const handleTableChange = (pag) => {
pagination.value.current = pag.current
pagination.value.pageSize = pag.pageSize
fetchData()
}
// 显示地图
const showMap = async (record) => {
mapVisible.value = true
// 等待 DOM 更新
await nextTick()
try {
const AMap = await initAMap()
// 创建地图实例
if (!currentMap.value) {
currentMap.value = new AMap.Map('map-container', {
zoom: 15,
viewMode: '3D'
})
}
// 设置中心点
const position = [record.longitude, record.latitude]
currentMap.value.setCenter(position)
// 清除旧标记
if (currentMarker.value) {
currentMap.value.remove(currentMarker.value)
}
// 添加新标记
currentMarker.value = new AMap.Marker({
position,
title: record.name
})
currentMap.value.add(currentMarker.value)
} catch (error) {
console.error('加载地图失败:', error)
message.error('加载地图失败')
}
}
// 关闭地图
const closeMap = () => {
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()
})
return {
loading,
columns,
tableData,
pagination,
mapVisible,
handleTableChange,
showMap,
closeMap,
getStatusText,
getStatusColor,
formatDateTime,
addModalVisible,
confirmLoading,
searchLoading,
searchAddress,
formState,
formRef,
rules,
showAddModal,
handleAdd,
handleCancel,
handleSearch,
searchOptions,
handleSelect
}
}
})
</script>
<style scoped>
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.table-header h1 {
margin: 0;
}
: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>