Files
biz-pc-cloud-zax/src/components/map/map-select.vue
T
2025-10-16 16:56:35 +08:00

258 lines
6.2 KiB
Vue

<template>
<div class="map-select-container">
<el-input v-model="state.selectAddress" :placeholder="props.placeholder" :disabled="props.disabled"
:clearable="props.clearable" class="map-select-input" readonly @clear="handleClear">
<template #prepend v-if="state.selectedLocation">
<el-icon class="location-icon">
<Location />
</el-icon>
</template>
<template #append>
<el-button @click="handleMapClick" :disabled="props.disabled" class="map-button">
<el-icon>
<MapLocation />
</el-icon>
选择位置
</el-button>
</template>
</el-input>
<!-- 坐标信息显示 -->
<div v-if="state.selectedLocation && state.showCoordinates" class="coordinates-info">
<span class="coordinates-text">
经纬度: {{ state.selectedLocation.lng.toFixed(6) }}, {{ state.selectedLocation.lat.toFixed(6) }}
</span>
<el-button text size="small" @click="copyCoordinates" class="copy-btn">
<el-icon>
<CopyDocument />
</el-icon>
复制
</el-button>
</div>
<map-dialog ref="mapDialogRef" @confirm="handleLocationConfirm" @close="handleDialogClose" />
</div>
</template>
<script setup lang="ts" name="map-location-select">
import { reactive, ref, onMounted, watch, defineAsyncComponent } from 'vue'
import { ElMessage } from 'element-plus'
import { MapLocation, Location, CopyDocument } from '@element-plus/icons-vue'
const MapDialog = defineAsyncComponent(() => import('./map-dialog.vue'))
// 定义事件
const emit = defineEmits(['change', 'update:modelValue'])
// 定义属性
const props = withDefaults(
defineProps<{
modelValue?: any
placeholder?: string
disabled?: boolean
clearable?: boolean
showCoordinates?: boolean
returnFormat?: 'object' | 'coordinates' | 'address'
}>(),
{
placeholder: "请点击选择地图位置",
disabled: false,
clearable: true,
showCoordinates: true,
returnFormat: 'object'
}
)
// 响应式状态
const state = reactive<any>({
selectAddress: '',
selectedLocation: null,
selectedData: null
})
// 引用
const mapDialogRef = ref()
// 清空选择
const handleClear = () => {
state.selectAddress = ''
state.selectedLocation = null
state.selectedData = null
emit('update:modelValue', null)
emit('change', null)
}
// 打开地图选择器
const handleMapClick = () => {
if (props.disabled) return
// 如果已有位置,传递给对话框作为初始位置
mapDialogRef.value?.open(state.selectedLocation)
}
// 处理位置确认
const handleLocationConfirm = (result: any) => {
state.selectAddress = result.address
state.selectedLocation = result.location
state.selectedData = result
// 根据返回格式发送不同的值
let emitValue
switch (props.returnFormat) {
case 'coordinates':
emitValue = result.coordinates
break
case 'address':
emitValue = result.address
break
case 'object':
default:
emitValue = result
break
}
emit('update:modelValue', emitValue)
emit('change', emitValue)
ElMessage.success('位置选择成功')
}
// 处理对话框关闭
const handleDialogClose = () => {
// 对话框关闭时的处理
}
// 监听外部值变化
watch(() => props.modelValue, (newValue) => {
if (newValue) {
if (typeof newValue === 'object' && newValue.address) {
state.selectAddress = newValue.address
state.selectedLocation = newValue.location
state.selectedData = newValue
} else if (typeof newValue === 'string') {
state.selectAddress = newValue
}
} else {
handleClear()
}
}, { immediate: true })
onMounted(() => {
// 初始化
})
// 复制坐标
const copyCoordinates = async () => {
if (!state.selectedLocation) return
const coordinates = `${state.selectedLocation.lng},${state.selectedLocation.lat}`
try {
await navigator.clipboard.writeText(coordinates)
ElMessage.success('坐标已复制到剪贴板')
} catch (error) {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = coordinates
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
ElMessage.success('坐标已复制到剪贴板')
}
}
// 暴露方法
defineExpose({
open: handleMapClick,
clear: handleClear,
getLocation: () => state.selectedLocation,
getAddress: () => state.selectAddress,
getData: () => state.selectedData
})
</script>
<style scoped>
.map-select-container {
width: 100%;
}
.map-select-input {
width: 100%;
}
.map-select-input :deep(.el-input__inner) {
cursor: pointer;
}
.location-icon {
color: #409eff;
}
.map-button {
border: none;
background: #409eff;
color: white;
padding: 0 16px;
display: flex;
align-items: center;
gap: 4px;
transition: background-color 0.2s;
}
.map-button:hover {
background: #337ecc;
}
.map-button:disabled {
background: #c0c4cc;
cursor: not-allowed;
}
.coordinates-info {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
padding: 8px 12px;
background: #f8f9fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
font-size: 12px;
}
.coordinates-text {
color: #606266;
flex: 1;
}
.copy-btn {
padding: 4px 8px;
height: auto;
font-size: 12px;
}
.copy-btn:hover {
color: #409eff;
}
/* 响应式设计 */
@media (max-width: 768px) {
.map-button {
padding: 0 12px;
font-size: 12px;
}
.coordinates-info {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.copy-btn {
align-self: flex-end;
}
}
</style>