diff --git a/config/env.ts b/config/env.ts index eb27c6c..16c21e5 100644 --- a/config/env.ts +++ b/config/env.ts @@ -2,8 +2,8 @@ export const ENV_CONFIG = { // 开发环境 development: { - API_BASE_URL: 'http://127.0.0.1:9200/api', - // API_BASE_URL: 'https://cms-api.websoft.top/api', + // API_BASE_URL: 'http://127.0.0.1:9200/api', + API_BASE_URL: 'https://cms-api.websoft.top/api', APP_NAME: '开发环境', DEBUG: 'true', }, diff --git a/src/app.config.ts b/src/app.config.ts index 78df1ad..651f4fe 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -116,12 +116,6 @@ export default { selectedIconPath: "assets/tabbar/home-active.png", text: "首页", }, - { - pagePath: "pages/category/index", - iconPath: "assets/tabbar/category.png", - selectedIconPath: "assets/tabbar/category-active.png", - text: "基地生活", - }, { pagePath: "pages/cart/cart", iconPath: "assets/tabbar/cart.png", diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index f6684da..0621f86 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react' -import { gradientThemes, GradientTheme, gradientUtils } from '@/styles/gradients' +import { useState, useEffect, useCallback } from 'react' +import { gradientThemes, type GradientTheme, gradientUtils } from '@/styles/gradients' import Taro from '@tarojs/taro' export interface UseThemeReturn { @@ -14,28 +14,42 @@ export interface UseThemeReturn { * 提供主题切换和状态管理功能 */ export const useTheme = (): UseThemeReturn => { - const [currentTheme, setCurrentTheme] = useState(gradientThemes[0]) - const [isAutoTheme, setIsAutoTheme] = useState(true) - - // 获取当前主题 - const getCurrentTheme = (): GradientTheme => { - const savedTheme = Taro.getStorageSync('user_theme') || 'auto' - - if (savedTheme === 'auto') { - // 自动主题:根据用户ID生成 - const userId = Taro.getStorageSync('userId') || '1' - return gradientUtils.getThemeByUserId(userId) - } else { - // 手动选择的主题 - return gradientThemes.find(t => t.name === savedTheme) || gradientThemes[0] + const getSavedThemeName = useCallback((): string => { + try { + return Taro.getStorageSync('user_theme') || 'nature' + } catch { + return 'nature' } - } + }, []) + + const getStoredUserId = useCallback((): number => { + try { + const raw = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') + const asNumber = typeof raw === 'number' ? raw : parseInt(String(raw || '1'), 10) + return Number.isFinite(asNumber) ? asNumber : 1 + } catch { + return 1 + } + }, []) + + const resolveTheme = useCallback( + (themeName: string): GradientTheme => { + if (themeName === 'auto') { + return gradientUtils.getThemeByUserId(getStoredUserId()) + } + return gradientThemes.find(t => t.name === themeName) || gradientUtils.getThemeByName('nature') || gradientThemes[0] + }, + [getStoredUserId] + ) + + const [isAutoTheme, setIsAutoTheme] = useState(() => getSavedThemeName() === 'auto') + const [currentTheme, setCurrentTheme] = useState(() => resolveTheme(getSavedThemeName())) // 初始化主题 useEffect(() => { - const savedTheme = Taro.getStorageSync('user_theme') || 'auto' + const savedTheme = getSavedThemeName() setIsAutoTheme(savedTheme === 'auto') - setCurrentTheme(getCurrentTheme()) + setCurrentTheme(resolveTheme(savedTheme)) }, []) // 设置主题 @@ -43,7 +57,7 @@ export const useTheme = (): UseThemeReturn => { try { Taro.setStorageSync('user_theme', themeName) setIsAutoTheme(themeName === 'auto') - setCurrentTheme(getCurrentTheme()) + setCurrentTheme(resolveTheme(themeName)) } catch (error) { console.error('保存主题失败:', error) } @@ -51,7 +65,7 @@ export const useTheme = (): UseThemeReturn => { // 刷新主题(用于自动主题模式下用户信息变更时) const refreshTheme = () => { - setCurrentTheme(getCurrentTheme()) + setCurrentTheme(resolveTheme(getSavedThemeName())) } return { diff --git a/src/pages/index/index.scss b/src/pages/index/index.scss index 403e22a..b8badac 100644 --- a/src/pages/index/index.scss +++ b/src/pages/index/index.scss @@ -4,6 +4,376 @@ page { background: linear-gradient(to bottom, #e9fff2, #ffffff); } +.home-page { + padding: 24rpx 24rpx calc(32rpx + env(safe-area-inset-bottom)); +} + +.home-hero { + position: relative; + overflow: hidden; + border-radius: 28rpx; + background: linear-gradient(180deg, #bfefff 0%, #eafaff 40%, #fff7ec 100%); + box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06); +} + +.home-hero__bg { + position: absolute; + inset: 0; + background: + radial-gradient(360rpx 240rpx at 18% 16%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)), + radial-gradient(320rpx 220rpx at 84% 18%, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0)), + linear-gradient(180deg, rgba(0, 207, 255, 0.12), rgba(0, 0, 0, 0)); + pointer-events: none; +} + +.home-hero__content { + position: relative; + display: flex; + justify-content: space-between; + gap: 18rpx; + padding: 26rpx 24rpx 28rpx; + min-height: 320rpx; +} + +.home-hero__left { + flex: 1; + min-width: 0; +} + +.home-hero__topRow { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16rpx; +} + +.home-hero__brand { + flex: none; + display: inline-flex; + align-items: center; + padding: 8rpx 14rpx; + border-radius: 999rpx; + background: rgba(255, 214, 84, 0.92); + color: #2a2a2a; + font-weight: 700; + font-size: 24rpx; + line-height: 1; +} + +.home-hero__brandText { + line-height: 1; +} + +.home-hero__tag { + flex: none; + display: inline-flex; + align-items: center; + padding: 10rpx 18rpx; + border-radius: 18rpx; + background: linear-gradient(90deg, #22d64a 0%, #7df4b0 100%); + box-shadow: 0 14rpx 24rpx rgba(36, 202, 148, 0.22); +} + +.home-hero__tagText { + font-size: 56rpx; + font-weight: 900; + color: #ffffff; + line-height: 1; +} + +.home-hero__date { + flex: 1; + min-width: 0; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10rpx 14rpx; + border-radius: 999rpx; + background: rgba(255, 255, 255, 0.75); +} + +.home-hero__dateText { + font-size: 26rpx; + font-weight: 700; + color: #1a1a1a; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.home-hero__headline { + margin-top: 22rpx; +} + +.home-hero__headlineText { + display: block; + font-size: 42rpx; + font-weight: 900; + color: #0b0b0b; + letter-spacing: 0.5px; + line-height: 1.15; +} + +.home-hero__right { + width: 200rpx; + display: flex; + justify-content: flex-end; + align-items: flex-end; +} + +.home-hero__bottle { + position: relative; + width: 190rpx; + height: 250rpx; + border-radius: 28rpx; + background: + radial-gradient(240rpx 360rpx at 60% 30%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.18)), + linear-gradient(180deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.1)); + border: 2rpx solid rgba(255, 255, 255, 0.65); + box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.12); +} + +.home-hero__bottleCap { + position: absolute; + top: 14rpx; + left: 50%; + transform: translateX(-50%); + width: 88rpx; + height: 26rpx; + border-radius: 999rpx; + background: linear-gradient(180deg, #d7e6f3, #b0cadd); + box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.12); +} + +.home-hero__bottleLabel { + position: absolute; + left: 18rpx; + right: 18rpx; + bottom: 30rpx; + padding: 12rpx 12rpx; + border-radius: 18rpx; + background: linear-gradient(90deg, rgba(0, 150, 255, 0.18), rgba(0, 255, 210, 0.18)); + border: 2rpx solid rgba(255, 255, 255, 0.45); +} + +.home-hero__bottleLabelText { + font-size: 30rpx; + font-weight: 800; + color: rgba(0, 80, 140, 0.95); + text-align: center; + display: block; +} + +.ticket-card { + margin-top: 18rpx; + border-radius: 22rpx; + overflow: hidden; + background: #ffffff; + box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06); +} + +.ticket-card__head { + display: flex; + justify-content: space-between; + align-items: center; + padding: 18rpx 20rpx; + background: linear-gradient(90deg, #22d64a 0%, #7df4b0 100%); +} + +.ticket-card__title { + color: #ffffff; + font-weight: 800; + font-size: 28rpx; +} + +.ticket-card__count { + color: rgba(255, 255, 255, 0.92); + font-size: 24rpx; +} + +.ticket-card__countNum { + color: #ffffff; + font-weight: 900; +} + +.ticket-card__body { + padding: 20rpx 10rpx 22rpx; +} + +.shortcut-grid { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12rpx; +} + +.shortcut-grid__item { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 12rpx; +} + +.shortcut-grid__icon { + width: 88rpx; + height: 88rpx; + border-radius: 18rpx; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + color: #20c26a; + border: 2rpx solid rgba(32, 194, 106, 0.35); +} + +.shortcut-grid__text { + font-size: 24rpx; + color: #333333; +} + +.home-tabs { + margin-top: 18rpx; +} + +.home-tabs__inner { + display: flex; + gap: 18rpx; + padding: 0 4rpx; +} + +.home-tabs__item { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10rpx 18rpx; + border-radius: 999rpx; + background: transparent; +} + +.home-tabs__item--active { + background: rgba(32, 194, 106, 0.16); +} + +.home-tabs__itemText { + font-size: 28rpx; + color: #2a2a2a; + white-space: nowrap; +} + +.home-tabs__item--active .home-tabs__itemText { + color: #16b65a; + font-weight: 800; +} + +.goods-grid { + margin-top: 18rpx; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18rpx; +} + +.goods-card { + border-radius: 22rpx; + overflow: hidden; + background: #ffffff; + box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06); +} + +.goods-card__imgWrap { + padding: 18rpx 18rpx 0; +} + +.goods-card__img { + width: 100%; + height: 280rpx; + border-radius: 18rpx; + background: #f4f4f4; +} + +.goods-card__body { + padding: 18rpx 18rpx 20rpx; +} + +.goods-card__title { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 26rpx; + font-weight: 700; + color: #1c1c1c; + min-height: 72rpx; +} + +.goods-card__meta { + margin-top: 10rpx; + display: flex; + justify-content: space-between; + align-items: flex-end; + gap: 10rpx; +} + +.goods-card__sold { + font-size: 22rpx; + color: #9a9a9a; + white-space: nowrap; +} + +.goods-card__price { + display: flex; + align-items: baseline; + gap: 4rpx; + color: #27c86b; + white-space: nowrap; +} + +.goods-card__priceUnit { + font-size: 22rpx; + font-weight: 800; +} + +.goods-card__priceValue { + font-size: 36rpx; + font-weight: 900; +} + +.goods-card__actions { + margin-top: 16rpx; + display: flex; + gap: 14rpx; +} + +.goods-card__btn { + flex: 1; + height: 64rpx; + border-radius: 999rpx; + display: flex; + align-items: center; + justify-content: center; +} + +.goods-card__btn--ghost { + border: 2rpx solid rgba(32, 194, 106, 0.7); + background: #ffffff; +} + +.goods-card__btn--primary { + background: linear-gradient(90deg, #24d34c 0%, #6df09a 100%); +} + +.goods-card__btnText { + font-size: 24rpx; + font-weight: 700; + color: #18b85a; + white-space: nowrap; +} + +.goods-card__btnText--primary { + color: #ffffff; +} + .buy-btn{ height: 70px; background: linear-gradient(to bottom, #1cd98a, #24ca94); @@ -73,4 +443,4 @@ page { .nut-swiper, .nut-swiper-item { -webkit-overflow-scrolling: touch; /* iOS平台启用硬件加速滚动 */ -} \ No newline at end of file +} diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index 3f33d9a..f3d3241 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -1,19 +1,17 @@ -import Header from './Header'; -import BestSellers from './BestSellers'; -import Taro from '@tarojs/taro'; -import {useShareAppMessage} from "@tarojs/taro" -import {useEffect, useState} from "react"; -import {getShopInfo} from "@/api/layout"; -import Menu from "./Menu"; -import Banner from "./Banner"; -import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite"; +import Header from './Header' +import Taro, { useShareAppMessage } from '@tarojs/taro' +import { View, Text, Image, ScrollView } from '@tarojs/components' +import { useEffect, useMemo, useState, type ReactNode } from 'react' +import { Cart, Coupon, Gift, Ticket } from '@nutui/icons-react-taro' +import { getShopInfo } from '@/api/layout' +import { checkAndHandleInviteRelation, hasPendingInvite } from '@/utils/invite' +import { pageShopGoods } from '@/api/shop/shopGoods' +import type { ShopGoods } from '@/api/shop/shopGoods/model' import './index.scss' function Home() { - // 吸顶状态 - // const [stickyStatus, setStickyStatus] = useState(false) - // Tabs粘性状态 - const [_, setTabsStickyStatus] = useState(false) + const [activeTab, setActiveTab] = useState('推荐') + const [goodsList, setGoodsList] = useState([]) useShareAppMessage(() => { // 获取当前用户ID,用于生成邀请链接 @@ -85,9 +83,7 @@ function Home() { // } // 处理Tabs粘性状态变化 - const handleTabsStickyChange = (isSticky: boolean) => { - setTabsStickyStatus(isSticky) - } + // const handleTabsStickyChange = (isSticky: boolean) => {} const reload = () => { @@ -99,6 +95,10 @@ function Home() { }) + pageShopGoods({}).then(res => { + setGoodsList(res?.list || []) + }) + // 检查是否有待处理的邀请关系 - 异步处理,不阻塞页面加载 if (hasPendingInvite()) { console.log('检测到待处理的邀请关系') @@ -152,16 +152,177 @@ function Home() { }); }, []); + const tabs = useMemo(() => ['推荐', '桶装水', '优惠组合', '购机套餐', '饮水设备'], []) + + const shortcuts = useMemo< + Array<{ key: string; title: string; icon: ReactNode; onClick: () => void }> + >( + () => [ + { + key: 'ticket', + title: '充值水票', + icon: , + onClick: () => Taro.navigateTo({ url: '/user/wallet/wallet' }), + }, + { + key: 'order', + title: '立即订水', + icon: , + onClick: () => Taro.switchTab({ url: '/pages/category/index' }), + }, + { + key: 'invite', + title: '邀请有礼', + icon: , + onClick: () => Taro.navigateTo({ url: '/dealer/qrcode/index' }), + }, + { + key: 'coupon', + title: '领券中心', + icon: , + onClick: () => Taro.navigateTo({ url: '/coupon/index' }), + }, + ], + [] + ) + + const visibleGoods = useMemo(() => { + // 先按效果图展示两列卡片,数据不够时也保持布局稳定 + const list = goodsList || [] + if (list.length <= 6) return list + return list.slice(0, 6) + }, [goodsList]) + return ( <> {/* Header区域 - 现在由Header组件内部处理吸顶逻辑 */}
-
- - - -
+ + {/* 顶部活动主视觉 */} + + + + + + + 桂乐淘 + + + 新人有礼 + + + 202X年X月X日 - X月X日 + + + + + 大容量家庭装 + 净含量15L + + + + + + + + 山泉水 + + + + + + + {/* 电子水票 */} + + + 电子水票 + + 您还有 0 张水票 + + + + + + {shortcuts.map((item) => ( + + {item.icon} + {item.title} + + ))} + + + + + {/* 分类Tabs */} + + + {tabs.map((tab) => { + const active = tab === activeTab + return ( + setActiveTab(tab)} + > + {tab} + + ) + })} + + + + {/* 商品列表 */} + + {visibleGoods.map((item) => ( + + + + Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` }) + } + /> + + + + {item.name} + + 已购:{item.sales || 0}人 + + + {item.price} + + + + + Taro.navigateTo({ url: '/user/coupon/index' })} + > + 买水票更优惠 + + + Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` }) + } + > + 立即购买 + + + + + ))} + + ) } diff --git a/src/user/theme/index.tsx b/src/user/theme/index.tsx index ec41396..5abe11d 100644 --- a/src/user/theme/index.tsx +++ b/src/user/theme/index.tsx @@ -11,13 +11,13 @@ const ThemeSelector: React.FC = () => { // 获取当前主题 useEffect(() => { - const savedTheme = Taro.getStorageSync('user_theme') || 'auto' + const savedTheme = Taro.getStorageSync('user_theme') || 'nature' setSelectedTheme(savedTheme) if (savedTheme === 'auto') { // 自动主题:根据用户ID生成 - const userId = Taro.getStorageSync('userId') || '1' - const theme = gradientUtils.getThemeByUserId(userId) + const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1' + const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10)) setCurrentTheme(theme) } else { // 手动选择的主题 @@ -33,8 +33,8 @@ const ThemeSelector: React.FC = () => { setSelectedTheme(themeName) if (themeName === 'auto') { - const userId = Taro.getStorageSync('userId') || '1' - const theme = gradientUtils.getThemeByUserId(userId) + const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1' + const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10)) setCurrentTheme(theme) } else { const theme = gradientThemes.find(t => t.name === themeName) @@ -61,8 +61,8 @@ const ThemeSelector: React.FC = () => { // 预览主题 const previewTheme = (themeName: string) => { if (themeName === 'auto') { - const userId = Taro.getStorageSync('userId') || '1' - const theme = gradientUtils.getThemeByUserId(userId) + const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1' + const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10)) setCurrentTheme(theme) } else { const theme = gradientThemes.find(t => t.name === themeName)