KNOWN-007: solarTime.ts 单元测试 KNOWN-008: 消除生产代码中 as any 类型断言 KNOWN-002: 工具层测试覆盖 KNOWN-003: lruCache 边界测试 KNOWN-004: templateService 分支覆盖(100%) KNOWN-001: 6个业务组件测试 额外修复: searchOptimizer memoize bug, templateService 常量修改 bug 测试覆盖率: 75.71% -> 90.22%, 测试用例: 412 -> 671
287 lines
8.4 KiB
TypeScript
287 lines
8.4 KiB
TypeScript
import type { Almanac, ZiweiChart, DailyFortune } from '../algorithms/types'
|
|
import type { SearchTemplate, SearchRequest } from '../types/search'
|
|
import { storage } from '../utils/storage'
|
|
|
|
export interface ReportTemplate {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
sections: TemplateSection[]
|
|
}
|
|
|
|
export interface TemplateSection {
|
|
id: string
|
|
title: string
|
|
type: 'almanac' | 'ziwei' | 'fortune' | 'text'
|
|
visible: boolean
|
|
order: number
|
|
}
|
|
|
|
export interface GeneratedReport {
|
|
templateId: string
|
|
generatedAt: string
|
|
sections: GeneratedSection[]
|
|
}
|
|
|
|
export interface GeneratedSection {
|
|
id: string
|
|
title: string
|
|
type: string
|
|
content: string
|
|
}
|
|
|
|
const DEFAULT_TEMPLATES: ReportTemplate[] = [
|
|
{
|
|
id: 'daily-brief',
|
|
name: '每日简报',
|
|
description: '包含今日黄历和运势概要',
|
|
sections: [
|
|
{ id: 'almanac', title: '今日黄历', type: 'almanac', visible: true, order: 1 },
|
|
{ id: 'fortune', title: '今日运势', type: 'fortune', visible: true, order: 2 },
|
|
],
|
|
},
|
|
{
|
|
id: 'full-report',
|
|
name: '完整报告',
|
|
description: '包含黄历、紫微盘和运势详情',
|
|
sections: [
|
|
{ id: 'almanac', title: '今日黄历', type: 'almanac', visible: true, order: 1 },
|
|
{ id: 'ziwei', title: '紫微斗数', type: 'ziwei', visible: true, order: 2 },
|
|
{ id: 'fortune', title: '运势详情', type: 'fortune', visible: true, order: 3 },
|
|
],
|
|
},
|
|
{
|
|
id: 'ziwei-only',
|
|
name: '紫微专报',
|
|
description: '仅包含紫微斗数排盘分析',
|
|
sections: [
|
|
{ id: 'ziwei', title: '紫微斗数', type: 'ziwei', visible: true, order: 1 },
|
|
],
|
|
},
|
|
]
|
|
|
|
const DEFAULT_SEARCH_TEMPLATES: SearchTemplate[] = [
|
|
{
|
|
id: 'wedding',
|
|
name: '嫁娶吉日',
|
|
description: '查找适合嫁娶的黄道吉日',
|
|
category: '婚嫁',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['嫁娶', '纳采', '订盟'], operator: 'and', exclude: false },
|
|
],
|
|
days: 90,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
{
|
|
id: 'moving',
|
|
name: '搬家吉日',
|
|
description: '查找适合搬家的好日子',
|
|
category: '居住',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['移徙', '入宅'], operator: 'or', exclude: false },
|
|
{ id: '2', type: 'unsuitable', items: ['破土', '安葬'], operator: 'or', exclude: true },
|
|
],
|
|
days: 60,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
{
|
|
id: 'business',
|
|
name: '开市吉日',
|
|
description: '查找适合开市交易的吉日',
|
|
category: '商业',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['开市', '交易', '立券', '纳财'], operator: 'or', exclude: false },
|
|
],
|
|
days: 30,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
{
|
|
id: 'travel',
|
|
name: '出行吉日',
|
|
description: '查找适合出行的安全日子',
|
|
category: '出行',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['出行'], operator: 'and', exclude: false },
|
|
{ id: '2', type: 'unsuitable', items: ['嫁娶', '安葬'], operator: 'or', exclude: true },
|
|
],
|
|
days: 30,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
{
|
|
id: 'pray',
|
|
name: '祈福吉日',
|
|
description: '查找适合祈福祭祀的日子',
|
|
category: '祭祀',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['祭祀', '祈福'], operator: 'or', exclude: false },
|
|
],
|
|
days: 30,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
{
|
|
id: 'construction',
|
|
name: '修造吉日',
|
|
description: '查找适合修造动土的日子',
|
|
category: '建筑',
|
|
condition: {
|
|
conditions: [
|
|
{ id: '1', type: 'suitable', items: ['修造', '动土'], operator: 'or', exclude: false },
|
|
],
|
|
days: 60,
|
|
sortBy: 'date',
|
|
sortOrder: 'asc',
|
|
},
|
|
},
|
|
]
|
|
|
|
export class TemplateService {
|
|
private templatesKey = 'report_templates'
|
|
private searchTemplatesKey = 'search_templates'
|
|
private cachedSearchTemplates: SearchTemplate[] | null = null
|
|
|
|
getSearchTemplates(): SearchTemplate[] {
|
|
if (this.cachedSearchTemplates) return this.cachedSearchTemplates
|
|
const stored = storage.get<SearchTemplate[]>(this.searchTemplatesKey)
|
|
this.cachedSearchTemplates = stored && stored.length > 0 ? stored : DEFAULT_SEARCH_TEMPLATES
|
|
return this.cachedSearchTemplates
|
|
}
|
|
|
|
getCategories(): string[] {
|
|
const templates = this.getSearchTemplates()
|
|
const categories = new Set(templates.map(t => t.category))
|
|
return Array.from(categories)
|
|
}
|
|
|
|
getTemplatesByCategory(category: string): SearchTemplate[] {
|
|
return this.getSearchTemplates().filter(t => t.category === category)
|
|
}
|
|
|
|
searchTemplates(keyword: string): SearchTemplate[] {
|
|
const lower = keyword.toLowerCase()
|
|
return this.getSearchTemplates().filter(
|
|
t => t.name.toLowerCase().includes(lower) || t.description.toLowerCase().includes(lower),
|
|
)
|
|
}
|
|
|
|
async getTemplates(): Promise<ReportTemplate[]> {
|
|
const stored = storage.get<ReportTemplate[]>(this.templatesKey)
|
|
if (stored && stored.length > 0) return stored
|
|
return DEFAULT_TEMPLATES
|
|
}
|
|
|
|
async getTemplateById(id: string): Promise<ReportTemplate | null> {
|
|
const templates = await this.getTemplates()
|
|
return templates.find(t => t.id === id) ?? null
|
|
}
|
|
|
|
async saveTemplate(template: ReportTemplate): Promise<void> {
|
|
const templates = [...(await this.getTemplates())]
|
|
const index = templates.findIndex(t => t.id === template.id)
|
|
if (index >= 0) {
|
|
templates[index] = template
|
|
} else {
|
|
templates.push(template)
|
|
}
|
|
storage.set(this.templatesKey, templates)
|
|
}
|
|
|
|
async deleteTemplate(id: string): Promise<boolean> {
|
|
const templates = [...(await this.getTemplates())]
|
|
const index = templates.findIndex(t => t.id === id)
|
|
if (index < 0) return false
|
|
templates.splice(index, 1)
|
|
storage.set(this.templatesKey, templates)
|
|
return true
|
|
}
|
|
|
|
async generateReport(
|
|
templateId: string,
|
|
data: { almanac?: Almanac; chart?: ZiweiChart; fortune?: DailyFortune },
|
|
): Promise<GeneratedReport | null> {
|
|
const template = await this.getTemplateById(templateId)
|
|
if (!template) return null
|
|
|
|
const sections: GeneratedSection[] = template.sections
|
|
.filter(s => s.visible)
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(s => ({
|
|
id: s.id,
|
|
title: s.title,
|
|
type: s.type,
|
|
content: this.renderSection(s.type, data),
|
|
}))
|
|
|
|
return {
|
|
templateId,
|
|
generatedAt: new Date().toISOString(),
|
|
sections,
|
|
}
|
|
}
|
|
|
|
private renderSection(
|
|
type: string,
|
|
data: { almanac?: Almanac; chart?: ZiweiChart; fortune?: DailyFortune },
|
|
): string {
|
|
switch (type) {
|
|
case 'almanac':
|
|
return data.almanac ? this.renderAlmanac(data.almanac) : '暂无黄历数据'
|
|
case 'ziwei':
|
|
return data.chart ? this.renderZiwei(data.chart) : '暂无紫微数据'
|
|
case 'fortune':
|
|
return data.fortune ? this.renderFortune(data.fortune) : '暂无运势数据'
|
|
case 'text':
|
|
return ''
|
|
default:
|
|
return ''
|
|
}
|
|
}
|
|
|
|
private renderAlmanac(almanac: Almanac): string {
|
|
const parts: string[] = []
|
|
parts.push(`日期:${almanac.solarDate}`)
|
|
parts.push(`宜:${almanac.suitable.join('、')}`)
|
|
parts.push(`忌:${almanac.unsuitable.join('、')}`)
|
|
parts.push(`建除:${almanac.jianChu}`)
|
|
parts.push(`星神:${almanac.starGod}`)
|
|
return parts.join('\n')
|
|
}
|
|
|
|
private renderZiwei(chart: ZiweiChart): string {
|
|
const parts: string[] = []
|
|
parts.push(chart.summary ?? '')
|
|
if (chart.sanFangSiZheng) {
|
|
parts.push(`三方得分:${chart.sanFangSiZheng.sanFangScore}`)
|
|
parts.push(`四正得分:${chart.sanFangSiZheng.siZhengScore}`)
|
|
}
|
|
return parts.join('\n')
|
|
}
|
|
|
|
private renderFortune(fortune: DailyFortune): string {
|
|
const parts: string[] = []
|
|
parts.push(`综合评分:${fortune.overallScore}`)
|
|
if (fortune.careerAdvice) parts.push(`事业:${fortune.careerAdvice}`)
|
|
if (fortune.wealthAdvice) parts.push(`财运:${fortune.wealthAdvice}`)
|
|
if (fortune.relationshipAdvice) parts.push(`感情:${fortune.relationshipAdvice}`)
|
|
if (fortune.healthAdvice) parts.push(`健康:${fortune.healthAdvice}`)
|
|
return parts.join('\n')
|
|
}
|
|
}
|
|
|
|
const templateService = new TemplateService()
|
|
export default templateService
|