269 lines
26 KiB
JSON
269 lines
26 KiB
JSON
{
|
|
"phase": {
|
|
"id": "phase-3",
|
|
"name": "本地服务层实现",
|
|
"description": "将远程 API 调用替换为本地服务层,封装算法调用为与原 API 接口兼容的 Service",
|
|
"status": "completed",
|
|
"completedAt": "2026-04-27T22:21:00+08:00",
|
|
"dependencies": ["phase-2"],
|
|
"tasks": [
|
|
{
|
|
"id": "3.1",
|
|
"name": "实现本地黄历服务",
|
|
"description": "创建 AlmanacService 替代远程 API,封装 almanac.ts 算法调用",
|
|
"files": {
|
|
"create": ["src/services/almanacService.ts"],
|
|
"test": ["src/services/__tests__/almanacService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.1.1",
|
|
"action": "write_test",
|
|
"description": "编写本地黄历服务测试",
|
|
"code": "import { describe, it, expect, vi } from 'vitest'\nimport { AlmanacService } from '../almanacService'\n\ndescribe('AlmanacService', () => {\n let service: AlmanacService\n\n beforeEach(() => {\n service = new AlmanacService()\n })\n\n it('getAlmanacByDate 应返回黄历数据', async () => {\n const result = await service.getAlmanacByDate('2026-04-27')\n expect(result).not.toBeNull()\n expect(result!.solarDate).toBe('2026-04-27')\n })\n\n it('getAlmanacsByRange 应返回日期范围数据', async () => {\n const result = await service.getAlmanacsByRange('2026-04-27', '2026-04-29')\n expect(result.length).toBe(3)\n })\n\n it('searchAlmanacs 应支持关键词搜索', async () => {\n const result = await service.searchAlmanacs({ keyword: '祭祀', dateFrom: '2026-04-01', dateTo: '2026-04-30' })\n expect(Array.isArray(result)).toBe(true)\n })\n\n it('缓存命中时不应重复计算', async () => {\n const spy = vi.spyOn(service as any, 'calculateAlmanac')\n await service.getAlmanacByDate('2026-04-27')\n await service.getAlmanacByDate('2026-04-27')\n expect(spy).toHaveBeenCalledTimes(1)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/almanacService.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "3.1.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/almanacService.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "3.1.3",
|
|
"action": "write_implementation",
|
|
"description": "实现本地黄历服务",
|
|
"code": "import { getAlmanacByDate, getAlmanacsByRange } from '../algorithms/almanac'\nimport type { Almanac } from '../algorithms/types'\nimport { LRUCache } from '../utils/lruCache'\n\nexport interface AlmanacSearchParams {\n keyword?: string\n dateFrom?: string\n dateTo?: string\n suitableFilter?: string[]\n unsuitableFilter?: string[]\n}\n\nexport class AlmanacService {\n private cache = new LRUCache<Almanac>(365)\n\n private calculateAlmanac(dateStr: string): Almanac | null {\n return getAlmanacByDate(dateStr)\n }\n\n async getAlmanacByDate(dateStr: string): Promise<Almanac | null> {\n const cached = this.cache.get(dateStr)\n if (cached) return cached\n\n const result = this.calculateAlmanac(dateStr)\n if (result) this.cache.set(dateStr, result)\n return result\n }\n\n async getAlmanacsByRange(startDateStr: string, endDateStr: string): Promise<Almanac[]> {\n return getAlmanacsByRange(startDateStr, endDateStr)\n }\n\n async searchAlmanacs(params: AlmanacSearchParams): Promise<Almanac[]> {\n const { keyword, dateFrom, dateTo, suitableFilter, unsuitableFilter } = params\n\n const startDate = dateFrom ?? '2026-01-01'\n const endDate = dateTo ?? '2026-12-31'\n let results = await this.getAlmanacsByRange(startDate, endDate)\n\n if (keyword) {\n results = results.filter(a =>\n a.suitable.some(s => s.includes(keyword)) ||\n a.unsuitable.some(s => s.includes(keyword)) ||\n a.jianChu.includes(keyword) ||\n a.starGod.includes(keyword)\n )\n }\n\n if (suitableFilter && suitableFilter.length > 0) {\n results = results.filter(a =>\n suitableFilter.some(f => a.suitable.includes(f))\n )\n }\n\n if (unsuitableFilter && unsuitableFilter.length > 0) {\n results = results.filter(a =>\n unsuitableFilter.some(f => a.unsuitable.includes(f))\n )\n }\n\n return results\n }\n}",
|
|
"sourceRef": "Java: IAlmanacService.java + AlmanacServiceImpl.java"
|
|
},
|
|
{
|
|
"id": "3.1.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/almanacService.test.ts",
|
|
"expectedResult": "PASS - 所有本地黄历服务测试通过"
|
|
},
|
|
{
|
|
"id": "3.1.5",
|
|
"action": "commit",
|
|
"message": "feat(services): 实现本地黄历服务 AlmanacService - 替代远程API"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "3.2",
|
|
"name": "实现本地紫微斗数服务",
|
|
"description": "创建 ZiweiService 替代远程 API,封装紫微排盘算法调用",
|
|
"files": {
|
|
"create": ["src/services/ziweiService.ts"],
|
|
"test": ["src/services/__tests__/ziweiService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.2.1",
|
|
"action": "write_test",
|
|
"description": "编写本地紫微服务测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { ZiweiService } from '../ziweiService'\n\ndescribe('ZiweiService', () => {\n let service: ZiweiService\n\n beforeEach(() => {\n service = new ZiweiService()\n })\n\n it('generateChart 应返回紫微盘数据', async () => {\n const result = await service.generateChart({\n birthTime: '1990-01-15T10:00:00',\n gender: 'male',\n timezone: 'Asia/Shanghai',\n })\n expect(result).not.toBeNull()\n expect(result.palaces.length).toBe(12)\n })\n\n it('getChartFromCache 应从缓存获取', async () => {\n const params = { birthTime: '1990-01-15T10:00:00', gender: 'male', timezone: 'Asia/Shanghai' }\n const first = await service.generateChart(params)\n const second = await service.getChartFromCache(params)\n expect(second).not.toBeNull()\n })\n\n it('相同参数应返回相同结果', async () => {\n const params = { birthTime: '1990-01-15T10:00:00', gender: 'male', timezone: 'Asia/Shanghai' }\n const a = await service.generateChart(params)\n const b = await service.generateChart(params)\n expect(a.mingGongBranch).toBe(b.mingGongBranch)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/ziweiService.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "3.2.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/ziweiService.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "3.2.3",
|
|
"action": "write_implementation",
|
|
"description": "实现本地紫微斗数服务",
|
|
"code": "import type { ZiweiChart, BirthInfo } from '../algorithms/types'\nimport { calculatePalaceScore } from '../algorithms/ziweiAlgorithm'\nimport { calculateSanFangSiZheng } from '../algorithms/sanFangSiZheng'\nimport { storage } from '../utils/storage'\nimport { HeavenlyStem, EarthlyBranch, PalaceType } from '../algorithms/enums'\n\nexport interface GenerateChartParams {\n birthTime: string\n gender: 'male' | 'female'\n timezone: string\n longitude?: number\n latitude?: number\n}\n\nexport class ZiweiService {\n private cacheKey = 'ziwei_charts'\n\n private buildBirthInfo(params: GenerateChartParams): BirthInfo {\n const birthDate = new Date(params.birthTime)\n const year = birthDate.getFullYear()\n const month = birthDate.getMonth() + 1\n const day = birthDate.getDate()\n const hour = birthDate.getHours()\n\n const yearStemIndex = ((year - 4) % 10 + 10) % 10 + 1\n const yearBranchIndex = ((year - 4) % 12 + 12) % 12 + 1\n const monthBranchIndex = ((month + 1) % 12 + 12) % 12 + 1\n const hourBranchIndex = Math.floor((hour + 1) / 2) % 12 + 1\n\n return {\n birthTime: params.birthTime,\n yearStem: HeavenlyStem.fromIndex(yearStemIndex),\n monthStem: HeavenlyStem.fromIndex(yearStemIndex),\n dayStem: HeavenlyStem.fromIndex(yearStemIndex),\n hourStem: HeavenlyStem.fromIndex(yearStemIndex),\n yearBranch: EarthlyBranch.fromIndex(yearBranchIndex),\n monthBranch: EarthlyBranch.fromIndex(monthBranchIndex),\n dayBranch: EarthlyBranch.fromIndex(monthBranchIndex),\n hourBranch: EarthlyBranch.fromIndex(hourBranchIndex),\n gender: params.gender,\n timezone: params.timezone,\n longitude: params.longitude,\n latitude: params.latitude,\n }\n }\n\n private calculateMingGong(birthInfo: BirthInfo): EarthlyBranch {\n const monthIndex = EarthlyBranch.index(birthInfo.monthBranch)\n const hourIndex = EarthlyBranch.index(birthInfo.hourBranch)\n let mingGongIndex = monthIndex - hourIndex + 2\n if (mingGongIndex <= 0) mingGongIndex += 12\n if (mingGongIndex > 12) mingGongIndex -= 12\n return EarthlyBranch.fromIndex(mingGongIndex)\n }\n\n async generateChart(params: GenerateChartParams): Promise<ZiweiChart> {\n const birthInfo = this.buildBirthInfo(params)\n const mingGongBranch = this.calculateMingGong(birthInfo)\n const shenGongBranch = this.calculateShenGong(birthInfo)\n\n const palaces = this.buildPalaces(mingGongBranch)\n for (const palace of palaces) {\n calculatePalaceScore(palace)\n }\n\n const chart: ZiweiChart = {\n birthInfo,\n palaces,\n yearStem: birthInfo.yearStem,\n mingGongBranch,\n shenGongBranch,\n summary: '',\n overallLuck: '',\n }\n\n const sfz = calculateSanFangSiZheng(chart)\n if (sfz) {\n chart.sanFangSiZheng = sfz\n chart.overallLuck = sfz.siZhengScore >= 70 ? '运势良好' : sfz.siZhengScore >= 50 ? '运势平稳' : '运势欠佳'\n }\n\n this.saveChartToCache(params, chart)\n return chart\n }\n\n private calculateShenGong(birthInfo: BirthInfo): EarthlyBranch {\n const monthIndex = EarthlyBranch.index(birthInfo.monthBranch)\n const hourIndex = EarthlyBranch.index(birthInfo.hourBranch)\n let shenGongIndex = monthIndex + hourIndex - 2\n if (shenGongIndex > 12) shenGongIndex -= 12\n if (shenGongIndex <= 0) shenGongIndex += 12\n return EarthlyBranch.fromIndex(shenGongIndex)\n }\n\n private buildPalaces(mingGongBranch: EarthlyBranch): ZiweiChart['palaces'] {\n const branchValues = Object.values(EarthlyBranch).filter(v => typeof v === 'string') as EarthlyBranch[]\n const types = Object.values(PalaceType).filter(v => typeof v === 'string') as PalaceType[]\n const mingIndex = branchValues.indexOf(mingGongBranch)\n const palaces = []\n for (let i = 0; i < 12; i++) {\n const branchIdx = (mingIndex + i) % 12\n palaces.push({\n palaceType: types[i],\n earthlyBranch: branchValues[branchIdx],\n majorStars: [],\n minorStars: [],\n analysis: '',\n score: 50,\n })\n }\n return palaces\n }\n\n private saveChartToCache(params: GenerateChartParams, chart: ZiweiChart): void {\n const key = `${params.birthTime}_${params.gender}_${params.timezone}`\n const charts = storage.get<Record<string, ZiweiChart>>(this.cacheKey) ?? {}\n charts[key] = chart\n storage.set(this.cacheKey, charts)\n }\n\n async getChartFromCache(params: GenerateChartParams): Promise<ZiweiChart | null> {\n const key = `${params.birthTime}_${params.gender}_${params.timezone}`\n const charts = storage.get<Record<string, ZiweiChart>>(this.cacheKey)\n return charts?.[key] ?? null\n }\n}",
|
|
"sourceRef": "Java: IZiweiChartService.java + ZiweiChartServiceImpl.java"
|
|
},
|
|
{
|
|
"id": "3.2.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/ziweiService.test.ts",
|
|
"expectedResult": "PASS - 所有本地紫微服务测试通过"
|
|
},
|
|
{
|
|
"id": "3.2.5",
|
|
"action": "commit",
|
|
"message": "feat(services): 实现本地紫微斗数服务 ZiweiService - 替代远程API"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "3.3",
|
|
"name": "实现本地运势服务",
|
|
"description": "创建 FortuneService 替代远程 API,封装运势算法调用",
|
|
"files": {
|
|
"create": ["src/services/fortuneService.ts"],
|
|
"test": ["src/services/__tests__/fortuneService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.3.1",
|
|
"action": "write_test",
|
|
"description": "编写本地运势服务测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { FortuneService } from '../fortuneService'\nimport type { ZiweiChart, BirthInfo } from '../../algorithms/types'\nimport { HeavenlyStem, EarthlyBranch, PalaceType } from '../../algorithms/enums'\n\nfunction makeTestChart(): ZiweiChart {\n const branchValues = Object.values(EarthlyBranch).filter(v => typeof v === 'string') as EarthlyBranch[]\n const types = Object.values(PalaceType).filter(v => typeof v === 'string') as PalaceType[]\n return {\n birthInfo: {} as BirthInfo,\n palaces: branchValues.map((b, i) => ({ palaceType: types[i], earthlyBranch: b, majorStars: [], minorStars: [], analysis: '', score: 60 })),\n yearStem: HeavenlyStem.JI,\n mingGongBranch: EarthlyBranch.YIN,\n shenGongBranch: EarthlyBranch.SHEN,\n summary: '',\n overallLuck: '',\n }\n}\n\ndescribe('FortuneService', () => {\n let service: FortuneService\n\n beforeEach(() => {\n service = new FortuneService()\n })\n\n it('getDailyFortune 应返回日运数据', async () => {\n const chart = makeTestChart()\n const result = await service.getDailyFortune(chart, '2026-04-27')\n expect(result).not.toBeNull()\n expect(result!.overallScore).toBeGreaterThanOrEqual(0)\n })\n\n it('getMonthlyFortune 应返回月运数据', async () => {\n const chart = makeTestChart()\n const result = await service.getMonthlyFortune(chart, 2026, 4)\n expect(result).not.toBeNull()\n expect(result!.overallScore).toBeGreaterThanOrEqual(0)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/fortuneService.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "3.3.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/fortuneService.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "3.3.3",
|
|
"action": "write_implementation",
|
|
"description": "实现本地运势服务",
|
|
"code": "import type { ZiweiChart, DailyFortune, MonthlyFortune } from '../algorithms/types'\nimport { generateDailyFortune, generateMonthlyFortune } from '../algorithms/fortuneStrategy'\n\nexport class FortuneService {\n async getDailyFortune(chart: ZiweiChart, dateStr: string): Promise<DailyFortune | null> {\n try {\n const date = new Date(dateStr)\n if (isNaN(date.getTime())) return null\n return generateDailyFortune(chart, date)\n } catch (e) {\n console.error(`Daily fortune error for ${dateStr}:`, e)\n return null\n }\n }\n\n async getMonthlyFortune(chart: ZiweiChart, year: number, month: number): Promise<MonthlyFortune | null> {\n try {\n return generateMonthlyFortune(chart, year, month)\n } catch (e) {\n console.error(`Monthly fortune error for ${year}-${month}:`, e)\n return null\n }\n }\n}",
|
|
"sourceRef": "Java: IFortuneAnalysisService.java + FortuneAnalysisServiceImpl.java"
|
|
},
|
|
{
|
|
"id": "3.3.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/fortuneService.test.ts",
|
|
"expectedResult": "PASS - 所有本地运势服务测试通过"
|
|
},
|
|
{
|
|
"id": "3.3.5",
|
|
"action": "commit",
|
|
"message": "feat(services): 实现本地运势服务 FortuneService - 替代远程API"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "3.3.6",
|
|
"name": "扩展运势服务 — 年运与综合运势接口",
|
|
"description": "为 FortuneService 添加 getYearlyFortune、getComprehensiveFortune、getComprehensiveMonthlyFortune、getComprehensiveYearlyFortune 方法",
|
|
"status": "completed",
|
|
"completedAt": "2026-04-28T04:52:00+08:00",
|
|
"files": {
|
|
"modify": ["src/services/fortuneService.ts"],
|
|
"test": ["src/services/__tests__/fortuneService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.3.6.1",
|
|
"action": "write_test",
|
|
"description": "编写年运和综合运势服务测试",
|
|
"status": "completed"
|
|
},
|
|
{
|
|
"id": "3.3.6.2",
|
|
"action": "write_implementation",
|
|
"description": "实现 getYearlyFortune、getComprehensiveFortune、getComprehensiveMonthlyFortune、getComprehensiveYearlyFortune",
|
|
"status": "completed"
|
|
},
|
|
{
|
|
"id": "3.3.6.3",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行全量测试确认通过(12个fortuneService测试全部通过)",
|
|
"status": "completed"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "3.4",
|
|
"name": "实现本地搜索服务",
|
|
"description": "创建 SearchService 替代远程搜索 API,基于已有的 searchOptimizer.ts 实现本地搜索",
|
|
"files": {
|
|
"create": ["src/services/searchService.ts"],
|
|
"test": ["src/services/__tests__/searchService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.4.1",
|
|
"action": "write_test",
|
|
"description": "编写本地搜索服务测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { SearchService } from '../searchService'\n\ndescribe('SearchService', () => {\n let service: SearchService\n\n beforeEach(() => {\n service = new SearchService()\n })\n\n it('search 应返回搜索结果', async () => {\n const result = await service.search({ keyword: '祭祀', dateFrom: '2026-04-01', dateTo: '2026-04-30' })\n expect(Array.isArray(result)).toBe(true)\n })\n\n it('getSearchHistory 应返回历史记录', async () => {\n await service.search({ keyword: '祭祀', dateFrom: '2026-04-01', dateTo: '2026-04-30' })\n const history = await service.getSearchHistory()\n expect(history.length).toBeGreaterThan(0)\n })\n\n it('clearSearchHistory 应清除历史', async () => {\n await service.search({ keyword: '祭祀', dateFrom: '2026-04-01', dateTo: '2026-04-30' })\n await service.clearSearchHistory()\n const history = await service.getSearchHistory()\n expect(history.length).toBe(0)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/searchService.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "3.4.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/searchService.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "3.4.3",
|
|
"action": "write_implementation",
|
|
"description": "实现本地搜索服务",
|
|
"code": "import { AlmanacService } from './almanacService'\nimport type { Almanac } from '../algorithms/types'\nimport { storage } from '../utils/storage'\n\nexport interface SearchParams {\n keyword?: string\n dateFrom?: string\n dateTo?: string\n suitableFilter?: string[]\n unsuitableFilter?: string[]\n}\n\nexport interface SearchHistoryItem {\n keyword: string\n timestamp: number\n resultCount: number\n}\n\nexport class SearchService {\n private almanacService = new AlmanacService()\n private historyKey = 'search_history'\n\n async search(params: SearchParams): Promise<Almanac[]> {\n const results = await this.almanacService.searchAlmanacs(params)\n this.addToHistory(params.keyword ?? '', results.length)\n return results\n }\n\n async getSearchHistory(): Promise<SearchHistoryItem[]> {\n return storage.get<SearchHistoryItem[]>(this.historyKey) ?? []\n }\n\n async clearSearchHistory(): Promise<void> {\n storage.remove(this.historyKey)\n }\n\n private addToHistory(keyword: string, resultCount: number): void {\n if (!keyword) return\n const history = storage.get<SearchHistoryItem[]>(this.historyKey) ?? []\n history.unshift({ keyword, timestamp: Date.now(), resultCount })\n if (history.length > 50) history.length = 50\n storage.set(this.historyKey, history)\n }\n}",
|
|
"sourceRef": "已有 searchOptimizer.ts + httpClient.ts 搜索API"
|
|
},
|
|
{
|
|
"id": "3.4.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/searchService.test.ts",
|
|
"expectedResult": "PASS - 所有本地搜索服务测试通过"
|
|
},
|
|
{
|
|
"id": "3.4.5",
|
|
"action": "commit",
|
|
"message": "feat(services): 实现本地搜索服务 SearchService - 替代远程搜索API"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "3.5",
|
|
"name": "实现本地模板服务",
|
|
"description": "创建 TemplateService 管理搜索模板的本地存储",
|
|
"files": {
|
|
"create": ["src/services/templateService.ts"],
|
|
"test": ["src/services/__tests__/templateService.test.ts"]
|
|
},
|
|
"steps": [
|
|
{
|
|
"id": "3.5.1",
|
|
"action": "write_test",
|
|
"description": "编写本地模板服务测试",
|
|
"code": "import { describe, it, expect } from 'vitest'\nimport { TemplateService } from '../templateService'\n\ndescribe('TemplateService', () => {\n let service: TemplateService\n\n beforeEach(() => {\n service = new TemplateService()\n })\n\n it('getTemplates 应返回模板列表', async () => {\n const result = await service.getTemplates()\n expect(Array.isArray(result)).toBe(true)\n })\n\n it('saveTemplate 应保存模板', async () => {\n await service.saveTemplate({ id: 'test', name: '测试模板', params: { keyword: '祭祀' } })\n const templates = await service.getTemplates()\n expect(templates.some(t => t.id === 'test')).toBe(true)\n })\n\n it('deleteTemplate 应删除模板', async () => {\n await service.saveTemplate({ id: 'test', name: '测试模板', params: { keyword: '祭祀' } })\n await service.deleteTemplate('test')\n const templates = await service.getTemplates()\n expect(templates.some(t => t.id === 'test')).toBe(false)\n })\n})",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/templateService.test.ts",
|
|
"expectedResult": "FAIL - 模块不存在"
|
|
},
|
|
{
|
|
"id": "3.5.2",
|
|
"action": "run_test_verify_fail",
|
|
"description": "运行测试确认失败",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/templateService.test.ts",
|
|
"expectedResult": "FAIL"
|
|
},
|
|
{
|
|
"id": "3.5.3",
|
|
"action": "write_implementation",
|
|
"description": "实现本地模板服务",
|
|
"code": "import { storage } from '../utils/storage'\n\nexport interface SearchTemplate {\n id: string\n name: string\n params: Record<string, any>\n createdAt?: number\n updatedAt?: number\n}\n\nexport class TemplateService {\n private storageKey = 'search_templates'\n\n async getTemplates(): Promise<SearchTemplate[]> {\n return storage.get<SearchTemplate[]>(this.storageKey) ?? []\n }\n\n async saveTemplate(template: SearchTemplate): Promise<void> {\n const templates = await this.getTemplates()\n const now = Date.now()\n const existing = templates.findIndex(t => t.id === template.id)\n\n if (existing >= 0) {\n templates[existing] = { ...template, updatedAt: now }\n } else {\n templates.push({ ...template, createdAt: now, updatedAt: now })\n }\n\n storage.set(this.storageKey, templates)\n }\n\n async deleteTemplate(id: string): Promise<void> {\n const templates = await this.getTemplates()\n const filtered = templates.filter(t => t.id !== id)\n storage.set(this.storageKey, filtered)\n }\n}",
|
|
"sourceRef": "设计规格 Section 5.3 模板管理"
|
|
},
|
|
{
|
|
"id": "3.5.4",
|
|
"action": "run_test_verify_pass",
|
|
"description": "运行测试确认通过",
|
|
"runCommand": "cd everything-is-suitable-uniapp && npx vitest run src/services/__tests__/templateService.test.ts",
|
|
"expectedResult": "PASS - 所有本地模板服务测试通过"
|
|
},
|
|
{
|
|
"id": "3.5.5",
|
|
"action": "commit",
|
|
"message": "feat(services): 实现本地模板服务 TemplateService"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|