From 0c9a4ac96b50b680e9914b4901c8d9b9bb21e308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Wed, 29 Apr 2026 21:15:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20UniApp=20=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E6=BA=90=E7=A0=81=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.vue 应用入口 - algorithms/ 算法模块(黄历、紫微斗数、运势、日历等) - services/ 业务服务层 - utils/ 工具函数(storage、exportImport) - 配置文件(manifest.json、pages.json、vite.config.ts) - H5 入口 index.html --- everything-is-suitable-uniapp/index.html | 14 + everything-is-suitable-uniapp/src/App.vue | 20 + .../src/algorithms/almanac.ts | 396 ++++++++++++++ .../src/algorithms/calendar.ts | 159 ++++++ .../src/algorithms/enums.ts | 316 +++++++++++ .../src/algorithms/fortune.ts | 510 ++++++++++++++++++ .../src/algorithms/fortuneStrategy.ts | 347 ++++++++++++ .../src/algorithms/sanFangSiZheng.ts | 81 +++ .../src/algorithms/types.ts | 305 +++++++++++ .../src/algorithms/ziweiAlgorithm.ts | 275 ++++++++++ .../src/manifest.json | 50 ++ everything-is-suitable-uniapp/src/pages.json | 28 + .../src/services/almanacService.ts | 63 +++ .../src/services/fortuneService.ts | 232 ++++++++ .../src/services/searchService.ts | 104 ++++ .../src/services/templateService.ts | 286 ++++++++++ .../src/services/ziweiService.ts | 270 ++++++++++ .../src/utils/exportImport.ts | 56 ++ .../src/utils/storage.ts | 56 ++ everything-is-suitable-uniapp/vite.config.ts | 8 + 20 files changed, 3576 insertions(+) create mode 100644 everything-is-suitable-uniapp/index.html create mode 100644 everything-is-suitable-uniapp/src/App.vue create mode 100644 everything-is-suitable-uniapp/src/algorithms/almanac.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/calendar.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/enums.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/fortune.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/fortuneStrategy.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/sanFangSiZheng.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/types.ts create mode 100644 everything-is-suitable-uniapp/src/algorithms/ziweiAlgorithm.ts create mode 100644 everything-is-suitable-uniapp/src/manifest.json create mode 100644 everything-is-suitable-uniapp/src/pages.json create mode 100644 everything-is-suitable-uniapp/src/services/almanacService.ts create mode 100644 everything-is-suitable-uniapp/src/services/fortuneService.ts create mode 100644 everything-is-suitable-uniapp/src/services/searchService.ts create mode 100644 everything-is-suitable-uniapp/src/services/templateService.ts create mode 100644 everything-is-suitable-uniapp/src/services/ziweiService.ts create mode 100644 everything-is-suitable-uniapp/src/utils/exportImport.ts create mode 100644 everything-is-suitable-uniapp/src/utils/storage.ts create mode 100644 everything-is-suitable-uniapp/vite.config.ts diff --git a/everything-is-suitable-uniapp/index.html b/everything-is-suitable-uniapp/index.html new file mode 100644 index 0000000..d66c7c2 --- /dev/null +++ b/everything-is-suitable-uniapp/index.html @@ -0,0 +1,14 @@ + + + + + + 万事宜 + + + + +
+ + + diff --git a/everything-is-suitable-uniapp/src/App.vue b/everything-is-suitable-uniapp/src/App.vue new file mode 100644 index 0000000..4750093 --- /dev/null +++ b/everything-is-suitable-uniapp/src/App.vue @@ -0,0 +1,20 @@ + + + diff --git a/everything-is-suitable-uniapp/src/algorithms/almanac.ts b/everything-is-suitable-uniapp/src/algorithms/almanac.ts new file mode 100644 index 0000000..4b590fc --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/almanac.ts @@ -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 = { + 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 +} diff --git a/everything-is-suitable-uniapp/src/algorithms/calendar.ts b/everything-is-suitable-uniapp/src/algorithms/calendar.ts new file mode 100644 index 0000000..0bdc433 --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/calendar.ts @@ -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 = { + '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) +} diff --git a/everything-is-suitable-uniapp/src/algorithms/enums.ts b/everything-is-suitable-uniapp/src/algorithms/enums.ts new file mode 100644 index 0000000..f5eeec4 --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/enums.ts @@ -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.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.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.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.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.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.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] + } +} diff --git a/everything-is-suitable-uniapp/src/algorithms/fortune.ts b/everything-is-suitable-uniapp/src/algorithms/fortune.ts new file mode 100644 index 0000000..9377460 --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/fortune.ts @@ -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 = { + '大吉': '下午3点至5点,傍晚6点至8点', + '吉': '下午2点至4点,晚上7点至9点', + '中吉': '上午10点至12点,下午3点至5点', + '平': '下午1点至3点', + '中平': '上午9点至11点', + '小凶': '暂不宜约会,宜先调整自身状态', + '凶': '不宜约会,宜静待时机', +} + +const STUDY_TIME_MAP: Record = { + '大吉': '清晨6点至8点,晚上8点至10点', + '吉': '上午8点至10点,晚上7点至9点', + '中吉': '上午9点至11点,下午3点至5点', + '平': '上午10点至12点', + '中平': '下午2点至4点', + '小凶': '学习效率较低,建议短暂休息后复习', + '凶': '不宜高强度学习,宜轻松阅读', +} + +const SOCIAL_TIME_MAP: Record = { + '大吉': '中午12点至2点,晚上6点至9点', + '吉': '下午1点至3点,晚上7点至10点', + '中吉': '上午10点至12点,下午3点至5点', + '平': '下午2点至4点', + '中平': '上午9点至11点', + '小凶': '社交运势偏弱,宜减少应酬', + '凶': '不宜社交,宜独处调整', +} + +const TRAVEL_TIME_MAP: Record = { + '大吉': '上午8点至10点出发最佳', + '吉': '上午9点至11点出发较佳', + '中吉': '上午10点至12点可出行', + '平': '中午前后出行即可', + '中平': '下午出行较为稳妥', + '小凶': '出行需谨慎,注意安全', + '凶': '不宜远行,近处活动为佳', +} + +const SUITABLE_SUBJECTS_MAP: Record = { + '大吉': '文学、哲学、历史等文科类,或管理、领导力相关', + '吉': '语言、艺术、社会科学等,或技术类基础学科', + '中吉': '数学、逻辑、计算机等理科类', + '平': '实用技能、职业培训等应用型学科', + '中平': '通识类课程,宜广泛涉猎', + '小凶': '宜复习巩固已有知识,不宜开启新领域', + '凶': '宜休息调整,轻松阅读即可', +} + +const SUITABLE_OCCASION_MAP: Record = { + '大吉': '商务宴请、高端社交、行业峰会', + '吉': '朋友聚会、团队活动、文化交流', + '中吉': '社区活动、兴趣小组、学习交流', + '平': '日常社交、同事聚餐', + '中平': '小范围聚会、线上交流', + '小凶': '减少社交活动,宜小范围相处', + '凶': '不宜参加社交活动,宜独处', +} + +const SUITABLE_TRANSPORT_MAP: Record = { + '大吉': '飞机、高铁等快速交通工具', + '吉': '高铁、自驾等舒适出行方式', + '中吉': '火车、大巴等常规交通', + '平': '公共交通、地铁等日常出行', + '中平': '短途公交、步行等', + '小凶': '出行需注意安全,避免自驾', + '凶': '不宜远行,就近活动为佳', +} + +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, + } +} diff --git a/everything-is-suitable-uniapp/src/algorithms/fortuneStrategy.ts b/everything-is-suitable-uniapp/src/algorithms/fortuneStrategy.ts new file mode 100644 index 0000000..57330ad --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/fortuneStrategy.ts @@ -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 = { + [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 = { + [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 = { + [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 | Record): 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 = {} + + 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 = {} + + 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 = {} + + 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: '', + } +} diff --git a/everything-is-suitable-uniapp/src/algorithms/sanFangSiZheng.ts b/everything-is-suitable-uniapp/src/algorithms/sanFangSiZheng.ts new file mode 100644 index 0000000..c5b34be --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/sanFangSiZheng.ts @@ -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') +} diff --git a/everything-is-suitable-uniapp/src/algorithms/types.ts b/everything-is-suitable-uniapp/src/algorithms/types.ts new file mode 100644 index 0000000..a2b2b95 --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/types.ts @@ -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 + 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 + 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 + 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 +} diff --git a/everything-is-suitable-uniapp/src/algorithms/ziweiAlgorithm.ts b/everything-is-suitable-uniapp/src/algorithms/ziweiAlgorithm.ts new file mode 100644 index 0000000..248f65f --- /dev/null +++ b/everything-is-suitable-uniapp/src/algorithms/ziweiAlgorithm.ts @@ -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.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.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.LU, 15], + [TransformationType.QUAN, 10], + [TransformationType.KE, 8], + [TransformationType.JI, -15], +]) + +const BRANCH_COLOR_MAP = new Map([ + [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.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 '凶' +} diff --git a/everything-is-suitable-uniapp/src/manifest.json b/everything-is-suitable-uniapp/src/manifest.json new file mode 100644 index 0000000..5aa97bc --- /dev/null +++ b/everything-is-suitable-uniapp/src/manifest.json @@ -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": { + "": 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 + } +} diff --git a/everything-is-suitable-uniapp/src/pages.json b/everything-is-suitable-uniapp/src/pages.json new file mode 100644 index 0000000..c0a228e --- /dev/null +++ b/everything-is-suitable-uniapp/src/pages.json @@ -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" + } +} diff --git a/everything-is-suitable-uniapp/src/services/almanacService.ts b/everything-is-suitable-uniapp/src/services/almanacService.ts new file mode 100644 index 0000000..3610321 --- /dev/null +++ b/everything-is-suitable-uniapp/src/services/almanacService.ts @@ -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({ maxSize: 365 }) + + private calculateAlmanac(dateStr: string): Almanac | null { + return getAlmanacByDate(dateStr) + } + + async getAlmanacByDate(dateStr: string): Promise { + 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 { + return getAlmanacsByRange(startDateStr, endDateStr) + } + + async searchAlmanacs(params: AlmanacSearchParams): Promise { + 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 + } +} diff --git a/everything-is-suitable-uniapp/src/services/fortuneService.ts b/everything-is-suitable-uniapp/src/services/fortuneService.ts new file mode 100644 index 0000000..c0197e1 --- /dev/null +++ b/everything-is-suitable-uniapp/src/services/fortuneService.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 + } + } +} diff --git a/everything-is-suitable-uniapp/src/services/searchService.ts b/everything-is-suitable-uniapp/src/services/searchService.ts new file mode 100644 index 0000000..2d6fb0c --- /dev/null +++ b/everything-is-suitable-uniapp/src/services/searchService.ts @@ -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 { + 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 { + 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 { + return storage.get(this.historyKey) ?? [] + } + + async clearSearchHistory(): Promise { + storage.remove(this.historyKey) + } + + private addToHistory(keyword: string, resultCount: number): void { + if (!keyword) return + const history = storage.get(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 diff --git a/everything-is-suitable-uniapp/src/services/templateService.ts b/everything-is-suitable-uniapp/src/services/templateService.ts new file mode 100644 index 0000000..ccfa373 --- /dev/null +++ b/everything-is-suitable-uniapp/src/services/templateService.ts @@ -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(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 { + const stored = storage.get(this.templatesKey) + if (stored && stored.length > 0) return stored + return DEFAULT_TEMPLATES + } + + async getTemplateById(id: string): Promise { + const templates = await this.getTemplates() + return templates.find(t => t.id === id) ?? null + } + + async saveTemplate(template: ReportTemplate): Promise { + 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 { + 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 { + 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 diff --git a/everything-is-suitable-uniapp/src/services/ziweiService.ts b/everything-is-suitable-uniapp/src/services/ziweiService.ts new file mode 100644 index 0000000..de30856 --- /dev/null +++ b/everything-is-suitable-uniapp/src/services/ziweiService.ts @@ -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 = { + 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({ 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 = { + 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 = { + 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 { + 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 { + const cacheKey = this.buildCacheKey(params) + return this.cache.get(cacheKey) + } + + private buildCacheKey(params: GenerateChartParams): string { + return `${params.birthTime}_${params.gender}_${params.timezone}` + } +} diff --git a/everything-is-suitable-uniapp/src/utils/exportImport.ts b/everything-is-suitable-uniapp/src/utils/exportImport.ts new file mode 100644 index 0000000..709299c --- /dev/null +++ b/everything-is-suitable-uniapp/src/utils/exportImport.ts @@ -0,0 +1,56 @@ +import { storage } from './storage' + +const EXPORT_VERSION = '1.0.0' + +export interface ExportData { + version: string + exportDate: string + data: Record +} + +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 + } +} diff --git a/everything-is-suitable-uniapp/src/utils/storage.ts b/everything-is-suitable-uniapp/src/utils/storage.ts new file mode 100644 index 0000000..76ca3d5 --- /dev/null +++ b/everything-is-suitable-uniapp/src/utils/storage.ts @@ -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(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(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() diff --git a/everything-is-suitable-uniapp/vite.config.ts b/everything-is-suitable-uniapp/vite.config.ts new file mode 100644 index 0000000..de2a82b --- /dev/null +++ b/everything-is-suitable-uniapp/vite.config.ts @@ -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()] +})