小程序修改

This commit is contained in:
2026-07-12 19:44:36 +08:00
parent 3b4833b51a
commit e2dfb08de3
45 changed files with 2028 additions and 347 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import { navBarStyle, useNavBarMetrics } from '../lib/nav-bar';
import { subPageNavBarStyle, useNavBarMetrics } from '../lib/nav-bar';
type PageNavBarProps = {
title: string;
@@ -23,7 +23,7 @@ export default function PageNavBar({
return (
<View
className={`page-nav-bar${solid ? ' page-nav-bar--solid' : ''}`}
style={navBarStyle(metrics)}
style={subPageNavBarStyle(metrics)}
>
<View
className="page-nav-bar__content"
+162 -25
View File
@@ -1,48 +1,185 @@
import { View, Text } from '@tarojs/components';
import { useEffect, useMemo, useState } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import {
REGION_ALL,
getCities,
getCitiesForPicker,
getDistricts,
getDistrictsForPicker,
getProvincesForPicker,
normalizeRegionSelection,
toCityLevelRegion,
type RegionSelection,
} from '../lib/region-data';
type RegionPickerProps = {
open: boolean;
valueLabel?: string;
value: RegionSelection;
onClose: () => void;
onConfirm?: (label: string) => void;
onConfirm: (region: RegionSelection) => void;
levels?: 2 | 3;
};
/**
* 区域选择弹层占位(门店/地址后续可接真实级联数据)。
* 当前提供「郑州市」确认交互,避免阻断主流程。
*/
type PickerLevel = 'province' | 'city' | 'district';
const ALL_TABS: Array<{ key: PickerLevel; label: string }> = [
{ key: 'province', label: '省份' },
{ key: 'city', label: '城市' },
{ key: 'district', label: '区县' },
];
function initialTab(value: RegionSelection, levels: 2 | 3): PickerLevel {
const normalized = levels === 2 ? toCityLevelRegion(value) : normalizeRegionSelection(value);
if (levels === 2) {
return normalized.province && normalized.province !== REGION_ALL ? 'city' : 'province';
}
if (normalized.district && normalized.district !== REGION_ALL) return 'district';
if (normalized.city && normalized.city !== REGION_ALL) return 'city';
return 'province';
}
function tabLabel(tab: PickerLevel, draft: RegionSelection, fallback: string) {
if (tab === 'province') {
return draft.province && draft.province !== REGION_ALL ? draft.province : fallback;
}
if (tab === 'city') {
return draft.city && draft.city !== REGION_ALL ? draft.city : fallback;
}
return draft.district && draft.district !== REGION_ALL ? draft.district : fallback;
}
export default function RegionPicker({
open,
valueLabel = '郑州市',
value,
onClose,
onConfirm,
levels = 3,
}: RegionPickerProps) {
const [draft, setDraft] = useState<RegionSelection>(value);
const [activeTab, setActiveTab] = useState<PickerLevel>('province');
const tabs = levels === 2 ? ALL_TABS.slice(0, 2) : ALL_TABS;
useEffect(() => {
if (!open) return;
const normalized = levels === 2 ? toCityLevelRegion(value) : normalizeRegionSelection(value);
setDraft(normalized);
setActiveTab(initialTab(value, levels));
}, [open, value, levels]);
const listItems = useMemo(() => {
if (activeTab === 'province') return getProvincesForPicker();
if (activeTab === 'city') return getCitiesForPicker(draft.province);
return getDistrictsForPicker(draft.province, draft.city);
}, [activeTab, draft.province, draft.city]);
const selectedValue =
activeTab === 'province' ? draft.province : activeTab === 'city' ? draft.city : draft.district;
const canConfirm =
levels === 2
? Boolean(draft.province && draft.city)
: Boolean(draft.province && draft.city && draft.district);
if (!open) return null;
function selectProvince(province: string) {
if (province === REGION_ALL) {
setDraft({ province: REGION_ALL, city: REGION_ALL, district: REGION_ALL });
setActiveTab('city');
return;
}
const nextCities = getCities(province);
const city = nextCities[0] ?? '';
if (levels === 2) {
setDraft({ province, city, district: REGION_ALL });
setActiveTab('city');
return;
}
const nextDistricts = getDistricts(province, city);
setDraft({ province, city, district: nextDistricts[0] ?? '' });
setActiveTab('city');
}
function selectCity(city: string) {
if (city === REGION_ALL) {
setDraft({ ...draft, city: REGION_ALL, district: REGION_ALL });
if (levels === 3) setActiveTab('district');
return;
}
if (levels === 2) {
setDraft({ ...draft, city, district: REGION_ALL });
return;
}
const nextDistricts = getDistricts(draft.province, city);
setDraft({ ...draft, city, district: nextDistricts[0] ?? '' });
setActiveTab('district');
}
function selectDistrict(district: string) {
setDraft({ ...draft, district });
}
function onSelectItem(item: string) {
if (activeTab === 'province') selectProvince(item);
else if (activeTab === 'city') selectCity(item);
else selectDistrict(item);
}
function onTabClick(tab: PickerLevel) {
if (tab === 'city' && !draft.province) return;
if (tab === 'district' && (!draft.province || !draft.city)) return;
setActiveTab(tab);
}
function handleConfirm() {
if (!canConfirm) return;
const next = levels === 2 ? toCityLevelRegion(draft) : normalizeRegionSelection(draft);
onConfirm(next);
onClose();
}
return (
<View className="region-picker-mask" onClick={onClose}>
<View className="region-picker-overlay" 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>
<View className="region-picker-toolbar">
<View className="region-picker-tabs">
{tabs.map((tab) => {
const disabled =
(tab.key === 'city' && !draft.province) ||
(tab.key === 'district' && (!draft.province || !draft.city));
return (
<Text
key={tab.key}
className={`region-picker-tab${activeTab === tab.key ? ' active' : ''}${disabled ? ' disabled' : ''}`}
onClick={() => !disabled && onTabClick(tab.key)}
>
{tabLabel(tab.key, draft, tab.label)}
</Text>
);
})}
</View>
<Text
className="region-picker-ok"
onClick={() => {
onConfirm?.(valueLabel);
onClose();
}}
className={`region-picker-confirm${canConfirm ? ' ready' : ''}`}
onClick={() => canConfirm && handleConfirm()}
>
</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>
<ScrollView className="region-picker-list" scrollY>
{listItems.map((item) => (
<View
key={item}
className={`region-picker-option${selectedValue === item ? ' selected' : ''}${
item === REGION_ALL ? ' region-picker-option--all' : ''
}`}
onClick={() => onSelectItem(item)}
>
<Text>{item}</Text>
</View>
))}
</ScrollView>
</View>
</View>
);
@@ -1,7 +1,7 @@
import type { ReactNode } from 'react';
import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { navBarStyle, useNavBarMetrics } from '../lib/nav-bar';
import { subPageNavBarStyle, useNavBarMetrics } from '../lib/nav-bar';
type SubPageHeaderProps = {
title: string;
@@ -27,7 +27,7 @@ export default function SubPageHeader({ title, onBack, right }: SubPageHeaderPro
}
return (
<View className="sub-page-header" style={navBarStyle(metrics)}>
<View className="sub-page-header" style={subPageNavBarStyle(metrics)}>
<View
className="sub-page-header__content"
style={{ height: `${metrics.navContentHeight}px` }}