|
|
@@ -0,0 +1,374 @@
|
|
|
+/**
|
|
|
+ * 运势计算工具类
|
|
|
+ * 基于用户资料(生日、星座、生肖)计算婚恋运势、脱单指数、幸运推荐
|
|
|
+ */
|
|
|
+
|
|
|
+import { getZodiacByBirthday, getZodiacInfo } from './zodiac.js'
|
|
|
+
|
|
|
+// 星座数据
|
|
|
+const CONSTELLATIONS = {
|
|
|
+ '白羊座': { element: '火', startDate: '03-21', endDate: '04-19', luckyColors: ['红色', '橙色'], luckyNumbers: [1, 9], personality: '热情冲动' },
|
|
|
+ '金牛座': { element: '土', startDate: '04-20', endDate: '05-20', luckyColors: ['绿色', '粉色'], luckyNumbers: [2, 6], personality: '稳重踏实' },
|
|
|
+ '双子座': { element: '风', startDate: '05-21', endDate: '06-21', luckyColors: ['黄色', '蓝色'], luckyNumbers: [3, 5], personality: '机智多变' },
|
|
|
+ '巨蟹座': { element: '水', startDate: '06-22', endDate: '07-22', luckyColors: ['白色', '银色'], luckyNumbers: [2, 7], personality: '温柔体贴' },
|
|
|
+ '狮子座': { element: '火', startDate: '07-23', endDate: '08-22', luckyColors: ['金色', '橙色'], luckyNumbers: [1, 5], personality: '自信大方' },
|
|
|
+ '处女座': { element: '土', startDate: '08-23', endDate: '09-22', luckyColors: ['灰色', '米色'], luckyNumbers: [4, 8], personality: '细心完美' },
|
|
|
+ '天秤座': { element: '风', startDate: '09-23', endDate: '10-23', luckyColors: ['粉色', '蓝色'], luckyNumbers: [6, 9], personality: '优雅和谐' },
|
|
|
+ '天蝎座': { element: '水', startDate: '10-24', endDate: '11-22', luckyColors: ['深红', '黑色'], luckyNumbers: [3, 9], personality: '神秘深情' },
|
|
|
+ '射手座': { element: '火', startDate: '11-23', endDate: '12-21', luckyColors: ['紫色', '蓝色'], luckyNumbers: [3, 7], personality: '乐观自由' },
|
|
|
+ '摩羯座': { element: '土', startDate: '12-22', endDate: '01-19', luckyColors: ['黑色', '深蓝'], luckyNumbers: [4, 8], personality: '务实稳重' },
|
|
|
+ '水瓶座': { element: '风', startDate: '01-20', endDate: '02-18', luckyColors: ['蓝色', '银色'], luckyNumbers: [4, 7], personality: '独立创新' },
|
|
|
+ '双鱼座': { element: '水', startDate: '02-19', endDate: '03-20', luckyColors: ['海蓝', '紫色'], luckyNumbers: [3, 9], personality: '浪漫敏感' }
|
|
|
+}
|
|
|
+
|
|
|
+// 约会建议库
|
|
|
+const DATE_SUGGESTIONS = [
|
|
|
+ '浪漫晚餐', '咖啡约会', '电影之夜', '公园散步', '逛街购物',
|
|
|
+ '看展览', '户外野餐', '游乐园', '密室逃脱', '烘焙体验',
|
|
|
+ '书店约会', '音乐会', '运动健身', '美食探店', '手工DIY'
|
|
|
+]
|
|
|
+
|
|
|
+// 穿搭风格库
|
|
|
+const STYLE_SUGGESTIONS = [
|
|
|
+ '清新甜美', '优雅知性', '休闲运动', '简约大方', '时尚潮流',
|
|
|
+ '温柔淑女', '帅气中性', '复古文艺', '可爱减龄', '成熟稳重'
|
|
|
+]
|
|
|
+
|
|
|
+// 幸运色库
|
|
|
+const LUCKY_COLORS = [
|
|
|
+ '粉红色', '天蓝色', '薄荷绿', '珊瑚橙', '薰衣草紫',
|
|
|
+ '米白色', '浅黄色', '玫瑰红', '湖蓝色', '杏色'
|
|
|
+]
|
|
|
+
|
|
|
+/**
|
|
|
+ * 根据生日获取星座
|
|
|
+ * @param {String} birthday - 生日,格式:YYYY-MM-DD
|
|
|
+ * @returns {String} 星座名称
|
|
|
+ */
|
|
|
+export function getConstellationByBirthday(birthday) {
|
|
|
+ if (!birthday) return null
|
|
|
+
|
|
|
+ const parts = birthday.split('-')
|
|
|
+ const month = parseInt(parts[1])
|
|
|
+ const day = parseInt(parts[2])
|
|
|
+ const mmdd = `${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
|
|
+
|
|
|
+ for (const [name, info] of Object.entries(CONSTELLATIONS)) {
|
|
|
+ const start = info.startDate
|
|
|
+ const end = info.endDate
|
|
|
+
|
|
|
+ // 处理跨年的星座(摩羯座)
|
|
|
+ if (start > end) {
|
|
|
+ if (mmdd >= start || mmdd <= end) return name
|
|
|
+ } else {
|
|
|
+ if (mmdd >= start && mmdd <= end) return name
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '未知'
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取基于日期的种子值(确保同一天结果一致)
|
|
|
+ * @param {Date} date - 日期
|
|
|
+ * @param {String} salt - 盐值(用于区分不同计算)
|
|
|
+ * @returns {Number} 种子值
|
|
|
+ */
|
|
|
+function getDaySeed(date, salt = '') {
|
|
|
+ const dateStr = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}-${salt}`
|
|
|
+ let hash = 0
|
|
|
+ for (let i = 0; i < dateStr.length; i++) {
|
|
|
+ const char = dateStr.charCodeAt(i)
|
|
|
+ hash = ((hash << 5) - hash) + char
|
|
|
+ hash = hash & hash
|
|
|
+ }
|
|
|
+ return Math.abs(hash)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 基于种子的伪随机数生成器
|
|
|
+ * @param {Number} seed - 种子值
|
|
|
+ * @param {Number} min - 最小值
|
|
|
+ * @param {Number} max - 最大值
|
|
|
+ * @returns {Number} 随机数
|
|
|
+ */
|
|
|
+function seededRandom(seed, min = 0, max = 100) {
|
|
|
+ const x = Math.sin(seed) * 10000
|
|
|
+ const random = x - Math.floor(x)
|
|
|
+ return Math.floor(random * (max - min + 1)) + min
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算用户的婚恋运势
|
|
|
+ * @param {Object} userInfo - 用户信息 { birthDate, star, animal, gender }
|
|
|
+ * @returns {Array} 运势数组
|
|
|
+ */
|
|
|
+export function calculateLoveFortune(userInfo) {
|
|
|
+ const today = new Date()
|
|
|
+ const birthDate = userInfo.birthDate || userInfo.birth_date
|
|
|
+
|
|
|
+ // 获取星座和生肖
|
|
|
+ const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
|
|
|
+ const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
|
|
|
+
|
|
|
+ // 基础分数
|
|
|
+ let baseScore = 70
|
|
|
+
|
|
|
+ // 根据星座元素调整基础分
|
|
|
+ if (constellation && CONSTELLATIONS[constellation]) {
|
|
|
+ const element = CONSTELLATIONS[constellation].element
|
|
|
+ const dayOfWeek = today.getDay()
|
|
|
+
|
|
|
+ // 不同元素在不同日子有不同运势
|
|
|
+ if (element === '火' && (dayOfWeek === 2 || dayOfWeek === 4)) baseScore += 5
|
|
|
+ if (element === '土' && (dayOfWeek === 0 || dayOfWeek === 6)) baseScore += 5
|
|
|
+ if (element === '风' && (dayOfWeek === 1 || dayOfWeek === 3)) baseScore += 5
|
|
|
+ if (element === '水' && (dayOfWeek === 5)) baseScore += 5
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用日期种子确保同一天结果一致
|
|
|
+ const seed1 = getDaySeed(today, `${constellation}-peach`)
|
|
|
+ const seed2 = getDaySeed(today, `${zodiac}-love`)
|
|
|
+ const seed3 = getDaySeed(today, `${constellation}-charm`)
|
|
|
+
|
|
|
+ // 计算三项运势分数
|
|
|
+ const peachScore = Math.min(95, Math.max(60, baseScore + seededRandom(seed1, -10, 20)))
|
|
|
+ const loveScore = Math.min(95, Math.max(55, baseScore + seededRandom(seed2, -15, 18)))
|
|
|
+ const charmScore = Math.min(98, Math.max(65, baseScore + seededRandom(seed3, -5, 25)))
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ label: '桃花运',
|
|
|
+ icon: '🌸',
|
|
|
+ score: peachScore,
|
|
|
+ color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '爱情指数',
|
|
|
+ icon: '💕',
|
|
|
+ score: loveScore,
|
|
|
+ color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '魅力值',
|
|
|
+ icon: '✨',
|
|
|
+ score: charmScore,
|
|
|
+ color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算本周脱单指数
|
|
|
+ * @param {Object} userInfo - 用户信息
|
|
|
+ * @returns {Object} 脱单指数数据
|
|
|
+ */
|
|
|
+export function calculateSingleIndex(userInfo) {
|
|
|
+ const today = new Date()
|
|
|
+ const birthDate = userInfo.birthDate || userInfo.birth_date
|
|
|
+
|
|
|
+ // 获取星座和生肖
|
|
|
+ const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
|
|
|
+ const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
|
|
|
+
|
|
|
+ // 获取本周的周一作为种子基准
|
|
|
+ const weekStart = new Date(today)
|
|
|
+ weekStart.setDate(today.getDate() - today.getDay() + 1)
|
|
|
+
|
|
|
+ const seed = getDaySeed(weekStart, `${constellation}-${zodiac}-single`)
|
|
|
+
|
|
|
+ // 计算基础分数
|
|
|
+ let score = seededRandom(seed, 65, 95)
|
|
|
+
|
|
|
+ // 根据生肖特性微调
|
|
|
+ if (zodiac) {
|
|
|
+ const zodiacInfo = getZodiacInfo(zodiac)
|
|
|
+ if (zodiacInfo) {
|
|
|
+ // 社交型生肖加分
|
|
|
+ if (['鼠', '马', '猴', '猪'].includes(zodiac)) score += 3
|
|
|
+ // 内敛型生肖略减
|
|
|
+ if (['牛', '蛇', '羊'].includes(zodiac)) score -= 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ score = Math.min(98, Math.max(50, score))
|
|
|
+
|
|
|
+ // 确定等级
|
|
|
+ let level, levelColor
|
|
|
+ if (score >= 90) {
|
|
|
+ level = '极佳'
|
|
|
+ levelColor = 'linear-gradient(135deg, #FF6B6B 0%, #FF8E53 100%)'
|
|
|
+ } else if (score >= 80) {
|
|
|
+ level = '很好'
|
|
|
+ levelColor = 'linear-gradient(135deg, #4CAF50 0%, #8BC34A 100%)'
|
|
|
+ } else if (score >= 70) {
|
|
|
+ level = '良好'
|
|
|
+ levelColor = 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)'
|
|
|
+ } else if (score >= 60) {
|
|
|
+ level = '一般'
|
|
|
+ levelColor = 'linear-gradient(135deg, #FF9800 0%, #FFC107 100%)'
|
|
|
+ } else {
|
|
|
+ level = '需努力'
|
|
|
+ levelColor = 'linear-gradient(135deg, #9E9E9E 0%, #BDBDBD 100%)'
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算最佳约会日
|
|
|
+ const bestDaySeed = getDaySeed(weekStart, `${constellation}-bestday`)
|
|
|
+ const bestDayIndex = seededRandom(bestDaySeed, 0, 6)
|
|
|
+ const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
|
+ const bestDay = weekDays[bestDayIndex]
|
|
|
+
|
|
|
+ // 计算最佳时间段
|
|
|
+ const timeSeed = getDaySeed(weekStart, `${zodiac}-time`)
|
|
|
+ const timeSlots = ['上午10-12点', '下午2-5点', '傍晚5-7点', '晚上7-9点']
|
|
|
+ const bestTime = timeSlots[seededRandom(timeSeed, 0, 3)]
|
|
|
+
|
|
|
+ // 推荐地点
|
|
|
+ const placeSeed = getDaySeed(weekStart, `${constellation}-place`)
|
|
|
+ const places = [
|
|
|
+ '咖啡厅、书店', '公园、广场', '商场、电影院',
|
|
|
+ '餐厅、美食街', '展览馆、博物馆', '游乐园、景区'
|
|
|
+ ]
|
|
|
+ const bestPlace = places[seededRandom(placeSeed, 0, 5)]
|
|
|
+
|
|
|
+ return {
|
|
|
+ score,
|
|
|
+ level,
|
|
|
+ levelColor,
|
|
|
+ tips: [
|
|
|
+ { icon: '📅', text: `最佳约会日:${bestDay}` },
|
|
|
+ { icon: '⏰', text: `最佳时间:${bestTime}` },
|
|
|
+ { icon: '📍', text: `推荐地点:${bestPlace}` }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 计算今日幸运推荐
|
|
|
+ * @param {Object} userInfo - 用户信息
|
|
|
+ * @returns {Array} 幸运推荐数组
|
|
|
+ */
|
|
|
+export function calculateLuckyRecommend(userInfo) {
|
|
|
+ const today = new Date()
|
|
|
+ const birthDate = userInfo.birthDate || userInfo.birth_date
|
|
|
+
|
|
|
+ // 获取星座
|
|
|
+ const constellation = userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null)
|
|
|
+ const zodiac = userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null)
|
|
|
+
|
|
|
+ // 幸运色
|
|
|
+ let luckyColor = '粉红色'
|
|
|
+ if (constellation && CONSTELLATIONS[constellation]) {
|
|
|
+ const constellationColors = CONSTELLATIONS[constellation].luckyColors
|
|
|
+ const colorSeed = getDaySeed(today, `${constellation}-color`)
|
|
|
+ luckyColor = constellationColors[seededRandom(colorSeed, 0, constellationColors.length - 1)]
|
|
|
+ } else {
|
|
|
+ const colorSeed = getDaySeed(today, 'default-color')
|
|
|
+ luckyColor = LUCKY_COLORS[seededRandom(colorSeed, 0, LUCKY_COLORS.length - 1)]
|
|
|
+ }
|
|
|
+
|
|
|
+ // 幸运数字
|
|
|
+ let luckyNumber = '7'
|
|
|
+ if (constellation && CONSTELLATIONS[constellation]) {
|
|
|
+ const constellationNumbers = CONSTELLATIONS[constellation].luckyNumbers
|
|
|
+ const numberSeed = getDaySeed(today, `${constellation}-number`)
|
|
|
+ luckyNumber = constellationNumbers[seededRandom(numberSeed, 0, constellationNumbers.length - 1)].toString()
|
|
|
+ } else {
|
|
|
+ const numberSeed = getDaySeed(today, 'default-number')
|
|
|
+ luckyNumber = seededRandom(numberSeed, 1, 9).toString()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 约会建议
|
|
|
+ const dateSeed = getDaySeed(today, `${zodiac}-date`)
|
|
|
+ const dateSuggestion = DATE_SUGGESTIONS[seededRandom(dateSeed, 0, DATE_SUGGESTIONS.length - 1)]
|
|
|
+
|
|
|
+ // 穿搭风格
|
|
|
+ const styleSeed = getDaySeed(today, `${constellation}-style`)
|
|
|
+ const styleSuggestion = STYLE_SUGGESTIONS[seededRandom(styleSeed, 0, STYLE_SUGGESTIONS.length - 1)]
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ label: '幸运色',
|
|
|
+ icon: '🎨',
|
|
|
+ value: luckyColor,
|
|
|
+ bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '幸运数字',
|
|
|
+ icon: '🔢',
|
|
|
+ value: luckyNumber,
|
|
|
+ bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '约会建议',
|
|
|
+ icon: '💡',
|
|
|
+ value: dateSuggestion,
|
|
|
+ bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: '穿搭风格',
|
|
|
+ icon: '👗',
|
|
|
+ value: styleSuggestion,
|
|
|
+ bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取用户完整的运势数据
|
|
|
+ * @param {Object} userInfo - 用户信息
|
|
|
+ * @returns {Object} 完整运势数据
|
|
|
+ */
|
|
|
+export function getFullFortuneData(userInfo) {
|
|
|
+ // 检查用户是否有足够的资料
|
|
|
+ const birthDate = userInfo.birthDate || userInfo.birth_date
|
|
|
+ const hasProfile = birthDate || userInfo.star || userInfo.animal
|
|
|
+
|
|
|
+ if (!hasProfile) {
|
|
|
+ // 返回默认数据并提示完善资料
|
|
|
+ return {
|
|
|
+ hasProfile: false,
|
|
|
+ message: '完善个人资料后可查看专属运势',
|
|
|
+ loveFortune: [
|
|
|
+ { label: '桃花运', icon: '🌸', score: 75, color: 'linear-gradient(135deg, #FF6B9D 0%, #FFA5C6 100%)' },
|
|
|
+ { label: '爱情指数', icon: '💕', score: 70, color: 'linear-gradient(135deg, #E91E63 0%, #FF6B9D 100%)' },
|
|
|
+ { label: '魅力值', icon: '✨', score: 80, color: 'linear-gradient(135deg, #9C27B0 0%, #BA68C8 100%)' }
|
|
|
+ ],
|
|
|
+ singleIndex: {
|
|
|
+ score: 75,
|
|
|
+ level: '良好',
|
|
|
+ levelColor: 'linear-gradient(135deg, #2196F3 0%, #03A9F4 100%)',
|
|
|
+ tips: [
|
|
|
+ { icon: '📅', text: '最佳约会日:周六' },
|
|
|
+ { icon: '⏰', text: '最佳时间:下午2-5点' },
|
|
|
+ { icon: '📍', text: '推荐地点:咖啡厅、公园' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ luckyRecommend: [
|
|
|
+ { label: '幸运色', icon: '🎨', value: '粉红色', bgColor: 'linear-gradient(135deg, #FFB6C1 0%, #FFE4E1 100%)' },
|
|
|
+ { label: '幸运数字', icon: '🔢', value: '7', bgColor: 'linear-gradient(135deg, #9C27B0 0%, #CE93D8 100%)' },
|
|
|
+ { label: '约会建议', icon: '💡', value: '浪漫晚餐', bgColor: 'linear-gradient(135deg, #FF9800 0%, #FFB74D 100%)' },
|
|
|
+ { label: '穿搭风格', icon: '👗', value: '清新甜美', bgColor: 'linear-gradient(135deg, #4CAF50 0%, #81C784 100%)' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ hasProfile: true,
|
|
|
+ constellation: userInfo.star || (birthDate ? getConstellationByBirthday(birthDate) : null),
|
|
|
+ zodiac: userInfo.animal || (birthDate ? getZodiacByBirthday(birthDate) : null),
|
|
|
+ loveFortune: calculateLoveFortune(userInfo),
|
|
|
+ singleIndex: calculateSingleIndex(userInfo),
|
|
|
+ luckyRecommend: calculateLuckyRecommend(userInfo)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ getConstellationByBirthday,
|
|
|
+ calculateLoveFortune,
|
|
|
+ calculateSingleIndex,
|
|
|
+ calculateLuckyRecommend,
|
|
|
+ getFullFortuneData,
|
|
|
+ CONSTELLATIONS
|
|
|
+}
|