<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>資產規劃花費預估</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/antd.min.js"></script>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/reset.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.header {
text-align: center;
margin-bottom: 48px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header h1 {
font-size: 3rem;
font-weight: 800;
margin-bottom: 16px;
letter-spacing: -0.02em;
}
.header p {
font-size: 1.2rem;
color: #6b7280;
font-weight: 400;
}
.input-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 40px;
padding: 32px;
background: linear-gradient(145deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 20px;
border: 1px solid rgba(226, 232, 240, 0.8);
}
.category-section {
grid-column: 1 / -1;
margin: 24px 0 16px 0;
padding: 16px 24px;
border-radius: 16px;
position: relative;
overflow: hidden;
}
.category-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, currentColor, transparent);
}
.category-section h4 {
font-size: 1.25rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 12px;
}
.input-group {
display: flex;
flex-direction: column;
position: relative;
}
.input-group label {
font-weight: 600;
margin-bottom: 12px;
color: #374151;
font-size: 0.95rem;
letter-spacing: 0.025em;
}
.input-group input {
padding: 16px 20px;
border: 2px solid #e5e7eb;
border-radius: 12px;
font-size: 16px;
font-weight: 500;
background: white;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
width: 100%;
}
.input-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
}
.input-group input:hover {
border-color: #9ca3af;
}
.summary-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 24px;
margin: 40px 0;
padding: 32px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
position: relative;
overflow: hidden;
}
.summary-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.1"/><circle cx="10" cy="60" r="0.5" fill="white" opacity="0.1"/><circle cx="90" cy="40" r="0.5" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
opacity: 0.3;
}
.summary-item {
text-align: center;
position: relative;
z-index: 1;
}
.summary-item h3 {
margin: 0 0 12px 0;
font-size: 0.9rem;
font-weight: 600;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.summary-item .value {
font-size: 2rem;
font-weight: 800;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.controls {
display: flex;
gap: 32px;
margin: 40px 0;
padding: 24px;
background: linear-gradient(145deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 16px;
border: 1px solid rgba(226, 232, 240, 0.8);
justify-content: center;
}
.control-group {
display: flex;
flex-direction: column;
align-items: center;
}
.control-group label {
font-weight: 600;
margin-bottom: 12px;
color: #374151;
font-size: 0.95rem;
}
.control-group input {
padding: 16px 20px;
border: 2px solid #e5e7eb;
border-radius: 12px;
width: 140px;
font-size: 16px;
font-weight: 600;
text-align: center;
background: white;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.control-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
}
.deposit-suggestion {
background: linear-gradient(135deg, #ff6b6b 0%, #ffa726 100%);
padding: 32px;
border-radius: 20px;
margin: 40px 0;
text-align: center;
color: white;
position: relative;
overflow: hidden;
}
.deposit-suggestion::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
animation: shimmer 3s ease-in-out infinite;
}
@keyframes shimmer {
0%, 100% { transform: translateX(-100%) translateY(-100%) rotate(30deg); }
50% { transform: translateX(100%) translateY(100%) rotate(30deg); }
}
.deposit-suggestion h3 {
margin: 0 0 16px 0;
font-size: 1.5rem;
font-weight: 700;
position: relative;
z-index: 1;
}
.deposit-suggestion p {
margin: 0;
font-size: 1.1rem;
position: relative;
z-index: 1;
}
.deposit-suggestion .highlight {
font-size: 1.5rem;
font-weight: 800;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.deposit-suggestion .note {
margin: 12px 0 0 0;
font-size: 0.9rem;
opacity: 0.9;
}
/* 響應式設計 */
@media (max-width: 768px) {
.container {
padding: 24px;
margin: 10px;
}
.header h1 {
font-size: 2rem;
}
.input-section {
grid-template-columns: 1fr;
padding: 20px;
}
.summary-section {
grid-template-columns: repeat(2, 1fr);
padding: 24px;
}
.controls {
flex-direction: column;
gap: 20px;
}
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
const ExpenseForecast = () => {
const [expenses, setExpenses] = useState({
// 食
餐費: 0,
食材: 0,
外食: 0,
飲料: 0,
零食: 0,
其他: 0,
// 衣
服裝: 0,
鞋子: 0,
配件: 0,
保養品: 0,
化妝品: 0,
其他: 0,
// 住
房租: 0,
水電: 0,
網路: 0,
管理費: 0,
家具: 0,
其他: 0,
// 行
交通費: 0,
油費: 0,
停車費: 0,
保養維修: 0,
保險: 0,
其他: 0,
// 育
學費: 0,
書籍: 0,
課程: 0,
文具: 0,
補習: 0,
育其他: 0,
// 樂
娛樂: 0,
旅遊: 0,
運動: 0,
嗜好: 0,
電影: 0,
其他: 0,
// 自定義
其他1: 0,
其他2: 0,
});
const [basicInfo, setBasicInfo] = useState({
姓名: '',
出生年月日: '',
});
const [retirementAge, setRetirementAge] = useState('');
// 計算年費總和
const yearlyBase = Object.values(expenses).reduce((sum, v) => sum + Number(v), 0) * 12;
// 計算每月需存款金額(假設年利率3%)
const monthlyRate = 0.03 / 12; // 月利率
const monthlyDeposit = yearlyBase * monthlyRate / (Math.pow(1 + monthlyRate, 12) - 1);
// 計算目前年齡
const calculateCurrentAge = (birthDate) => {
if (!birthDate) return 0;
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
};
const currentAge = calculateCurrentAge(basicInfo.出生年月日);
// 計算工作年數
const workingYears = retirementAge ? retirementAge - currentAge : 0;
// 計算未來每年預測
const dataSource = Array.from({ length: workingYears }, (_, i) => {
return {
year: i + 1,
age: currentAge + i,
amount: yearlyBase,
};
});
return (
<div className="container">
<div className="header">
<h1>💰 資產規劃花費預估系統</h1>
<p>輸入每月基本花費,預測未來年度支出趨勢</p>
</div>
<div className="input-section" style={{marginBottom: '24px'}}>
<h3 style={{gridColumn: '1/-1', margin: '0 0 16px 0', color: '#333'}}>基本資料</h3>
<div className="input-group">
<label>姓名</label>
<input
type="text"
value={basicInfo.姓名}
onChange={(e) => setBasicInfo(prev => ({
...prev,
姓名: e.target.value
}))}
placeholder="請輸入姓名"
/>
</div>
<div className="input-group">
<label>出生年月日</label>
<input
type="date"
value={basicInfo.出生年月日}
onChange={(e) => setBasicInfo(prev => ({
...prev,
出生年月日: e.target.value
}))}
/>
</div>
<div className="input-group">
<label>目前年齡</label>
<div style={{
padding: '16px 20px',
border: '2px solid #e5e7eb',
borderRadius: '12px',
fontSize: '16px',
fontWeight: '600',
textAlign: 'center',
background: '#f8fafc',
color: currentAge > 0 ? '#374151' : '#9ca3af'
}}>
{currentAge > 0 ? `${currentAge}歲` : '請先輸入出生年月日'}
</div>
</div>
<div className="input-group">
<label>退休年齡</label>
<input
type="number"
min="50"
max="100"
value={retirementAge}
onChange={(e) => setRetirementAge(e.target.value)}
placeholder="請輸入退休年齡"
/>
</div>
<div style={{
gridColumn: '1/-1',
fontSize: '12px',
color: '#6b7280',
marginTop: '8px',
textAlign: 'center',
padding: '8px',
background: '#f8fafc',
borderRadius: '8px',
border: '1px solid #e5e7eb'
}}>
參考:2025年男性平均壽命 78.5歲,女性平均壽命 84.2歲
</div>
</div>
<div className="input-section">
<h3 style={{gridColumn: '1/-1', margin: '0 0 16px 0', color: '#333'}}>退休後每月基本花費</h3>
{/* 食 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #ff7a00 0%, #ff9a56 100%)', color: 'white'}}>
<h4>🍽️ 食</h4>
</div>
{['餐費', '食材', '外食', '飲料', '零食', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
{/* 衣 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)', color: 'white'}}>
<h4>👕 衣</h4>
</div>
{['服裝', '鞋子', '配件', '保養品', '化妝品', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
{/* 住 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #1890ff 0%, #40a9ff 100%)', color: 'white'}}>
<h4>🏠 住</h4>
</div>
{['房租', '水電', '網路', '管理費', '家具', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
{/* 行 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%)', color: 'white'}}>
<h4>🚗 行</h4>
</div>
{['交通費', '油費', '停車費', '保養維修', '保險', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
{/* 育 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #722ed1 0%, #9254de 100%)', color: 'white'}}>
<h4>📚 育</h4>
</div>
{['學費', '書籍', '課程', '文具', '補習', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
{/* 樂 */}
<div className="category-section" style={{background: 'linear-gradient(135deg, #faad14 0%, #ffc53d 100%)', color: 'white'}}>
<h4>🎮 樂</h4>
</div>
{['娛樂', '旅遊', '運動', '嗜好', '電影', '其他'].map(category => (
<div key={category} className="input-group">
<label>{category}</label>
<input
type="number"
min="0"
value={expenses[category]}
onChange={(e) => setExpenses(prev => ({
...prev,
[category]: Number(e.target.value) || 0
}))}
placeholder="0"
/>
</div>
))}
</div>
<div className="summary-section">
<div className="summary-item">
<h3>年度基本花費</h3>
<div className="value">{yearlyBase.toLocaleString()}</div>
</div>
<div className="summary-item">
<h3>每月需存款</h3>
<div className="value">{Math.round(monthlyDeposit).toLocaleString()}</div>
</div>
<div className="summary-item">
<h3>目前年齡</h3>
<div className="value">{currentAge > 0 ? `${currentAge}歲` : '未設定'}</div>
</div>
<div className="summary-item">
<h3>退休年齡</h3>
<div className="value">{retirementAge ? `${retirementAge}歲` : '未設定'}</div>
</div>
<div className="summary-item">
<h3>工作年數</h3>
<div className="value">{workingYears}年</div>
</div>
</div>
<div className="deposit-suggestion">
<h3>💰 存款建議</h3>
<p>
要達成每年 <strong>{yearlyBase.toLocaleString()}元</strong> 的基本花費,
建議每月存款 <span className="highlight">{Math.round(monthlyDeposit).toLocaleString()}元</span>
</p>
<p className="note">
(假設年利率3%,複利計算)
</p>
</div>
</div>
);
};
ReactDOM.render(<ExpenseForecast />, document.getElementById('root'));
</script>
</body>
</html>