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.
285 lines
9.5 KiB
285 lines
9.5 KiB
/**
|
|
* 评分权重设置页:调整九大评价维度权重,支持套用预设模板。
|
|
* 数据通过 useWeightSettings 获取,页面只负责交互与渲染。
|
|
*/
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useRouter } from 'expo-router';
|
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
import { useWeightSettings } from '../../src/hooks';
|
|
import {colors, spacing} from '../../src/theme';
|
|
|
|
const MIN_WEIGHT = 0;
|
|
const MAX_WEIGHT = 100;
|
|
const STEP = 1;
|
|
|
|
function Header({ onBack }: { onBack: () => void }) {
|
|
return (
|
|
<View style={styles.header}>
|
|
<Pressable onPress={onBack} hitSlop={8} style={styles.headerBtn}>
|
|
<Ionicons name="chevron-back" size={24} color={colors.textPrimary} />
|
|
</Pressable>
|
|
<Text style={styles.headerTitle}>评分权重设置</Text>
|
|
<View style={styles.headerBtn} />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function WeightSettingsScreen() {
|
|
const router = useRouter();
|
|
const {
|
|
settings,
|
|
dimensions,
|
|
totalWeight,
|
|
loading,
|
|
error,
|
|
saving,
|
|
refresh,
|
|
updateWeight,
|
|
applyTemplate,
|
|
save,
|
|
} = useWeightSettings();
|
|
|
|
const isValid = totalWeight === 100;
|
|
|
|
// 错误态
|
|
if (error) {
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.stateWrap}>
|
|
<Text style={styles.stateText}>加载失败,请重试</Text>
|
|
<Pressable style={styles.retryBtn} onPress={refresh}>
|
|
<Text style={styles.retryText}>重试</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
// 加载态
|
|
if (loading) {
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<View style={styles.stateWrap}>
|
|
<Text style={styles.stateText}>加载中…</Text>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={styles.safeArea} edges={['top']}>
|
|
<Header onBack={() => router.back()} />
|
|
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
|
|
<View style={[styles.summaryCard, !isValid && styles.summaryCardWarn]}>
|
|
<View>
|
|
<Text style={styles.summaryLabel}>权重总和</Text>
|
|
<Text style={[styles.summaryValue, !isValid && styles.summaryValueWarn]}>
|
|
{totalWeight}%
|
|
</Text>
|
|
</View>
|
|
<Text style={styles.summaryHint}>
|
|
{isValid ? '配置合理,可直接保存' : '各维度权重之和需等于 100%'}
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.sectionHeader}>
|
|
<View style={styles.sectionBar} />
|
|
<Text style={styles.sectionTitle}>预设模板</Text>
|
|
</View>
|
|
<ScrollView
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={styles.templateRow}
|
|
>
|
|
{(settings?.templates ?? []).map((template) => (
|
|
<Pressable
|
|
key={template.id}
|
|
style={styles.templateCard}
|
|
onPress={() => applyTemplate(template.id)}
|
|
>
|
|
<Text style={styles.templateName}>{template.name}</Text>
|
|
<Text style={styles.templateDesc}>{template.description}</Text>
|
|
</Pressable>
|
|
))}
|
|
</ScrollView>
|
|
|
|
<View style={styles.sectionHeader}>
|
|
<View style={styles.sectionBar} />
|
|
<Text style={styles.sectionTitle}>评价维度</Text>
|
|
</View>
|
|
<View style={styles.dimensionCard}>
|
|
{dimensions.map((item, index) => {
|
|
const isLast = index === dimensions.length - 1;
|
|
return (
|
|
<View key={item.id} style={[styles.dimRow, isLast && styles.dimRowLast]}>
|
|
<View style={styles.dimHead}>
|
|
<Text style={styles.dimName}>{item.name}</Text>
|
|
{item.isReversed ? (
|
|
<View style={styles.reversedTag}>
|
|
<Text style={styles.reversedText}>反向指标</Text>
|
|
</View>
|
|
) : null}
|
|
<Text style={styles.dimValue}>{item.weight}%</Text>
|
|
</View>
|
|
<View style={styles.dimTrack}>
|
|
<View
|
|
style={[
|
|
styles.dimFill,
|
|
{ width: `${item.weight}%`, backgroundColor: colors.primaryBtn },
|
|
]}
|
|
/>
|
|
</View>
|
|
<View style={styles.dimActions}>
|
|
<Pressable
|
|
style={styles.stepBtn}
|
|
onPress={() =>
|
|
updateWeight(item.id, Math.max(MIN_WEIGHT, item.weight - STEP))
|
|
}
|
|
>
|
|
<Ionicons name="remove" size={16} color={colors.primaryText} />
|
|
</Pressable>
|
|
<Pressable
|
|
style={styles.stepBtn}
|
|
onPress={() =>
|
|
updateWeight(item.id, Math.min(MAX_WEIGHT, item.weight + STEP))
|
|
}
|
|
>
|
|
<Ionicons name="add" size={16} color={colors.primaryText} />
|
|
</Pressable>
|
|
</View>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
</ScrollView>
|
|
|
|
<View style={styles.footer}>
|
|
<Pressable
|
|
style={[styles.saveBtn, (!isValid || saving) && styles.saveBtnDisabled]}
|
|
disabled={!isValid || saving}
|
|
onPress={save}
|
|
>
|
|
<Text style={styles.saveText}>{saving ? '保存中…' : '保存设置'}</Text>
|
|
</Pressable>
|
|
</View>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
safeArea: { flex: 1, backgroundColor: colors.pageBg },
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: spacing.framework,
|
|
paddingVertical: 12,
|
|
backgroundColor: colors.surface,
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: colors.borderLight,
|
|
},
|
|
headerBtn: { width: 32, alignItems: 'center' },
|
|
headerTitle: { fontSize: 17, fontWeight: '500', color: colors.textPrimary },
|
|
content: { padding: 8, paddingBottom: 24 },
|
|
stateWrap: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 12 },
|
|
stateText: { fontSize: 14, color: colors.textSecondary },
|
|
retryBtn: {
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 8,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.primaryLight,
|
|
},
|
|
retryText: { fontSize: 14, color: colors.primaryText },
|
|
summaryCard: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
padding: 8,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.primaryLight,
|
|
},
|
|
summaryCardWarn: { backgroundColor: colors.dangerLight },
|
|
summaryLabel: { fontSize: 12, color: colors.textSecondary, marginBottom: 4 },
|
|
summaryValue: { fontSize: 24, fontWeight: '500', color: colors.primaryText },
|
|
summaryValueWarn: { color: colors.dangerText },
|
|
summaryHint: { flex: 1, textAlign: 'right', fontSize: 12, color: colors.textSecondary },
|
|
sectionHeader: { flexDirection: 'row', alignItems: 'center', marginTop: 20, marginBottom: 12 },
|
|
sectionBar: {
|
|
width: 3,
|
|
height: 14,
|
|
borderRadius: 2,
|
|
backgroundColor: colors.primaryBtn,
|
|
marginRight: 8,
|
|
},
|
|
sectionTitle: { fontSize: 15, fontWeight: '500', color: colors.textPrimary },
|
|
templateRow: { gap: 8, paddingRight: 16 },
|
|
templateCard: {
|
|
width: 140,
|
|
padding: 8,
|
|
borderRadius: 8,
|
|
backgroundColor: colors.surface,
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: colors.borderLight,
|
|
},
|
|
templateName: { fontSize: 14, fontWeight: '500', color: colors.textPrimary, marginBottom: 4 },
|
|
templateDesc: { fontSize: 11, color: colors.textSecondary },
|
|
dimensionCard: {
|
|
borderRadius: 8,
|
|
backgroundColor: colors.surface,
|
|
borderWidth: StyleSheet.hairlineWidth,
|
|
borderColor: colors.borderLight,
|
|
paddingHorizontal: 8,
|
|
},
|
|
dimRow: {
|
|
paddingVertical: 14,
|
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
borderBottomColor: colors.borderLight,
|
|
},
|
|
dimRowLast: { borderBottomWidth: 0 },
|
|
dimHead: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 },
|
|
dimName: { fontSize: 14, color: colors.textPrimary, marginRight: 6 },
|
|
reversedTag: {
|
|
paddingHorizontal: 6,
|
|
paddingVertical: 1,
|
|
borderRadius: 4,
|
|
backgroundColor: colors.infoLight,
|
|
marginRight: 6,
|
|
},
|
|
reversedText: { fontSize: 10, color: colors.infoText },
|
|
dimValue: { marginLeft: 'auto', fontSize: 14, fontWeight: '500', color: colors.primaryText },
|
|
dimTrack: {
|
|
height: 6,
|
|
borderRadius: 3,
|
|
backgroundColor: colors.borderLight,
|
|
overflow: 'hidden',
|
|
marginBottom: 10,
|
|
},
|
|
dimFill: { height: '100%', borderRadius: 3 },
|
|
dimActions: { flexDirection: 'row', gap: 8 },
|
|
stepBtn: {
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: 6,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.primaryLight,
|
|
},
|
|
footer: {
|
|
padding: spacing.framework,
|
|
backgroundColor: colors.surface,
|
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
borderTopColor: colors.borderLight,
|
|
},
|
|
saveBtn: {
|
|
height: 46,
|
|
borderRadius: 10,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
backgroundColor: colors.primaryBtn,
|
|
},
|
|
saveBtnDisabled: { backgroundColor: colors.border },
|
|
saveText: { fontSize: 15, fontWeight: '500', color: colors.white },
|
|
});
|
|
|