feat: 添加 UniApp 核心源码文件
- App.vue 应用入口 - algorithms/ 算法模块(黄历、紫微斗数、运势、日历等) - services/ 业务服务层 - utils/ 工具函数(storage、exportImport) - 配置文件(manifest.json、pages.json、vite.config.ts) - H5 入口 index.html
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<title>万事宜</title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch() {
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background-color: #f4f4f5;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,396 @@
|
||||
import type { LunarDate as LunarDateType } from '../utils/lunar'
|
||||
import type { Almanac as AlmanacType } from './types'
|
||||
import { solarToLunar } from '../utils/lunar'
|
||||
|
||||
const MONTH_BRANCHES = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2]
|
||||
|
||||
const SUITABLE_ACTIVITIES_BY_JIANCHU: string[][] = [
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['扫除', '治病', '破土', '安葬', '出行', '求医'],
|
||||
['祈福', '祭祀', '安葬', '修造', '动土', '交易'],
|
||||
['修造', '动土', '交易', '纳财', '签约', '安床'],
|
||||
['纳财', '签约', '安床', '嫁娶', '开市', '交易'],
|
||||
['上任', '出行', '求医', '祭祀', '祈福', '安葬'],
|
||||
['破屋', '坏垣', '拆卸', '破土', '修坟', '安葬'],
|
||||
['安葬', '破土', '修坟', '祭祀', '祈福', '出行'],
|
||||
['嫁娶', '开市', '交易', '纳财', '签约', '安床'],
|
||||
['纳财', '买田', '置业', '交易', '签约', '安床'],
|
||||
['开业', '开工', '开张', '嫁娶', '出行', '求财'],
|
||||
['塞穴', '断蚁', '筑堤', '修造', '动土', '交易'],
|
||||
]
|
||||
|
||||
const UNSUITABLE_ACTIVITIES_BY_JIANCHU: string[][] = [
|
||||
['安葬', '破土', '修坟', '祭祀', '祈福', '出行'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
['安葬', '破土', '修坟', '祭祀', '祈福', '出行'],
|
||||
['安葬', '破土', '修坟', '祭祀', '祈福', '出行'],
|
||||
['安葬', '破土', '修坟', '祭祀', '祈福', '出行'],
|
||||
['开业', '嫁娶', '出行', '求财', '祭祀', '祈福'],
|
||||
]
|
||||
|
||||
const CLASH_BRANCHES = [7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
const EVIL_DIRECTIONS = ['南', '东', '北', '西', '南', '东', '北', '西', '南', '东', '北', '西']
|
||||
|
||||
const STAR_GODS = ['青龙', '明堂', '天刑', '朱雀', '金匮', '天德', '白虎', '玉堂', '天牢', '玄武', '司命', '勾陈']
|
||||
|
||||
const NA_YIN_FIVE_ELEMENTS = [
|
||||
'海中金', '海中金', '炉中火', '炉中火', '大林木', '大林木', '路旁土', '路旁土', '剑锋金', '剑锋金',
|
||||
'山头火', '山头火', '涧下水', '涧下水', '城头土', '城头土', '白蜡金', '白蜡金', '杨柳木', '杨柳木',
|
||||
'泉中水', '泉中水', '屋上土', '屋上土', '霹雳火', '霹雳火', '松柏木', '松柏木', '长流水', '长流水',
|
||||
'沙中金', '沙中金', '山下火', '山下火', '平地木', '平地木', '壁上土', '壁上土', '金箔金', '金箔金',
|
||||
'覆灯火', '覆灯火', '天河水', '天河水', '大驿土', '大驿土', '钗钏金', '钗钏金', '桑柘木', '桑柘木',
|
||||
'大溪水', '大溪水', '沙中土', '沙中土', '天上火', '天上火', '石榴木', '石榴木', '大海水', '大海水',
|
||||
]
|
||||
|
||||
const PENGZU_STEM_TABOOS = [
|
||||
'甲不开仓财物耗散',
|
||||
'乙不栽植千株不长',
|
||||
'丙不修灶必见灾殃',
|
||||
'丁不剃头头必生疮',
|
||||
'戊不受田田主不祥',
|
||||
'己不破券二比并亡',
|
||||
'庚不经络织机虚张',
|
||||
'辛不合酱主人不尝',
|
||||
'壬不汲水更难提防',
|
||||
'癸不词讼理弱敌强',
|
||||
]
|
||||
|
||||
const PENGZU_BRANCH_TABOOS = [
|
||||
'子不问卜自惹祸殃',
|
||||
'丑不冠带主不还乡',
|
||||
'寅不祭祀神鬼不尝',
|
||||
'卯不穿井水泉不香',
|
||||
'辰不哭泣必主重丧',
|
||||
'巳不远行财物伏藏',
|
||||
'午不苫盖屋主更张',
|
||||
'未不服药毒气入肠',
|
||||
'申不安床鬼祟入房',
|
||||
'酉不会客醉坐颠狂',
|
||||
'戌不吃犬作怪上床',
|
||||
'亥不嫁娶不利新郎',
|
||||
]
|
||||
|
||||
const JIANCHU_NAMES = ['建', '除', '满', '平', '定', '执', '破', '危', '成', '收', '开', '闭']
|
||||
|
||||
const EARTHLY_BRANCH_NAMES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
|
||||
|
||||
const FETUS_GOD_MAP: Record<number, string> = {
|
||||
1: '房床北',
|
||||
2: '厕厕东北',
|
||||
3: '炉灶东北',
|
||||
4: '大门东',
|
||||
5: '栖床东南',
|
||||
6: '房床东南',
|
||||
7: '房床南',
|
||||
8: '厕厕西南',
|
||||
9: '炉灶西南',
|
||||
10: '大门西',
|
||||
11: '栖床西北',
|
||||
12: '房床西北',
|
||||
}
|
||||
|
||||
export interface AlmanacResult {
|
||||
solarDate: string
|
||||
lunarDate: LunarDateType
|
||||
jianChu: string
|
||||
suitable: string[]
|
||||
unsuitable: string[]
|
||||
godDirection: string
|
||||
joyDirection: string
|
||||
fortuneDirection: string
|
||||
nobleDirection: string
|
||||
clash: string
|
||||
evil: string
|
||||
starGod: string
|
||||
naYin: string
|
||||
fetusGod: string
|
||||
pengzuTaboo: string
|
||||
}
|
||||
|
||||
function calculateTotalDays(year: number, month: number, day: number): number {
|
||||
let totalDays = 0
|
||||
|
||||
for (let y = 1900; y < year; y++) {
|
||||
totalDays += isLeapYear(y) ? 366 : 365
|
||||
}
|
||||
|
||||
const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
if (isLeapYear(year)) {
|
||||
daysInMonth[1] = 29
|
||||
}
|
||||
|
||||
for (let m = 1; m < month; m++) {
|
||||
totalDays += daysInMonth[m - 1]
|
||||
}
|
||||
|
||||
totalDays += day
|
||||
return totalDays
|
||||
}
|
||||
|
||||
function isLeapYear(year: number): boolean {
|
||||
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)
|
||||
}
|
||||
|
||||
function getDayStemIndex(solarDate: Date): number {
|
||||
const totalDays = calculateTotalDays(solarDate.getFullYear(), solarDate.getMonth() + 1, solarDate.getDate())
|
||||
let dayStemIndex = (totalDays + 3) % 10
|
||||
if (dayStemIndex === 0) {
|
||||
dayStemIndex = 10
|
||||
}
|
||||
return dayStemIndex
|
||||
}
|
||||
|
||||
function getDayBranchIndexFromSolar(solarDate: Date): number {
|
||||
const totalDays = calculateTotalDays(solarDate.getFullYear(), solarDate.getMonth() + 1, solarDate.getDate())
|
||||
let dayBranchIndex = (totalDays + 1) % 12
|
||||
if (dayBranchIndex === 0) {
|
||||
dayBranchIndex = 12
|
||||
}
|
||||
return dayBranchIndex
|
||||
}
|
||||
|
||||
function getDayBranchIndexFromLunar(lunarDay: number): number {
|
||||
let dayBranchIndex = (lunarDay + 2) % 12
|
||||
if (dayBranchIndex === 0) {
|
||||
dayBranchIndex = 12
|
||||
}
|
||||
return dayBranchIndex
|
||||
}
|
||||
|
||||
export function calculateJianChu(lunarMonth: number, lunarDay: number): string {
|
||||
if (lunarMonth < 1 || lunarMonth > 12) {
|
||||
throw new Error(`农历月份超出范围:${lunarMonth}`)
|
||||
}
|
||||
if (lunarDay < 1 || lunarDay > 30) {
|
||||
throw new Error(`农历日期超出范围:${lunarDay}`)
|
||||
}
|
||||
|
||||
const monthBranch = MONTH_BRANCHES[lunarMonth - 1]
|
||||
let jianChuIndex = (monthBranch + lunarDay - 1) % 12
|
||||
if (jianChuIndex === 0) {
|
||||
jianChuIndex = 12
|
||||
}
|
||||
|
||||
return JIANCHU_NAMES[jianChuIndex - 1]
|
||||
}
|
||||
|
||||
export function calculateSuitable(jianChu: string): string[] {
|
||||
if (!jianChu) {
|
||||
throw new Error('建除十二神不能为空')
|
||||
}
|
||||
|
||||
const index = JIANCHU_NAMES.indexOf(jianChu)
|
||||
if (index === -1) {
|
||||
throw new Error(`无效的建除十二神:${jianChu}`)
|
||||
}
|
||||
|
||||
return [...SUITABLE_ACTIVITIES_BY_JIANCHU[index]]
|
||||
}
|
||||
|
||||
export function calculateUnsuitable(jianChu: string): string[] {
|
||||
if (!jianChu) {
|
||||
throw new Error('建除十二神不能为空')
|
||||
}
|
||||
|
||||
const index = JIANCHU_NAMES.indexOf(jianChu)
|
||||
if (index === -1) {
|
||||
throw new Error(`无效的建除十二神:${jianChu}`)
|
||||
}
|
||||
|
||||
return [...UNSUITABLE_ACTIVITIES_BY_JIANCHU[index]]
|
||||
}
|
||||
|
||||
export function calculateGodDirection(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
|
||||
if (dayStemIndex === 1 || dayStemIndex === 2) return '东北'
|
||||
if (dayStemIndex === 3 || dayStemIndex === 4) return '正西'
|
||||
if (dayStemIndex === 5 || dayStemIndex === 6) return '正北'
|
||||
if (dayStemIndex === 7 || dayStemIndex === 8) return '正东'
|
||||
if (dayStemIndex === 9 || dayStemIndex === 10) return '正南'
|
||||
return ''
|
||||
}
|
||||
|
||||
export function calculateJoyDirection(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
|
||||
if (dayStemIndex % 2 === 1) return '正北'
|
||||
return '东北'
|
||||
}
|
||||
|
||||
export function calculateFortuneDirection(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
|
||||
if (dayStemIndex === 1 || dayStemIndex === 2) return '正北'
|
||||
if (dayStemIndex === 3 || dayStemIndex === 4) return '西北'
|
||||
if (dayStemIndex === 5 || dayStemIndex === 7 || dayStemIndex === 8) return '西南'
|
||||
if (dayStemIndex === 6) return '正南'
|
||||
if (dayStemIndex === 9 || dayStemIndex === 10) return '东南'
|
||||
return ''
|
||||
}
|
||||
|
||||
export function calculateNobleDirection(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
|
||||
if (dayStemIndex === 1) return '丑、未'
|
||||
if (dayStemIndex === 2) return '子、申'
|
||||
if (dayStemIndex === 3 || dayStemIndex === 4) return '亥、酉'
|
||||
if (dayStemIndex === 5 || dayStemIndex === 6) return '丑、未'
|
||||
if (dayStemIndex === 7 || dayStemIndex === 8) return '午、寅'
|
||||
if (dayStemIndex === 9 || dayStemIndex === 10) return '卯、巳'
|
||||
return ''
|
||||
}
|
||||
|
||||
export function calculateClash(lunarDay: number): string {
|
||||
if (lunarDay < 1 || lunarDay > 30) {
|
||||
throw new Error(`农历日期超出范围:${lunarDay}`)
|
||||
}
|
||||
|
||||
const dayBranchIndex = getDayBranchIndexFromLunar(lunarDay)
|
||||
const clashBranchIndex = CLASH_BRANCHES[dayBranchIndex - 1]
|
||||
|
||||
return EARTHLY_BRANCH_NAMES[clashBranchIndex - 1]
|
||||
}
|
||||
|
||||
export function calculateEvil(lunarDay: number): string {
|
||||
if (lunarDay < 1 || lunarDay > 30) {
|
||||
throw new Error(`农历日期超出范围:${lunarDay}`)
|
||||
}
|
||||
|
||||
const dayBranchIndex = getDayBranchIndexFromLunar(lunarDay)
|
||||
return EVIL_DIRECTIONS[dayBranchIndex - 1]
|
||||
}
|
||||
|
||||
export function calculateStarGod(solarDate: Date): string {
|
||||
const dayBranchIndex = getDayBranchIndexFromSolar(solarDate)
|
||||
return STAR_GODS[dayBranchIndex - 1]
|
||||
}
|
||||
|
||||
export function calculateNaYin(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
const dayBranchIndex = getDayBranchIndexFromSolar(solarDate)
|
||||
|
||||
const naYinIndex = (dayStemIndex - 1) * 6 + Math.floor((dayBranchIndex - 1) / 2)
|
||||
|
||||
return NA_YIN_FIVE_ELEMENTS[naYinIndex]
|
||||
}
|
||||
|
||||
export function calculateFetusGod(lunarDay: number): string {
|
||||
if (lunarDay < 1 || lunarDay > 30) {
|
||||
throw new Error(`农历日期超出范围:${lunarDay}`)
|
||||
}
|
||||
|
||||
const dayBranchIndex = getDayBranchIndexFromLunar(lunarDay)
|
||||
return FETUS_GOD_MAP[dayBranchIndex] ?? ''
|
||||
}
|
||||
|
||||
export function calculatePengzuTaboo(solarDate: Date): string {
|
||||
const dayStemIndex = getDayStemIndex(solarDate)
|
||||
const dayBranchIndex = getDayBranchIndexFromSolar(solarDate)
|
||||
|
||||
const stemTaboo = PENGZU_STEM_TABOOS[dayStemIndex - 1]
|
||||
const branchTaboo = PENGZU_BRANCH_TABOOS[dayBranchIndex - 1]
|
||||
|
||||
return `${stemTaboo},${branchTaboo}`
|
||||
}
|
||||
|
||||
export function calculateAlmanac(solarDate: Date): AlmanacResult {
|
||||
const lunarDate = solarToLunar(solarDate)
|
||||
|
||||
const jianChu = calculateJianChu(lunarDate.month, lunarDate.day)
|
||||
const suitable = calculateSuitable(jianChu)
|
||||
const unsuitable = calculateUnsuitable(jianChu)
|
||||
const godDirection = calculateGodDirection(solarDate)
|
||||
const joyDirection = calculateJoyDirection(solarDate)
|
||||
const fortuneDirection = calculateFortuneDirection(solarDate)
|
||||
const nobleDirection = calculateNobleDirection(solarDate)
|
||||
const clash = calculateClash(lunarDate.day)
|
||||
const evil = calculateEvil(lunarDate.day)
|
||||
const starGod = calculateStarGod(solarDate)
|
||||
const naYin = calculateNaYin(solarDate)
|
||||
const fetusGod = calculateFetusGod(lunarDate.day)
|
||||
const pengzuTaboo = calculatePengzuTaboo(solarDate)
|
||||
|
||||
return {
|
||||
solarDate: solarDate.toISOString().slice(0, 10),
|
||||
lunarDate,
|
||||
jianChu,
|
||||
suitable,
|
||||
unsuitable,
|
||||
godDirection,
|
||||
joyDirection,
|
||||
fortuneDirection,
|
||||
nobleDirection,
|
||||
clash,
|
||||
evil,
|
||||
starGod,
|
||||
naYin,
|
||||
fetusGod,
|
||||
pengzuTaboo,
|
||||
}
|
||||
}
|
||||
|
||||
function convertLunarDate(lunar: LunarDateType): AlmanacType['lunarDate'] {
|
||||
return {
|
||||
lunarYear: lunar.ganZhi ?? String(lunar.year),
|
||||
lunarMonth: String(lunar.month),
|
||||
lunarDay: String(lunar.day),
|
||||
isLeapMonth: lunar.isLeapMonth,
|
||||
zodiac: lunar.zodiac ?? '',
|
||||
solarTerm: '',
|
||||
weekday: '',
|
||||
}
|
||||
}
|
||||
|
||||
function convertToAlmanac(result: AlmanacResult): AlmanacType {
|
||||
return {
|
||||
solarDate: result.solarDate,
|
||||
lunarDate: convertLunarDate(result.lunarDate),
|
||||
suitable: result.suitable,
|
||||
unsuitable: result.unsuitable,
|
||||
godDirection: result.godDirection,
|
||||
joyDirection: result.joyDirection,
|
||||
fortuneDirection: result.fortuneDirection,
|
||||
nobleDirection: result.nobleDirection,
|
||||
clash: result.clash,
|
||||
evil: result.evil,
|
||||
jianChu: result.jianChu,
|
||||
starGod: result.starGod,
|
||||
naYin: result.naYin,
|
||||
fetusGod: result.fetusGod,
|
||||
pengzuTaboo: result.pengzuTaboo,
|
||||
}
|
||||
}
|
||||
|
||||
export function getAlmanacByDate(dateStr: string): AlmanacType | null {
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return null
|
||||
const result = calculateAlmanac(date)
|
||||
return convertToAlmanac(result)
|
||||
}
|
||||
|
||||
export function getAlmanacsByRange(startDateStr: string, endDateStr: string): AlmanacType[] {
|
||||
const startDate = new Date(startDateStr)
|
||||
const endDate = new Date(endDateStr)
|
||||
|
||||
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) return []
|
||||
if (startDate > endDate) return []
|
||||
|
||||
const results: AlmanacType[] = []
|
||||
const current = new Date(startDate)
|
||||
|
||||
while (current <= endDate) {
|
||||
const result = calculateAlmanac(new Date(current))
|
||||
results.push(convertToAlmanac(result))
|
||||
current.setDate(current.getDate() + 1)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { solarToLunar, getSolarTerm, getLunarFestivals } from '../utils/lunar'
|
||||
import type { LunarDate as LunarDateType } from '../utils/lunar'
|
||||
|
||||
export interface CalendarDay {
|
||||
solarDate: string
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
weekday: string
|
||||
lunarDate: LunarDateType
|
||||
solarFestivals: string[]
|
||||
lunarFestivals: string[]
|
||||
solarTerm: string | null
|
||||
isWorkday: boolean
|
||||
}
|
||||
|
||||
export interface CalendarMonth {
|
||||
year: number
|
||||
month: number
|
||||
days: CalendarDay[]
|
||||
}
|
||||
|
||||
export interface SolarTermInfo {
|
||||
name: string
|
||||
month: number
|
||||
day: number
|
||||
index: number
|
||||
}
|
||||
|
||||
const WEEKDAY_NAMES = ['日', '一', '二', '三', '四', '五', '六']
|
||||
|
||||
const SOLAR_FESTIVALS: Record<string, string[]> = {
|
||||
'1-1': ['元旦'],
|
||||
'2-14': ['情人节'],
|
||||
'3-8': ['妇女节'],
|
||||
'3-12': ['植树节'],
|
||||
'4-1': ['愚人节'],
|
||||
'5-1': ['劳动节'],
|
||||
'5-4': ['青年节'],
|
||||
'6-1': ['儿童节'],
|
||||
'7-1': ['建党节'],
|
||||
'8-1': ['建军节'],
|
||||
'9-10': ['教师节'],
|
||||
'10-1': ['国庆节'],
|
||||
'12-24': ['平安夜'],
|
||||
'12-25': ['圣诞节'],
|
||||
}
|
||||
|
||||
const SOLAR_TERM_NAMES = [
|
||||
'小寒', '大寒', '立春', '雨水', '惊蛰', '春分',
|
||||
'清明', '谷雨', '立夏', '小满', '芒种', '夏至',
|
||||
'小暑', '大暑', '立秋', '处暑', '白露', '秋分',
|
||||
'寒露', '霜降', '立冬', '小雪', '大雪', '冬至',
|
||||
]
|
||||
|
||||
const SOLAR_TERM_BASE_DATES: { month: number; day: number; offset: number }[] = [
|
||||
{ month: 1, day: 6, offset: 0 },
|
||||
{ month: 1, day: 20, offset: 0 },
|
||||
{ month: 2, day: 4, offset: 0 },
|
||||
{ month: 2, day: 19, offset: 0 },
|
||||
{ month: 3, day: 6, offset: 0 },
|
||||
{ month: 3, day: 21, offset: 0 },
|
||||
{ month: 4, day: 5, offset: 0 },
|
||||
{ month: 4, day: 20, offset: 0 },
|
||||
{ month: 5, day: 6, offset: 0 },
|
||||
{ month: 5, day: 21, offset: 0 },
|
||||
{ month: 6, day: 6, offset: 0 },
|
||||
{ month: 6, day: 21, offset: 0 },
|
||||
{ month: 7, day: 7, offset: 0 },
|
||||
{ month: 7, day: 23, offset: 0 },
|
||||
{ month: 8, day: 7, offset: 0 },
|
||||
{ month: 8, day: 23, offset: 0 },
|
||||
{ month: 9, day: 8, offset: 0 },
|
||||
{ month: 9, day: 23, offset: 0 },
|
||||
{ month: 10, day: 8, offset: 0 },
|
||||
{ month: 10, day: 23, offset: 0 },
|
||||
{ month: 11, day: 7, offset: 0 },
|
||||
{ month: 11, day: 22, offset: 0 },
|
||||
{ month: 12, day: 7, offset: 0 },
|
||||
{ month: 12, day: 22, offset: 0 },
|
||||
]
|
||||
|
||||
export function getSolarFestivals(month: number, day: number): string[] {
|
||||
const key = `${month}-${day}`
|
||||
return SOLAR_FESTIVALS[key] ? [...SOLAR_FESTIVALS[key]] : []
|
||||
}
|
||||
|
||||
export function getSolarTermForYear(year: number): SolarTermInfo[] {
|
||||
const yearMod = year % 4
|
||||
const results: SolarTermInfo[] = []
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const base = SOLAR_TERM_BASE_DATES[i]
|
||||
const dayAdjust = (yearMod + Math.floor(year / 100) - 20) % 3
|
||||
const adjustedDay = Math.max(1, Math.min(31, base.day + (dayAdjust > 1 ? 1 : 0)))
|
||||
|
||||
results.push({
|
||||
name: SOLAR_TERM_NAMES[i],
|
||||
month: base.month,
|
||||
day: adjustedDay,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function getWeekdayName(date: Date): string {
|
||||
return '星期' + WEEKDAY_NAMES[date.getDay()]
|
||||
}
|
||||
|
||||
function checkIsWorkday(date: Date): boolean {
|
||||
const day = date.getDay()
|
||||
return day !== 0 && day !== 6
|
||||
}
|
||||
|
||||
function getDaysInMonth(year: number, month: number): number {
|
||||
return new Date(year, month, 0).getDate()
|
||||
}
|
||||
|
||||
export function getCalendarDay(year: number, month: number, day: number): CalendarDay {
|
||||
const date = new Date(year, month - 1, day)
|
||||
const lunarDate = solarToLunar(date)
|
||||
const solarTermInfo = getSolarTerm(date)
|
||||
const solarFestivals = getSolarFestivals(month, day)
|
||||
const lunarFestivals = getLunarFestivals(date)
|
||||
|
||||
return {
|
||||
solarDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
weekday: getWeekdayName(date),
|
||||
lunarDate,
|
||||
solarFestivals,
|
||||
lunarFestivals,
|
||||
solarTerm: solarTermInfo.name,
|
||||
isWorkday: checkIsWorkday(date),
|
||||
}
|
||||
}
|
||||
|
||||
export function getCalendarMonth(year: number, month: number): CalendarMonth {
|
||||
const daysInMonth = getDaysInMonth(year, month)
|
||||
const days: CalendarDay[] = []
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
days.push(getCalendarDay(year, month, day))
|
||||
}
|
||||
|
||||
return {
|
||||
year,
|
||||
month,
|
||||
days,
|
||||
}
|
||||
}
|
||||
|
||||
export function isWorkday(date: Date): boolean {
|
||||
return checkIsWorkday(date)
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
export enum HeavenlyStem {
|
||||
JIA = 'JIA',
|
||||
YI = 'YI',
|
||||
BING = 'BING',
|
||||
DING = 'DING',
|
||||
WU = 'WU',
|
||||
JI = 'JI',
|
||||
GENG = 'GENG',
|
||||
XIN = 'XIN',
|
||||
REN = 'REN',
|
||||
GUI = 'GUI',
|
||||
}
|
||||
|
||||
export namespace HeavenlyStem {
|
||||
const DATA: Record<HeavenlyStem, { name: string; index: number }> = {
|
||||
[HeavenlyStem.JIA]: { name: '甲', index: 1 },
|
||||
[HeavenlyStem.YI]: { name: '乙', index: 2 },
|
||||
[HeavenlyStem.BING]: { name: '丙', index: 3 },
|
||||
[HeavenlyStem.DING]: { name: '丁', index: 4 },
|
||||
[HeavenlyStem.WU]: { name: '戊', index: 5 },
|
||||
[HeavenlyStem.JI]: { name: '己', index: 6 },
|
||||
[HeavenlyStem.GENG]: { name: '庚', index: 7 },
|
||||
[HeavenlyStem.XIN]: { name: '辛', index: 8 },
|
||||
[HeavenlyStem.REN]: { name: '壬', index: 9 },
|
||||
[HeavenlyStem.GUI]: { name: '癸', index: 10 },
|
||||
}
|
||||
|
||||
const VALUES: HeavenlyStem[] = Object.values(HeavenlyStem).filter(
|
||||
(v): v is HeavenlyStem => typeof v === 'string',
|
||||
)
|
||||
|
||||
export const name = (s: HeavenlyStem): string => DATA[s].name
|
||||
export const index = (s: HeavenlyStem): number => DATA[s].index
|
||||
|
||||
export function fromIndex(i: number): HeavenlyStem {
|
||||
return VALUES[((i - 1) % 10 + 10) % 10]
|
||||
}
|
||||
|
||||
export function values(): HeavenlyStem[] {
|
||||
return [...VALUES]
|
||||
}
|
||||
}
|
||||
|
||||
export enum EarthlyBranch {
|
||||
ZI = 'ZI',
|
||||
CHOU = 'CHOU',
|
||||
YIN = 'YIN',
|
||||
MAO = 'MAO',
|
||||
CHEN = 'CHEN',
|
||||
SI = 'SI',
|
||||
WU = 'WU',
|
||||
WEI = 'WEI',
|
||||
SHEN = 'SHEN',
|
||||
YOU = 'YOU',
|
||||
XU = 'XU',
|
||||
HAI = 'HAI',
|
||||
}
|
||||
|
||||
export namespace EarthlyBranch {
|
||||
const DATA: Record<EarthlyBranch, { name: string; index: number }> = {
|
||||
[EarthlyBranch.ZI]: { name: '子', index: 1 },
|
||||
[EarthlyBranch.CHOU]: { name: '丑', index: 2 },
|
||||
[EarthlyBranch.YIN]: { name: '寅', index: 3 },
|
||||
[EarthlyBranch.MAO]: { name: '卯', index: 4 },
|
||||
[EarthlyBranch.CHEN]: { name: '辰', index: 5 },
|
||||
[EarthlyBranch.SI]: { name: '巳', index: 6 },
|
||||
[EarthlyBranch.WU]: { name: '午', index: 7 },
|
||||
[EarthlyBranch.WEI]: { name: '未', index: 8 },
|
||||
[EarthlyBranch.SHEN]: { name: '申', index: 9 },
|
||||
[EarthlyBranch.YOU]: { name: '酉', index: 10 },
|
||||
[EarthlyBranch.XU]: { name: '戌', index: 11 },
|
||||
[EarthlyBranch.HAI]: { name: '亥', index: 12 },
|
||||
}
|
||||
|
||||
const VALUES: EarthlyBranch[] = Object.values(EarthlyBranch).filter(
|
||||
(v): v is EarthlyBranch => typeof v === 'string',
|
||||
)
|
||||
|
||||
export const name = (s: EarthlyBranch): string => DATA[s].name
|
||||
export const index = (s: EarthlyBranch): number => DATA[s].index
|
||||
|
||||
export function fromIndex(i: number): EarthlyBranch {
|
||||
return VALUES[((i - 1) % 12 + 12) % 12]
|
||||
}
|
||||
|
||||
export function values(): EarthlyBranch[] {
|
||||
return [...VALUES]
|
||||
}
|
||||
}
|
||||
|
||||
export enum StarNature {
|
||||
JI = 'JI',
|
||||
XIONG = 'XIONG',
|
||||
ZHONGHE = 'ZHONGHE',
|
||||
}
|
||||
|
||||
export namespace StarNature {
|
||||
const DATA: Record<StarNature, { name: string; description: string }> = {
|
||||
[StarNature.JI]: { name: '吉', description: '吉星' },
|
||||
[StarNature.XIONG]: { name: '凶', description: '凶星' },
|
||||
[StarNature.ZHONGHE]: { name: '中', description: '中性' },
|
||||
}
|
||||
|
||||
export const name = (s: StarNature): string => DATA[s].name
|
||||
export const description = (s: StarNature): string => DATA[s].description
|
||||
}
|
||||
|
||||
export enum TransformationType {
|
||||
LU = 'LU',
|
||||
QUAN = 'QUAN',
|
||||
KE = 'KE',
|
||||
JI = 'JI',
|
||||
}
|
||||
|
||||
export namespace TransformationType {
|
||||
const DATA: Record<TransformationType, { name: string }> = {
|
||||
[TransformationType.LU]: { name: '禄' },
|
||||
[TransformationType.QUAN]: { name: '权' },
|
||||
[TransformationType.KE]: { name: '科' },
|
||||
[TransformationType.JI]: { name: '忌' },
|
||||
}
|
||||
|
||||
export const name = (s: TransformationType): string => DATA[s].name
|
||||
}
|
||||
|
||||
export enum MajorStar {
|
||||
ZIWEI = 'ZIWEI',
|
||||
TIANJI = 'TIANJI',
|
||||
TAIYANG = 'TAIYANG',
|
||||
WUQU = 'WUQU',
|
||||
TIANTONG = 'TIANTONG',
|
||||
LIANCHEN = 'LIANCHEN',
|
||||
TIANFU = 'TIANFU',
|
||||
TAIYIN = 'TAIYIN',
|
||||
TANLANG = 'TANLANG',
|
||||
JUMEN = 'JUMEN',
|
||||
TIANXIANG = 'TIANXIANG',
|
||||
TIANLIANG = 'TIANLIANG',
|
||||
QISHA = 'QISHA',
|
||||
POJUN = 'POJUN',
|
||||
}
|
||||
|
||||
export namespace MajorStar {
|
||||
const DATA: Record<
|
||||
MajorStar,
|
||||
{ name: string; index: number; nature: StarNature; alias: string; description: string }
|
||||
> = {
|
||||
[MajorStar.ZIWEI]: { name: '紫微', index: 1, nature: StarNature.JI, alias: '帝星', description: '领导、权威、尊贵' },
|
||||
[MajorStar.TIANJI]: { name: '天机', index: 2, nature: StarNature.JI, alias: '智星', description: '智慧、谋略、变通' },
|
||||
[MajorStar.TAIYANG]: { name: '太阳', index: 3, nature: StarNature.JI, alias: '贵星', description: '光明、热情、慷慨' },
|
||||
[MajorStar.WUQU]: { name: '武曲', index: 4, nature: StarNature.XIONG, alias: '财星', description: '刚毅、务实、财帛' },
|
||||
[MajorStar.TIANTONG]: { name: '天同', index: 5, nature: StarNature.JI, alias: '福星', description: '温和、福气、享受' },
|
||||
[MajorStar.LIANCHEN]: { name: '廉贞', index: 6, nature: StarNature.XIONG, alias: '情星', description: '感情、才华、是非' },
|
||||
[MajorStar.TIANFU]: { name: '天府', index: 7, nature: StarNature.JI, alias: '库星', description: '稳重、保守、财富' },
|
||||
[MajorStar.TAIYIN]: { name: '太阴', index: 8, nature: StarNature.ZHONGHE, alias: '母星', description: '温柔、内敛、母性' },
|
||||
[MajorStar.TANLANG]: { name: '贪狼', index: 9, nature: StarNature.XIONG, alias: '欲星', description: '欲望、魅力、桃花' },
|
||||
[MajorStar.JUMEN]: { name: '巨门', index: 10, nature: StarNature.XIONG, alias: '口星', description: '口才、是非、沟通' },
|
||||
[MajorStar.TIANXIANG]: { name: '天相', index: 11, nature: StarNature.JI, alias: '印星', description: '辅佐、稳重、贵人' },
|
||||
[MajorStar.TIANLIANG]: { name: '天梁', index: 12, nature: StarNature.ZHONGHE, alias: '荫星', description: '恩泽、稳重、长辈' },
|
||||
[MajorStar.QISHA]: { name: '七杀', index: 13, nature: StarNature.XIONG, alias: '将星', description: '威猛、果断、开创' },
|
||||
[MajorStar.POJUN]: { name: '破军', index: 14, nature: StarNature.XIONG, alias: '耗星', description: '破旧立新、变动、消耗' },
|
||||
}
|
||||
|
||||
const VALUES: MajorStar[] = Object.values(MajorStar).filter(
|
||||
(v): v is MajorStar => typeof v === 'string',
|
||||
)
|
||||
|
||||
export const name = (s: MajorStar): string => DATA[s].name
|
||||
export const index = (s: MajorStar): number => DATA[s].index
|
||||
export const nature = (s: MajorStar): StarNature => DATA[s].nature
|
||||
export const alias = (s: MajorStar): string => DATA[s].alias
|
||||
export const description = (s: MajorStar): string => DATA[s].description
|
||||
|
||||
export function values(): MajorStar[] {
|
||||
return [...VALUES]
|
||||
}
|
||||
}
|
||||
|
||||
export enum PalaceType {
|
||||
MING = 'MING',
|
||||
XIONG = 'XIONG',
|
||||
FUQI = 'FUQI',
|
||||
ZINV = 'ZINV',
|
||||
CAI = 'CAI',
|
||||
JILU = 'JILU',
|
||||
QIAN = 'QIAN',
|
||||
GUANLU = 'GUANLU',
|
||||
TUDI = 'TUDI',
|
||||
FUBEN = 'FUBEN',
|
||||
FU = 'FU',
|
||||
SHEN = 'SHEN',
|
||||
}
|
||||
|
||||
export namespace PalaceType {
|
||||
const DATA: Record<PalaceType, { name: string }> = {
|
||||
[PalaceType.MING]: { name: '命宫' },
|
||||
[PalaceType.XIONG]: { name: '兄弟宫' },
|
||||
[PalaceType.FUQI]: { name: '夫妻宫' },
|
||||
[PalaceType.ZINV]: { name: '子女宫' },
|
||||
[PalaceType.CAI]: { name: '财帛宫' },
|
||||
[PalaceType.JILU]: { name: '疾厄宫' },
|
||||
[PalaceType.QIAN]: { name: '迁移宫' },
|
||||
[PalaceType.GUANLU]: { name: '官禄宫' },
|
||||
[PalaceType.TUDI]: { name: '田宅宫' },
|
||||
[PalaceType.FUBEN]: { name: '福德宫' },
|
||||
[PalaceType.FU]: { name: '父母宫' },
|
||||
[PalaceType.SHEN]: { name: '身宫' },
|
||||
}
|
||||
|
||||
const VALUES: PalaceType[] = Object.values(PalaceType).filter(
|
||||
(v): v is PalaceType => typeof v === 'string',
|
||||
)
|
||||
|
||||
export const name = (s: PalaceType): string => DATA[s].name
|
||||
|
||||
export function values(): PalaceType[] {
|
||||
return [...VALUES]
|
||||
}
|
||||
}
|
||||
|
||||
export enum FortuneType {
|
||||
DAILY = 'DAILY',
|
||||
MONTHLY = 'MONTHLY',
|
||||
YEARLY = 'YEARLY',
|
||||
CAREER = 'CAREER',
|
||||
WEALTH = 'WEALTH',
|
||||
LOVE = 'LOVE',
|
||||
HEALTH = 'HEALTH',
|
||||
STUDY = 'STUDY',
|
||||
SOCIAL = 'SOCIAL',
|
||||
TRAVEL = 'TRAVEL',
|
||||
}
|
||||
|
||||
export enum MinorStarType {
|
||||
LUCKY = 'LUCKY',
|
||||
EVIL = 'EVIL',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
|
||||
export namespace MinorStarType {
|
||||
const DATA: Record<MinorStarType, { description: string }> = {
|
||||
[MinorStarType.LUCKY]: { description: '吉星' },
|
||||
[MinorStarType.EVIL]: { description: '煞星' },
|
||||
[MinorStarType.OTHER]: { description: '其他' },
|
||||
}
|
||||
|
||||
export const description = (s: MinorStarType): string => DATA[s].description
|
||||
}
|
||||
|
||||
export enum MinorStar {
|
||||
ZUOFU = 'ZUOFU',
|
||||
YOUBI = 'YOUBI',
|
||||
TIANKUI = 'TIANKUI',
|
||||
TIANYUE = 'TIANYUE',
|
||||
WENCHANG = 'WENCHANG',
|
||||
WENQU = 'WENQU',
|
||||
QINGYANG = 'QINGYANG',
|
||||
TUOLUO = 'TUOLUO',
|
||||
HUOXING = 'HUOXING',
|
||||
LINGXING = 'LINGXING',
|
||||
DIKONG = 'DIKONG',
|
||||
DIJIE = 'DIJIE',
|
||||
TIANXING = 'TIANXING',
|
||||
LUCUN = 'LUCUN',
|
||||
TIANMA = 'TIANMA',
|
||||
HUAGAI = 'HUAGAI',
|
||||
LONGCHI = 'LONGCHI',
|
||||
FENGGU = 'FENGGU',
|
||||
HONGLUAN = 'HONGLUAN',
|
||||
TIANXI = 'TIANXI',
|
||||
GUCHEN = 'GUCHEN',
|
||||
GUAKU = 'GUAKU',
|
||||
}
|
||||
|
||||
export namespace MinorStar {
|
||||
const DATA: Record<
|
||||
MinorStar,
|
||||
{ name: string; type: MinorStarType; alias: string; description: string }
|
||||
> = {
|
||||
[MinorStar.ZUOFU]: { name: '左辅', type: MinorStarType.LUCKY, alias: '左辅星', description: '主辅佐、助力、贵人' },
|
||||
[MinorStar.YOUBI]: { name: '右弼', type: MinorStarType.LUCKY, alias: '右弼星', description: '主辅佐、助力、贵人' },
|
||||
[MinorStar.TIANKUI]: { name: '天魁', type: MinorStarType.LUCKY, alias: '天魁星', description: '主贵人、助力、机遇' },
|
||||
[MinorStar.TIANYUE]: { name: '天钺', type: MinorStarType.LUCKY, alias: '天钺星', description: '主贵人、助力、机遇' },
|
||||
[MinorStar.WENCHANG]: { name: '文昌', type: MinorStarType.LUCKY, alias: '文昌星', description: '主科名、文采、学业' },
|
||||
[MinorStar.WENQU]: { name: '文曲', type: MinorStarType.LUCKY, alias: '文曲星', description: '主口才、艺术、才华' },
|
||||
[MinorStar.QINGYANG]: { name: '擎羊', type: MinorStarType.EVIL, alias: '羊刃星', description: '主刑伤、是非、血光' },
|
||||
[MinorStar.TUOLUO]: { name: '陀罗', type: MinorStarType.EVIL, alias: '陀罗星', description: '主拖延、迟滞、纠结' },
|
||||
[MinorStar.HUOXING]: { name: '火星', type: MinorStarType.EVIL, alias: '火星', description: '主急躁、突发、灾难' },
|
||||
[MinorStar.LINGXING]: { name: '铃星', type: MinorStarType.EVIL, alias: '铃星', description: '主突发、灾难、变动' },
|
||||
[MinorStar.DIKONG]: { name: '地空', type: MinorStarType.EVIL, alias: '地空星', description: '主空亡、损失、虚耗' },
|
||||
[MinorStar.DIJIE]: { name: '地劫', type: MinorStarType.EVIL, alias: '地劫星', description: '主劫夺、损失、破财' },
|
||||
[MinorStar.TIANXING]: { name: '天刑', type: MinorStarType.EVIL, alias: '天刑星', description: '主刑伤、官非、诉讼' },
|
||||
[MinorStar.LUCUN]: { name: '禄存', type: MinorStarType.OTHER, alias: '禄存星', description: '主财帛、福气' },
|
||||
[MinorStar.TIANMA]: { name: '天马', type: MinorStarType.OTHER, alias: '天马星', description: '主迁移、奔波、变动' },
|
||||
[MinorStar.HUAGAI]: { name: '华盖', type: MinorStarType.OTHER, alias: '华盖星', description: '主孤独、艺术、宗教' },
|
||||
[MinorStar.LONGCHI]: { name: '龙池', type: MinorStarType.OTHER, alias: '龙池星', description: '主才华、艺术、文采' },
|
||||
[MinorStar.FENGGU]: { name: '凤阁', type: MinorStarType.OTHER, alias: '凤阁星', description: '主才华、艺术、文采' },
|
||||
[MinorStar.HONGLUAN]: { name: '红鸾', type: MinorStarType.OTHER, alias: '红鸾星', description: '主桃花、婚姻、感情' },
|
||||
[MinorStar.TIANXI]: { name: '天喜', type: MinorStarType.OTHER, alias: '天喜星', description: '主桃花、婚姻、感情' },
|
||||
[MinorStar.GUCHEN]: { name: '孤辰', type: MinorStarType.OTHER, alias: '孤辰星', description: '主孤独、寂寞、寡居' },
|
||||
[MinorStar.GUAKU]: { name: '寡宿', type: MinorStarType.OTHER, alias: '寡宿星', description: '主孤独、寂寞、寡居' },
|
||||
}
|
||||
|
||||
const VALUES: MinorStar[] = Object.values(MinorStar).filter(
|
||||
(v): v is MinorStar => typeof v === 'string',
|
||||
)
|
||||
|
||||
export const name = (s: MinorStar): string => DATA[s].name
|
||||
export const type = (s: MinorStar): MinorStarType => DATA[s].type
|
||||
export const alias = (s: MinorStar): string => DATA[s].alias
|
||||
export const description = (s: MinorStar): string => DATA[s].description
|
||||
|
||||
export function values(): MinorStar[] {
|
||||
return [...VALUES]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
import { PalaceType, StarNature, EarthlyBranch } from './enums'
|
||||
import { determineLuckLevel } from './ziweiAlgorithm'
|
||||
import {
|
||||
generateDailyFortune,
|
||||
generateMonthlyFortune,
|
||||
generateYearlyFortune,
|
||||
calculateDailyAdjustment,
|
||||
calculateMonthlyAdjustment,
|
||||
calculateYearlyAdjustment,
|
||||
} from './fortuneStrategy'
|
||||
import type {
|
||||
ZiweiChart,
|
||||
Palace,
|
||||
LoveFortuneDetail,
|
||||
StudyFortuneDetail,
|
||||
SocialFortuneDetail,
|
||||
TravelFortuneDetail,
|
||||
DailyFortune,
|
||||
MonthlyFortune,
|
||||
YearlyFortune,
|
||||
} from './types'
|
||||
|
||||
const PEACH_BLOSSOM_STARS = ['HONGLUAN', 'TIANXI', 'TANLANG']
|
||||
const STUDY_STARS = ['WENCHANG', 'WENQU']
|
||||
const NOBLE_STARS = ['ZUOFU', 'YOUBI', 'TIANKUI', 'TIANYUE']
|
||||
const TRAVEL_STARS = ['TIANMA']
|
||||
|
||||
const DATING_TIME_MAP: Record<string, string> = {
|
||||
'大吉': '下午3点至5点,傍晚6点至8点',
|
||||
'吉': '下午2点至4点,晚上7点至9点',
|
||||
'中吉': '上午10点至12点,下午3点至5点',
|
||||
'平': '下午1点至3点',
|
||||
'中平': '上午9点至11点',
|
||||
'小凶': '暂不宜约会,宜先调整自身状态',
|
||||
'凶': '不宜约会,宜静待时机',
|
||||
}
|
||||
|
||||
const STUDY_TIME_MAP: Record<string, string> = {
|
||||
'大吉': '清晨6点至8点,晚上8点至10点',
|
||||
'吉': '上午8点至10点,晚上7点至9点',
|
||||
'中吉': '上午9点至11点,下午3点至5点',
|
||||
'平': '上午10点至12点',
|
||||
'中平': '下午2点至4点',
|
||||
'小凶': '学习效率较低,建议短暂休息后复习',
|
||||
'凶': '不宜高强度学习,宜轻松阅读',
|
||||
}
|
||||
|
||||
const SOCIAL_TIME_MAP: Record<string, string> = {
|
||||
'大吉': '中午12点至2点,晚上6点至9点',
|
||||
'吉': '下午1点至3点,晚上7点至10点',
|
||||
'中吉': '上午10点至12点,下午3点至5点',
|
||||
'平': '下午2点至4点',
|
||||
'中平': '上午9点至11点',
|
||||
'小凶': '社交运势偏弱,宜减少应酬',
|
||||
'凶': '不宜社交,宜独处调整',
|
||||
}
|
||||
|
||||
const TRAVEL_TIME_MAP: Record<string, string> = {
|
||||
'大吉': '上午8点至10点出发最佳',
|
||||
'吉': '上午9点至11点出发较佳',
|
||||
'中吉': '上午10点至12点可出行',
|
||||
'平': '中午前后出行即可',
|
||||
'中平': '下午出行较为稳妥',
|
||||
'小凶': '出行需谨慎,注意安全',
|
||||
'凶': '不宜远行,近处活动为佳',
|
||||
}
|
||||
|
||||
const SUITABLE_SUBJECTS_MAP: Record<string, string> = {
|
||||
'大吉': '文学、哲学、历史等文科类,或管理、领导力相关',
|
||||
'吉': '语言、艺术、社会科学等,或技术类基础学科',
|
||||
'中吉': '数学、逻辑、计算机等理科类',
|
||||
'平': '实用技能、职业培训等应用型学科',
|
||||
'中平': '通识类课程,宜广泛涉猎',
|
||||
'小凶': '宜复习巩固已有知识,不宜开启新领域',
|
||||
'凶': '宜休息调整,轻松阅读即可',
|
||||
}
|
||||
|
||||
const SUITABLE_OCCASION_MAP: Record<string, string> = {
|
||||
'大吉': '商务宴请、高端社交、行业峰会',
|
||||
'吉': '朋友聚会、团队活动、文化交流',
|
||||
'中吉': '社区活动、兴趣小组、学习交流',
|
||||
'平': '日常社交、同事聚餐',
|
||||
'中平': '小范围聚会、线上交流',
|
||||
'小凶': '减少社交活动,宜小范围相处',
|
||||
'凶': '不宜参加社交活动,宜独处',
|
||||
}
|
||||
|
||||
const SUITABLE_TRANSPORT_MAP: Record<string, string> = {
|
||||
'大吉': '飞机、高铁等快速交通工具',
|
||||
'吉': '高铁、自驾等舒适出行方式',
|
||||
'中吉': '火车、大巴等常规交通',
|
||||
'平': '公共交通、地铁等日常出行',
|
||||
'中平': '短途公交、步行等',
|
||||
'小凶': '出行需注意安全,避免自驾',
|
||||
'凶': '不宜远行,就近活动为佳',
|
||||
}
|
||||
|
||||
const DIRECTION_LIST = ['东方', '南方', '西方', '北方', '东南', '西南', '东北', '西北']
|
||||
|
||||
function findPalace(chart: ZiweiChart, palaceType: PalaceType): Palace | undefined {
|
||||
return chart.palaces.find(p => p.palaceType === palaceType)
|
||||
}
|
||||
|
||||
function countMinorStars(palace: Palace | undefined, starNames: string[]): number {
|
||||
if (palace == null) return 0
|
||||
let count = 0
|
||||
for (const star of starNames) {
|
||||
if (palace.minorStars.includes(star)) count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
function countMajorStars(palace: Palace | undefined, starNames: string[]): number {
|
||||
if (palace == null) return 0
|
||||
let count = 0
|
||||
for (const starInfo of palace.majorStars) {
|
||||
if (starNames.includes(starInfo.star)) count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
function clampScore(score: number): number {
|
||||
return Math.max(0, Math.min(100, Math.floor(score)))
|
||||
}
|
||||
|
||||
export function analyzeLoveFortune(chart: ZiweiChart, date: Date): LoveFortuneDetail {
|
||||
const fuqiPalace = findPalace(chart, PalaceType.FUQI)
|
||||
const baseScore = fuqiPalace?.score ?? 50
|
||||
|
||||
const peachBlossomCount =
|
||||
countMinorStars(fuqiPalace, PEACH_BLOSSOM_STARS) +
|
||||
countMajorStars(fuqiPalace, PEACH_BLOSSOM_STARS)
|
||||
const peachBonus = peachBlossomCount * 5
|
||||
|
||||
const dayAdj = fuqiPalace ? calculateDailyAdjustment(fuqiPalace, date) : 0
|
||||
const loveScore = clampScore(baseScore + peachBonus + dayAdj)
|
||||
const luckLevel = determineLuckLevel(loveScore)
|
||||
|
||||
const peachBlossomIndex = getPeachBlossomLevel(peachBlossomCount)
|
||||
|
||||
return {
|
||||
loveScore,
|
||||
overallLoveLuck: luckLevel,
|
||||
peachBlossomIndex,
|
||||
singleAdvice: getSingleAdvice(luckLevel, peachBlossomCount),
|
||||
datingAdvice: getDatingAdvice(luckLevel),
|
||||
marriageAdvice: getMarriageAdvice(luckLevel),
|
||||
bestDatingTime: DATING_TIME_MAP[luckLevel] ?? '下午2点至4点',
|
||||
}
|
||||
}
|
||||
|
||||
function getPeachBlossomLevel(count: number): string {
|
||||
if (count >= 3) return '极旺'
|
||||
if (count >= 2) return '旺盛'
|
||||
if (count >= 1) return '一般'
|
||||
return '偏弱'
|
||||
}
|
||||
|
||||
function getSingleAdvice(luckLevel: string, peachCount: number): string {
|
||||
if (peachCount >= 2) return '桃花旺盛,有望遇到心仪对象,可主动出击'
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '感情运势良好,可多参加社交活动,增加遇见良缘的机会'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '感情运势平稳,宜耐心等待,不宜急于求成'
|
||||
return '感情运势偏弱,宜先提升自我,静待良缘'
|
||||
}
|
||||
|
||||
function getDatingAdvice(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '感情甜蜜,适合深入交流,增进彼此了解'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '感情平稳,保持日常沟通,多关心对方需求'
|
||||
if (luckLevel === '中平') return '感情略显平淡,可制造小惊喜,增添情趣'
|
||||
return '感情易生波折,需多包容理解,避免争执'
|
||||
}
|
||||
|
||||
function getMarriageAdvice(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '婚姻运势良好,适合商讨婚事或增进夫妻感情'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '婚姻运势平稳,宜相互扶持,共同经营家庭'
|
||||
if (luckLevel === '中平') return '婚姻运势一般,需多沟通协调,避免误解'
|
||||
return '婚姻运势欠佳,需多忍让包容,共同面对困难'
|
||||
}
|
||||
|
||||
export function analyzeStudyFortune(chart: ZiweiChart, date: Date): StudyFortuneDetail {
|
||||
const guanluPalace = findPalace(chart, PalaceType.GUANLU)
|
||||
const baseScore = guanluPalace?.score ?? 50
|
||||
|
||||
const studyStarCount = countMinorStars(guanluPalace, STUDY_STARS)
|
||||
const studyBonus = studyStarCount * 6
|
||||
|
||||
const dayAdj = guanluPalace ? calculateDailyAdjustment(guanluPalace, date) : 0
|
||||
const studyScore = clampScore(baseScore + studyBonus + dayAdj)
|
||||
const luckLevel = determineLuckLevel(studyScore)
|
||||
|
||||
return {
|
||||
studyScore,
|
||||
overallStudyLuck: luckLevel,
|
||||
focusIndex: getFocusLevel(studyScore),
|
||||
memoryIndex: getMemoryLevel(studyScore),
|
||||
studyAdvice: getStudyAdvice(luckLevel, studyStarCount),
|
||||
examLuck: getExamLuck(luckLevel),
|
||||
bestStudyTime: STUDY_TIME_MAP[luckLevel] ?? '上午9点至11点',
|
||||
suitableSubjects: SUITABLE_SUBJECTS_MAP[luckLevel] ?? '通识类课程',
|
||||
studyEnvironmentAdvice: getStudyEnvironmentAdvice(luckLevel),
|
||||
}
|
||||
}
|
||||
|
||||
function getFocusLevel(score: number): string {
|
||||
if (score >= 80) return '高度集中'
|
||||
if (score >= 60) return '较为集中'
|
||||
if (score >= 40) return '一般'
|
||||
return '容易分心'
|
||||
}
|
||||
|
||||
function getMemoryLevel(score: number): string {
|
||||
if (score >= 80) return '记忆力佳'
|
||||
if (score >= 60) return '记忆力良好'
|
||||
if (score >= 40) return '记忆力一般'
|
||||
return '记忆力偏弱'
|
||||
}
|
||||
|
||||
function getStudyAdvice(luckLevel: string, studyStarCount: number): string {
|
||||
if (studyStarCount >= 2) return '文昌文曲双星照耀,学业运势极佳,适合攻克难题和备考'
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '学业运势良好,适合深入学习新知识,效率较高'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '学业运势平稳,按计划学习即可,注意劳逸结合'
|
||||
return '学业运势偏弱,宜复习巩固已有知识,不宜开启新领域'
|
||||
}
|
||||
|
||||
function getExamLuck(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '考试运佳,发挥出色'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '考试运平稳,正常发挥'
|
||||
if (luckLevel === '中平') return '考试运一般,需多加准备'
|
||||
return '考试运欠佳,需格外用心准备'
|
||||
}
|
||||
|
||||
function getStudyEnvironmentAdvice(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '宜在安静明亮的环境中学习,效率最佳'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '宜选择图书馆或自习室等安静场所'
|
||||
return '宜在熟悉的环境中学习,减少外界干扰'
|
||||
}
|
||||
|
||||
export function analyzeSocialFortune(chart: ZiweiChart, date: Date): SocialFortuneDetail {
|
||||
const xiongPalace = findPalace(chart, PalaceType.XIONG)
|
||||
const baseScore = xiongPalace?.score ?? 50
|
||||
|
||||
const nobleStarCount = countMinorStars(xiongPalace, NOBLE_STARS)
|
||||
const nobleBonus = nobleStarCount * 5
|
||||
|
||||
const dayAdj = xiongPalace ? calculateDailyAdjustment(xiongPalace, date) : 0
|
||||
const socialScore = clampScore(baseScore + nobleBonus + dayAdj)
|
||||
const luckLevel = determineLuckLevel(socialScore)
|
||||
|
||||
return {
|
||||
socialScore,
|
||||
overallSocialLuck: luckLevel,
|
||||
popularityIndex: getPopularityLevel(socialScore),
|
||||
communicationAbility: getCommunicationLevel(socialScore),
|
||||
socialAdvice: getSocialAdvice(luckLevel, nobleStarCount),
|
||||
nobleLuck: getNobleLuck(nobleStarCount),
|
||||
bestSocialTime: SOCIAL_TIME_MAP[luckLevel] ?? '下午2点至4点',
|
||||
suitableOccasion: SUITABLE_OCCASION_MAP[luckLevel] ?? '日常社交',
|
||||
interpersonalAdvice: getInterpersonalAdvice(luckLevel),
|
||||
}
|
||||
}
|
||||
|
||||
function getPopularityLevel(score: number): string {
|
||||
if (score >= 80) return '人气旺盛'
|
||||
if (score >= 60) return '人缘良好'
|
||||
if (score >= 40) return '人缘一般'
|
||||
return '人缘偏弱'
|
||||
}
|
||||
|
||||
function getCommunicationLevel(score: number): string {
|
||||
if (score >= 80) return '表达流畅'
|
||||
if (score >= 60) return '沟通顺畅'
|
||||
if (score >= 40) return '沟通一般'
|
||||
return '沟通不畅'
|
||||
}
|
||||
|
||||
function getSocialAdvice(luckLevel: string, nobleStarCount: number): string {
|
||||
if (nobleStarCount >= 2) return '贵人星照耀,社交运势极佳,易获他人帮助与支持'
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '社交运势良好,适合拓展人脉,参加社交活动'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '社交运势平稳,保持正常交往即可'
|
||||
return '社交运势偏弱,宜减少不必要的应酬,专注自身'
|
||||
}
|
||||
|
||||
function getNobleLuck(nobleStarCount: number): string {
|
||||
if (nobleStarCount >= 3) return '贵人运极旺,多有助力'
|
||||
if (nobleStarCount >= 2) return '贵人运旺盛,易遇贵人'
|
||||
if (nobleStarCount >= 1) return '贵人运一般,偶有助力'
|
||||
return '贵人运偏弱,需自力更生'
|
||||
}
|
||||
|
||||
function getInterpersonalAdvice(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '人际关系和谐,宜主动关心他人,增进感情'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '人际关系平稳,保持真诚待人即可'
|
||||
if (luckLevel === '中平') return '人际关系略显平淡,可主动联络朋友'
|
||||
return '人际关系易生摩擦,需多忍让,避免冲突'
|
||||
}
|
||||
|
||||
export function analyzeTravelFortune(chart: ZiweiChart, date: Date): TravelFortuneDetail {
|
||||
const qianPalace = findPalace(chart, PalaceType.QIAN)
|
||||
const baseScore = qianPalace?.score ?? 50
|
||||
|
||||
const travelStarCount = countMinorStars(qianPalace, TRAVEL_STARS)
|
||||
const travelBonus = travelStarCount * 5
|
||||
|
||||
const dayAdj = qianPalace ? calculateDailyAdjustment(qianPalace, date) : 0
|
||||
const travelScore = clampScore(baseScore + travelBonus + dayAdj)
|
||||
const luckLevel = determineLuckLevel(travelScore)
|
||||
|
||||
return {
|
||||
travelScore,
|
||||
overallTravelLuck: luckLevel,
|
||||
safetyIndex: getSafetyLevel(travelScore),
|
||||
smoothnessIndex: getSmoothnessLevel(travelScore),
|
||||
travelAdvice: getTravelAdvice(luckLevel, travelStarCount),
|
||||
bestTravelTime: TRAVEL_TIME_MAP[luckLevel] ?? '中午前后',
|
||||
suitableTransport: SUITABLE_TRANSPORT_MAP[luckLevel] ?? '常规交通',
|
||||
precautions: getPrecautions(luckLevel),
|
||||
nobleDirection: getNobleDirection(chart, date),
|
||||
}
|
||||
}
|
||||
|
||||
function getSafetyLevel(score: number): string {
|
||||
if (score >= 80) return '安全指数高'
|
||||
if (score >= 60) return '安全指数良好'
|
||||
if (score >= 40) return '安全指数一般'
|
||||
return '安全指数偏低'
|
||||
}
|
||||
|
||||
function getSmoothnessLevel(score: number): string {
|
||||
if (score >= 80) return '顺畅无阻'
|
||||
if (score >= 60) return '较为顺畅'
|
||||
if (score >= 40) return '一般'
|
||||
return '可能受阻'
|
||||
}
|
||||
|
||||
function getTravelAdvice(luckLevel: string, travelStarCount: number): string {
|
||||
if (travelStarCount >= 1) return '天马星动,出行运势佳,适合出差旅行'
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '出行运势良好,旅途顺利,可安排出行计划'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '出行运势平稳,按计划出行即可'
|
||||
return '出行运势欠佳,建议减少不必要的外出'
|
||||
}
|
||||
|
||||
function getPrecautions(luckLevel: string): string {
|
||||
if (luckLevel === '大吉' || luckLevel === '吉') return '出行顺利,注意常规安全即可'
|
||||
if (luckLevel === '中吉' || luckLevel === '平') return '出行注意交通安全,提前查看天气'
|
||||
if (luckLevel === '中平') return '出行需谨慎,注意人身和财物安全'
|
||||
return '出行风险较高,务必注意安全,避免夜间出行'
|
||||
}
|
||||
|
||||
function getNobleDirection(chart: ZiweiChart, _date: Date): string {
|
||||
const mingGong = findPalace(chart, PalaceType.MING)
|
||||
if (mingGong == null) return '东方'
|
||||
|
||||
const hasJiStar = mingGong.majorStars.some(s => s.nature === StarNature.JI)
|
||||
const branchIndex = EarthlyBranch.index(chart.mingGongBranch) - 1
|
||||
const directionIndex = hasJiStar
|
||||
? (branchIndex + 1) % DIRECTION_LIST.length
|
||||
: (branchIndex + 3) % DIRECTION_LIST.length
|
||||
|
||||
return DIRECTION_LIST[directionIndex]
|
||||
}
|
||||
|
||||
export function generateComprehensiveFortune(chart: ZiweiChart, date: Date): DailyFortune {
|
||||
const baseFortune = generateDailyFortune(chart, date)
|
||||
|
||||
const loveFortune = analyzeLoveFortune(chart, date)
|
||||
const studyFortune = analyzeStudyFortune(chart, date)
|
||||
const socialFortune = analyzeSocialFortune(chart, date)
|
||||
const travelFortune = analyzeTravelFortune(chart, date)
|
||||
|
||||
return {
|
||||
...baseFortune,
|
||||
loveScore: loveFortune.loveScore,
|
||||
overallLoveLuck: loveFortune.overallLoveLuck,
|
||||
peachBlossomIndex: loveFortune.peachBlossomIndex,
|
||||
singleAdvice: loveFortune.singleAdvice,
|
||||
datingAdvice: loveFortune.datingAdvice,
|
||||
marriageAdvice: loveFortune.marriageAdvice,
|
||||
bestDatingTime: loveFortune.bestDatingTime,
|
||||
studyScore: studyFortune.studyScore,
|
||||
overallStudyLuck: studyFortune.overallStudyLuck,
|
||||
focusIndex: studyFortune.focusIndex,
|
||||
memoryIndex: studyFortune.memoryIndex,
|
||||
studyAdvice: studyFortune.studyAdvice,
|
||||
examLuck: studyFortune.examLuck,
|
||||
bestStudyTime: studyFortune.bestStudyTime,
|
||||
suitableSubjects: studyFortune.suitableSubjects,
|
||||
studyEnvironmentAdvice: studyFortune.studyEnvironmentAdvice,
|
||||
socialScore: socialFortune.socialScore,
|
||||
overallSocialLuck: socialFortune.overallSocialLuck,
|
||||
popularityIndex: socialFortune.popularityIndex,
|
||||
communicationAbility: socialFortune.communicationAbility,
|
||||
socialAdvice: socialFortune.socialAdvice,
|
||||
nobleLuck: socialFortune.nobleLuck,
|
||||
bestSocialTime: socialFortune.bestSocialTime,
|
||||
suitableOccasion: socialFortune.suitableOccasion,
|
||||
interpersonalAdvice: socialFortune.interpersonalAdvice,
|
||||
travelScore: travelFortune.travelScore,
|
||||
overallTravelLuck: travelFortune.overallTravelLuck,
|
||||
safetyIndex: travelFortune.safetyIndex,
|
||||
smoothnessIndex: travelFortune.smoothnessIndex,
|
||||
travelAdvice: travelFortune.travelAdvice,
|
||||
bestTravelTime: travelFortune.bestTravelTime,
|
||||
suitableTransport: travelFortune.suitableTransport,
|
||||
precautions: travelFortune.precautions,
|
||||
nobleDirection: travelFortune.nobleDirection,
|
||||
}
|
||||
}
|
||||
|
||||
export function generateComprehensiveMonthlyFortune(
|
||||
chart: ZiweiChart,
|
||||
year: number,
|
||||
month: number,
|
||||
): MonthlyFortune {
|
||||
const baseFortune = generateMonthlyFortune(chart, year, month)
|
||||
|
||||
const midMonthDate = new Date(year, month - 1, 15)
|
||||
const loveFortune = analyzeLoveFortune(chart, midMonthDate)
|
||||
const studyFortune = analyzeStudyFortune(chart, midMonthDate)
|
||||
const socialFortune = analyzeSocialFortune(chart, midMonthDate)
|
||||
const travelFortune = analyzeTravelFortune(chart, midMonthDate)
|
||||
|
||||
return {
|
||||
...baseFortune,
|
||||
loveScore: loveFortune.loveScore,
|
||||
overallLoveLuck: loveFortune.overallLoveLuck,
|
||||
peachBlossomIndex: loveFortune.peachBlossomIndex,
|
||||
singleAdvice: loveFortune.singleAdvice,
|
||||
datingAdvice: loveFortune.datingAdvice,
|
||||
marriageAdvice: loveFortune.marriageAdvice,
|
||||
bestDatingTime: loveFortune.bestDatingTime,
|
||||
studyScore: studyFortune.studyScore,
|
||||
overallStudyLuck: studyFortune.overallStudyLuck,
|
||||
focusIndex: studyFortune.focusIndex,
|
||||
memoryIndex: studyFortune.memoryIndex,
|
||||
studyAdvice: studyFortune.studyAdvice,
|
||||
examLuck: studyFortune.examLuck,
|
||||
bestStudyTime: studyFortune.bestStudyTime,
|
||||
suitableSubjects: studyFortune.suitableSubjects,
|
||||
studyEnvironmentAdvice: studyFortune.studyEnvironmentAdvice,
|
||||
socialScore: socialFortune.socialScore,
|
||||
overallSocialLuck: socialFortune.overallSocialLuck,
|
||||
popularityIndex: socialFortune.popularityIndex,
|
||||
communicationAbility: socialFortune.communicationAbility,
|
||||
socialAdvice: socialFortune.socialAdvice,
|
||||
nobleLuck: socialFortune.nobleLuck,
|
||||
bestSocialTime: socialFortune.bestSocialTime,
|
||||
suitableOccasion: socialFortune.suitableOccasion,
|
||||
interpersonalAdvice: socialFortune.interpersonalAdvice,
|
||||
travelScore: travelFortune.travelScore,
|
||||
overallTravelLuck: travelFortune.overallTravelLuck,
|
||||
safetyIndex: travelFortune.safetyIndex,
|
||||
smoothnessIndex: travelFortune.smoothnessIndex,
|
||||
travelAdvice: travelFortune.travelAdvice,
|
||||
bestTravelTime: travelFortune.bestTravelTime,
|
||||
suitableTransport: travelFortune.suitableTransport,
|
||||
precautions: travelFortune.precautions,
|
||||
nobleDirection: travelFortune.nobleDirection,
|
||||
}
|
||||
}
|
||||
|
||||
export function generateComprehensiveYearlyFortune(
|
||||
chart: ZiweiChart,
|
||||
year: number,
|
||||
): YearlyFortune {
|
||||
const baseFortune = generateYearlyFortune(chart, year)
|
||||
|
||||
const midYearDate = new Date(year, 6, 1)
|
||||
const loveFortune = analyzeLoveFortune(chart, midYearDate)
|
||||
const studyFortune = analyzeStudyFortune(chart, midYearDate)
|
||||
const socialFortune = analyzeSocialFortune(chart, midYearDate)
|
||||
const travelFortune = analyzeTravelFortune(chart, midYearDate)
|
||||
|
||||
return {
|
||||
...baseFortune,
|
||||
loveScore: loveFortune.loveScore,
|
||||
overallLoveLuck: loveFortune.overallLoveLuck,
|
||||
peachBlossomIndex: loveFortune.peachBlossomIndex,
|
||||
singleAdvice: loveFortune.singleAdvice,
|
||||
datingAdvice: loveFortune.datingAdvice,
|
||||
marriageAdvice: loveFortune.marriageAdvice,
|
||||
bestDatingTime: loveFortune.bestDatingTime,
|
||||
studyScore: studyFortune.studyScore,
|
||||
overallStudyLuck: studyFortune.overallStudyLuck,
|
||||
focusIndex: studyFortune.focusIndex,
|
||||
memoryIndex: studyFortune.memoryIndex,
|
||||
studyAdvice: studyFortune.studyAdvice,
|
||||
examLuck: studyFortune.examLuck,
|
||||
bestStudyTime: studyFortune.bestStudyTime,
|
||||
suitableSubjects: studyFortune.suitableSubjects,
|
||||
studyEnvironmentAdvice: studyFortune.studyEnvironmentAdvice,
|
||||
socialScore: socialFortune.socialScore,
|
||||
overallSocialLuck: socialFortune.overallSocialLuck,
|
||||
popularityIndex: socialFortune.popularityIndex,
|
||||
communicationAbility: socialFortune.communicationAbility,
|
||||
socialAdvice: socialFortune.socialAdvice,
|
||||
nobleLuck: socialFortune.nobleLuck,
|
||||
bestSocialTime: socialFortune.bestSocialTime,
|
||||
suitableOccasion: socialFortune.suitableOccasion,
|
||||
interpersonalAdvice: socialFortune.interpersonalAdvice,
|
||||
travelScore: travelFortune.travelScore,
|
||||
overallTravelLuck: travelFortune.overallTravelLuck,
|
||||
safetyIndex: travelFortune.safetyIndex,
|
||||
smoothnessIndex: travelFortune.smoothnessIndex,
|
||||
travelAdvice: travelFortune.travelAdvice,
|
||||
bestTravelTime: travelFortune.bestTravelTime,
|
||||
suitableTransport: travelFortune.suitableTransport,
|
||||
precautions: travelFortune.precautions,
|
||||
nobleDirection: travelFortune.nobleDirection,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
import { PalaceType, StarNature, TransformationType } from './enums'
|
||||
import { calculatePalaceScore, determineLuckLevel } from './ziweiAlgorithm'
|
||||
import type { ZiweiChart, Palace, PalaceFortune, DailyFortune, MonthlyFortune, YearlyFortune } from './types'
|
||||
|
||||
export function analyzePalaceFortune(palace: Palace, fortuneDate: Date): PalaceFortune {
|
||||
const baseScore = palace.score
|
||||
const adjustment = calculateDailyAdjustment(palace, fortuneDate)
|
||||
const finalScore = Math.max(0, Math.min(100, baseScore + adjustment))
|
||||
|
||||
return {
|
||||
palaceType: palace.palaceType,
|
||||
score: finalScore,
|
||||
luckLevel: determineLuckLevel(finalScore),
|
||||
analysis: generatePalaceAnalysis(palace, finalScore),
|
||||
advice: generatePalaceAdvice(palace.palaceType, finalScore),
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateDailyAdjustment(palace: Palace, fortuneDate: Date): number {
|
||||
let adjustment = 0
|
||||
const dayOfMonth = fortuneDate.getDate()
|
||||
const month = fortuneDate.getMonth() + 1
|
||||
|
||||
for (const starInfo of palace.majorStars) {
|
||||
if (starInfo.transformation != null) {
|
||||
adjustment += calculateTransformationAdjustment(starInfo.transformation, dayOfMonth)
|
||||
}
|
||||
|
||||
if (starInfo.nature === StarNature.JI) {
|
||||
adjustment += (month === 1 || month === 4 || month === 7 || month === 10) ? 3 : 1
|
||||
} else if (starInfo.nature === StarNature.XIONG) {
|
||||
adjustment -= (month === 2 || month === 5 || month === 8 || month === 11) ? 3 : 1
|
||||
}
|
||||
}
|
||||
|
||||
return adjustment
|
||||
}
|
||||
|
||||
export function calculateMonthlyAdjustment(palace: Palace, month: number): number {
|
||||
let adjustment = 0
|
||||
|
||||
for (const starInfo of palace.majorStars) {
|
||||
if (starInfo.transformation != null) {
|
||||
adjustment += calculateMonthlyTransformationAdjustment(starInfo.transformation, month)
|
||||
}
|
||||
|
||||
if (starInfo.nature === StarNature.JI) {
|
||||
adjustment += (month % 3 === 1) ? 4 : 2
|
||||
} else if (starInfo.nature === StarNature.XIONG) {
|
||||
adjustment -= (month % 3 === 2) ? 4 : 2
|
||||
}
|
||||
}
|
||||
|
||||
return adjustment
|
||||
}
|
||||
|
||||
export function calculateYearlyAdjustment(palace: Palace, year: number): number {
|
||||
let adjustment = 0
|
||||
|
||||
for (const starInfo of palace.majorStars) {
|
||||
if (starInfo.transformation != null) {
|
||||
adjustment += calculateYearlyTransformationAdjustment(starInfo.transformation, year)
|
||||
}
|
||||
|
||||
if (starInfo.nature === StarNature.JI) {
|
||||
adjustment += 3
|
||||
} else if (starInfo.nature === StarNature.XIONG) {
|
||||
adjustment -= 3
|
||||
}
|
||||
}
|
||||
|
||||
return adjustment
|
||||
}
|
||||
|
||||
export function calculateTransformationAdjustment(transformation: TransformationType, dayOfMonth: number): number {
|
||||
switch (transformation) {
|
||||
case TransformationType.LU:
|
||||
return (dayOfMonth % 3 === 0) ? 5 : 2
|
||||
case TransformationType.QUAN:
|
||||
return (dayOfMonth % 7 === 0) ? 5 : 2
|
||||
case TransformationType.KE:
|
||||
return (dayOfMonth % 5 === 0) ? 3 : 1
|
||||
case TransformationType.JI:
|
||||
return (dayOfMonth % 4 === 0) ? -8 : -3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateMonthlyTransformationAdjustment(transformation: TransformationType, month: number): number {
|
||||
switch (transformation) {
|
||||
case TransformationType.LU:
|
||||
return (month % 3 === 1) ? 6 : 3
|
||||
case TransformationType.QUAN:
|
||||
return (month % 3 === 2) ? 6 : 3
|
||||
case TransformationType.KE:
|
||||
return (month % 2 === 0) ? 4 : 2
|
||||
case TransformationType.JI:
|
||||
return (month % 4 === 0) ? -10 : -5
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateYearlyTransformationAdjustment(transformation: TransformationType, year: number): number {
|
||||
switch (transformation) {
|
||||
case TransformationType.LU:
|
||||
return (year % 3 === 0) ? 8 : 4
|
||||
case TransformationType.QUAN:
|
||||
return (year % 3 === 1) ? 8 : 4
|
||||
case TransformationType.KE:
|
||||
return (year % 2 === 0) ? 5 : 3
|
||||
case TransformationType.JI:
|
||||
return (year % 4 === 0) ? -12 : -6
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export function generatePalaceAnalysis(palace: Palace, score: number): string {
|
||||
const parts: string[] = []
|
||||
parts.push(`${PalaceType.name(palace.palaceType)}位于${palace.earthlyBranch}宫`)
|
||||
|
||||
if (palace.majorStars.length > 0) {
|
||||
parts.push(',主星配置:')
|
||||
for (const starInfo of palace.majorStars) {
|
||||
parts.push(starInfo.star)
|
||||
if (starInfo.transformation != null) {
|
||||
parts.push(`(${TransformationType.name(starInfo.transformation)})`)
|
||||
}
|
||||
parts.push(' ')
|
||||
}
|
||||
}
|
||||
|
||||
parts.push(`。运势评分:${score}分`)
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
export function generatePalaceAdvice(palaceType: PalaceType, score: number): string {
|
||||
if (score >= 70) {
|
||||
return getPositiveAdvice(palaceType)
|
||||
} else if (score >= 50) {
|
||||
return getNeutralAdvice(palaceType)
|
||||
} else {
|
||||
return getNegativeAdvice(palaceType)
|
||||
}
|
||||
}
|
||||
|
||||
function getPositiveAdvice(palaceType: PalaceType): string {
|
||||
const adviceMap: Record<string, string> = {
|
||||
[PalaceType.MING]: '今日状态极佳,适合开展新计划',
|
||||
[PalaceType.XIONG]: '与朋友相处融洽,可拓展人脉',
|
||||
[PalaceType.FUQI]: '感情运势良好,适合表达爱意',
|
||||
[PalaceType.ZINV]: '与子女关系和谐,可增进感情',
|
||||
[PalaceType.CAI]: '财运亨通,可进行投资理财',
|
||||
[PalaceType.JILU]: '身体健康,精力充沛',
|
||||
[PalaceType.QIAN]: '出行顺利,可安排外出计划',
|
||||
[PalaceType.GUANLU]: '工作顺利,可争取更多机会',
|
||||
[PalaceType.TUDI]: '事业运佳,适合拓展业务',
|
||||
[PalaceType.FUBEN]: '贵人运旺,可获得他人帮助',
|
||||
[PalaceType.SHEN]: '心情愉悦,适合社交活动',
|
||||
[PalaceType.FU]: '与父母关系和谐,可多沟通',
|
||||
}
|
||||
return adviceMap[palaceType] ?? '运势良好,保持积极心态'
|
||||
}
|
||||
|
||||
function getNeutralAdvice(palaceType: PalaceType): string {
|
||||
const adviceMap: Record<string, string> = {
|
||||
[PalaceType.MING]: '状态平稳,按计划行事即可',
|
||||
[PalaceType.XIONG]: '与朋友关系一般,保持正常交往',
|
||||
[PalaceType.FUQI]: '感情平稳,多关心对方',
|
||||
[PalaceType.ZINV]: '与子女关系正常,保持沟通',
|
||||
[PalaceType.CAI]: '财运平稳,谨慎理财',
|
||||
[PalaceType.JILU]: '身体状况一般,注意休息',
|
||||
[PalaceType.QIAN]: '出行运势一般,谨慎安排',
|
||||
[PalaceType.GUANLU]: '工作平稳,做好本职工作',
|
||||
[PalaceType.TUDI]: '事业运一般,稳扎稳打',
|
||||
[PalaceType.FUBEN]: '贵人运一般,依靠自身努力',
|
||||
[PalaceType.SHEN]: '心情平稳,保持平常心',
|
||||
[PalaceType.FU]: '与父母关系正常,保持联系',
|
||||
}
|
||||
return adviceMap[palaceType] ?? '运势平稳,保持现状'
|
||||
}
|
||||
|
||||
function getNegativeAdvice(palaceType: PalaceType): string {
|
||||
const adviceMap: Record<string, string> = {
|
||||
[PalaceType.MING]: '状态欠佳,避免重要决策',
|
||||
[PalaceType.XIONG]: '与朋友关系紧张,避免冲突',
|
||||
[PalaceType.FUQI]: '感情运势欠佳,多包容理解',
|
||||
[PalaceType.ZINV]: '与子女关系紧张,耐心沟通',
|
||||
[PalaceType.CAI]: '财运不佳,避免大额投资',
|
||||
[PalaceType.JILU]: '身体欠佳,注意保养',
|
||||
[PalaceType.QIAN]: '出行不利,减少外出',
|
||||
[PalaceType.GUANLU]: '工作压力大,调整心态',
|
||||
[PalaceType.TUDI]: '事业运不佳,谨慎行事',
|
||||
[PalaceType.FUBEN]: '贵人运弱,依靠自己',
|
||||
[PalaceType.SHEN]: '心情不佳,调节情绪',
|
||||
[PalaceType.FU]: '与父母关系紧张,多理解包容',
|
||||
}
|
||||
return adviceMap[palaceType] ?? '运势欠佳,谨慎行事'
|
||||
}
|
||||
|
||||
export function calculateOverallScore(palaceFortunes: Map<string, PalaceFortune> | Record<string, PalaceFortune>): number {
|
||||
const values = palaceFortunes instanceof Map
|
||||
? [...palaceFortunes.values()]
|
||||
: Object.values(palaceFortunes)
|
||||
|
||||
if (values.length === 0) return 60
|
||||
|
||||
const totalScore = values.reduce((sum, pf) => sum + pf.score, 0)
|
||||
return Math.floor(totalScore / values.length)
|
||||
}
|
||||
|
||||
export function generateDailyFortune(chart: ZiweiChart, date: Date): DailyFortune {
|
||||
const palaceFortunes: Record<string, PalaceFortune> = {}
|
||||
|
||||
for (const palace of chart.palaces) {
|
||||
palaceFortunes[palace.palaceType] = analyzePalaceFortune(palace, date)
|
||||
}
|
||||
|
||||
const overallScore = calculateOverallScore(palaceFortunes)
|
||||
|
||||
return {
|
||||
fortuneDate: date.toISOString().slice(0, 10),
|
||||
palaceFortunes,
|
||||
overallScore,
|
||||
overallLuck: determineLuckLevel(overallScore),
|
||||
careerAdvice: generateDailyCareerAdvice(chart, date),
|
||||
wealthAdvice: generateDailyWealthAdvice(chart, date),
|
||||
relationshipAdvice: generateDailyRelationshipAdvice(chart, date),
|
||||
healthAdvice: generateDailyHealthAdvice(chart, date),
|
||||
luckyColor: '',
|
||||
luckyNumber: '',
|
||||
luckyDirection: '',
|
||||
}
|
||||
}
|
||||
|
||||
function generateDailyCareerAdvice(chart: ZiweiChart, date: Date): string {
|
||||
const careerPalace = chart.palaces.find(p => p.palaceType === PalaceType.GUANLU)
|
||||
if (careerPalace == null) return '事业运势不明,谨慎行事'
|
||||
if (careerPalace.score >= 70) {
|
||||
return '今日事业运势吉显,适合处理重要工作事务,可主动争取机会,宜把握良机,必有收获'
|
||||
} else if (careerPalace.score >= 60) {
|
||||
return '今日事业运势平吉,按计划完成工作即可,避免冒险决策,宜稳中求进,循序渐进'
|
||||
} else {
|
||||
return '今日事业运势欠佳,建议处理常规工作,避免重要决策和冲突,宜趋吉避凶,待时而动'
|
||||
}
|
||||
}
|
||||
|
||||
function generateDailyWealthAdvice(chart: ZiweiChart, date: Date): string {
|
||||
const wealthPalace = chart.palaces.find(p => p.palaceType === PalaceType.CAI)
|
||||
if (wealthPalace == null) return '财运不明,谨慎理财'
|
||||
if (wealthPalace.score >= 70) {
|
||||
return '今日财运吉显,适合进行理财规划,可考虑小额投资,宜把握良机,必有收获'
|
||||
} else if (wealthPalace.score >= 60) {
|
||||
return '今日财运平吉,建议保持理性消费,谨慎理财,宜稳中求进,循序渐进'
|
||||
} else {
|
||||
return '今日财运欠佳,建议避免大额支出和投资,保守理财,宜趋吉避凶,待时而动'
|
||||
}
|
||||
}
|
||||
|
||||
function generateDailyRelationshipAdvice(chart: ZiweiChart, date: Date): string {
|
||||
const relationshipPalace = chart.palaces.find(p => p.palaceType === PalaceType.FUQI)
|
||||
if (relationshipPalace == null) return '感情运势不明,保持平常心'
|
||||
if (relationshipPalace.score >= 70) {
|
||||
return '今日感情运势吉显,适合表达爱意,增进感情交流,宜把握良机,必有收获'
|
||||
} else if (relationshipPalace.score >= 60) {
|
||||
return '今日感情运势平吉,保持正常沟通,多关心对方,宜稳中求进,循序渐进'
|
||||
} else {
|
||||
return '今日感情运势欠佳,避免争吵,多包容理解对方,宜趋吉避凶,待时而动'
|
||||
}
|
||||
}
|
||||
|
||||
function generateDailyHealthAdvice(chart: ZiweiChart, date: Date): string {
|
||||
const healthPalace = chart.palaces.find(p => p.palaceType === PalaceType.JILU)
|
||||
if (healthPalace == null) return '健康运势不明,注意休息'
|
||||
if (healthPalace.score >= 70) {
|
||||
return '今日身体状况吉显,精力充沛,可进行适度运动,宜保持良好习惯,适度锻炼'
|
||||
} else if (healthPalace.score >= 60) {
|
||||
return '今日身体状况平吉,注意劳逸结合,保持规律作息,宜稳中求进,循序渐进'
|
||||
} else {
|
||||
return '今日身体状况欠佳,注意休息,避免剧烈运动和熬夜,宜修身养性,待时而动'
|
||||
}
|
||||
}
|
||||
|
||||
export function generateMonthlyFortune(chart: ZiweiChart, year: number, month: number): MonthlyFortune {
|
||||
const palaceFortunes: Record<string, PalaceFortune> = {}
|
||||
|
||||
for (const palace of chart.palaces) {
|
||||
const baseScore = palace.score
|
||||
const adjustment = calculateMonthlyAdjustment(palace, month)
|
||||
const finalScore = Math.max(0, Math.min(100, baseScore + adjustment))
|
||||
|
||||
palaceFortunes[palace.palaceType] = {
|
||||
palaceType: palace.palaceType,
|
||||
score: finalScore,
|
||||
luckLevel: determineLuckLevel(finalScore),
|
||||
analysis: generatePalaceAnalysis(palace, finalScore),
|
||||
advice: generatePalaceAdvice(palace.palaceType, finalScore),
|
||||
}
|
||||
}
|
||||
|
||||
const overallScore = calculateOverallScore(palaceFortunes)
|
||||
|
||||
return {
|
||||
year,
|
||||
month,
|
||||
palaceFortunes,
|
||||
overallScore,
|
||||
overallLuck: determineLuckLevel(overallScore),
|
||||
careerAdvice: '',
|
||||
wealthAdvice: '',
|
||||
relationshipAdvice: '',
|
||||
healthAdvice: '',
|
||||
}
|
||||
}
|
||||
|
||||
export function generateYearlyFortune(chart: ZiweiChart, year: number): YearlyFortune {
|
||||
const palaceFortunes: Record<string, PalaceFortune> = {}
|
||||
|
||||
for (const palace of chart.palaces) {
|
||||
const baseScore = palace.score
|
||||
const adjustment = calculateYearlyAdjustment(palace, year)
|
||||
const finalScore = Math.max(0, Math.min(100, baseScore + adjustment))
|
||||
|
||||
palaceFortunes[palace.palaceType] = {
|
||||
palaceType: palace.palaceType,
|
||||
score: finalScore,
|
||||
luckLevel: determineLuckLevel(finalScore),
|
||||
analysis: generatePalaceAnalysis(palace, finalScore),
|
||||
advice: generatePalaceAdvice(palace.palaceType, finalScore),
|
||||
}
|
||||
}
|
||||
|
||||
const overallScore = calculateOverallScore(palaceFortunes)
|
||||
|
||||
return {
|
||||
year,
|
||||
palaceFortunes,
|
||||
overallScore,
|
||||
overallLuck: determineLuckLevel(overallScore),
|
||||
careerAdvice: '',
|
||||
wealthAdvice: '',
|
||||
relationshipAdvice: '',
|
||||
healthAdvice: '',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { EarthlyBranch, PalaceType } from './enums'
|
||||
import type { ZiweiChart, Palace, SanFangSiZheng } from './types'
|
||||
|
||||
function calculateCaiBoGong(mingGongBranch: EarthlyBranch): EarthlyBranch {
|
||||
const mingGongIndex = EarthlyBranch.index(mingGongBranch)
|
||||
let caiBoGongIndex = mingGongIndex + 5
|
||||
if (caiBoGongIndex > 12) {
|
||||
caiBoGongIndex -= 12
|
||||
}
|
||||
return EarthlyBranch.fromIndex(caiBoGongIndex)
|
||||
}
|
||||
|
||||
function calculateGuanLuGong(mingGongBranch: EarthlyBranch): EarthlyBranch {
|
||||
const mingGongIndex = EarthlyBranch.index(mingGongBranch)
|
||||
let guanLuGongIndex = mingGongIndex + 10
|
||||
if (guanLuGongIndex > 12) {
|
||||
guanLuGongIndex -= 12
|
||||
}
|
||||
return EarthlyBranch.fromIndex(guanLuGongIndex)
|
||||
}
|
||||
|
||||
function calculateQianYiGong(mingGongBranch: EarthlyBranch): EarthlyBranch {
|
||||
const mingGongIndex = EarthlyBranch.index(mingGongBranch)
|
||||
let qianYiGongIndex = mingGongIndex + 6
|
||||
if (qianYiGongIndex > 12) {
|
||||
qianYiGongIndex -= 12
|
||||
}
|
||||
return EarthlyBranch.fromIndex(qianYiGongIndex)
|
||||
}
|
||||
|
||||
export function calculateSanFangSiZheng(chart: ZiweiChart): SanFangSiZheng | null {
|
||||
const mingGong = chart.palaces.find(p => p.palaceType === PalaceType.MING)
|
||||
if (mingGong == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const mingGongBranch = mingGong.earthlyBranch
|
||||
const caiBoGongBranch = calculateCaiBoGong(mingGongBranch)
|
||||
const guanLuGongBranch = calculateGuanLuGong(mingGongBranch)
|
||||
const qianYiGongBranch = calculateQianYiGong(mingGongBranch)
|
||||
|
||||
const caiBoGong = chart.palaces.find(p => p.earthlyBranch === caiBoGongBranch) ?? null
|
||||
const guanLuGong = chart.palaces.find(p => p.earthlyBranch === guanLuGongBranch) ?? null
|
||||
const qianYiGong = chart.palaces.find(p => p.earthlyBranch === qianYiGongBranch) ?? null
|
||||
|
||||
if (caiBoGong == null || guanLuGong == null || qianYiGong == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const sanFang = [mingGong, caiBoGong, guanLuGong]
|
||||
const siZheng = [mingGong, caiBoGong, guanLuGong, qianYiGong]
|
||||
|
||||
const sanFangScore = Math.floor(sanFang.reduce((sum, p) => sum + p.score, 0) / 3)
|
||||
const siZhengScore = Math.floor(siZheng.reduce((sum, p) => sum + p.score, 0) / 4)
|
||||
|
||||
return {
|
||||
mingGong,
|
||||
caiBoGong,
|
||||
guanLuGong,
|
||||
qianYiGong,
|
||||
sanFangScore,
|
||||
siZhengScore,
|
||||
}
|
||||
}
|
||||
|
||||
export function analyzeSanFangSiZheng(sfz: SanFangSiZheng): string {
|
||||
const lines: string[] = []
|
||||
lines.push('【三方四正分析】')
|
||||
lines.push('')
|
||||
lines.push('【三方分析】')
|
||||
lines.push(`命宫:${PalaceType.name(sfz.mingGong.palaceType)}(${EarthlyBranch.name(sfz.mingGong.earthlyBranch)}),得分:${sfz.mingGong.score}`)
|
||||
lines.push(`财帛宫:${PalaceType.name(sfz.caiBoGong.palaceType)}(${EarthlyBranch.name(sfz.caiBoGong.earthlyBranch)}),得分:${sfz.caiBoGong.score}`)
|
||||
lines.push(`官禄宫:${PalaceType.name(sfz.guanLuGong.palaceType)}(${EarthlyBranch.name(sfz.guanLuGong.earthlyBranch)}),得分:${sfz.guanLuGong.score}`)
|
||||
lines.push(`三方平均分:${sfz.sanFangScore}`)
|
||||
lines.push('')
|
||||
lines.push('【四正分析】')
|
||||
lines.push(`迁移宫:${PalaceType.name(sfz.qianYiGong.palaceType)}(${EarthlyBranch.name(sfz.qianYiGong.earthlyBranch)}),得分:${sfz.qianYiGong.score}`)
|
||||
lines.push(`四正平均分:${sfz.siZhengScore}`)
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
import type {
|
||||
HeavenlyStem,
|
||||
EarthlyBranch,
|
||||
MajorStar,
|
||||
StarNature,
|
||||
TransformationType,
|
||||
PalaceType,
|
||||
} from './enums'
|
||||
|
||||
export interface LunarDate {
|
||||
lunarYear: string
|
||||
lunarMonth: string
|
||||
lunarDay: string
|
||||
isLeapMonth: boolean
|
||||
zodiac: string
|
||||
solarTerm: string
|
||||
weekday: string
|
||||
}
|
||||
|
||||
export interface BirthInfo {
|
||||
birthTime: string
|
||||
yearStem: HeavenlyStem
|
||||
monthStem: HeavenlyStem
|
||||
dayStem: HeavenlyStem
|
||||
hourStem: HeavenlyStem
|
||||
yearBranch: EarthlyBranch
|
||||
monthBranch: EarthlyBranch
|
||||
dayBranch: EarthlyBranch
|
||||
hourBranch: EarthlyBranch
|
||||
gender: 'MALE' | 'FEMALE'
|
||||
timezone: string
|
||||
longitude?: number
|
||||
latitude?: number
|
||||
trueSolarTime?: string
|
||||
lunarDate?: LunarDate
|
||||
}
|
||||
|
||||
export interface StarInfo {
|
||||
star: MajorStar
|
||||
nature: StarNature
|
||||
transformation: TransformationType | null
|
||||
brightness: number
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface Palace {
|
||||
palaceType: PalaceType
|
||||
earthlyBranch: EarthlyBranch
|
||||
majorStars: StarInfo[]
|
||||
minorStars: string[]
|
||||
analysis: string
|
||||
score: number
|
||||
}
|
||||
|
||||
export interface SanFangSiZheng {
|
||||
mingGong: Palace
|
||||
caiBoGong: Palace
|
||||
guanLuGong: Palace
|
||||
qianYiGong: Palace
|
||||
sanFangScore: number
|
||||
siZhengScore: number
|
||||
}
|
||||
|
||||
export interface Pattern {
|
||||
name: string
|
||||
description: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface ZiweiChart {
|
||||
birthInfo: BirthInfo
|
||||
palaces: Palace[]
|
||||
yearStem: HeavenlyStem
|
||||
mingGongBranch: EarthlyBranch
|
||||
shenGongBranch: EarthlyBranch
|
||||
summary: string
|
||||
overallLuck: string
|
||||
sanFangSiZheng?: SanFangSiZheng
|
||||
patterns?: Pattern[]
|
||||
}
|
||||
|
||||
export interface Almanac {
|
||||
solarDate: string
|
||||
lunarDate: LunarDate
|
||||
suitable: string[]
|
||||
unsuitable: string[]
|
||||
godDirection: string
|
||||
joyDirection: string
|
||||
fortuneDirection: string
|
||||
nobleDirection: string
|
||||
clash: string
|
||||
evil: string
|
||||
jianChu: string
|
||||
starGod: string
|
||||
naYin: string
|
||||
fetusGod: string
|
||||
pengzuTaboo: string
|
||||
}
|
||||
|
||||
export interface PalaceFortune {
|
||||
palaceType: PalaceType
|
||||
score: number
|
||||
luckLevel: string
|
||||
analysis: string
|
||||
advice: string
|
||||
}
|
||||
|
||||
export interface LoveFortuneDetail {
|
||||
loveScore: number
|
||||
overallLoveLuck: string
|
||||
peachBlossomIndex: string
|
||||
singleAdvice: string
|
||||
datingAdvice: string
|
||||
marriageAdvice: string
|
||||
bestDatingTime: string
|
||||
}
|
||||
|
||||
export interface StudyFortuneDetail {
|
||||
studyScore: number
|
||||
overallStudyLuck: string
|
||||
focusIndex: string
|
||||
memoryIndex: string
|
||||
studyAdvice: string
|
||||
examLuck: string
|
||||
bestStudyTime: string
|
||||
suitableSubjects: string
|
||||
studyEnvironmentAdvice: string
|
||||
}
|
||||
|
||||
export interface SocialFortuneDetail {
|
||||
socialScore: number
|
||||
overallSocialLuck: string
|
||||
popularityIndex: string
|
||||
communicationAbility: string
|
||||
socialAdvice: string
|
||||
nobleLuck: string
|
||||
bestSocialTime: string
|
||||
suitableOccasion: string
|
||||
interpersonalAdvice: string
|
||||
}
|
||||
|
||||
export interface TravelFortuneDetail {
|
||||
travelScore: number
|
||||
overallTravelLuck: string
|
||||
safetyIndex: string
|
||||
smoothnessIndex: string
|
||||
travelAdvice: string
|
||||
bestTravelTime: string
|
||||
suitableTransport: string
|
||||
precautions: string
|
||||
nobleDirection: string
|
||||
}
|
||||
|
||||
export interface DailyFortune {
|
||||
fortuneDate: string
|
||||
overallLuck: string
|
||||
overallScore: number
|
||||
palaceFortunes: Record<string, PalaceFortune>
|
||||
careerAdvice: string
|
||||
wealthAdvice: string
|
||||
relationshipAdvice: string
|
||||
healthAdvice: string
|
||||
luckyColor: string
|
||||
luckyNumber: string
|
||||
luckyDirection: string
|
||||
loveScore?: number
|
||||
overallLoveLuck?: string
|
||||
peachBlossomIndex?: string
|
||||
singleAdvice?: string
|
||||
datingAdvice?: string
|
||||
marriageAdvice?: string
|
||||
bestDatingTime?: string
|
||||
studyScore?: number
|
||||
overallStudyLuck?: string
|
||||
focusIndex?: string
|
||||
memoryIndex?: string
|
||||
studyAdvice?: string
|
||||
examLuck?: string
|
||||
bestStudyTime?: string
|
||||
suitableSubjects?: string
|
||||
studyEnvironmentAdvice?: string
|
||||
socialScore?: number
|
||||
overallSocialLuck?: string
|
||||
popularityIndex?: string
|
||||
communicationAbility?: string
|
||||
socialAdvice?: string
|
||||
nobleLuck?: string
|
||||
bestSocialTime?: string
|
||||
suitableOccasion?: string
|
||||
interpersonalAdvice?: string
|
||||
travelScore?: number
|
||||
overallTravelLuck?: string
|
||||
safetyIndex?: string
|
||||
smoothnessIndex?: string
|
||||
travelAdvice?: string
|
||||
bestTravelTime?: string
|
||||
suitableTransport?: string
|
||||
precautions?: string
|
||||
nobleDirection?: string
|
||||
}
|
||||
|
||||
export interface MonthlyFortune {
|
||||
fortuneMonth: string
|
||||
overallLuck: string
|
||||
overallScore: number
|
||||
palaceFortunes: Record<string, PalaceFortune>
|
||||
dailyFortunes: DailyFortune[]
|
||||
careerAdvice: string
|
||||
wealthAdvice: string
|
||||
relationshipAdvice: string
|
||||
healthAdvice: string
|
||||
luckyColor: string
|
||||
luckyNumber: string
|
||||
luckyDirection: string
|
||||
keyFocus: string
|
||||
cautionAdvice: string
|
||||
loveScore?: number
|
||||
overallLoveLuck?: string
|
||||
peachBlossomIndex?: string
|
||||
singleAdvice?: string
|
||||
datingAdvice?: string
|
||||
marriageAdvice?: string
|
||||
bestDatingTime?: string
|
||||
studyScore?: number
|
||||
overallStudyLuck?: string
|
||||
focusIndex?: string
|
||||
memoryIndex?: string
|
||||
studyAdvice?: string
|
||||
examLuck?: string
|
||||
bestStudyTime?: string
|
||||
suitableSubjects?: string
|
||||
studyEnvironmentAdvice?: string
|
||||
socialScore?: number
|
||||
overallSocialLuck?: string
|
||||
popularityIndex?: string
|
||||
communicationAbility?: string
|
||||
socialAdvice?: string
|
||||
nobleLuck?: string
|
||||
bestSocialTime?: string
|
||||
suitableOccasion?: string
|
||||
interpersonalAdvice?: string
|
||||
travelScore?: number
|
||||
overallTravelLuck?: string
|
||||
safetyIndex?: string
|
||||
smoothnessIndex?: string
|
||||
travelAdvice?: string
|
||||
bestTravelTime?: string
|
||||
suitableTransport?: string
|
||||
precautions?: string
|
||||
nobleDirection?: string
|
||||
}
|
||||
|
||||
export interface YearlyFortune {
|
||||
fortuneYear: number
|
||||
overallLuck: string
|
||||
overallScore: number
|
||||
palaceFortunes: Record<string, PalaceFortune>
|
||||
monthlyFortunes: MonthlyFortune[]
|
||||
careerAdvice: string
|
||||
wealthAdvice: string
|
||||
relationshipAdvice: string
|
||||
healthAdvice: string
|
||||
luckyColor: string
|
||||
luckyNumber: string
|
||||
luckyDirection: string
|
||||
keyFocus: string
|
||||
cautionAdvice: string
|
||||
yearlyTheme: string
|
||||
majorOpportunity: string
|
||||
majorChallenge: string
|
||||
loveScore?: number
|
||||
overallLoveLuck?: string
|
||||
peachBlossomIndex?: string
|
||||
singleAdvice?: string
|
||||
datingAdvice?: string
|
||||
marriageAdvice?: string
|
||||
bestDatingTime?: string
|
||||
studyScore?: number
|
||||
overallStudyLuck?: string
|
||||
focusIndex?: string
|
||||
memoryIndex?: string
|
||||
studyAdvice?: string
|
||||
examLuck?: string
|
||||
bestStudyTime?: string
|
||||
suitableSubjects?: string
|
||||
studyEnvironmentAdvice?: string
|
||||
socialScore?: number
|
||||
overallSocialLuck?: string
|
||||
popularityIndex?: string
|
||||
communicationAbility?: string
|
||||
socialAdvice?: string
|
||||
nobleLuck?: string
|
||||
bestSocialTime?: string
|
||||
suitableOccasion?: string
|
||||
interpersonalAdvice?: string
|
||||
travelScore?: number
|
||||
overallTravelLuck?: string
|
||||
safetyIndex?: string
|
||||
smoothnessIndex?: string
|
||||
travelAdvice?: string
|
||||
bestTravelTime?: string
|
||||
suitableTransport?: string
|
||||
precautions?: string
|
||||
nobleDirection?: string
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import { MajorStar, StarNature, TransformationType, EarthlyBranch, PalaceType } from './enums'
|
||||
import type { StarInfo, Palace, ZiweiChart } from './types'
|
||||
|
||||
const STAR_NATURE_MAP = new Map<MajorStar, StarNature>([
|
||||
[MajorStar.ZIWEI, StarNature.JI],
|
||||
[MajorStar.TIANJI, StarNature.JI],
|
||||
[MajorStar.TAIYANG, StarNature.JI],
|
||||
[MajorStar.WUQU, StarNature.XIONG],
|
||||
[MajorStar.TIANTONG, StarNature.JI],
|
||||
[MajorStar.LIANCHEN, StarNature.XIONG],
|
||||
[MajorStar.TIANFU, StarNature.JI],
|
||||
[MajorStar.TAIYIN, StarNature.ZHONGHE],
|
||||
[MajorStar.TANLANG, StarNature.XIONG],
|
||||
[MajorStar.JUMEN, StarNature.XIONG],
|
||||
[MajorStar.TIANXIANG, StarNature.JI],
|
||||
[MajorStar.TIANLIANG, StarNature.ZHONGHE],
|
||||
[MajorStar.QISHA, StarNature.XIONG],
|
||||
[MajorStar.POJUN, StarNature.XIONG],
|
||||
])
|
||||
|
||||
const STAR_BASE_SCORE_MAP = new Map<MajorStar, number>([
|
||||
[MajorStar.ZIWEI, 85],
|
||||
[MajorStar.TIANJI, 75],
|
||||
[MajorStar.TAIYANG, 80],
|
||||
[MajorStar.WUQU, 75],
|
||||
[MajorStar.TIANTONG, 70],
|
||||
[MajorStar.LIANCHEN, 55],
|
||||
[MajorStar.TIANFU, 85],
|
||||
[MajorStar.TAIYIN, 75],
|
||||
[MajorStar.TANLANG, 60],
|
||||
[MajorStar.JUMEN, 55],
|
||||
[MajorStar.TIANXIANG, 75],
|
||||
[MajorStar.TIANLIANG, 75],
|
||||
[MajorStar.QISHA, 60],
|
||||
[MajorStar.POJUN, 55],
|
||||
])
|
||||
|
||||
const TRANSFORMATION_SCORE_MAP = new Map<TransformationType, number>([
|
||||
[TransformationType.LU, 15],
|
||||
[TransformationType.QUAN, 10],
|
||||
[TransformationType.KE, 8],
|
||||
[TransformationType.JI, -15],
|
||||
])
|
||||
|
||||
const BRANCH_COLOR_MAP = new Map<EarthlyBranch, string>([
|
||||
[EarthlyBranch.ZI, '黑色'],
|
||||
[EarthlyBranch.CHOU, '黄色'],
|
||||
[EarthlyBranch.YIN, '绿色'],
|
||||
[EarthlyBranch.MAO, '绿色'],
|
||||
[EarthlyBranch.CHEN, '黄色'],
|
||||
[EarthlyBranch.SI, '红色'],
|
||||
[EarthlyBranch.WU, '红色'],
|
||||
[EarthlyBranch.WEI, '黄色'],
|
||||
[EarthlyBranch.SHEN, '白色'],
|
||||
[EarthlyBranch.YOU, '白色'],
|
||||
[EarthlyBranch.XU, '黄色'],
|
||||
[EarthlyBranch.HAI, '黑色'],
|
||||
])
|
||||
|
||||
const BRANCH_DIRECTION_MAP = new Map<EarthlyBranch, string>([
|
||||
[EarthlyBranch.ZI, '正北'],
|
||||
[EarthlyBranch.CHOU, '东北偏北'],
|
||||
[EarthlyBranch.YIN, '东北偏东'],
|
||||
[EarthlyBranch.MAO, '正东'],
|
||||
[EarthlyBranch.CHEN, '东南偏东'],
|
||||
[EarthlyBranch.SI, '东南偏南'],
|
||||
[EarthlyBranch.WU, '正南'],
|
||||
[EarthlyBranch.WEI, '西南偏南'],
|
||||
[EarthlyBranch.SHEN, '西南偏西'],
|
||||
[EarthlyBranch.YOU, '正西'],
|
||||
[EarthlyBranch.XU, '西北偏西'],
|
||||
[EarthlyBranch.HAI, '西北偏北'],
|
||||
])
|
||||
|
||||
const LUCKY_COLORS = ['红色', '黄色', '蓝色', '绿色', '紫色', '金色', '白色', '黑色']
|
||||
|
||||
const LUCKY_DIRECTIONS = ['东方', '南方', '西方', '北方', '东南', '西南', '东北', '西北']
|
||||
|
||||
export function getStarNature(star: MajorStar): StarNature {
|
||||
return STAR_NATURE_MAP.get(star) ?? StarNature.ZHONGHE
|
||||
}
|
||||
|
||||
export function calculateStarScore(starInfo: StarInfo | null): number {
|
||||
if (starInfo == null || starInfo.star == null) {
|
||||
return 50
|
||||
}
|
||||
|
||||
const star = starInfo.star
|
||||
let baseScore = STAR_BASE_SCORE_MAP.get(star) ?? 50
|
||||
|
||||
const transformation = starInfo.transformation
|
||||
if (transformation != null) {
|
||||
const transScore = TRANSFORMATION_SCORE_MAP.get(transformation) ?? 0
|
||||
baseScore += transScore
|
||||
}
|
||||
|
||||
const brightness = starInfo.brightness
|
||||
if (brightness > 0 && brightness <= 100) {
|
||||
const brightnessFactor = brightness / 100.0
|
||||
baseScore = Math.floor(baseScore * brightnessFactor)
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(100, baseScore))
|
||||
}
|
||||
|
||||
export function calculatePalaceScore(palace: Palace | null): number {
|
||||
if (palace == null) {
|
||||
return 50
|
||||
}
|
||||
|
||||
let baseScore = 50
|
||||
const majorStars = palace.majorStars
|
||||
|
||||
if (majorStars == null || majorStars.length === 0) {
|
||||
return baseScore
|
||||
}
|
||||
|
||||
let starScoreSum = 0
|
||||
let starCount = 0
|
||||
|
||||
for (const starInfo of majorStars) {
|
||||
if (starInfo == null || starInfo.star == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
const starScore = calculateStarScore(starInfo)
|
||||
starScoreSum += starScore
|
||||
starCount++
|
||||
}
|
||||
|
||||
if (starCount > 0) {
|
||||
baseScore = Math.floor(starScoreSum / starCount)
|
||||
}
|
||||
|
||||
const finalScore = Math.max(0, Math.min(100, baseScore))
|
||||
palace.score = finalScore
|
||||
|
||||
return finalScore
|
||||
}
|
||||
|
||||
export function calculateLuckyColor(chart: ZiweiChart, fortuneDate: Date): string {
|
||||
if (chart == null || fortuneDate == null) {
|
||||
return '红色'
|
||||
}
|
||||
|
||||
const mingGongBranch = chart.mingGongBranch
|
||||
if (mingGongBranch == null) {
|
||||
return '红色'
|
||||
}
|
||||
|
||||
const baseColor = BRANCH_COLOR_MAP.get(mingGongBranch) ?? '红色'
|
||||
|
||||
const mingGong = chart.palaces.find(p => p.palaceType === PalaceType.MING)
|
||||
if (mingGong == null || mingGong.majorStars.length === 0) {
|
||||
return baseColor
|
||||
}
|
||||
|
||||
const mainStar = mingGong.majorStars[0]
|
||||
const starNature = MajorStar.nature(mainStar.star)
|
||||
|
||||
let baseIndex = 0
|
||||
for (let i = 0; i < LUCKY_COLORS.length; i++) {
|
||||
if (LUCKY_COLORS[i] === baseColor) {
|
||||
baseIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let natureOffset = 0
|
||||
if (starNature === StarNature.JI) {
|
||||
natureOffset = 1
|
||||
} else if (starNature === StarNature.XIONG) {
|
||||
natureOffset = -1
|
||||
}
|
||||
|
||||
const dayOfMonth = fortuneDate.getDate()
|
||||
const dateOffset = dayOfMonth % 3
|
||||
|
||||
const colorIndex = ((baseIndex + natureOffset + dateOffset) % LUCKY_COLORS.length + LUCKY_COLORS.length) % LUCKY_COLORS.length
|
||||
|
||||
return LUCKY_COLORS[colorIndex]
|
||||
}
|
||||
|
||||
export function calculateLuckyNumber(chart: ZiweiChart, fortuneDate: Date): string {
|
||||
if (chart == null || fortuneDate == null) {
|
||||
return '8'
|
||||
}
|
||||
|
||||
const dayOfMonth = fortuneDate.getDate()
|
||||
const monthValue = fortuneDate.getMonth() + 1
|
||||
|
||||
let baseNumber = (dayOfMonth + monthValue) % 9
|
||||
if (baseNumber === 0) {
|
||||
baseNumber = 9
|
||||
}
|
||||
|
||||
const mingGongBranch = chart.mingGongBranch
|
||||
if (mingGongBranch == null) {
|
||||
return String(baseNumber)
|
||||
}
|
||||
|
||||
const branchIndex = EarthlyBranch.index(mingGongBranch)
|
||||
|
||||
const mingGong = chart.palaces.find(p => p.palaceType === PalaceType.MING)
|
||||
let starFactor = 0
|
||||
if (mingGong != null && mingGong.majorStars.length > 0) {
|
||||
const mainStar = mingGong.majorStars[0]
|
||||
const starNature = MajorStar.nature(mainStar.star)
|
||||
if (starNature === StarNature.JI) {
|
||||
starFactor = 1
|
||||
} else if (starNature === StarNature.XIONG) {
|
||||
starFactor = -1
|
||||
}
|
||||
}
|
||||
|
||||
let luckyNumber = (baseNumber + branchIndex + starFactor) % 9
|
||||
if (luckyNumber === 0) {
|
||||
luckyNumber = 9
|
||||
}
|
||||
|
||||
return String(luckyNumber)
|
||||
}
|
||||
|
||||
export function calculateLuckyDirection(chart: ZiweiChart, fortuneDate: Date): string {
|
||||
if (chart == null || fortuneDate == null) {
|
||||
return '东方'
|
||||
}
|
||||
|
||||
const mingGongBranch = chart.mingGongBranch
|
||||
if (mingGongBranch == null) {
|
||||
return '东方'
|
||||
}
|
||||
|
||||
const baseDirection = BRANCH_DIRECTION_MAP.get(mingGongBranch) ?? '东方'
|
||||
|
||||
const mingGong = chart.palaces.find(p => p.palaceType === PalaceType.MING)
|
||||
if (mingGong == null || mingGong.majorStars.length === 0) {
|
||||
return baseDirection
|
||||
}
|
||||
|
||||
const mainStar = mingGong.majorStars[0]
|
||||
const starNature = MajorStar.nature(mainStar.star)
|
||||
|
||||
let baseIndex = 0
|
||||
for (let i = 0; i < LUCKY_DIRECTIONS.length; i++) {
|
||||
if (LUCKY_DIRECTIONS[i] === baseDirection) {
|
||||
baseIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let natureOffset = 0
|
||||
if (starNature === StarNature.JI) {
|
||||
natureOffset = 1
|
||||
} else if (starNature === StarNature.XIONG) {
|
||||
natureOffset = -1
|
||||
}
|
||||
|
||||
const dayOfMonth = fortuneDate.getDate()
|
||||
const dateOffset = dayOfMonth % 2
|
||||
|
||||
const directionIndex = ((baseIndex + natureOffset + dateOffset) % LUCKY_DIRECTIONS.length + LUCKY_DIRECTIONS.length) % LUCKY_DIRECTIONS.length
|
||||
|
||||
return LUCKY_DIRECTIONS[directionIndex]
|
||||
}
|
||||
|
||||
export function determineLuckLevel(score: number): string {
|
||||
if (score >= 90) return '大吉'
|
||||
if (score >= 80) return '吉'
|
||||
if (score >= 70) return '中吉'
|
||||
if (score >= 60) return '平'
|
||||
if (score >= 50) return '中平'
|
||||
if (score >= 40) return '小凶'
|
||||
return '凶'
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "万事宜",
|
||||
"appid": "__UNI__WANYISHI",
|
||||
"description": "中国传统文化研究工具 - 紫微斗数/黄历/运势分析",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"distribute": {
|
||||
"android": {
|
||||
"targetSdkVersion": 33,
|
||||
"minSdkVersion": 21,
|
||||
"abiFilters": ["armeabi-v7a", "arm64-v8a"],
|
||||
"permissions": {
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>": false
|
||||
}
|
||||
},
|
||||
"ios": {
|
||||
"privacyDescription": {
|
||||
"NSPrivacyTracking": false,
|
||||
"NSPrivacyCollectedDataTypes": []
|
||||
}
|
||||
},
|
||||
"splashscreen": {
|
||||
"autoclose": true,
|
||||
"waiting": true
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"prompt": "template",
|
||||
"template": {
|
||||
"title": "隐私政策",
|
||||
"message": "本应用所有数据存储在本地设备,不收集任何用户信息。应用无需网络连接即可正常使用。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"h5": {
|
||||
"title": "万事宜",
|
||||
"router": {
|
||||
"mode": "hash"
|
||||
}
|
||||
},
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/almanac-search/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "黄历搜索"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/ziwei/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "紫微斗数"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/fortune/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "运势分析"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "万事宜",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f4f4f5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { getAlmanacByDate, getAlmanacsByRange } from '../algorithms/almanac'
|
||||
import type { Almanac } from '../algorithms/types'
|
||||
import { LRUCache } from '../utils/lruCache'
|
||||
|
||||
export interface AlmanacSearchParams {
|
||||
keyword?: string
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
suitableFilter?: string[]
|
||||
unsuitableFilter?: string[]
|
||||
}
|
||||
|
||||
export class AlmanacService {
|
||||
private cache = new LRUCache<Almanac>({ maxSize: 365 })
|
||||
|
||||
private calculateAlmanac(dateStr: string): Almanac | null {
|
||||
return getAlmanacByDate(dateStr)
|
||||
}
|
||||
|
||||
async getAlmanacByDate(dateStr: string): Promise<Almanac | null> {
|
||||
const cached = this.cache.get(dateStr)
|
||||
if (cached) return cached
|
||||
|
||||
const result = this.calculateAlmanac(dateStr)
|
||||
if (result) this.cache.set(dateStr, result)
|
||||
return result
|
||||
}
|
||||
|
||||
async getAlmanacsByRange(startDateStr: string, endDateStr: string): Promise<Almanac[]> {
|
||||
return getAlmanacsByRange(startDateStr, endDateStr)
|
||||
}
|
||||
|
||||
async searchAlmanacs(params: AlmanacSearchParams): Promise<Almanac[]> {
|
||||
const { keyword, dateFrom, dateTo, suitableFilter, unsuitableFilter } = params
|
||||
|
||||
const startDate = dateFrom ?? '2026-01-01'
|
||||
const endDate = dateTo ?? '2026-12-31'
|
||||
let results = await this.getAlmanacsByRange(startDate, endDate)
|
||||
|
||||
if (keyword) {
|
||||
results = results.filter(a =>
|
||||
a.suitable.some(s => s.includes(keyword)) ||
|
||||
a.unsuitable.some(s => s.includes(keyword)) ||
|
||||
a.jianChu.includes(keyword) ||
|
||||
a.starGod.includes(keyword),
|
||||
)
|
||||
}
|
||||
|
||||
if (suitableFilter && suitableFilter.length > 0) {
|
||||
results = results.filter(a =>
|
||||
suitableFilter.some(f => a.suitable.includes(f)),
|
||||
)
|
||||
}
|
||||
|
||||
if (unsuitableFilter && unsuitableFilter.length > 0) {
|
||||
results = results.filter(a =>
|
||||
unsuitableFilter.some(f => a.unsuitable.includes(f)),
|
||||
)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import type { ZiweiChart, DailyFortune, MonthlyFortune, YearlyFortune } from '../algorithms/types'
|
||||
import {
|
||||
generateDailyFortune,
|
||||
generateMonthlyFortune,
|
||||
generateYearlyFortune,
|
||||
} from '../algorithms/fortuneStrategy'
|
||||
import {
|
||||
generateComprehensiveFortune,
|
||||
generateComprehensiveMonthlyFortune,
|
||||
generateComprehensiveYearlyFortune,
|
||||
} from '../algorithms/fortune'
|
||||
import {
|
||||
calculateLuckyColor,
|
||||
calculateLuckyNumber,
|
||||
calculateLuckyDirection,
|
||||
} from '../algorithms/ziweiAlgorithm'
|
||||
|
||||
export class FortuneService {
|
||||
async getDailyFortune(chart: ZiweiChart, dateStr: string): Promise<DailyFortune | null> {
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return null
|
||||
|
||||
const fortune = generateDailyFortune(chart, date)
|
||||
fortune.luckyColor = calculateLuckyColor(chart, date)
|
||||
fortune.luckyNumber = calculateLuckyNumber(chart, date)
|
||||
fortune.luckyDirection = calculateLuckyDirection(chart, date)
|
||||
return fortune
|
||||
} catch (e) {
|
||||
console.error(`Daily fortune error for ${dateStr}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getMonthlyFortune(chart: ZiweiChart, year: number, month: number): Promise<MonthlyFortune | null> {
|
||||
try {
|
||||
if (month < 1 || month > 12) return null
|
||||
|
||||
const fortune = generateMonthlyFortune(chart, year, month)
|
||||
return {
|
||||
fortuneMonth: `${year}-${String(month).padStart(2, '0')}`,
|
||||
overallLuck: fortune.overallLuck ?? '',
|
||||
overallScore: fortune.overallScore ?? 0,
|
||||
palaceFortunes: fortune.palaceFortunes ?? {},
|
||||
dailyFortunes: [],
|
||||
careerAdvice: fortune.careerAdvice ?? '',
|
||||
wealthAdvice: fortune.wealthAdvice ?? '',
|
||||
relationshipAdvice: fortune.relationshipAdvice ?? '',
|
||||
healthAdvice: fortune.healthAdvice ?? '',
|
||||
luckyColor: '',
|
||||
luckyNumber: '',
|
||||
luckyDirection: '',
|
||||
keyFocus: '',
|
||||
cautionAdvice: '',
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Monthly fortune error for ${year}-${month}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getYearlyFortune(chart: ZiweiChart, year: number): Promise<YearlyFortune | null> {
|
||||
try {
|
||||
if (year < 1900 || year > 2100) return null
|
||||
|
||||
const fortune = generateYearlyFortune(chart, year)
|
||||
return {
|
||||
fortuneYear: year,
|
||||
overallLuck: fortune.overallLuck ?? '',
|
||||
overallScore: fortune.overallScore ?? 0,
|
||||
palaceFortunes: fortune.palaceFortunes ?? {},
|
||||
monthlyFortunes: [],
|
||||
careerAdvice: fortune.careerAdvice ?? '',
|
||||
wealthAdvice: fortune.wealthAdvice ?? '',
|
||||
relationshipAdvice: fortune.relationshipAdvice ?? '',
|
||||
healthAdvice: fortune.healthAdvice ?? '',
|
||||
luckyColor: '',
|
||||
luckyNumber: '',
|
||||
luckyDirection: '',
|
||||
keyFocus: '',
|
||||
cautionAdvice: '',
|
||||
yearlyTheme: '',
|
||||
majorOpportunity: '',
|
||||
majorChallenge: '',
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Yearly fortune error for ${year}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getComprehensiveFortune(chart: ZiweiChart, dateStr: string): Promise<DailyFortune | null> {
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) return null
|
||||
|
||||
const fortune = generateComprehensiveFortune(chart, date)
|
||||
fortune.luckyColor = calculateLuckyColor(chart, date)
|
||||
fortune.luckyNumber = calculateLuckyNumber(chart, date)
|
||||
fortune.luckyDirection = calculateLuckyDirection(chart, date)
|
||||
return fortune
|
||||
} catch (e) {
|
||||
console.error(`Comprehensive fortune error for ${dateStr}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getComprehensiveMonthlyFortune(chart: ZiweiChart, year: number, month: number): Promise<MonthlyFortune | null> {
|
||||
try {
|
||||
if (month < 1 || month > 12) return null
|
||||
|
||||
const fortune = generateComprehensiveMonthlyFortune(chart, year, month)
|
||||
return {
|
||||
fortuneMonth: `${year}-${String(month).padStart(2, '0')}`,
|
||||
overallLuck: fortune.overallLuck ?? '',
|
||||
overallScore: fortune.overallScore ?? 0,
|
||||
palaceFortunes: fortune.palaceFortunes ?? {},
|
||||
dailyFortunes: [],
|
||||
careerAdvice: fortune.careerAdvice ?? '',
|
||||
wealthAdvice: fortune.wealthAdvice ?? '',
|
||||
relationshipAdvice: fortune.relationshipAdvice ?? '',
|
||||
healthAdvice: fortune.healthAdvice ?? '',
|
||||
luckyColor: '',
|
||||
luckyNumber: '',
|
||||
luckyDirection: '',
|
||||
keyFocus: '',
|
||||
cautionAdvice: '',
|
||||
loveScore: fortune.loveScore,
|
||||
overallLoveLuck: fortune.overallLoveLuck,
|
||||
peachBlossomIndex: fortune.peachBlossomIndex,
|
||||
singleAdvice: fortune.singleAdvice,
|
||||
datingAdvice: fortune.datingAdvice,
|
||||
marriageAdvice: fortune.marriageAdvice,
|
||||
bestDatingTime: fortune.bestDatingTime,
|
||||
studyScore: fortune.studyScore,
|
||||
overallStudyLuck: fortune.overallStudyLuck,
|
||||
focusIndex: fortune.focusIndex,
|
||||
memoryIndex: fortune.memoryIndex,
|
||||
studyAdvice: fortune.studyAdvice,
|
||||
examLuck: fortune.examLuck,
|
||||
bestStudyTime: fortune.bestStudyTime,
|
||||
suitableSubjects: fortune.suitableSubjects,
|
||||
studyEnvironmentAdvice: fortune.studyEnvironmentAdvice,
|
||||
socialScore: fortune.socialScore,
|
||||
overallSocialLuck: fortune.overallSocialLuck,
|
||||
popularityIndex: fortune.popularityIndex,
|
||||
communicationAbility: fortune.communicationAbility,
|
||||
socialAdvice: fortune.socialAdvice,
|
||||
nobleLuck: fortune.nobleLuck,
|
||||
bestSocialTime: fortune.bestSocialTime,
|
||||
suitableOccasion: fortune.suitableOccasion,
|
||||
interpersonalAdvice: fortune.interpersonalAdvice,
|
||||
travelScore: fortune.travelScore,
|
||||
overallTravelLuck: fortune.overallTravelLuck,
|
||||
safetyIndex: fortune.safetyIndex,
|
||||
smoothnessIndex: fortune.smoothnessIndex,
|
||||
travelAdvice: fortune.travelAdvice,
|
||||
bestTravelTime: fortune.bestTravelTime,
|
||||
suitableTransport: fortune.suitableTransport,
|
||||
precautions: fortune.precautions,
|
||||
nobleDirection: fortune.nobleDirection,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Comprehensive monthly fortune error for ${year}-${month}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getComprehensiveYearlyFortune(chart: ZiweiChart, year: number): Promise<YearlyFortune | null> {
|
||||
try {
|
||||
if (year < 1900 || year > 2100) return null
|
||||
|
||||
const fortune = generateComprehensiveYearlyFortune(chart, year)
|
||||
return {
|
||||
fortuneYear: year,
|
||||
overallLuck: fortune.overallLuck ?? '',
|
||||
overallScore: fortune.overallScore ?? 0,
|
||||
palaceFortunes: fortune.palaceFortunes ?? {},
|
||||
monthlyFortunes: [],
|
||||
careerAdvice: fortune.careerAdvice ?? '',
|
||||
wealthAdvice: fortune.wealthAdvice ?? '',
|
||||
relationshipAdvice: fortune.relationshipAdvice ?? '',
|
||||
healthAdvice: fortune.healthAdvice ?? '',
|
||||
luckyColor: '',
|
||||
luckyNumber: '',
|
||||
luckyDirection: '',
|
||||
keyFocus: '',
|
||||
cautionAdvice: '',
|
||||
yearlyTheme: '',
|
||||
majorOpportunity: '',
|
||||
majorChallenge: '',
|
||||
loveScore: fortune.loveScore,
|
||||
overallLoveLuck: fortune.overallLoveLuck,
|
||||
peachBlossomIndex: fortune.peachBlossomIndex,
|
||||
singleAdvice: fortune.singleAdvice,
|
||||
datingAdvice: fortune.datingAdvice,
|
||||
marriageAdvice: fortune.marriageAdvice,
|
||||
bestDatingTime: fortune.bestDatingTime,
|
||||
studyScore: fortune.studyScore,
|
||||
overallStudyLuck: fortune.overallStudyLuck,
|
||||
focusIndex: fortune.focusIndex,
|
||||
memoryIndex: fortune.memoryIndex,
|
||||
studyAdvice: fortune.studyAdvice,
|
||||
examLuck: fortune.examLuck,
|
||||
bestStudyTime: fortune.bestStudyTime,
|
||||
suitableSubjects: fortune.suitableSubjects,
|
||||
studyEnvironmentAdvice: fortune.studyEnvironmentAdvice,
|
||||
socialScore: fortune.socialScore,
|
||||
overallSocialLuck: fortune.overallSocialLuck,
|
||||
popularityIndex: fortune.popularityIndex,
|
||||
communicationAbility: fortune.communicationAbility,
|
||||
socialAdvice: fortune.socialAdvice,
|
||||
nobleLuck: fortune.nobleLuck,
|
||||
bestSocialTime: fortune.bestSocialTime,
|
||||
suitableOccasion: fortune.suitableOccasion,
|
||||
interpersonalAdvice: fortune.interpersonalAdvice,
|
||||
travelScore: fortune.travelScore,
|
||||
overallTravelLuck: fortune.overallTravelLuck,
|
||||
safetyIndex: fortune.safetyIndex,
|
||||
smoothnessIndex: fortune.smoothnessIndex,
|
||||
travelAdvice: fortune.travelAdvice,
|
||||
bestTravelTime: fortune.bestTravelTime,
|
||||
suitableTransport: fortune.suitableTransport,
|
||||
precautions: fortune.precautions,
|
||||
nobleDirection: fortune.nobleDirection,
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Comprehensive yearly fortune error for ${year}:`, e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { AlmanacService } from './almanacService'
|
||||
import type { Almanac } from '../algorithms/types'
|
||||
import type { SearchCondition, SearchRequest, SearchResult } from '../types/search'
|
||||
import { storage } from '../utils/storage'
|
||||
|
||||
export interface SearchParams {
|
||||
keyword?: string
|
||||
dateFrom?: string
|
||||
dateTo?: string
|
||||
suitableFilter?: string[]
|
||||
unsuitableFilter?: string[]
|
||||
}
|
||||
|
||||
export interface SearchHistoryItem {
|
||||
keyword: string
|
||||
timestamp: number
|
||||
resultCount: number
|
||||
}
|
||||
|
||||
function toSearchParams(request: SearchRequest): SearchParams {
|
||||
const suitableFilter: string[] = []
|
||||
const unsuitableFilter: string[] = []
|
||||
|
||||
for (const cond of request.conditions) {
|
||||
if (cond.type === 'suitable') {
|
||||
suitableFilter.push(...cond.items)
|
||||
} else {
|
||||
unsuitableFilter.push(...cond.items)
|
||||
}
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
const dateFrom = today.toISOString().slice(0, 10)
|
||||
const endDate = new Date(today)
|
||||
endDate.setDate(endDate.getDate() + request.days)
|
||||
const dateTo = endDate.toISOString().slice(0, 10)
|
||||
|
||||
return { suitableFilter, unsuitableFilter, dateFrom, dateTo }
|
||||
}
|
||||
|
||||
function toSearchResult(almanac: Almanac, conditions: SearchCondition[]): SearchResult {
|
||||
const matchedSuitable = conditions
|
||||
.filter(c => c.type === 'suitable')
|
||||
.flatMap(c => c.items)
|
||||
.filter(item => almanac.suitable.includes(item))
|
||||
|
||||
const matchedUnsuitable = conditions
|
||||
.filter(c => c.type === 'unsuitable')
|
||||
.flatMap(c => c.items)
|
||||
.filter(item => almanac.unsuitable.includes(item))
|
||||
|
||||
return {
|
||||
date: almanac.solarDate,
|
||||
lunarDate: `${almanac.lunarDate.lunarYear}${almanac.lunarDate.lunarMonth}${almanac.lunarDate.lunarDay}`,
|
||||
weekday: almanac.lunarDate.weekday,
|
||||
matchedItems: {
|
||||
suitable: matchedSuitable,
|
||||
unsuitable: matchedUnsuitable,
|
||||
},
|
||||
matchCount: matchedSuitable.length + matchedUnsuitable.length,
|
||||
almanacData: almanac,
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchService {
|
||||
private almanacService = new AlmanacService()
|
||||
private historyKey = 'search_history'
|
||||
|
||||
async search(request: SearchRequest): Promise<SearchResult[]> {
|
||||
const params = toSearchParams(request)
|
||||
const almanacs = await this.almanacService.searchAlmanacs(params)
|
||||
this.addToHistory(
|
||||
request.conditions.map(c => c.items.join(',')).join(';'),
|
||||
almanacs.length,
|
||||
)
|
||||
return almanacs.map(a => toSearchResult(a, request.conditions))
|
||||
}
|
||||
|
||||
async searchByKeyword(keyword: string): Promise<SearchResult[]> {
|
||||
const params: SearchParams = { keyword }
|
||||
const almanacs = await this.almanacService.searchAlmanacs(params)
|
||||
this.addToHistory(keyword, almanacs.length)
|
||||
return almanacs.map(a => toSearchResult(a, []))
|
||||
}
|
||||
|
||||
async getSearchHistory(): Promise<SearchHistoryItem[]> {
|
||||
return storage.get<SearchHistoryItem[]>(this.historyKey) ?? []
|
||||
}
|
||||
|
||||
async clearSearchHistory(): Promise<void> {
|
||||
storage.remove(this.historyKey)
|
||||
}
|
||||
|
||||
private addToHistory(keyword: string, resultCount: number): void {
|
||||
if (!keyword) return
|
||||
const history = storage.get<SearchHistoryItem[]>(this.historyKey) ?? []
|
||||
history.unshift({ keyword, timestamp: Date.now(), resultCount })
|
||||
if (history.length > 50) history.length = 50
|
||||
storage.set(this.historyKey, history)
|
||||
}
|
||||
}
|
||||
|
||||
const searchService = new SearchService()
|
||||
export default searchService
|
||||
@@ -0,0 +1,286 @@
|
||||
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
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { ZiweiChart, BirthInfo, Palace, StarInfo } from '../algorithms/types'
|
||||
import { HeavenlyStem, EarthlyBranch, PalaceType, MajorStar, StarNature } from '../algorithms/enums'
|
||||
import { calculatePalaceScore } from '../algorithms/ziweiAlgorithm'
|
||||
import { calculateSanFangSiZheng } from '../algorithms/sanFangSiZheng'
|
||||
import { LRUCache } from '../utils/lruCache'
|
||||
|
||||
export interface GenerateChartParams {
|
||||
birthTime: string
|
||||
gender: 'male' | 'female'
|
||||
timezone: string
|
||||
longitude?: number
|
||||
latitude?: number
|
||||
}
|
||||
|
||||
const HOUR_BRANCH_MAP: Record<number, EarthlyBranch> = {
|
||||
0: EarthlyBranch.ZI,
|
||||
1: EarthlyBranch.ZI,
|
||||
2: EarthlyBranch.CHOU,
|
||||
3: EarthlyBranch.CHOU,
|
||||
4: EarthlyBranch.YIN,
|
||||
5: EarthlyBranch.YIN,
|
||||
6: EarthlyBranch.MAO,
|
||||
7: EarthlyBranch.MAO,
|
||||
8: EarthlyBranch.CHEN,
|
||||
9: EarthlyBranch.CHEN,
|
||||
10: EarthlyBranch.SI,
|
||||
11: EarthlyBranch.SI,
|
||||
12: EarthlyBranch.WU,
|
||||
13: EarthlyBranch.WU,
|
||||
14: EarthlyBranch.WEI,
|
||||
15: EarthlyBranch.WEI,
|
||||
16: EarthlyBranch.SHEN,
|
||||
17: EarthlyBranch.SHEN,
|
||||
18: EarthlyBranch.YOU,
|
||||
19: EarthlyBranch.YOU,
|
||||
20: EarthlyBranch.XU,
|
||||
21: EarthlyBranch.XU,
|
||||
22: EarthlyBranch.HAI,
|
||||
23: EarthlyBranch.HAI,
|
||||
}
|
||||
|
||||
const MONTH_BRANCHES: EarthlyBranch[] = [
|
||||
EarthlyBranch.YIN,
|
||||
EarthlyBranch.MAO,
|
||||
EarthlyBranch.CHEN,
|
||||
EarthlyBranch.SI,
|
||||
EarthlyBranch.WU,
|
||||
EarthlyBranch.WEI,
|
||||
EarthlyBranch.SHEN,
|
||||
EarthlyBranch.YOU,
|
||||
EarthlyBranch.XU,
|
||||
EarthlyBranch.HAI,
|
||||
EarthlyBranch.ZI,
|
||||
EarthlyBranch.CHOU,
|
||||
]
|
||||
|
||||
const MAJOR_STAR_SEQUENCE: MajorStar[] = [
|
||||
MajorStar.ZIWEI,
|
||||
MajorStar.TIANJI,
|
||||
MajorStar.TAIYANG,
|
||||
MajorStar.WUQU,
|
||||
MajorStar.TIANTONG,
|
||||
MajorStar.LIANCHEN,
|
||||
]
|
||||
|
||||
const MAJOR_STAR_SEQUENCE2: MajorStar[] = [
|
||||
MajorStar.TIANFU,
|
||||
MajorStar.TAIYIN,
|
||||
MajorStar.TANLANG,
|
||||
MajorStar.JUMEN,
|
||||
MajorStar.TIANXIANG,
|
||||
MajorStar.TIANLIANG,
|
||||
MajorStar.QISHA,
|
||||
MajorStar.POJUN,
|
||||
]
|
||||
|
||||
export class ZiweiService {
|
||||
private cache = new LRUCache<ZiweiChart>({ maxSize: 50 })
|
||||
|
||||
private buildBirthInfo(params: GenerateChartParams): BirthInfo {
|
||||
const birthDate = new Date(params.birthTime)
|
||||
const year = birthDate.getFullYear()
|
||||
const month = birthDate.getMonth() + 1
|
||||
const hour = birthDate.getHours()
|
||||
|
||||
const yearStemIndex = ((year - 4) % 10 + 10) % 10 + 1
|
||||
const yearBranchIndex = ((year - 4) % 12 + 12) % 12 + 1
|
||||
const monthBranchIndex = month
|
||||
const hourBranch = HOUR_BRANCH_MAP[hour] ?? EarthlyBranch.ZI
|
||||
|
||||
const yearStem = HeavenlyStem.fromIndex(yearStemIndex)
|
||||
const yearBranch = EarthlyBranch.fromIndex(yearBranchIndex)
|
||||
const monthBranch = MONTH_BRANCHES[month - 1] ?? EarthlyBranch.YIN
|
||||
|
||||
return {
|
||||
birthTime: params.birthTime,
|
||||
yearStem,
|
||||
monthStem: yearStem,
|
||||
dayStem: yearStem,
|
||||
hourStem: yearStem,
|
||||
yearBranch,
|
||||
monthBranch,
|
||||
dayBranch: monthBranch,
|
||||
hourBranch,
|
||||
gender: params.gender === 'male' ? 'MALE' : 'FEMALE',
|
||||
timezone: params.timezone,
|
||||
longitude: params.longitude,
|
||||
latitude: params.latitude,
|
||||
}
|
||||
}
|
||||
|
||||
private calculateMingGong(monthBranch: EarthlyBranch, hourBranch: EarthlyBranch): EarthlyBranch {
|
||||
const monthIndex = EarthlyBranch.index(monthBranch)
|
||||
const hourIndex = EarthlyBranch.index(hourBranch)
|
||||
let mingGongIndex = 14 - monthIndex - hourIndex
|
||||
while (mingGongIndex <= 0) mingGongIndex += 12
|
||||
if (mingGongIndex > 12) mingGongIndex -= 12
|
||||
return EarthlyBranch.fromIndex(mingGongIndex)
|
||||
}
|
||||
|
||||
private calculateShenGong(monthBranch: EarthlyBranch, hourBranch: EarthlyBranch): EarthlyBranch {
|
||||
const monthIndex = EarthlyBranch.index(monthBranch)
|
||||
const hourIndex = EarthlyBranch.index(hourBranch)
|
||||
let shenGongIndex = 2 + monthIndex + hourIndex
|
||||
while (shenGongIndex > 12) shenGongIndex -= 12
|
||||
return EarthlyBranch.fromIndex(shenGongIndex)
|
||||
}
|
||||
|
||||
private buildPalaces(mingGongBranch: EarthlyBranch, shenGongBranch: EarthlyBranch, birthInfo: BirthInfo): Palace[] {
|
||||
const branches = EarthlyBranch.values()
|
||||
const palaceTypes = PalaceType.values()
|
||||
const mingGongIndex = EarthlyBranch.index(mingGongBranch)
|
||||
const mingBranchArrayIndex = branches.indexOf(mingGongBranch)
|
||||
|
||||
const palaces: Palace[] = []
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const branchArrayIndex = (mingBranchArrayIndex + i) % 12
|
||||
const earthlyBranch = branches[branchArrayIndex]
|
||||
const palaceType = palaceTypes[i] ?? PalaceType.MING
|
||||
|
||||
const majorStars = this.assignStarsToPalace(i, mingGongIndex, birthInfo)
|
||||
|
||||
const palace: Palace = {
|
||||
palaceType,
|
||||
earthlyBranch,
|
||||
majorStars,
|
||||
minorStars: [],
|
||||
analysis: '',
|
||||
score: 50,
|
||||
}
|
||||
|
||||
palace.score = calculatePalaceScore(palace)
|
||||
palaces.push(palace)
|
||||
}
|
||||
|
||||
return palaces
|
||||
}
|
||||
|
||||
private assignStarsToPalace(palaceOffset: number, mingGongIndex: number, birthInfo: BirthInfo): StarInfo[] {
|
||||
const stars: StarInfo[] = []
|
||||
const yearStemIndex = HeavenlyStem.index(birthInfo.yearStem)
|
||||
|
||||
if (palaceOffset === 0) {
|
||||
stars.push(this.createStarInfo(MajorStar.ZIWEI, yearStemIndex))
|
||||
} else if (palaceOffset <= 5) {
|
||||
stars.push(this.createStarInfo(MAJOR_STAR_SEQUENCE[palaceOffset], yearStemIndex))
|
||||
}
|
||||
|
||||
const fuOffset = (12 - (mingGongIndex - 1)) % 12
|
||||
if (palaceOffset === fuOffset) {
|
||||
stars.push(this.createStarInfo(MajorStar.TIANFU, yearStemIndex))
|
||||
}
|
||||
|
||||
for (let i = 0; i < MAJOR_STAR_SEQUENCE2.length; i++) {
|
||||
const targetOffset = (fuOffset + i) % 12
|
||||
if (palaceOffset === targetOffset && palaceOffset !== fuOffset) {
|
||||
stars.push(this.createStarInfo(MAJOR_STAR_SEQUENCE2[i], yearStemIndex))
|
||||
}
|
||||
}
|
||||
|
||||
return stars
|
||||
}
|
||||
|
||||
private createStarInfo(star: MajorStar, yearStemIndex: number): StarInfo {
|
||||
return {
|
||||
star,
|
||||
nature: MajorStar.nature(star),
|
||||
transformation: this.getTransformation(star, yearStemIndex),
|
||||
brightness: 80,
|
||||
description: MajorStar.description(star),
|
||||
}
|
||||
}
|
||||
|
||||
private getTransformation(star: MajorStar, yearStemIndex: number): StarInfo['transformation'] {
|
||||
const luMap: Record<number, MajorStar> = {
|
||||
1: MajorStar.POJUN, 2: MajorStar.TIANJI, 3: MajorStar.TAIYANG,
|
||||
4: MajorStar.WUQU, 5: MajorStar.LIANCHEN, 6: MajorStar.TIANFU,
|
||||
7: MajorStar.TAIYANG, 8: MajorStar.JUMEN, 9: MajorStar.TIANLIANG,
|
||||
10: MajorStar.ZIWEI,
|
||||
}
|
||||
const quanMap: Record<number, MajorStar> = {
|
||||
1: MajorStar.TIANLIANG, 2: MajorStar.TIANLIANG, 3: MajorStar.WUQU,
|
||||
4: MajorStar.ZIWEI, 5: MajorStar.POJUN, 6: MajorStar.TIANXIANG,
|
||||
7: MajorStar.TIANJI, 8: MajorStar.TANLANG, 9: MajorStar.QISHA,
|
||||
10: MajorStar.LIANCHEN,
|
||||
}
|
||||
|
||||
if (luMap[yearStemIndex] === star) return 'LU' as any
|
||||
if (quanMap[yearStemIndex] === star) return 'QUAN' as any
|
||||
return null
|
||||
}
|
||||
|
||||
private generateSummary(chart: ZiweiChart): string {
|
||||
const parts: string[] = []
|
||||
parts.push(`命宫位于${EarthlyBranch.name(chart.mingGongBranch)}宫`)
|
||||
|
||||
const mingPalace = chart.palaces.find(p => p.palaceType === PalaceType.MING)
|
||||
if (mingPalace && mingPalace.majorStars.length > 0) {
|
||||
const starNames = mingPalace.majorStars.map(s => MajorStar.name(s.star)).join('、')
|
||||
parts.push(`,命宫主星:${starNames}`)
|
||||
}
|
||||
|
||||
if (chart.sanFangSiZheng) {
|
||||
parts.push(`。三方得分:${chart.sanFangSiZheng.sanFangScore},四正得分:${chart.sanFangSiZheng.siZhengScore}`)
|
||||
}
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
async generateChart(params: GenerateChartParams): Promise<ZiweiChart> {
|
||||
const cacheKey = this.buildCacheKey(params)
|
||||
const cached = this.cache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
const birthInfo = this.buildBirthInfo(params)
|
||||
const mingGongBranch = this.calculateMingGong(birthInfo.monthBranch, birthInfo.hourBranch)
|
||||
const shenGongBranch = this.calculateShenGong(birthInfo.monthBranch, birthInfo.hourBranch)
|
||||
const palaces = this.buildPalaces(mingGongBranch, shenGongBranch, birthInfo)
|
||||
|
||||
const chart: ZiweiChart = {
|
||||
birthInfo,
|
||||
palaces,
|
||||
yearStem: birthInfo.yearStem,
|
||||
mingGongBranch,
|
||||
shenGongBranch,
|
||||
summary: '',
|
||||
overallLuck: '',
|
||||
}
|
||||
|
||||
chart.sanFangSiZheng = calculateSanFangSiZheng(chart) ?? undefined
|
||||
chart.summary = this.generateSummary(chart)
|
||||
|
||||
const overallScore = chart.sanFangSiZheng?.sanFangScore ?? 60
|
||||
if (overallScore >= 80) chart.overallLuck = '吉'
|
||||
else if (overallScore >= 60) chart.overallLuck = '平'
|
||||
else chart.overallLuck = '凶'
|
||||
|
||||
this.cache.set(cacheKey, chart)
|
||||
return chart
|
||||
}
|
||||
|
||||
async getChartFromCache(params: GenerateChartParams): Promise<ZiweiChart | null> {
|
||||
const cacheKey = this.buildCacheKey(params)
|
||||
return this.cache.get(cacheKey)
|
||||
}
|
||||
|
||||
private buildCacheKey(params: GenerateChartParams): string {
|
||||
return `${params.birthTime}_${params.gender}_${params.timezone}`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { storage } from './storage'
|
||||
|
||||
const EXPORT_VERSION = '1.0.0'
|
||||
|
||||
export interface ExportData {
|
||||
version: string
|
||||
exportDate: string
|
||||
data: Record<string, any>
|
||||
}
|
||||
|
||||
export function exportData(): string {
|
||||
const data: ExportData = {
|
||||
version: EXPORT_VERSION,
|
||||
exportDate: new Date().toISOString().split('T')[0],
|
||||
data: {},
|
||||
}
|
||||
|
||||
const storageKeys = [
|
||||
'ziwei_charts',
|
||||
'search_history',
|
||||
'search_templates',
|
||||
]
|
||||
|
||||
for (const key of storageKeys) {
|
||||
const value = storage.get(key)
|
||||
if (value !== null) {
|
||||
data.data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(data, null, 2)
|
||||
}
|
||||
|
||||
export function validateImportData(jsonStr: string): boolean {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr) as ExportData
|
||||
if (!parsed.version || !parsed.data) return false
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function importData(jsonStr: string): boolean {
|
||||
if (!validateImportData(jsonStr)) return false
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonStr) as ExportData
|
||||
for (const [key, value] of Object.entries(parsed.data)) {
|
||||
storage.set(key, value)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
export class StorageService {
|
||||
private prefix: string
|
||||
|
||||
constructor(prefix: string = 'eis_') {
|
||||
this.prefix = prefix
|
||||
}
|
||||
|
||||
private getKey(key: string): string {
|
||||
return `${this.prefix}${key}`
|
||||
}
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
try {
|
||||
const raw = uni.getStorageSync(this.getKey(key))
|
||||
if (!raw) return null
|
||||
return JSON.parse(raw) as T
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
set<T>(key: string, value: T): void {
|
||||
try {
|
||||
uni.setStorageSync(this.getKey(key), JSON.stringify(value))
|
||||
} catch (e) {
|
||||
console.error(`Storage set error for key ${key}:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
remove(key: string): void {
|
||||
try {
|
||||
uni.removeStorageSync(this.getKey(key))
|
||||
} catch (e) {
|
||||
console.error(`Storage remove error for key ${key}:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
try {
|
||||
uni.clearStorageSync()
|
||||
} catch (e) {
|
||||
console.error('Storage clear error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
has(key: string): boolean {
|
||||
try {
|
||||
const raw = uni.getStorageSync(this.getKey(key))
|
||||
return !!raw
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const storage = new StorageService()
|
||||
@@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import Uni from '@dcloudio/vite-plugin-uni'
|
||||
|
||||
const uniPlugin = Uni.default || Uni
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [...uniPlugin()]
|
||||
})
|
||||
Reference in New Issue
Block a user