修复首页来馆动效与腾讯地图跨域处理
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-06 23:49:33 +08:00
parent 50902eca82
commit aa432bc4db
3 changed files with 171 additions and 8 deletions

View File

@@ -38,7 +38,7 @@
</view> </view>
<!-- #endif --> <!-- #endif -->
</template> </template>
<view v-else class="outdoor-map-bg"> <view v-else class="outdoor-map-bg" :class="`variant-${props.outdoorVariant}`">
<TencentMap <TencentMap
class="outdoor-map" class="outdoor-map"
:active-floor="activeFloor" :active-floor="activeFloor"
@@ -793,6 +793,33 @@ defineExpose({
background: #f6f8f5; background: #f6f8f5;
} }
.outdoor-map-bg.variant-entrance {
animation: outdoor-entrance-focus 0.38s ease-out both;
}
.outdoor-map-bg.variant-entrance::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
background:
radial-gradient(circle at 52% 60%, rgba(224, 225, 0, 0.16), rgba(224, 225, 0, 0) 32%),
linear-gradient(180deg, rgba(0, 0, 0, 0) 48%, rgba(0, 0, 0, 0.1) 100%);
z-index: 1;
}
@keyframes outdoor-entrance-focus {
from {
opacity: 0.72;
transform: scale(1.018);
}
to {
opacity: 1;
transform: scale(1);
}
}
.indoor-platform-fallback { .indoor-platform-fallback {
position: absolute; position: absolute;
inset: 0; inset: 0;

View File

@@ -114,20 +114,32 @@
v-if="showGuideFloatingActions" v-if="showGuideFloatingActions"
class="guide-quick-actions" class="guide-quick-actions"
> >
<view class="guide-quick-action primary" @tap="handleMoreRouteGuide"> <view
class="guide-quick-action"
:class="{ active: is3DMode }"
@tap="handleMoreRouteGuide"
>
<view class="guide-quick-icon indoor"> <view class="guide-quick-icon indoor">
<view class="guide-quick-icon-floor top"></view> <view class="guide-quick-icon-floor top"></view>
<view class="guide-quick-icon-floor bottom"></view> <view class="guide-quick-icon-floor bottom"></view>
</view> </view>
<text class="guide-quick-action-text primary">馆内</text> <text class="guide-quick-action-text">馆内</text>
</view> </view>
<view class="guide-quick-action" @tap="handleMoreOutdoorNav"> <view
class="guide-quick-action"
:class="{ active: guideOutdoorState === 'entrance' && !is3DMode }"
@tap="handleMoreOutdoorNav"
>
<view class="guide-quick-icon arrival"> <view class="guide-quick-icon arrival">
<view class="guide-quick-pin"></view> <view class="guide-quick-pin"></view>
</view> </view>
<text class="guide-quick-action-text">来馆</text> <text class="guide-quick-action-text">来馆</text>
</view> </view>
<view class="guide-quick-action" @tap="handleExplainQuickTap"> <view
class="guide-quick-action"
:class="{ active: isExplainQuickActionActive }"
@tap="handleExplainQuickTap"
>
<view class="guide-quick-icon explain"> <view class="guide-quick-icon explain">
<view class="guide-quick-explain-wave top"></view> <view class="guide-quick-explain-wave top"></view>
<view class="guide-quick-explain-wave middle"></view> <view class="guide-quick-explain-wave middle"></view>
@@ -637,6 +649,8 @@ const showGuideFloatingActions = computed(() => (
&& (!showGuideHomeDock.value || !homeSearchExpanded.value) && (!showGuideHomeDock.value || !homeSearchExpanded.value)
)) ))
const isExplainQuickActionActive = computed(() => currentTab.value === 'explain')
const toRoutePointOption = (target: GuideRouteTarget): RoutePointOption => ({ const toRoutePointOption = (target: GuideRouteTarget): RoutePointOption => ({
poiId: target.poiId, poiId: target.poiId,
name: target.name, name: target.name,
@@ -1319,7 +1333,24 @@ const closeOutdoorNavPanel = () => {
clearOutdoorNavState() clearOutdoorNavState()
} }
const showOutdoorEntrancePreview = () => {
showOutdoorNavPanel.value = false
showRoutePlanner.value = false
selectedGuidePoi.value = null
isPoiCardCollapsed.value = false
isSimulatingRoute.value = false
clearRoutePreviewState()
clearOutdoorNavState()
is3DMode.value = false
guideOutdoorState.value = 'entrance'
}
const handleMoreOutdoorNav = () => { const handleMoreOutdoorNav = () => {
if (!isOutdoorGuidePanelEnabled) {
showOutdoorEntrancePreview()
return
}
openOutdoorNavPanel() openOutdoorNavPanel()
} }
@@ -2040,7 +2071,7 @@ const handleExplainBack = () => {
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
} }
.guide-quick-action.primary { .guide-quick-action.active {
background: var(--museum-accent); background: var(--museum-accent);
border-color: var(--museum-accent); border-color: var(--museum-accent);
} }
@@ -2057,7 +2088,7 @@ const handleExplainBack = () => {
color: #151713; color: #151713;
} }
.guide-quick-action.primary .guide-quick-icon { .guide-quick-action.active .guide-quick-icon {
color: #0a0b08; color: #0a0b08;
} }
@@ -2139,10 +2170,23 @@ const handleExplainBack = () => {
white-space: nowrap; white-space: nowrap;
} }
.guide-quick-action-text.primary { .guide-quick-action.active .guide-quick-action-text {
color: #0a0b08; color: #0a0b08;
} }
.guide-quick-action.active .guide-quick-pin::before {
content: '';
position: absolute;
left: -7px;
top: -7px;
width: 24px;
height: 24px;
border: 1.5px solid currentColor;
border-radius: 50%;
opacity: 0;
animation: entrance-pin-pulse 1.6s ease-out infinite;
}
.route-sim-top-pill { .route-sim-top-pill {
position: absolute; position: absolute;
top: 126px; top: 126px;
@@ -2232,6 +2276,8 @@ const handleExplainBack = () => {
background: rgba(255, 255, 255, 0.96); background: rgba(255, 255, 255, 0.96);
border: 1px solid #e5e6de; border: 1px solid #e5e6de;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.14);
animation: entrance-tip-in 0.34s ease-out both;
z-index: 45; z-index: 45;
} }
@@ -2486,6 +2532,7 @@ const handleExplainBack = () => {
border: 1px solid #ffffff; border: 1px solid #ffffff;
border-radius: 16px; border-radius: 16px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08); box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
animation: entrance-card-in 0.42s cubic-bezier(0.2, 0.82, 0.2, 1) both;
z-index: 45; z-index: 45;
} }
@@ -2547,4 +2594,41 @@ const handleExplainBack = () => {
color: var(--museum-accent); color: var(--museum-accent);
} }
@keyframes entrance-tip-in {
from {
opacity: 0;
transform: translate3d(-10px, 8px, 0) scale(0.96);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0) scale(1);
}
}
@keyframes entrance-card-in {
from {
opacity: 0;
transform: translate3d(0, 28px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes entrance-pin-pulse {
0% {
opacity: 0.42;
transform: scale(0.68);
}
72%,
100% {
opacity: 0;
transform: scale(1.36);
}
}
</style> </style>

View File

@@ -6,6 +6,7 @@
const TENCENT_MAP_KEY = import.meta.env.VITE_TENCENT_MAP_KEY?.trim() || '' const TENCENT_MAP_KEY = import.meta.env.VITE_TENCENT_MAP_KEY?.trim() || ''
const TENCENT_MAP_BASE_URL = 'https://apis.map.qq.com/ws' const TENCENT_MAP_BASE_URL = 'https://apis.map.qq.com/ws'
const TENCENT_DIRECTION_BASE_URL = `${TENCENT_MAP_BASE_URL}/direction/v1` const TENCENT_DIRECTION_BASE_URL = `${TENCENT_MAP_BASE_URL}/direction/v1`
const isH5BrowserRuntime = typeof window !== 'undefined'
export interface TencentPoiResult { export interface TencentPoiResult {
id: string id: string
@@ -42,6 +43,18 @@ const FALLBACK_MUSEUM_ENTRANCE: TencentPoiResult = {
distance: 0 distance: 0
} }
const createUnavailableSearchResult = (message = 'H5 端腾讯地图 WebService 搜索需服务端代理'): TencentSearchResult => ({
status: -1,
message,
count: 0,
data: []
})
const createUnavailableRouteResult = (message = 'H5 端腾讯地图路线规划需服务端代理'): RouteResult => ({
status: -1,
message
})
/** /**
* 路线规划起点/终点坐标 * 路线规划起点/终点坐标
*/ */
@@ -158,6 +171,11 @@ export async function planWalkingRoute(
from: RoutePoint, from: RoutePoint,
to: RoutePoint to: RoutePoint
): Promise<RouteResult> { ): Promise<RouteResult> {
if (isH5BrowserRuntime) {
console.info('H5 端跳过腾讯地图步行路线 WebService 请求,避免浏览器 CORS 拦截')
return createUnavailableRouteResult()
}
return new Promise((resolve) => { return new Promise((resolve) => {
uni.request({ uni.request({
url: `${TENCENT_DIRECTION_BASE_URL}/walking`, url: `${TENCENT_DIRECTION_BASE_URL}/walking`,
@@ -231,6 +249,11 @@ export async function planDrivingRoute(
from: RoutePoint, from: RoutePoint,
to: RoutePoint to: RoutePoint
): Promise<RouteResult> { ): Promise<RouteResult> {
if (isH5BrowserRuntime) {
console.info('H5 端跳过腾讯地图驾车路线 WebService 请求,避免浏览器 CORS 拦截')
return createUnavailableRouteResult()
}
return new Promise((resolve) => { return new Promise((resolve) => {
uni.request({ uni.request({
url: `${TENCENT_DIRECTION_BASE_URL}/driving`, url: `${TENCENT_DIRECTION_BASE_URL}/driving`,
@@ -304,6 +327,11 @@ export async function planTransitRoute(
from: RoutePoint, from: RoutePoint,
to: RoutePoint to: RoutePoint
): Promise<RouteResult> { ): Promise<RouteResult> {
if (isH5BrowserRuntime) {
console.info('H5 端跳过腾讯地图公交路线 WebService 请求,避免浏览器 CORS 拦截')
return createUnavailableRouteResult()
}
return new Promise((resolve) => { return new Promise((resolve) => {
uni.request({ uni.request({
url: `${TENCENT_DIRECTION_BASE_URL}/transit`, url: `${TENCENT_DIRECTION_BASE_URL}/transit`,
@@ -388,6 +416,16 @@ export function searchPoi(
pageIndex: number = 1, pageIndex: number = 1,
pageSize: number = 10 pageSize: number = 10
): Promise<TencentSearchResult> { ): Promise<TencentSearchResult> {
if (isH5BrowserRuntime) {
console.info('H5 端跳过腾讯地图 POI WebService 搜索,避免浏览器 CORS 拦截:', {
keyword,
region,
pageIndex,
pageSize
})
return Promise.resolve(createUnavailableSearchResult())
}
const boundary = `region(${region},0)` const boundary = `region(${region},0)`
return new Promise((resolve) => { return new Promise((resolve) => {
@@ -432,6 +470,10 @@ export function searchPoi(
* @returns 入口点位信息 * @returns 入口点位信息
*/ */
export async function searchMuseumEntrance(): Promise<TencentPoiResult | null> { export async function searchMuseumEntrance(): Promise<TencentPoiResult | null> {
if (isH5BrowserRuntime) {
return FALLBACK_MUSEUM_ENTRANCE
}
// 先搜索博物馆 // 先搜索博物馆
const museumResult = await searchPoi('深圳自然博物馆', '深圳市', 1, 5) const museumResult = await searchPoi('深圳自然博物馆', '深圳市', 1, 5)
@@ -475,6 +517,16 @@ export function searchNearbyPoi(
keyword: string, keyword: string,
radius: number = 1000 radius: number = 1000
): Promise<TencentSearchResult> { ): Promise<TencentSearchResult> {
if (isH5BrowserRuntime) {
console.info('H5 端跳过腾讯地图周边 WebService 搜索,避免浏览器 CORS 拦截:', {
lat,
lng,
keyword,
radius
})
return Promise.resolve(createUnavailableSearchResult())
}
const boundary = `nearby(${lat},${lng},${radius})` const boundary = `nearby(${lat},${lng},${radius})`
return new Promise((resolve) => { return new Promise((resolve) => {