dman-web-admin/src/views/community/CommunityList.vue
2025-02-23 17:32:41 +08:00

909 lines
21 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 v-if="column.key === 'action'">
<a-space>
<a @click="handleEdit(record)">编辑</a>
</a-space>
</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="modalVisible"
:title="isEdit ? '编辑小区' : '添加小区'"
@ok="handleSubmit"
@cancel="handleCancel"
:confirmLoading="confirmLoading"
width="680px"
>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" :loading="confirmLoading" @click="handleSubmit">
保存
</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">
<div class="qrcode-upload-wrapper">
<a-upload
v-model:file-list="fileList"
name="file"
:customRequest="handleQRCodeUpload"
@remove="handleQRCodeRemove"
list-type="picture-card"
accept=".jpg,.jpeg,.png"
:max-count="1"
@preview="previewQRCode"
>
<div v-if="!fileList.length">
<plus-outlined />
<div style="margin-top: 8px">上传</div>
</div>
</a-upload>
<!-- 添加预览模态框 -->
<a-modal
v-model:visible="previewVisible"
:title="previewTitle"
:footer="null"
@cancel="handlePreviewCancel"
>
<img style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</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, updateCommunity, uploadImage } 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'
import request from '../../utils/request'
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',
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
fixed: 'right'
},
]
// 获取小区列表数据
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 modalVisible = ref(false)
const confirmLoading = ref(false)
const isEdit = ref(false)
const formRef = ref(null)
const currentId = ref(null)
const formState = ref({
name: '',
location: {
address: '',
longitude: null,
latitude: null
},
qy_group_qrcode: '',
status: 'UNOPEN'
})
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 previewVisible = ref(false)
const previewImage = ref('')
const previewTitle = ref('')
// 显示编辑模态框
const handleEdit = (record) => {
isEdit.value = true
currentId.value = record.id
modalVisible.value = true
// 填充表单数据
formState.value = {
name: record.name,
location: {
address: record.address,
longitude: record.longitude,
latitude: record.latitude
},
qy_group_qrcode: record.qy_group_qrcode,
status: record.status
}
// 如果有二维码,设置文件列表
if (record.qy_group_qrcode) {
fileList.value = [{
uid: '-1',
name: '群二维码',
status: 'done',
url: record.qy_group_qrcode
}]
} else {
fileList.value = []
}
}
// 显示添加模态框
const showAddModal = () => {
isEdit.value = false
currentId.value = null
modalVisible.value = true
formState.value = {
name: '',
location: {
address: '',
longitude: null,
latitude: null
},
qy_group_qrcode: '',
status: 'UNOPEN'
}
fileList.value = []
}
// 修改预览图片函数
const previewQRCode = async (file) => {
// 如果是已上传的图片,直接使用 url
if (file.url) {
previewImage.value = file.url
previewVisible.value = true
previewTitle.value = file.name || '群二维码预览'
return
}
// 如果是新上传的图片,需要处理 File 对象
if (file.originFileObj) {
try {
const preview = await getBase64(file.originFileObj)
previewImage.value = preview
previewVisible.value = true
previewTitle.value = file.name || '群二维码预览'
} catch (error) {
console.error('预览图片失败:', error)
message.error('预览失败')
}
}
}
// 关闭预览
const handlePreviewCancel = () => {
previewVisible.value = false
}
// 文件转base64
const getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = error => reject(error)
})
}
// 修改上传处理函数
const handleQRCodeUpload = async ({ file, onSuccess, onError }) => {
const formData = new FormData()
formData.append('file', file)
try {
const res = await request.post('/api/upload/image', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.code === 200) {
formState.value.qy_group_qrcode = res.data.url
// 更新文件列表以支持预览
fileList.value = [{
uid: file.uid,
name: file.name,
status: 'done',
url: res.data.url
}]
message.success('上传成功')
onSuccess(res)
} else {
message.error(res.message || '上传失败')
onError(new Error(res.message || '上传失败'))
}
} catch (error) {
console.error('上传图片失败:', error)
message.error('上传失败')
onError(error)
}
}
// 删除二维码
const handleQRCodeRemove = (file) => {
formState.value.qy_group_qrcode = ''
fileList.value = []
}
// 统一提交处理
const handleSubmit = () => {
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,
status: formState.value.status
}
let res
if (isEdit.value) {
res = await updateCommunity(currentId.value, params)
} else {
res = await createCommunity(params)
}
if (res.code === 200) {
message.success(isEdit.value ? '更新成功' : '添加成功')
modalVisible.value = false
fetchData() // 刷新列表
} else {
message.error(res.message || (isEdit.value ? '更新失败' : '添加失败'))
}
} catch (error) {
console.error(isEdit.value ? '更新小区失败:' : '添加小区失败:', error)
message.error(isEdit.value ? '更新失败' : '添加失败')
} finally {
confirmLoading.value = false
}
})
}
// 取消处理
const handleCancel = () => {
formRef.value?.resetFields()
fileList.value = []
modalVisible.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('状态更新失败')
}
}
onMounted(() => {
fetchData()
})
return {
loading,
columns,
tableData,
pagination,
mapVisible,
handleTableChange,
showMap,
closeMap,
getStatusText,
getStatusColor,
formatDateTime,
modalVisible,
confirmLoading,
isEdit,
formState,
formRef,
rules,
showAddModal,
handleSubmit,
handleCancel,
handleStatusChange,
fileList,
handleQRCodeUpload,
handleQRCodeRemove,
handleEdit,
previewVisible,
previewImage,
previewTitle,
handlePreviewCancel,
previewQRCode
}
}
})
</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;
}
.qrcode-upload-wrapper {
:deep(.ant-upload-list-picture-card-container) {
width: 104px;
height: 104px;
}
:deep(.ant-upload.ant-upload-select-picture-card) {
width: 104px;
height: 104px;
}
:deep(.ant-upload-list-picture-card .ant-upload-list-item) {
padding: 4px;
}
:deep(.ant-upload-list-picture-card .ant-upload-list-item-thumbnail) {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>