登录页面
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { navBarStyle, useNavBarMetrics } from '../lib/nav-bar';
|
||||
|
||||
type PageNavBarProps = {
|
||||
title: string;
|
||||
solid?: boolean;
|
||||
titleVisible?: boolean;
|
||||
onBack?: () => void;
|
||||
right?: ReactNode;
|
||||
};
|
||||
|
||||
/** 内页自定义导航栏(返回 + 标题 + 右侧操作),适配刘海屏 */
|
||||
export default function PageNavBar({
|
||||
title,
|
||||
solid = false,
|
||||
titleVisible = true,
|
||||
onBack,
|
||||
right,
|
||||
}: PageNavBarProps) {
|
||||
const metrics = useNavBarMetrics();
|
||||
|
||||
return (
|
||||
<View
|
||||
className={`page-nav-bar${solid ? ' page-nav-bar--solid' : ''}`}
|
||||
style={navBarStyle(metrics)}
|
||||
>
|
||||
<View
|
||||
className="page-nav-bar__content"
|
||||
style={{ height: `${metrics.navContentHeight}px` }}
|
||||
>
|
||||
{onBack ? (
|
||||
<View className="page-nav-bar__btn" onClick={onBack}>
|
||||
<Text className="page-nav-bar__icon">‹</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className="page-nav-bar__btn page-nav-bar__btn--placeholder" />
|
||||
)}
|
||||
<Text
|
||||
className={`page-nav-bar__title${titleVisible ? ' page-nav-bar__title--visible' : ''}`}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
{right ?? <View className="page-nav-bar__btn page-nav-bar__btn--placeholder" />}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
import { View } from '@tarojs/components';
|
||||
import { pageShellCssVars, useNavBarMetrics } from '../lib/nav-bar';
|
||||
|
||||
type PageShellVariant = 'tab' | 'scroll' | 'sub' | 'plain';
|
||||
|
||||
type PageShellProps = PropsWithChildren<{
|
||||
variant?: PageShellVariant;
|
||||
className?: string;
|
||||
/** 栈页固定底栏时额外底部留白 */
|
||||
hasFixedFooter?: boolean;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 页面壳:唯一注入刘海屏 CSS 变量;统一 min-height / 底栏安全区。
|
||||
* 顶栏组件自行调用 navBarStyle,不要在根节点再套 navBarStyle。
|
||||
*/
|
||||
export default function PageShell({
|
||||
variant = 'tab',
|
||||
className = '',
|
||||
hasFixedFooter = false,
|
||||
children,
|
||||
}: PageShellProps) {
|
||||
const metrics = useNavBarMetrics();
|
||||
const classes = [
|
||||
'page-shell',
|
||||
`page-shell--${variant}`,
|
||||
hasFixedFooter ? 'page-shell--fixed-footer' : '',
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<View className={classes} style={pageShellCssVars(metrics)}>
|
||||
{children as ReactNode}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useState } from 'react';
|
||||
import { View, Image, Swiper, SwiperItem } from '@tarojs/components';
|
||||
|
||||
type ProductCarouselProps = {
|
||||
images: string[];
|
||||
alt: string;
|
||||
variant?: 'home' | 'detail' | 'store';
|
||||
};
|
||||
|
||||
/** 商品/门店轮播(对齐 h5 ProductCarousel 三变体) */
|
||||
export default function ProductCarousel({ images, alt, variant = 'detail' }: ProductCarouselProps) {
|
||||
const slides = images.length > 0 ? images : [''];
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const prefix =
|
||||
variant === 'store' ? 'store-detail-carousel' : variant === 'home' ? 'home-carousel' : 'detail-carousel';
|
||||
|
||||
return (
|
||||
<View className={`${prefix}-wrap`}>
|
||||
<Swiper
|
||||
className={prefix}
|
||||
circular={slides.length > 1}
|
||||
onChange={(e) => setActiveIndex(e.detail.current)}
|
||||
>
|
||||
{slides.map((src, index) => (
|
||||
<SwiperItem key={`${src}-${index}`} className={`${prefix}-item`}>
|
||||
{src ? (
|
||||
<Image className={`${prefix}-image`} src={src} mode="aspectFill" alt={alt} />
|
||||
) : (
|
||||
<View className={`${prefix}-placeholder`} />
|
||||
)}
|
||||
</SwiperItem>
|
||||
))}
|
||||
</Swiper>
|
||||
{slides.length > 1 ? (
|
||||
<View className={`${prefix}-dots`}>
|
||||
{slides.map((_, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className={`${prefix}-dot${index === activeIndex ? ` ${prefix}-dot--active` : ''}`}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { View, Text } from '@tarojs/components';
|
||||
|
||||
type RegionPickerProps = {
|
||||
open: boolean;
|
||||
valueLabel?: string;
|
||||
onClose: () => void;
|
||||
onConfirm?: (label: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* 区域选择弹层占位(门店/地址后续可接真实级联数据)。
|
||||
* 当前提供「郑州市」确认交互,避免阻断主流程。
|
||||
*/
|
||||
export default function RegionPicker({
|
||||
open,
|
||||
valueLabel = '郑州市',
|
||||
onClose,
|
||||
onConfirm,
|
||||
}: RegionPickerProps) {
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<View className="region-picker-mask" onClick={onClose}>
|
||||
<View className="region-picker-sheet" onClick={(e) => e.stopPropagation()}>
|
||||
<View className="region-picker-head">
|
||||
<Text className="region-picker-cancel" onClick={onClose}>
|
||||
取消
|
||||
</Text>
|
||||
<Text className="region-picker-title">选择区域</Text>
|
||||
<Text
|
||||
className="region-picker-ok"
|
||||
onClick={() => {
|
||||
onConfirm?.(valueLabel);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Text>
|
||||
</View>
|
||||
<View className="region-picker-body">
|
||||
<Text className="region-picker-item region-picker-item--active">{valueLabel}</Text>
|
||||
<Text className="u-muted" style={{ display: 'block', marginTop: 12, textAlign: 'center' }}>
|
||||
完整省市区级联后续接入
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { navBarStyle, useNavBarMetrics } from '../lib/nav-bar';
|
||||
|
||||
type SubPageHeaderProps = {
|
||||
title: string;
|
||||
onBack?: () => void;
|
||||
right?: ReactNode;
|
||||
};
|
||||
|
||||
/** 子页面固定实底顶栏(确认订单 / 地址 / 支付 / 订单列表等) */
|
||||
export default function SubPageHeader({ title, onBack, right }: SubPageHeaderProps) {
|
||||
const metrics = useNavBarMetrics();
|
||||
|
||||
function handleBack() {
|
||||
if (onBack) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
const pages = Taro.getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
Taro.navigateBack();
|
||||
return;
|
||||
}
|
||||
Taro.switchTab({ url: '/pages/home/index' });
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="sub-page-header" style={navBarStyle(metrics)}>
|
||||
<View
|
||||
className="sub-page-header__content"
|
||||
style={{ height: `${metrics.navContentHeight}px` }}
|
||||
>
|
||||
<View className="sub-page-header__back" onClick={handleBack}>
|
||||
<Text className="sub-page-header__back-icon">‹</Text>
|
||||
</View>
|
||||
<Text className="sub-page-header__title">{title}</Text>
|
||||
{right ? <View className="sub-page-header__right">{right}</View> : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,25 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { navBarStyle, useNavBarMetrics } from '../lib/nav-bar';
|
||||
|
||||
type TabMainHeaderProps = {
|
||||
title: string;
|
||||
extra?: ReactNode;
|
||||
};
|
||||
|
||||
/** Tab 页顶栏:适配刘海屏 + 微信胶囊避让 */
|
||||
export default function TabMainHeader({ title, extra }: TabMainHeaderProps) {
|
||||
const metrics = useNavBarMetrics();
|
||||
|
||||
return (
|
||||
<View className="tab-main-header">
|
||||
<Text className="tab-main-header__title">{title}</Text>
|
||||
{extra ? <View className="tab-main-header__extra">{extra}</View> : null}
|
||||
<View className="tab-main-header" style={navBarStyle(metrics)}>
|
||||
<View
|
||||
className="tab-main-header__content"
|
||||
style={{ height: `${metrics.navContentHeight}px` }}
|
||||
>
|
||||
<Text className="tab-main-header__title">{title}</Text>
|
||||
{extra ? <View className="tab-main-header__extra">{extra}</View> : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user