162 lines
17 KiB
JSON
162 lines
17 KiB
JSON
{
|
|
"phase": {
|
|
"id": "phase-5",
|
|
"name": "新增页面与功能",
|
|
"description": "实现紫微斗数排盘页面、运势分析页面、数据导出/导入功能",
|
|
"status": "completed",
|
|
"completedAt": "2026-04-28T03:57:24+08:00",
|
|
"dependencies": ["phase-4"],
|
|
"tasks": [
|
|
{
|
|
"id": "5.1",
|
|
"name": "实现紫微斗数排盘页面",
|
|
"description": "创建紫微斗数排盘页面,包含出生信息输入、排盘展示、三方四正分析",
|
|
"files": {
|
|
"create": ["src/pages/ziwei/index.vue"],
|
|
"test": ["src/pages/ziwei/__tests__/index.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "5.1.1",
|
|
"action": "write_test",
|
|
"description": "编写紫微页面组件测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { mount } from '@vue/test-utils'\nimport ZiweiPage from '../index.vue'\n\ndescribe('ZiweiPage', () => {\n it('应包含出生信息输入表单', () => {\n const wrapper = mount(ZiweiPage)\n expect(wrapper.find('[data-test=\"birth-form\"]').exists()).toBe(true)\n })\n\n it('应包含排盘按钮', () => {\n const wrapper = mount(ZiweiPage)\n expect(wrapper.find('[data-test=\"generate-btn\"]').exists()).toBe(true)\n })\n\n it('点击排盘按钮应触发计算', async () => {\n const wrapper = mount(ZiweiPage)\n await wrapper.find('[data-test=\"generate-btn\"]').trigger('click')\n expect(wrapper.vm.isCalculating).toBeDefined()\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/ziwei/__tests__/index.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "5.1.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/ziwei/__tests__/index.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "5.1.3",
|
|
"action": "write_implementation",
|
|
"description": "实现紫微斗数排盘页面",
|
|
"code": "<template>\n <view class=\"ziwei-page\">\n <view class=\"section\" data-test=\"birth-form\">\n <text class=\"section-title\">出生信息</text>\n <view class=\"form-group\">\n <text class=\"label\">出生日期</text>\n <picker mode=\"date\" :value=\"birthDate\" @change=\"onBirthDateChange\">\n <text class=\"picker-value\">{{ birthDate || '请选择' }}</text>\n </picker>\n </view>\n <view class=\"form-group\">\n <text class=\"label\">出生时辰</text>\n <picker :range=\"hourOptions\" :value=\"hourIndex\" @change=\"onHourChange\">\n <text class=\"picker-value\">{{ hourOptions[hourIndex] }}</text>\n </picker>\n </view>\n <view class=\"form-group\">\n <text class=\"label\">性别</text>\n <view class=\"gender-switch\">\n <text :class=\"['gender-option', gender === 'male' ? 'active' : '']\" @tap=\"gender = 'male'\">男</text>\n <text :class=\"['gender-option', gender === 'female' ? 'active' : '']\" @tap=\"gender = 'female'\">女</text>\n </view>\n </view>\n </view>\n\n <view class=\"action-bar\">\n <button data-test=\"generate-btn\" class=\"generate-btn\" @tap=\"generateChart\" :disabled=\"isCalculating\">\n {{ isCalculating ? '排盘中...' : '排盘' }}\n </button>\n </view>\n\n <view v-if=\"chart\" class=\"section chart-result\">\n <text class=\"section-title\">紫微斗数盘</text>\n <view class=\"chart-grid\">\n <view v-for=\"palace in chart.palaces\" :key=\"palace.palaceType\" class=\"palace-card\">\n <text class=\"palace-name\">{{ getPalaceName(palace.palaceType) }}</text>\n <text class=\"palace-branch\">{{ getBranchName(palace.earthlyBranch) }}</text>\n <text class=\"palace-score\">{{ palace.score }}分</text>\n <view class=\"star-list\">\n <text v-for=\"star in palace.majorStars\" :key=\"star.star\" class=\"star-item\">\n {{ getStarName(star.star) }}\n </text>\n </view>\n </view>\n </view>\n </view>\n\n <view v-if=\"chart?.sanFangSiZheng\" class=\"section sfz-result\">\n <text class=\"section-title\">三方四正分析</text>\n <text class=\"sfz-score\">三方平均分:{{ chart.sanFangSiZheng.sanFangScore }}</text>\n <text class=\"sfz-score\">四正平均分:{{ chart.sanFangSiZheng.siZhengScore }}</text>\n </view>\n </view>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { ZiweiService } from '@/services/ziweiService'\nimport { PalaceType, EarthlyBranch, MajorStar } from '@/algorithms/enums'\nimport type { ZiweiChart } from '@/algorithms/types'\n\nconst ziweiService = new ZiweiService()\n\nconst birthDate = ref('')\nconst hourIndex = ref(0)\nconst gender = ref<'male' | 'female'>('male')\nconst isCalculating = ref(false)\nconst chart = ref<ZiweiChart | null>(null)\n\nconst hourOptions = ['子时(23-1)', '丑时(1-3)', '寅时(3-5)', '卯时(5-7)', '辰时(7-9)', '巳时(9-11)', '午时(11-13)', '未时(13-15)', '申时(15-17)', '酉时(17-19)', '戌时(19-21)', '亥时(21-23)']\n\nfunction onBirthDateChange(e: any) {\n birthDate.value = e.detail.value\n}\n\nfunction onHourChange(e: any) {\n hourIndex.value = e.detail.value\n}\n\nasync function generateChart() {\n if (!birthDate.value) return\n isCalculating.value = true\n try {\n const hour = hourIndex.value * 2 + 1\n const birthTime = `${birthDate.value}T${String(hour).padStart(2, '0')}:00:00`\n chart.value = await ziweiService.generateChart({\n birthTime,\n gender: gender.value,\n timezone: 'Asia/Shanghai',\n })\n } finally {\n isCalculating.value = false\n }\n}\n\nfunction getPalaceName(type: PalaceType): string {\n return PalaceType.name(type)\n}\n\nfunction getBranchName(branch: EarthlyBranch): string {\n return EarthlyBranch.name(branch)\n}\n\nfunction getStarName(star: MajorStar): string {\n return MajorStar.name(star)\n}\n</script>",
|
|
"sourceRef": "设计规格 Section 5.1 紫微斗数排盘页面"
|
|
},
|
|
{
|
|
"id": "5.1.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/ziwei/__tests__/index.test.ts",
|
|
"expectedResult": "PASS - 所有紫微页面测试通过"
|
|
},
|
|
{
|
|
"id": "5.1.5",
|
|
"action": "register_page_route",
|
|
"description": "在 pages.json 中注册紫微页面路由",
|
|
"targetFile": "src/pages.json",
|
|
"addEntry": { "path": "pages/ziwei/index", "style": { "navigationBarTitleText": "紫微斗数" } }
|
|
},
|
|
{
|
|
"id": "5.1.6",
|
|
"action": "commit",
|
|
"message": "feat(pages): 实现紫微斗数排盘页面 - 出生信息输入/排盘展示/三方四正"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "5.2",
|
|
"name": "实现运势分析页面",
|
|
"description": "创建运势分析页面,包含日运/月运/年运展示",
|
|
"files": {
|
|
"create": ["src/pages/fortune/index.vue"],
|
|
"test": ["src/pages/fortune/__tests__/index.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "5.2.1",
|
|
"action": "write_test",
|
|
"description": "编写运势页面组件测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { mount } from '@vue/test-utils'\nimport FortunePage from '../index.vue'\n\ndescribe('FortunePage', () => {\n it('应包含运势类型切换', () => {\n const wrapper = mount(FortunePage)\n expect(wrapper.find('[data-test=\"fortune-tabs\"]').exists()).toBe(true)\n })\n\n it('默认应显示日运', () => {\n const wrapper = mount(FortunePage)\n expect(wrapper.vm.activeTab).toBe('daily')\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/fortune/__tests__/index.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "5.2.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/fortune/__tests__/index.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "5.2.3",
|
|
"action": "write_implementation",
|
|
"description": "实现运势分析页面",
|
|
"code": "<template>\n <view class=\"fortune-page\">\n <view class=\"tabs\" data-test=\"fortune-tabs\">\n <text :class=\"['tab', activeTab === 'daily' ? 'active' : '']\" @tap=\"activeTab = 'daily'\">日运</text>\n <text :class=\"['tab', activeTab === 'monthly' ? 'active' : '']\" @tap=\"activeTab = 'monthly'\">月运</text>\n </view>\n\n <view v-if=\"activeTab === 'daily'\" class=\"daily-fortune\">\n <picker mode=\"date\" :value=\"selectedDate\" @change=\"onDateChange\">\n <text class=\"date-picker\">{{ selectedDate || '选择日期' }}</text>\n </picker>\n <view v-if=\"dailyFortune\" class=\"fortune-result\">\n <text class=\"overall-score\">综合运势:{{ dailyFortune.overallScore }}分</text>\n <text class=\"overall-luck\">{{ dailyFortune.overallLuck }}</text>\n <view class=\"advice-section\">\n <text class=\"advice\">事业:{{ dailyFortune.careerAdvice }}</text>\n <text class=\"advice\">财运:{{ dailyFortune.wealthAdvice }}</text>\n <text class=\"advice\">感情:{{ dailyFortune.relationshipAdvice }}</text>\n <text class=\"advice\">健康:{{ dailyFortune.healthAdvice }}</text>\n </view>\n <view class=\"lucky-section\">\n <text class=\"lucky\">幸运色:{{ dailyFortune.luckyColor }}</text>\n <text class=\"lucky\">幸运数字:{{ dailyFortune.luckyNumber }}</text>\n <text class=\"lucky\">幸运方位:{{ dailyFortune.luckyDirection }}</text>\n </view>\n </view>\n </view>\n\n <view v-if=\"activeTab === 'monthly'\" class=\"monthly-fortune\">\n <picker mode=\"date\" fields=\"month\" :value=\"selectedMonth\" @change=\"onMonthChange\">\n <text class=\"date-picker\">{{ selectedMonth || '选择月份' }}</text>\n </picker>\n <view v-if=\"monthlyFortune\" class=\"fortune-result\">\n <text class=\"overall-score\">综合运势:{{ monthlyFortune.overallScore }}分</text>\n <text class=\"overall-luck\">{{ monthlyFortune.overallLuck }}</text>\n </view>\n </view>\n </view>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { FortuneService } from '@/services/fortuneService'\nimport { ZiweiService } from '@/services/ziweiService'\nimport type { DailyFortune, MonthlyFortune, ZiweiChart } from '@/algorithms/types'\n\nconst fortuneService = new FortuneService()\nconst ziweiService = new ZiweiService()\n\nconst activeTab = ref<'daily' | 'monthly'>('daily')\nconst selectedDate = ref('')\nconst selectedMonth = ref('')\nconst dailyFortune = ref<DailyFortune | null>(null)\nconst monthlyFortune = ref<MonthlyFortune | null>(null)\nconst currentChart = ref<ZiweiChart | null>(null)\n\nasync function loadChart() {\n const cached = await ziweiService.getChartFromCache({ birthTime: '', gender: 'male', timezone: 'Asia/Shanghai' })\n if (cached) currentChart.value = cached\n}\n\nfunction onDateChange(e: any) {\n selectedDate.value = e.detail.value\n}\n\nfunction onMonthChange(e: any) {\n selectedMonth.value = e.detail.value\n}\n\nwatch(selectedDate, async (val) => {\n if (!val || !currentChart.value) return\n dailyFortune.value = await fortuneService.getDailyFortune(currentChart.value, val)\n})\n\nwatch(selectedMonth, async (val) => {\n if (!val || !currentChart.value) return\n const [year, month] = val.split('-').map(Number)\n monthlyFortune.value = await fortuneService.getMonthlyFortune(currentChart.value, year, month)\n})\n\nloadChart()\n</script>",
|
|
"sourceRef": "设计规格 Section 5.2 运势分析页面"
|
|
},
|
|
{
|
|
"id": "5.2.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/pages/fortune/__tests__/index.test.ts",
|
|
"expectedResult": "PASS - 所有运势页面测试通过"
|
|
},
|
|
{
|
|
"id": "5.2.5",
|
|
"action": "register_page_route",
|
|
"description": "在 pages.json 中注册运势页面路由",
|
|
"targetFile": "src/pages.json",
|
|
"addEntry": { "path": "pages/fortune/index", "style": { "navigationBarTitleText": "运势分析" } }
|
|
},
|
|
{
|
|
"id": "5.2.6",
|
|
"action": "commit",
|
|
"message": "feat(pages): 实现运势分析页面 - 日运/月运展示"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "5.3",
|
|
"name": "实现数据导出/导入功能",
|
|
"description": "创建 exportImport.ts 工具,支持用户数据导出为 JSON 文件和从 JSON 文件导入恢复",
|
|
"files": {
|
|
"create": ["src/utils/exportImport.ts"],
|
|
"test": ["src/utils/__tests__/exportImport.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "5.3.1",
|
|
"action": "write_test",
|
|
"description": "编写导出/导入测试",
|
|
"code": "import { describe, it, expect, vi } from 'vitest'\nimport { exportData, importData, validateImportData } from '../exportImport'\n\nvi.stubGlobal('uni', {\n getStorageSync: vi.fn(),\n setStorageSync: vi.fn(),\n})\n\ndescribe('exportData', () => {\n it('应导出包含版本信息的JSON', () => {\n const result = exportData()\n const parsed = JSON.parse(result)\n expect(parsed.version).toBe('1.0.0')\n expect(parsed.exportDate).toBeTruthy()\n expect(parsed.data).toBeDefined()\n })\n})\n\ndescribe('validateImportData', () => {\n it('有效数据应返回true', () => {\n const validJson = JSON.stringify({ version: '1.0.0', exportDate: '2026-04-27', data: {} })\n expect(validateImportData(validJson)).toBe(true)\n })\n\n it('缺少版本信息应返回false', () => {\n const invalidJson = JSON.stringify({ data: {} })\n expect(validateImportData(invalidJson)).toBe(false)\n })\n\n it('无效JSON应返回false', () => {\n expect(validateImportData('not json')).toBe(false)\n })\n})\n\ndescribe('importData', () => {\n it('有效数据应成功导入', () => {\n const validJson = JSON.stringify({ version: '1.0.0', exportDate: '2026-04-27', data: { test_key: 'test_value' } })\n const result = importData(validJson)\n expect(result).toBe(true)\n })\n\n it('无效数据应返回false', () => {\n const result = importData('invalid')\n expect(result).toBe(false)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/utils/__tests__/exportImport.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "5.3.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/utils/__tests__/exportImport.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "5.3.3",
|
|
"action": "write_implementation",
|
|
"description": "实现数据导出/导入功能",
|
|
"code": "import { storage } from './storage'\n\nconst EXPORT_VERSION = '1.0.0'\n\nexport interface ExportData {\n version: string\n exportDate: string\n data: Record<string, any>\n}\n\nexport function exportData(): string {\n const data: ExportData = {\n version: EXPORT_VERSION,\n exportDate: new Date().toISOString().split('T')[0],\n data: {},\n }\n\n const storageKeys = [\n 'ziwei_charts',\n 'search_history',\n 'search_templates',\n ]\n\n for (const key of storageKeys) {\n const value = storage.get(key)\n if (value !== null) {\n data.data[key] = value\n }\n }\n\n return JSON.stringify(data, null, 2)\n}\n\nexport function validateImportData(jsonStr: string): boolean {\n try {\n const parsed = JSON.parse(jsonStr) as ExportData\n if (!parsed.version || !parsed.data) return false\n return true\n } catch {\n return false\n }\n}\n\nexport function importData(jsonStr: string): boolean {\n if (!validateImportData(jsonStr)) return false\n\n try {\n const parsed = JSON.parse(jsonStr) as ExportData\n for (const [key, value] of Object.entries(parsed.data)) {\n storage.set(key, value)\n }\n return true\n } catch {\n return false\n }\n}",
|
|
"sourceRef": "设计规格 Section 5.4 数据导出/导入"
|
|
},
|
|
{
|
|
"id": "5.3.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/utils/__tests__/exportImport.test.ts",
|
|
"expectedResult": "PASS - 所有导出/导入测试通过"
|
|
},
|
|
{
|
|
"id": "5.3.5",
|
|
"action": "commit",
|
|
"message": "feat(utils): 实现数据导出/导入功能 - JSON格式导出导入"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|