fortune.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /**
  2. * 运势计算工具类
  3. * 基于用户资料(生日、星座、生肖)计算婚恋运势、脱单指数、幸运推荐
  4. */
  5. import { getZodiacByBirthday, getZodiacInfo } from './zodiac.js'
  6. // 星座数据
  7. const CONSTELLATIONS = {
  8. '白羊座': { element: '火', startDate: '03-21', endDate: '04-19', luckyColors: ['红色', '橙色'], luckyNumbers: [1, 9], personality: '热情冲动' },
  9. '金牛座': { element: '土', startDate: '04-20', endDate: '05-20', luckyColors: ['绿色', '粉色'], luckyNumbers: [2, 6], personality: '稳重踏实' },
  10. '双子座': { element: '风', startDate: '05-21', endDate: '06-21', luckyColors: ['黄色', '蓝色'], luckyNumbers: [3, 5], personality: '机智多变' },
  11. '巨蟹座': { element: '水', startDate: '06-22', endDate: '07-22', luckyColors: ['白色', '银色'], luckyNumbers: [2, 7], personality: '温柔体贴' },
  12. '狮子座': { element: '火', startDate: '07-23', endDate: '08-22', luckyColors: ['金色', '橙色'], luckyNumbers: [1, 5], personality: '自信大方' },
  13. '处女座': { element: '土', startDate: '08-23', endDate: '09-22', luckyColors: ['灰色', '米色'], luckyNumbers: [4, 8], personality: '细心完美' },
  14. '天秤座': { element: '风', startDate: '09-23', endDate: '10-23', luckyColors: ['粉色', '蓝色'], luckyNumbers: [6, 9], personality: '优雅和谐' },
  15. '天蝎座': { element: '水', startDate: '10-24', endDate: '11-22', luckyColors: ['深红', '黑色'], luckyNumbers: [3, 9], personality: '神秘深情' },
  16. '射手座': { element: '火', startDate: '11-23', endDate: '12-21', luckyColors: ['紫色', '蓝色'], luckyNumbers: [3, 7], personality: '乐观自由' },
  17. '摩羯座': { element: '土', startDate: '12-22', endDate: '01-19', luckyColors: ['黑色', '深蓝'], luckyNumbers: [4, 8], personality: '务实稳重' },
  18. '水瓶座': { element: '风', startDate: '01-20', endDate: '02-18', luckyColors: ['蓝色', '银色'], luckyNumbers: [4, 7], personality: '独立创新' },
  19. '双鱼座': { element: '水', startDate: '02-19', endDate: '03-20', luckyColors: ['海蓝', '紫色'], luckyNumbers: [3, 9], personality: '浪漫敏感' }
  20. }
  21. // 约会建议库
  22. const DATE_SUGGESTIONS = [
  23. '浪漫晚餐', '咖啡约会', '电影之夜', '公园散步', '逛街购物',
  24. '看展览', '户外野餐', '游乐园', '密室逃脱', '烘焙体验',
  25. '书店约会', '音乐会', '运动健身', '美食探店', '手工DIY'
  26. ]
  27. // 穿搭风格库
  28. const STYLE_SUGGESTIONS = [
  29. '清新甜美', '优雅知性', '休闲运动', '简约大方', '时尚潮流',
  30. '温柔淑女', '帅气中性', '复古文艺', '可爱减龄', '成熟稳重'
  31. ]
  32. // 幸运色库
  33. const LUCKY_COLORS = [
  34. '粉红色', '天蓝色', '薄荷绿', '珊瑚橙', '薰衣草紫',
  35. '米白色', '浅黄色', '玫瑰红', '湖蓝色', '杏色'
  36. ]
  37. /**
  38. * 根据生日获取星座
  39. * @param {String} birthday - 生日,格式:YYYY-MM-DD
  40. * @returns {String} 星座名称
  41. */
  42. export function getConstellationByBirthday(birthday) {
  43. if (!birthday) return null
  44. const parts = birthday.split('-')
  45. const month = parseInt(parts[1])
  46. const day = parseInt(parts[2])
  47. const mmdd = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
  48. for (const [name, info] of Object.entries(CONSTELLATIONS)) {
  49. const start = info.startDate
  50. const end = info.endDate
  51. // 处理跨年的星座(摩羯座)
  52. if (start > end) {
  53. if (mmdd >= start || mmdd <= end) return name
  54. } else {
  55. if (mmdd >= start && mmdd <= end) return name
  56. }
  57. }
  58. return '未知'
  59. }
  60. /**
  61. * 获取基于日期的种子值(确保同一天结果一致)
  62. * @param {Date} date - 日期
  63. * @param {String} salt - 盐值(用于区分不同计算)
  64. * @returns {Number} 种子值
  65. */
  66. function getDaySeed(date, salt = '') {
  67. const dateStr = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${salt}`
  68. let hash = 0
  69. for (let i = 0; i < dateStr.length; i++) {
  70. const char = dateStr.charCodeAt(i)
  71. hash = ((hash << 5) - hash) + char
  72. hash = hash & hash
  73. }
  74. return Math.abs(hash)
  75. }
  76. /**
  77. * 基于种子的伪随机数生成器
  78. * @param {Number} seed - 种子值
  79. * @param {Number} min - 最小值
  80. * @param {Number} max - 最大值
  81. * @returns {Number} 随机数
  82. */
  83. function seededRandom(seed, min = 0, max = 100) {
  84. const x = Math.sin(seed) * 10000
  85. const random = x - Math.floor(x)
  86. return Math.floor(random * (max - min + 1)) + min
  87. }
  88. /**
  89. * 计算用户的婚恋运势
  90. * @param {Object} userInfo - 用户信息 { birthDate, star, animal, gender }
  91. * @returns {Array} 运势数组
  92. */
  93. export function calculateLoveFortune(userInfo) {
  94. const today = new Date()
  95. const birthDate = userInfo.birthDate || userInfo.birth_date
  96. // 获取星座和生肖
  97. const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
  98. const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
  99. // 基础分数
  100. let baseScore = 70
  101. // 根据星座元素调整基础分
  102. if (constellation && CONSTELLATIONS[constellation]) {
  103. const element = CONSTELLATIONS[constellation].element
  104. const dayOfWeek = today.getDay()
  105. // 不同元素在不同日子有不同运势
  106. if (element === '火' && (dayOfWeek === 2 || dayOfWeek === 4)) baseScore += 5
  107. if (element === '土' && (dayOfWeek === 0 || dayOfWeek === 6)) baseScore += 5
  108. if (element === '风' && (dayOfWeek === 1 || dayOfWeek === 3)) baseScore += 5
  109. if (element === '水' && (dayOfWeek === 5)) baseScore += 5
  110. }
  111. // 使用日期种子确保同一天结果一致
  112. const seed1 = getDaySeed(today, `${constellation}-peach`)
  113. const seed2 = getDaySeed(today, `${zodiac}-love`)
  114. const seed3 = getDaySeed(today, `${constellation}-charm`)
  115. // 计算三项运势分数
  116. const peachScore = Math.min(95, Math.max(60, baseScore + seededRandom(seed1, -10, 20)))
  117. const loveScore = Math.min(95, Math.max(55, baseScore + seededRandom(seed2, -15, 18)))
  118. const charmScore = Math.min(98, Math.max(65, baseScore + seededRandom(seed3, -5, 25)))
  119. return [
  120. {
  121. label: '桃花运',
  122. icon: '🌸',
  123. score: peachScore,
  124. color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)'
  125. },
  126. {
  127. label: '爱情指数',
  128. icon: '💕',
  129. score: loveScore,
  130. color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)'
  131. },
  132. {
  133. label: '魅力值',
  134. icon: '✨',
  135. score: charmScore,
  136. color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)'
  137. }
  138. ]
  139. }
  140. /**
  141. * 计算本周脱单指数
  142. * @param {Object} userInfo - 用户信息
  143. * @returns {Object} 脱单指数数据
  144. */
  145. export function calculateSingleIndex(userInfo) {
  146. const today = new Date()
  147. const birthDate = userInfo.birthDate || userInfo.birth_date
  148. // 获取星座和生肖
  149. const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
  150. const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
  151. // 获取本周的周一作为种子基准
  152. const weekStart = new Date(today)
  153. weekStart.setDate(today.getDate() - today.getDay() + 1)
  154. const seed = getDaySeed(weekStart, `${constellation}-${zodiac}-single`)
  155. // 计算基础分数
  156. let score = seededRandom(seed, 65, 95)
  157. // 根据生肖特性微调
  158. if (zodiac) {
  159. const zodiacInfo = getZodiacInfo(zodiac)
  160. if (zodiacInfo) {
  161. // 社交型生肖加分
  162. if (['鼠', '马', '猴', '猪'].includes(zodiac)) score += 3
  163. // 内敛型生肖略减
  164. if (['牛', '蛇', '羊'].includes(zodiac)) score -= 2
  165. }
  166. }
  167. score = Math.min(98, Math.max(50, score))
  168. // 确定等级
  169. let level, levelColor
  170. if (score >= 90) {
  171. level = '极佳'
  172. levelColor = 'linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%)'
  173. } else if (score >= 80) {
  174. level = '很好'
  175. levelColor = 'linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%)'
  176. } else if (score >= 70) {
  177. level = '良好'
  178. levelColor = 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)'
  179. } else if (score >= 60) {
  180. level = '一般'
  181. levelColor = 'linear-gradient(135deg, #FF9800 0%, #FFC107 100%)'
  182. } else {
  183. level = '需努力'
  184. levelColor = 'linear-gradient(135deg, #9E9E9E 0%, #BDBDBD 100%)'
  185. }
  186. // 计算最佳约会日
  187. const bestDaySeed = getDaySeed(weekStart, `${constellation}-bestday`)
  188. const bestDayIndex = seededRandom(bestDaySeed, 0, 6)
  189. const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
  190. const bestDay = weekDays[bestDayIndex]
  191. // 计算最佳时间段
  192. const timeSeed = getDaySeed(weekStart, `${zodiac}-time`)
  193. const timeSlots = ['上午10-12点', '下午2-5点', '傍晚5-7点', '晚上7-9点']
  194. const bestTime = timeSlots[seededRandom(timeSeed, 0, 3)]
  195. // 推荐地点
  196. const placeSeed = getDaySeed(weekStart, `${constellation}-place`)
  197. const places = [
  198. '咖啡厅、书店', '公园、广场', '商场、电影院',
  199. '餐厅、美食街', '展览馆、博物馆', '游乐园、景区'
  200. ]
  201. const bestPlace = places[seededRandom(placeSeed, 0, 5)]
  202. return {
  203. score,
  204. level,
  205. levelColor,
  206. tips: [
  207. { icon: '📅', text: `最佳约会日:${bestDay}` },
  208. { icon: '⏰', text: `最佳时间:${bestTime}` },
  209. { icon: '📍', text: `推荐地点:${bestPlace}` }
  210. ]
  211. }
  212. }
  213. /**
  214. * 计算今日幸运推荐
  215. * @param {Object} userInfo - 用户信息
  216. * @returns {Array} 幸运推荐数组
  217. */
  218. export function calculateLuckyRecommend(userInfo) {
  219. const today = new Date()
  220. const birthDate = userInfo.birthDate || userInfo.birth_date
  221. // 获取星座
  222. const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
  223. const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
  224. // 幸运色
  225. let luckyColor = '粉红色'
  226. if (constellation && CONSTELLATIONS[constellation]) {
  227. const constellationColors = CONSTELLATIONS[constellation].luckyColors
  228. const colorSeed = getDaySeed(today, `${constellation}-color`)
  229. luckyColor = constellationColors[seededRandom(colorSeed, 0, constellationColors.length - 1)]
  230. } else {
  231. const colorSeed = getDaySeed(today, 'default-color')
  232. luckyColor = LUCKY_COLORS[seededRandom(colorSeed, 0, LUCKY_COLORS.length - 1)]
  233. }
  234. // 幸运数字
  235. let luckyNumber = '7'
  236. if (constellation && CONSTELLATIONS[constellation]) {
  237. const constellationNumbers = CONSTELLATIONS[constellation].luckyNumbers
  238. const numberSeed = getDaySeed(today, `${constellation}-number`)
  239. luckyNumber = constellationNumbers[seededRandom(numberSeed, 0, constellationNumbers.length - 1)].toString()
  240. } else {
  241. const numberSeed = getDaySeed(today, 'default-number')
  242. luckyNumber = seededRandom(numberSeed, 1, 9).toString()
  243. }
  244. // 约会建议
  245. const dateSeed = getDaySeed(today, `${zodiac}-date`)
  246. const dateSuggestion = DATE_SUGGESTIONS[seededRandom(dateSeed, 0, DATE_SUGGESTIONS.length - 1)]
  247. // 穿搭风格
  248. const styleSeed = getDaySeed(today, `${constellation}-style`)
  249. const styleSuggestion = STYLE_SUGGESTIONS[seededRandom(styleSeed, 0, STYLE_SUGGESTIONS.length - 1)]
  250. return [
  251. {
  252. label: '幸运色',
  253. icon: '🎨',
  254. value: luckyColor,
  255. bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)'
  256. },
  257. {
  258. label: '幸运数字',
  259. icon: '🔢',
  260. value: luckyNumber,
  261. bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)'
  262. },
  263. {
  264. label: '约会建议',
  265. icon: '💡',
  266. value: dateSuggestion,
  267. bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)'
  268. },
  269. {
  270. label: '穿搭风格',
  271. icon: '👗',
  272. value: styleSuggestion,
  273. bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)'
  274. }
  275. ]
  276. }
  277. /**
  278. * 获取用户完整的运势数据
  279. * @param {Object} userInfo - 用户信息
  280. * @returns {Object} 完整运势数据
  281. */
  282. export function getFullFortuneData(userInfo) {
  283. // 检查用户是否有足够的资料
  284. const birthDate = userInfo.birthDate || userInfo.birth_date
  285. const hasProfile = birthDate || userInfo.star || userInfo.animal
  286. if (!hasProfile) {
  287. // 返回默认数据并提示完善资料
  288. return {
  289. hasProfile: false,
  290. message: '完善个人资料后可查看专属运势',
  291. loveFortune: [
  292. { label: '桃花运', icon: '🌸', score: 75, color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)' },
  293. { label: '爱情指数', icon: '💕', score: 70, color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)' },
  294. { label: '魅力值', icon: '✨', score: 80, color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)' }
  295. ],
  296. singleIndex: {
  297. score: 75,
  298. level: '良好',
  299. levelColor: 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)',
  300. tips: [
  301. { icon: '📅', text: '最佳约会日:周六' },
  302. { icon: '⏰', text: '最佳时间:下午2-5点' },
  303. { icon: '📍', text: '推荐地点:咖啡厅、公园' }
  304. ]
  305. },
  306. luckyRecommend: [
  307. { label: '幸运色', icon: '🎨', value: '粉红色', bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)' },
  308. { label: '幸运数字', icon: '🔢', value: '7', bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)' },
  309. { label: '约会建议', icon: '💡', value: '浪漫晚餐', bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)' },
  310. { label: '穿搭风格', icon: '👗', value: '清新甜美', bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)' }
  311. ]
  312. }
  313. }
  314. return {
  315. hasProfile: true,
  316. constellation: userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null),
  317. zodiac: userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null),
  318. loveFortune: calculateLoveFortune(userInfo),
  319. singleIndex: calculateSingleIndex(userInfo),
  320. luckyRecommend: calculateLuckyRecommend(userInfo)
  321. }
  322. }
  323. export default {
  324. getConstellationByBirthday,
  325. calculateLoveFortune,
  326. calculateSingleIndex,
  327. calculateLuckyRecommend,
  328. getFullFortuneData,
  329. CONSTELLATIONS
  330. }