From 2f8ce1711f76e1afea78a528c594afe90338fe40 Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Sun, 15 Mar 2026 10:44:48 +0900 Subject: [PATCH] feat(club-summary): add map navigation and optimized article display - Added a "Navigation" button in ClubSummary that opens a Dropdown menu. - Supported multiple map types: Google, Apple, AMap, Tencent, and Baidu. - Mobile-only features (AMap, Tencent, Baidu) are disabled on desktop devices. - Added `geo` property to `ClubDetail` type to support location data. - Implemented `openMapDirection` utility to launch specific map apps based on the selected type. - Conditionally render the "View Announcement" button only if an article exists. - Updated BaseLayout export name from `Layout` to `BaseLayout` for consistency. --- src/components/AppBar.tsx | 48 +++++++++++++++++ src/components/ClubSummary.tsx | 73 ++++++++++++++++++++++---- src/components/Layout/AppBarLayout.tsx | 17 ++++++ src/components/Layout/BaseLayout.tsx | 2 +- src/components/Layout/index.ts | 2 + src/routes.tsx | 4 +- src/types/index.ts | 1 + src/utils/front.ts | 61 +++++++++++++++++++++ 8 files changed, 196 insertions(+), 12 deletions(-) create mode 100644 src/components/AppBar.tsx create mode 100644 src/components/Layout/AppBarLayout.tsx create mode 100644 src/components/Layout/index.ts diff --git a/src/components/AppBar.tsx b/src/components/AppBar.tsx new file mode 100644 index 0000000..32bdff6 --- /dev/null +++ b/src/components/AppBar.tsx @@ -0,0 +1,48 @@ +import { CalendarOutlined, HeartOutlined, ScheduleOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons"; +import { Button, Flex } from "antd"; +import { useNavigate } from "react-router"; +import styled from "styled-components"; + +const StyledContainer = styled.div` + position: fixed; + bottom: 0; + width: 100vw; + box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); + z-index: 1; + background: #181818; + font-size: 16px; + padding: 8px; + box-sizing: border-box; + .ant-btn { + padding: 24px; + } +`; +export const AppBar = () => { + const navigate = useNavigate(); + return ( + + + - - setIsArticleOpen(false)} placement="bottom"> + {info?.name} + + {noArticle ? null : ( + + )} + {info?.geo && ( + openMapDirection(e.key as MapType, info.geo!), + }} + > + + + )} + + setIsArticleOpen(false)} + placement="top" + title={
{info?.name}
} + >
{info?.article}
diff --git a/src/components/Layout/AppBarLayout.tsx b/src/components/Layout/AppBarLayout.tsx new file mode 100644 index 0000000..0be9028 --- /dev/null +++ b/src/components/Layout/AppBarLayout.tsx @@ -0,0 +1,17 @@ +import { Outlet, useNavigation } from "react-router"; +import { HydrateFallback } from "../HydrateFallback"; +import { AppBar } from "../AppBar"; +import styled from "styled-components"; + +const StyledContainer = styled.div` + padding-bottom: 54px; + box-sizing: border-box; +`; +export const AppBarLayout = () => { + const navigation = useNavigation(); + const loading = navigation.state === 'loading'; + return loading ? : ( + + + ); +} \ No newline at end of file diff --git a/src/components/Layout/BaseLayout.tsx b/src/components/Layout/BaseLayout.tsx index 1f797ea..c47fc9e 100644 --- a/src/components/Layout/BaseLayout.tsx +++ b/src/components/Layout/BaseLayout.tsx @@ -2,7 +2,7 @@ import { Outlet, useNavigation } from "react-router"; import { HydrateFallback } from "../HydrateFallback"; import { MenuButtons } from "../MenuButtons"; -export function Layout() { +export function BaseLayout() { const navigation = useNavigation(); const loading = navigation.state === 'loading'; return loading ? : (<> diff --git a/src/components/Layout/index.ts b/src/components/Layout/index.ts new file mode 100644 index 0000000..8c4aedc --- /dev/null +++ b/src/components/Layout/index.ts @@ -0,0 +1,2 @@ +export { BaseLayout as ActionButtonLayout } from './BaseLayout'; +export { AppBarLayout } from './AppBarLayout'; \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index f789c4d..040ea0a 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -9,12 +9,12 @@ import { CallbackPage } from "./page/Logto/Callback"; import App from "./App"; import { ClubEventsPage } from "./page/ClubEvents"; import { HydrateFallback } from "./components/HydrateFallback"; -import { Layout } from "./components/Layout/BaseLayout"; +import { ActionButtonLayout, AppBarLayout } from './components/Layout'; export const route = createBrowserRouter([ { path: '/', - element: , + element: , children: [ { path: '', diff --git a/src/types/index.ts b/src/types/index.ts index 03b43d7..fc78a81 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -75,4 +75,5 @@ export interface ClubDetail { name: string; article: string; img: string; + geo: { lng: number; lat: number; } | null; } \ No newline at end of file diff --git a/src/utils/front.ts b/src/utils/front.ts index d57695a..bbc4715 100644 --- a/src/utils/front.ts +++ b/src/utils/front.ts @@ -3,3 +3,64 @@ export enum SEX { '男' = 1, '女' = 2, } +export enum MapType { + AMAP = 'amap', + BAIDU = 'baidu', + TENCENT = 'qq', + APPLE = 'apple', + GOOGLE = 'google' // 新增 Google Maps +} + +interface MapLocation { + lat: number; + lng: number; + name?: string; // 目的地名称 +} + +export const isMobile = (): boolean => { + const ua = navigator.userAgent; + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua); +}; + +/** + * 唤起地图导航 + * @param type 地图类型 + * @param location 坐标点 (默认输入为 GCJ-02 坐标) + */ +export const openMapDirection = (type: MapType, location: MapLocation): void => { + const { lat, lng, name = '目的地' } = location; + const encodedName = encodeURIComponent(name); + let url = ''; + + switch (type) { + case MapType.GOOGLE: + /** + * Google Maps Scheme + * saddr: 起点 (为空则默认为当前位置) + * daddr: 终点经纬度 + * directionsmode: 导航模式 (driving, walking, bicycling, transit) + */ + url = `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}`; + break; + + case MapType.AMAP: + url = `iosamap://path?sourceApplication=appName&dlat=${lat}&dlon=${lng}&dname=${encodedName}&dev=0&t=0`; + break; + + case MapType.BAIDU: + url = `baidumap://map/direction?destination=name:${encodedName}|latlng:${lat},${lng}&mode=driving&coord_type=gcj02`; + break; + + case MapType.TENCENT: + url = `qqmap://map/routeplan?type=drive&to=${encodedName}&tocoord=${lat},${lng}&referer=myapp`; + break; + + case MapType.APPLE: + url = `http://maps.apple.com/?daddr=${lat},${lng}&q=${encodedName}`; + break; + } + + if (url) { + window.open(url); + } +}; \ No newline at end of file