You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
756 lines
20 KiB
756 lines
20 KiB
import { Ionicons } from '@expo/vector-icons';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { useRouter } from 'expo-router';
|
|
import { useState } from 'react';
|
|
import {
|
|
ActivityIndicator,
|
|
Modal,
|
|
Pressable,
|
|
ScrollView,
|
|
StyleSheet,
|
|
Text,
|
|
TextInput,
|
|
View,
|
|
} from 'react-native';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useJobList } from '../../src/hooks';
|
|
import {
|
|
CITIES,
|
|
EDUCATION_OPTIONS,
|
|
EXPERIENCE_OPTIONS,
|
|
JOB_LEVEL_LABEL,
|
|
SALARY_RANGES,
|
|
} from '../../src/constants';
|
|
import type { Job, SortType } from '../../src/types';
|
|
import { colors, headerGradient } from '../../src/theme';
|
|
|
|
/** 筛选项(下拉占位) */
|
|
const FILTERS = ['全部行业', '薪资', '经验', '更多筛选'];
|
|
|
|
/** 排序选项 */
|
|
const SORTS: { key: SortType; label: string }[] = [
|
|
{ key: 'comprehensive', label: '综合排序' },
|
|
{ key: 'latest', label: '最新发布' },
|
|
{ key: 'salary', label: '薪资最高' },
|
|
];
|
|
|
|
/** 等级 → 文字颜色 */
|
|
const LEVEL_COLOR = {
|
|
excellent: colors.primaryDark,
|
|
good: colors.primary,
|
|
medium: colors.textSecondary,
|
|
} as const;
|
|
|
|
/** 更多筛选的三组字段 */
|
|
type FilterKey = 'salary' | 'experience' | 'education';
|
|
|
|
/** 切换某个筛选值(多选:已在则移除,否则加入) */
|
|
function toggleValue(
|
|
draft: Record<FilterKey, string[]>,
|
|
key: FilterKey,
|
|
value: string,
|
|
): Record<FilterKey, string[]> {
|
|
const current = draft[key];
|
|
const next = current.includes(value) ? current.filter((v) => v !== value) : [...current, value];
|
|
return { ...draft, [key]: next };
|
|
}
|
|
|
|
/** 清空某一组筛选 */
|
|
function clearGroup(draft: Record<FilterKey, string[]>, key: FilterKey): Record<FilterKey, string[]> {
|
|
return { ...draft, [key]: [] };
|
|
}
|
|
|
|
/** 筛选面板中的一组选项(「不限」为清空态) */
|
|
function FilterGroup({
|
|
title,
|
|
options,
|
|
selected,
|
|
onToggle,
|
|
onClear,
|
|
}: {
|
|
title: string;
|
|
options: string[];
|
|
selected: string[];
|
|
onToggle: (value: string) => void;
|
|
onClear: () => void;
|
|
}) {
|
|
return (
|
|
<>
|
|
<Text style={styles.filterLabel}>{title}</Text>
|
|
<View style={styles.optionRow}>
|
|
{options.map((option) => {
|
|
const isNone = option === '不限';
|
|
const active = isNone ? selected.length === 0 : selected.includes(option);
|
|
return (
|
|
<Pressable
|
|
key={option}
|
|
style={[styles.optionChip, active && styles.optionChipActive]}
|
|
onPress={() => (isNone ? onClear() : onToggle(option))}
|
|
>
|
|
<Text style={[styles.optionText, active && styles.optionTextActive]}>{option}</Text>
|
|
</Pressable>
|
|
);
|
|
})}
|
|
</View>
|
|
</>
|
|
);
|
|
}
|
|
|
|
/** 单张岗位卡片(整卡可点击进入详情) */
|
|
function JobCard({ job }: { job: Job }) {
|
|
const router = useRouter();
|
|
return (
|
|
<Pressable
|
|
style={({ pressed }) => [styles.card, pressed && styles.cardPressed]}
|
|
onPress={() => router.push(`/jobs/${job.id}`)}
|
|
>
|
|
{/* 公司名 + 地点 */}
|
|
<View style={styles.cardHeader}>
|
|
<Text style={styles.companyName}>{job.companyName}</Text>
|
|
<Text style={styles.companyLocation}>{job.companyLocation}</Text>
|
|
</View>
|
|
|
|
{/* 岗位名 + 薪资 */}
|
|
<View style={styles.positionRow}>
|
|
<Text style={styles.positionName}>{job.positionName}</Text>
|
|
<Text style={styles.salary}>
|
|
{job.salaryMin}-{job.salaryMax}K
|
|
</Text>
|
|
</View>
|
|
|
|
{/* 评分标签 + 查看详情 */}
|
|
<View style={styles.scoreRow}>
|
|
<Text style={[styles.scoreText, { color: LEVEL_COLOR[job.level] }]}>
|
|
{job.score}分 · {JOB_LEVEL_LABEL[job.level]}
|
|
</Text>
|
|
<Text style={styles.detailLink}>查看详情 ></Text>
|
|
</View>
|
|
|
|
{/* 福利标签 */}
|
|
<View style={styles.benefits}>
|
|
{[job.experience, job.education, ...job.benefits].map((item) => (
|
|
<View key={item} style={styles.benefitTag}>
|
|
<Text style={styles.benefitText}>{item}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</Pressable>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 岗位列表页
|
|
*/
|
|
export default function JobsScreen() {
|
|
const router = useRouter();
|
|
const insets = useSafeAreaInsets();
|
|
// 定位城市(点击可切换)
|
|
const [city, setCity] = useState('北京');
|
|
const [cityVisible, setCityVisible] = useState(false);
|
|
const [keyword, setKeyword] = useState('');
|
|
const [sort, setSort] = useState<SortType>('comprehensive');
|
|
// 更多筛选:已应用的筛选条件(多选,空数组 = 不限)
|
|
const [filters, setFilters] = useState<Record<FilterKey, string[]>>({
|
|
salary: [],
|
|
experience: [],
|
|
education: [],
|
|
});
|
|
// 筛选面板内的临时选择(点「确定」后才生效)
|
|
const [draft, setDraft] = useState<Record<FilterKey, string[]>>(filters);
|
|
const [filterVisible, setFilterVisible] = useState(false);
|
|
const { jobs, total, loading, error, refresh, isEmpty } = useJobList({
|
|
sortBy: sort,
|
|
city,
|
|
salary: filters.salary,
|
|
experience: filters.experience,
|
|
education: filters.education,
|
|
});
|
|
|
|
// 已勾选的筛选项总数(用于「更多筛选」角标)
|
|
const activeCount =
|
|
filters.salary.length + filters.experience.length + filters.education.length;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* 顶部渐变区:状态栏 + 导航 + 搜索 */}
|
|
<LinearGradient
|
|
colors={headerGradient}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={[styles.headerGradient, { paddingTop: insets.top }]}
|
|
>
|
|
{/* 顶部导航:定位城市 + 标题 + 评分权重设置 */}
|
|
<View style={styles.nav}>
|
|
<View style={styles.navSide}>
|
|
<Pressable style={styles.locationBtn} onPress={() => setCityVisible(true)}>
|
|
<Ionicons name="location-outline" size={18} color={colors.white} />
|
|
<Text style={styles.locationText}>{city}</Text>
|
|
<Ionicons name="chevron-down" size={14} color={colors.white} />
|
|
</Pressable>
|
|
</View>
|
|
<Text style={styles.navTitle}>找工作</Text>
|
|
<View style={[styles.navSide, { justifyContent: 'flex-end' }]}>
|
|
<Pressable
|
|
style={styles.navIconBtn}
|
|
onPress={() => {
|
|
router.push('/jobs/weight-settings');
|
|
}}
|
|
hitSlop={6}
|
|
>
|
|
<Ionicons name="options-outline" size={22} color={colors.white} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 搜索框 */}
|
|
<View style={styles.searchBox}>
|
|
<Ionicons name="search" size={18} color={colors.textSecondary} />
|
|
<TextInput
|
|
style={styles.searchInput}
|
|
placeholder="搜索职位、公司、关键词"
|
|
placeholderTextColor={colors.textSecondary}
|
|
value={keyword}
|
|
onChangeText={setKeyword}
|
|
/>
|
|
</View>
|
|
</LinearGradient>
|
|
|
|
{/* 筛选条件栏(横向滚动) */}
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
style={styles.filterBar}
|
|
contentContainerStyle={styles.filterContent}
|
|
>
|
|
{FILTERS.map((filter) => (
|
|
<Pressable
|
|
key={filter}
|
|
style={styles.filterChip}
|
|
onPress={() => {
|
|
if (filter === '更多筛选') {
|
|
setDraft(filters);
|
|
setFilterVisible(true);
|
|
}
|
|
/* 其余筛选(行业/城市/薪资/经验)留待后续接入 */
|
|
}}
|
|
>
|
|
<Text style={styles.filterText}>{filter}</Text>
|
|
<Ionicons
|
|
name={filter === '更多筛选' ? 'options-outline' : 'chevron-down'}
|
|
size={14}
|
|
color={colors.textSecondary}
|
|
/>
|
|
{filter === '更多筛选' && activeCount > 0 && (
|
|
<View style={styles.filterBadge}>
|
|
<Text style={styles.filterBadgeText}>{activeCount}</Text>
|
|
</View>
|
|
)}
|
|
</Pressable>
|
|
))}
|
|
</ScrollView>
|
|
|
|
{/* 结果统计 + 排序 + 岗位对比 */}
|
|
<View style={styles.resultRow}>
|
|
<Text style={styles.resultCount}>共找到 {total} 个岗位</Text>
|
|
<View style={styles.sortRow}>
|
|
{SORTS.map((option) => (
|
|
<Pressable key={option.key} onPress={() => setSort(option.key)}>
|
|
<Text style={[styles.sortText, sort === option.key && styles.sortTextActive]}>
|
|
{option.label}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
<View style={styles.sortDivider} />
|
|
<Pressable
|
|
style={styles.compareBtn}
|
|
onPress={() => {
|
|
router.push('/jobs/compare');
|
|
}}
|
|
hitSlop={6}
|
|
>
|
|
<Ionicons name="git-compare-outline" size={15} color={colors.primaryBtn} />
|
|
<Text style={styles.compareBtnText}>对比</Text>
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
|
|
{/* 岗位列表:统一处理加载 / 错误 / 空 / 成功四态 */}
|
|
{loading ? (
|
|
<View style={styles.stateBox}>
|
|
<ActivityIndicator size="large" color={colors.textSecondary} />
|
|
</View>
|
|
) : error ? (
|
|
<View style={styles.stateBox}>
|
|
<Text style={styles.stateText}>加载失败,请重试</Text>
|
|
<Pressable style={styles.retryBtn} onPress={refresh}>
|
|
<Text style={styles.retryText}>重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
) : isEmpty ? (
|
|
<View style={styles.stateBox}>
|
|
<Ionicons name="file-tray-outline" size={48} color={colors.textSecondary} />
|
|
<Text style={styles.stateText}>暂无相关岗位</Text>
|
|
</View>
|
|
) : (
|
|
<ScrollView
|
|
style={styles.list}
|
|
contentContainerStyle={styles.listContent}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{jobs.map((job) => (
|
|
<JobCard key={job.id} job={job} />
|
|
))}
|
|
</ScrollView>
|
|
)}
|
|
|
|
{/* 更多筛选面板(底部弹层) */}
|
|
<Modal
|
|
visible={filterVisible}
|
|
transparent
|
|
animationType="slide"
|
|
onRequestClose={() => setFilterVisible(false)}
|
|
>
|
|
<Pressable style={styles.modalOverlay} onPress={() => setFilterVisible(false)}>
|
|
<Pressable style={styles.filterPanel} onPress={() => {}}>
|
|
<View style={styles.panelHeader}>
|
|
<Text style={styles.panelTitle}>更多筛选</Text>
|
|
<Pressable onPress={() => setFilterVisible(false)}>
|
|
<Ionicons name="close" size={22} color={colors.textSecondary} />
|
|
</Pressable>
|
|
</View>
|
|
|
|
{/* 薪资范围 */}
|
|
<FilterGroup
|
|
title="薪资范围"
|
|
options={SALARY_RANGES.map((range) => range.label)}
|
|
selected={draft.salary}
|
|
onToggle={(value) => setDraft((d) => toggleValue(d, 'salary', value))}
|
|
onClear={() => setDraft((d) => clearGroup(d, 'salary'))}
|
|
/>
|
|
|
|
{/* 经验要求 */}
|
|
<FilterGroup
|
|
title="经验要求"
|
|
options={EXPERIENCE_OPTIONS}
|
|
selected={draft.experience}
|
|
onToggle={(value) => setDraft((d) => toggleValue(d, 'experience', value))}
|
|
onClear={() => setDraft((d) => clearGroup(d, 'experience'))}
|
|
/>
|
|
|
|
{/* 学历要求 */}
|
|
<FilterGroup
|
|
title="学历要求"
|
|
options={EDUCATION_OPTIONS}
|
|
selected={draft.education}
|
|
onToggle={(value) => setDraft((d) => toggleValue(d, 'education', value))}
|
|
onClear={() => setDraft((d) => clearGroup(d, 'education'))}
|
|
/>
|
|
|
|
{/* 底部操作 */}
|
|
<View style={styles.panelActions}>
|
|
<Pressable
|
|
style={styles.resetBtn}
|
|
onPress={() => setDraft({ salary: [], experience: [], education: [] })}
|
|
>
|
|
<Text style={styles.resetText}>重置</Text>
|
|
</Pressable>
|
|
<Pressable
|
|
style={styles.confirmBtn}
|
|
onPress={() => {
|
|
setFilters(draft);
|
|
setFilterVisible(false);
|
|
}}
|
|
>
|
|
<Text style={styles.confirmText}>确定</Text>
|
|
</Pressable>
|
|
</View>
|
|
</Pressable>
|
|
</Pressable>
|
|
</Modal>
|
|
|
|
{/* 城市选择弹层(底部弹层) */}
|
|
<Modal
|
|
visible={cityVisible}
|
|
transparent
|
|
animationType="slide"
|
|
onRequestClose={() => setCityVisible(false)}
|
|
>
|
|
<Pressable style={styles.modalOverlay} onPress={() => setCityVisible(false)}>
|
|
<Pressable style={styles.filterPanel} onPress={() => {}}>
|
|
<View style={styles.panelHeader}>
|
|
<Text style={styles.panelTitle}>选择城市</Text>
|
|
<Pressable onPress={() => setCityVisible(false)}>
|
|
<Ionicons name="close" size={22} color={colors.textSecondary} />
|
|
</Pressable>
|
|
</View>
|
|
<View style={styles.cityGrid}>
|
|
{CITIES.map((item) => (
|
|
<Pressable
|
|
key={item}
|
|
style={[styles.optionChip, city === item && styles.optionChipActive]}
|
|
onPress={() => {
|
|
setCity(item);
|
|
setCityVisible(false);
|
|
}}
|
|
>
|
|
<Text style={[styles.optionText, city === item && styles.optionTextActive]}>
|
|
{item}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
</Pressable>
|
|
</Pressable>
|
|
</Modal>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: colors.pageBg,
|
|
},
|
|
headerGradient: {
|
|
paddingBottom: 16,
|
|
},
|
|
nav: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 8,
|
|
paddingTop: 4,
|
|
paddingBottom: 8,
|
|
},
|
|
navSide: {
|
|
flex: 1,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
locationBtn: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 2,
|
|
},
|
|
locationText: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
color: colors.white,
|
|
},
|
|
navTitle: {
|
|
fontSize: 18,
|
|
fontWeight: '700',
|
|
color: colors.white,
|
|
},
|
|
searchBox: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
height: 40,
|
|
borderRadius: 20,
|
|
backgroundColor: colors.white,
|
|
paddingHorizontal: 14,
|
|
marginHorizontal: 8,
|
|
marginTop: 4,
|
|
shadowColor: colors.shadow,
|
|
shadowOpacity: 0.06,
|
|
shadowRadius: 8,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
elevation: 2,
|
|
},
|
|
searchInput: {
|
|
flex: 1,
|
|
marginLeft: 8,
|
|
fontSize: 14,
|
|
color: colors.textPrimary,
|
|
},
|
|
filterBar: {
|
|
flexGrow: 0,
|
|
height: 32,
|
|
marginTop: 12,
|
|
},
|
|
filterContent: {
|
|
paddingHorizontal: 8,
|
|
gap: 12,
|
|
},
|
|
filterChip: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
height: 32,
|
|
paddingHorizontal: 12,
|
|
borderRadius: 16,
|
|
backgroundColor: colors.white,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
},
|
|
filterText: {
|
|
marginRight: 4,
|
|
fontSize: 13,
|
|
color: colors.textPrimary,
|
|
},
|
|
filterBadge: {
|
|
marginLeft: 4,
|
|
minWidth: 16,
|
|
height: 16,
|
|
borderRadius: 8,
|
|
paddingHorizontal: 4,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.textPrimary,
|
|
},
|
|
filterBadgeText: {
|
|
fontSize: 10,
|
|
fontWeight: '700',
|
|
color: colors.white,
|
|
},
|
|
resultRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
|
|
paddingHorizontal: 8,
|
|
marginTop: 16,
|
|
marginBottom: 12,
|
|
},
|
|
resultCount: {
|
|
fontSize: 12,
|
|
color: colors.textSecondary,
|
|
},
|
|
sortRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 12,
|
|
},
|
|
sortText: {
|
|
fontSize: 13,
|
|
color: colors.textSecondary,
|
|
},
|
|
sortTextActive: {
|
|
color: colors.textPrimary,
|
|
fontWeight: '700',
|
|
},
|
|
sortDivider: {
|
|
width: StyleSheet.hairlineWidth,
|
|
height: 12,
|
|
backgroundColor: colors.border,
|
|
marginHorizontal: 2,
|
|
},
|
|
compareBtn: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 12,
|
|
backgroundColor: colors.primaryLight,
|
|
gap: 3,
|
|
},
|
|
compareBtnText: {
|
|
fontSize: 12,
|
|
fontWeight: '600',
|
|
color: colors.primaryText,
|
|
},
|
|
navIconBtn: {
|
|
width: 36,
|
|
height: 36,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 18,
|
|
backgroundColor: 'rgba(255,255,255,0.15)',
|
|
},
|
|
stateBox: {
|
|
flex: 1,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
stateText: {
|
|
marginTop: 12,
|
|
fontSize: 14,
|
|
color: colors.textSecondary,
|
|
},
|
|
retryBtn: {
|
|
marginTop: 16,
|
|
paddingHorizontal: 24,
|
|
paddingVertical: 8,
|
|
borderRadius: 18,
|
|
backgroundColor: colors.primaryBtn,
|
|
},
|
|
retryText: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
color: colors.white,
|
|
},
|
|
list: {
|
|
flex: 1,
|
|
},
|
|
listContent: {
|
|
paddingHorizontal: 8,
|
|
paddingBottom: 24,
|
|
},
|
|
card: {
|
|
backgroundColor: colors.white,
|
|
borderRadius: 8,
|
|
padding: 16,
|
|
marginBottom: 8,
|
|
},
|
|
cardPressed: {
|
|
opacity: 0.85,
|
|
},
|
|
cardHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
},
|
|
companyName: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
},
|
|
companyLocation: {
|
|
fontSize: 12,
|
|
color: colors.textSecondary,
|
|
},
|
|
positionRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginTop: 8,
|
|
},
|
|
positionName: {
|
|
fontSize: 15,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
},
|
|
salary: {
|
|
fontSize: 15,
|
|
fontWeight: '700',
|
|
color: colors.primary,
|
|
},
|
|
scoreRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginTop: 10,
|
|
},
|
|
scoreText: {
|
|
fontSize: 13,
|
|
fontWeight: '600',
|
|
},
|
|
detailLink: {
|
|
fontSize: 13,
|
|
color: colors.primary,
|
|
},
|
|
benefits: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
marginTop: 12,
|
|
},
|
|
benefitTag: {
|
|
backgroundColor: colors.primaryLight,
|
|
borderWidth: 1,
|
|
borderColor: colors.primaryBorder,
|
|
borderRadius: 12,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
marginRight: 8,
|
|
marginBottom: 8,
|
|
},
|
|
benefitText: {
|
|
fontSize: 12,
|
|
color: colors.primary,
|
|
},
|
|
/* ---- 更多筛选面板 ---- */
|
|
modalOverlay: {
|
|
flex: 1,
|
|
backgroundColor: colors.scrim,
|
|
justifyContent: 'flex-end',
|
|
},
|
|
filterPanel: {
|
|
backgroundColor: colors.white,
|
|
borderTopLeftRadius: 20,
|
|
borderTopRightRadius: 20,
|
|
paddingHorizontal: 20,
|
|
paddingTop: 16,
|
|
paddingBottom: 24,
|
|
},
|
|
panelHeader: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
marginBottom: 4,
|
|
},
|
|
panelTitle: {
|
|
fontSize: 17,
|
|
fontWeight: '700',
|
|
color: colors.textPrimary,
|
|
},
|
|
filterLabel: {
|
|
marginTop: 16,
|
|
marginBottom: 10,
|
|
fontSize: 14,
|
|
fontWeight: '600',
|
|
color: colors.textPrimary,
|
|
},
|
|
optionRow: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 10,
|
|
},
|
|
cityGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 12,
|
|
marginTop: 16,
|
|
},
|
|
optionChip: {
|
|
paddingHorizontal: 14,
|
|
paddingVertical: 8,
|
|
borderRadius: 18,
|
|
backgroundColor: colors.pageBg,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
},
|
|
optionChipActive: {
|
|
backgroundColor: colors.surface,
|
|
borderColor: colors.borderStrong,
|
|
},
|
|
optionText: {
|
|
fontSize: 13,
|
|
color: colors.textPrimary,
|
|
},
|
|
optionTextActive: {
|
|
color: colors.textPrimary,
|
|
fontWeight: '700',
|
|
},
|
|
panelActions: {
|
|
flexDirection: 'row',
|
|
gap: 12,
|
|
marginTop: 24,
|
|
},
|
|
resetBtn: {
|
|
flex: 1,
|
|
height: 44,
|
|
borderRadius: 22,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.pageBg,
|
|
borderWidth: 1,
|
|
borderColor: colors.border,
|
|
},
|
|
resetText: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
color: colors.textSecondary,
|
|
},
|
|
confirmBtn: {
|
|
flex: 1,
|
|
height: 44,
|
|
borderRadius: 22,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.primaryBtn,
|
|
},
|
|
confirmText: {
|
|
fontSize: 15,
|
|
fontWeight: '600',
|
|
color: colors.white,
|
|
},
|
|
});
|
|
|