This commit is contained in:
aaron 2025-03-16 14:12:13 +08:00
parent 619d17f9a1
commit 018bc7c640
4 changed files with 1056 additions and 134 deletions

View File

@ -8,10 +8,10 @@
<title>蜂快 · 运营管理系统</title> <title>蜂快 · 运营管理系统</title>
<script> <script>
window._AMapSecurityConfig = { window._AMapSecurityConfig = {
securityJsCode: '6c7f5a402a13bf2dee1a4bbe0d8023c7' securityJsCode: '93527b49270ba2142f47f0407da7c0d6'
} }
</script> </script>
<script src="https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f&plugin=AMap.Geocoder,AMap.AutoComplete,AMap.PlaceSearch"></script> <script src="https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f&plugin=AMap.Geocoder,AMap.AutoComplete,AMap.PlaceSearch,AMap.DistrictSearch"></script>
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@ -1,16 +1,42 @@
<template> <template>
<div class="map-picker-container"> <div class="map-picker-container">
<a-form-item :label="label"> <a-form-item :label="label">
<a-auto-complete <div class="region-search-container">
v-model:value="searchAddress" <!-- 省市选择器 -->
:options="searchOptions" <div class="region-selector">
placeholder="输入地址搜索" <a-select
@change="handleSearch" v-model:value="selectedProvince"
@select="handleSelect" placeholder="选择省份"
:loading="searchLoading" style="width: 48%"
allow-clear @change="handleProvinceChange"
class="map-picker-input" :options="provinceOptions"
/> show-search
:filter-option="filterOption"
/>
<a-select
v-model:value="selectedCity"
placeholder="选择城市"
style="width: 48%"
@change="handleCityChange"
:options="cityOptions"
:disabled="!selectedProvince"
show-search
:filter-option="filterOption"
/>
</div>
<!-- 详细地址搜索 -->
<a-auto-complete
v-model:value="searchAddress"
:options="searchOptions"
placeholder="输入详细地址搜索"
@change="handleSearch"
@select="handleSelect"
:loading="searchLoading"
allow-clear
class="map-picker-input"
/>
</div>
</a-form-item> </a-form-item>
<a-form-item label="地图选点" required> <a-form-item label="地图选点" required>
@ -38,6 +64,110 @@ import { ref, onMounted, nextTick, watch } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js' import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js'
//
const fallbackProvinces = [
{ value: '北京市', label: '北京市' },
{ value: '天津市', label: '天津市' },
{ value: '河北省', label: '河北省' },
{ value: '山西省', label: '山西省' },
{ value: '内蒙古自治区', label: '内蒙古' },
{ value: '辽宁省', label: '辽宁省' },
{ value: '吉林省', label: '吉林省' },
{ value: '黑龙江省', label: '黑龙江省' },
{ value: '上海市', label: '上海市' },
{ value: '江苏省', label: '江苏省' },
{ value: '浙江省', label: '浙江省' },
{ value: '安徽省', label: '安徽省' },
{ value: '福建省', label: '福建省' },
{ value: '江西省', label: '江西省' },
{ value: '山东省', label: '山东省' },
{ value: '河南省', label: '河南省' },
{ value: '湖北省', label: '湖北省' },
{ value: '湖南省', label: '湖南省' },
{ value: '广东省', label: '广东省' },
{ value: '广西壮族自治区', label: '广西' },
{ value: '海南省', label: '海南省' },
{ value: '重庆市', label: '重庆市' },
{ value: '四川省', label: '四川省' },
{ value: '贵州省', label: '贵州省' },
{ value: '云南省', label: '云南省' },
{ value: '西藏自治区', label: '西藏' },
{ value: '陕西省', label: '陕西省' },
{ value: '甘肃省', label: '甘肃省' },
{ value: '青海省', label: '青海省' },
{ value: '宁夏回族自治区', label: '宁夏' },
{ value: '新疆维吾尔自治区', label: '新疆' },
{ value: '台湾省', label: '台湾省' },
{ value: '香港特别行政区', label: '香港' },
{ value: '澳门特别行政区', label: '澳门' }
]
//
const fallbackCitiesMap = {
'北京市': [{ value: '北京市', label: '北京市' }],
'上海市': [{ value: '上海市', label: '上海市' }],
'天津市': [{ value: '天津市', label: '天津市' }],
'重庆市': [{ value: '重庆市', label: '重庆市' }],
'广东省': [
{ value: '广州市', label: '广州市' },
{ value: '深圳市', label: '深圳市' },
{ value: '珠海市', label: '珠海市' },
{ value: '汕头市', label: '汕头市' },
{ value: '佛山市', label: '佛山市' },
{ value: '韶关市', label: '韶关市' },
{ value: '湛江市', label: '湛江市' },
{ value: '肇庆市', label: '肇庆市' },
{ value: '江门市', label: '江门市' },
{ value: '茂名市', label: '茂名市' },
{ value: '惠州市', label: '惠州市' },
{ value: '梅州市', label: '梅州市' },
{ value: '汕尾市', label: '汕尾市' },
{ value: '河源市', label: '河源市' },
{ value: '阳江市', label: '阳江市' },
{ value: '清远市', label: '清远市' },
{ value: '东莞市', label: '东莞市' },
{ value: '中山市', label: '中山市' },
{ value: '潮州市', label: '潮州市' },
{ value: '揭阳市', label: '揭阳市' },
{ value: '云浮市', label: '云浮市' }
],
'浙江省': [
{ value: '杭州市', label: '杭州市' },
{ value: '宁波市', label: '宁波市' },
{ value: '温州市', label: '温州市' },
{ value: '嘉兴市', label: '嘉兴市' },
{ value: '湖州市', label: '湖州市' },
{ value: '绍兴市', label: '绍兴市' },
{ value: '金华市', label: '金华市' },
{ value: '衢州市', label: '衢州市' },
{ value: '舟山市', label: '舟山市' },
{ value: '台州市', label: '台州市' },
{ value: '丽水市', label: '丽水市' }
],
'江苏省': [
{ value: '南京市', label: '南京市' },
{ value: '无锡市', label: '无锡市' },
{ value: '徐州市', label: '徐州市' },
{ value: '常州市', label: '常州市' },
{ value: '苏州市', label: '苏州市' },
{ value: '南通市', label: '南通市' },
{ value: '连云港市', label: '连云港市' },
{ value: '淮安市', label: '淮安市' },
{ value: '盐城市', label: '盐城市' },
{ value: '扬州市', label: '扬州市' },
{ value: '镇江市', label: '镇江市' },
{ value: '泰州市', label: '泰州市' },
{ value: '宿迁市', label: '宿迁市' }
]
}
//
for (const province of fallbackProvinces) {
if (!fallbackCitiesMap[province.value]) {
fallbackCitiesMap[province.value] = [{ value: province.value, label: province.value }]
}
}
export default { export default {
name: 'MapPicker', name: 'MapPicker',
props: { props: {
@ -63,11 +193,73 @@ export default {
const searchOptions = ref([]) const searchOptions = ref([])
const searchLoading = ref(false) const searchLoading = ref(false)
//
const provinceOptions = ref(fallbackProvinces)
const cityOptions = ref([])
const selectedProvince = ref('')
const selectedCity = ref('')
// //
const address = ref(props.modelValue.address) const address = ref(props.modelValue.address)
const longitude = ref(props.modelValue.longitude) const longitude = ref(props.modelValue.longitude)
const latitude = ref(props.modelValue.latitude) const latitude = ref(props.modelValue.latitude)
//
const filterOption = (input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
//
const handleProvinceChange = (value) => {
selectedProvince.value = value
selectedCity.value = ''
//
cityOptions.value = fallbackCitiesMap[value] || []
//
if (map.value) {
try {
// 使
const geocoder = createGeocoder()
geocoder.getLocation(value, (status, result) => {
if (status === 'complete' && result.info === 'OK' && result.geocodes.length > 0) {
const location = result.geocodes[0].location
map.value.setCenter([location.lng, location.lat])
map.value.setZoom(8) // 8
}
})
} catch (error) {
console.error('设置地图中心点失败:', error)
}
}
}
//
const handleCityChange = (value) => {
selectedCity.value = value
//
if (map.value) {
try {
// 使
const geocoder = createGeocoder()
geocoder.getLocation(value, (status, result) => {
if (status === 'complete' && result.info === 'OK' && result.geocodes.length > 0) {
const location = result.geocodes[0].location
map.value.setCenter([location.lng, location.lat])
map.value.setZoom(12) // 12
//
searchAddress.value = ''
}
})
} catch (error) {
console.error('设置地图中心点失败:', error)
}
}
}
// props // props
watch(() => props.modelValue, (newVal, oldVal) => { watch(() => props.modelValue, (newVal, oldVal) => {
console.log('props变化:', newVal, oldVal) console.log('props变化:', newVal, oldVal)
@ -97,6 +289,42 @@ export default {
}) })
} }
map.value.setCenter([newVal.longitude, newVal.latitude]) map.value.setCenter([newVal.longitude, newVal.latitude])
//
if (newVal.address) {
// 使
const geocoder = createGeocoder()
geocoder.getAddress([newVal.longitude, newVal.latitude], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const addressComponent = result.regeocode.addressComponent
if (addressComponent) {
const province = addressComponent.province
const city = addressComponent.city
//
const foundProvince = provinceOptions.value.find(p =>
province.includes(p.value) || p.value.includes(province)
)
if (foundProvince) {
selectedProvince.value = foundProvince.value
//
cityOptions.value = fallbackCitiesMap[foundProvince.value] || []
if (city) {
const foundCity = cityOptions.value.find(c =>
city.includes(c.value) || c.value.includes(city)
)
if (foundCity) {
selectedCity.value = foundCity.value
}
}
}
}
}
})
}
} catch (error) { } catch (error) {
console.error('更新地图标记失败:', error) console.error('更新地图标记失败:', error)
} }
@ -174,9 +402,46 @@ export default {
geocoder.getAddress([lng, lat], (status, result) => { geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') { if (status === 'complete' && result.info === 'OK') {
address.value = result.regeocode.formattedAddress address.value = result.regeocode.formattedAddress
//
const addressComponent = result.regeocode.addressComponent
if (addressComponent) {
const province = addressComponent.province
const city = addressComponent.city
//
const foundProvince = provinceOptions.value.find(p =>
province.includes(p.value) || p.value.includes(province)
)
if (foundProvince) {
selectedProvince.value = foundProvince.value
//
cityOptions.value = fallbackCitiesMap[foundProvince.value] || []
if (city) {
const foundCity = cityOptions.value.find(c =>
city.includes(c.value) || c.value.includes(city)
)
if (foundCity) {
selectedCity.value = foundCity.value
}
}
}
}
updateValue() updateValue()
} else { } else {
message.error('获取地址信息失败') // 使
if (selectedCity.value) {
address.value = `${selectedProvince.value} ${selectedCity.value} (${lng.toFixed(6)},${lat.toFixed(6)})`
} else if (selectedProvince.value) {
address.value = `${selectedProvince.value} (${lng.toFixed(6)},${lat.toFixed(6)})`
} else {
address.value = `位置坐标: ${lng.toFixed(6)},${lat.toFixed(6)}`
}
updateValue()
} }
}) })
}) })
@ -212,48 +477,140 @@ export default {
searchLoading.value = true searchLoading.value = true
await loadAMap() await loadAMap()
const autoComplete = createAutoComplete() // (: 116.123456,39.123456)
await new Promise((resolve, reject) => { const lngLatRegex = /^(\d+\.\d+),(\d+\.\d+)$/
autoComplete.search(value, (status, result) => { const lngLatMatch = value.match(lngLatRegex)
if (status === 'complete') {
searchOptions.value = result.tips.map(tip => ({ if (lngLatMatch) {
value: tip.name, const lng = parseFloat(lngLatMatch[1])
label: `${tip.name} (${tip.district})`, const lat = parseFloat(lngLatMatch[2])
location: tip.location
})) if (!isNaN(lng) && !isNaN(lat) && lng >= -180 && lng <= 180 && lat >= -90 && lat <= 90) {
resolve(result) // 使
} else { searchOptions.value = [{
reject(new Error(status)) value: `位置坐标: ${lng},${lat}`,
} label: `位置坐标: ${lng},${lat}`,
location: { lng, lat }
}]
searchLoading.value = false searchLoading.value = false
}) return
}
}
const autoComplete = createAutoComplete({
city: selectedCity.value || selectedProvince.value || '全国',
citylimit: !!selectedCity.value
})
//
let keyword = value
if (selectedCity.value) {
keyword = `${selectedCity.value} ${value}`
} else if (selectedProvince.value) {
keyword = `${selectedProvince.value} ${value}`
}
autoComplete.search(keyword, (status, result) => {
if (status === 'complete' && result.tips && result.tips.length > 0) {
searchOptions.value = result.tips.map(tip => ({
value: tip.name,
label: `${tip.name} (${tip.district || ''})`,
location: tip.location
}))
} else {
//
searchOptions.value = [{
value: value,
label: `使用"${value}"作为地址(需手动在地图上选点)`,
manualInput: true
}]
message.info('未找到匹配的地址,您可以直接使用输入的文本或在地图上选点')
}
searchLoading.value = false
}) })
} catch (error) { } catch (error) {
console.error('搜索地址失败:', error) console.error('搜索地址失败:', error)
searchLoading.value = false searchLoading.value = false
message.error('搜索地址失败')
//
searchOptions.value = [{
value: value,
label: `使用"${value}"作为地址(需手动在地图上选点)`,
manualInput: true
}]
message.info('搜索地址失败,您可以直接使用输入的文本或在地图上选点')
} }
} }
// //
const handleSelect = (value, option) => { const handleSelect = (value, option) => {
// 使
address.value = value
//
const selected = searchOptions.value.find(opt => opt.value === value) const selected = searchOptions.value.find(opt => opt.value === value)
if (selected && selected.location) {
const { lng, lat } = selected.location //
updateMarkerPosition(lng, lat) if (!selected || selected.manualInput || !selected.location) {
message.info('请在地图上选择具体位置')
updateValue()
return
}
const { lng, lat } = selected.location
//
updateMarkerPosition(lng, lat)
//
if (map.value) {
map.value.setCenter([lng, lat]) map.value.setCenter([lng, lat])
map.value.setZoom(15) //
// 使 }
//
try {
const geocoder = createGeocoder() const geocoder = createGeocoder()
geocoder.getAddress([lng, lat], (status, result) => { geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') { if (status === 'complete' && result.info === 'OK') {
address.value = result.regeocode.formattedAddress address.value = result.regeocode.formattedAddress
updateValue()
} else { //
address.value = value const addressComponent = result.regeocode.addressComponent
updateValue() if (addressComponent) {
const province = addressComponent.province
const city = addressComponent.city
//
const foundProvince = provinceOptions.value.find(p =>
province.includes(p.value) || p.value.includes(province)
)
if (foundProvince) {
selectedProvince.value = foundProvince.value
//
cityOptions.value = fallbackCitiesMap[foundProvince.value] || []
if (city) {
const foundCity = cityOptions.value.find(c =>
city.includes(c.value) || c.value.includes(city)
)
if (foundCity) {
selectedCity.value = foundCity.value
}
}
}
}
} }
//
updateValue()
}) })
} catch (error) {
console.error('获取详细地址失败:', error)
// 使使
updateValue()
} }
} }
@ -272,7 +629,15 @@ export default {
longitude, longitude,
latitude, latitude,
handleSearch, handleSearch,
handleSelect handleSelect,
//
provinceOptions,
cityOptions,
selectedProvince,
selectedCity,
handleProvinceChange,
handleCityChange,
filterOption
} }
} }
} }
@ -283,6 +648,19 @@ export default {
width: 100%; width: 100%;
} }
.region-search-container {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 8px;
}
.region-selector {
display: flex;
justify-content: space-between;
gap: 8px;
}
.map-picker-input { .map-picker-input {
width: 100%; width: 100%;
} }
@ -301,4 +679,21 @@ export default {
font-size: 0.8em; font-size: 0.8em;
color: #999; color: #999;
} }
:deep(.ant-select-selector) {
border-radius: 4px !important;
}
:deep(.ant-select-dropdown) {
max-height: 300px;
overflow-y: auto;
}
:deep(.ant-select-item) {
padding: 6px 12px;
}
:deep(.ant-input-affix-wrapper) {
border-radius: 4px;
}
</style> </style>

View File

@ -1,62 +1,244 @@
// 高德地图配置 // 高德地图配置
const MAP_CONFIG = { const MAP_CONFIG = {
key: 'fd47f3d4f54b675693c7d59dcd2a6c5f', key: 'fd47f3d4f54b675693c7d59dcd2a6c5f', // Web端key
securityJsCode: '93527b49270ba2142f47f0407da7c0d6' securityJsCode: '93527b49270ba2142f47f0407da7c0d6', // 安全密钥
webServiceKey: '4f017f9f211e76a8fc8093e29531f09c', // Web服务key
version: '2.0',
plugins: [
'AMap.ToolBar',
'AMap.Scale',
'AMap.HawkEye',
'AMap.MapType',
'AMap.Geolocation',
'AMap.Geocoder',
'AMap.AutoComplete',
'AMap.PlaceSearch',
'AMap.DistrictSearch'
]
} }
// 设置安全密钥 // 加载状态
window._AMapSecurityConfig = { let loadingPromise = null
securityJsCode: MAP_CONFIG.securityJsCode let AMap = null
}
let isMapLoaded = false /**
* 加载高德地图API
// 加载高德地图及其插件 * @returns {Promise} 加载完成的Promise
*/
export const loadAMap = () => { export const loadAMap = () => {
return new Promise((resolve, reject) => { if (window.AMap) {
if (isMapLoaded && window.AMap) { AMap = window.AMap
resolve(window.AMap) return Promise.resolve(window.AMap)
return }
}
if (loadingPromise) {
return loadingPromise
}
loadingPromise = new Promise((resolve, reject) => {
// 创建script标签
const script = document.createElement('script') const script = document.createElement('script')
script.type = 'text/javascript' script.type = 'text/javascript'
script.async = true script.async = true
script.src = `https://webapi.amap.com/maps?v=2.0&key=${MAP_CONFIG.key}&plugin=AMap.Geocoder,AMap.AutoComplete,AMap.PlaceSearch` script.src = `https://webapi.amap.com/maps?v=${MAP_CONFIG.version}&key=${MAP_CONFIG.key}&plugin=${MAP_CONFIG.plugins.join(',')}&security=${MAP_CONFIG.securityJsCode}`
script.onerror = reject
script.onload = () => { script.onload = () => {
isMapLoaded = true // 设置安全密钥
if (window.AMap && window.AMap.SecurityConfig) {
window.AMap.SecurityConfig.securityJsCode = MAP_CONFIG.securityJsCode
}
AMap = window.AMap
resolve(window.AMap) resolve(window.AMap)
} }
script.onerror = () => {
reject(new Error('加载高德地图失败'))
}
document.head.appendChild(script) document.head.appendChild(script)
}) })
return loadingPromise
} }
// 创建地图实例 /**
* 创建地图实例
* @param {string} container 容器ID
* @param {Object} options 地图选项
* @returns {Object} 地图实例
*/
export const createMap = (container, options = {}) => { export const createMap = (container, options = {}) => {
const defaultOptions = { if (!window.AMap) {
zoom: 15, console.error('AMap未加载请先调用loadAMap')
viewMode: '2D', return null
center: [116.397428, 39.90923] // 默认中心点 }
try {
const defaultOptions = {
zoom: 11,
center: [116.397428, 39.90923], // 默认北京
viewMode: '2D'
}
return new window.AMap.Map(container, { ...defaultOptions, ...options })
} catch (error) {
console.error('创建地图失败:', error)
return null
} }
return new window.AMap.Map(container, { ...defaultOptions, ...options })
} }
// 创建地址搜索服务 /**
* 创建自动完成实例
* @param {Object} options 选项
* @returns {Object} 自动完成实例
*/
export const createAutoComplete = (options = {}) => { export const createAutoComplete = (options = {}) => {
return new window.AMap.AutoComplete({ if (!window.AMap) {
city: '全国', console.error('AMap未加载请先调用loadAMap')
citylimit: true, return null
...options }
try {
const defaultOptions = {
city: '全国', // 城市,默认全国
citylimit: false, // 是否限制城市内搜索
input: 'search-input' // 输入框ID这里不使用
}
return new window.AMap.AutoComplete({ ...defaultOptions, ...options })
} catch (error) {
console.error('创建自动完成实例失败:', error)
return null
}
}
/**
* 创建地理编码实例
* @param {Object} options 选项
* @returns {Object} 地理编码实例
*/
export const createGeocoder = (options = {}) => {
if (!window.AMap) {
console.error('AMap未加载请先调用loadAMap')
return null
}
try {
const defaultOptions = {
city: '全国', // 城市,默认全国
radius: 1000 // 搜索半径
}
return new window.AMap.Geocoder({ ...defaultOptions, ...options })
} catch (error) {
console.error('创建地理编码实例失败:', error)
return null
}
}
/**
* 创建地点搜索实例
* @param {Object} options 选项
* @returns {Object} 地点搜索实例
*/
export const createPlaceSearch = (options = {}) => {
if (!window.AMap) {
console.error('AMap未加载请先调用loadAMap')
return null
}
try {
const defaultOptions = {
city: '全国', // 城市,默认全国
citylimit: false, // 是否限制城市内搜索
autoFitView: true // 是否自动调整地图视野
}
return new window.AMap.PlaceSearch({ ...defaultOptions, ...options })
} catch (error) {
console.error('创建地点搜索实例失败:', error)
return null
}
}
/**
* 创建行政区查询实例
* @param {Object} options 选项
* @returns {Object} 行政区查询实例
*/
export const createDistrictSearch = (options = {}) => {
if (!window.AMap) {
console.error('AMap未加载请先调用loadAMap')
return null
}
try {
const defaultOptions = {
level: 'district', // 级别
showbiz: false, // 是否显示商圈
extensions: 'all' // 返回行政区边界坐标点
}
return new window.AMap.DistrictSearch({ ...defaultOptions, ...options })
} catch (error) {
console.error('创建行政区查询实例失败:', error)
return null
}
}
/**
* 获取行政区数据
* @param {string} keywords 关键字"中国"
* @param {number} subdistrict 子行政区级数
* @returns {Promise} 包含行政区数据的Promise
*/
export const fetchDistrictData = (keywords = '中国', subdistrict = 1) => {
return new Promise((resolve, reject) => {
try {
// 尝试使用Web服务API直接获取
fetch(`https://restapi.amap.com/v3/config/district?keywords=${keywords}&subdistrict=${subdistrict}&key=${MAP_CONFIG.webServiceKey}&output=json`)
.then(response => response.json())
.then(data => {
if (data.status === '1') {
resolve(data)
} else {
// 如果Web服务API失败尝试使用DistrictSearch
loadAMap().then(() => {
const districtSearch = createDistrictSearch()
districtSearch.search(keywords, (status, result) => {
if (status === 'complete') {
resolve(result)
} else {
reject(new Error('获取行政区数据失败'))
}
})
}).catch(reject)
}
})
.catch(error => {
// 如果fetch失败尝试使用DistrictSearch
loadAMap().then(() => {
const districtSearch = createDistrictSearch()
districtSearch.search(keywords, (status, result) => {
if (status === 'complete') {
resolve(result)
} else {
reject(error)
}
})
}).catch(reject)
})
} catch (error) {
reject(error)
}
}) })
} }
// 创建地理编码服务 export default {
export const createGeocoder = () => { MAP_CONFIG,
return new window.AMap.Geocoder() loadAMap,
createMap,
createAutoComplete,
createGeocoder,
createPlaceSearch,
createDistrictSearch,
fetchDistrictData
} }

View File

@ -39,6 +39,27 @@
<a v-if="record.qy_group_qrcode" @click="previewQRCode(record.qy_group_qrcode)">查看二维码</a> <a v-if="record.qy_group_qrcode" @click="previewQRCode(record.qy_group_qrcode)">查看二维码</a>
<span v-else>-</span> <span v-else>-</span>
</template> </template>
<template v-if="column.key === 'weekdays'">
<div class="weekdays-display">
<template v-if="record.weekdays && record.weekdays.length > 0">
<template v-if="isAllWeekdaysSelected(record.weekdays)">
<a-tag color="green">所有时间</a-tag>
</template>
<template v-else>
<div class="weekdays-tags">
<a-tag v-if="hasWeekday(record.weekdays, 1)" color="blue">周一</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 2)" color="blue">周二</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 3)" color="blue">周三</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 4)" color="blue">周四</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 5)" color="blue">周五</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 6)" color="green">周六</a-tag>
<a-tag v-if="hasWeekday(record.weekdays, 7)" color="green">周日</a-tag>
</div>
</template>
</template>
<span v-else>-</span>
</div>
</template>
<template v-if="column.key === 'delivery_price'"> <template v-if="column.key === 'delivery_price'">
<div class="delivery-price-info"> <div class="delivery-price-info">
<div>基础费<span style="color: #1890ff; font-weight: bold;">{{ record.base_price }}</span> /</div> <div>基础费<span style="color: #1890ff; font-weight: bold;">{{ record.base_price }}</span> /</div>
@ -121,7 +142,7 @@
@ok="handleSubmit" @ok="handleSubmit"
@cancel="handleCancel" @cancel="handleCancel"
:confirmLoading="confirmLoading" :confirmLoading="confirmLoading"
width="900px" width="1000px"
> >
<template #footer> <template #footer>
<a-space> <a-space>
@ -145,6 +166,36 @@
<a-input v-model:value="formState.name" placeholder="请输入小区名称" /> <a-input v-model:value="formState.name" placeholder="请输入小区名称" />
</a-form-item> </a-form-item>
<a-form-item required>
<template #label>
<div class="weekdays-label-container">
<span>服务时间</span>
<a-button
type="link"
size="small"
@click="toggleAllWeekdays"
class="select-all-btn"
>
{{ isAllSelected ? '取消全选' : '全选' }}
</a-button>
</div>
</template>
<div class="weekdays-checkbox-group">
<a-checkbox-group v-model:value="formState.weekdays">
<a-checkbox :value="1">周一</a-checkbox>
<a-checkbox :value="2">周二</a-checkbox>
<a-checkbox :value="3">周三</a-checkbox>
<a-checkbox :value="4">周四</a-checkbox>
<a-checkbox :value="5">周五</a-checkbox>
<a-checkbox :value="6">周六</a-checkbox>
<a-checkbox :value="7">周日</a-checkbox>
</a-checkbox-group>
</div>
<div class="form-item-tip">
选择小区可提供配送服务的时间
</div>
</a-form-item>
<a-form-item <a-form-item
label="企业微信Webhook" label="企业微信Webhook"
name="webot_webhook" name="webot_webhook"
@ -463,27 +514,42 @@
</template> </template>
<script> <script>
import { defineComponent, ref, onMounted, nextTick } from 'vue' import { defineComponent, ref, onMounted, nextTick, computed } from 'vue'
import { message, Tag, Menu, Dropdown, Image, Upload } from 'ant-design-vue' import { message, Tag, Menu, Dropdown, Image, Upload, Checkbox, Table, Button, Input, Form, Modal, Space, InputNumber, Card } from 'ant-design-vue'
import { getCommunityList, createCommunity, updateCommunityStatus, updateCommunity, uploadImage } from '@/api/community' import { getCommunityList, createCommunity, updateCommunityStatus, updateCommunity, uploadImage } from '@/api/community'
import { loadAMap, createMap } from '@/utils/amap.js' import { loadAMap, createMap } from '@/utils/amap.js'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue' import PageContainer from '@/components/PageContainer.vue'
import { DownOutlined, PlusOutlined } from '@ant-design/icons-vue'
import MapPicker from '@/components/MapPicker/index.vue' import MapPicker from '@/components/MapPicker/index.vue'
import request from '../../utils/request' import {
DownOutlined,
PlusOutlined
} from '@ant-design/icons-vue'
import request from '@/utils/request'
export default defineComponent({ export default defineComponent({
components: { components: {
PageContainer, PageContainer,
ATable: Table,
ATag: Tag, ATag: Tag,
AButton: Button,
AInput: Input,
AForm: Form,
AFormItem: Form.Item,
AModal: Modal,
ADropdown: Dropdown, ADropdown: Dropdown,
AMenu: Menu, AMenu: Menu,
AMenuItem: Menu.Item, AMenuItem: Menu.Item,
DownOutlined, DownOutlined,
MapPicker, MapPicker,
ASpace: Space,
AInputNumber: InputNumber,
ACard: Card,
AImage: Image, AImage: Image,
AUpload: Upload, AUpload: Upload,
PlusOutlined PlusOutlined,
ACheckbox: Checkbox,
ACheckboxGroup: Checkbox.Group
}, },
setup() { setup() {
const loading = ref(false) const loading = ref(false)
@ -553,6 +619,12 @@ export default defineComponent({
key: 'admin', key: 'admin',
width: 150, width: 150,
}, },
{
title: '服务时间',
key: 'weekdays',
width: 150,
align: 'center',
},
{ {
title: '位置', title: '位置',
key: 'location', key: 'location',
@ -668,8 +740,28 @@ export default defineComponent({
}, },
qy_group_qrcode: '', qy_group_qrcode: '',
status: 'UNOPEN', status: 'UNOPEN',
webot_webhook: '' webot_webhook: '',
weekdays: [1, 2, 3, 4, 5, 6, 7] // 使
}) })
//
const isAllSelected = computed(() => {
const allDays = [1, 2, 3, 4, 5, 6, 7];
return formState.value.weekdays &&
formState.value.weekdays.length === 7 &&
allDays.every(day => formState.value.weekdays.includes(day));
});
// /
const toggleAllWeekdays = () => {
if (isAllSelected.value) {
//
formState.value.weekdays = [];
} else {
//
formState.value.weekdays = [1, 2, 3, 4, 5, 6, 7];
}
};
const rules = { const rules = {
name: [{ required: true, message: '请输入小区名称' }], name: [{ required: true, message: '请输入小区名称' }],
@ -682,6 +774,9 @@ export default defineComponent({
], ],
qy_group_qrcode: [ qy_group_qrcode: [
{ required: true, message: '请上传群二维码' } { required: true, message: '请上传群二维码' }
],
weekdays: [
{ required: false, type: 'array' }
] ]
} }
@ -710,13 +805,47 @@ export default defineComponent({
console.log('编辑小区,处理后的地址信息:', locationData) console.log('编辑小区,处理后的地址信息:', locationData)
// weekdays
let weekdaysData = []; //
if (record.weekdays) {
try {
// 使
if (Array.isArray(record.weekdays)) {
weekdaysData = record.weekdays.map(item => typeof item === 'string' ? parseInt(item, 10) : item);
}
// JSON
else if (typeof record.weekdays === 'string') {
try {
const parsedData = JSON.parse(record.weekdays);
if (Array.isArray(parsedData) && parsedData.length > 0) {
//
weekdaysData = parsedData.map(item => typeof item === 'string' ? parseInt(item, 10) : item);
} else if (typeof parsedData === 'string' && parsedData.trim() !== '') {
//
weekdaysData = parsedData.split(',').map(item => parseInt(item, 10));
}
} catch (e) {
// JSON
if (record.weekdays.trim() !== '') {
weekdaysData = record.weekdays.split(',').map(item => parseInt(item, 10));
}
}
}
} catch (e) {
console.error('解析 weekdays 失败:', e);
}
}
console.log('编辑小区,处理后的服务时间:', weekdaysData, '原始数据:', record.weekdays);
// //
formState.value = { formState.value = {
name: record.name || '', name: record.name || '',
location: locationData, location: locationData,
qy_group_qrcode: record.qy_group_qrcode || '', qy_group_qrcode: record.qy_group_qrcode || '',
status: record.status || 'UNOPEN', status: record.status || 'UNOPEN',
webot_webhook: record.webot_webhook || '' webot_webhook: record.webot_webhook || '',
weekdays: weekdaysData
} }
// //
@ -760,7 +889,8 @@ export default defineComponent({
}, },
qy_group_qrcode: '', qy_group_qrcode: '',
status: 'UNOPEN', status: 'UNOPEN',
webot_webhook: '' webot_webhook: '',
weekdays: [1, 2, 3, 4, 5, 6, 7] // 使
} }
fileList.value = [] fileList.value = []
@ -868,7 +998,8 @@ export default defineComponent({
latitude: formState.value.location.latitude, latitude: formState.value.location.latitude,
qy_group_qrcode: formState.value.qy_group_qrcode, qy_group_qrcode: formState.value.qy_group_qrcode,
status: formState.value.status, status: formState.value.status,
webot_webhook: formState.value.webot_webhook webot_webhook: formState.value.webot_webhook,
weekdays: formState.value.weekdays // JSON
} }
let res let res
@ -1268,6 +1399,64 @@ export default defineComponent({
currentCommunity.value = null currentCommunity.value = null
} }
//
const hasWeekday = (weekdays, day) => {
if (!weekdays) return false;
try {
//
if (typeof weekdays === 'string') {
try {
const parsedWeekdays = JSON.parse(weekdays);
return Array.isArray(parsedWeekdays) && parsedWeekdays.includes(day);
} catch (e) {
//
return weekdays.split(',').map(item => parseInt(item, 10)).includes(day);
}
}
//
if (Array.isArray(weekdays)) {
return weekdays.includes(day);
}
return false;
} catch (e) {
console.error('解析 weekdays 失败:', e);
return false;
}
};
//
const isAllWeekdaysSelected = (weekdays) => {
if (!weekdays) return false;
try {
let weekdaysArray = weekdays;
//
if (typeof weekdays === 'string') {
try {
weekdaysArray = JSON.parse(weekdays);
} catch (e) {
//
weekdaysArray = weekdays.split(',').map(item => parseInt(item, 10));
}
}
// 1-7
if (Array.isArray(weekdaysArray)) {
const allDays = [1, 2, 3, 4, 5, 6, 7];
return allDays.every(day => weekdaysArray.includes(day)) && weekdaysArray.length === 7;
}
return false;
} catch (e) {
console.error('检查所有时间失败:', e);
return false;
}
};
onMounted(() => { onMounted(() => {
fetchData() fetchData()
}) })
@ -1331,7 +1520,11 @@ export default defineComponent({
handleAdminSave, handleAdminSave,
handleAdminCancel, handleAdminCancel,
selectSearchResult, selectSearchResult,
handleRemoveAdmin handleRemoveAdmin,
hasWeekday,
isAllWeekdaysSelected,
isAllSelected,
toggleAllWeekdays
} }
} }
}) })
@ -1653,56 +1846,6 @@ export default defineComponent({
color: #ff4d4f; color: #ff4d4f;
} }
.profit-sharing-form {
margin-bottom: 16px;
}
.profit-rate-row {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.profit-rate-label {
width: 150px;
font-size: 14px;
color: rgba(0, 0, 0, 0.85);
}
.profit-rate-input {
width: 100px;
margin-right: 12px;
}
.profit-rate-tip {
flex: 1;
font-size: 12px;
color: rgba(0, 0, 0, 0.45);
}
.total-rate-info {
margin-top: 16px;
padding: 8px 12px;
background-color: #f6ffed;
border-radius: 4px;
border: 1px solid #b7eb8f;
color: #52c41a;
font-weight: 500;
text-align: center;
}
.rate-error {
background-color: #fff2f0;
border-color: #ffccc7;
color: #ff4d4f;
}
.rate-error-message {
margin-left: 8px;
font-weight: normal;
}
/* 表格中分润比例显示样式 */
.profit-sharing-info-table { .profit-sharing-info-table {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1927,7 +2070,7 @@ export default defineComponent({
/* 左右布局样式 */ /* 左右布局样式 */
.form-content { .form-content {
display: flex; display: flex;
gap: 20px; gap: 24px;
} }
.form-left { .form-left {
@ -1939,4 +2082,206 @@ export default defineComponent({
flex: 1; flex: 1;
max-width: 50%; max-width: 50%;
} }
/* 服务时间复选框组样式 */
.weekdays-checkbox-group {
display: flex;
flex-direction: column;
margin-bottom: 4px;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 12px;
background-color: #fafafa;
}
.weekdays-header {
display: flex;
justify-content: flex-start;
margin-bottom: 12px;
border-bottom: 1px dashed #e8e8e8;
padding-bottom: 8px;
}
.weekdays-label-container {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.select-all-btn {
padding: 0;
height: auto;
font-size: 14px;
color: #1890ff;
margin-left: 8px;
}
.weekdays-checkbox-group :deep(.ant-checkbox-group) {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
width: 100%;
}
.weekdays-checkbox-group :deep(.ant-checkbox-wrapper) {
margin-right: 0;
margin-bottom: 0;
min-width: 80px;
padding: 8px 12px;
border-radius: 4px;
background-color: white;
border: 1px solid #e8e8e8;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.weekdays-checkbox-group :deep(.ant-checkbox-wrapper:hover) {
border-color: #1890ff;
background-color: #e6f7ff;
}
.weekdays-checkbox-group :deep(.ant-checkbox-wrapper-checked) {
background-color: #e6f7ff;
border-color: #1890ff;
}
.weekdays-checkbox-group :deep(.ant-checkbox) {
top: 0.2em;
}
.weekdays-checkbox-group :deep(.ant-checkbox-wrapper span:last-child) {
width: 100%;
text-align: center;
padding-left: 8px;
padding-right: 8px;
color: rgba(0, 0, 0, 0.85);
}
/* 表单项提示文字样式优化 */
.form-item-tip {
color: rgba(0, 0, 0, 0.45);
font-size: 12px;
margin-top: 4px;
line-height: 1.5;
}
/* 表单布局优化 */
.form-content {
display: flex;
gap: 24px;
}
.form-left {
flex: 1;
max-width: 50%;
}
.form-right {
flex: 1;
max-width: 50%;
}
/* 表单项间距优化 */
:deep(.ant-form-item) {
margin-bottom: 16px !important;
}
:deep(.ant-form-item-label) {
padding-bottom: 6px;
}
/* 表单项标签样式优化 */
.community-form :deep(.ant-form-item-label > label) {
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
/* 必填星号样式优化 */
.community-form :deep(.ant-form-item-required::before) {
color: #ff4d4f !important;
margin-right: 6px !important;
}
/* 输入框样式优化 */
:deep(.ant-input),
:deep(.ant-input-number),
:deep(.ant-select) {
border-radius: 4px;
}
:deep(.ant-input:hover),
:deep(.ant-input-number:hover),
:deep(.ant-select:hover) {
border-color: #40a9ff;
}
:deep(.ant-input:focus),
:deep(.ant-input-number-focused),
:deep(.ant-select-focused .ant-select-selector) {
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 模态框样式优化 */
:deep(.ant-modal-header) {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-modal-title) {
font-weight: 500;
font-size: 16px;
}
:deep(.ant-modal-body) {
padding: 24px;
}
:deep(.ant-modal-footer) {
padding: 16px 24px;
border-top: 1px solid #f0f0f0;
}
/* 按钮样式优化 */
:deep(.ant-btn) {
border-radius: 4px;
height: 32px;
padding: 0 16px;
}
:deep(.ant-btn-primary) {
background-color: #1890ff;
border-color: #1890ff;
}
:deep(.ant-btn-primary:hover) {
background-color: #40a9ff;
border-color: #40a9ff;
}
/* 服务时间显示样式 */
.weekdays-display {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.weekdays-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
justify-content: center;
}
.weekdays-tags :deep(.ant-tag) {
margin-right: 0;
font-size: 12px;
padding: 0 6px;
line-height: 20px;
height: 22px;
}
</style> </style>