From 952d5ded20e5d4398da7412ee4a53ad0ed386501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E6=A3=AE?= Date: Wed, 5 Nov 2025 15:59:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20hook=20=E5=B0=81?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.tsx | 1 + app/components/layout/Layout.tsx | 129 ++++++++++++++++++++++++++++--- app/components/layout/hooks.ts | 71 +++++++++++++++++ app/components/layout/index.ts | 4 +- app/hooks/refresher.hook.ts | 0 app/model/header.model.ts | 53 +++++++++++++ app/pages/index/hooks.ts | 7 +- app/pages/index/index.config.ts | 5 +- app/pages/index/index.tsx | 32 +++++++- package-lock.json | 10 +++ package.json | 1 + project.config.json | 4 +- 12 files changed, 299 insertions(+), 18 deletions(-) create mode 100644 app/components/layout/hooks.ts create mode 100644 app/hooks/refresher.hook.ts create mode 100644 app/model/header.model.ts diff --git a/app/app.tsx b/app/app.tsx index a1f6bd8..31d1dc3 100644 --- a/app/app.tsx +++ b/app/app.tsx @@ -8,6 +8,7 @@ import "@/styles/tailwind.basic.css"; function App({ children }: PropsWithChildren) { useLaunch(() => { loadGlobalConfig(); + // 自定义header信息 console.log("App launched."); }); diff --git a/app/components/layout/Layout.tsx b/app/components/layout/Layout.tsx index 3ef39de..7b4b447 100644 --- a/app/components/layout/Layout.tsx +++ b/app/components/layout/Layout.tsx @@ -1,22 +1,131 @@ -import { View, Text } from "@tarojs/components"; +import { headerModel } from "@/model/header.model"; +import { + View, + Text, + ScrollView, + CommonEventFunction, +} from "@tarojs/components"; +import Taro from "@tarojs/taro"; +import { clsx } from "clsx"; import { FC, memo, PropsWithChildren } from "react"; +import { useRecoilValue } from "recoil"; +import type { loadMoreConfig, refresherConfig } from "./hooks"; const PageLoader = memo(() => { return ( - + Loading... ); }); +interface LayoutHeaderProps { + className?: string; + hideBack?: boolean; +} + +// 自定义导航栏 +const LayoutHeader = memo( + ({ + className, + hideBack = true, + children, + }: PropsWithChildren) => { + const header = useRecoilValue(headerModel); + + const capsStyles = { + width: `${header.capsuleWidth}px`, + height: `${header.capsuleHeight}px`, + }; + + const handlerBack = () => { + Taro.navigateBack(); + }; + + return ( + + + + + {hideBack ? ( + + 返回 + + ) : null} + + + {children} + + + + + ); + }, +); + +interface LayoutContainerProps { + className?: string; + refresherConfig?: refresherConfig; + refresherEnabled?: boolean; + loadMore?: loadMoreConfig; + onScroll?: CommonEventFunction; +} +// 页面容器 +const LayoutContainer = memo( + ({ + className, + refresherConfig, + loadMore, + onScroll, + children, + }: PropsWithChildren) => { + return ( + + {children} + + ); + }, +); + +// 自定义页脚 +const LayoutFooter = memo(({ children }: PropsWithChildren) => { + return ( + + {children} + + ); +}); + interface LayoutProps { loadding: boolean; } interface LayoutComponent extends FC> { - Header: FC; + Header: FC>; Footer: FC; - Container: FC; + Container: FC>; } const LayoutBase = ({ @@ -26,13 +135,15 @@ const LayoutBase = ({ if (loadding) { return ; } - return ( - - {children} - - ); + return {children}; }; const Layout = LayoutBase as unknown as LayoutComponent; +Layout.Header = LayoutHeader; + +Layout.Container = LayoutContainer; + +Layout.Footer = LayoutFooter; + export default Layout; diff --git a/app/components/layout/hooks.ts b/app/components/layout/hooks.ts new file mode 100644 index 0000000..7d76fcd --- /dev/null +++ b/app/components/layout/hooks.ts @@ -0,0 +1,71 @@ +import { CommonEventFunction } from "@tarojs/components"; +import { useCallback, useState } from "react"; + +export interface refresherConfig { + refresherEnabled: boolean; + refresherThreshold?: number; + refresherDefaultStyle?: "black" | "white" | "none" | string; + refresherBackground?: string; + onRefresherPulling?: CommonEventFunction; + onRefresherRefresh?: CommonEventFunction; + onRefresherRestore?: CommonEventFunction; + onRefresherAbort?: CommonEventFunction; +} + +// 下拉刷新 +export const useRefresherConfig = ({ + onRefresherRefresh, + ...safeConfig +}: refresherConfig) => { + const [refresherTriggered, setRefresherTriggered] = useState(false); + + const handleRefresh = useCallback( + async (event) => { + setRefresherTriggered(true); + onRefresherRefresh?.(event); + await new Promise((resolve) => setTimeout(resolve, 400)); + setRefresherTriggered(false); + }, + [onRefresherRefresh, setRefresherTriggered], + ); + + return { + refresherThreshold: 45, + refresherDefaultStyle: "none", + refresherBackground: "transparent", + refresherTriggered: refresherTriggered, + onRefresherRefresh: handleRefresh, + ...safeConfig, + }; +}; + +export interface loadMoreConfig { + onScrollToLower: CommonEventFunction; + threshold?: number; // 距离底部触发加载 + hasMore?: boolean; // 是否还有更多 +} +// 上拉加载更多 +export const useLoadMoreConfig = ({ + onScrollToLower, + threshold = 50, + hasMore = true, +}: loadMoreConfig) => { + const [isLoading, setIsLoading] = useState(false); + + const handleScrollToLower: CommonEventFunction = useCallback( + async (event) => { + if (isLoading || !hasMore) return; + setIsLoading(true); + onScrollToLower(event); + setIsLoading(false); + }, + [isLoading, hasMore, onScrollToLower], + ); + + return { + lowerThreshold: threshold, + hasMore: hasMore, + isLoading: isLoading, + onScrollToLower: handleScrollToLower, + }; +}; diff --git a/app/components/layout/index.ts b/app/components/layout/index.ts index a82a819..80affd1 100644 --- a/app/components/layout/index.ts +++ b/app/components/layout/index.ts @@ -1,2 +1,2 @@ - -export * from './Layout'; \ No newline at end of file +export * from "./Layout"; +export * from "./hooks"; diff --git a/app/hooks/refresher.hook.ts b/app/hooks/refresher.hook.ts new file mode 100644 index 0000000..e69de29 diff --git a/app/model/header.model.ts b/app/model/header.model.ts new file mode 100644 index 0000000..8e12c63 --- /dev/null +++ b/app/model/header.model.ts @@ -0,0 +1,53 @@ +import Taro from "@tarojs/taro"; +import { atom, selector } from "recoil"; + +export interface HeaderModel { + safeAreaBottom: number; + statusBar: number; + customBar: number; + capsulePadding: number; + capsuleWidth: number; + capsuleHeight: number; + capsuleBottom: number; +} + +const handleSelector = selector({ + key: "handleSelector", + get: async () => { + try { + const result = await Taro.getSystemInfo(); + let windowsWidth = result.windowWidth; + const rect = Taro.getMenuButtonBoundingClientRect(); + let customBar = + (rect.top - (result.statusBarHeight as number)) * 2 + rect.height; + let safeAreaBottom = + result.screenHeight != result.safeArea?.bottom + ? result.screenHeight - (result.safeArea?.bottom as number) + : 0; + return { + safeAreaBottom: safeAreaBottom, + statusBar: result.statusBarHeight ?? 0, + customBar: customBar, + capsulePadding: windowsWidth - rect.right, + capsuleWidth: rect.width, + capsuleHeight: rect.height, + capsuleBottom: customBar - rect.bottom, + }; + } catch (error) { + return { + safeAreaBottom: 0, + statusBar: 0, + customBar: 0, + capsulePadding: 0, + capsuleWidth: 0, + capsuleHeight: 0, + capsuleBottom: 0, + }; + } + }, +}); + +export const headerModel = atom({ + key: "headerModel", + default: handleSelector, +}); diff --git a/app/pages/index/hooks.ts b/app/pages/index/hooks.ts index 55374b0..7f9b1aa 100644 --- a/app/pages/index/hooks.ts +++ b/app/pages/index/hooks.ts @@ -1,3 +1,4 @@ +import Taro from "@tarojs/taro"; import { useState } from "react"; // 页面请求hooks @@ -9,9 +10,13 @@ export const usePageContent = () => { try { console.log("Page content loaded:", code); setPageContent("请求结果"); + await Taro.setNavigationBarColor({ + frontColor: "#000000", + backgroundColor: "#000000", + }); setTimeout(() => { setPageLoading(false); - }, 5000); + }, 400); } catch (error) { console.error("Error loading page content:", error); setPageLoading(false); diff --git a/app/pages/index/index.config.ts b/app/pages/index/index.config.ts index 8f24860..b530ea3 100644 --- a/app/pages/index/index.config.ts +++ b/app/pages/index/index.config.ts @@ -1,4 +1,5 @@ export default definePageConfig({ - navigationBarTitleText: '首页', + navigationBarTitleText: "首页", disableScroll: true, -}) + navigationBarTextStyle: "white", +}); diff --git a/app/pages/index/index.tsx b/app/pages/index/index.tsx index 3af7908..1d9e609 100644 --- a/app/pages/index/index.tsx +++ b/app/pages/index/index.tsx @@ -1,11 +1,23 @@ import Layout from "@/components/layout/Layout"; import { View, Text } from "@tarojs/components"; import { useDidShow } from "@tarojs/taro"; +import { useRefresherConfig } from "@/components/layout"; import { usePageContent } from "./hooks"; export default function Index() { const { pageLoading, pageContent, loadPageContent } = usePageContent(); + // 下拉刷新 + const refresherConfig = useRefresherConfig({ + refresherEnabled: true, + refresherBackground: "#000", + refresherDefaultStyle: "white", + refresherThreshold: 50, + onRefresherRefresh: (event) => { + console.log("触发下拉刷新", event); + }, + }); + useDidShow(async () => { await loadPageContent("测试身份编码"); console.log("Page shown."); @@ -13,9 +25,23 @@ export default function Index() { return ( - - {pageContent} - + 测试导航栏 + + + {Array.from({ length: 10 }).map((_, index) => ( + + {pageContent} + + ))} + + + 测试页脚 ); } diff --git a/package-lock.json b/package-lock.json index ac74f57..76e04fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@tarojs/runtime": "4.1.7", "@tarojs/shared": "4.1.7", "@tarojs/taro": "4.1.7", + "clsx": "^2.1.1", "mockjs": "^1.1.0", "react": "^18.0.0", "react-dom": "^18.0.0", @@ -9669,6 +9670,15 @@ "mimic-response": "^1.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/code-block-writer": { "version": "13.0.3", "resolved": "https://registry.npmmirror.com/code-block-writer/-/code-block-writer-13.0.3.tgz", diff --git a/package.json b/package.json index e6bd642..25c5b5a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@tarojs/runtime": "4.1.7", "@tarojs/shared": "4.1.7", "@tarojs/taro": "4.1.7", + "clsx": "^2.1.1", "mockjs": "^1.1.0", "react": "^18.0.0", "react-dom": "^18.0.0", diff --git a/project.config.json b/project.config.json index 5b15866..279eafa 100644 --- a/project.config.json +++ b/project.config.json @@ -6,7 +6,9 @@ "setting": { "urlCheck": false, "bigPackageSizeSupport": true, - "preloadBackgroundData": false + "preloadBackgroundData": false, + "enhance": true, + "es6": true }, "compileType": "miniprogram" }