From fcd9cc7d4cf4118194ebd96f156da6d1ab4713cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=9D=B0?= Date: Wed, 15 Oct 2025 20:34:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=87=E6=8D=A2=E5=9F=8E=E5=B8=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CommonPopup/index.module.scss | 23 ++- src/components/CustomNavbar/index.module.scss | 5 +- src/components/Picker/CityPicker.tsx | 72 +++++++ src/components/Picker/index.ts | 3 +- src/container/listCustomNavbar/index.tsx | 91 ++++++--- src/game_pages/list/index.module.scss | 75 +++++++- src/game_pages/list/index.tsx | 179 ++++++++++-------- src/other_pages/ntrp-evaluate/index.tsx | 9 +- src/services/listApi.ts | 31 ++- src/store/listStore.ts | 44 +++++ src/utils/index.ts | 1 + src/utils/wx_helper.ts | 35 ++++ types/list/types.ts | 22 ++- 13 files changed, 472 insertions(+), 118 deletions(-) create mode 100644 src/components/Picker/CityPicker.tsx create mode 100644 src/utils/wx_helper.ts diff --git a/src/components/CommonPopup/index.module.scss b/src/components/CommonPopup/index.module.scss index cb12009..a51cb08 100644 --- a/src/components/CommonPopup/index.module.scss +++ b/src/components/CommonPopup/index.module.scss @@ -1,8 +1,8 @@ -@use '~@/scss/themeColor.scss' as theme; +@use "~@/scss/themeColor.scss" as theme; .common-popup { position: fixed; - z-index: 9999!important; + z-index: 9999 !important; .common-popup__drag-handle-container { position: position; } @@ -12,12 +12,12 @@ left: 50%; width: 32px; height: 4px; - background-color: rgba(22, 24, 35, 0.20); + background-color: rgba(22, 24, 35, 0.2); border-radius: 2px; z-index: 10; cursor: pointer; transition: background-color 0.2s ease; - + &:active { background-color: #9ca3af; } @@ -48,13 +48,18 @@ padding: 8px 10px 0 10px; display: flex; gap: 8px; - background: #FFF; + background: #fff; padding-bottom: env(safe-area-inset-bottom); - } .common-popup__btn { flex: 1; + font-feature-settings: "liga" off, "clig" off; + font-family: "PingFang SC"; + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: normal; } .common-popup__btn-cancel { @@ -63,7 +68,7 @@ border: none; width: 154px; height: 44px; - border-radius: 12px!important; + border-radius: 12px !important; border: 0.5px solid rgba(0, 0, 0, 0.06); background: #fff; padding: 4px 10px; @@ -75,7 +80,7 @@ height: 44px; border: 0.5px solid rgba(0, 0, 0, 0.06); background: #000; - border-radius: 12px!important; + border-radius: 12px !important; padding: 4px 10px; } -} \ No newline at end of file +} diff --git a/src/components/CustomNavbar/index.module.scss b/src/components/CustomNavbar/index.module.scss index b1ebd81..fab7c1d 100644 --- a/src/components/CustomNavbar/index.module.scss +++ b/src/components/CustomNavbar/index.module.scss @@ -2,9 +2,8 @@ position: fixed; top: 0; left: 0; - z-index: 999; overflow: hidden; - z-index: 999; + z-index: 9991; width: 100%; background-color: #fff; -} \ No newline at end of file +} diff --git a/src/components/Picker/CityPicker.tsx b/src/components/Picker/CityPicker.tsx new file mode 100644 index 0000000..d4bbaa6 --- /dev/null +++ b/src/components/Picker/CityPicker.tsx @@ -0,0 +1,72 @@ +import React, { useState } from "react"; +import CommonPopup from "@/components/CommonPopup"; +import Picker from "./Picker"; +interface PickerOption { + text: string | number; + value: string | number; +} + +interface PickerProps { + visible: boolean; + setvisible: (visible: boolean) => void; + options?: PickerOption[][] | PickerOption[]; + value?: (string | number)[]; + type?: "month" | "day" | "hour" | "ntrp" | null; + img?: string; + onConfirm?: (options: PickerOption[], values: (string | number)[]) => void; + onChange?: (value: (string | number)[]) => void; + style?: React.CSSProperties; +} + +const PopupPicker = ({ + visible, + setvisible, + value = [], + onConfirm, + onChange, + options = [], + style, +}: PickerProps) => { + const [defaultValue, setDefaultValue] = useState<(string | number)[]>(value); + const changePicker = (_options: any[], values: any, _columnIndex: number) => { + setDefaultValue(values); + }; + + const handleConfirm = () => { + console.log(defaultValue, "defaultValue"); + onChange?.(defaultValue); + setvisible(false); + }; + + const dialogClose = () => { + setvisible(false); + }; + + return ( + <> + + + + + ); +}; + +export default PopupPicker; diff --git a/src/components/Picker/index.ts b/src/components/Picker/index.ts index a8915a1..33a37f7 100644 --- a/src/components/Picker/index.ts +++ b/src/components/Picker/index.ts @@ -3,4 +3,5 @@ export { default as PopupPicker } from './PopupPicker' export { default as PickerCommon } from './PickerCommon' export type { PickerCommonRef } from './PickerCommon' export { default as CalendarUI } from './CalendarUI/CalendarUI' -export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard' \ No newline at end of file +export { default as DialogCalendarCard } from './CalendarDialog/DialogCalendarCard' +export { default as CityPicker } from './CityPicker' \ No newline at end of file diff --git a/src/container/listCustomNavbar/index.tsx b/src/container/listCustomNavbar/index.tsx index 9786f42..a57f39a 100644 --- a/src/container/listCustomNavbar/index.tsx +++ b/src/container/listCustomNavbar/index.tsx @@ -1,13 +1,15 @@ +import { useEffect, useState } from "react"; import { View, Text, Image } from "@tarojs/components"; import img from "@/config/images"; import { useGlobalState } from "@/store/global"; -import { useUserInfo, } from '@/store/userStore' +import { useUserInfo } from "@/store/userStore"; import { useListState } from "@/store/listStore"; import CustomNavbar from "@/components/CustomNavbar"; import { Input } from "@nutui/nutui-react-taro"; import Taro from "@tarojs/taro"; import "./index.scss"; import { getCurrentFullPath } from "@/utils"; +import { CityPicker as PopupPicker } from "@/components/Picker"; interface IProps { config?: { @@ -18,30 +20,60 @@ interface IProps { }; } +function CityPicker(props) { + const { visible, setVisible, cities, area, setArea } = props; + console.log(cities, "cities"); + const [index, setIndex] = useState(0); + const [value, setValue] = useState(area); + + useEffect(() => { + if (visible) { + setIndex(index + 1); + } + }, [visible]); + + function onChange(value: any) { + console.log(value, "value"); + setValue(value); + setArea(value); + } + return ( + visible && ( + + ) + ); +} + const ListHeader = (props: IProps) => { const { config } = props; - const { - showInput = false, - inputLeftIcon, - leftIconClick, - } = config || {}; - const { - getLocationLoading, - statusNavbarHeightInfo, - } = useGlobalState(); - const { gamesNum, searchValue } = useListState(); + const { showInput = false, inputLeftIcon, leftIconClick } = config || {}; + const { getLocationLoading, statusNavbarHeightInfo } = useGlobalState(); + const { gamesNum, searchValue, cities, area, updateArea } = useListState(); const { navBarHeight } = statusNavbarHeightInfo; - const userInfo = useUserInfo() - const city = (userInfo as any)?.city || '' - const district = (userInfo as any)?.district || '' + const [cityPopupVisible, setCityPopupVisible] = useState(false); - console.log("useUserInfo",city,district ) + const userInfo = useUserInfo(); + const province = (userInfo as any)?.province || ""; + const city = (userInfo as any)?.city || ""; + // const district = (userInfo as any)?.district || ""; - const currentAddress = city + district + useEffect(() => { + updateArea(["中国", province, city]); + }, [province, city]); + + // const currentAddress = city + district; const handleInputClick = () => { - const currentPagePath = getCurrentFullPath() + const currentPagePath = getCurrentFullPath(); if (currentPagePath === "/game_pages/searchResult/index") { Taro.navigateBack(); } else { @@ -77,13 +109,18 @@ const ListHeader = (props: IProps) => { height: `${navBarHeight}px`, }; + function handleToggleCity() { + setCityPopupVisible(true); + } + + const area_city = area.at(-1); return ( {/* 首页logo 导航*/} ); }; diff --git a/src/game_pages/list/index.module.scss b/src/game_pages/list/index.module.scss index a6f8c79..db7b707 100644 --- a/src/game_pages/list/index.module.scss +++ b/src/game_pages/list/index.module.scss @@ -4,6 +4,79 @@ } } +.cqContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100vw; + height: 100vh; + + .wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + // gap: 24px; + + .tips { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + + .tip1 { + color: #000; + text-align: center; + font-family: "PingFang SC"; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: 28px; + } + + .tip2 { + color: rgba(0, 0, 0, 0.65); + text-align: center; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + } + } + + .qrcodeWrappper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + + .qrcode { + width: 180px; + height: 180px; + // border-radius: 12px; + // border: 1px solid rgba(0, 0, 0, 0.06); + // background: lightgray 50% / cover no-repeat; + // box-shadow: 0 4px 36px 0 rgba(0, 0, 0, 0.16); + } + + .qrcodeTip { + color: rgba(0, 0, 0, 0.65); + text-align: center; + font-family: "PingFang SC"; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + margin-top: -30px; + } + } + } +} + .listPage { background-color: #fefefe; @@ -83,4 +156,4 @@ transform: translateY(20px); pointer-events: none; /* 隐藏时不响应鼠标事件 */ -} \ No newline at end of file +} diff --git a/src/game_pages/list/index.tsx b/src/game_pages/list/index.tsx index 36b2bbc..847603a 100644 --- a/src/game_pages/list/index.tsx +++ b/src/game_pages/list/index.tsx @@ -9,7 +9,7 @@ import Taro, { } from "@tarojs/taro"; import { useListStore } from "@/store/listStore"; import { useGlobalState } from "@/store/global"; -import { View } from "@tarojs/components"; +import { View, Image, Text } from "@tarojs/components"; import CustomerNavBar from "@/container/listCustomNavbar"; import GuideBar from "@/components/GuideBar"; import ListContainer from "@/container/listContainer"; @@ -19,7 +19,7 @@ import { updateUserLocation } from "@/services/userService"; // import ShareCardCanvas from "@/components/ShareCardCanvas"; import { useUserActions } from "@/store/userStore"; import { useDictionaryStore } from "@/store/dictionaryStore"; -// import generateShareImage from '@/utils/share' +import { saveImage } from "@/utils"; const ListPage = () => { // 从 store 获取数据和方法 @@ -47,6 +47,10 @@ const ListPage = () => { loadMoreMatches, fetchGetGamesCount, updateDistanceQuickFilter, + getCities, + getCityQrCode, + area, + cityQrCode, } = store; const { @@ -104,6 +108,8 @@ const ListPage = () => { useEffect(() => { getLocation(); fetchUserInfo(); + getCities(); + getCityQrCode(); }, []); // 监听数据变化,如果是第一页就滚动到顶部 @@ -317,6 +323,44 @@ const ListPage = () => { // }) // }, []) + const area_city = area.at(-2); + + function renderCityQrcode() { + let item = cityQrCode.find((item) => item.city_name === area_city); + if (!item) item = cityQrCode.find((item) => item.city_name === "其他"); + return ( + + {item ? ( + + + 当前城市暂无球局 + + 加入城市球友群,获得最新球局消息 + + + + { + saveImage(item.qr_code_url); + }} + /> + + 点击图片保存,使用微信扫码加入群聊 + + + + ) : ( + + 当前城市暂无球局, 敬请期待 + + )} + + ); + } + return ( <> {/* 自定义导航 */} @@ -325,82 +369,69 @@ const ListPage = () => { showInput: isShowInputCustomerNavBar, }} /> - - {/* 列表内容 */} - - {/* 综合筛选 */} - {isShowFilterPopup && ( - - + {/* 列表内容 */} + + {/* 综合筛选 */} + {isShowFilterPopup && ( + + + + )} + + 0} + filterCount={filterCount} + onChange={handleSearchChange} + value={searchValue} + onInputClick={handleSearchClick} + /> + + {/* 筛选 */} + + - )} - - 0} - filterCount={filterCount} - onChange={handleSearchChange} - value={searchValue} - onInputClick={handleSearchClick} - /> - - {/* 筛选 */} - - - - {/* 列表内容 */} - + {/* 列表内容 */} + + - - {/* 测试分享功能 */} - {/* */} + )} ); diff --git a/src/other_pages/ntrp-evaluate/index.tsx b/src/other_pages/ntrp-evaluate/index.tsx index aac57e7..4b8a238 100644 --- a/src/other_pages/ntrp-evaluate/index.tsx +++ b/src/other_pages/ntrp-evaluate/index.tsx @@ -553,8 +553,13 @@ function Result() { }, }); } else { - const url = await genCardImage(); - Taro.saveImageToPhotosAlbum({ filePath: url }); + try { + const url = await genCardImage(); + Taro.saveImageToPhotosAlbum({ filePath: url }); + Taro.showToast({ title: "保存成功" }); + } catch (e) { + Taro.showToast({ title: "图片保存失败", icon: "none" }); + } } }); } diff --git a/src/services/listApi.ts b/src/services/listApi.ts index 2104ed4..82debd5 100644 --- a/src/services/listApi.ts +++ b/src/services/listApi.ts @@ -48,8 +48,8 @@ export const getGamesIntegrateList = async (params?: Record) => { /** * 获取列表数量 - * @param params - * @returns + * @param params + * @returns */ export const getGamesCount = async (params?: Record) => { try { @@ -79,7 +79,7 @@ export const getSearchHistory = async (params) => { /** * @description 清除搜索历史 - * @returns + * @returns */ export const clearHistory = async (params) => { try { @@ -96,7 +96,7 @@ export const clearHistory = async (params) => { /** * @description 获取联想 * @param params 查询参数 - * @returns + * @returns */ export const searchSuggestion = async (params) => { try { @@ -109,3 +109,26 @@ export const searchSuggestion = async (params) => { throw error; } } + +export const getCities = async () => { + try { + // 调用HTTP服务获取城市列表 + return httpService.post('/cities/tree', {}) + } catch (error) { + // 捕获并打印错误信息 + console.error("城市列表获取失败:", error); + // 抛出错误以便上层处理 + throw error; + } +} +export const getCityQrCode = async () => { + try { + // 调用HTTP服务获取城市二维码 + return httpService.post('/hot_city_qr/list', {}) + } catch (error) { + // 捕获并打印错误信息 + console.error("城市二维码获取失败:", error); + // 抛出错误以便上层处理 + throw error; + } +} diff --git a/src/store/listStore.ts b/src/store/listStore.ts index 58675e9..29f1d4c 100644 --- a/src/store/listStore.ts +++ b/src/store/listStore.ts @@ -7,6 +7,8 @@ import { clearHistory, searchSuggestion, getGamesCount, + getCities, + getCityQrCode, } from "../services/listApi"; import { ListActions, @@ -15,6 +17,19 @@ import { IPayload, } from "../../types/list/types"; +function translateCityData(dataTree) { + return dataTree.map((item) => { + const { children, ...rest } = item; + return { + ...rest, + text: rest.name, + label: rest.name, + value: rest.name, + children: children?.length > 0 ? translateCityData(children) : null, + }; + }); +} + // 完整的 Store 类型 type TennisStore = ListState & ListActions; @@ -138,6 +153,9 @@ const commonStateDefaultValue = { { id: 5, label: "晚上 18:00-22:00", value: "18:00-22:00" }, { id: 6, label: "夜间 22:00-24:00", value: "22:00-24:00" }, ], + cities: [], + cityQrCode: [], + area: ['', '', ''] as [string, string, string], } // 创建 store @@ -486,6 +504,32 @@ export const useListStore = create()((set, get) => ({ }); console.log("===更新搜索页状态:", state); }, + + async getCities() { + const res = await getCities(); + const state = get(); + set({ + ...state, + cities: translateCityData(res.data), + }) + }, + + async getCityQrCode() { + const res = await getCityQrCode(); + const state = get(); + set({ + ...state, + cityQrCode: res.data, + }) + }, + + updateArea(payload: [string, string, string]) { + const state = get(); + set({ + ...state, + area: payload, + }) + }, })); // 导出便捷的 hooks diff --git a/src/utils/index.ts b/src/utils/index.ts index 198e242..8841d7a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -9,3 +9,4 @@ export * from './orderActions'; export * from './routeUtil'; export * from './share' export * from './genPoster' +export * from './wx_helper' diff --git a/src/utils/wx_helper.ts b/src/utils/wx_helper.ts new file mode 100644 index 0000000..a3382f8 --- /dev/null +++ b/src/utils/wx_helper.ts @@ -0,0 +1,35 @@ +import Taro from "@tarojs/taro"; + +export function saveImage(url) { + Taro.getSetting().then(async (res) => { + if (!res.authSetting["scope.writePhotosAlbum"]) { + Taro.authorize({ + scope: "scope.writePhotosAlbum", + success: async () => { + try { + Taro.saveImageToPhotosAlbum({ filePath: url }); + Taro.showToast({ title: "保存成功" }); + } catch (e) { + Taro.showToast({ title: "图片保存失败", icon: "none" }); + } + }, + fail: () => { + Taro.showModal({ + title: "提示", + content: "需要开启相册权限才能保存图片", + success: (r) => { + if (r.confirm) Taro.openSetting(); + }, + }); + }, + }); + } else { + try { + Taro.saveImageToPhotosAlbum({ filePath: url }); + Taro.showToast({ title: "保存成功" }); + } catch (e) { + Taro.showToast({ title: "图片保存失败", icon: "none" }); + } + } + }); +} \ No newline at end of file diff --git a/types/list/types.ts b/types/list/types.ts index e647e61..4ae190f 100644 --- a/types/list/types.ts +++ b/types/list/types.ts @@ -51,10 +51,24 @@ export interface SearchPageState extends PageState { data: TennisMatch[] suggestionList: string[] isShowSuggestion: boolean - searchHistory: {id: number, title: string}[] + searchHistory: { id: number, title: string }[] searchHistoryParams: Record } +export interface CityTree { + id?: number; + name: string; + children: CityTree[]; +} + +export interface CityQrCodeItem { + id: number, + city_name: string, + qr_code_url: string, + description: string, + sort_order: number +} + // 主状态接口 export interface ListState { currentPage: string @@ -73,6 +87,9 @@ export interface ListState { timeBubbleData: BubbleOption[] dateRangeOptions: BubbleOption[] gamesNum: number + cities: CityTree[] + cityQrCode: CityQrCodeItem[] + area: [string, string, string] } export interface ListActions { @@ -96,6 +113,9 @@ export interface ListActions { getCurrentPageState: () => { currentPageState: any; currentPageKey: string } updateCurrentPageState: (payload: Record) => void updateDistanceQuickFilter: (payload: Record) => void + getCities: () => Promise + getCityQrCode: () => Promise + updateArea: (payload: [string, string, string]) => void } export interface IPayload {