dman-web-admin/src/views/community/CommunityList.vue
2025-02-19 23:14:59 +08:00

737 lines
16 KiB
Vue

<template>
<page-container>
<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-dropdown>
<a-tag :color="getStatusColor(record.status)" class="status-tag">
{{ getStatusText(record.status) }}
<down-outlined />
</a-tag>
<template #overlay>
<a-menu @click="({ key }) => handleStatusChange(record, key)">
<a-menu-item key="OPENING">运营中</a-menu-item>
<a-menu-item key="UNOPEN">未启用</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</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 v-if="column.key === 'qrcode'">
<a-image
v-if="record.qy_group_qrcode"
:src="record.qy_group_qrcode"
:width="50"
alt="群二维码"
/>
<span v-else>-</span>
</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 }"
class="community-form"
>
<a-form-item label="小区名称" name="name" required>
<a-input v-model:value="formState.name" placeholder="请输入小区名称" />
</a-form-item>
<map-picker
v-model="formState.location"
label="地址搜索"
/>
<a-form-item label="群二维码" name="qy_group_qrcode">
<a-upload
v-model:file-list="fileList"
name="file"
:action="uploadUrl"
list-type="picture-card"
:max-count="1"
@change="handleQrcodeChange"
>
<div v-if="!fileList.length">
<plus-outlined />
<div style="margin-top: 8px">上传</div>
</div>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
</div>
</page-container>
</template>
<script>
import { defineComponent, ref, onMounted, nextTick } from 'vue'
import { message, Tag, Menu, Dropdown, Image, Upload } from 'ant-design-vue'
import { getCommunityList, createCommunity, updateCommunityStatus } from '@/api/community'
import { loadAMap, createMap } from '@/utils/amap.js'
import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue'
import { DownOutlined, PlusOutlined } from '@ant-design/icons-vue'
import MapPicker from '@/components/MapPicker/index.vue'
export default defineComponent({
components: {
PageContainer,
ATag: Tag,
ADropdown: Dropdown,
AMenu: Menu,
AMenuItem: Menu.Item,
DownOutlined,
MapPicker,
AImage: Image,
AUpload: Upload,
PlusOutlined
},
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',
},
{
title: '群二维码',
key: 'qrcode',
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
await nextTick()
try {
await loadAMap()
if (!currentMap.value) {
currentMap.value = createMap('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 window.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: '',
location: {
address: '',
longitude: null,
latitude: null
},
qy_group_qrcode: ''
})
const rules = {
name: [{ required: true, message: '请输入小区名称' }],
'location.address': [{ required: true, message: '请选择地址' }],
'location.longitude': [{ required: true, message: '请在地图上选择位置' }],
'location.latitude': [{ required: true, message: '请在地图上选择位置' }]
}
// 添加上传相关的响应式变量
const fileList = ref([])
const uploadUrl = '/api/upload/image'
// 显示添加模态框
const showAddModal = () => {
addModalVisible.value = true
}
// 提交表单
const handleAdd = () => {
formRef.value.validate().then(async () => {
try {
confirmLoading.value = true
const params = {
name: formState.value.name,
address: formState.value.location.address,
longitude: formState.value.location.longitude,
latitude: formState.value.location.latitude,
qy_group_qrcode: formState.value.qy_group_qrcode
}
const res = await createCommunity(params)
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()
fileList.value = []
addModalVisible.value = false
}
// 处理状态变更
const handleStatusChange = async (record, status) => {
try {
const res = await updateCommunityStatus(record.id, status)
if (res.code === 200) {
message.success('状态更新成功')
// 更新本地数据
const index = tableData.value.findIndex(item => item.id === record.id)
if (index !== -1) {
tableData.value[index].status = status
}
} else {
message.error(res.message || '状态更新失败')
}
} catch (error) {
console.error('更新小区状态失败:', error)
message.error('状态更新失败')
}
}
// 处理二维码上传
const handleQrcodeChange = (info) => {
if (info.file.status === 'done') {
if (info.file.response.code === 200) {
formState.value.qy_group_qrcode = info.file.response.data.url
message.success('上传成功')
} else {
message.error(info.file.response.message || '上传失败')
}
} else if (info.file.status === 'error') {
message.error('上传失败')
}
}
onMounted(() => {
fetchData()
})
return {
loading,
columns,
tableData,
pagination,
mapVisible,
handleTableChange,
showMap,
closeMap,
getStatusText,
getStatusColor,
formatDateTime,
addModalVisible,
confirmLoading,
formState,
formRef,
rules,
showAddModal,
handleAdd,
handleCancel,
handleStatusChange,
fileList,
uploadUrl,
handleQrcodeChange
}
}
})
</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;
}
/* 调整表单样式 */
.community-form {
padding: 8px 0;
}
:deep(.ant-form-item) {
margin-bottom: 24px !important;
}
:deep(.ant-form-item-label) {
height: 32px;
line-height: 32px;
text-align: right;
padding-right: 12px;
/* 调整必填星号的样式 */
> label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
color: #ff4d4f;
font-size: 14px;
margin-right: 4px;
}
}
:deep(.ant-form-item-control-input) {
min-height: 32px;
}
:deep(.ant-input),
:deep(.ant-select),
:deep(.ant-auto-complete),
:deep(.ant-input-number) {
height: 32px;
}
:deep(.ant-select-selector),
:deep(.ant-auto-complete .ant-input) {
height: 32px !important;
line-height: 32px !important;
padding: 0 11px !important;
.ant-select-selection-search-input {
height: 30px !important;
}
.ant-select-selection-item {
line-height: 30px !important;
}
}
:deep(.ant-input-number-input) {
height: 30px;
line-height: 30px;
}
:deep(.ant-input::placeholder),
:deep(.ant-select-selection-placeholder),
:deep(.ant-input-number-input::placeholder) {
color: #bfbfbf;
}
:deep(.ant-modal-body) {
padding: 0;
}
:deep(.ant-modal-footer) {
border-top: 1px solid #f0f0f0;
padding: 16px 24px;
}
.map-container {
border: 1px solid #f0f0f0;
border-radius: 2px;
overflow: hidden;
margin-bottom: 24px;
}
/* 移除重复的样式定义 */
:deep(.ant-form) {
max-width: 600px;
margin: 0 auto;
}
/* 清理和合并重复的样式 */
:deep(.ant-input-group) {
display: flex;
.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-modal-body),
:deep(.ant-form),
:deep(.ant-form-item) {
padding-right: 0;
padding-left: 0;
}
:deep(.ant-col) {
padding-right: 0;
padding-left: 0;
}
.status-tag {
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
gap: 4px;
}
:deep(.anticon) {
font-size: 12px;
}
/* 调整 Select 组件的样式 */
:deep(.ant-select-selector) {
height: 32px !important;
.ant-select-selection-item {
line-height: 30px !important; /* 调整文字行高 */
padding-top: 0 !important; /* 移除顶部内边距 */
padding-bottom: 0 !important; /* 移除底部内边距 */
}
}
/* 调整选项的样式 */
:deep(.ant-select-dropdown) {
.ant-select-item {
padding: 5px 12px; /* 调整选项内边距 */
line-height: 22px; /* 调整选项行高 */
}
}
/* 确保输入框内的文字垂直居中 */
:deep(.ant-select-selection-search-input) {
height: 30px !important;
line-height: 30px !important;
}
/* 确保占位符文字垂直居中 */
:deep(.ant-select-selection-placeholder) {
line-height: 30px !important;
}
/* 调整地图容器样式 */
:deep(.map-container) {
border: 1px solid #d9d9d9;
border-radius: 2px;
overflow: hidden;
}
.community-list {
/* 移除 padding */
}
:deep(.ant-upload-list-picture-card-container) {
width: 104px;
height: 104px;
}
:deep(.ant-upload.ant-upload-select-picture-card) {
width: 104px;
height: 104px;
}
</style>