|
|
|
|
@ -8,9 +8,42 @@
|
|
|
|
|
<!-- 社区背景和OC活动区域 -->
|
|
|
|
|
<div class="community-scene">
|
|
|
|
|
<div class="scene-background">
|
|
|
|
|
<img src="/photo/map1.png" alt="社区背景" class="scene-image background-image" />
|
|
|
|
|
<div class="background-image-container"></div>
|
|
|
|
|
<!-- 添加:天气信息显示 -->
|
|
|
|
|
<div v-if="weatherInfo" class="weather-widget">
|
|
|
|
|
<div class="weather-content">
|
|
|
|
|
<div class="weather-details">
|
|
|
|
|
<!-- 添加地址输入框 -->
|
|
|
|
|
<div class="location-input">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="inputLocation"
|
|
|
|
|
placeholder="输入任意城市名称"
|
|
|
|
|
size="small"
|
|
|
|
|
@keyup.enter="updateWeather"
|
|
|
|
|
:disabled="weatherLoading"
|
|
|
|
|
>
|
|
|
|
|
<template #append>
|
|
|
|
|
<el-button
|
|
|
|
|
@click="updateWeather"
|
|
|
|
|
:icon="Search"
|
|
|
|
|
:loading="weatherLoading"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-input>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="temperature">{{ weatherInfo.temperature }}°C</div>
|
|
|
|
|
<div class="condition">{{ weatherInfo.condition }}</div>
|
|
|
|
|
<div class="location">{{ weatherInfo.province }}{{ weatherInfo.city !== weatherInfo.province ? weatherInfo.city : '' }}</div>
|
|
|
|
|
<div class="weather-extras" v-if="weatherInfo?.humidity">
|
|
|
|
|
<span>湿度: {{ weatherInfo.humidity }}%</span>
|
|
|
|
|
<span>风速: {{ weatherInfo.windSpeed }}级</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 地点标记区域 - 修复:移到 oc-characters 外部 -->
|
|
|
|
|
<div class="location-pins">
|
|
|
|
|
<div
|
|
|
|
|
@ -103,6 +136,103 @@ import OCListDialog from './OCListDialog.vue'
|
|
|
|
|
import OCInfoDialog from './OCInfoDialog.vue'
|
|
|
|
|
import { ocList } from '../stores/ocStore.js'
|
|
|
|
|
|
|
|
|
|
const inputLocation = ref('天津')
|
|
|
|
|
const currentLocation = ref('天津')
|
|
|
|
|
|
|
|
|
|
//地图和天气函数
|
|
|
|
|
const getRealMap = async (location) => {
|
|
|
|
|
return {
|
|
|
|
|
imageUrl: '/photo/map1.png',
|
|
|
|
|
coordinates:
|
|
|
|
|
locationCoords
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//更新天气函数
|
|
|
|
|
const updateWeather = async () => {
|
|
|
|
|
if (inputLocation.value.trim()) {
|
|
|
|
|
weatherLoading.value = true
|
|
|
|
|
currentLocation.value = inputLocation.value.trim()
|
|
|
|
|
try {
|
|
|
|
|
const weatherData = await getWeatherData(currentLocation.value)
|
|
|
|
|
weatherInfo.value = weatherData
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('更新天气失败:', error)
|
|
|
|
|
} finally {
|
|
|
|
|
weatherLoading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getWeatherData = async (location) => {
|
|
|
|
|
try {
|
|
|
|
|
const API_KEY = '7d1e2dfdd86a45c64e4178c0ad321fcd'
|
|
|
|
|
|
|
|
|
|
// 第一步:通过地理编码API获取城市编码
|
|
|
|
|
const geoResponse = await fetch(`https://restapi.amap.com/v3/geocode/geo?address=${encodeURIComponent(location)}&key=${API_KEY}`)
|
|
|
|
|
const geoData = await geoResponse.json()
|
|
|
|
|
|
|
|
|
|
if (geoData.status !== '1' || !geoData.geocodes || geoData.geocodes.length === 0) {
|
|
|
|
|
throw new Error(`找不到地点: ${location}`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 获取第一个匹配的城市编码
|
|
|
|
|
const adcode = geoData.geocodes[0].adcode
|
|
|
|
|
|
|
|
|
|
// 第二步:使用城市编码获取天气
|
|
|
|
|
const weatherResponse = await fetch(`https://restapi.amap.com/v3/weather/weatherInfo?city=${adcode}&key=${API_KEY}&extensions=base`)
|
|
|
|
|
const weatherData = await weatherResponse.json()
|
|
|
|
|
|
|
|
|
|
if (weatherData.status === '1' && weatherData.lives && weatherData.lives.length > 0) {
|
|
|
|
|
const live = weatherData.lives[0]
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
temperature: live.temperature,
|
|
|
|
|
condition: live.weather,
|
|
|
|
|
humidity: live.humidity,
|
|
|
|
|
windSpeed: live.windpower,
|
|
|
|
|
windDir: live.winddirection,
|
|
|
|
|
reportTime: live.reporttime,
|
|
|
|
|
province: live.province,
|
|
|
|
|
city: live.city
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('获取天气数据失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取真实天气失败:', error)
|
|
|
|
|
return getMockWeatherData(location)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加模拟数据作为后备函数
|
|
|
|
|
const getMockWeatherData = (location) => {
|
|
|
|
|
// 生成随机的天气数据
|
|
|
|
|
const conditions = ['晴', '多云', '阴', '小雨', '中雨', '大雨', '阵雨', '雷阵雨', '雪', '雾', '霾']
|
|
|
|
|
const randomCondition = conditions[Math.floor(Math.random() * conditions.length)]
|
|
|
|
|
|
|
|
|
|
// 根据季节生成合理温度
|
|
|
|
|
const now = new Date()
|
|
|
|
|
const month = now.getMonth() + 1
|
|
|
|
|
let baseTemp = 20
|
|
|
|
|
|
|
|
|
|
if (month >= 3 && month <= 5) baseTemp = 18 // 春季
|
|
|
|
|
else if (month >= 6 && month <= 8) baseTemp = 28 // 夏季
|
|
|
|
|
else if (month >= 9 && month <= 11) baseTemp = 15 // 秋季
|
|
|
|
|
else baseTemp = 5 // 冬季
|
|
|
|
|
|
|
|
|
|
const temperature = baseTemp + Math.floor(Math.random() * 15) - 5
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
temperature: temperature,
|
|
|
|
|
condition: randomCondition,
|
|
|
|
|
humidity: 30 + Math.floor(Math.random() * 50),
|
|
|
|
|
windSpeed: (1 + Math.random() * 6).toFixed(1),
|
|
|
|
|
province: '未知省份',
|
|
|
|
|
city: location
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 默认头像
|
|
|
|
|
const defaultAvatar = 'https://i.pravatar.cc/100?img=1'
|
|
|
|
|
|
|
|
|
|
@ -121,6 +251,10 @@ const locationCoords = {
|
|
|
|
|
'宠物店': { x: 35, y: 75 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentMap = ref('')
|
|
|
|
|
const weatherInfo = ref(null)
|
|
|
|
|
const weatherLoading = ref(false)
|
|
|
|
|
|
|
|
|
|
// 计算OC当前位置(基于其 schedule)
|
|
|
|
|
const ocPositions = ref([])
|
|
|
|
|
// 每个地点的实时状态(位置、在场人数、对应 oc 列表)
|
|
|
|
|
@ -166,6 +300,7 @@ onMounted(() => {
|
|
|
|
|
computePositions()
|
|
|
|
|
posTimer = setInterval(computePositions, 60 * 1000)
|
|
|
|
|
document.addEventListener('click', closePopoverOnClickOutside)
|
|
|
|
|
initMapAndWeather()
|
|
|
|
|
})
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
if (posTimer) clearInterval(posTimer)
|
|
|
|
|
@ -224,6 +359,27 @@ const closePopoverOnClickOutside = (event) => {
|
|
|
|
|
closeLocationPopover()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const initMapAndWeather = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 获取地图(始终使用本地图片)
|
|
|
|
|
const mapData = await getRealMap(currentLocation.value)
|
|
|
|
|
currentMap
|
|
|
|
|
.value = mapData.
|
|
|
|
|
imageUrl
|
|
|
|
|
|
|
|
|
|
// 获取天气信息
|
|
|
|
|
const weatherData = await getWeatherData(currentLocation.value)
|
|
|
|
|
weatherInfo
|
|
|
|
|
.value =
|
|
|
|
|
weatherData
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console
|
|
|
|
|
.error('初始化天气失败:', error)
|
|
|
|
|
currentMap
|
|
|
|
|
.value = '/photo/map1.png'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
@ -261,18 +417,34 @@ const closePopoverOnClickOutside = (event) => {
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
/* 强制基于视口计算高度,不受父元素padding影响 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scene-background {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
position: relative;
|
|
|
|
|
background: #f0f0f0; /* 加载前的背景色 */
|
|
|
|
|
overflow: visible; /* 改为visible避免裁剪 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.scene-image {
|
|
|
|
|
.background-image-container {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
/* 使用背景图替代img,利用background-size控制显示方式 */
|
|
|
|
|
background-image: url("/photo/map1.png");
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-position: center; /* 图片居中显示 */
|
|
|
|
|
background-size: contain; /* 完整显示图片,可能有留白 */
|
|
|
|
|
/* 若希望图片铺满容器(允许裁剪边缘),可替换为:
|
|
|
|
|
background-size: cover;
|
|
|
|
|
*/
|
|
|
|
|
z-index: 1; /* 确保在更底层,不遮挡其他内容 */
|
|
|
|
|
pointer-events: none; /* 禁止背景图片拦截鼠标事件,保障天气卡等可交互 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.right-panel {
|
|
|
|
|
@ -320,7 +492,8 @@ const closePopoverOnClickOutside = (event) => {
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
pointer-events: none; /* children override */
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
z-index: 10; /* 高于背景图 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-pin {
|
|
|
|
|
@ -436,4 +609,118 @@ const closePopoverOnClickOutside = (event) => {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* 响应式调整 */
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.weather-widget {
|
|
|
|
|
top: 10px;
|
|
|
|
|
right: 10px;
|
|
|
|
|
padding: 8px;
|
|
|
|
|
min-width: 110px;
|
|
|
|
|
max-width: 130px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.temperature {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.condition, .location {
|
|
|
|
|
font-size: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.weather-extras {
|
|
|
|
|
font-size: 8px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.community-scene {
|
|
|
|
|
height: calc(100vh - 150px); /* 小屏幕下根据布局调整高度,适配更多空间 */
|
|
|
|
|
}
|
|
|
|
|
.scene-image {
|
|
|
|
|
object-fit: contain; /* 小屏幕下优先保证图片完整显示 */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* 天气组件样式 */
|
|
|
|
|
.weather-widget {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 20px;
|
|
|
|
|
right: 20px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.95);
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
padding: 10px; /* 减少内边距 */
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
|
|
|
z-index: 30;
|
|
|
|
|
min-width: 120px; /* 减小最小宽度 */
|
|
|
|
|
max-width: 140px; /* 添加最大宽度限制 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.weather-content {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px; /* 减小间距 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.weather-details {
|
|
|
|
|
color: #333;
|
|
|
|
|
text-align: center;
|
|
|
|
|
font-size: 0.9em; /* 整体缩小字体 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-input {
|
|
|
|
|
margin-bottom: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-input :deep(.el-input-group) {
|
|
|
|
|
background: rgba(255,255,255,0.9);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
border: 1px solid #e4e7ed;
|
|
|
|
|
height: 28px; /* 减小高度 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-input :deep(.el-input__inner) {
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
font-size: 11px; /* 减小字体 */
|
|
|
|
|
height: 26px;
|
|
|
|
|
line-height: 26px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-input :deep(.el-input-group__append) {
|
|
|
|
|
padding: 0 6px;
|
|
|
|
|
background: transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location-input :deep(.el-button) {
|
|
|
|
|
padding: 6px;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
.temperature {
|
|
|
|
|
font-size: 16px; /* 减小温度字体 */
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.condition {
|
|
|
|
|
font-size: 11px; /* 减小天气状况字体 */
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.location {
|
|
|
|
|
font-size: 10px; /* 减小地点字体 */
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.weather-extras {
|
|
|
|
|
font-size: 9px; /* 减小额外信息字体 */
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
margin-top: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.weather-extras span {
|
|
|
|
|
display: block;
|
|
|
|
|
line-height: 1.2;
|
|
|
|
|
}
|
|
|
|
|
</style>
|