2950 lines
82 KiB
Vue
2950 lines
82 KiB
Vue
<template>
|
||
<view class="naming-detail">
|
||
<!-- Starry Background -->
|
||
<view class="starry-bg">
|
||
<view v-for="star in stars" :key="star.id" class="star" :style="{
|
||
top: star.top,
|
||
left: star.left,
|
||
width: star.size + 'px',
|
||
height: star.size + 'px',
|
||
animationDuration: star.duration + 's',
|
||
animationDelay: star.delay + 's',
|
||
}"></view>
|
||
<view class="glow-top"></view>
|
||
<view class="glow-bottom"></view>
|
||
</view>
|
||
|
||
<!-- Header -->
|
||
<view class="detail-header">
|
||
<view class="detail-header-back" @click="handleBack">
|
||
<text class="back-icon">‹</text>
|
||
<text class="back-text">返回</text>
|
||
</view>
|
||
<text class="detail-header-title">详解报告</text>
|
||
<view class="detail-header-download" @click="handleDownloadPdf">
|
||
<text class="detail-header-download-icon">↓</text>
|
||
<text class="detail-header-download-text">下载PDF</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Content -->
|
||
<scroll-view scroll-y class="detail-content">
|
||
<!-- 1. 总分卡片 -->
|
||
<view class="score-card clickable" @click="openScoreDetail">
|
||
<view class="score-corner score-corner-tl"></view>
|
||
<view class="score-corner score-corner-tr"></view>
|
||
<view class="score-corner score-corner-bl"></view>
|
||
<view class="score-corner score-corner-br"></view>
|
||
|
||
<view class="score-label">Total Score</view>
|
||
<view class="score-value-wrap">
|
||
<text class="score-value">{{ report.header.score }}</text>
|
||
<view class="score-ring"></view>
|
||
</view>
|
||
|
||
<view class="score-stars">
|
||
<text v-for="i in 5" :key="i" class="score-star" :class="{ active: i <= report.header.stars }">★</text>
|
||
</view>
|
||
|
||
<view class="score-divider"></view>
|
||
|
||
<text class="score-name">{{ report.header.name }}</text>
|
||
<text class="score-pinyin">{{ report.header.pinyin }}</text>
|
||
</view>
|
||
|
||
<!-- 2. 五行生克与八卦(放第一位) -->
|
||
<view v-if="hasDetailWuxingBagua" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">☰</text>
|
||
<text class="section-text">五行生克与八卦</text>
|
||
</view>
|
||
<view class="wuge-card clickable" @click="openWuxingBaguaDetail">
|
||
<text v-if="detailWuxingBaguaPreview[0]" class="info-card-title">{{ detailWuxingBaguaPreview[0] }}</text>
|
||
<text v-if="detailWuxingBaguaPreview[1]" class="info-card-text">{{ detailWuxingBaguaPreview[1] }}</text>
|
||
<text v-if="detailWuxingBaguaPreview[2]" class="info-card-text">{{ detailWuxingBaguaPreview[2] }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2a. 五行单字详解(用于“五行八卦”补全每个字) -->
|
||
<view v-if="hasCharDetail" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">字</text>
|
||
<text class="section-text">五行单字详解</text>
|
||
</view>
|
||
<view class="info-card clickable" @click="openCharDetail">
|
||
<text v-for="(it, idx) in charDetailPreview" :key="idx" class="info-card-text">{{ charDetailLine(it) }}</text>
|
||
<text v-if="charDetailItems.length > 2" class="info-card-text muted">查看更多…</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2b. 八字与姓名五行(喜用 · 生克 · 补益) -->
|
||
<view v-if="hasDetailBazi" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">⚖</text>
|
||
<text class="section-text">八字与姓名五行</text>
|
||
</view>
|
||
<view class="zodiac-card clickable" @click="openBaziNameFitDetail">
|
||
<view class="zodiac-header">
|
||
<view class="zodiac-info">
|
||
<text v-if="detailBaziLines[0]" class="info-card-text">{{ detailBaziLines[0] }}</text>
|
||
<text v-if="detailBaziLines[1]" class="info-card-text">{{ detailBaziLines[1] }}</text>
|
||
<text v-if="detailBaziLines[2]" class="info-card-text">{{ detailBaziLines[2] }}</text>
|
||
</view>
|
||
<text
|
||
v-if="detailBaziScoreText"
|
||
class="zodiac-score"
|
||
>{{ detailBaziScoreText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2c. 字义与生肖解析 -->
|
||
<view
|
||
v-if="report.meaningZodiac.meaning || report.meaningZodiac.judge || report.meaningZodiac.zodiac.value || report.meaningZodiac.zodiac.desc"
|
||
class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">📖</text>
|
||
<text class="section-text">字义与生肖解析</text>
|
||
</view>
|
||
|
||
<view class="meaning-card clickable" @click="openMeaningDetail">
|
||
<view class="meaning-quote">❝</view>
|
||
<text class="meaning-label">名字寓意</text>
|
||
<text class="meaning-text">{{ report.meaningZodiac.meaning }}</text>
|
||
<view class="meaning-judge">
|
||
<text class="meaning-judge-label">判断:</text>
|
||
<text class="meaning-judge-text">{{ report.meaningZodiac.judge }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="zodiac-card clickable" @click="openMeaningDetail">
|
||
<view class="zodiac-header">
|
||
<view class="zodiac-icon-wrap">
|
||
<text class="zodiac-icon">{{ report.meaningZodiac.zodiac.icon }}</text>
|
||
</view>
|
||
<view class="zodiac-info">
|
||
<text class="zodiac-label">{{ report.meaningZodiac.zodiac.label }}</text>
|
||
<text class="zodiac-value">{{ report.meaningZodiac.zodiac.value }}</text>
|
||
</view>
|
||
<text class="zodiac-score">{{ report.meaningZodiac.zodiac.scoreText }}</text>
|
||
</view>
|
||
<text class="zodiac-desc">
|
||
{{ report.meaningZodiac.zodiac.desc }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2d. 属相与名字合宜 -->
|
||
<view v-if="hasDetailZodiacSign" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">🐲</text>
|
||
<text class="section-text">属相与名字</text>
|
||
</view>
|
||
<view class="zodiac-card clickable" @click="openZodiacSignDetail">
|
||
<view class="zodiac-header">
|
||
<view class="zodiac-icon-wrap">
|
||
<text class="zodiac-icon">{{ detailZodiacIcon }}</text>
|
||
</view>
|
||
<view class="zodiac-info">
|
||
<text class="zodiac-label">生肖</text>
|
||
<text class="zodiac-value">{{ detailZodiacTitle }}</text>
|
||
<text v-if="detailZodiacHarmony" class="zodiac-desc">{{ detailZodiacHarmony }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 3. 家族与起名原理 -->
|
||
<view v-if="report.family.cards.length" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">👥</text>
|
||
<text class="section-text">家族与起名原理</text>
|
||
</view>
|
||
|
||
<view v-for="(card, idx) in report.family.cards" :key="idx" class="info-card clickable"
|
||
@click="openFamilyDetail">
|
||
<text class="info-card-title">{{ card.title }}</text>
|
||
<text class="info-card-text">{{ card.text }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 4. 笔画数理分析 -->
|
||
<view v-if="report.stroke.wuge.items.length || report.stroke.wuxing.items.length" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">#</text>
|
||
<text class="section-text">笔画数理分析</text>
|
||
</view>
|
||
|
||
<view class="wuge-card clickable" @click="openWugeDetail">
|
||
<view class="wuge-header">
|
||
<text class="wuge-label">三才五格配置</text>
|
||
<text class="wuge-badge">{{ report.stroke.wuge.badge }}</text>
|
||
</view>
|
||
|
||
<view v-for="(it, idx) in report.stroke.wuge.items" :key="idx" class="wuge-item">
|
||
<view class="wuge-item-header">
|
||
<text class="wuge-item-name" :class="it.primary ? 'wuge-item-name-primary' : ''">{{ it.name }}</text>
|
||
<text class="wuge-item-result" :class="it.primary ? 'wuge-item-result-primary' : ''">{{ it.result
|
||
}}</text>
|
||
</view>
|
||
<view class="wuge-bar">
|
||
<view class="wuge-bar-fill" :style="{ width: it.percent + '%', background: it.color }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 五行能量分布 -->
|
||
<view v-if="report.stroke.wuxing.items.length" class="wuxing-card clickable" @click="openWuxingDetail">
|
||
<text class="wuxing-title">{{ report.stroke.wuxing.title }}</text>
|
||
<view class="wuxing-list">
|
||
<view v-for="item in wuxingData" :key="item.label" class="wuxing-item">
|
||
<text class="wuxing-label">{{ item.label }}</text>
|
||
<view class="wuxing-bar">
|
||
<view class="wuxing-bar-fill" :style="{ width: item.value + '%', background: item.color }"></view>
|
||
</view>
|
||
<text class="wuxing-value">{{ item.value }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 5. 三才配置深度解析 -->
|
||
<view v-if="report.sancai.items.length || report.sancai.configText || report.sancai.badge" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">⊞</text>
|
||
<text class="section-text">三才配置深度解析</text>
|
||
</view>
|
||
|
||
<view class="sancai-card clickable" @click="openSancaiDetail">
|
||
<view class="sancai-header">
|
||
<view class="sancai-config">
|
||
<text class="sancai-config-text">{{ report.sancai.configText }}</text>
|
||
<text class="sancai-config-label">{{ report.sancai.configLabel }}</text>
|
||
</view>
|
||
<text class="sancai-badge">{{ report.sancai.badge }}</text>
|
||
</view>
|
||
|
||
<view v-for="(it, idx) in report.sancai.items" :key="idx" class="sancai-item">
|
||
<text :class="it.primary ? 'sancai-item-label' : 'sancai-item-label-normal'">{{ it.label }}</text>
|
||
<text class="sancai-item-text">{{ it.text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 6. 潜在性格剖析 -->
|
||
<view v-if="report.personality.strengths.items.length || report.personality.challenges.items.length"
|
||
class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">👤</text>
|
||
<text class="section-text">潜在性格剖析</text>
|
||
</view>
|
||
|
||
<view class="personality-grid">
|
||
<view class="personality-card personality-card-strength clickable" @click="openPersonalityDetail('strength')">
|
||
<text class="personality-title personality-title-gold">{{ report.personality.strengths.title }}</text>
|
||
<view class="personality-list">
|
||
<view v-for="(t, idx) in report.personality.strengths.items" :key="idx" class="personality-item">
|
||
<view class="personality-dot personality-dot-gold"></view>
|
||
<text class="personality-text">{{ t }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="personality-card personality-card-challenge clickable"
|
||
@click="openPersonalityDetail('challenge')">
|
||
<text class="personality-title personality-title-red">{{ report.personality.challenges.title }}</text>
|
||
<view class="personality-list">
|
||
<view v-for="(t, idx) in report.personality.challenges.items" :key="idx" class="personality-item">
|
||
<view class="personality-dot personality-dot-red"></view>
|
||
<text class="personality-text">{{ t }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 7. 周易卦象 -->
|
||
<view v-if="report.gua.name || report.gua.desc || report.gua.lines.length" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">☯</text>
|
||
<text class="section-text">周易卦象</text>
|
||
</view>
|
||
|
||
<view class="gua-card clickable" @click="openGuaDetail">
|
||
<view class="gua-bg-text">{{ report.gua.bgText }}</view>
|
||
<view class="gua-content">
|
||
<view class="gua-symbol">
|
||
<view v-for="(ln, idx) in report.gua.lines" :key="idx">
|
||
<view v-if="ln === 'solid'" class="gua-line gua-line-solid"></view>
|
||
<view v-else class="gua-line gua-line-broken">
|
||
<view class="gua-line-part"></view>
|
||
<view class="gua-line-part"></view>
|
||
</view>
|
||
<view v-if="idx === 2" class="gua-spacer"></view>
|
||
</view>
|
||
</view>
|
||
<view class="gua-info">
|
||
<view class="gua-header">
|
||
<text class="gua-name">{{ report.gua.name }}</text>
|
||
<text class="gua-badge">{{ report.gua.badge }}</text>
|
||
</view>
|
||
<text class="gua-desc">{{ report.gua.desc }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 8. 开运锦囊 -->
|
||
<view v-if="report.lucky.cards.length || report.lucky.health.value || report.lucky.health.note" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">☀</text>
|
||
<text class="section-text">开运锦囊</text>
|
||
</view>
|
||
|
||
<view class="lucky-grid">
|
||
<view v-for="(c, idx) in report.lucky.cards" :key="idx" class="lucky-card clickable"
|
||
@click="openLuckyDetail(c.key)">
|
||
<view class="lucky-icon" :class="c.iconClass">{{ c.icon }}</view>
|
||
<view class="lucky-info">
|
||
<text class="lucky-label">{{ c.label }}</text>
|
||
<text class="lucky-value">{{ c.value }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="health-card clickable" @click="openHealthDetail">
|
||
<view class="health-icon">❤</view>
|
||
<view class="health-info">
|
||
<view class="health-header">
|
||
<text class="health-label">健康易感提示</text>
|
||
<text class="health-note">{{ report.lucky.health.note }}</text>
|
||
</view>
|
||
<text class="health-value">{{ report.lucky.health.value }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 9. 六维格局 -->
|
||
<SixDimensionRadar v-if="report.sixDimension.labels.length && report.sixDimension.values.length"
|
||
:labels="report.sixDimension.labels" :values="report.sixDimension.values"
|
||
:remark="report.sixDimension.remark" />
|
||
|
||
<!-- 10. 人生运程模拟 -->
|
||
<view v-if="report.fortune.labels.length || report.fortune.note" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">📈</text>
|
||
<text class="section-text">人生运程模拟</text>
|
||
</view>
|
||
|
||
<view class="fortune-card clickable" @click="openFortuneDetail">
|
||
<view class="fortune-chart">
|
||
<view class="fortune-line">
|
||
<view class="fortune-point fortune-point-1"></view>
|
||
<view class="fortune-point fortune-point-2"></view>
|
||
<view class="fortune-point fortune-point-3"></view>
|
||
<view class="fortune-point fortune-point-4"></view>
|
||
</view>
|
||
<view class="fortune-labels">
|
||
<view v-for="(lb, idx) in report.fortune.labels" :key="idx" class="fortune-label-item"
|
||
:class="lb.peak ? 'fortune-label-peak' : ''">
|
||
<text class="fortune-label-text">{{ lb.text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<text class="fortune-note">{{ report.fortune.note }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 11. 情感与社交运势 -->
|
||
<view v-if="report.emotion.title || report.emotion.desc || report.emotion.tags.length" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">💕</text>
|
||
<text class="section-text">情感与社交运势</text>
|
||
</view>
|
||
|
||
<view class="emotion-card clickable" @click="openEmotionDetail">
|
||
<view class="emotion-header">
|
||
<view class="emotion-icon-wrap">
|
||
<text class="emotion-icon">💗</text>
|
||
</view>
|
||
<view class="emotion-info">
|
||
<text class="emotion-title">{{ report.emotion.title }}</text>
|
||
<text class="emotion-desc">{{ report.emotion.desc }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="emotion-divider"></view>
|
||
<view class="emotion-tags-section">
|
||
<text class="emotion-tags-title">{{ report.emotion.tagsTitle }}</text>
|
||
<view class="emotion-tags">
|
||
<text v-for="(t, idx) in report.emotion.tags" :key="idx" class="emotion-tag"
|
||
:class="idx === 0 ? 'emotion-tag-primary' : ''">{{ t }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 12. 日常生活开运指南 -->
|
||
<view v-if="report.daily.cards.length || report.daily.avatar.items.length" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">✨</text>
|
||
<text class="section-text">日常生活开运指南</text>
|
||
</view>
|
||
|
||
<view class="daily-grid">
|
||
<view v-for="(c, idx) in report.daily.cards" :key="idx" class="daily-card clickable" @click="openDailyDetail">
|
||
<text class="daily-label">{{ c.title }}</text>
|
||
<template v-if="c.type === 'time'">
|
||
<view v-for="(t, i) in c.times" :key="i" class="daily-time-item">
|
||
<text class="daily-time-icon">{{ t.icon }}</text>
|
||
<text class="daily-time-text">{{ t.text }}</text>
|
||
</view>
|
||
</template>
|
||
<template v-else>
|
||
<view class="daily-items">
|
||
<view v-for="(it, i) in c.items" :key="i" :class="it.class">{{ it.text }}</view>
|
||
</view>
|
||
<text class="daily-tip">{{ c.tip }}</text>
|
||
</template>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 开运头像风格推荐 -->
|
||
<view class="avatar-card">
|
||
<text class="avatar-label">{{ report.daily.avatar.label }}</text>
|
||
<view class="avatar-list">
|
||
<view v-for="(it, idx) in report.daily.avatar.items" :key="idx" :class="it.class">
|
||
<text class="avatar-text">{{ it.text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 13. 诗词出处 -->
|
||
<view v-if="report.poetry.text" class="section">
|
||
<view class="section-title">
|
||
<text class="section-icon">📜</text>
|
||
<text class="section-text">诗词出处</text>
|
||
</view>
|
||
|
||
<view class="poetry-card">
|
||
<view class="poetry-quote">❝</view>
|
||
<text class="poetry-text">"{{ report.poetry.text }}"</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Action Buttons -->
|
||
<view class="action-buttons">
|
||
<!-- <view class="action-btn action-btn-share">
|
||
<text class="action-btn-text">生成海报</text>
|
||
</view> -->
|
||
<view class="action-btn action-btn-primary" @click="handleWealthAnalysis">
|
||
<text class="action-btn-text">财运解析</text>
|
||
</view>
|
||
<!-- <view class="action-btn action-btn-download">
|
||
<text class="action-btn-text">下载报告</text>
|
||
</view> -->
|
||
</view>
|
||
|
||
<!-- Footer -->
|
||
<view class="detail-footer">
|
||
<text class="footer-text">壹梵 · 致力于东方美学与易经智慧的传承</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view v-if="showDetailModal" class="detail-modal-overlay" @click.stop="closeDetailModal">
|
||
<view class="detail-modal" @click.stop="">
|
||
<view class="detail-modal-head">
|
||
<text class="detail-modal-title">{{ detailModalTitle }}</text>
|
||
<view class="detail-modal-close" @click.stop="closeDetailModal"><text class="detail-modal-close-text">×</text>
|
||
</view>
|
||
</view>
|
||
<scroll-view scroll-y class="detail-modal-body">
|
||
<view class="detail-modal-content">
|
||
<text v-for="(t, i) in detailModalLines" :key="i" class="detail-modal-text">{{ t }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed } from "vue";
|
||
import SixDimensionRadar from "../SixDimensionRadar.vue";
|
||
import { getWealthAnalysisByReportId } from "@/api/cai-yun";
|
||
import pdfBgUrl from "../../utils/pdf/background.png";
|
||
|
||
const props = defineProps<{
|
||
data: any | null;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
back: [];
|
||
wealthAnalysis: [any];
|
||
}>();
|
||
|
||
type DetailBlock = { title: string; lines: string[] };
|
||
|
||
type PersonalNamingReport = {
|
||
header: {
|
||
score: number;
|
||
stars: number;
|
||
name: string;
|
||
pinyin: string;
|
||
};
|
||
meaningZodiac: {
|
||
meaning: string;
|
||
judge: string;
|
||
zodiac: {
|
||
icon: string;
|
||
label: string;
|
||
value: string;
|
||
scoreText: string;
|
||
desc: string;
|
||
};
|
||
};
|
||
family: {
|
||
cards: Array<{ title: string; text: string }>;
|
||
};
|
||
stroke: {
|
||
wuge: {
|
||
badge: string;
|
||
items: Array<{
|
||
name: string;
|
||
result: string;
|
||
primary?: boolean;
|
||
percent: number;
|
||
color: string;
|
||
}>;
|
||
};
|
||
wuxing: {
|
||
title: string;
|
||
items: Array<{ label: string; value: number; color: string }>;
|
||
};
|
||
};
|
||
sancai: {
|
||
configText: string;
|
||
configLabel: string;
|
||
badge: string;
|
||
items: Array<{ label: string; text: string; primary?: boolean }>;
|
||
};
|
||
personality: {
|
||
strengths: { title: string; items: string[] };
|
||
challenges: { title: string; items: string[] };
|
||
};
|
||
gua: {
|
||
bgText: string;
|
||
name: string;
|
||
badge: string;
|
||
desc: string;
|
||
lines: Array<'solid' | 'broken'>;
|
||
};
|
||
lucky: {
|
||
cards: Array<{ key: 'color' | 'number' | 'industry' | 'constellation'; icon: string; iconClass: string; label: string; value: string }>;
|
||
health: { note: string; value: string };
|
||
};
|
||
sixDimension: { labels: string[]; values: number[]; remark: string };
|
||
fortune: { labels: Array<{ text: string; peak?: boolean }>; note: string };
|
||
emotion: { title: string; desc: string; tagsTitle: string; tags: string[] };
|
||
daily: {
|
||
cards: Array<
|
||
| { type: 'time'; title: string; times: Array<{ icon: string; text: string }> }
|
||
| { type: 'wear'; title: string; items: Array<{ text: string; class: string }>; tip: string }
|
||
>;
|
||
avatar: { label: string; items: Array<{ text: string; class: string }> };
|
||
};
|
||
poetry: { text: string };
|
||
/** 与测名详解同构:五行生克、八字与名、属相与名 */
|
||
detailModules?: {
|
||
bazi_name_fit?: any;
|
||
wuxing_bagua?: any;
|
||
zodiac_sign?: any;
|
||
/** 单字五行/阴阳/卦象(用于“五行八卦”里展示每个字) */
|
||
char_detail?: any;
|
||
};
|
||
details: {
|
||
score: DetailBlock;
|
||
meaning: DetailBlock;
|
||
family: DetailBlock;
|
||
wuge: DetailBlock;
|
||
wuxing: DetailBlock;
|
||
sancai: DetailBlock;
|
||
personality_strength: DetailBlock;
|
||
personality_challenge: DetailBlock;
|
||
gua: DetailBlock;
|
||
lucky: Record<'color' | 'number' | 'industry' | 'constellation', DetailBlock>;
|
||
health: DetailBlock;
|
||
fortune: DetailBlock;
|
||
emotion: DetailBlock;
|
||
daily: DetailBlock;
|
||
};
|
||
};
|
||
|
||
const normalizePersonalReport = (raw: any): PersonalNamingReport => {
|
||
if (raw && raw.header && raw.meaningZodiac && raw.stroke && raw.details) {
|
||
const legacy = raw as any;
|
||
return {
|
||
...legacy,
|
||
detailModules: {
|
||
bazi_name_fit: legacy?.bazi_name_fit,
|
||
wuxing_bagua: legacy?.wuxing_bagua,
|
||
zodiac_sign: legacy?.zodiac_sign,
|
||
char_detail: legacy?.char_detail,
|
||
},
|
||
family: {
|
||
cards: Array.isArray(legacy?.family?.cards) ? legacy.family.cards : [],
|
||
},
|
||
stroke: {
|
||
...legacy?.stroke,
|
||
wuge: {
|
||
...(legacy?.stroke?.wuge || {}),
|
||
items: Array.isArray(legacy?.stroke?.wuge?.items) ? legacy.stroke.wuge.items : [],
|
||
},
|
||
wuxing: {
|
||
...(legacy?.stroke?.wuxing || {}),
|
||
items: Array.isArray(legacy?.stroke?.wuxing?.items) ? legacy.stroke.wuxing.items : [],
|
||
},
|
||
},
|
||
sancai: {
|
||
...(legacy?.sancai || {}),
|
||
configText: String(legacy?.sancai?.configText || ''),
|
||
configLabel: String(legacy?.sancai?.configLabel || '(天 - 人 - 地)'),
|
||
badge: String(legacy?.sancai?.badge || ''),
|
||
items: Array.isArray(legacy?.sancai?.items) ? legacy.sancai.items.filter(Boolean) : [],
|
||
},
|
||
personality: {
|
||
strengths: {
|
||
...(legacy?.personality?.strengths || {}),
|
||
items: Array.isArray(legacy?.personality?.strengths?.items) ? legacy.personality.strengths.items : [],
|
||
},
|
||
challenges: {
|
||
...(legacy?.personality?.challenges || {}),
|
||
items: Array.isArray(legacy?.personality?.challenges?.items) ? legacy.personality.challenges.items : [],
|
||
},
|
||
},
|
||
gua: {
|
||
...(legacy?.gua || {}),
|
||
lines: Array.isArray(legacy?.gua?.lines) ? legacy.gua.lines : [],
|
||
},
|
||
lucky: {
|
||
...(legacy?.lucky || {}),
|
||
cards: Array.isArray(legacy?.lucky?.cards) ? legacy.lucky.cards : [],
|
||
health: legacy?.lucky?.health || { note: '', value: '' },
|
||
},
|
||
sixDimension: {
|
||
...(legacy?.sixDimension || {}),
|
||
labels: Array.isArray(legacy?.sixDimension?.labels) ? legacy.sixDimension.labels : [],
|
||
values: Array.isArray(legacy?.sixDimension?.values) ? legacy.sixDimension.values : [],
|
||
},
|
||
fortune: {
|
||
...(legacy?.fortune || {}),
|
||
labels: Array.isArray(legacy?.fortune?.labels) ? legacy.fortune.labels : [],
|
||
},
|
||
emotion: {
|
||
...(legacy?.emotion || {}),
|
||
tags: Array.isArray(legacy?.emotion?.tags) ? legacy.emotion.tags : [],
|
||
},
|
||
daily: {
|
||
cards: Array.isArray(legacy?.daily?.cards) ? legacy.daily.cards : [],
|
||
avatar: {
|
||
label: String(legacy?.daily?.avatar?.label || ''),
|
||
items: Array.isArray(legacy?.daily?.avatar?.items) ? legacy.daily.avatar.items : [],
|
||
},
|
||
},
|
||
} as PersonalNamingReport;
|
||
}
|
||
|
||
const name = String(raw?.name || '');
|
||
const pinyin = String(raw?.pinyin || '');
|
||
const score = Number(raw?.total_score ?? raw?.score ?? 0);
|
||
const stars = Number(raw?.star_rating ?? 0);
|
||
const meaning = String(raw?.name_meaning ?? raw?.meaning ?? '');
|
||
const poetrySource = String(raw?.poetry_source ?? raw?.source ?? '');
|
||
|
||
const toLines = (v: any): string[] => {
|
||
if (typeof v !== 'string') return [];
|
||
const s = v.trim();
|
||
if (!s) return [];
|
||
return s.split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
|
||
};
|
||
|
||
const zodiacAnalysis = String(raw?.zodiac_analysis ?? '');
|
||
const wuxingAnalysis = String(raw?.wuxing_analysis ?? '');
|
||
const sancaiAnalysis = String(raw?.sancai_analysis ?? '');
|
||
const strokeAnalysis = String(raw?.stroke_analysis ?? '');
|
||
|
||
const sixDim = raw?.six_dimension;
|
||
const sixLabels = sixDim && typeof sixDim === 'object'
|
||
? Object.keys(sixDim)
|
||
: [];
|
||
const sixValues = sixDim && typeof sixDim === 'object'
|
||
? Object.values(sixDim).map((n: any) => Number(n) || 0)
|
||
: [];
|
||
|
||
const derivedStars = stars || (score ? Math.min(5, Math.max(1, Math.round(score / 20))) : 0);
|
||
|
||
return {
|
||
header: {
|
||
score: Number.isFinite(score) ? score : 0,
|
||
stars: Number.isFinite(derivedStars) ? derivedStars : 0,
|
||
name,
|
||
pinyin,
|
||
},
|
||
meaningZodiac: {
|
||
meaning,
|
||
judge: zodiacAnalysis,
|
||
zodiac: {
|
||
icon: '',
|
||
label: '生肖适配度',
|
||
value: '',
|
||
scoreText: '',
|
||
desc: zodiacAnalysis,
|
||
},
|
||
},
|
||
family: {
|
||
cards: [],
|
||
},
|
||
stroke: {
|
||
wuge: {
|
||
badge: '',
|
||
items: [],
|
||
},
|
||
wuxing: {
|
||
title: '五行能量分布',
|
||
items: [],
|
||
},
|
||
},
|
||
sancai: {
|
||
configText: '',
|
||
configLabel: '(天 - 人 - 地)',
|
||
badge: '',
|
||
items: sancaiAnalysis ? [{ label: '', text: sancaiAnalysis, primary: true }] : [],
|
||
},
|
||
personality: {
|
||
strengths: { title: '', items: [] },
|
||
challenges: { title: '', items: [] },
|
||
},
|
||
gua: {
|
||
bgText: '',
|
||
name: '',
|
||
badge: '',
|
||
desc: '',
|
||
lines: [],
|
||
},
|
||
lucky: {
|
||
cards: [],
|
||
health: { note: '', value: '' },
|
||
},
|
||
sixDimension: {
|
||
labels: sixLabels,
|
||
values: sixValues,
|
||
remark: '',
|
||
},
|
||
fortune: {
|
||
labels: [],
|
||
note: '',
|
||
},
|
||
emotion: {
|
||
title: '',
|
||
desc: '',
|
||
tagsTitle: '',
|
||
tags: [],
|
||
},
|
||
daily: {
|
||
cards: [],
|
||
avatar: { label: '', items: [] },
|
||
},
|
||
poetry: { text: poetrySource },
|
||
detailModules: {
|
||
bazi_name_fit: raw?.bazi_name_fit,
|
||
wuxing_bagua: raw?.wuxing_bagua,
|
||
zodiac_sign: raw?.zodiac_sign,
|
||
char_detail: raw?.char_detail,
|
||
},
|
||
details: {
|
||
score: {
|
||
title: '总分详解',
|
||
lines: [
|
||
name ? `姓名:${name}` : '',
|
||
pinyin ? `拼音:${pinyin}` : '',
|
||
score ? `综合评分:${score}分` : '',
|
||
].filter(Boolean),
|
||
},
|
||
meaning: {
|
||
title: '字义与生肖详解',
|
||
lines: [
|
||
meaning ? `名字寓意:${meaning}` : '',
|
||
...(raw?.zodiac_sign?.name_harmony
|
||
? [`属相与名字:${String(raw.zodiac_sign.name_harmony).trim()}`]
|
||
: []),
|
||
...toLines(zodiacAnalysis),
|
||
].filter(Boolean),
|
||
},
|
||
family: {
|
||
title: '家族与起名原理详解',
|
||
lines: [],
|
||
},
|
||
wuge: {
|
||
title: '三才五格配置详解',
|
||
lines: toLines(strokeAnalysis),
|
||
},
|
||
wuxing: {
|
||
title: '五行能量分布详解',
|
||
lines: [
|
||
...toLines(wuxingAnalysis),
|
||
...(raw?.wuxing_bagua?.mutual_sketch ? [String(raw.wuxing_bagua.mutual_sketch).trim()] : []),
|
||
...(raw?.wuxing_bagua?.summary ? [String(raw.wuxing_bagua.summary).trim()] : []),
|
||
].filter(Boolean),
|
||
},
|
||
sancai: {
|
||
title: '三才配置深度解析',
|
||
lines: toLines(sancaiAnalysis),
|
||
},
|
||
personality_strength: {
|
||
title: '优势性格详解',
|
||
lines: [],
|
||
},
|
||
personality_challenge: {
|
||
title: '潜在盲点详解',
|
||
lines: [],
|
||
},
|
||
gua: {
|
||
title: '周易卦象详解',
|
||
lines: [],
|
||
},
|
||
lucky: {
|
||
color: { title: '幸运色详解', lines: [] },
|
||
number: { title: '幸运数详解', lines: [] },
|
||
industry: { title: '建议行业详解', lines: [] },
|
||
constellation: { title: '星座详解', lines: [] },
|
||
},
|
||
health: {
|
||
title: '健康开运详解',
|
||
lines: [],
|
||
},
|
||
fortune: {
|
||
title: '人生运程模拟详解',
|
||
lines: [],
|
||
},
|
||
emotion: {
|
||
title: '情感与社交运势详解',
|
||
lines: [],
|
||
},
|
||
daily: {
|
||
title: '日常生活开运指南详解',
|
||
lines: [],
|
||
},
|
||
},
|
||
};
|
||
};
|
||
|
||
const rawData = computed(() => props.data);
|
||
|
||
const report = computed<PersonalNamingReport>(() => normalizePersonalReport(rawData.value));
|
||
|
||
const arr = (v: any) => (Array.isArray(v) ? v : []);
|
||
const toStr = (v: any) => String(v ?? "").trim();
|
||
|
||
const charDetailItems = computed(() => {
|
||
const x = report.value.detailModules?.char_detail;
|
||
const items = Array.isArray(x?.items) ? x.items : [];
|
||
return items;
|
||
});
|
||
const hasCharDetail = computed(() => charDetailItems.value.length > 0);
|
||
const charDetailPreview = computed(() => charDetailItems.value.slice(0, 2));
|
||
|
||
function charDetailLine(it: any): string {
|
||
const char = toStr(it?.char);
|
||
const element = toStr(it?.element);
|
||
const yinYang = toStr(it?.yin_yang_element) || toStr(it?.yin_yang);
|
||
const trigram = [toStr(it?.bagua_trigram_symbol), toStr(it?.bagua_trigram)].filter(Boolean).join(' ');
|
||
const parts = [char, element, yinYang, trigram ? `卦:${trigram}` : ''].filter(Boolean);
|
||
return parts.join(' · ');
|
||
}
|
||
|
||
function flattenDetailNodes(nodes: any[]): string[] {
|
||
const out: string[] = [];
|
||
for (const n of nodes) {
|
||
if (!n || typeof n !== "object") continue;
|
||
if (n.type === "text" && toStr(n.text)) out.push(toStr(n.text));
|
||
if (n.type === "list") {
|
||
for (const item of arr(n.items)) {
|
||
const s = toStr(item);
|
||
if (s) out.push(`· ${s}`);
|
||
}
|
||
}
|
||
if (n.type === "kv") {
|
||
for (const item of arr(n.items)) {
|
||
const lab = toStr(item?.label);
|
||
const val = toStr(item?.value);
|
||
if (lab || val) out.push(`${lab}:${val}`);
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function linesFromCharDetail(x: any): string[] {
|
||
if (!x || typeof x !== "object") return [];
|
||
const lines: string[] = [];
|
||
const items = Array.isArray((x as any).items) ? (x as any).items : [];
|
||
for (const it of items) {
|
||
const head = charDetailLine(it);
|
||
if (head) lines.push(`【${head}】`);
|
||
|
||
const note = toStr(it?.note);
|
||
if (note) lines.push(note);
|
||
|
||
const nodeLines = flattenDetailNodes(arr(it?.details?.nodes));
|
||
nodeLines.forEach((l) => lines.push(l));
|
||
|
||
const hexName = toStr(it?.hexagram_name);
|
||
const hexCodeRaw = it?.hexagram_code;
|
||
const hexCode = Number(hexCodeRaw);
|
||
if (hexName || Number.isFinite(hexCode)) {
|
||
lines.push(
|
||
[
|
||
'卦象:',
|
||
hexName || '',
|
||
Number.isFinite(hexCode) ? `(${hexCode})` : '',
|
||
].join('')
|
||
);
|
||
}
|
||
|
||
lines.push('');
|
||
}
|
||
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
||
|
||
const summaryNodes = flattenDetailNodes(arr((x as any)?.details?.nodes));
|
||
if (summaryNodes.length) {
|
||
if (lines.length) lines.push('');
|
||
summaryNodes.forEach((l) => lines.push(l));
|
||
}
|
||
return lines.filter((l) => l !== undefined && l !== null).map((l) => String(l));
|
||
}
|
||
|
||
function linesFromBazi(b: any): string[] {
|
||
if (!b || typeof b !== "object") return [];
|
||
const lines: string[] = [];
|
||
const xy = toStr(b.xiyongshen);
|
||
const profile = toStr(b.name_wuxing_profile);
|
||
const comp = toStr(b.complement_summary);
|
||
if (xy) lines.push(`喜用神:${xy}`);
|
||
if (profile) lines.push(`姓名五行:${profile}`);
|
||
if (comp) lines.push(comp);
|
||
const rawScore = b.fit_score;
|
||
const n = Number(rawScore);
|
||
if (rawScore !== undefined && rawScore !== null && rawScore !== "" && Number.isFinite(n)) {
|
||
lines.push(`与八字契合度:${n}`);
|
||
}
|
||
return lines.filter(Boolean);
|
||
}
|
||
|
||
function linesFromWuxingBagua(x: any): string[] {
|
||
if (!x || typeof x !== "object") return [];
|
||
const lines: string[] = [];
|
||
const w = toStr(x.wuxing_sketch);
|
||
const bg = toStr(x.bagua_profile);
|
||
const mut = toStr(x.mutual_sketch);
|
||
const sum = toStr(x.summary);
|
||
if (w) lines.push(`五行:${w}`);
|
||
if (bg) lines.push(`八卦:${bg}`);
|
||
if (mut) lines.push(`生克互助:${mut}`);
|
||
if (sum) lines.push(sum);
|
||
return lines.filter(Boolean);
|
||
}
|
||
|
||
function linesFromZodiac(z: any): string[] {
|
||
if (!z || typeof z !== "object") return [];
|
||
const lines: string[] = [];
|
||
const animal = toStr(z.animal);
|
||
const br = toStr(z.earthly_branch);
|
||
if (animal || br) lines.push(`属相:${animal}${br ? `(${br})` : ""}`);
|
||
const trait = toStr(z.trait_summary);
|
||
if (trait) lines.push(`生肖特性:${trait}`);
|
||
const harm = toStr(z.name_harmony);
|
||
if (harm) lines.push(`与名字:${harm}`);
|
||
return lines.filter(Boolean);
|
||
}
|
||
|
||
const hasDetailBazi = computed(() => linesFromBazi(report.value.detailModules?.bazi_name_fit).length > 0);
|
||
const hasDetailWuxingBagua = computed(() => linesFromWuxingBagua(report.value.detailModules?.wuxing_bagua).length > 0);
|
||
const hasDetailZodiacSign = computed(() => linesFromZodiac(report.value.detailModules?.zodiac_sign).length > 0);
|
||
|
||
const detailBaziLines = computed(() => {
|
||
const b = report.value.detailModules?.bazi_name_fit;
|
||
const full = linesFromBazi(b);
|
||
return full.slice(0, 3);
|
||
});
|
||
|
||
const detailBaziScoreText = computed(() => {
|
||
const b = report.value.detailModules?.bazi_name_fit;
|
||
if (!b) return "";
|
||
const n = Number(b.fit_score);
|
||
if (!Number.isFinite(n)) return "";
|
||
return `${n}`;
|
||
});
|
||
|
||
const detailWuxingBaguaPreview = computed(() => linesFromWuxingBagua(report.value.detailModules?.wuxing_bagua).slice(0, 3));
|
||
|
||
const detailZodiacIcon = computed(() => {
|
||
const z = report.value.detailModules?.zodiac_sign;
|
||
const icon = toStr(z?.animal_icon);
|
||
if (icon) return icon;
|
||
const a = toStr(z?.animal);
|
||
return a || "肖";
|
||
});
|
||
|
||
const detailZodiacTitle = computed(() => {
|
||
const z = report.value.detailModules?.zodiac_sign;
|
||
const animal = toStr(z?.animal);
|
||
const br = toStr(z?.earthly_branch);
|
||
if (animal && br) return `${animal} · ${br}`;
|
||
return animal || br || "属相";
|
||
});
|
||
|
||
const detailZodiacHarmony = computed(() => toStr(report.value.detailModules?.zodiac_sign?.name_harmony));
|
||
|
||
// 星星数据
|
||
const stars = ref(
|
||
Array.from({ length: 30 }).map((_, i) => ({
|
||
id: i,
|
||
top: `${Math.random() * 100}%`,
|
||
left: `${Math.random() * 100}%`,
|
||
size: Math.random() * 2 + 1,
|
||
duration: Math.random() * 3 + 2,
|
||
delay: Math.random() * 2,
|
||
}))
|
||
);
|
||
|
||
// 五行数据
|
||
const wuxingData = computed(() => report.value.stroke.wuxing.items);
|
||
|
||
const handleBack = () => {
|
||
emit("back");
|
||
};
|
||
|
||
const handleDownloadPdf = async () => {
|
||
if (typeof window === "undefined" || typeof document === "undefined") return;
|
||
|
||
uni.showLoading({ title: "正在生成PDF..." });
|
||
|
||
try {
|
||
const raw = rawData.value || {};
|
||
const name = String(report.value.header?.name || raw?.name || "").trim();
|
||
const score = Number(report.value.header?.score ?? raw?.total_score ?? raw?.score ?? 0) || 0;
|
||
const stars = Number(report.value.header?.stars ?? raw?.star_rating ?? 0) || 0;
|
||
|
||
const escapeHtml = (s: string) =>
|
||
s
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
|
||
const normalizeLines = (lines: any): string[] => {
|
||
if (Array.isArray(lines)) return lines.map((x) => String(x ?? "").trim()).filter(Boolean);
|
||
const single = String(lines ?? "").trim();
|
||
return single ? [single] : [];
|
||
};
|
||
const uniqueLines = (lines: string[]) => {
|
||
const seen = new Set<string>();
|
||
const out: string[] = [];
|
||
lines.forEach((line) => {
|
||
const k = line.trim();
|
||
if (!k || seen.has(k)) return;
|
||
seen.add(k);
|
||
out.push(k);
|
||
});
|
||
return out;
|
||
};
|
||
const noisyKeys = new Set(["details", "nodes", "type", "meta", "status", "code", "raw"]);
|
||
const flattenAnyToLines = (val: any, depth = 0): string[] => {
|
||
if (depth > 4 || val === null || val === undefined) return [];
|
||
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
||
const s = String(val).trim();
|
||
return s ? [s] : [];
|
||
}
|
||
if (Array.isArray(val)) {
|
||
return val.flatMap((it) => flattenAnyToLines(it, depth + 1));
|
||
}
|
||
if (typeof val === "object") {
|
||
const out: string[] = [];
|
||
Object.keys(val).forEach((k) => {
|
||
if (noisyKeys.has(k)) return;
|
||
const child = flattenAnyToLines((val as any)[k], depth + 1);
|
||
if (!child.length) return;
|
||
child.forEach((line) => out.push(`${k}:${line}`));
|
||
});
|
||
return out;
|
||
}
|
||
return [];
|
||
};
|
||
const mergeLines = (...groups: any[]) => uniqueLines(groups.flatMap((g) => normalizeLines(g)));
|
||
const charDetailLines = mergeLines(
|
||
linesFromCharDetail((raw as any)?.char_detail),
|
||
linesFromCharDetail(report.value.detailModules?.char_detail),
|
||
// 兼容老结构:没有 items 时仍尽量保留
|
||
charDetailItems.value.map(charDetailLine),
|
||
flattenDetailNodes(arr((raw as any)?.char_detail?.details?.nodes)),
|
||
flattenAnyToLines((raw as any)?.char_detail),
|
||
flattenAnyToLines(report.value.detailModules?.char_detail),
|
||
);
|
||
const rawLineMap: Record<string, string[]> = {
|
||
"分数": mergeLines(flattenAnyToLines((raw as any)?.header)),
|
||
"五行单字详解": charDetailLines,
|
||
"五行生克与八卦": mergeLines(flattenAnyToLines((raw as any)?.wuxing_bagua), flattenAnyToLines(report.value.detailModules?.wuxing_bagua)),
|
||
"八字与姓名五行": mergeLines(flattenAnyToLines((raw as any)?.bazi_name_fit), flattenAnyToLines(report.value.detailModules?.bazi_name_fit)),
|
||
"字义与生肖": mergeLines(flattenAnyToLines((raw as any)?.meaning_zodiac), flattenAnyToLines((raw as any)?.meaning), flattenAnyToLines((raw as any)?.zodiac_sign)),
|
||
"属相与名字": mergeLines(flattenAnyToLines((raw as any)?.zodiac_sign), flattenAnyToLines(report.value.detailModules?.zodiac_sign)),
|
||
"三才五格": mergeLines(flattenAnyToLines((raw as any)?.sancai), flattenAnyToLines((raw as any)?.strokes_wuge_sancai)),
|
||
"笔画数理": mergeLines(flattenAnyToLines((raw as any)?.stroke), flattenAnyToLines((raw as any)?.wuge)),
|
||
"五行能量分布": mergeLines(flattenAnyToLines((raw as any)?.wuxing)),
|
||
"周易卦象": mergeLines(flattenAnyToLines((raw as any)?.gua), flattenAnyToLines((raw as any)?.liuyao)),
|
||
"幸运建议": mergeLines(flattenAnyToLines((raw as any)?.lucky_numbers), flattenAnyToLines((raw as any)?.lucky_colors), flattenAnyToLines((raw as any)?.lucky_tips)),
|
||
"健康开运": mergeLines(flattenAnyToLines((raw as any)?.health)),
|
||
"人生运程": mergeLines(flattenAnyToLines((raw as any)?.fortune), flattenAnyToLines((raw as any)?.lifespan)),
|
||
"情感与社交": mergeLines(flattenAnyToLines((raw as any)?.emotion)),
|
||
"日常开运指南": mergeLines(flattenAnyToLines((raw as any)?.daily)),
|
||
"诗词出处": mergeLines(flattenAnyToLines((raw as any)?.poetry_source), flattenAnyToLines((raw as any)?.poetry)),
|
||
};
|
||
|
||
const moduleList: Array<{ title: string; lines: string[] }> = [];
|
||
const push = (title: string, lines: string[]) => {
|
||
const l = normalizeLines(lines);
|
||
if (!l.length) return;
|
||
moduleList.push({ title, lines: l });
|
||
};
|
||
|
||
// 主要模块(起名/改名详解)
|
||
push("五行生克与八卦", (() => {
|
||
const x = report.value.detailModules?.wuxing_bagua;
|
||
const nodeLines = flattenDetailNodes(arr(x?.details?.nodes));
|
||
return mergeLines(rawLineMap["五行生克与八卦"], nodeLines, linesFromWuxingBagua(x), flattenAnyToLines(x));
|
||
})());
|
||
push("五行单字详解", mergeLines(rawLineMap["五行单字详解"]));
|
||
push("八字与姓名五行", (() => {
|
||
const b = report.value.detailModules?.bazi_name_fit;
|
||
const nodeLines = flattenDetailNodes(arr(b?.details?.nodes));
|
||
return mergeLines(rawLineMap["八字与姓名五行"], nodeLines, linesFromBazi(b), flattenAnyToLines(b));
|
||
})());
|
||
push("字义与生肖", mergeLines(rawLineMap["字义与生肖"], report.value.details?.meaning?.lines || []));
|
||
push("属相与名字", (() => {
|
||
const z = report.value.detailModules?.zodiac_sign;
|
||
const nodeLines = flattenDetailNodes(arr(z?.details?.nodes));
|
||
return mergeLines(rawLineMap["属相与名字"], nodeLines, linesFromZodiac(z), flattenAnyToLines(z));
|
||
})());
|
||
push("三才五格", mergeLines(rawLineMap["三才五格"], report.value.details?.sancai?.lines || []));
|
||
push("笔画数理", mergeLines(rawLineMap["笔画数理"], report.value.details?.wuge?.lines || []));
|
||
push("五行能量分布", mergeLines(rawLineMap["五行能量分布"], report.value.details?.wuxing?.lines || []));
|
||
push("周易卦象", mergeLines(rawLineMap["周易卦象"], report.value.details?.gua?.lines || []));
|
||
push("幸运建议", mergeLines(rawLineMap["幸运建议"], [
|
||
...normalizeLines(report.value.details?.lucky?.color?.lines),
|
||
...normalizeLines(report.value.details?.lucky?.number?.lines),
|
||
...normalizeLines(report.value.details?.lucky?.industry?.lines),
|
||
...normalizeLines(report.value.details?.lucky?.constellation?.lines),
|
||
]));
|
||
push("健康开运", mergeLines(rawLineMap["健康开运"], report.value.details?.health?.lines || []));
|
||
push("人生运程", mergeLines(rawLineMap["人生运程"], report.value.details?.fortune?.lines || []));
|
||
push("情感与社交", mergeLines(rawLineMap["情感与社交"], report.value.details?.emotion?.lines || []));
|
||
push("日常开运指南", mergeLines(rawLineMap["日常开运指南"], report.value.details?.daily?.lines || []));
|
||
push("诗词出处", mergeLines(rawLineMap["诗词出处"], normalizeLines([report.value.poetry?.text])));
|
||
|
||
// 通用兜底:把接口新增但未纳入固定章节的字段补进 PDF,避免后端新增字段丢失
|
||
const consumedRawKeys = new Set([
|
||
"header",
|
||
"char_detail",
|
||
"wuxing_bagua",
|
||
"bazi_name_fit",
|
||
"meaning_zodiac",
|
||
"meaning",
|
||
"zodiac_sign",
|
||
"sancai",
|
||
"strokes_wuge_sancai",
|
||
"stroke",
|
||
"wuge",
|
||
"wuxing",
|
||
"gua",
|
||
"liuyao",
|
||
"lucky_numbers",
|
||
"lucky_colors",
|
||
"lucky_tips",
|
||
"health",
|
||
"fortune",
|
||
"lifespan",
|
||
"emotion",
|
||
"daily",
|
||
"poetry_source",
|
||
"poetry",
|
||
"details",
|
||
"detailModules",
|
||
"id",
|
||
"report_id",
|
||
"is_unlocked",
|
||
"unlock_price",
|
||
"locked",
|
||
"lock",
|
||
"price",
|
||
]);
|
||
const rawKeyTitleMap: Record<string, string> = {
|
||
lucky_numbers: "幸运数字",
|
||
lucky_colors: "幸运颜色",
|
||
career_plan: "事业规划",
|
||
liuyao: "六爻分析",
|
||
};
|
||
Object.keys(raw || {}).forEach((k) => {
|
||
if (consumedRawKeys.has(k)) return;
|
||
const v = (raw as any)[k];
|
||
const lines = mergeLines(
|
||
v && typeof v === "object" ? flattenDetailNodes(arr((v as any)?.details?.nodes)) : [],
|
||
flattenAnyToLines(v),
|
||
);
|
||
if (!lines.length) return;
|
||
const title = rawKeyTitleMap[k] || (/^[a-z0-9_]+$/i.test(k) ? "" : k);
|
||
push(title || "其他信息", lines);
|
||
});
|
||
|
||
// 兜底:若后端在根级还带了 detailModules 的 nodes,尽量补齐
|
||
const extraKeys = ["bazi_name_fit", "wuxing_bagua", "zodiac_sign"];
|
||
extraKeys.forEach((k) => {
|
||
const v = (raw as any)?.[k];
|
||
const lines = mergeLines(flattenDetailNodes(arr(v?.details?.nodes)), flattenAnyToLines(v));
|
||
if (!lines.length) return;
|
||
const titleMap: any = {
|
||
bazi_name_fit: "八字与姓名五行(详)",
|
||
wuxing_bagua: "五行生克与八卦(详)",
|
||
zodiac_sign: "属相与名字(详)",
|
||
};
|
||
push(titleMap[k] || "补充", lines);
|
||
});
|
||
|
||
if (!moduleList.length) {
|
||
uni.showToast({ title: "暂无可导出的报告内容", icon: "none" });
|
||
return;
|
||
}
|
||
|
||
const [{ jsPDF }, html2canvasMod] = await Promise.all([import("jspdf"), import("html2canvas")]);
|
||
const html2canvas = (html2canvasMod as any).default || (html2canvasMod as any);
|
||
|
||
const PAGE_W = 794;
|
||
const PAGE_H = 1123;
|
||
const MAX_LINES = 46;
|
||
const wrappedLineCost = (line: string) => {
|
||
const text = String(line || "").trim();
|
||
if (!text) return 1;
|
||
return Math.max(1, Math.ceil(text.length / 30));
|
||
};
|
||
const splitByLines = (lines: string[], max: number) => {
|
||
const res: string[][] = [];
|
||
let bucket: string[] = [];
|
||
let cost = 0;
|
||
lines.forEach((line) => {
|
||
const lc = wrappedLineCost(line);
|
||
if (bucket.length && cost + lc > max) {
|
||
res.push(bucket);
|
||
bucket = [];
|
||
cost = 0;
|
||
}
|
||
bucket.push(line);
|
||
cost += lc;
|
||
});
|
||
if (bucket.length) res.push(bucket);
|
||
return res.length ? res : [[]];
|
||
};
|
||
|
||
const styleId = "pdf-gen-mystic-style";
|
||
let style = document.getElementById(styleId) as HTMLStyleElement | null;
|
||
if (!style) {
|
||
style = document.createElement("style");
|
||
style.id = styleId;
|
||
document.head.appendChild(style);
|
||
}
|
||
style.textContent = `
|
||
.pdf-gen-page{position:absolute;width:${PAGE_W}px;height:${PAGE_H}px;background-size:cover;background-position:center;font-family:"Songti SC","SimSun","STSong","Noto Serif SC",serif;color:#f2e6d8;}
|
||
.pdf-gen-panel{position:absolute;left:48px;right:48px;top:116px;bottom:92px;background:rgba(10,10,15,.62);border:1px solid rgba(212,175,55,.38);border-radius:16px;padding:30px 28px;box-sizing:border-box;overflow:hidden;}
|
||
.pdf-cover-kicker{text-align:center;font-size:12px;letter-spacing:.28em;color:rgba(212,175,55,.72);margin-bottom:12px;}
|
||
.pdf-cover-title{text-align:center;font-size:25px;letter-spacing:.16em;font-weight:700;color:rgba(212,175,55,.95);margin-bottom:22px;}
|
||
.pdf-name{text-align:center;font-size:50px;font-weight:800;margin-bottom:14px;color:rgba(242,230,216,.98);text-shadow:0 0 18px rgba(212,175,55,.18);}
|
||
.pdf-score-row{display:flex;align-items:center;justify-content:center;gap:16px;margin-top:8px;}
|
||
.pdf-score{font-size:38px;font-weight:900;color:rgba(212,175,55,.98);}
|
||
.pdf-cover-divider{width:210px;height:1px;margin:22px auto 18px;background:linear-gradient(90deg,rgba(212,175,55,0),rgba(212,175,55,.7),rgba(212,175,55,0));}
|
||
.pdf-cover-seal{width:66px;height:66px;margin:26px auto 0;border-radius:50%;border:1px solid rgba(212,175,55,.34);box-shadow:inset 0 0 0 6px rgba(212,175,55,.05);display:flex;align-items:center;justify-content:center;color:rgba(212,175,55,.92);font-size:20px;}
|
||
.pdf-stars{display:flex;gap:8px;font-size:16px;color:rgba(212,175,55,.95);letter-spacing:.08em;}
|
||
.pdf-dir-title{font-size:20px;font-weight:800;letter-spacing:.14em;color:rgba(212,175,55,.95);margin-bottom:12px;}
|
||
.pdf-dir-kicker{font-size:11px;letter-spacing:.24em;color:rgba(212,175,55,.68);margin-bottom:8px;}
|
||
.pdf-dir-list{margin-top:6px;border-top:1px solid rgba(212,175,55,.14);}
|
||
.pdf-dir-item{display:flex;align-items:center;border-bottom:1px solid rgba(255,255,255,.08);min-height:42px;padding:0 4px;box-sizing:border-box;gap:8px;}
|
||
.pdf-dir-idx{width:68px;flex:0 0 68px;font-size:15px;color:rgba(212,175,55,.96);font-weight:800;}
|
||
.pdf-dir-name{flex:1;font-size:16px;color:rgba(232,232,232,.96);font-weight:700;}
|
||
.pdf-dir-dots{flex:1;border-bottom:1px dashed rgba(212,175,55,.34);height:0;transform:translateY(2px);}
|
||
.pdf-dir-page{width:62px;flex:0 0 62px;text-align:right;font-size:13px;color:rgba(226,214,188,.92);font-weight:700;border:1px solid rgba(212,175,55,.22);border-radius:999px;padding:2px 8px;box-sizing:border-box;background:rgba(212,175,55,.06);}
|
||
.pdf-chapter-bar{position:absolute;left:48px;right:48px;top:78px;height:48px;border-radius:12px;background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.24);display:flex;align-items:center;padding:0 22px;box-sizing:border-box;}
|
||
.pdf-chapter-no{font-size:16px;font-weight:900;color:rgba(212,175,55,.98);margin-right:14px;}
|
||
.pdf-chapter-title{font-size:16px;font-weight:800;color:rgba(242,230,216,.98);}
|
||
.pdf-flow-section{margin-bottom:10px;border-radius:10px;border:1px solid rgba(212,175,55,.16);background:rgba(11,16,38,.55);overflow:hidden;}
|
||
.pdf-flow-head{height:38px;display:flex;align-items:center;gap:10px;padding:0 14px;border-bottom:1px solid rgba(212,175,55,.16);background:linear-gradient(90deg,rgba(212,175,55,.12),rgba(212,175,55,.03));}
|
||
.pdf-flow-bullet{color:rgba(212,175,55,.96);font-size:15px;}
|
||
.pdf-flow-title{color:rgba(245,236,218,.98);font-size:16px;font-weight:800;letter-spacing:.05em;}
|
||
.pdf-flow-body{padding:10px 14px 12px;}
|
||
.pdf-flow-line{font-size:15px;line-height:1.72;color:rgba(228,230,238,.95);margin-bottom:2px;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;}
|
||
.pdf-page-footer{position:absolute;left:48px;right:48px;bottom:42px;height:24px;display:flex;align-items:center;justify-content:space-between;font-size:12px;color:rgba(210,210,210,.72);letter-spacing:.04em;}
|
||
.pdf-footer-line{position:absolute;left:48px;right:48px;bottom:70px;height:1px;background:linear-gradient(90deg,rgba(212,175,55,0),rgba(212,175,55,.45),rgba(212,175,55,0));}
|
||
`;
|
||
|
||
const wrapper = document.createElement("div");
|
||
wrapper.style.position = "fixed";
|
||
wrapper.style.left = "-100000px";
|
||
wrapper.style.top = "0";
|
||
wrapper.style.zIndex = "-1";
|
||
wrapper.style.width = "0";
|
||
wrapper.style.height = "0";
|
||
document.body.appendChild(wrapper);
|
||
|
||
const pages: HTMLElement[] = [];
|
||
const createPageEl = (idx: number) => {
|
||
const el = document.createElement("div");
|
||
el.className = "pdf-gen-page";
|
||
el.style.left = `${idx * 2}px`;
|
||
el.style.top = `${idx * 2}px`;
|
||
(el.style as any).backgroundImage = `url(${pdfBgUrl})`;
|
||
wrapper.appendChild(el);
|
||
pages.push(el);
|
||
return el;
|
||
};
|
||
|
||
const appendFooter = (el: HTMLElement, pageNo: number, label = "起名详解报告") => {
|
||
const footer = document.createElement("div");
|
||
footer.className = "pdf-page-footer";
|
||
footer.innerHTML = `<div>${escapeHtml(label)}</div><div>第 ${pageNo} 页</div>`;
|
||
const line = document.createElement("div");
|
||
line.className = "pdf-footer-line";
|
||
el.appendChild(line);
|
||
el.appendChild(footer);
|
||
};
|
||
|
||
// 封面
|
||
const coverEl = createPageEl(0);
|
||
coverEl.innerHTML = `
|
||
<div class="pdf-gen-panel">
|
||
<div class="pdf-cover-kicker">玄 · 名 · 天 · 成</div>
|
||
<div class="pdf-cover-title">起名详解报告</div>
|
||
<div class="pdf-name">${escapeHtml(name || "未命名")}</div>
|
||
<div style="text-align:center;color:rgba(226,226,226,0.92);font-size:14px;margin-bottom:18px;">易经数理 · 五行生克 · 三才五格</div>
|
||
<div class="pdf-cover-divider"></div>
|
||
<div class="pdf-score-row">
|
||
<div class="pdf-score">${escapeHtml(String(score || ""))}</div>
|
||
<div class="pdf-stars">${"★".repeat(Math.max(0, Math.min(5, stars)))}${"☆".repeat(Math.max(0, 5 - Math.max(0, Math.min(5, stars))))}</div>
|
||
</div>
|
||
<div class="pdf-cover-seal">名</div>
|
||
</div>
|
||
`;
|
||
appendFooter(coverEl, 1, "起名详解报告 · 封面");
|
||
|
||
// 目录
|
||
const dirEl = createPageEl(1);
|
||
|
||
// 分章分页:每章至少独占一页;超长章节自动续页
|
||
type ChapterPage = { chapterNo: number; title: string; lines: string[]; continued?: boolean };
|
||
const contentPages: ChapterPage[] = [];
|
||
const dirItems: Array<{ no: number; title: string; start: number; end: number }> = [];
|
||
let currentPageNo = 3;
|
||
|
||
moduleList.forEach((m, idx) => {
|
||
const chapterNo = idx + 1;
|
||
const chunks = splitByLines(m.lines, MAX_LINES);
|
||
const start = currentPageNo;
|
||
chunks.forEach((chunk, cIdx) => {
|
||
contentPages.push({
|
||
chapterNo,
|
||
title: m.title,
|
||
lines: chunk,
|
||
continued: cIdx > 0,
|
||
});
|
||
currentPageNo += 1;
|
||
});
|
||
const end = Math.max(start, currentPageNo - 1);
|
||
dirItems.push({ no: chapterNo, title: m.title, start, end });
|
||
});
|
||
|
||
dirEl.innerHTML = `
|
||
<div class="pdf-gen-panel">
|
||
<div class="pdf-dir-kicker">卷 一 · 纲 目</div>
|
||
<div class="pdf-dir-title">目录</div>
|
||
<div class="pdf-dir-list">
|
||
${dirItems
|
||
.map((it) => {
|
||
const pageText = it.start === it.end ? `${it.start}` : `${it.start}-${it.end}`;
|
||
return `<div class="pdf-dir-item">
|
||
<div class="pdf-dir-idx">第${it.no}章</div>
|
||
<div class="pdf-dir-name">${escapeHtml(it.title)}</div>
|
||
<div class="pdf-dir-dots"></div>
|
||
<div class="pdf-dir-page">${escapeHtml(pageText)}</div>
|
||
</div>`;
|
||
})
|
||
.join("")}
|
||
</div>
|
||
</div>
|
||
`;
|
||
appendFooter(dirEl, 2, "起名详解报告 · 目录");
|
||
|
||
// 正文页
|
||
contentPages.forEach((section, i) => {
|
||
const el = createPageEl(2 + i);
|
||
const titleText = `第${section.chapterNo}章 ${section.title}${section.continued ? "(续)" : ""}`;
|
||
const linesHtml = section.lines.map((ln) => `<div class="pdf-flow-line">${escapeHtml(ln || " ")}</div>`).join("");
|
||
const sectionsHtml = `<div class="pdf-flow-section">
|
||
<div class="pdf-flow-head">
|
||
<div class="pdf-flow-bullet">✧</div>
|
||
<div class="pdf-flow-title">${escapeHtml(titleText)}</div>
|
||
</div>
|
||
<div class="pdf-flow-body">${linesHtml}</div>
|
||
</div>`;
|
||
el.innerHTML = `
|
||
<div class="pdf-chapter-bar">
|
||
<div class="pdf-chapter-no">正文</div>
|
||
<div class="pdf-chapter-title">${escapeHtml(titleText)}</div>
|
||
</div>
|
||
<div class="pdf-gen-panel" style="top:138px;">${sectionsHtml}</div>
|
||
`;
|
||
appendFooter(el, 3 + i);
|
||
});
|
||
|
||
const doc = new jsPDF({ unit: "pt", format: "a4" });
|
||
const pageW = doc.internal.pageSize.getWidth();
|
||
const pageH = doc.internal.pageSize.getHeight();
|
||
// 提速关键:降低渲染倍率并使用 JPEG,通常可显著减少导出耗时
|
||
const renderScale = Math.min(1.45, Math.max(1.15, Number((window as any)?.devicePixelRatio || 1)));
|
||
const jpegQuality = 0.86;
|
||
|
||
for (let i = 0; i < pages.length; i++) {
|
||
const canvas = await html2canvas(pages[i], {
|
||
scale: renderScale,
|
||
useCORS: true,
|
||
allowTaint: true,
|
||
backgroundColor: "#0a0a0f",
|
||
logging: false,
|
||
});
|
||
const img = canvas.toDataURL("image/jpeg", jpegQuality);
|
||
if (i > 0) doc.addPage();
|
||
doc.addImage(img, "JPEG", 0, 0, pageW, pageH, undefined, "FAST");
|
||
}
|
||
|
||
wrapper.remove();
|
||
const safeName = name ? name.replace(/[\\/:*?\"<>|]/g, "_") : "起名详解";
|
||
doc.save(`${safeName}-起名详解.pdf`);
|
||
uni.showToast({ title: "PDF下载成功", icon: "success" });
|
||
} catch (e) {
|
||
console.error("生成PDF失败:", e);
|
||
uni.showToast({ title: "PDF下载失败", icon: "none" });
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
};
|
||
|
||
const handleWealthAnalysis = async () => {
|
||
|
||
const solutionId = Number(props.data?.id || 0);
|
||
console.log('props.data===>>>', props.data);
|
||
|
||
if (!solutionId) {
|
||
uni.showToast({ title: '方案ID不存在', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({ title: '加载中...' });
|
||
|
||
|
||
|
||
const reportId = props.data?.report_id;
|
||
|
||
if (!reportId) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '报告ID不存在', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
// 使用report_id获取财运解析数据
|
||
const wealthData = await getWealthAnalysisByReportId(reportId);
|
||
uni.hideLoading();
|
||
|
||
uni.setStorageSync('wealth_analysis_report_id', reportId);
|
||
emit('wealthAnalysis', { id: reportId, wealthData });
|
||
} catch (error: any) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: error?.msg || error?.message || '加载失败,请重试', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
// 详情弹窗
|
||
const showDetailModal = ref(false);
|
||
const detailModalTitle = ref('');
|
||
const detailModalLines = ref<string[]>([]);
|
||
|
||
const openDetailModal = (title: string, lines: string[]) => {
|
||
detailModalTitle.value = title;
|
||
detailModalLines.value = lines;
|
||
showDetailModal.value = true;
|
||
};
|
||
|
||
const closeDetailModal = () => {
|
||
showDetailModal.value = false;
|
||
detailModalTitle.value = '';
|
||
detailModalLines.value = [];
|
||
};
|
||
|
||
// 各模块点击事件
|
||
const openScoreDetail = () => {
|
||
const d = report.value.details.score;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openMeaningDetail = () => {
|
||
const d = report.value.details.meaning;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openBaziNameFitDetail = () => {
|
||
const b = report.value.detailModules?.bazi_name_fit;
|
||
const nodes = flattenDetailNodes(arr(b?.details?.nodes));
|
||
const lines = nodes.length ? nodes : linesFromBazi(b);
|
||
openDetailModal("八字与姓名五行详解", lines);
|
||
};
|
||
|
||
const openWuxingBaguaDetail = () => {
|
||
const x = report.value.detailModules?.wuxing_bagua;
|
||
const nodes = flattenDetailNodes(arr(x?.details?.nodes));
|
||
const charLines = charDetailItems.value.map(charDetailLine).filter(Boolean);
|
||
const baguaLines = nodes.length ? nodes : linesFromWuxingBagua(x);
|
||
const lines = [
|
||
...(charLines.length ? ['【五行单字详解 · 阴阳 · 卦象】', ...charLines, ''] : []),
|
||
...baguaLines,
|
||
];
|
||
openDetailModal("五行生克与八卦详解", lines);
|
||
};
|
||
|
||
const openCharDetail = () => {
|
||
const x = report.value.detailModules?.char_detail;
|
||
const lines = linesFromCharDetail(x);
|
||
openDetailModal("五行单字详解", lines.length ? lines : ['暂无数据']);
|
||
};
|
||
|
||
const openZodiacSignDetail = () => {
|
||
const z = report.value.detailModules?.zodiac_sign;
|
||
const nodes = flattenDetailNodes(arr(z?.details?.nodes));
|
||
const lines = nodes.length ? nodes : linesFromZodiac(z);
|
||
openDetailModal("属相与名字详解", lines);
|
||
};
|
||
|
||
const openFamilyDetail = () => {
|
||
const d = report.value.details.family;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openWugeDetail = () => {
|
||
const d = report.value.details.wuge;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openWuxingDetail = () => {
|
||
const d = report.value.details.wuxing;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openSancaiDetail = () => {
|
||
const d = report.value.details.sancai;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openPersonalityDetail = (type: 'strength' | 'challenge') => {
|
||
const d = type === 'strength'
|
||
? report.value.details.personality_strength
|
||
: report.value.details.personality_challenge;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openGuaDetail = () => {
|
||
const d = report.value.details.gua;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openLuckyDetail = (type: string) => {
|
||
const key = type as 'color' | 'number' | 'industry' | 'constellation';
|
||
const d = report.value.details.lucky[key];
|
||
if (d) openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openHealthDetail = () => {
|
||
const d = report.value.details.health;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openFortuneDetail = () => {
|
||
const d = report.value.details.fortune;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openEmotionDetail = () => {
|
||
const d = report.value.details.emotion;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
|
||
const openDailyDetail = () => {
|
||
const d = report.value.details.daily;
|
||
openDetailModal(d.title, d.lines);
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.naming-detail {
|
||
min-height: 100vh;
|
||
background: #0a0a0f;
|
||
position: relative;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
/* Starry Background */
|
||
.starry-bg {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
background: linear-gradient(to bottom, #050508, #10101a, #1a1a2e);
|
||
}
|
||
|
||
.star {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
background: #fff;
|
||
opacity: 0.2;
|
||
animation: twinkle ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes twinkle {
|
||
|
||
0%,
|
||
100% {
|
||
opacity: 0.1;
|
||
transform: scale(1);
|
||
}
|
||
|
||
50% {
|
||
opacity: 0.5;
|
||
transform: scale(1.2);
|
||
}
|
||
}
|
||
|
||
.glow-top {
|
||
position: absolute;
|
||
top: -10%;
|
||
left: -10%;
|
||
width: 50%;
|
||
height: 50%;
|
||
background: #2a3d5d;
|
||
opacity: 0.2;
|
||
filter: blur(100px);
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.glow-bottom {
|
||
position: absolute;
|
||
bottom: -10%;
|
||
right: -10%;
|
||
width: 50%;
|
||
height: 50%;
|
||
background: #9c2a2a;
|
||
opacity: 0.1;
|
||
filter: blur(100px);
|
||
border-radius: 50%;
|
||
}
|
||
|
||
/* Header */
|
||
.detail-header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 60rpx 32rpx 24rpx;
|
||
background: rgba(10, 10, 15, 0.8);
|
||
backdrop-filter: blur(10px);
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.detail-header-back {
|
||
display: flex;
|
||
align-items: center;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 24px;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.back-text {
|
||
font-size: 14px;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.detail-header-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
letter-spacing: 0.3em;
|
||
}
|
||
|
||
.detail-header-download {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 8rpx;
|
||
padding: 10rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
border: 1px solid rgba(212, 175, 55, 0.35);
|
||
background: rgba(212, 175, 55, 0.10);
|
||
color: rgba(255, 242, 210, 0.92);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.detail-header-download-icon {
|
||
font-size: 18px;
|
||
line-height: 1;
|
||
}
|
||
|
||
.detail-header-download-text {
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
/* Content */
|
||
.detail-content {
|
||
position: relative;
|
||
z-index: 10;
|
||
padding: 180rpx 32rpx 60rpx;
|
||
height: 100vh;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Score Card */
|
||
.score-card {
|
||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8rpx;
|
||
padding: 48rpx;
|
||
text-align: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
margin-bottom: 48rpx;
|
||
}
|
||
|
||
.score-corner {
|
||
position: absolute;
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-color: #d4af37;
|
||
border-style: solid;
|
||
}
|
||
|
||
.score-corner-tl {
|
||
top: 0;
|
||
left: 0;
|
||
border-width: 4rpx 0 0 4rpx;
|
||
}
|
||
|
||
.score-corner-tr {
|
||
top: 0;
|
||
right: 0;
|
||
border-width: 4rpx 4rpx 0 0;
|
||
}
|
||
|
||
.score-corner-bl {
|
||
bottom: 0;
|
||
left: 0;
|
||
border-width: 0 0 4rpx 4rpx;
|
||
}
|
||
|
||
.score-corner-br {
|
||
bottom: 0;
|
||
right: 0;
|
||
border-width: 0 4rpx 4rpx 0;
|
||
}
|
||
|
||
.score-label {
|
||
font-size: 10px;
|
||
color: #d4af37;
|
||
letter-spacing: 0.5em;
|
||
text-transform: uppercase;
|
||
opacity: 0.8;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.score-value-wrap {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 280rpx;
|
||
height: 280rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.score-value {
|
||
font-size: 72px;
|
||
font-weight: 700;
|
||
background: linear-gradient(to bottom, #d4af37, #8a6e1e);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
|
||
.score-ring {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 1px dashed rgba(212, 175, 55, 0.2);
|
||
border-radius: 50%;
|
||
animation: rotate 10s linear infinite;
|
||
}
|
||
|
||
@keyframes rotate {
|
||
from {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.score-stars {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.score-star {
|
||
font-size: 14px;
|
||
color: #5a5a5a;
|
||
}
|
||
|
||
.score-star.active {
|
||
color: #d4af37;
|
||
}
|
||
|
||
.score-divider {
|
||
height: 1px;
|
||
background: linear-gradient(to right, transparent, rgba(212, 175, 55, 0.3), transparent);
|
||
margin: 16rpx 0;
|
||
}
|
||
|
||
.score-name {
|
||
display: block;
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
letter-spacing: 0.2em;
|
||
margin-top: 16rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.score-pinyin {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Section */
|
||
.section {
|
||
margin-bottom: 48rpx;
|
||
}
|
||
|
||
.section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 24rpx;
|
||
padding-left: 8rpx;
|
||
}
|
||
|
||
.section-icon {
|
||
font-size: 16px;
|
||
color: #d4af37;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.section-text {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
/* Meaning Card */
|
||
.meaning-card {
|
||
background: #1a1a2e;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.meaning-quote {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 16rpx;
|
||
font-size: 48px;
|
||
color: #d4af37;
|
||
opacity: 0.1;
|
||
}
|
||
|
||
.meaning-label {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.meaning-text {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
line-height: 1.8;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.meaning-judge {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.meaning-judge-label {
|
||
font-size: 14px;
|
||
color: #d4af37;
|
||
}
|
||
|
||
.meaning-judge-text {
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
/* Zodiac Card */
|
||
.zodiac-card {
|
||
background: linear-gradient(to right, #16213e, #1a1a2e);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.zodiac-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.zodiac-icon-wrap {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: rgba(212, 175, 55, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.zodiac-icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.zodiac-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.zodiac-label {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.zodiac-value {
|
||
display: block;
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
.zodiac-score {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
}
|
||
|
||
.zodiac-desc {
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Info Card */
|
||
.info-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.info-card-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.info-card-text {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
/* Wuge Card */
|
||
.wuge-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.wuge-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.wuge-label {
|
||
font-size: 14px;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
.wuge-badge {
|
||
font-size: 12px;
|
||
color: #d4af37;
|
||
border: 1px solid #d4af37;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 4rpx;
|
||
}
|
||
|
||
.wuge-item {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.wuge-item-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.wuge-item-name {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.wuge-item-name-primary {
|
||
color: #e2e2e2;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.wuge-item-result {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.wuge-item-result-primary {
|
||
color: #d4af37;
|
||
}
|
||
|
||
.wuge-bar {
|
||
height: 8rpx;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wuge-bar-fill {
|
||
height: 100%;
|
||
border-radius: 4rpx;
|
||
transition: width 1s ease-out;
|
||
}
|
||
|
||
/* Wuxing Card */
|
||
.wuxing-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.wuxing-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
margin-bottom: 20rpx;
|
||
padding-left: 16rpx;
|
||
border-left: 4rpx solid #d4af37;
|
||
}
|
||
|
||
.wuxing-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.wuxing-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.wuxing-label {
|
||
width: 48rpx;
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.wuxing-bar {
|
||
flex: 1;
|
||
height: 8rpx;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
border-radius: 4rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.wuxing-bar-fill {
|
||
height: 100%;
|
||
border-radius: 4rpx;
|
||
transition: width 1.2s ease-out;
|
||
}
|
||
|
||
.wuxing-value {
|
||
width: 60rpx;
|
||
text-align: right;
|
||
font-size: 14px;
|
||
color: #e2e2e2;
|
||
font-family: monospace;
|
||
}
|
||
|
||
/* Sancai Card */
|
||
.sancai-card {
|
||
background: #1a1a2e;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.sancai-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.sancai-config {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.sancai-config-text {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
}
|
||
|
||
.sancai-config-label {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.sancai-badge {
|
||
font-size: 12px;
|
||
color: #e2e2e2;
|
||
background: rgba(212, 175, 55, 0.2);
|
||
border: 1px solid #d4af37;
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.sancai-item {
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.sancai-item-label {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #d4af37;
|
||
}
|
||
|
||
.sancai-item-label-normal {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
.sancai-item-text {
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Personality Grid */
|
||
.personality-grid {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.personality-card {
|
||
flex: 1;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.personality-title {
|
||
display: block;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.personality-title-gold {
|
||
color: #d4af37;
|
||
}
|
||
|
||
.personality-title-red {
|
||
color: #9c2a2a;
|
||
}
|
||
|
||
.personality-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.personality-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.personality-dot {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
border-radius: 50%;
|
||
margin-right: 12rpx;
|
||
}
|
||
|
||
.personality-dot-gold {
|
||
background: #d4af37;
|
||
}
|
||
|
||
.personality-dot-red {
|
||
background: #9c2a2a;
|
||
}
|
||
|
||
.personality-text {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
/* Gua Card */
|
||
.gua-card {
|
||
background: linear-gradient(to right, #1a1a2e, #16213e);
|
||
border: 1px solid rgba(212, 175, 55, 0.3);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.gua-bg-text {
|
||
position: absolute;
|
||
right: -20rpx;
|
||
top: -20rpx;
|
||
font-size: 120px;
|
||
color: rgba(255, 255, 255, 0.05);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.gua-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 40rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.gua-symbol {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
width: 120rpx;
|
||
}
|
||
|
||
.gua-line {
|
||
height: 8rpx;
|
||
}
|
||
|
||
.gua-line-solid {
|
||
background: #d4af37;
|
||
box-shadow: 0 0 8px #d4af37;
|
||
}
|
||
|
||
.gua-line-broken {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.gua-line-part {
|
||
width: 45%;
|
||
height: 8rpx;
|
||
background: rgba(212, 175, 55, 0.5);
|
||
}
|
||
|
||
.gua-spacer {
|
||
height: 8rpx;
|
||
}
|
||
|
||
.gua-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.gua-header {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 16rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.gua-name {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
.gua-badge {
|
||
font-size: 12px;
|
||
color: #9c2a2a;
|
||
border: 1px solid #9c2a2a;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 4rpx;
|
||
}
|
||
|
||
.gua-desc {
|
||
font-size: 14px;
|
||
color: #a0a0a0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Lucky Grid */
|
||
.lucky-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.lucky-card {
|
||
width: calc(50% - 8rpx);
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.lucky-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.lucky-icon-blue {
|
||
color: #60a5fa;
|
||
}
|
||
|
||
.lucky-icon-pink {
|
||
color: #f472b6;
|
||
}
|
||
|
||
.lucky-icon-green {
|
||
color: #4ade80;
|
||
}
|
||
|
||
.lucky-icon-yellow {
|
||
color: #facc15;
|
||
}
|
||
|
||
.lucky-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.lucky-label {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.lucky-value {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
/* Health Card */
|
||
.health-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.health-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
color: #f87171;
|
||
}
|
||
|
||
.health-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.health-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.health-label {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.health-note {
|
||
font-size: 12px;
|
||
color: #5a5a5a;
|
||
}
|
||
|
||
.health-value {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
/* Poetry Card */
|
||
.poetry-card {
|
||
background: rgba(226, 226, 226, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 40rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.poetry-quote {
|
||
position: absolute;
|
||
top: -16rpx;
|
||
left: -16rpx;
|
||
font-size: 80px;
|
||
color: rgba(255, 255, 255, 0.05);
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
.poetry-text {
|
||
display: block;
|
||
font-size: 16px;
|
||
color: #e2e2e2;
|
||
line-height: 2;
|
||
font-style: italic;
|
||
text-align: center;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Action Buttons */
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin: 48rpx 0;
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12rpx;
|
||
padding: 24rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.action-btn-share,
|
||
.action-btn-download {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.action-btn-primary {
|
||
background: rgba(212, 175, 55, 0.1);
|
||
border: 1px solid rgba(212, 175, 55, 0.5);
|
||
color: #d4af37;
|
||
box-shadow: 0 0 15px rgba(212, 175, 55, 0.15);
|
||
}
|
||
|
||
.action-btn-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.action-btn-text {
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
/* Fortune Card - 人生运程模拟 */
|
||
.fortune-card {
|
||
background: #1a1a2e;
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.fortune-chart {
|
||
height: 200rpx;
|
||
position: relative;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.fortune-line {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 40rpx;
|
||
background: linear-gradient(90deg,
|
||
transparent 5%,
|
||
rgba(212, 175, 55, 0.1) 15%,
|
||
rgba(212, 175, 55, 0.3) 30%,
|
||
rgba(212, 175, 55, 0.5) 35%,
|
||
rgba(212, 175, 55, 0.3) 50%,
|
||
rgba(212, 175, 55, 0.2) 70%,
|
||
transparent 95%);
|
||
clip-path: polygon(0% 80%, 25% 50%, 35% 15%, 50% 25%, 70% 35%, 100% 55%, 100% 100%, 0% 100%);
|
||
}
|
||
|
||
.fortune-point {
|
||
position: absolute;
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
background: #d4af37;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.fortune-point-1 {
|
||
left: 10%;
|
||
top: 70%;
|
||
}
|
||
|
||
.fortune-point-2 {
|
||
left: 35%;
|
||
top: 15%;
|
||
}
|
||
|
||
.fortune-point-3 {
|
||
left: 60%;
|
||
top: 30%;
|
||
}
|
||
|
||
.fortune-point-4 {
|
||
left: 90%;
|
||
top: 50%;
|
||
}
|
||
|
||
.fortune-labels {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.fortune-label-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.fortune-label-text {
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.fortune-label-peak .fortune-label-text {
|
||
color: #d4af37;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.fortune-note {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #5a5a5a;
|
||
text-align: center;
|
||
padding-top: 16rpx;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
/* Emotion Card - 情感与社交运势 */
|
||
.emotion-card {
|
||
background: #1a1a2e;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.emotion-header {
|
||
display: flex;
|
||
gap: 24rpx;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.emotion-icon-wrap {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50%;
|
||
background: rgba(236, 72, 153, 0.1);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.emotion-icon {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.emotion-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.emotion-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.emotion-desc {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.emotion-divider {
|
||
height: 1px;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
margin: 24rpx 0;
|
||
}
|
||
|
||
.emotion-tags-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.emotion-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.emotion-tag {
|
||
padding: 12rpx 20rpx;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 8rpx;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.emotion-tag-primary {
|
||
color: #d4af37;
|
||
border-color: rgba(212, 175, 55, 0.3);
|
||
}
|
||
|
||
/* Daily Grid - 日常生活开运指南 */
|
||
.daily-grid {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.daily-card {
|
||
flex: 1;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.daily-label {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.daily-time-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.daily-time-icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.daily-time-text {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #e2e2e2;
|
||
}
|
||
|
||
.daily-items {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.daily-item {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
}
|
||
|
||
.daily-item-red {
|
||
background: rgba(127, 29, 29, 0.5);
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
color: #fecaca;
|
||
}
|
||
|
||
.daily-item-yellow {
|
||
background: rgba(113, 63, 18, 0.5);
|
||
border: 1px solid rgba(234, 179, 8, 0.3);
|
||
color: #fef08a;
|
||
}
|
||
|
||
.daily-tip {
|
||
display: block;
|
||
font-size: 10px;
|
||
color: #5a5a5a;
|
||
}
|
||
|
||
/* Avatar Card - 开运头像风格推荐 */
|
||
.avatar-card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.avatar-label {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #a0a0a0;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.avatar-list {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.avatar-item {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.avatar-item-sunrise {
|
||
background: linear-gradient(135deg, #fb923c, #ef4444);
|
||
}
|
||
|
||
.avatar-item-calligraphy {
|
||
background: #2c2c2c;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.avatar-item-festive {
|
||
background: #fecaca;
|
||
}
|
||
|
||
.avatar-text {
|
||
font-size: 10px;
|
||
color: #fff;
|
||
}
|
||
|
||
.avatar-item-calligraphy .avatar-text {
|
||
color: #a0a0a0;
|
||
}
|
||
|
||
.avatar-item-festive .avatar-text {
|
||
color: #991b1b;
|
||
}
|
||
|
||
/* Footer */
|
||
.detail-footer {
|
||
text-align: center;
|
||
padding: 40rpx 0 80rpx;
|
||
}
|
||
|
||
.footer-text {
|
||
font-size: 10px;
|
||
color: #5a5a5a;
|
||
}
|
||
|
||
.clickable {
|
||
transition: opacity 0.15s ease;
|
||
}
|
||
|
||
.clickable:active {
|
||
opacity: 0.82;
|
||
}
|
||
|
||
.detail-modal-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 120;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 28rpx;
|
||
}
|
||
|
||
.detail-modal {
|
||
width: 100%;
|
||
max-width: 680rpx;
|
||
background: rgba(10, 10, 15, 0.98);
|
||
border: 1px solid rgba(212, 175, 55, 0.25);
|
||
border-radius: 18rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.detail-modal-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 18rpx 18rpx;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
|
||
.detail-modal-title {
|
||
flex: 1;
|
||
color: #d4af37;
|
||
font-weight: 900;
|
||
font-size: 28rpx;
|
||
padding-right: 16rpx;
|
||
}
|
||
|
||
.detail-modal-close {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.detail-modal-close-text {
|
||
color: #a0a0a0;
|
||
font-size: 34rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
.detail-modal-body {
|
||
max-height: 70vh;
|
||
}
|
||
|
||
.detail-modal-content {
|
||
padding: 18rpx;
|
||
}
|
||
|
||
.detail-modal-text {
|
||
display: block;
|
||
color: #a0a0a0;
|
||
font-size: 24rpx;
|
||
line-height: 1.8;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
</style>
|