feat: 初始化天选公司 MVP
- 首页、信息录入页、结果页完整流程 - 命理分析规则引擎(五行计算) - 公司类型与岗位方向匹配 - 响应式东方命理风格 UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 预留后续环境变量
|
||||||
|
# VITE_API_BASE_URL=
|
||||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
npm-debug.log*
|
||||||
|
*.tsbuildinfo
|
||||||
|
vite.config.d.ts
|
||||||
88
PRD.md
Normal file
88
PRD.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# 天选公司 PRD v1
|
||||||
|
|
||||||
|
## 产品定位
|
||||||
|
|
||||||
|
天选公司是一款结合东方命理逻辑与职业规划思路的 Web 产品。用户输入出生年月日、时辰、性别、出生地后,系统基于通用八字/五行逻辑生成职业命理画像,并给出适合的公司类型、岗位方向和当前阶段择业建议。
|
||||||
|
|
||||||
|
## 产品目标
|
||||||
|
|
||||||
|
- 帮助用户理解自己更适合什么类型的公司
|
||||||
|
- 帮助用户理解自己更适合什么岗位方向
|
||||||
|
- 提供当前阶段择业建议
|
||||||
|
- 将命理兴趣转化为职业决策参考
|
||||||
|
|
||||||
|
## 目标用户
|
||||||
|
|
||||||
|
- 应届生
|
||||||
|
- 1-5 年职场人
|
||||||
|
- 5 年以上职场人
|
||||||
|
|
||||||
|
## MVP 功能
|
||||||
|
|
||||||
|
1. 出生信息输入
|
||||||
|
2. 命理职业画像生成
|
||||||
|
3. 公司类型匹配
|
||||||
|
4. 岗位类型匹配
|
||||||
|
5. 当前阶段择业建议
|
||||||
|
6. 结果页先展示简版,再展开详版
|
||||||
|
|
||||||
|
## 输入项
|
||||||
|
|
||||||
|
- 出生年月日
|
||||||
|
- 出生时辰
|
||||||
|
- 性别
|
||||||
|
- 出生地
|
||||||
|
|
||||||
|
## 公司类型范围
|
||||||
|
|
||||||
|
- 一线大厂
|
||||||
|
- 上市公司
|
||||||
|
- 独角兽公司
|
||||||
|
- 高增长创业公司
|
||||||
|
- 成熟型中大型企业
|
||||||
|
- 强技术驱动型公司
|
||||||
|
- 强产品驱动型公司
|
||||||
|
- 强销售驱动型公司
|
||||||
|
- 稳定流程型组织
|
||||||
|
- 高压竞争型组织
|
||||||
|
|
||||||
|
## 岗位类型范围
|
||||||
|
|
||||||
|
- 前端开发
|
||||||
|
- 后端开发
|
||||||
|
- 算法 / AI
|
||||||
|
- 测试 / QA
|
||||||
|
- 产品经理
|
||||||
|
- 设计师
|
||||||
|
- 内容运营
|
||||||
|
- 用户运营
|
||||||
|
- 增长运营
|
||||||
|
- 销售
|
||||||
|
- 商务拓展
|
||||||
|
- HR
|
||||||
|
- 财务
|
||||||
|
- 项目管理
|
||||||
|
- 管理岗
|
||||||
|
|
||||||
|
## 产品风格
|
||||||
|
|
||||||
|
- 专业
|
||||||
|
- 克制
|
||||||
|
- 东方命理气质
|
||||||
|
- 有神秘感但不过度玄化
|
||||||
|
|
||||||
|
## 首版不做
|
||||||
|
|
||||||
|
- 登录注册
|
||||||
|
- 报告保存
|
||||||
|
- 付费能力
|
||||||
|
- 企业真实岗位抓取
|
||||||
|
- 精准推荐具体公司
|
||||||
|
|
||||||
|
## 下一步开发重点
|
||||||
|
|
||||||
|
1. 首页信息架构细化
|
||||||
|
2. 表单页设计
|
||||||
|
3. 结果页结构设计
|
||||||
|
4. 命理标签映射设计
|
||||||
|
5. 推荐文案模板设计
|
||||||
65
README.md
65
README.md
@@ -1,3 +1,64 @@
|
|||||||
# tianxuan-company
|
# 天选公司
|
||||||
|
|
||||||
天选公司 - 基于命理推理的职业匹配产品
|
一个基于出生信息、天干地支与命理推理的职业匹配 Web 产品。
|
||||||
|
|
||||||
|
## 当前阶段
|
||||||
|
|
||||||
|
当前仓库为项目初始化版本,已完成:
|
||||||
|
|
||||||
|
- 独立项目目录创建
|
||||||
|
- Web 前端基础骨架搭建
|
||||||
|
- README 与 PRD 文档落地
|
||||||
|
- 首页占位页搭建
|
||||||
|
|
||||||
|
暂未包含:
|
||||||
|
|
||||||
|
- 登录/注册
|
||||||
|
- 报告保存
|
||||||
|
- 付费能力
|
||||||
|
- 真实命理引擎细节
|
||||||
|
- 具体公司库接入
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- Vite
|
||||||
|
- React
|
||||||
|
- TypeScript
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
tianxuan-company/
|
||||||
|
├── README.md
|
||||||
|
├── PRD.md
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── tsconfig.app.json
|
||||||
|
├── tsconfig.node.json
|
||||||
|
├── vite.config.ts
|
||||||
|
├── index.html
|
||||||
|
└── src/
|
||||||
|
├── App.tsx
|
||||||
|
├── main.tsx
|
||||||
|
├── pages/
|
||||||
|
├── components/
|
||||||
|
├── data/
|
||||||
|
├── lib/
|
||||||
|
├── styles/
|
||||||
|
└── types/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 本地启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. 设计信息填写页
|
||||||
|
2. 设计结果页结构
|
||||||
|
3. 建立命理标签与职业映射规则
|
||||||
|
4. 补充页面交互流
|
||||||
|
5. 在确认后创建 Gitea 仓库并推送
|
||||||
|
|||||||
16
index.html
Normal file
16
index.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>天选公司</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="基于出生信息、天干地支与命理推理,为用户生成公司类型与岗位方向匹配建议。"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1820
package-lock.json
generated
Normal file
1820
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "tianxuan-company",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.1.1",
|
||||||
|
"react-dom": "^19.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.1.13",
|
||||||
|
"@types/react-dom": "^19.1.9",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"typescript": "^5.9.2",
|
||||||
|
"vite": "^7.1.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/App.tsx
Normal file
39
src/App.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { HomePage } from './pages/HomePage';
|
||||||
|
import { FormPage } from './pages/FormPage';
|
||||||
|
import { ResultPage } from './pages/ResultPage';
|
||||||
|
import type { UserInput, FortuneResult } from './types';
|
||||||
|
|
||||||
|
type Page = 'home' | 'form' | 'result';
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [currentPage, setCurrentPage] = useState<Page>('home');
|
||||||
|
const [userInput, setUserInput] = useState<UserInput | null>(null);
|
||||||
|
const [fortuneResult, setFortuneResult] = useState<FortuneResult | null>(null);
|
||||||
|
|
||||||
|
const goToForm = () => {
|
||||||
|
setCurrentPage('form');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToResult = (input: UserInput, result: FortuneResult) => {
|
||||||
|
setUserInput(input);
|
||||||
|
setFortuneResult(result);
|
||||||
|
setCurrentPage('result');
|
||||||
|
};
|
||||||
|
|
||||||
|
const goHome = () => {
|
||||||
|
setCurrentPage('home');
|
||||||
|
setUserInput(null);
|
||||||
|
setFortuneResult(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentPage === 'form') {
|
||||||
|
return <FormPage onSubmit={goToResult} onBack={goHome} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage === 'result' && userInput && fortuneResult) {
|
||||||
|
return <ResultPage input={userInput} result={fortuneResult} onBack={goHome} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <HomePage onStart={goToForm} />;
|
||||||
|
}
|
||||||
279
src/lib/fortune.ts
Normal file
279
src/lib/fortune.ts
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import type { UserInput, FortuneResult, FortuneProfile, CompanyRecommendation, JobRecommendation, StageAdvice } from '../types';
|
||||||
|
|
||||||
|
// 简化的八字五行计算 - 基于出生年份
|
||||||
|
function calculateWuxing(birthDate: string): { wuxing: FortuneProfile['wuxing'], wuxingLabel: string } {
|
||||||
|
const date = new Date(birthDate);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
|
||||||
|
// 简化版:根据年份天干映射五行
|
||||||
|
const tiangan = year % 10;
|
||||||
|
const tianganMap: Record<number, string> = {
|
||||||
|
0: 'metal', 1: 'metal',
|
||||||
|
2: 'water', 3: 'water',
|
||||||
|
4: 'wood', 5: 'wood',
|
||||||
|
6: 'fire', 7: 'fire',
|
||||||
|
8: 'earth', 9: 'earth'
|
||||||
|
};
|
||||||
|
|
||||||
|
const wuxingKey = tianganMap[tiangan] || 'earth';
|
||||||
|
const labelMap: Record<string, string> = {
|
||||||
|
wood: '木',
|
||||||
|
fire: '火',
|
||||||
|
earth: '土',
|
||||||
|
metal: '金',
|
||||||
|
water: '水'
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
wuxing: wuxingKey as FortuneProfile['wuxing'],
|
||||||
|
wuxingLabel: labelMap[wuxingKey] || '土'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据五行生成命理画像
|
||||||
|
function generateProfile(wuxing: FortuneProfile['wuxing'], wuxingLabel: string): FortuneProfile {
|
||||||
|
const profiles: Record<string, Partial<FortuneProfile>> = {
|
||||||
|
wood: {
|
||||||
|
personality: ['有生发之力', '善于规划与成长', '追求目标坚定'],
|
||||||
|
strengths: ['领导力', '规划能力', '适应力'],
|
||||||
|
weaknesses: ['有时过于刚硬', '易忽略细节'],
|
||||||
|
developmentRhythm: 'growth'
|
||||||
|
},
|
||||||
|
fire: {
|
||||||
|
personality: ['热情洋溢', '执行力强', '喜欢表达'],
|
||||||
|
strengths: ['创造力', '感染力', '行动力'],
|
||||||
|
weaknesses: ['易急躁', '缺乏耐心'],
|
||||||
|
developmentRhythm: 'breakthrough'
|
||||||
|
},
|
||||||
|
earth: {
|
||||||
|
personality: ['稳重务实', '注重规则', '善于协调'],
|
||||||
|
strengths: ['稳定性', '执行力', '资源整合'],
|
||||||
|
weaknesses: ['过于保守', '创新不足'],
|
||||||
|
developmentRhythm: 'stable'
|
||||||
|
},
|
||||||
|
metal: {
|
||||||
|
personality: ['刚毅果断', '追求效率', '原则性强'],
|
||||||
|
strengths: ['决断力', '专业度', '抗压能力'],
|
||||||
|
weaknesses: ['过于严厉', '不够灵活'],
|
||||||
|
developmentRhythm: 'accumulation'
|
||||||
|
},
|
||||||
|
water: {
|
||||||
|
personality: ['灵活变通', '善于沟通', '适应力强'],
|
||||||
|
strengths: ['沟通力', '学习力', '洞察力'],
|
||||||
|
weaknesses: ['易动摇', '缺乏坚持'],
|
||||||
|
developmentRhythm: 'growth'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const profile = profiles[wuxing] || profiles.earth;
|
||||||
|
|
||||||
|
return {
|
||||||
|
wuxing,
|
||||||
|
wuxingLabel,
|
||||||
|
personality: profile.personality || [],
|
||||||
|
strengths: profile.strengths || [],
|
||||||
|
weaknesses: profile.weaknesses || [],
|
||||||
|
developmentRhythm: profile.developmentRhythm || 'stable',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 公司类型推荐
|
||||||
|
function generateCompanyRecommendations(profile: FortuneProfile): CompanyRecommendation[] {
|
||||||
|
const { developmentRhythm } = profile;
|
||||||
|
|
||||||
|
const companyTypes: CompanyRecommendation[] = [
|
||||||
|
{
|
||||||
|
type: '一线大厂',
|
||||||
|
matchScore: 85,
|
||||||
|
reasons: ['资源丰富,平台稳定', '制度完善,适合长期发展', '能发挥你的专业能力'],
|
||||||
|
suitableFor: ['追求稳定发展', '希望积累大平台经验'],
|
||||||
|
notSuitableFor: ['渴望快速晋升', '喜欢灵活工作方式'],
|
||||||
|
stageAdvice: developmentRhythm === 'stable' ? '适合进入积累期' : '可考虑内部晋升机会'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '独角兽公司',
|
||||||
|
matchScore: 78,
|
||||||
|
reasons: ['成长空间大', '能接触核心业务', '晋升机会多'],
|
||||||
|
suitableFor: ['渴望快速成长', '愿意承担风险'],
|
||||||
|
notSuitableFor: ['追求极度稳定', '不喜欢不确定性'],
|
||||||
|
stageAdvice: developmentRhythm === 'growth' ? '非常适合当前阶段' : '需评估团队稳定性'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '高增长创业公司',
|
||||||
|
matchScore: 72,
|
||||||
|
reasons: ['决策灵活', '成长速度快', '能全面锻炼能力'],
|
||||||
|
suitableFor: ['敢于冒险', '希望快速晋升'],
|
||||||
|
notSuitableFor: ['喜欢按部就班', '需要明确指引'],
|
||||||
|
stageAdvice: '适合寻求突破的年轻人'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '强技术驱动型公司',
|
||||||
|
matchScore: 80,
|
||||||
|
reasons: ['重视技术能力', '氛围开放', '创新机会多'],
|
||||||
|
suitableFor: ['技术导向', '喜欢钻研'],
|
||||||
|
notSuitableFor: ['销售导向', '不喜欢深度工作'],
|
||||||
|
stageAdvice: '技术路线发展的理想选择'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '强产品驱动型公司',
|
||||||
|
matchScore: 76,
|
||||||
|
reasons: ['用户体验为核心', '重视创新', '成长空间大'],
|
||||||
|
suitableFor: ['产品思维', '用户导向'],
|
||||||
|
notSuitableFor: ['纯执行导向', '不喜欢思考'],
|
||||||
|
stageAdvice: '产品路线发展的好平台'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: '稳定流程型组织',
|
||||||
|
matchScore: 68,
|
||||||
|
reasons: ['工作稳定', '流程规范', '压力适中'],
|
||||||
|
suitableFor: ['追求工作生活平衡', '喜欢清晰职责'],
|
||||||
|
notSuitableFor: ['渴望快速变化', '喜欢创新'],
|
||||||
|
stageAdvice: developmentRhythm === 'stable' ? '适合当前阶段' : '可能限制发展'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return companyTypes
|
||||||
|
.sort((a, b) => b.matchScore - a.matchScore)
|
||||||
|
.slice(0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 岗位类型推荐
|
||||||
|
function generateJobRecommendations(profile: FortuneProfile): JobRecommendation[] {
|
||||||
|
|
||||||
|
const jobTypes: JobRecommendation[] = [
|
||||||
|
{
|
||||||
|
category: '技术研发',
|
||||||
|
matchScore: 88,
|
||||||
|
reasons: ['你的特质适合深耕技术', '需要专业能力和持续学习'],
|
||||||
|
suitableWorkStyle: ['独立钻研', '解决复杂问题'],
|
||||||
|
notSuitableFor: ['频繁社交', '简单重复工作']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '产品经理',
|
||||||
|
matchScore: 82,
|
||||||
|
reasons: ['需要平衡多方需求', '注重规划和沟通'],
|
||||||
|
suitableWorkStyle: ['协调资源', '用户洞察'],
|
||||||
|
notSuitableFor: ['纯技术执行', '不喜欢沟通']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '运营',
|
||||||
|
matchScore: 79,
|
||||||
|
reasons: ['需要数据分析能力', '注重用户增长'],
|
||||||
|
suitableWorkStyle: ['数据分析', '活动策划'],
|
||||||
|
notSuitableFor: ['极度内向', '不喜欢变化']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '销售商务',
|
||||||
|
matchScore: 75,
|
||||||
|
reasons: ['需要人际沟通能力', '结果导向'],
|
||||||
|
suitableWorkStyle: ['人际交往', '资源整合'],
|
||||||
|
notSuitableFor: ['不喜欢竞争', '害怕被拒绝']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '设计',
|
||||||
|
matchScore: 77,
|
||||||
|
reasons: ['需要审美和创造力', '注重用户体验'],
|
||||||
|
suitableWorkStyle: ['视觉表达', '创意工作'],
|
||||||
|
notSuitableFor: ['不喜欢抽象思维', '追求确定答案']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: '管理岗',
|
||||||
|
matchScore: 73,
|
||||||
|
reasons: ['需要领导力和规划能力', '注重团队发展'],
|
||||||
|
suitableWorkStyle: ['团队管理', '战略规划'],
|
||||||
|
notSuitableFor: ['喜欢独立工作', '不喜欢处理人事']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据五行微调分数
|
||||||
|
if (profile.wuxing === 'wood') {
|
||||||
|
jobTypes.find(j => j.category === '管理岗')!.matchScore += 5;
|
||||||
|
jobTypes.find(j => j.category === '产品经理')!.matchScore += 3;
|
||||||
|
} else if (profile.wuxing === 'fire') {
|
||||||
|
jobTypes.find(j => j.category === '销售商务')!.matchScore += 5;
|
||||||
|
jobTypes.find(j => j.category === '运营')!.matchScore += 3;
|
||||||
|
} else if (profile.wuxing === 'water') {
|
||||||
|
jobTypes.find(j => j.category === '运营')!.matchScore += 5;
|
||||||
|
jobTypes.find(j => j.category === '产品经理')!.matchScore += 3;
|
||||||
|
} else if (profile.wuxing === 'metal') {
|
||||||
|
jobTypes.find(j => j.category === '技术研发')!.matchScore += 5;
|
||||||
|
jobTypes.find(j => j.category === '管理岗')!.matchScore += 3;
|
||||||
|
} else if (profile.wuxing === 'earth') {
|
||||||
|
jobTypes.find(j => j.category === '运营')!.matchScore += 3;
|
||||||
|
jobTypes.find(j => j.category === '管理岗')!.matchScore += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobTypes
|
||||||
|
.sort((a, b) => b.matchScore - a.matchScore)
|
||||||
|
.slice(0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成当前阶段建议
|
||||||
|
function generateStageAdvice(profile: FortuneProfile): StageAdvice {
|
||||||
|
const { developmentRhythm, wuxingLabel } = profile;
|
||||||
|
|
||||||
|
const adviceMap: Record<string, StageAdvice> = {
|
||||||
|
stable: {
|
||||||
|
overall: `你目前处于稳定积累期,五行属${wuxingLabel},适合稳扎稳打。`,
|
||||||
|
careerDirection: '建议在现有岗位上深耕,积累专业能力。',
|
||||||
|
jobChangeTiming: '当前不建议频繁变动,保持稳定是更好的选择。',
|
||||||
|
platformPreference: '优先选择成熟稳定的大平台。',
|
||||||
|
keyObservations: [
|
||||||
|
'关注岗位的成长空间而非只看薪资',
|
||||||
|
'建立专业壁垒是关键',
|
||||||
|
'可以开始培养管理能力'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
growth: {
|
||||||
|
overall: `你目前处于成长期,五行属${wuxingLabel},有较强的上升动力。`,
|
||||||
|
careerDirection: '适合寻求晋升机会或承担更大责任。',
|
||||||
|
jobChangeTiming: '可以开始关注外部机会,但不要急于变动。',
|
||||||
|
platformPreference: '独角兽或高速成长公司可能更适合你。',
|
||||||
|
keyObservations: [
|
||||||
|
'主动争取项目机会',
|
||||||
|
'扩大影响力范围',
|
||||||
|
'开始建立个人品牌'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
breakthrough: {
|
||||||
|
overall: `你目前处于突破期,五行属${wuxingLabel},适合做出重大改变。`,
|
||||||
|
careerDirection: '可以考虑转型或创业的机会。',
|
||||||
|
jobChangeTiming: '现在是尝试新方向的好时机。',
|
||||||
|
platformPreference: '创业公司或新业务部门可能更适合你。',
|
||||||
|
keyObservations: [
|
||||||
|
'勇敢尝试新领域',
|
||||||
|
'不要害怕失败',
|
||||||
|
'整合资源是关键'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
accumulation: {
|
||||||
|
overall: `你目前处于沉淀期,五行属${wuxingLabel},需要耐心积累。`,
|
||||||
|
careerDirection: '适合深耕现有领域,等待时机。',
|
||||||
|
jobChangeTiming: '建议先完成当前阶段的积累。',
|
||||||
|
platformPreference: '稳定平台更适合当前状态。',
|
||||||
|
keyObservations: [
|
||||||
|
'专注提升专业深度',
|
||||||
|
'建立行业人脉',
|
||||||
|
'等待合适的机会'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return adviceMap[developmentRhythm] || adviceMap.stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主分析函数
|
||||||
|
export function analyzeFortune(input: UserInput): FortuneResult {
|
||||||
|
const { wuxing, wuxingLabel } = calculateWuxing(input.birthDate);
|
||||||
|
const profile = generateProfile(wuxing, wuxingLabel);
|
||||||
|
const companyRecommendations = generateCompanyRecommendations(profile);
|
||||||
|
const jobRecommendations = generateJobRecommendations(profile);
|
||||||
|
const stageAdvice = generateStageAdvice(profile);
|
||||||
|
|
||||||
|
return {
|
||||||
|
profile,
|
||||||
|
companyRecommendations,
|
||||||
|
jobRecommendations,
|
||||||
|
stageAdvice
|
||||||
|
};
|
||||||
|
}
|
||||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import './styles/index.css';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
160
src/pages/FormPage.tsx
Normal file
160
src/pages/FormPage.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { UserInput, FortuneResult } from '../types';
|
||||||
|
import { analyzeFortune } from '../lib/fortune';
|
||||||
|
|
||||||
|
interface FormPageProps {
|
||||||
|
onSubmit: (input: UserInput, result: FortuneResult) => void;
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FormPage({ onSubmit, onBack }: FormPageProps) {
|
||||||
|
const [birthDate, setBirthDate] = useState('');
|
||||||
|
const [birthTime, setBirthTime] = useState('');
|
||||||
|
const [gender, setGender] = useState<'male' | 'female'>('male');
|
||||||
|
const [birthPlace, setBirthPlace] = useState('');
|
||||||
|
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!birthDate || !birthTime || !birthPlace) return;
|
||||||
|
|
||||||
|
setIsAnalyzing(true);
|
||||||
|
|
||||||
|
// 模拟分析过程
|
||||||
|
setTimeout(() => {
|
||||||
|
const input: UserInput = {
|
||||||
|
birthDate,
|
||||||
|
birthTime,
|
||||||
|
gender,
|
||||||
|
birthPlace
|
||||||
|
};
|
||||||
|
const result = analyzeFortune(input);
|
||||||
|
onSubmit(input, result);
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isValid = birthDate && birthTime && birthPlace;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="page-shell">
|
||||||
|
<div className="form-section">
|
||||||
|
<header className="form-header">
|
||||||
|
<button type="button" className="back-btn" onClick={onBack}>
|
||||||
|
← 返回
|
||||||
|
</button>
|
||||||
|
<h1 className="form-title">输入你的信息</h1>
|
||||||
|
<p className="form-subtitle">
|
||||||
|
我们会根据你的出生信息,推算你的职业命理画像
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form className="form-card" onSubmit={handleSubmit}>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="birthDate" className="form-label">
|
||||||
|
出生日期 <span className="required">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="birthDate"
|
||||||
|
className="form-input"
|
||||||
|
value={birthDate}
|
||||||
|
onChange={(e) => setBirthDate(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="birthTime" className="form-label">
|
||||||
|
出生时辰 <span className="required">*</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="birthTime"
|
||||||
|
className="form-input form-select"
|
||||||
|
value={birthTime}
|
||||||
|
onChange={(e) => setBirthTime(e.target.value)}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="">请选择时辰</option>
|
||||||
|
<option value="00:00">子时 (23:00-01:00)</option>
|
||||||
|
<option value="02:00">丑时 (01:00-03:00)</option>
|
||||||
|
<option value="04:00">寅时 (03:00-05:00)</option>
|
||||||
|
<option value="06:00">卯时 (05:00-07:00)</option>
|
||||||
|
<option value="08:00">辰时 (07:00-09:00)</option>
|
||||||
|
<option value="10:00">巳时 (09:00-11:00)</option>
|
||||||
|
<option value="12:00">午时 (11:00-13:00)</option>
|
||||||
|
<option value="14:00">未时 (13:00-15:00)</option>
|
||||||
|
<option value="16:00">申时 (15:00-17:00)</option>
|
||||||
|
<option value="18:00">酉时 (17:00-19:00)</option>
|
||||||
|
<option value="20:00">戌时 (19:00-21:00)</option>
|
||||||
|
<option value="22:00">亥时 (21:00-23:00)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label">
|
||||||
|
性别 <span className="required">*</span>
|
||||||
|
</label>
|
||||||
|
<div className="radio-group">
|
||||||
|
<label className="radio-label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="gender"
|
||||||
|
value="male"
|
||||||
|
checked={gender === 'male'}
|
||||||
|
onChange={() => setGender('male')}
|
||||||
|
/>
|
||||||
|
<span className="radio-custom"></span>
|
||||||
|
男
|
||||||
|
</label>
|
||||||
|
<label className="radio-label">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="gender"
|
||||||
|
value="female"
|
||||||
|
checked={gender === 'female'}
|
||||||
|
onChange={() => setGender('female')}
|
||||||
|
/>
|
||||||
|
<span className="radio-custom"></span>
|
||||||
|
女
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="birthPlace" className="form-label">
|
||||||
|
出生地 <span className="required">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="birthPlace"
|
||||||
|
className="form-input"
|
||||||
|
placeholder="例如:上海、北京、广州"
|
||||||
|
value={birthPlace}
|
||||||
|
onChange={(e) => setBirthPlace(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-notice">
|
||||||
|
<p>※ 您的信息仅用于本次命理分析,不会保存</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary btn-full"
|
||||||
|
disabled={!isValid || isAnalyzing}
|
||||||
|
>
|
||||||
|
{isAnalyzing ? (
|
||||||
|
<span className="loading-state">
|
||||||
|
<span className="loading-dot"></span>
|
||||||
|
分析中...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
'开始分析'
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
94
src/pages/HomePage.tsx
Normal file
94
src/pages/HomePage.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface HomePageProps {
|
||||||
|
onStart: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HomePage({ onStart }: HomePageProps) {
|
||||||
|
const [showIntro, setShowIntro] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="page-shell">
|
||||||
|
<div className="hero-section">
|
||||||
|
<header className="hero-header">
|
||||||
|
<span className="brand-tag">东方命理 × 职业选择</span>
|
||||||
|
<h1 className="hero-title">
|
||||||
|
<span className="title-main">天选公司</span>
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<p className="hero-description">
|
||||||
|
输入你的出生年月日、时辰、性别与出生地,生成你的职业命理画像,
|
||||||
|
获得更适合你的公司类型与岗位方向建议。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="hero-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={onStart}
|
||||||
|
>
|
||||||
|
开始测试
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={() => setShowIntro(!showIntro)}
|
||||||
|
>
|
||||||
|
{showIntro ? '收起说明' : '查看产品说明'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showIntro && (
|
||||||
|
<section className="intro-section">
|
||||||
|
<div className="intro-card">
|
||||||
|
<h3>首版能力</h3>
|
||||||
|
<ul className="feature-list">
|
||||||
|
<li>
|
||||||
|
<span className="feature-icon">✦</span>
|
||||||
|
命理职业画像
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="feature-icon">✦</span>
|
||||||
|
公司类型匹配
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="feature-icon">✦</span>
|
||||||
|
岗位方向推荐
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="feature-icon">✦</span>
|
||||||
|
当前阶段择业建议
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="intro-card">
|
||||||
|
<h3>输入信息</h3>
|
||||||
|
<ul className="feature-list">
|
||||||
|
<li>出生年月日</li>
|
||||||
|
<li>出生时辰</li>
|
||||||
|
<li>性别</li>
|
||||||
|
<li>出生地</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="intro-card">
|
||||||
|
<h3>适合人群</h3>
|
||||||
|
<ul className="feature-list">
|
||||||
|
<li>正在找工作的应届生</li>
|
||||||
|
<li>考虑跳槽的职场人</li>
|
||||||
|
<li>对职业方向迷茫的人</li>
|
||||||
|
<li>对命理与职业规划感兴趣的人</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className="page-footer">
|
||||||
|
<p>本产品仅供娱乐参考,不构成职业决策依据</p>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
185
src/pages/ResultPage.tsx
Normal file
185
src/pages/ResultPage.tsx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { UserInput, FortuneResult } from '../types';
|
||||||
|
|
||||||
|
interface ResultPageProps {
|
||||||
|
input: UserInput;
|
||||||
|
result: FortuneResult;
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResultPage({ result, onBack }: ResultPageProps) {
|
||||||
|
const [showDetail, setShowDetail] = useState(false);
|
||||||
|
const { profile, companyRecommendations, jobRecommendations, stageAdvice } = result;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="page-shell">
|
||||||
|
<div className="result-section">
|
||||||
|
<header className="result-header">
|
||||||
|
<button type="button" className="back-btn" onClick={onBack}>
|
||||||
|
← 重新测试
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* 简版结果 */}
|
||||||
|
<section className="result-card summary-card">
|
||||||
|
<div className="summary-header">
|
||||||
|
<span className="summary-label">你的职业命理画像</span>
|
||||||
|
<span className="wuxing-badge">{profile.wuxingLabel} 行</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="summary-content">
|
||||||
|
<p className="summary-text">
|
||||||
|
{profile.personality.join(',')}。
|
||||||
|
你的优势是{profile.strengths.slice(0, 3).join('、')},
|
||||||
|
需要注意{profile.weaknesses[0]}。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="recommendation-grid">
|
||||||
|
<div className="recommendation-item">
|
||||||
|
<span className="rec-label">推荐公司类型</span>
|
||||||
|
<span className="rec-value">{companyRecommendations[0]?.type}</span>
|
||||||
|
</div>
|
||||||
|
<div className="recommendation-item">
|
||||||
|
<span className="rec-label">推荐岗位方向</span>
|
||||||
|
<span className="rec-value">{jobRecommendations[0]?.category}</span>
|
||||||
|
</div>
|
||||||
|
<div className="recommendation-item">
|
||||||
|
<span className="rec-label">当前阶段</span>
|
||||||
|
<span className="rec-value">
|
||||||
|
{profile.developmentRhythm === 'stable' && '稳定积累期'}
|
||||||
|
{profile.developmentRhythm === 'growth' && '成长期'}
|
||||||
|
{profile.developmentRhythm === 'breakthrough' && '突破期'}
|
||||||
|
{profile.developmentRhythm === 'accumulation' && '沉淀期'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 展开详细解读按钮 */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary btn-full toggle-detail"
|
||||||
|
onClick={() => setShowDetail(!showDetail)}
|
||||||
|
>
|
||||||
|
{showDetail ? '收起详细解读' : '展开详细解读'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* 详细解读 */}
|
||||||
|
{showDetail && (
|
||||||
|
<div className="detail-section">
|
||||||
|
{/* 命理画像详情 */}
|
||||||
|
<section className="result-card">
|
||||||
|
<h3 className="section-title">命理职业画像</h3>
|
||||||
|
<div className="detail-grid">
|
||||||
|
<div className="detail-item">
|
||||||
|
<span className="detail-label">五行属性</span>
|
||||||
|
<span className="detail-value">{profile.wuxingLabel} 行</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-item">
|
||||||
|
<span className="detail-label">性格特征</span>
|
||||||
|
<span className="detail-value">{profile.personality.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-item">
|
||||||
|
<span className="detail-label">核心优势</span>
|
||||||
|
<span className="detail-value">{profile.strengths.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-item">
|
||||||
|
<span className="detail-label">注意短板</span>
|
||||||
|
<span className="detail-value">{profile.weaknesses.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
<div className="detail-item">
|
||||||
|
<span className="detail-label">发展节奏</span>
|
||||||
|
<span className="detail-value">
|
||||||
|
{profile.developmentRhythm === 'stable' && '稳定积累期 - 适合稳扎稳打'}
|
||||||
|
{profile.developmentRhythm === 'growth' && '成长期 - 适合寻求突破'}
|
||||||
|
{profile.developmentRhythm === 'breakthrough' && '突破期 - 适合重大改变'}
|
||||||
|
{profile.developmentRhythm === 'accumulation' && '沉淀期 - 需要耐心积累'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 公司推荐详情 */}
|
||||||
|
<section className="result-card">
|
||||||
|
<h3 className="section-title">公司类型推荐</h3>
|
||||||
|
<div className="recommendation-list">
|
||||||
|
{companyRecommendations.map((company, index) => (
|
||||||
|
<div key={index} className="recommendation-row">
|
||||||
|
<div className="rec-rank">{index + 1}</div>
|
||||||
|
<div className="rec-content">
|
||||||
|
<div className="rec-header">
|
||||||
|
<span className="rec-name">{company.type}</span>
|
||||||
|
<span className="rec-score">{company.matchScore}% 匹配</span>
|
||||||
|
</div>
|
||||||
|
<p className="rec-reason">{company.reasons.join(',')}</p>
|
||||||
|
<div className="rec-tags">
|
||||||
|
<span className="tag">适合:{company.suitableFor.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 岗位推荐详情 */}
|
||||||
|
<section className="result-card">
|
||||||
|
<h3 className="section-title">岗位方向推荐</h3>
|
||||||
|
<div className="recommendation-list">
|
||||||
|
{jobRecommendations.map((job, index) => (
|
||||||
|
<div key={index} className="recommendation-row">
|
||||||
|
<div className="rec-rank">{index + 1}</div>
|
||||||
|
<div className="rec-content">
|
||||||
|
<div className="rec-header">
|
||||||
|
<span className="rec-name">{job.category}</span>
|
||||||
|
<span className="rec-score">{job.matchScore}% 匹配</span>
|
||||||
|
</div>
|
||||||
|
<p className="rec-reason">{job.reasons.join(',')}</p>
|
||||||
|
<div className="rec-tags">
|
||||||
|
<span className="tag">适合:{job.suitableWorkStyle.join('、')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 当前阶段建议 */}
|
||||||
|
<section className="result-card">
|
||||||
|
<h3 className="section-title">当前阶段建议</h3>
|
||||||
|
<div className="stage-advice">
|
||||||
|
<p className="advice-overview">{stageAdvice.overall}</p>
|
||||||
|
<div className="advice-items">
|
||||||
|
<div className="advice-item">
|
||||||
|
<span className="advice-label">职业方向</span>
|
||||||
|
<span className="advice-value">{stageAdvice.careerDirection}</span>
|
||||||
|
</div>
|
||||||
|
<div className="advice-item">
|
||||||
|
<span className="advice-label">变动时机</span>
|
||||||
|
<span className="advice-value">{stageAdvice.jobChangeTiming}</span>
|
||||||
|
</div>
|
||||||
|
<div className="advice-item">
|
||||||
|
<span className="advice-label">平台偏好</span>
|
||||||
|
<span className="advice-value">{stageAdvice.platformPreference}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="key-observations">
|
||||||
|
<h4>重点观察</h4>
|
||||||
|
<ul>
|
||||||
|
{stageAdvice.keyObservations.map((obs, i) => (
|
||||||
|
<li key={i}>{obs}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<footer className="result-footer">
|
||||||
|
<p>本结果仅供娱乐参考,不构成职业决策依据</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
768
src/styles/index.css
Normal file
768
src/styles/index.css
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
:root {
|
||||||
|
/* 字体系统 */
|
||||||
|
--font-display: "Noto Serif SC", "Source Han Serif SC", "Songti SC", serif;
|
||||||
|
--font-body: "Noto Sans SC", "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
|
||||||
|
/* 色彩体系 - 东方命理气质 */
|
||||||
|
--color-bg-deep: #08090c;
|
||||||
|
--color-bg-primary: #0c0f14;
|
||||||
|
--color-bg-card: rgba(18, 22, 28, 0.92);
|
||||||
|
--color-bg-elevated: rgba(26, 31, 40, 0.88);
|
||||||
|
|
||||||
|
--color-gold-primary: #d6b47a;
|
||||||
|
--color-gold-light: #e8cda0;
|
||||||
|
--color-gold-dark: #a67c3d;
|
||||||
|
--color-gold-muted: rgba(214, 180, 122, 0.18);
|
||||||
|
|
||||||
|
--color-text-primary: #f4efe6;
|
||||||
|
--color-text-secondary: rgba(244, 239, 230, 0.78);
|
||||||
|
--color-text-muted: rgba(244, 239, 230, 0.52);
|
||||||
|
|
||||||
|
--color-border: rgba(214, 180, 122, 0.14);
|
||||||
|
--color-border-hover: rgba(214, 180, 122, 0.32);
|
||||||
|
|
||||||
|
/* 间距系统 */
|
||||||
|
--space-xs: 4px;
|
||||||
|
--space-sm: 8px;
|
||||||
|
--space-md: 16px;
|
||||||
|
--space-lg: 24px;
|
||||||
|
--space-xl: 32px;
|
||||||
|
--space-2xl: 48px;
|
||||||
|
--space-3xl: 64px;
|
||||||
|
|
||||||
|
/* 圆角 */
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 20px;
|
||||||
|
--radius-xl: 28px;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
/* 阴影 */
|
||||||
|
--shadow-card: 0 8px 32px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
--shadow-elevated: 0 16px 48px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
--transition-fast: 0.15s ease;
|
||||||
|
--transition-base: 0.25s ease;
|
||||||
|
--transition-slow: 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
background: var(--color-bg-deep);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 背景氛围 */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(166, 116, 53, 0.12) 0%, transparent 60%),
|
||||||
|
radial-gradient(ellipse 60% 40% at 85% 80%, rgba(139, 90, 43, 0.08) 0%, transparent 50%),
|
||||||
|
linear-gradient(180deg, var(--color-bg-deep) 0%, var(--color-bg-primary) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 主容器 */
|
||||||
|
.page-shell {
|
||||||
|
width: min(960px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-2xl) 0 var(--space-3xl);
|
||||||
|
animation: fadeIn 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(12px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通用按钮 */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
padding: 14px 28px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-base);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, var(--color-gold-primary) 0%, var(--color-gold-dark) 100%);
|
||||||
|
color: #0a0b0e;
|
||||||
|
box-shadow: 0 4px 16px rgba(214, 180, 122, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 24px rgba(214, 180, 122, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover:not(:disabled) {
|
||||||
|
border-color: var(--color-border-hover);
|
||||||
|
background: var(--color-gold-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页样式 */
|
||||||
|
.hero-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-header {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-xl) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-tag {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: clamp(48px, 10vw, 84px);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.1;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-main {
|
||||||
|
background: linear-gradient(135deg, var(--color-gold-light) 0%, var(--color-gold-primary) 50%, var(--color-gold-dark) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-description {
|
||||||
|
max-width: 640px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 产品说明区域 */
|
||||||
|
.intro-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
animation: slideUp 0.5s ease-out 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { opacity: 0; transform: translateY(16px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-card {
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-card h3 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 18px;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页脚 */
|
||||||
|
.page-footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--space-3xl);
|
||||||
|
padding-top: var(--space-xl);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-footer p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== 表单页样式 ==== */
|
||||||
|
.form-section {
|
||||||
|
max-width: 520px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
background: var(--color-gold-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-subtitle {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-gold-primary);
|
||||||
|
box-shadow: 0 0 0 3px var(--color-gold-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d6b47a' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 14px center;
|
||||||
|
padding-right: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单选按钮 */
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-custom {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid var(--color-border);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label input:checked + .radio-custom {
|
||||||
|
border-color: var(--color-gold-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label input:checked + .radio-custom::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background: var(--color-gold-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-notice {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-notice p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载状态 */
|
||||||
|
.loading-state {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
||||||
|
50% { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==== 结果页样式 ==== */
|
||||||
|
.result-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
background: var(--color-bg-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-xl);
|
||||||
|
padding: var(--space-xl);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 简版结果卡片 */
|
||||||
|
.summary-card {
|
||||||
|
background: linear-gradient(135deg, rgba(214, 180, 122, 0.08) 0%, rgba(18, 22, 28, 0.92) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wuxing-badge {
|
||||||
|
background: var(--color-gold-muted);
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-content {
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-text {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐网格 */
|
||||||
|
.recommendation-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommendation-item {
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 展开按钮 */
|
||||||
|
.toggle-detail {
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 详细解读区域 */
|
||||||
|
.detail-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
animation: slideUp 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
padding-bottom: var(--space-sm);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 详情网格 */
|
||||||
|
.detail-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 推荐列表 */
|
||||||
|
.recommendation-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommendation-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-md);
|
||||||
|
padding: var(--space-md);
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommendation-row:hover {
|
||||||
|
background: rgba(214, 180, 122, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-rank {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--color-gold-muted);
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-score {
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-reason {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rec-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background: var(--color-gold-muted);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 阶段建议 */
|
||||||
|
.stage-advice {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advice-overview {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.7;
|
||||||
|
padding: var(--space-md);
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border-left: 3px solid var(--color-gold-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advice-items {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advice-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
padding: var(--space-md);
|
||||||
|
background: var(--color-bg-elevated);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advice-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advice-value {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-observations h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-observations ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-observations li {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 14px;
|
||||||
|
padding-left: var(--space-md);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-observations li::before {
|
||||||
|
content: '→';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--color-gold-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 结果页脚 */
|
||||||
|
.result-footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
padding-top: var(--space-lg);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-footer p {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-shell {
|
||||||
|
padding: var(--space-lg) 0 var(--space-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-description {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommendation-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-card {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/types/index.ts
Normal file
47
src/types/index.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
export interface UserInput {
|
||||||
|
birthDate: string; // YYYY-MM-DD
|
||||||
|
birthTime: string; // HH:mm
|
||||||
|
gender: 'male' | 'female';
|
||||||
|
birthPlace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FortuneProfile {
|
||||||
|
wuxing: 'wood' | 'fire' | 'earth' | 'metal' | 'water';
|
||||||
|
wuxingLabel: string;
|
||||||
|
personality: string[];
|
||||||
|
strengths: string[];
|
||||||
|
weaknesses: string[];
|
||||||
|
developmentRhythm: 'stable' | 'growth' | 'breakthrough' | 'accumulation';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyRecommendation {
|
||||||
|
type: string;
|
||||||
|
matchScore: number;
|
||||||
|
reasons: string[];
|
||||||
|
suitableFor: string[];
|
||||||
|
notSuitableFor: string[];
|
||||||
|
stageAdvice: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobRecommendation {
|
||||||
|
category: string;
|
||||||
|
matchScore: number;
|
||||||
|
reasons: string[];
|
||||||
|
suitableWorkStyle: string[];
|
||||||
|
notSuitableFor: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StageAdvice {
|
||||||
|
overall: string;
|
||||||
|
careerDirection: string;
|
||||||
|
jobChangeTiming: string;
|
||||||
|
platformPreference: string;
|
||||||
|
keyObservations: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FortuneResult {
|
||||||
|
profile: FortuneProfile;
|
||||||
|
companyRecommendations: CompanyRecommendation[];
|
||||||
|
jobRecommendations: JobRecommendation[];
|
||||||
|
stageAdvice: StageAdvice;
|
||||||
|
}
|
||||||
21
tsconfig.app.json
Normal file
21
tsconfig.app.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowImportingTsExtensions": false,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
1
tsconfig.app.tsbuildinfo
Normal file
1
tsconfig.app.tsbuildinfo
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/app.tsx","./src/main.tsx","./src/lib/fortune.ts","./src/pages/formpage.tsx","./src/pages/homepage.tsx","./src/pages/resultpage.tsx","./src/types/index.ts"],"version":"5.9.3"}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
1
tsconfig.node.tsbuildinfo
Normal file
1
tsconfig.node.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
2
vite.config.d.ts
vendored
Normal file
2
vite.config.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
declare const _default: import("vite").UserConfig;
|
||||||
|
export default _default;
|
||||||
5
vite.config.js
Normal file
5
vite.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user