index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import React, { useState, useEffect } from 'react';
  2. import { View } from '@tarojs/components';
  3. import { useQuery } from '@tanstack/react-query';
  4. import { goodsCategoryClient, advertisementClient } from '@/api';
  5. import CategorySidebar from '@/components/category/CategorySidebar';
  6. import CategorySidebarItem from '@/components/category/CategorySidebarItem';
  7. import CategoryTabbar, { TabItem } from '@/components/category/CategoryTabbar';
  8. import { Image } from '@/components/ui/image';
  9. import TDesignToast from '@/components/tdesign/toast';
  10. import Taro,{ useRouter, navigateTo,useShareAppMessage,useShareTimeline } from '@tarojs/taro';
  11. import { InferResponseType } from 'hono';
  12. import { TabBarLayout } from '@/layouts/tab-bar-layout';
  13. import { Navbar } from '@/components/ui/navbar';
  14. import TDesignIcon from '@/components/tdesign/icon';
  15. import './index.css';
  16. type GoodsCategoryResponse = InferResponseType<typeof goodsCategoryClient.$get, 200>
  17. type Category = GoodsCategoryResponse['data'][0]
  18. type AdvertisementResponse = InferResponseType<typeof advertisementClient.$get, 200>
  19. type Advertisement = AdvertisementResponse['data'][0]
  20. const CategoryPage: React.FC = () => {
  21. const [activeCategoryIndex, setActiveCategoryIndex] = useState<number>(0);
  22. const [activeSubCategoryId, setActiveSubCategoryId] = useState<string>('');
  23. const [toastVisible, setToastVisible] = useState<boolean>(false);
  24. const [toastMessage, setToastMessage] = useState<string>('');
  25. // 使用useRouter钩子获取路由参数
  26. const router = useRouter()
  27. const params = router.params
  28. const goodsId = params?.id ? parseInt(params.id) : 0
  29. const fromPage = params?.from || ''
  30. // 动态设置导航栏和tabbar高度
  31. useEffect(() => {
  32. const setDynamicHeights = () => {
  33. try {
  34. // 获取窗口信息
  35. const windowInfo = Taro.getWindowInfo();
  36. // 计算导航栏高度(状态栏高度 + 导航栏高度)
  37. // 状态栏高度已经是rpx单位,导航栏高度通常为88rpx
  38. const navbarHeightRpx = windowInfo.statusBarHeight + 88;
  39. // 计算tabbar高度(通常为100rpx)
  40. const tabbarHeightRpx = 100; // 标准tabbar高度
  41. // 设置CSS变量
  42. const rootElement = document.documentElement;
  43. if (rootElement) {
  44. rootElement.style.setProperty('--navbar-height', `${navbarHeightRpx}rpx`);
  45. rootElement.style.setProperty('--tabbar-height', `${tabbarHeightRpx}rpx`);
  46. }
  47. } catch (error) {
  48. console.error('设置动态高度失败:', error);
  49. // 使用默认值作为fallback
  50. const rootElement = document.documentElement;
  51. if (rootElement) {
  52. rootElement.style.setProperty('--navbar-height', '96rpx');
  53. rootElement.style.setProperty('--tabbar-height', '100rpx');
  54. }
  55. }
  56. };
  57. setDynamicHeights();
  58. }, []);
  59. // 分享功能
  60. useShareAppMessage(() => {
  61. // 如果有当前选中的分类,使用分类图片作为分享图片
  62. // 如果没有,使用默认图片或空字符串
  63. const shareImageUrl = currentCategory?.imageFile?.fullUrl ||
  64. subCategories?.[0]?.imageFile?.fullUrl ||
  65. '';
  66. return {
  67. title: currentCategory ? `${currentCategory.name} - 发现好物` : '商品分类 - 发现好物',
  68. path: `/pages/category/index?from=share`,
  69. imageUrl: shareImageUrl
  70. }
  71. })
  72. // 分享到朋友圈功能
  73. useShareTimeline(() => {
  74. const shareImageUrl = currentCategory?.imageFile?.fullUrl ||
  75. subCategories?.[0]?.imageFile?.fullUrl ||
  76. '';
  77. return {
  78. title: currentCategory ? `${currentCategory.name} - 发现好物` : '商品分类 - 发现好物',
  79. path: `/pages/category/index?from=share`,
  80. imageUrl: shareImageUrl
  81. }
  82. })
  83. // 获取分类数据
  84. const { data: categoryData, isLoading, error } = useQuery({
  85. queryKey: ['goods-categories'],
  86. queryFn: async () => {
  87. const response = await goodsCategoryClient.$get({
  88. query: {
  89. page: 1,
  90. pageSize: 100,
  91. filters: JSON.stringify({ level: 1, state: 1 }), // 只显示启用的分类
  92. sortBy: 'sort',
  93. sortOrder: 'DESC'
  94. }
  95. });
  96. if (response.status !== 200) {
  97. throw new Error('获取分类数据失败');
  98. }
  99. return response.json();
  100. },
  101. staleTime: 5 * 60 * 1000, // 5分钟缓存
  102. });
  103. // 显示错误Toast
  104. React.useEffect(() => {
  105. if (error) {
  106. setToastMessage('获取分类数据失败,请重试');
  107. setToastVisible(true);
  108. }
  109. }, [error]);
  110. // 获取广告数据
  111. const { data: advertisementData } = useQuery({
  112. queryKey: ['category-advertisements'],
  113. queryFn: async () => {
  114. const response = await advertisementClient.$get({
  115. query: {
  116. filters: JSON.stringify({ status: 1, typeId: 2 }), // 过滤启用的分类页广告
  117. sortBy: 'sort',
  118. sortOrder: 'ASC'
  119. }
  120. });
  121. if (response.status !== 200) {
  122. console.debug('分类页广告API响应状态:', response.status, response.statusText);
  123. throw new Error(`获取广告数据失败 (状态码: ${response.status})`);
  124. }
  125. return response.json();
  126. },
  127. staleTime: 5 * 60 * 1000
  128. });
  129. //console.log("categoryData:",categoryData);
  130. const categories = categoryData?.data || [];
  131. const advertisements = advertisementData?.data || [];
  132. // 当前选中的一级分类
  133. const currentCategory = categories[activeCategoryIndex];
  134. // 当前一级分类的子分类, 当前先用当前选中的分类自身代替
  135. const subCategories = currentCategory ? categories.filter(item => item.id === currentCategory.id) : [];
  136. // 当前一级分类的子分类
  137. // const subCategories = currentCategory?.child_cate || [];
  138. // 广告图片
  139. const advertisementImage = advertisements[0]?.imageFile?.fullUrl || '';
  140. // 处理一级分类切换
  141. const handleCategoryChange = (index: number) => {
  142. setActiveCategoryIndex(index);
  143. // 重置二级分类选中状态
  144. setActiveSubCategoryId('');
  145. };
  146. // 处理二级分类切换
  147. const handleSubCategoryChange = (id: string) => {
  148. setActiveSubCategoryId(id);
  149. };
  150. // 处理分类跳转
  151. const handleCategoryClick = (categoryId: string) => {
  152. navigateTo({
  153. url: `/pages/goods-list/index?cateId=${encodeURIComponent(categoryId)}`,
  154. });
  155. };
  156. // 处理二级分类点击
  157. const handleSubCategoryClick = (category: Category) => {
  158. handleCategoryClick(String(category.id));
  159. };
  160. if (isLoading) {
  161. return (
  162. <TabBarLayout activeKey="category">
  163. <View className="category-page">
  164. <View className="loading">加载中...</View>
  165. </View>
  166. </TabBarLayout>
  167. );
  168. }
  169. if (error) {
  170. return (
  171. <TabBarLayout activeKey="category">
  172. <View className="category-page">
  173. <View className="error">加载失败,请重试</View>
  174. </View>
  175. </TabBarLayout>
  176. );
  177. }
  178. return (
  179. <TabBarLayout activeKey="category">
  180. <Navbar
  181. title="商品分类"
  182. leftIcon=""
  183. onClickLeft={() => {
  184. // 根据来源页面决定返回逻辑
  185. // 分享,返回首页
  186. if (fromPage === 'share') {
  187. Taro.switchTab({
  188. url: `/pages/index/index`
  189. })
  190. } else {
  191. // 从其他页面来的
  192. Taro.navigateBack()
  193. }
  194. }}
  195. rightIcon=""
  196. onClickRight={() => {}}
  197. />
  198. <View className="category-page">
  199. {/* Toast 组件 */}
  200. <TDesignToast
  201. visible={toastVisible}
  202. message={toastMessage}
  203. theme="error"
  204. duration={2000}
  205. onClose={() => setToastVisible(false)}
  206. />
  207. <View className="category-container">
  208. {/* 左侧边栏 - 一级分类 */}
  209. <View className="category-sidebar-container">
  210. <CategorySidebar
  211. activeKey={activeCategoryIndex}
  212. onChange={handleCategoryChange}
  213. >
  214. {categories.map((category: Category, index: number) => (
  215. <CategorySidebarItem
  216. key={category.id}
  217. title={category.name}
  218. onClick={() => handleCategoryChange(index)}
  219. />
  220. ))}
  221. </CategorySidebar>
  222. </View>
  223. {/* 右侧内容区 */}
  224. <View className="category-content">
  225. {/* 二级分类标签栏 */}
  226. {/* {subCategories.length > 0 && (
  227. <View className="sub-category-tabbar">
  228. <CategoryTabbar
  229. tabList={subCategories.map((cat: Category) => ({
  230. id: cat.id.toString(),
  231. name: cat.name,
  232. } as TabItem))}
  233. currentActive={activeSubCategoryId}
  234. onChange={handleSubCategoryChange}
  235. showMore={true}
  236. />
  237. </View>
  238. )} */}
  239. {/* 二级分类网格布局 */}
  240. <View className="sub-category-grid">
  241. {subCategories.map((category: Category) => (
  242. <View
  243. key={category.id}
  244. className="sub-category-item"
  245. onClick={() => handleSubCategoryClick(category)}
  246. >
  247. <View className="sub-category-image-container">
  248. <View className="sub-category-image">
  249. {category.imageFile?.fullUrl ? (
  250. <Image
  251. src={category.imageFile.fullUrl}
  252. alt={category.name}
  253. mode="aspectFill"
  254. rounded="lg"
  255. className="w-full h-full"
  256. showLoading={true}
  257. showError={true}
  258. />
  259. ) : (
  260. <View className="image-placeholder">
  261. <View className="image-fallback">
  262. {category.name.charAt(0)}
  263. </View>
  264. </View>
  265. )}
  266. </View>
  267. </View>
  268. <View className="sub-category-name">
  269. {category.name}
  270. </View>
  271. </View>
  272. ))}
  273. </View>
  274. {/* 广告图 */}
  275. {advertisementImage && (
  276. <View className="advertisement-container">
  277. <View className="advertisement-image">
  278. <Image
  279. src={advertisementImage}
  280. alt="分类页广告"
  281. mode="heightFix"
  282. rounded="lg"
  283. className="w-full h-full"
  284. showLoading={true}
  285. showError={true}
  286. />
  287. </View>
  288. </View>
  289. )}
  290. </View>
  291. </View>
  292. </View>
  293. </TabBarLayout>
  294. );
  295. };
  296. export default CategoryPage;