通话逻辑调整

This commit is contained in:
2025-06-29 01:33:41 +08:00
parent deb2900acc
commit 48d22a1e94
37 changed files with 9242 additions and 238 deletions
+580
View File
@@ -0,0 +1,580 @@
import { FC, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { AppointmentService } from '../../services/appointmentService';
import { BillingService } from '../../services/billingService';
import {
Appointment,
Interpreter,
CallType,
TranslationType,
UserAccount
} from '../../types/billing';
const MobileAppointment: FC = () => {
const navigate = useNavigate();
const appointmentService = AppointmentService.getInstance();
const billingService = BillingService.getInstance();
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [selectedDate, setSelectedDate] = useState<string>('');
const [selectedTime, setSelectedTime] = useState<string>('');
const [callType, setCallType] = useState<CallType>(CallType.VOICE);
const [translationType, setTranslationType] = useState<TranslationType>(TranslationType.TEXT);
const [selectedLanguages, setSelectedLanguages] = useState({
from: 'zh-CN',
to: 'en-US',
});
const [selectedInterpreter, setSelectedInterpreter] = useState<Interpreter | null>(null);
const [availableInterpreters, setAvailableInterpreters] = useState<Interpreter[]>([]);
const [description, setDescription] = useState('');
const [estimatedCost, setEstimatedCost] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const languages = [
{ code: 'zh-CN', name: '中文', flag: '🇨🇳' },
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
{ code: 'ko-KR', name: '한국어', flag: '🇰🇷' },
{ code: 'es-ES', name: 'Español', flag: '🇪🇸' },
{ code: 'fr-FR', name: 'Français', flag: '🇫🇷' },
{ code: 'de-DE', name: 'Deutsch', flag: '🇩🇪' },
];
// 生成可选时间段
const timeSlots = [
'09:00', '09:30', '10:00', '10:30', '11:00', '11:30',
'14:00', '14:30', '15:00', '15:30', '16:00', '16:30',
'17:00', '17:30', '18:00', '18:30', '19:00', '19:30',
'20:00', '20:30', '21:00'
];
// 生成未来7天的日期选项
const getDateOptions = () => {
const dates = [];
for (let i = 0; i < 7; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
dates.push({
value: date.toISOString().split('T')[0],
label: i === 0 ? '今天' : i === 1 ? '明天' :
`${date.getMonth() + 1}${date.getDate()}`,
weekday: date.toLocaleDateString('zh-CN', { weekday: 'short' })
});
}
return dates;
};
useEffect(() => {
const account = billingService.getUserAccount();
setUserAccount(account);
}, []);
useEffect(() => {
if (selectedDate) {
const date = new Date(selectedDate);
const interpreters = appointmentService.getAvailableInterpreters(
date,
[selectedLanguages.from, selectedLanguages.to]
);
setAvailableInterpreters(interpreters);
}
}, [selectedDate, selectedLanguages]);
useEffect(() => {
// 计算预估费用
if (callType && translationType && userAccount) {
const interpreterRate = selectedInterpreter?.pricePerMinute;
const baseCost = billingService.calculateCallCost(
callType,
translationType,
30, // 假设30分钟
interpreterRate
);
setEstimatedCost(baseCost);
}
}, [callType, translationType, selectedInterpreter, userAccount]);
useEffect(() => {
// 根据通话类型设置默认翻译类型
if (callType === CallType.VIDEO) {
setTranslationType(TranslationType.SIGN_LANGUAGE);
} else {
setTranslationType(TranslationType.TEXT);
}
}, [callType]);
const formatCurrency = (cents: number) => {
return (cents / 100).toFixed(2);
};
const handleSubmit = async () => {
if (!selectedDate || !selectedTime || !callType || !translationType) {
alert('请完善预约信息');
return;
}
if (!userAccount || userAccount.balance < estimatedCost) {
alert('账户余额不足,请先充值');
navigate('/mobile/recharge');
return;
}
setIsSubmitting(true);
try {
// 构建预约数据
const appointmentData = {
userId: 'user_1', // 实际应该从用户状态获取
title: `${callType === CallType.VOICE ? '语音' : '视频'}通话预约`,
description: `${translationType === TranslationType.TEXT ? '文本翻译' :
translationType === TranslationType.SIGN_LANGUAGE ? '手语翻译' : '人工翻译'}`,
scheduledTime: new Date(`${selectedDate}T${selectedTime}`),
duration: 60, // 默认60分钟
callType,
translationType,
interpreterIds: selectedInterpreter ? [selectedInterpreter.id] : undefined,
estimatedCost,
status: 'scheduled' as const,
};
// 创建预约
const appointment = appointmentService.createAppointment(appointmentData);
alert('预约创建成功!');
navigate('/mobile/home');
} catch (error) {
console.error('创建预约失败:', error);
alert('创建预约失败,请重试');
} finally {
setIsSubmitting(false);
}
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100vh',
},
header: {
display: 'flex',
alignItems: 'center',
marginBottom: '20px',
},
backButton: {
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
marginRight: '12px',
},
title: {
fontSize: '20px',
fontWeight: '600' as const,
color: '#333',
},
section: {
backgroundColor: 'white',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
sectionTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
},
callTypeSelector: {
display: 'flex',
gap: '12px',
marginBottom: '16px',
},
callTypeButton: (active: boolean) => ({
flex: 1,
padding: '16px',
borderRadius: '12px',
border: active ? '2px solid #1890ff' : '2px solid #f0f0f0',
backgroundColor: active ? '#e6f7ff' : 'white',
color: active ? '#1890ff' : '#666',
fontSize: '16px',
fontWeight: '500' as const,
cursor: 'pointer',
textAlign: 'center' as const,
}),
dateGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '12px',
marginBottom: '16px',
},
dateOption: (selected: boolean) => ({
padding: '16px',
borderRadius: '12px',
border: selected ? '2px solid #1890ff' : '2px solid #f0f0f0',
backgroundColor: selected ? '#e6f7ff' : 'white',
cursor: 'pointer',
textAlign: 'center' as const,
}),
dateLabel: {
fontSize: '16px',
fontWeight: '500' as const,
color: '#333',
marginBottom: '4px',
},
dateWeekday: {
fontSize: '12px',
color: '#999',
},
timeGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(3, 1fr)',
gap: '8px',
},
timeSlot: (selected: boolean, available: boolean) => ({
padding: '12px 8px',
borderRadius: '8px',
border: selected ? '2px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: selected ? '#e6f7ff' : available ? 'white' : '#f5f5f5',
color: selected ? '#1890ff' : available ? '#333' : '#999',
fontSize: '14px',
textAlign: 'center' as const,
cursor: available ? 'pointer' : 'not-allowed',
}),
languageRow: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '16px',
},
languageSelect: {
flex: 1,
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
backgroundColor: 'white',
fontSize: '14px',
},
swapButton: {
margin: '0 12px',
padding: '8px',
borderRadius: '20px',
border: 'none',
backgroundColor: '#f0f8ff',
cursor: 'pointer',
fontSize: '16px',
},
translationTypeSelector: {
display: 'flex',
flexDirection: 'column' as const,
gap: '12px',
marginBottom: '16px',
},
translationTypeButton: (active: boolean) => ({
padding: '16px',
borderRadius: '12px',
border: active ? '2px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: active ? '#e6f7ff' : 'white',
color: active ? '#1890ff' : '#666',
fontSize: '14px',
cursor: 'pointer',
textAlign: 'left' as const,
}),
interpreterList: {
marginTop: '16px',
},
interpreterItem: (selected: boolean) => ({
display: 'flex',
alignItems: 'center',
padding: '16px',
borderRadius: '12px',
border: selected ? '2px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: selected ? '#e6f7ff' : 'white',
marginBottom: '12px',
cursor: 'pointer',
}),
interpreterAvatar: {
fontSize: '32px',
marginRight: '16px',
},
interpreterInfo: {
flex: 1,
},
interpreterName: {
fontSize: '16px',
fontWeight: '500' as const,
color: '#333',
marginBottom: '4px',
},
interpreterDetails: {
fontSize: '12px',
color: '#666',
marginBottom: '4px',
},
interpreterRate: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#fa8c16',
},
descriptionInput: {
width: '100%',
minHeight: '80px',
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
fontSize: '14px',
resize: 'vertical' as const,
},
costSummary: {
backgroundColor: '#f8f9fa',
borderRadius: '12px',
padding: '16px',
marginBottom: '20px',
},
costRow: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '8px',
},
costLabel: {
fontSize: '14px',
color: '#666',
},
costValue: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#1890ff',
},
submitButton: {
width: '100%',
padding: '16px',
borderRadius: '12px',
border: 'none',
backgroundColor: selectedDate && selectedTime && !isSubmitting ? '#1890ff' : '#d9d9d9',
color: 'white',
fontSize: '16px',
fontWeight: '600' as const,
cursor: selectedDate && selectedTime && !isSubmitting ? 'pointer' : 'not-allowed',
},
};
return (
<div style={styles.container}>
{/* 头部 */}
<div style={styles.header}>
<button style={styles.backButton} onClick={() => navigate(-1)}>
</button>
<div style={styles.title}></div>
</div>
{/* 通话类型选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.callTypeSelector}>
<button
style={styles.callTypeButton(callType === CallType.VOICE)}
onClick={() => setCallType(CallType.VOICE)}
>
📞
</button>
<button
style={styles.callTypeButton(callType === CallType.VIDEO)}
onClick={() => setCallType(CallType.VIDEO)}
>
📹
</button>
</div>
</div>
{/* 日期选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.dateGrid}>
{getDateOptions().map((date) => (
<div
key={date.value}
style={styles.dateOption(selectedDate === date.value)}
onClick={() => setSelectedDate(date.value)}
>
<div style={styles.dateLabel}>{date.label}</div>
<div style={styles.dateWeekday}>{date.weekday}</div>
</div>
))}
</div>
</div>
{/* 时间选择 */}
{selectedDate && (
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.timeGrid}>
{timeSlots.map((time) => {
const isAvailable = appointmentService.isTimeSlotAvailable(
new Date(`${selectedDate}T${time}:00`),
30
);
return (
<button
key={time}
style={styles.timeSlot(selectedTime === time, isAvailable)}
onClick={() => isAvailable && setSelectedTime(time)}
disabled={!isAvailable}
>
{time}
</button>
);
})}
</div>
</div>
)}
{/* 语言选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.languageRow}>
<select
style={styles.languageSelect}
value={selectedLanguages.from}
onChange={(e) => setSelectedLanguages({...selectedLanguages, from: e.target.value})}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
<button
style={styles.swapButton}
onClick={() => setSelectedLanguages({
from: selectedLanguages.to,
to: selectedLanguages.from
})}
>
🔄
</button>
<select
style={styles.languageSelect}
value={selectedLanguages.to}
onChange={(e) => setSelectedLanguages({...selectedLanguages, to: e.target.value})}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
</div>
</div>
{/* 翻译服务选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.translationTypeSelector}>
{callType === CallType.VOICE && (
<button
style={styles.translationTypeButton(translationType === TranslationType.TEXT)}
onClick={() => setTranslationType(TranslationType.TEXT)}
>
<div>💬 </div>
<div style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
AI自动翻译
</div>
</button>
)}
{callType === CallType.VIDEO && (
<>
<button
style={styles.translationTypeButton(translationType === TranslationType.SIGN_LANGUAGE)}
onClick={() => setTranslationType(TranslationType.SIGN_LANGUAGE)}
>
<div>👋 </div>
<div style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
AI虚拟人实时手语翻译
</div>
</button>
<button
style={styles.translationTypeButton(translationType === TranslationType.HUMAN_INTERPRETER)}
onClick={() => setTranslationType(TranslationType.HUMAN_INTERPRETER)}
>
<div>👨💼 </div>
<div style={{ fontSize: '12px', color: '#666', marginTop: '4px' }}>
</div>
</button>
</>
)}
</div>
{/* 翻译员选择 */}
{translationType === TranslationType.HUMAN_INTERPRETER && (
<div style={styles.interpreterList}>
<div style={styles.sectionTitle}></div>
{availableInterpreters.map((interpreter) => (
<div
key={interpreter.id}
style={styles.interpreterItem(selectedInterpreter?.id === interpreter.id)}
onClick={() => setSelectedInterpreter(interpreter)}
>
<div style={styles.interpreterAvatar}>{interpreter.avatar}</div>
<div style={styles.interpreterInfo}>
<div style={styles.interpreterName}>{interpreter.name}</div>
<div style={styles.interpreterDetails}>
{interpreter.languages.join(', ')} | {interpreter.rating} | {interpreter.specialties.join(', ')}
</div>
<div style={styles.interpreterRate}>
¥{formatCurrency(interpreter.pricePerMinute)}/
</div>
</div>
</div>
))}
</div>
)}
</div>
{/* 备注信息 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<textarea
style={styles.descriptionInput}
placeholder="请输入特殊要求或备注信息..."
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
{/* 费用预估 */}
{estimatedCost > 0 && (
<div style={styles.costSummary}>
<div style={styles.costRow}>
<span style={styles.costLabel}>30</span>
<span style={styles.costValue}>¥{formatCurrency(estimatedCost)}</span>
</div>
<div style={styles.costRow}>
<span style={styles.costLabel}></span>
<span style={styles.costValue}>
¥{userAccount ? formatCurrency(userAccount.balance) : '0.00'}
</span>
</div>
</div>
)}
{/* 提交按钮 */}
<button
style={styles.submitButton}
onClick={handleSubmit}
disabled={!selectedDate || !selectedTime || isSubmitting}
>
{isSubmitting ? '预约中...' : '确认预约'}
</button>
</div>
);
};
export default MobileAppointment;
+698
View File
@@ -0,0 +1,698 @@
import { FC, useState, useEffect, useRef } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { BillingService } from '../../services/billingService';
import { AppointmentService } from '../../services/appointmentService';
import {
UserAccount,
CallType,
TranslationType,
Interpreter,
BillingRule
} from '../../types/billing';
const MobileCall: FC = () => {
const navigate = useNavigate();
const location = useLocation();
const billingService = BillingService.getInstance();
const appointmentService = AppointmentService.getInstance();
// 从URL参数获取通话类型
const urlParams = new URLSearchParams(location.search);
const initialCallType = urlParams.get('type') === 'video' ? CallType.VIDEO : CallType.VOICE;
// 状态管理
const [callType, setCallType] = useState<CallType>(initialCallType);
const [translationType, setTranslationType] = useState<TranslationType>(
callType === CallType.VIDEO ? TranslationType.SIGN_LANGUAGE : TranslationType.TEXT
);
const [isConnected, setIsConnected] = useState(false);
const [callDuration, setCallDuration] = useState(0);
const [currentCost, setCurrentCost] = useState(0);
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [selectedInterpreter, setSelectedInterpreter] = useState<Interpreter | null>(null);
const [availableInterpreters, setAvailableInterpreters] = useState<Interpreter[]>([]);
const [billingRule, setBillingRule] = useState<BillingRule | null>(null);
const [showLowBalanceWarning, setShowLowBalanceWarning] = useState(false);
const [selectedLanguages, setSelectedLanguages] = useState({
from: 'zh-CN',
to: 'en-US',
});
// 计费相关
const [lastBillingMinute, setLastBillingMinute] = useState(0);
const billingIntervalRef = useRef<NodeJS.Timeout | null>(null);
// 翻译历史
const [translationHistory, setTranslationHistory] = useState([
{
time: '14:23:15',
original: '你好,很高兴见到你',
translated: 'Hello, nice to meet you',
speaker: 'you'
},
{
time: '14:23:18',
original: 'Nice to meet you too!',
translated: '我也很高兴见到你!',
speaker: 'other'
},
]);
const languages = [
{ code: 'zh-CN', name: '中文', flag: '🇨🇳' },
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
{ code: 'ko-KR', name: '한국어', flag: '🇰🇷' },
{ code: 'es-ES', name: 'Español', flag: '🇪🇸' },
{ code: 'fr-FR', name: 'Français', flag: '🇫🇷' },
{ code: 'de-DE', name: 'Deutsch', flag: '🇩🇪' },
];
useEffect(() => {
// 获取用户账户信息
const account = billingService.getUserAccount();
setUserAccount(account);
// 获取计费规则
if (account) {
const rule = billingService.getBillingRule(callType, translationType, account.userType);
setBillingRule(rule);
}
// 获取可用翻译员
if (translationType === TranslationType.HUMAN_INTERPRETER) {
const interpreters = appointmentService.getAvailableInterpreters(
new Date(),
[selectedLanguages.from, selectedLanguages.to]
);
setAvailableInterpreters(interpreters);
}
}, [callType, translationType, selectedLanguages]);
useEffect(() => {
let interval: NodeJS.Timeout;
if (isConnected) {
interval = setInterval(() => {
setCallDuration(prev => {
const newDuration = prev + 1;
const currentMinute = Math.ceil(newDuration / 60);
// 计算当前费用
if (billingRule && userAccount) {
const interpreterRate = selectedInterpreter?.pricePerMinute;
const cost = billingService.calculateCallCost(
callType,
translationType,
newDuration / 60,
interpreterRate
);
setCurrentCost(cost);
// 每分钟开始时扣费
if (currentMinute > lastBillingMinute) {
const minuteCost = billingService.calculateCallCost(
callType,
translationType,
1,
interpreterRate
);
if (billingService.deductBalance(minuteCost)) {
setLastBillingMinute(currentMinute);
setUserAccount(billingService.getUserAccount());
} else {
// 余额不足,断开通话
handleDisconnect();
alert('余额不足,通话已断开');
}
}
// 检查低余额警告
if (billingService.shouldShowLowBalanceWarning(callType, translationType, interpreterRate)) {
setShowLowBalanceWarning(true);
}
}
return newDuration;
});
}, 1000);
}
return () => clearInterval(interval);
}, [isConnected, billingRule, userAccount, selectedInterpreter, lastBillingMinute]);
const formatDuration = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
const formatCost = (cents: number) => {
return (cents / 100).toFixed(2);
};
const handleConnect = () => {
if (!userAccount || !billingRule) {
alert('账户信息或计费规则未加载');
return;
}
const interpreterRate = selectedInterpreter?.pricePerMinute;
const balanceCheck = billingService.checkBalance(callType, translationType, 1, interpreterRate);
if (!balanceCheck.sufficient) {
alert(`余额不足,需要至少 ¥${formatCost(balanceCheck.requiredAmount)} 才能开始通话`);
navigate('/mobile/recharge');
return;
}
setIsConnected(true);
setCallDuration(0);
setCurrentCost(0);
setLastBillingMinute(0);
};
const handleDisconnect = () => {
setIsConnected(false);
if (billingIntervalRef.current) {
clearInterval(billingIntervalRef.current);
}
};
const handleCallTypeChange = (newCallType: CallType) => {
if (isConnected) {
alert('通话进行中无法切换类型');
return;
}
setCallType(newCallType);
// 根据通话类型设置默认翻译类型
if (newCallType === CallType.VIDEO) {
setTranslationType(TranslationType.SIGN_LANGUAGE);
} else {
setTranslationType(TranslationType.TEXT);
}
};
const handleTranslationTypeChange = (newTranslationType: TranslationType) => {
if (isConnected) {
alert('通话进行中无法切换翻译类型');
return;
}
setTranslationType(newTranslationType);
};
const handleInterpreterSelect = (interpreter: Interpreter) => {
if (isConnected) {
alert('通话进行中无法切换翻译员');
return;
}
setSelectedInterpreter(interpreter);
};
const swapLanguages = () => {
setSelectedLanguages({
from: selectedLanguages.to,
to: selectedLanguages.from,
});
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100vh',
display: 'flex',
flexDirection: 'column' as const,
},
header: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
marginBottom: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
callTypeSelector: {
display: 'flex',
gap: '8px',
marginBottom: '16px',
},
callTypeButton: (active: boolean) => ({
flex: 1,
padding: '12px',
borderRadius: '8px',
border: 'none',
backgroundColor: active ? '#1890ff' : '#f0f0f0',
color: active ? 'white' : '#666',
fontSize: '14px',
fontWeight: '500' as const,
cursor: 'pointer',
}),
statusRow: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '12px',
},
statusIndicator: {
display: 'flex',
alignItems: 'center',
},
statusDot: {
width: '12px',
height: '12px',
borderRadius: '6px',
backgroundColor: isConnected ? '#52c41a' : '#d9d9d9',
marginRight: '8px',
},
statusText: {
fontSize: '14px',
fontWeight: '500' as const,
color: isConnected ? '#52c41a' : '#666',
},
costInfo: {
textAlign: 'right' as const,
},
duration: {
fontSize: '20px',
fontWeight: '600' as const,
color: '#1890ff',
textAlign: 'center' as const,
marginBottom: '8px',
},
currentCost: {
fontSize: '16px',
fontWeight: '500' as const,
color: '#fa8c16',
textAlign: 'center' as const,
},
billingInfo: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
marginBottom: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
billingTitle: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '12px',
},
translationTypeSelector: {
display: 'flex',
flexDirection: 'column' as const,
gap: '8px',
marginBottom: '16px',
},
translationTypeButton: (active: boolean) => ({
padding: '12px',
borderRadius: '8px',
border: active ? '2px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: active ? '#e6f7ff' : 'white',
color: active ? '#1890ff' : '#666',
fontSize: '14px',
cursor: 'pointer',
textAlign: 'left' as const,
}),
rateInfo: {
fontSize: '14px',
color: '#666',
marginBottom: '8px',
},
interpreterSelector: {
marginTop: '12px',
},
interpreterItem: (selected: boolean) => ({
display: 'flex',
alignItems: 'center',
padding: '12px',
borderRadius: '8px',
border: selected ? '2px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: selected ? '#e6f7ff' : 'white',
marginBottom: '8px',
cursor: 'pointer',
}),
interpreterInfo: {
marginLeft: '12px',
flex: 1,
},
interpreterName: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
},
interpreterDetails: {
fontSize: '12px',
color: '#666',
},
interpreterRate: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#fa8c16',
},
languageSelector: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
marginBottom: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
languageRow: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '12px',
},
languageSelect: {
flex: 1,
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
backgroundColor: 'white',
fontSize: '14px',
},
swapButton: {
margin: '0 12px',
padding: '8px',
borderRadius: '20px',
border: 'none',
backgroundColor: '#f0f8ff',
cursor: 'pointer',
fontSize: '16px',
},
controlButtons: {
display: 'flex',
justifyContent: 'center',
gap: '20px',
marginBottom: '24px',
},
controlButton: {
width: '80px',
height: '80px',
borderRadius: '40px',
border: 'none',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '32px',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transition: 'transform 0.2s ease',
},
connectButton: {
backgroundColor: isConnected ? '#ff4d4f' : '#52c41a',
},
translationContainer: {
flex: 1,
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
translationTitle: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
},
translationItem: {
marginBottom: '16px',
padding: '12px',
borderRadius: '8px',
backgroundColor: '#f8f9fa',
},
translationTime: {
fontSize: '12px',
color: '#999',
marginBottom: '4px',
},
translationText: {
fontSize: '14px',
lineHeight: '1.4',
marginBottom: '4px',
},
originalText: {
color: '#333',
fontWeight: '500' as const,
},
translatedText: {
color: '#1890ff',
},
speakerTag: {
fontSize: '12px',
color: '#666',
fontStyle: 'italic' as const,
},
warningModal: {
position: 'fixed' as const,
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
},
warningContent: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '24px',
margin: '20px',
textAlign: 'center' as const,
},
warningTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#fa8c16',
marginBottom: '12px',
},
warningText: {
fontSize: '14px',
color: '#666',
marginBottom: '20px',
},
warningButtons: {
display: 'flex',
gap: '12px',
justifyContent: 'center',
},
warningButton: (primary: boolean) => ({
padding: '8px 16px',
borderRadius: '6px',
border: 'none',
backgroundColor: primary ? '#1890ff' : '#f0f0f0',
color: primary ? 'white' : '#666',
fontSize: '14px',
cursor: 'pointer',
}),
};
return (
<div style={styles.container}>
{/* 通话类型选择 */}
<div style={styles.header}>
<div style={styles.callTypeSelector}>
<button
style={styles.callTypeButton(callType === CallType.VOICE)}
onClick={() => handleCallTypeChange(CallType.VOICE)}
disabled={isConnected}
>
📞
</button>
<button
style={styles.callTypeButton(callType === CallType.VIDEO)}
onClick={() => handleCallTypeChange(CallType.VIDEO)}
disabled={isConnected}
>
📹
</button>
</div>
<div style={styles.statusRow}>
<div style={styles.statusIndicator}>
<div style={styles.statusDot}></div>
<span style={styles.statusText}>
{isConnected ? '通话中' : '未连接'}
</span>
</div>
<div style={styles.costInfo}>
<div style={styles.duration}>{formatDuration(callDuration)}</div>
<div style={styles.currentCost}>¥{formatCost(currentCost)}</div>
</div>
</div>
</div>
{/* 计费信息 */}
<div style={styles.billingInfo}>
<div style={styles.billingTitle}></div>
<div style={styles.translationTypeSelector}>
{callType === CallType.VOICE && (
<button
style={styles.translationTypeButton(translationType === TranslationType.TEXT)}
onClick={() => handleTranslationTypeChange(TranslationType.TEXT)}
disabled={isConnected}
>
<div>💬 </div>
<div style={styles.rateInfo}>
{billingRule && `¥${formatCost(billingRule.pricePerMinute)}/分钟`}
</div>
</button>
)}
{callType === CallType.VIDEO && (
<>
<button
style={styles.translationTypeButton(translationType === TranslationType.SIGN_LANGUAGE)}
onClick={() => handleTranslationTypeChange(TranslationType.SIGN_LANGUAGE)}
disabled={isConnected}
>
<div>👋 </div>
<div style={styles.rateInfo}>
{billingRule && `¥${formatCost(billingRule.pricePerMinute)}/分钟`}
</div>
</button>
<button
style={styles.translationTypeButton(translationType === TranslationType.HUMAN_INTERPRETER)}
onClick={() => handleTranslationTypeChange(TranslationType.HUMAN_INTERPRETER)}
disabled={isConnected}
>
<div>👨💼 </div>
<div style={styles.rateInfo}>
{billingRule && `¥${formatCost(billingRule.pricePerMinute)}/分钟 + 翻译员费用`}
</div>
</button>
</>
)}
</div>
{/* 翻译员选择 */}
{translationType === TranslationType.HUMAN_INTERPRETER && (
<div style={styles.interpreterSelector}>
<div style={styles.billingTitle}></div>
{availableInterpreters.map((interpreter) => (
<div
key={interpreter.id}
style={styles.interpreterItem(selectedInterpreter?.id === interpreter.id)}
onClick={() => handleInterpreterSelect(interpreter)}
>
<div>{interpreter.avatar}</div>
<div style={styles.interpreterInfo}>
<div style={styles.interpreterName}>{interpreter.name}</div>
<div style={styles.interpreterDetails}>
{interpreter.languages.join(', ')} | {interpreter.rating}
</div>
</div>
<div style={styles.interpreterRate}>
¥{formatCost(interpreter.pricePerMinute)}/
</div>
</div>
))}
</div>
)}
</div>
{/* 语言选择器 */}
<div style={styles.languageSelector}>
<div style={styles.languageRow}>
<select
style={styles.languageSelect}
value={selectedLanguages.from}
onChange={(e) => setSelectedLanguages({...selectedLanguages, from: e.target.value})}
disabled={isConnected}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
<button style={styles.swapButton} onClick={swapLanguages} disabled={isConnected}>
🔄
</button>
<select
style={styles.languageSelect}
value={selectedLanguages.to}
onChange={(e) => setSelectedLanguages({...selectedLanguages, to: e.target.value})}
disabled={isConnected}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
</div>
</div>
{/* 控制按钮 */}
<div style={styles.controlButtons}>
<button
style={{...styles.controlButton, ...styles.connectButton}}
onClick={isConnected ? handleDisconnect : handleConnect}
>
{isConnected ? '📞' : (callType === CallType.VIDEO ? '📹' : '📱')}
</button>
</div>
{/* 翻译历史 */}
<div style={styles.translationContainer}>
<h3 style={styles.translationTitle}>
{translationType === TranslationType.TEXT ? '实时翻译' :
translationType === TranslationType.SIGN_LANGUAGE ? '手语翻译' : '翻译员服务'}
</h3>
{translationHistory.map((item, index) => (
<div key={index} style={styles.translationItem}>
<div style={styles.translationTime}>{item.time}</div>
<div style={{...styles.translationText, ...styles.originalText}}>
{item.original}
</div>
<div style={{...styles.translationText, ...styles.translatedText}}>
{item.translated}
</div>
<div style={styles.speakerTag}>
{item.speaker === 'you' ? '您' : '对方'}
</div>
</div>
))}
</div>
{/* 低余额警告 */}
{showLowBalanceWarning && (
<div style={styles.warningModal}>
<div style={styles.warningContent}>
<div style={styles.warningTitle}> </div>
<div style={styles.warningText}>
5
</div>
<div style={styles.warningButtons}>
<button
style={styles.warningButton(false)}
onClick={() => setShowLowBalanceWarning(false)}
>
</button>
<button
style={styles.warningButton(true)}
onClick={() => {
setShowLowBalanceWarning(false);
navigate('/mobile/recharge');
}}
>
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default MobileCall;
+407
View File
@@ -0,0 +1,407 @@
import { FC, useState } from 'react';
interface Document {
id: string;
name: string;
size: string;
status: 'uploading' | 'processing' | 'completed' | 'failed';
progress: number;
originalLanguage: string;
targetLanguage: string;
uploadTime: string;
}
const MobileDocuments: FC = () => {
const [documents, setDocuments] = useState<Document[]>([
{
id: '1',
name: '商业合同.pdf',
size: '2.3 MB',
status: 'completed',
progress: 100,
originalLanguage: 'zh-CN',
targetLanguage: 'en-US',
uploadTime: '2024-01-15 14:30',
},
{
id: '2',
name: '技术文档.docx',
size: '1.8 MB',
status: 'processing',
progress: 65,
originalLanguage: 'en-US',
targetLanguage: 'zh-CN',
uploadTime: '2024-01-15 15:20',
},
]);
const [selectedLanguages, setSelectedLanguages] = useState({
from: 'zh-CN',
to: 'en-US',
});
const languages = [
{ code: 'zh-CN', name: '中文', flag: '🇨🇳' },
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
{ code: 'ko-KR', name: '한국어', flag: '🇰🇷' },
{ code: 'es-ES', name: 'Español', flag: '🇪🇸' },
{ code: 'fr-FR', name: 'Français', flag: '🇫🇷' },
{ code: 'de-DE', name: 'Deutsch', flag: '🇩🇪' },
];
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files && files.length > 0) {
const file = files[0];
const newDocument: Document = {
id: Date.now().toString(),
name: file.name,
size: `${(file.size / 1024 / 1024).toFixed(1)} MB`,
status: 'uploading',
progress: 0,
originalLanguage: selectedLanguages.from,
targetLanguage: selectedLanguages.to,
uploadTime: new Date().toLocaleString('zh-CN'),
};
setDocuments(prev => [newDocument, ...prev]);
// 模拟上传进度
simulateUpload(newDocument.id);
}
};
const simulateUpload = (docId: string) => {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
setDocuments(prev => prev.map(doc =>
doc.id === docId
? { ...doc, status: 'processing', progress: 0 }
: doc
));
clearInterval(interval);
simulateProcessing(docId);
} else {
setDocuments(prev => prev.map(doc =>
doc.id === docId
? { ...doc, progress: Math.floor(progress) }
: doc
));
}
}, 200);
};
const simulateProcessing = (docId: string) => {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 100) {
progress = 100;
setDocuments(prev => prev.map(doc =>
doc.id === docId
? { ...doc, status: 'completed', progress: 100 }
: doc
));
clearInterval(interval);
} else {
setDocuments(prev => prev.map(doc =>
doc.id === docId
? { ...doc, progress: Math.floor(progress) }
: doc
));
}
}, 300);
};
const getStatusText = (status: Document['status']) => {
switch (status) {
case 'uploading': return '上传中';
case 'processing': return '翻译中';
case 'completed': return '已完成';
case 'failed': return '失败';
default: return '';
}
};
const getStatusColor = (status: Document['status']) => {
switch (status) {
case 'uploading': return '#1890ff';
case 'processing': return '#fa8c16';
case 'completed': return '#52c41a';
case 'failed': return '#ff4d4f';
default: return '#666';
}
};
const swapLanguages = () => {
setSelectedLanguages({
from: selectedLanguages.to,
to: selectedLanguages.from,
});
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100%',
},
uploadSection: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
uploadTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
textAlign: 'center' as const,
},
languageSelector: {
marginBottom: '20px',
},
languageRow: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: '12px',
},
languageSelect: {
flex: 1,
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
backgroundColor: 'white',
fontSize: '14px',
},
swapButton: {
margin: '0 12px',
padding: '8px',
borderRadius: '20px',
border: 'none',
backgroundColor: '#f0f8ff',
cursor: 'pointer',
fontSize: '16px',
},
uploadArea: {
border: '2px dashed #d9d9d9',
borderRadius: '8px',
padding: '40px 20px',
textAlign: 'center' as const,
backgroundColor: '#fafafa',
cursor: 'pointer',
transition: 'border-color 0.3s ease',
},
uploadIcon: {
fontSize: '48px',
marginBottom: '12px',
color: '#1890ff',
},
uploadText: {
fontSize: '16px',
color: '#666',
marginBottom: '8px',
},
uploadSubtext: {
fontSize: '14px',
color: '#999',
},
hiddenInput: {
display: 'none',
},
documentsSection: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
sectionTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
},
documentItem: {
display: 'flex',
alignItems: 'center',
padding: '16px',
borderRadius: '8px',
backgroundColor: '#f8f9fa',
marginBottom: '12px',
},
documentIcon: {
fontSize: '24px',
marginRight: '12px',
},
documentInfo: {
flex: 1,
},
documentName: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
marginBottom: '4px',
},
documentDetails: {
fontSize: '12px',
color: '#666',
marginBottom: '4px',
},
documentLanguages: {
fontSize: '12px',
color: '#1890ff',
},
documentStatus: {
textAlign: 'right' as const,
},
statusText: {
fontSize: '12px',
fontWeight: '500' as const,
marginBottom: '4px',
},
progressBar: {
width: '60px',
height: '4px',
backgroundColor: '#f0f0f0',
borderRadius: '2px',
overflow: 'hidden',
},
progressFill: {
height: '100%',
backgroundColor: '#1890ff',
transition: 'width 0.3s ease',
},
actionButton: {
padding: '4px 8px',
borderRadius: '4px',
border: 'none',
backgroundColor: '#1890ff',
color: 'white',
fontSize: '12px',
cursor: 'pointer',
marginTop: '4px',
},
};
return (
<div style={styles.container}>
{/* 上传区域 */}
<div style={styles.uploadSection}>
<h2 style={styles.uploadTitle}>📄 </h2>
{/* 语言选择 */}
<div style={styles.languageSelector}>
<div style={styles.languageRow}>
<select
style={styles.languageSelect}
value={selectedLanguages.from}
onChange={(e) => setSelectedLanguages({...selectedLanguages, from: e.target.value})}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
<button style={styles.swapButton} onClick={swapLanguages}>
🔄
</button>
<select
style={styles.languageSelect}
value={selectedLanguages.to}
onChange={(e) => setSelectedLanguages({...selectedLanguages, to: e.target.value})}
>
{languages.map(lang => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
</div>
</div>
{/* 上传区域 */}
<label style={styles.uploadArea}>
<input
type="file"
style={styles.hiddenInput}
accept=".pdf,.doc,.docx,.txt"
onChange={handleFileUpload}
/>
<div style={styles.uploadIcon}>📁</div>
<div style={styles.uploadText}></div>
<div style={styles.uploadSubtext}>
PDFDOCDOCXTXT
</div>
</label>
</div>
{/* 文档列表 */}
<div style={styles.documentsSection}>
<h3 style={styles.sectionTitle}></h3>
{documents.length === 0 ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
</div>
) : (
documents.map((doc) => (
<div key={doc.id} style={styles.documentItem}>
<div style={styles.documentIcon}>
{doc.name.endsWith('.pdf') ? '📄' : '📝'}
</div>
<div style={styles.documentInfo}>
<div style={styles.documentName}>{doc.name}</div>
<div style={styles.documentDetails}>
{doc.size} {doc.uploadTime}
</div>
<div style={styles.documentLanguages}>
{languages.find(l => l.code === doc.originalLanguage)?.flag} {' '}
{languages.find(l => l.code === doc.targetLanguage)?.flag}
</div>
</div>
<div style={styles.documentStatus}>
<div style={{
...styles.statusText,
color: getStatusColor(doc.status)
}}>
{getStatusText(doc.status)}
</div>
{doc.status !== 'completed' && (
<div style={styles.progressBar}>
<div
style={{
...styles.progressFill,
width: `${doc.progress}%`
}}
/>
</div>
)}
{doc.status === 'completed' && (
<button style={styles.actionButton}>
</button>
)}
</div>
</div>
))
)}
</div>
</div>
);
};
export default MobileDocuments;
+388
View File
@@ -0,0 +1,388 @@
import { FC, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { BillingService } from '../../services/billingService';
import { AppointmentService } from '../../services/appointmentService';
import { UserType, UserAccount, Appointment } from '../../types/billing';
const MobileHome: FC = () => {
const navigate = useNavigate();
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [upcomingAppointments, setUpcomingAppointments] = useState<Appointment[]>([]);
const billingService = BillingService.getInstance();
const appointmentService = AppointmentService.getInstance();
useEffect(() => {
// 模拟用户账户数据
const mockAccount: UserAccount = {
id: 'user_1',
userType: UserType.INDIVIDUAL,
balance: 5000, // 50元
};
billingService.setUserAccount(mockAccount);
setUserAccount(mockAccount);
// 获取即将到来的预约
const appointments = appointmentService.getUpcomingAppointments('user_1', 3);
setUpcomingAppointments(appointments);
}, []);
const quickActions = [
{
id: 1,
title: '语音通话',
description: '发起实时语音翻译通话',
icon: '📞',
color: '#52c41a',
action: () => navigate('/mobile/call?type=voice'),
},
{
id: 2,
title: '视频通话',
description: '发起视频通话和手语翻译',
icon: '📹',
color: '#1890ff',
action: () => navigate('/mobile/call?type=video'),
},
{
id: 3,
title: '预约通话',
description: '预约专业翻译员服务',
icon: '📅',
color: '#722ed1',
action: () => navigate('/mobile/appointment'),
},
{
id: 4,
title: '充值',
description: '账户余额充值',
icon: '💰',
color: '#fa8c16',
action: () => navigate('/mobile/recharge'),
},
];
const recentActivities = [
{
id: 1,
type: 'call',
title: '与 John Smith 的通话',
time: '2小时前',
status: '已完成',
cost: '¥15.50',
},
{
id: 2,
type: 'appointment',
title: '商务会议翻译',
time: '明天 14:00',
status: '已预约',
cost: '¥180.00',
},
{
id: 3,
type: 'call',
title: '医疗咨询翻译',
time: '2天前',
status: '已完成',
cost: '¥32.00',
},
];
const formatBalance = (balance: number) => {
return (balance / 100).toFixed(2);
};
const getActivityIcon = (type: string) => {
switch (type) {
case 'call': return '📞';
case 'appointment': return '📅';
case 'recharge': return '💰';
default: return '📋';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case '已完成': return '#52c41a';
case '已预约': return '#1890ff';
case '进行中': return '#fa8c16';
case '已取消': return '#ff4d4f';
default: return '#666';
}
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100%',
},
balanceCard: {
backgroundColor: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
color: 'white',
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
},
balanceTitle: {
fontSize: '14px',
opacity: 0.9,
marginBottom: '8px',
},
balanceAmount: {
fontSize: '32px',
fontWeight: '700' as const,
marginBottom: '4px',
},
balanceSubtitle: {
fontSize: '12px',
opacity: 0.8,
},
userTypeTag: {
position: 'absolute' as const,
top: '16px',
right: '16px',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '12px',
fontWeight: '500' as const,
},
welcomeCard: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
welcomeTitle: {
fontSize: '24px',
fontWeight: '600' as const,
color: '#1890ff',
marginBottom: '8px',
},
welcomeSubtitle: {
fontSize: '16px',
color: '#666',
marginBottom: '0',
},
sectionTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
marginTop: '24px',
},
quickActionsGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '12px',
marginBottom: '24px',
},
actionCard: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
textAlign: 'center' as const,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
border: 'none',
cursor: 'pointer',
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
position: 'relative' as const,
},
actionIcon: {
fontSize: '32px',
marginBottom: '8px',
display: 'block',
},
actionTitle: {
fontSize: '14px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
actionDescription: {
fontSize: '12px',
color: '#666',
lineHeight: '1.4',
},
appointmentPreview: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
marginBottom: '20px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
appointmentTitle: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '12px',
},
appointmentItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '8px 0',
borderBottom: '1px solid #f0f0f0',
},
appointmentInfo: {
flex: 1,
},
appointmentName: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
marginBottom: '2px',
},
appointmentTime: {
fontSize: '12px',
color: '#666',
},
appointmentStatus: {
fontSize: '12px',
padding: '2px 8px',
borderRadius: '10px',
backgroundColor: '#e6f7ff',
color: '#1890ff',
},
activityList: {
backgroundColor: 'white',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
activityItem: {
display: 'flex',
alignItems: 'center',
padding: '12px 0',
borderBottom: '1px solid #f0f0f0',
},
activityIcon: {
width: '40px',
height: '40px',
borderRadius: '20px',
backgroundColor: '#f0f8ff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginRight: '12px',
fontSize: '18px',
},
activityContent: {
flex: 1,
},
activityTitle: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
marginBottom: '4px',
},
activityTime: {
fontSize: '12px',
color: '#999',
},
activityRight: {
textAlign: 'right' as const,
},
activityStatus: {
fontSize: '12px',
fontWeight: '500' as const,
marginBottom: '4px',
},
activityCost: {
fontSize: '14px',
fontWeight: '600' as const,
color: '#333',
},
};
const handleActionPress = (action: () => void) => {
action();
};
return (
<div style={styles.container}>
{/* 余额卡片 */}
{userAccount && (
<div style={styles.balanceCard}>
<div style={styles.userTypeTag}>
{userAccount.userType === UserType.INDIVIDUAL ? '个人用户' : '企业用户'}
</div>
<div style={styles.balanceTitle}></div>
<div style={styles.balanceAmount}>¥{formatBalance(userAccount.balance)}</div>
<div style={styles.balanceSubtitle}></div>
</div>
)}
{/* 快捷操作 */}
<h2 style={styles.sectionTitle}></h2>
<div style={styles.quickActionsGrid}>
{quickActions.map((action) => (
<button
key={action.id}
style={styles.actionCard}
onClick={() => handleActionPress(action.action)}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)';
}}
>
<span style={styles.actionIcon}>{action.icon}</span>
<div style={styles.actionTitle}>{action.title}</div>
<div style={styles.actionDescription}>{action.description}</div>
</button>
))}
</div>
{/* 即将到来的预约 */}
{upcomingAppointments.length > 0 && (
<div style={styles.appointmentPreview}>
<div style={styles.appointmentTitle}></div>
{upcomingAppointments.map((appointment) => (
<div key={appointment.id} style={styles.appointmentItem}>
<div style={styles.appointmentInfo}>
<div style={styles.appointmentName}>{appointment.title}</div>
<div style={styles.appointmentTime}>
{appointment.scheduledTime.toLocaleDateString()} {appointment.scheduledTime.toLocaleTimeString()}
</div>
</div>
<div style={styles.appointmentStatus}>{appointment.status}</div>
</div>
))}
</div>
)}
{/* 最近活动 */}
<h2 style={styles.sectionTitle}></h2>
<div style={styles.activityList}>
{recentActivities.map((activity) => (
<div key={activity.id} style={styles.activityItem}>
<div style={styles.activityIcon}>
{getActivityIcon(activity.type)}
</div>
<div style={styles.activityContent}>
<div style={styles.activityTitle}>{activity.title}</div>
<div style={styles.activityTime}>{activity.time}</div>
</div>
<div style={styles.activityRight}>
<div style={{...styles.activityStatus, color: getStatusColor(activity.status)}}>
{activity.status}
</div>
<div style={styles.activityCost}>{activity.cost}</div>
</div>
</div>
))}
</div>
</div>
);
};
export default MobileHome;
+497
View File
@@ -0,0 +1,497 @@
import { FC, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { BillingService } from '../../services/billingService';
import { UserAccount, UserType, RechargeRecord } from '../../types/billing';
const MobileRecharge: FC = () => {
const navigate = useNavigate();
const billingService = BillingService.getInstance();
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [selectedAmount, setSelectedAmount] = useState<number>(0);
const [customAmount, setCustomAmount] = useState<string>('');
const [selectedPayment, setSelectedPayment] = useState<'wechat' | 'alipay' | 'card'>('wechat');
const [rechargeHistory, setRechargeHistory] = useState<RechargeRecord[]>([]);
const [isProcessing, setIsProcessing] = useState(false);
// 预设充值金额(分)
const presetAmounts = [
{ value: 5000, label: '¥50', bonus: 0, popular: false },
{ value: 10000, label: '¥100', bonus: 500, popular: true },
{ value: 20000, label: '¥200', bonus: 1500, popular: false },
{ value: 50000, label: '¥500', bonus: 5000, popular: false },
{ value: 100000, label: '¥1000', bonus: 15000, popular: false },
];
const paymentMethods = [
{ id: 'wechat', name: '微信支付', icon: '💚', color: '#07c160' },
{ id: 'alipay', name: '支付宝', icon: '💙', color: '#1677ff' },
{ id: 'card', name: '银行卡', icon: '💳', color: '#722ed1' },
];
useEffect(() => {
const account = billingService.getUserAccount();
setUserAccount(account);
// 模拟充值历史
const mockHistory: RechargeRecord[] = [
{
id: '1',
userId: account?.id || '1',
amount: 10000,
bonus: 500,
paymentMethod: 'wechat',
status: 'completed',
createdAt: new Date(Date.now() - 86400000), // 1天前
},
{
id: '2',
userId: account?.id || '1',
amount: 5000,
bonus: 0,
paymentMethod: 'alipay',
status: 'completed',
createdAt: new Date(Date.now() - 7 * 86400000), // 7天前
},
];
setRechargeHistory(mockHistory);
}, []);
const formatCurrency = (cents: number) => {
return (cents / 100).toFixed(2);
};
const handleAmountSelect = (amount: number) => {
setSelectedAmount(amount);
setCustomAmount('');
};
const handleCustomAmountChange = (value: string) => {
const numValue = parseFloat(value);
if (!isNaN(numValue) && numValue > 0) {
setSelectedAmount(Math.round(numValue * 100));
setCustomAmount(value);
} else {
setSelectedAmount(0);
setCustomAmount(value);
}
};
const getBonus = (amount: number) => {
const preset = presetAmounts.find(p => p.value === amount);
return preset?.bonus || 0;
};
const getTotalAmount = () => {
return selectedAmount + getBonus(selectedAmount);
};
const handleRecharge = async () => {
if (selectedAmount < 100) { // 最低1元
alert('充值金额不能少于1元');
return;
}
if (!userAccount) {
alert('用户账户信息未加载');
return;
}
setIsProcessing(true);
try {
// 模拟支付处理
await new Promise(resolve => setTimeout(resolve, 2000));
// 执行充值
const success = billingService.rechargeAccount(selectedAmount);
if (success) {
// 更新用户账户
setUserAccount(billingService.getUserAccount());
// 添加充值记录
const newRecord: RechargeRecord = {
id: Date.now().toString(),
userId: userAccount.id,
amount: selectedAmount,
bonus: getBonus(selectedAmount),
paymentMethod: selectedPayment,
status: 'completed',
createdAt: new Date(),
};
setRechargeHistory(prev => [newRecord, ...prev]);
alert(`充值成功!到账金额:¥${formatCurrency(getTotalAmount())}`);
setSelectedAmount(0);
setCustomAmount('');
} else {
alert('充值失败,请重试');
}
} catch (error) {
alert('充值失败,请重试');
} finally {
setIsProcessing(false);
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'completed':
return '#52c41a';
case 'pending':
return '#fa8c16';
case 'failed':
return '#ff4d4f';
default:
return '#d9d9d9';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'completed':
return '成功';
case 'pending':
return '处理中';
case 'failed':
return '失败';
default:
return '未知';
}
};
const styles = {
container: {
padding: '16px',
backgroundColor: '#f5f5f5',
minHeight: '100vh',
},
header: {
display: 'flex',
alignItems: 'center',
marginBottom: '20px',
},
backButton: {
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
marginRight: '12px',
},
title: {
fontSize: '20px',
fontWeight: '600' as const,
color: '#333',
},
balanceCard: {
backgroundColor: 'white',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
textAlign: 'center' as const,
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
balanceLabel: {
fontSize: '14px',
color: '#666',
marginBottom: '8px',
},
balanceAmount: {
fontSize: '32px',
fontWeight: '700' as const,
color: '#1890ff',
marginBottom: '8px',
},
userType: {
fontSize: '12px',
color: '#999',
backgroundColor: '#f0f8ff',
padding: '4px 12px',
borderRadius: '12px',
display: 'inline-block',
},
section: {
backgroundColor: 'white',
borderRadius: '16px',
padding: '20px',
marginBottom: '20px',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
sectionTitle: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '16px',
},
amountGrid: {
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '12px',
marginBottom: '16px',
},
amountCard: (selected: boolean, popular: boolean) => ({
padding: '16px',
borderRadius: '12px',
border: selected ? '2px solid #1890ff' : '2px solid transparent',
backgroundColor: selected ? '#e6f7ff' : '#fafafa',
cursor: 'pointer',
textAlign: 'center' as const,
position: 'relative' as const,
...(popular && {
borderColor: '#fa8c16',
backgroundColor: '#fff7e6',
}),
}),
popularBadge: {
position: 'absolute' as const,
top: '-8px',
right: '-8px',
backgroundColor: '#fa8c16',
color: 'white',
fontSize: '10px',
padding: '2px 6px',
borderRadius: '8px',
fontWeight: '500' as const,
},
amountValue: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
bonusText: {
fontSize: '12px',
color: '#52c41a',
fontWeight: '500' as const,
},
customAmountInput: {
width: '100%',
padding: '12px',
borderRadius: '8px',
border: '1px solid #d9d9d9',
fontSize: '16px',
marginBottom: '16px',
},
paymentMethods: {
display: 'flex',
gap: '12px',
marginBottom: '20px',
},
paymentMethod: (selected: boolean, color: string) => ({
flex: 1,
padding: '16px',
borderRadius: '12px',
border: selected ? `2px solid ${color}` : '2px solid #f0f0f0',
backgroundColor: selected ? `${color}10` : 'white',
cursor: 'pointer',
textAlign: 'center' as const,
}),
paymentIcon: {
fontSize: '24px',
marginBottom: '8px',
},
paymentName: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
},
summaryCard: {
backgroundColor: '#f8f9fa',
borderRadius: '12px',
padding: '16px',
marginBottom: '20px',
},
summaryRow: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '8px',
},
summaryLabel: {
fontSize: '14px',
color: '#666',
},
summaryValue: {
fontSize: '14px',
fontWeight: '500' as const,
color: '#333',
},
totalRow: {
borderTop: '1px solid #e8e8e8',
paddingTop: '8px',
marginTop: '8px',
},
totalValue: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#1890ff',
},
rechargeButton: {
width: '100%',
padding: '16px',
borderRadius: '12px',
border: 'none',
backgroundColor: selectedAmount > 0 && !isProcessing ? '#1890ff' : '#d9d9d9',
color: 'white',
fontSize: '16px',
fontWeight: '600' as const,
cursor: selectedAmount > 0 && !isProcessing ? 'pointer' : 'not-allowed',
marginBottom: '20px',
},
historyItem: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '16px 0',
borderBottom: '1px solid #f0f0f0',
},
historyLeft: {
flex: 1,
},
historyAmount: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
historyDate: {
fontSize: '12px',
color: '#999',
},
historyStatus: (status: string) => ({
fontSize: '12px',
fontWeight: '500' as const,
color: getStatusColor(status),
backgroundColor: `${getStatusColor(status)}15`,
padding: '2px 8px',
borderRadius: '8px',
}),
};
return (
<div style={styles.container}>
{/* 头部 */}
<div style={styles.header}>
<button style={styles.backButton} onClick={() => navigate(-1)}>
</button>
<div style={styles.title}></div>
</div>
{/* 余额显示 */}
<div style={styles.balanceCard}>
<div style={styles.balanceLabel}></div>
<div style={styles.balanceAmount}>
¥{userAccount ? formatCurrency(userAccount.balance) : '0.00'}
</div>
<div style={styles.userType}>
{userAccount?.userType === UserType.ENTERPRISE ? '企业用户' : '个人用户'}
</div>
</div>
{/* 充值金额选择 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.amountGrid}>
{presetAmounts.map((amount) => (
<div
key={amount.value}
style={styles.amountCard(selectedAmount === amount.value, amount.popular)}
onClick={() => handleAmountSelect(amount.value)}
>
{amount.popular && <div style={styles.popularBadge}></div>}
<div style={styles.amountValue}>{amount.label}</div>
{amount.bonus > 0 && (
<div style={styles.bonusText}>
¥{formatCurrency(amount.bonus)}
</div>
)}
</div>
))}
</div>
<input
type="number"
placeholder="自定义金额(元)"
style={styles.customAmountInput}
value={customAmount}
onChange={(e) => handleCustomAmountChange(e.target.value)}
/>
</div>
{/* 支付方式 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
<div style={styles.paymentMethods}>
{paymentMethods.map((method) => (
<div
key={method.id}
style={styles.paymentMethod(
selectedPayment === method.id,
method.color
)}
onClick={() => setSelectedPayment(method.id as any)}
>
<div style={styles.paymentIcon}>{method.icon}</div>
<div style={styles.paymentName}>{method.name}</div>
</div>
))}
</div>
</div>
{/* 费用明细 */}
{selectedAmount > 0 && (
<div style={styles.summaryCard}>
<div style={styles.summaryRow}>
<span style={styles.summaryLabel}></span>
<span style={styles.summaryValue}>¥{formatCurrency(selectedAmount)}</span>
</div>
{getBonus(selectedAmount) > 0 && (
<div style={styles.summaryRow}>
<span style={styles.summaryLabel}></span>
<span style={{...styles.summaryValue, color: '#52c41a'}}>
+¥{formatCurrency(getBonus(selectedAmount))}
</span>
</div>
)}
<div style={{...styles.summaryRow, ...styles.totalRow}}>
<span style={styles.summaryLabel}></span>
<span style={styles.totalValue}>¥{formatCurrency(getTotalAmount())}</span>
</div>
</div>
)}
{/* 充值按钮 */}
<button
style={styles.rechargeButton}
onClick={handleRecharge}
disabled={selectedAmount === 0 || isProcessing}
>
{isProcessing ? '处理中...' : `确认充值 ¥${formatCurrency(selectedAmount)}`}
</button>
{/* 充值历史 */}
<div style={styles.section}>
<div style={styles.sectionTitle}></div>
{rechargeHistory.map((record) => (
<div key={record.id} style={styles.historyItem}>
<div style={styles.historyLeft}>
<div style={styles.historyAmount}>
+¥{formatCurrency(record.amount + record.bonus)}
</div>
<div style={styles.historyDate}>
{record.createdAt.toLocaleDateString()} {record.createdAt.toLocaleTimeString()}
</div>
</div>
<div style={styles.historyStatus(record.status)}>
{getStatusText(record.status)}
</div>
</div>
))}
</div>
</div>
);
};
export default MobileRecharge;
+357
View File
@@ -0,0 +1,357 @@
import { FC, useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { BillingService } from '../../services/billingService';
import { UserAccount, UserType } from '../../types/billing';
const MobileSettings: FC = () => {
const navigate = useNavigate();
const billingService = BillingService.getInstance();
const [userAccount, setUserAccount] = useState<UserAccount | null>(null);
const [notificationSettings, setNotificationSettings] = useState({
callReminder: true,
balanceAlert: true,
promotions: false,
});
const [languageSettings, setLanguageSettings] = useState({
interface: 'zh-CN',
defaultSource: 'zh-CN',
defaultTarget: 'en-US',
});
useEffect(() => {
const account = billingService.getUserAccount();
setUserAccount(account);
}, []);
const formatCurrency = (cents: number) => {
return `¥${(cents / 100).toFixed(2)}`;
};
const getUserTypeText = (userType: UserType) => {
return userType === UserType.INDIVIDUAL ? '个人用户' : '企业用户';
};
const handleLogout = () => {
const confirmed = confirm('确定要退出登录吗?');
if (confirmed) {
// 这里应该清除用户登录状态
navigate('/login');
}
};
const settingsOptions = [
{
category: '账户信息',
items: [
{
label: '个人资料',
value: userAccount?.id || '未设置',
icon: '👤',
onClick: () => navigate('/mobile/profile'),
},
{
label: '账户类型',
value: userAccount ? getUserTypeText(userAccount.userType) : '未知',
icon: '🏷️',
onClick: () => {},
},
{
label: '账户余额',
value: userAccount ? formatCurrency(userAccount.balance) : '¥0.00',
icon: '💰',
onClick: () => navigate('/mobile/recharge'),
},
],
},
{
category: '通话设置',
items: [
{
label: '默认通话类型',
value: '语音通话',
icon: '📞',
onClick: () => {},
},
{
label: '音质设置',
value: '高清',
icon: '🎵',
onClick: () => {},
},
{
label: '自动录音',
value: '关闭',
icon: '🎙️',
onClick: () => {},
},
],
},
{
category: '翻译设置',
items: [
{
label: '默认源语言',
value: '中文',
icon: '🌐',
onClick: () => {},
},
{
label: '默认目标语言',
value: 'English',
icon: '🌍',
onClick: () => {},
},
{
label: '翻译历史',
value: '查看记录',
icon: '📝',
onClick: () => navigate('/mobile/translation-history'),
},
],
},
{
category: '通知设置',
items: [
{
label: '通话提醒',
value: notificationSettings.callReminder ? '开启' : '关闭',
icon: '🔔',
onClick: () => setNotificationSettings(prev => ({
...prev,
callReminder: !prev.callReminder
})),
},
{
label: '余额提醒',
value: notificationSettings.balanceAlert ? '开启' : '关闭',
icon: '⚠️',
onClick: () => setNotificationSettings(prev => ({
...prev,
balanceAlert: !prev.balanceAlert
})),
},
{
label: '推广消息',
value: notificationSettings.promotions ? '开启' : '关闭',
icon: '📢',
onClick: () => setNotificationSettings(prev => ({
...prev,
promotions: !prev.promotions
})),
},
],
},
{
category: '其他',
items: [
{
label: '帮助中心',
value: '',
icon: '❓',
onClick: () => navigate('/mobile/help'),
},
{
label: '意见反馈',
value: '',
icon: '💬',
onClick: () => navigate('/mobile/feedback'),
},
{
label: '关于我们',
value: 'v1.0.0',
icon: '️',
onClick: () => navigate('/mobile/about'),
},
{
label: '退出登录',
value: '',
icon: '🚪',
onClick: handleLogout,
},
],
},
];
const styles = {
container: {
backgroundColor: '#f5f5f5',
minHeight: '100vh',
paddingBottom: '80px',
},
header: {
backgroundColor: 'white',
padding: '16px',
display: 'flex',
alignItems: 'center',
borderBottom: '1px solid #f0f0f0',
},
backButton: {
background: 'none',
border: 'none',
fontSize: '24px',
cursor: 'pointer',
marginRight: '12px',
},
title: {
fontSize: '20px',
fontWeight: '600' as const,
color: '#333',
},
userCard: {
backgroundColor: 'white',
margin: '16px',
borderRadius: '16px',
padding: '20px',
display: 'flex',
alignItems: 'center',
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)',
},
avatar: {
width: '60px',
height: '60px',
borderRadius: '50%',
backgroundColor: '#1890ff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '24px',
color: 'white',
marginRight: '16px',
},
userInfo: {
flex: 1,
},
userName: {
fontSize: '18px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '4px',
},
userType: {
fontSize: '14px',
color: '#666',
marginBottom: '8px',
},
balance: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#1890ff',
},
section: {
margin: '16px',
marginTop: '8px',
},
sectionTitle: {
fontSize: '16px',
fontWeight: '600' as const,
color: '#333',
marginBottom: '12px',
paddingLeft: '4px',
},
settingsGroup: {
backgroundColor: 'white',
borderRadius: '12px',
overflow: 'hidden',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
},
settingItem: {
padding: '16px 20px',
display: 'flex',
alignItems: 'center',
borderBottom: '1px solid #f8f8f8',
cursor: 'pointer',
transition: 'background-color 0.2s',
},
settingItemLast: {
borderBottom: 'none',
},
settingIcon: {
fontSize: '20px',
marginRight: '12px',
width: '24px',
textAlign: 'center' as const,
},
settingContent: {
flex: 1,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
settingLabel: {
fontSize: '16px',
color: '#333',
},
settingValue: {
fontSize: '14px',
color: '#666',
},
arrow: {
fontSize: '12px',
color: '#ccc',
marginLeft: '8px',
},
};
return (
<div style={styles.container}>
{/* 头部 */}
<div style={styles.header}>
<button style={styles.backButton} onClick={() => navigate(-1)}>
</button>
<h1 style={styles.title}></h1>
</div>
{/* 用户信息卡片 */}
<div style={styles.userCard}>
<div style={styles.avatar}>
👤
</div>
<div style={styles.userInfo}>
<div style={styles.userName}>
{userAccount?.id || '用户'}
</div>
<div style={styles.userType}>
{userAccount ? getUserTypeText(userAccount.userType) : '未知类型'}
</div>
<div style={styles.balance}>
: {userAccount ? formatCurrency(userAccount.balance) : '¥0.00'}
</div>
</div>
</div>
{/* 设置选项 */}
{settingsOptions.map((section, sectionIndex) => (
<div key={sectionIndex} style={styles.section}>
<div style={styles.sectionTitle}>{section.category}</div>
<div style={styles.settingsGroup}>
{section.items.map((item, itemIndex) => (
<div
key={itemIndex}
style={{
...styles.settingItem,
...(itemIndex === section.items.length - 1 ? styles.settingItemLast : {}),
}}
onClick={item.onClick}
>
<div style={styles.settingIcon}>{item.icon}</div>
<div style={styles.settingContent}>
<div style={styles.settingLabel}>{item.label}</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
{item.value && (
<div style={styles.settingValue}>{item.value}</div>
)}
<div style={styles.arrow}></div>
</div>
</div>
</div>
))}
</div>
</div>
))}
</div>
);
};
export default MobileSettings;