Initial commit: 大型开支决策计算器 V2.1 - 完整的React+Next.js+TypeScript项目,8个维度的购买决策评分系统,4种个性化消费风格支持,现代化响应式UI设计

This commit is contained in:
zihanjian
2025-06-08 11:21:48 +08:00
commit 10073dbbe5
12 changed files with 7064 additions and 0 deletions

41
.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Dependencies
/node_modules
/.pnp
.pnp.js
# Testing
/coverage
# Next.js
/.next/
/out/
# Production
/build
# Misc
.DS_Store
*.pem
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Local env files
.env*.local
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
next-env.d.ts
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db

76
README.md Normal file
View File

@@ -0,0 +1,76 @@
# 大型开支决策计算器
一个帮助您理性决策、明智消费的工具。通过多维度评分系统,为您的大型开支提供科学的决策建议。
## 功能特点
- **多维度评估**从功能必要性、经济合理性、生命周期收益等8个维度进行综合评分
- **个性化设置**:支持标准型、保守型、效率型、品质型等不同消费风格
- **智能建议**:根据总分提供详细的购买建议和冷静期检查
- **直观界面**:现代化的响应式设计,支持移动端访问
## 快速开始
### 安装依赖
```bash
npm install
```
### 运行开发服务器
```bash
npm run dev
```
打开 [http://localhost:3000](http://localhost:3000) 查看应用。
### 构建生产版本
```bash
npm run build
npm start
```
## 技术栈
- **React 18** - 用户界面框架
- **Next.js 14** - 全栈 React 框架
- **TypeScript** - 类型安全的 JavaScript
- **Tailwind CSS** - 实用优先的 CSS 框架
- **Lucide Icons** - 美观的图标库
## 使用说明
1. **填写产品信息**:输入产品名称、价格和类型
2. **选择消费风格**:根据个人偏好选择合适的评分权重
3. **进行多维度评分**:对每个维度进行 0-5 分的评分
4. **查看决策建议**:系统会根据总分给出购买建议
5. **冷静期检查**:遵循系统提供的理性检查清单
## 评分维度
- **A. 功能必要性** (23分):基础需求匹配、使用频率、可替代性
- **B. 经济合理性** (23分):性价比、生命周期成本、现金流占用、机会成本
- **C. 生命周期收益** (18分):使用寿命、保值率、技术迭代风险
- **D. 效率与时间价值** (9分):直接效率提升、间接收益
- **E. 心理与生活质量** (11分):情绪价值、社交价值、家庭接受度
- **F. 风险与压力** (10分):资金压力、维护风险、价格波动风险
- **G. 时机与环境适配** (6分):购买时机、环境匹配
- **H. 学习与健康成本** (5分):学习成本、健康影响、隐私风险
## 决策建议
- **85+分**:🟢 闭眼入手
- **75-84分**:🟢 放心购买
- **65-74分**:🟡 择机购买
- **50-64分**:🟠 谨慎观望
- **<50分**:🔴 建议放弃
## 贡献指南
欢迎提交 Issue 和 Pull Request 来改进这个项目。
## 许可证
MIT License

7
next.config.js Normal file
View File

@@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
module.exports = nextConfig

6086
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
package.json Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "purchase-decision-calculator",
"version": "1.0.0",
"description": "大型开支决策计算器",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"next": "^14.0.0",
"lucide-react": "^0.294.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/node": "^20.0.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"tailwindcss": "^3.3.0",
"autoprefixer": "^10.4.16",
"postcss": "^8.4.31",
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0"
}
}

733
page.tsx Normal file
View File

@@ -0,0 +1,733 @@
import React, { useState, useMemo } from 'react';
import { Calculator, ShoppingCart, TrendingUp, Shield, Clock, Heart, AlertTriangle, Settings } from 'lucide-react';
const PurchaseDecisionCalculator = () => {
const [productName, setProductName] = useState('');
const [productPrice, setProductPrice] = useState('');
const [productType, setProductType] = useState('digital'); // digital, appliance, fitness, luxury, other
const [personalityType, setPersonalityType] = useState('standard'); // standard, conservative, efficiency, quality
// 评分状态
const [scores, setScores] = useState({
A1: 0, A2: 0, A3: 0,
B1: 0, B2: 0, B3: 0, B4: 0,
C1: 0, C2: 0, C3: 0,
D1: 0, D2: 0,
E1: 0, E2: 0, E3: 0,
F1: 0, F2: 0, F3: 0,
G1: 0, G2: 0,
H1: 0, H2: 0, H3: 0
});
// 特殊调整
const [adjustments, setAdjustments] = useState({
urgent: false, // 紧急需求 +1
discount: false, // 限时优惠 +1
brand: false, // 品牌信仰 +1
gift: false, // 送礼需求 +5
investment: false // 投资属性 +1
});
// 基础权重
const baseWeights = {
A1: 2, A2: 2, A3: 0.6,
B1: 2, B2: 2, B3: 0.4, B4: 0.2,
C1: 2, C2: 0.8, C3: 0.8,
D1: 1, D2: 0.8,
E1: 1, E2: 0.8, E3: 0.4,
F1: 0.4, F2: 0.8, F3: 0.8,
G1: 0.6, G2: 0.6,
H1: 0.4, H2: 0.4, H3: 0.2
};
// 个性化权重调整
const personalityWeights = {
conservative: {
A1: 2.5, A2: 2.5, A3: 0.8,
B1: 2.5, B2: 2.5, B3: 0.6, B4: 0.4,
C1: 2.5, C2: 1.0, C3: 1.0,
D1: 0.5, D2: 0.4,
E1: 0.5, E2: 0.4, E3: 0.2,
F1: 0.6, F2: 1.0, F3: 1.0,
G1: 0.4, G2: 0.4,
H1: 0.2, H2: 0.2, H3: 0.1
},
efficiency: {
A1: 1.5, A2: 1.5, A3: 0.4,
B1: 1.5, B2: 1.5, B3: 0.3, B4: 0.1,
C1: 1.5, C2: 0.6, C3: 0.6,
D1: 2.0, D2: 1.6,
E1: 1.5, E2: 1.2, E3: 0.6,
F1: 0.4, F2: 0.8, F3: 0.8,
G1: 0.8, G2: 0.8,
H1: 0.4, H2: 0.4, H3: 0.2
},
quality: {
A1: 1.8, A2: 1.8, A3: 0.5,
B1: 1.5, B2: 1.5, B3: 0.3, B4: 0.1,
C1: 2.5, C2: 1.0, C3: 1.0,
D1: 1.0, D2: 0.8,
E1: 1.5, E2: 1.2, E3: 0.6,
F1: 0.2, F2: 0.6, F3: 0.6,
G1: 0.5, G2: 0.5,
H1: 0.3, H2: 0.3, H3: 0.1
}
};
// 获取当前权重
const getCurrentWeights = () => {
if (personalityType === 'standard') return baseWeights;
return personalityWeights[personalityType];
};
// 计算总分
const calculateScore = useMemo(() => {
const weights = getCurrentWeights();
let baseScore = 0;
// 基础分数计算
Object.keys(weights).forEach(key => {
if (key === 'H3' && productType !== 'digital') {
// 非数码产品H3默认满分
baseScore += 5 * weights[key];
} else {
baseScore += scores[key] * weights[key];
}
});
// 特殊调整
let adjustmentScore = 0;
if (adjustments.urgent) adjustmentScore += 2; // A1 +1分
if (adjustments.discount) adjustmentScore += 2; // B1 +1分
if (adjustments.brand) adjustmentScore += 0.8; // E2 +1分
if (adjustments.gift) adjustmentScore += 5; // E维度权重临时提升
if (adjustments.investment) adjustmentScore += 0.8; // C2 +1分
return Math.min(100, baseScore + adjustmentScore);
}, [scores, adjustments, productType, personalityType]);
// 获取决策建议
const getDecisionAdvice = (score) => {
if (score >= 85) return { level: 'excellent', text: '🟢 闭眼入手', desc: '无需犹豫,遇到好价格立即下单', color: 'bg-green-500' };
if (score >= 75) return { level: 'good', text: '🟢 放心购买', desc: '可安心购买,适当比价即可', color: 'bg-green-400' };
if (score >= 65) return { level: 'caution', text: '🟡 择机购买', desc: '等待促销、货比三家、考虑替代', color: 'bg-yellow-400' };
if (score >= 50) return { level: 'wait', text: '🟠 谨慎观望', desc: '继续研究、延期3个月、寻找替代', color: 'bg-orange-400' };
return { level: 'avoid', text: '🔴 建议放弃', desc: '暂缓购买,重新评估真实需求', color: 'bg-red-500' };
};
const handleScoreChange = (dimension, value) => {
setScores(prev => ({ ...prev, [dimension]: parseInt(value) || 0 }));
};
const handleAdjustmentChange = (type) => {
setAdjustments(prev => ({ ...prev, [type]: !prev[type] }));
};
const resetScores = () => {
setScores(Object.fromEntries(Object.keys(scores).map(key => [key, 0])) as typeof scores);
setAdjustments({
urgent: false, discount: false, brand: false, gift: false, investment: false
});
};
const advice = getDecisionAdvice(calculateScore);
// 评分选项定义
const scoreOptions = {
A1: [
{ value: 0, label: "纯想要" },
{ value: 1, label: "锦上添花" },
{ value: 2, label: "有点用处" },
{ value: 3, label: "较实用" },
{ value: 4, label: "很有用" },
{ value: 5, label: "强刚需" }
],
A2: [
{ value: 0, label: "几乎不用" },
{ value: 1, label: "偶尔使用(月1-2次)" },
{ value: 2, label: "一般使用(周1-2次)" },
{ value: 3, label: "较频繁(隔天使用)" },
{ value: 4, label: "高频使用(每天)" },
{ value: 5, label: "多次使用/天" }
],
A3: [
{ value: 0, label: "多种免费替代" },
{ value: 1, label: "有便宜替代" },
{ value: 2, label: "替代品一般" },
{ value: 3, label: "替代品较差" },
{ value: 4, label: "少量替代" },
{ value: 5, label: "无可替代" }
],
B1: [
{ value: 0, label: "明显超贵" },
{ value: 1, label: "偏贵" },
{ value: 2, label: "价格一般" },
{ value: 3, label: "性价比可以" },
{ value: 4, label: "性价比不错" },
{ value: 5, label: "性价比极高" }
],
B2: [
{ value: 0, label: ">年收入20%" },
{ value: 1, label: "年收入15-20%" },
{ value: 2, label: "年收入10-15%" },
{ value: 3, label: "年收入6-10%" },
{ value: 4, label: "年收入3-6%" },
{ value: 5, label: "<年收入3%" }
],
B3: [
{ value: 0, label: "影响生活质量" },
{ value: 1, label: "占用应急资金" },
{ value: 2, label: "需要分期付款" },
{ value: 3, label: "稍有压力" },
{ value: 4, label: "小有富余" },
{ value: 5, label: "完全无压力" }
],
B4: [
{ value: 0, label: "错失高收益投资" },
{ value: 1, label: "影响定期投资" },
{ value: 2, label: "延迟理财计划" },
{ value: 3, label: "机会成本一般" },
{ value: 4, label: "机会成本很小" },
{ value: 5, label: "无机会成本" }
],
C1: [
{ value: 0, label: "不到1年" },
{ value: 1, label: "1-2年" },
{ value: 2, label: "2-3年" },
{ value: 3, label: "3-5年" },
{ value: 4, label: "5-8年" },
{ value: 5, label: "8年以上" }
],
C2: [
{ value: 0, label: "保值率<10%" },
{ value: 1, label: "保值率10-20%" },
{ value: 2, label: "保值率20-30%" },
{ value: 3, label: "保值率30-50%" },
{ value: 4, label: "保值率50-70%" },
{ value: 5, label: "保值率>70%" }
],
C3: [
{ value: 0, label: "半年一代新品" },
{ value: 1, label: "年度更新换代" },
{ value: 2, label: "2年一代" },
{ value: 3, label: "技术相对成熟" },
{ value: 4, label: "迭代速度缓慢" },
{ value: 5, label: "技术非常稳定" }
],
D1: [
{ value: 0, label: "无效率提升" },
{ value: 1, label: "微小提升" },
{ value: 2, label: "小幅提升" },
{ value: 3, label: "明显提升" },
{ value: 4, label: "大幅提升" },
{ value: 5, label: "显著提升" }
],
D2: [
{ value: 0, label: "无收益" },
{ value: 1, label: "小幅改善" },
{ value: 2, label: "改善健康" },
{ value: 3, label: "提升技能" },
{ value: 4, label: "节省成本" },
{ value: 5, label: "带来收入" }
],
E1: [
{ value: 0, label: "无感觉" },
{ value: 1, label: "略有愉悦" },
{ value: 2, label: "比较开心" },
{ value: 3, label: "很开心" },
{ value: 4, label: "非常满足" },
{ value: 5, label: "极大快乐" }
],
E2: [
{ value: 0, label: "负面影响" },
{ value: 1, label: "无影响" },
{ value: 2, label: "略有帮助" },
{ value: 3, label: "有一定帮助" },
{ value: 4, label: "较好提升" },
{ value: 5, label: "显著提升" }
],
E3: [
{ value: 0, label: "强烈反对" },
{ value: 1, label: "不太支持" },
{ value: 2, label: "中性态度" },
{ value: 3, label: "比较支持" },
{ value: 4, label: "很支持" },
{ value: 5, label: "非常支持" }
],
F1: [
{ value: 0, label: ">月收入150%" },
{ value: 1, label: "月收入120-150%" },
{ value: 2, label: "月收入80-120%" },
{ value: 3, label: "月收入50-80%" },
{ value: 4, label: "月收入30-50%" },
{ value: 5, label: "<月收入30%" }
],
F2: [
{ value: 0, label: "故障率高且维修贵" },
{ value: 1, label: "维护成本较高" },
{ value: 2, label: "维护成本一般" },
{ value: 3, label: "维护成本便宜" },
{ value: 4, label: "很少需要维护" },
{ value: 5, label: "几乎免维护" }
],
F3: [
{ value: 0, label: "近期必大跌价" },
{ value: 1, label: "可能跌价20%+" },
{ value: 2, label: "小幅波动" },
{ value: 3, label: "价格稳定" },
{ value: 4, label: "持续涨价" },
{ value: 5, label: "限量稀缺品" }
],
G1: [
{ value: 0, label: "时机很差" },
{ value: 1, label: "时机不佳" },
{ value: 2, label: "时机一般" },
{ value: 3, label: "时机较好" },
{ value: 4, label: "时机很好" },
{ value: 5, label: "最佳时机" }
],
G2: [
{ value: 0, label: "完全不匹配" },
{ value: 1, label: "勉强够用" },
{ value: 2, label: "基本适合" },
{ value: 3, label: "比较合适" },
{ value: 4, label: "很合适" },
{ value: 5, label: "完美匹配" }
],
H1: [
{ value: 0, label: "很难学会" },
{ value: 1, label: "需要专门培训" },
{ value: 2, label: "需要学习研究" },
{ value: 3, label: "较容易上手" },
{ value: 4, label: "很容易使用" },
{ value: 5, label: "零学习成本" }
],
H2: [
{ value: 0, label: "明显有害健康" },
{ value: 1, label: "可能有害" },
{ value: 2, label: "健康中性" },
{ value: 3, label: "略有益健康" },
{ value: 4, label: "较有益健康" },
{ value: 5, label: "明显有益健康" }
],
H3: [
{ value: 0, label: "隐私高风险" },
{ value: 1, label: "隐私中等风险" },
{ value: 2, label: "隐私低风险" },
{ value: 3, label: "相对安全" },
{ value: 4, label: "很安全" },
{ value: 5, label: "完全安全" }
]
};
const ScoreInput = ({ dimension, label, description }) => {
const options = scoreOptions[dimension] || [];
return (
<div className="mb-4 p-3 bg-gray-50 rounded-lg">
<div className="mb-2">
<label className="font-medium text-gray-700 block mb-2">{dimension}. {label}</label>
<select
value={scores[dimension]}
onChange={(e) => handleScoreChange(dimension, e.target.value)}
className="w-full px-3 py-2 border rounded-lg text-sm"
>
<option value="">...</option>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
{description && <p className="text-xs text-gray-500">{description}</p>}
</div>
);
};
return (
<div className="max-w-4xl mx-auto p-6 bg-white">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-800 mb-2 flex items-center justify-center gap-2">
<Calculator className="text-blue-600" />
V2.1
</h1>
<p className="text-gray-600"></p>
</div>
{/* 产品信息 */}
<div className="bg-blue-50 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<ShoppingCart className="text-blue-600" />
</h2>
<div className="grid md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
type="text"
value={productName}
onChange={(e) => setProductName(e.target.value)}
placeholder="如戴森V15吸尘器"
className="w-full px-3 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"> (¥)</label>
<input
type="number"
value={productPrice}
onChange={(e) => setProductPrice(e.target.value)}
placeholder="4999"
className="w-full px-3 py-2 border rounded-lg"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"></label>
<select
value={productType}
onChange={(e) => setProductType(e.target.value)}
className="w-full px-3 py-2 border rounded-lg"
>
<option value="digital">3C</option>
<option value="appliance"></option>
<option value="fitness"></option>
<option value="luxury">/</option>
<option value="other"></option>
</select>
</div>
</div>
</div>
{/* 个性化设置 */}
<div className="bg-purple-50 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<Settings className="text-purple-600" />
</h2>
<div className="mb-4">
<label className="block text-sm font-medium mb-2"></label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
{[
{ value: 'standard', label: '标准型', desc: '均衡考虑各项因素' },
{ value: 'conservative', label: '保守型', desc: '重视必要性和经济性' },
{ value: 'efficiency', label: '效率型', desc: '愿意为效率付费' },
{ value: 'quality', label: '品质型', desc: '追求长期价值' }
].map(type => (
<label key={type.value} className="cursor-pointer">
<input
type="radio"
name="personality"
value={type.value}
checked={personalityType === type.value}
onChange={(e) => setPersonalityType(e.target.value)}
className="sr-only"
/>
<div className={`p-3 border-2 rounded-lg text-center transition-colors ${
personalityType === type.value
? 'border-purple-500 bg-purple-100'
: 'border-gray-200 hover:border-purple-300'
}`}>
<div className="font-medium">{type.label}</div>
<div className="text-xs text-gray-600">{type.desc}</div>
</div>
</label>
))}
</div>
</div>
<div>
<label className="block text-sm font-medium mb-2"></label>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{[
{ key: 'urgent', label: '紧急需求', desc: '+2分' },
{ key: 'discount', label: '限时优惠', desc: '+2分' },
{ key: 'brand', label: '品牌信仰', desc: '+0.8分' },
{ key: 'gift', label: '送礼需求', desc: '+5分' },
{ key: 'investment', label: '投资属性', desc: '+0.8分' }
].map(adj => (
<label key={adj.key} className="cursor-pointer">
<input
type="checkbox"
checked={adjustments[adj.key]}
onChange={() => handleAdjustmentChange(adj.key)}
className="sr-only"
/>
<div className={`p-2 border-2 rounded-lg text-center transition-colors ${
adjustments[adj.key]
? 'border-purple-500 bg-purple-100'
: 'border-gray-200 hover:border-purple-300'
}`}>
<div className="text-sm font-medium">{adj.label}</div>
<div className="text-xs text-gray-600">{adj.desc}</div>
</div>
</label>
))}
</div>
</div>
</div>
<div className="grid lg:grid-cols-3 gap-6">
{/* 评分区域 */}
<div className="lg:col-span-2 space-y-6">
{/* A. 功能必要性 */}
<div className="bg-red-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-red-700">A. (23)</h3>
<ScoreInput
dimension="A1"
label="基础需求匹配"
description=""
/>
<ScoreInput
dimension="A2"
label="使用频率预测"
description=""
/>
<ScoreInput
dimension="A3"
label="可替代性"
description=""
/>
</div>
{/* B. 经济合理性 */}
<div className="bg-green-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-green-700">B. (23)</h3>
<ScoreInput
dimension="B1"
label="性价比"
description=""
/>
<ScoreInput
dimension="B2"
label="全生命周期成本"
description=""
/>
<ScoreInput
dimension="B3"
label="现金流占用"
description=""
/>
<ScoreInput
dimension="B4"
label="投资机会成本"
description=""
/>
</div>
{/* C. 生命周期收益 */}
<div className="bg-blue-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-blue-700">C. (18)</h3>
<ScoreInput
dimension="C1"
label="使用寿命与折旧"
description=""
/>
<ScoreInput
dimension="C2"
label="二手保值率"
description=""
/>
<ScoreInput
dimension="C3"
label="技术迭代风险"
description=""
/>
</div>
{/* D. 效率与时间价值 */}
<div className="bg-yellow-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-yellow-700">D. (9)</h3>
<ScoreInput
dimension="D1"
label="直接效率提升"
description=""
/>
<ScoreInput
dimension="D2"
label="间接收益"
description=""
/>
</div>
{/* E. 心理与生活质量 */}
<div className="bg-pink-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-pink-700">E. (11)</h3>
<ScoreInput
dimension="E1"
label="情绪价值"
description=""
/>
<ScoreInput
dimension="E2"
label="社交/形象价值"
description=""
/>
<ScoreInput
dimension="E3"
label="家庭/伴侣接受度"
description=""
/>
</div>
{/* F. 风险与压力 */}
<div className="bg-orange-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-orange-700">F. (10)</h3>
<ScoreInput
dimension="F1"
label="一次性资金压力"
description=""
/>
<ScoreInput
dimension="F2"
label="维护风险"
description=""
/>
<ScoreInput
dimension="F3"
label="价格波动风险"
description=""
/>
</div>
{/* G. 时机与环境适配 */}
<div className="bg-indigo-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-indigo-700">G. (6)</h3>
<ScoreInput
dimension="G1"
label="购买时机"
description=""
/>
<ScoreInput
dimension="G2"
label="空间环境匹配"
description=""
/>
</div>
{/* H. 学习与健康成本 */}
<div className="bg-gray-50 rounded-lg p-6">
<h3 className="text-lg font-semibold mb-4 text-gray-700">H. (5)</h3>
<ScoreInput
dimension="H1"
label="学习适应成本"
description=""
/>
<ScoreInput
dimension="H2"
label="健康安全影响"
description=""
/>
{productType === 'digital' && (
<ScoreInput
dimension="H3"
label="隐私数据风险"
description=""
/>
)}
{productType !== 'digital' && (
<div className="mb-4 p-3 bg-green-100 rounded-lg">
<div className="flex justify-between items-center">
<span className="font-medium text-gray-700">H3. </span>
<span className="px-2 py-1 bg-green-200 rounded text-sm"></span>
</div>
<p className="text-sm text-gray-600"></p>
</div>
)}
</div>
</div>
{/* 结果面板 */}
<div className="space-y-6">
{/* 总分显示 */}
<div className={`${advice.color} rounded-lg p-6 text-white`}>
<div className="text-center">
<div className="text-4xl font-bold mb-2">{calculateScore.toFixed(1)}</div>
<div className="text-xl font-semibold mb-1">{advice.text}</div>
<div className="text-sm opacity-90">{advice.desc}</div>
</div>
</div>
{/* 冷静期检查 */}
{productPrice && (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h4 className="font-semibold text-yellow-800 mb-2 flex items-center gap-2">
<AlertTriangle size={18} />
</h4>
<div className="text-sm text-yellow-700 space-y-1">
{parseInt(productPrice) > 5000 && (
<div> 24</div>
)}
{adjustments.discount && (
<div> 退</div>
)}
{calculateScore >= 75 && calculateScore < 85 && (
<div> </div>
)}
</div>
</div>
)}
{/* 理性检查清单 */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 className="font-semibold text-blue-800 mb-2"></h4>
<div className="text-sm text-blue-700 space-y-2">
<div> </div>
<div> </div>
<div> </div>
<div> </div>
</div>
</div>
{/* 操作按钮 */}
<div className="space-y-3">
<button
onClick={resetScores}
className="w-full px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
</button>
{productName && (
<div className="text-sm text-gray-600 bg-gray-50 p-3 rounded">
<strong>{productName}</strong>
{productPrice && <span> - ¥{productPrice}</span>}
<br />
: {calculateScore.toFixed(1)} | {advice.text}
</div>
)}
</div>
{/* 维度雷达图简化显示 */}
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="font-semibold mb-3"></h4>
<div className="space-y-2 text-sm">
{[
{ name: 'A-必要性', score: (scores.A1*2 + scores.A2*2 + scores.A3*0.6), max: 23 },
{ name: 'B-经济性', score: (scores.B1*2 + scores.B2*2 + scores.B3*0.4 + scores.B4*0.2), max: 23 },
{ name: 'C-周期收益', score: (scores.C1*2 + scores.C2*0.8 + scores.C3*0.8), max: 18 },
{ name: 'D-效率价值', score: (scores.D1*1 + scores.D2*0.8), max: 9 },
{ name: 'E-生活质量', score: (scores.E1*1 + scores.E2*0.8 + scores.E3*0.4), max: 11 },
{ name: 'F-风险压力', score: (scores.F1*0.4 + scores.F2*0.8 + scores.F3*0.8), max: 10 }
].map(dim => (
<div key={dim.name} className="flex items-center gap-2">
<span className="w-16 text-xs">{dim.name}</span>
<div className="flex-1 bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all"
style={{ width: `${Math.min(100, (dim.score / dim.max) * 100)}%` }}
/>
</div>
<span className="text-xs w-12">{dim.score.toFixed(1)}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
export default PurchaseDecisionCalculator;

6
pages/_app.tsx Normal file
View File

@@ -0,0 +1,6 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

18
pages/index.tsx Normal file
View File

@@ -0,0 +1,18 @@
import Head from 'next/head'
import PurchaseDecisionCalculator from '../page'
export default function Home() {
return (
<div>
<Head>
<title></title>
<meta name="description" content="理性决策,明智消费" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<PurchaseDecisionCalculator />
</main>
</div>
)
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

19
styles/globals.css Normal file
View File

@@ -0,0 +1,19 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html, body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
body {
background-color: #f9fafb;
color: #374151;
}
* {
box-sizing: border-box;
}

16
tailwind.config.js Normal file
View File

@@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}