Files
----/前端源码/uni-app/components/screens/TestNameDetail.vue

2874 lines
94 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="test-name-detail" :class="{ 'test-name-detail--desktop': desktopLayout }">
<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>
<view class="detail-header">
<view class="detail-header-back" @click="emit('back')">
<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>
<scroll-view scroll-y class="detail-content" :class="{ 'detail-content--desktop': desktopLayout }">
<!-- 1 分数 -->
<view class="score-card mystic-score-card clickable module-order-1" @click="openDetail('总分详解', dataObj?.header?.details?.nodes)">
<view class="mystic-score-card__glow" />
<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">命盘总分</view>
<view class="score-value-wrap">
<view v-if="desktopLayout" class="score-bagua-aura">
<view class="score-yinyang-bg" aria-hidden="true"></view>
<view class="score-rune-particles" aria-hidden="true">
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<view class="score-rune-dot" />
<text class="score-rune-glyph"></text>
<text class="score-rune-glyph"></text>
<text class="score-rune-glyph"></text>
<text class="score-rune-glyph"></text>
</view>
<view class="score-bagua-core"></view>
<view class="score-bagua-ring-2"></view>
<view class="score-bagua-symbols">
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
<text class="score-bagua-symbol"></text>
</view>
</view>
<text class="score">{{ num(dataObj?.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 <= num(dataObj?.header?.star_rating) }"></text>
</view>
<view class="score-divider"></view>
<text class="name">{{ toText(dataObj?.header?.name) || '—' }}</text>
<text class="quote">{{ toText(dataObj?.header?.poem_quote) }}</text>
<view v-if="desktopLayout" class="score-mystic-ornaments">
<view class="score-mystic-line"></view>
<view class="score-mystic-bagua">
<text class="score-mystic-symbol"></text>
<text class="score-mystic-dot">·</text>
<text class="score-mystic-symbol"></text>
<text class="score-mystic-dot">·</text>
<text class="score-mystic-symbol"></text>
<text class="score-mystic-dot">·</text>
<text class="score-mystic-symbol"></text>
<text class="score-mystic-dot">·</text>
<text class="score-mystic-symbol"></text>
<text class="score-mystic-dot">·</text>
<text class="score-mystic-symbol"></text>
</view>
<view class="score-mystic-sigil">
<text class="score-mystic-sigil-text"></text>
</view>
</view>
<view class="detail-entry">
<text class="detail-entry-text">查看总分详解</text>
</view>
</view>
<!-- 2 五行八卦相生相克 -->
<view v-if="hasWuxingBagua" class="section mystic-panel module-order-2">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">五行八卦</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="wuge-card mystic-inner-card clickable" @click="openWuxingBaguaDetail">
<view v-if="hasWuxingSupplement" class="wuxing-adapt-grid">
<text v-if="toText(dataObj?.wuxing_bagua?.lacking)" class="wuxing-adapt-item"><text class="wuxing-adapt-label">先天偏缺</text>{{ toText(dataObj?.wuxing_bagua?.lacking) }}</text>
<text v-if="toText(dataObj?.wuxing_bagua?.supplement)" class="wuxing-adapt-item"><text class="wuxing-adapt-label">补益建议</text>{{ toText(dataObj?.wuxing_bagua?.supplement) }}</text>
</view>
<text v-if="toText(dataObj?.wuxing_bagua?.wuxing_sketch)" class="line line-strong">五行{{ toText(dataObj?.wuxing_bagua?.wuxing_sketch) }}</text>
<text v-if="toText(dataObj?.wuxing_bagua?.bagua_profile)" class="line">八卦{{ toText(dataObj?.wuxing_bagua?.bagua_profile) }}</text>
<text v-if="toText(dataObj?.wuxing_bagua?.mutual_sketch)" class="line">{{ toText(dataObj?.wuxing_bagua?.mutual_sketch) }}</text>
<text v-if="toText(dataObj?.wuxing_bagua?.summary)" class="line">{{ toText(dataObj?.wuxing_bagua?.summary) }}</text>
</view>
<!-- 五行单字详解放到五行八卦下面 -->
<view v-if="hasCharDetail" class="mystic-panel mystic-panel--extra">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">五行单字详解</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openCharDetail">
<view v-for="(it, idx) in arr(dataObj?.char_detail?.items)" :key="idx" class="line">
{{ toText(it?.char) }}
<text v-if="toText(it?.element)"> · {{ toText(it?.element) }}</text>
<text v-if="toText(it?.yin_yang_element) || toText(it?.yin_yang)"> · {{ toText(it?.yin_yang_element) || toText(it?.yin_yang) }}</text>
<text v-if="toText(it?.bagua_trigram_symbol) || toText(it?.bagua_trigram)"> · {{ toText(it?.bagua_trigram_symbol) }}{{ toText(it?.bagua_trigram_symbol) && toText(it?.bagua_trigram) ? ' ' : '' }}{{ toText(it?.bagua_trigram) }}</text>
<text v-if="toText(it?.stroke)"> · {{ toText(it?.stroke) }}</text>
<text v-if="toText(it?.tone)"> · {{ toText(it?.tone) }}</text>
</view>
</view>
</view>
</view>
<!-- 3 五行相生还是相克 -->
<view v-if="hasBaziNameFit" class="section mystic-panel module-order-3">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">八字与姓名五行</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="zodiac-card mystic-inner-card clickable" @click="openDetail('八字与姓名五行详解', dataObj?.bazi_name_fit?.details?.nodes)">
<view class="zodiac-header">
<view class="zodiac-info">
<text v-if="toText(dataObj?.bazi_name_fit?.xiyongshen)" class="line">喜用神{{ toText(dataObj?.bazi_name_fit?.xiyongshen) }}</text>
<text v-if="toText(dataObj?.bazi_name_fit?.name_wuxing_profile)" class="line">姓名五行{{ toText(dataObj?.bazi_name_fit?.name_wuxing_profile) }}</text>
<text v-if="toText(dataObj?.bazi_name_fit?.complement_summary)" class="line">{{ toText(dataObj?.bazi_name_fit?.complement_summary) }}</text>
</view>
<text
v-if="dataObj?.bazi_name_fit?.fit_score !== undefined && dataObj?.bazi_name_fit?.fit_score !== null && dataObj?.bazi_name_fit?.fit_score !== ''"
class="zodiac-score"
>{{ num(dataObj?.bazi_name_fit?.fit_score) }}</text>
</view>
</view>
</view>
<!-- 4 三才五格 -->
<view class="section mystic-panel module-order-4">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">笔画 · 五格 · 三才</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="wuge-card mystic-inner-card clickable" @click="openDetail('笔画数理详解', dataObj?.strokes_wuge_sancai?.details?.nodes)">
<view v-for="(it, idx) in arr(dataObj?.strokes_wuge_sancai?.wuge)" :key="idx" class="line">
{{ toText(it?.name) }}{{ toText(it?.desc) }}
</view>
<text class="line">三才{{ toText(dataObj?.strokes_wuge_sancai?.sancai?.result) }}</text>
</view>
</view>
<!-- 5 六爻 -->
<view v-if="hasLiuyao" class="section mystic-panel module-order-5">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">六爻</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="meaning-card mystic-inner-card clickable" @click="openDetail('六爻详解', dataObj?.liuyao?.details?.nodes)">
<text v-if="toText(dataObj?.liuyao?.hexagram_title)" class="line line-strong">{{ toText(dataObj?.liuyao?.hexagram_title) }}</text>
<text v-if="toText(dataObj?.liuyao?.changing_summary)" class="line">动爻{{ toText(dataObj?.liuyao?.changing_summary) }}</text>
<text v-if="toText(dataObj?.liuyao?.interpretation)" class="line">{{ toText(dataObj?.liuyao?.interpretation) }}</text>
<view v-for="(yl, yi) in arr(dataObj?.liuyao?.yao_lines).slice(0, 6)" :key="yi" class="line">{{ toText(yl) }}</view>
</view>
</view>
<!-- 6 属相 -->
<view v-if="hasZodiacSign" class="section mystic-panel module-order-6">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">属相</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="zodiac-card mystic-inner-card clickable" @click="openZodiacDetail">
<view class="zodiac-header">
<view class="zodiac-icon-wrap">
<text class="zodiac-icon">{{ toText(dataObj?.zodiac_sign?.animal_icon) || toText(dataObj?.zodiac_sign?.animal) || '属相' }}</text>
</view>
<view class="zodiac-info">
<text class="zodiac-label">属相</text>
<text class="zodiac-value">{{ toText(dataObj?.zodiac_sign?.animal) }} {{ toText(dataObj?.zodiac_sign?.earthly_branch) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.trait_summary)" class="line trait-line">{{ toText(dataObj?.zodiac_sign?.trait_summary) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.name_harmony)" class="line trait-line">与名{{ toText(dataObj?.zodiac_sign?.name_harmony) }}</text>
<view v-if="hasZodiacAdaptation" class="zodiac-adapt-grid">
<text v-if="toText(dataObj?.zodiac_sign?.sanhe)" class="zodiac-adapt-item"><text class="zodiac-adapt-label">三合</text>{{ toText(dataObj?.zodiac_sign?.sanhe) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.liuhe)" class="zodiac-adapt-item"><text class="zodiac-adapt-label">六合</text>{{ toText(dataObj?.zodiac_sign?.liuhe) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.xiangchong)" class="zodiac-adapt-item"><text class="zodiac-adapt-label">相冲</text>{{ toText(dataObj?.zodiac_sign?.xiangchong) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.xianghai)" class="zodiac-adapt-item"><text class="zodiac-adapt-label">相害</text>{{ toText(dataObj?.zodiac_sign?.xianghai) }}</text>
<text v-if="toText(dataObj?.zodiac_sign?.xiangxing)" class="zodiac-adapt-item"><text class="zodiac-adapt-label">相刑</text>{{ toText(dataObj?.zodiac_sign?.xiangxing) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 7 幸运数字 -->
<view v-if="hasLuckyNumbers" class="section mystic-panel module-order-7">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">幸运数字</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openDetail('幸运数字详解', dataObj?.lucky_numbers?.details?.nodes)">
<text v-if="toText(dataObj?.lucky_numbers?.primary)" class="line line-strong">主推{{ toText(dataObj?.lucky_numbers?.primary) }}</text>
<text v-if="luckyNumbersDisplay" class="line">{{ luckyNumbersDisplay }}</text>
<text v-if="toText(dataObj?.lucky_numbers?.meaning)" class="line">{{ toText(dataObj?.lucky_numbers?.meaning) }}</text>
</view>
</view>
<!-- 8 颜色 -->
<view v-if="hasLuckyColors" class="section mystic-panel module-order-8">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">幸运颜色</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openDetail('幸运颜色详解', dataObj?.lucky_colors?.details?.nodes)">
<text v-if="toText(dataObj?.lucky_colors?.primary)" class="line line-strong">主推{{ toText(dataObj?.lucky_colors?.primary) }}</text>
<view v-for="(c, ci) in arr(dataObj?.lucky_colors?.colors)" :key="ci" class="lucky-color-row">
<view
v-if="toText(c?.hex)"
class="lucky-color-swatch"
:style="{ backgroundColor: toText(c?.hex) }"
/>
<text class="line lucky-color-text">{{ toText(c?.name) }}{{ toText(c?.note) ? ' — ' + toText(c.note) : '' }}</text>
</view>
<text v-if="toText(dataObj?.lucky_colors?.meaning)" class="line">{{ toText(dataObj?.lucky_colors?.meaning) }}</text>
</view>
</view>
<!-- 9 事业规划 -->
<view v-if="hasCareerPlan" class="section mystic-panel module-order-9">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">事业规划</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openDetail('事业规划详解', dataObj?.career_plan?.details?.nodes)">
<text v-if="toText(dataObj?.career_plan?.summary)" class="line">{{ toText(dataObj?.career_plan?.summary) }}</text>
<view v-for="(m, mi) in arr(dataObj?.career_plan?.milestones)" :key="mi" class="career-milestone">
<text class="line line-strong">{{ toText(m?.phase) }}{{ toText(m?.period) ? ' · ' + toText(m.period) : '' }}</text>
<text v-if="toText(m?.focus)" class="line">{{ toText(m.focus) }}</text>
<text v-if="toText(m?.advice)" class="line">{{ toText(m.advice) }}</text>
</view>
</view>
</view>
<!-- 10 开运锦囊 -->
<view class="section mystic-panel module-order-10">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">开运锦囊</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openDetail('开运锦囊详解', dataObj?.lucky_tips?.details?.nodes)">
<view v-for="(it, idx) in arr(dataObj?.lucky_tips?.items)" :key="idx" class="line">
{{ toText(it?.icon) }} {{ toText(it?.label) }}{{ toText(it?.value) }}
</view>
</view>
</view>
<!-- 11 人生运程 -->
<view class="section mystic-panel module-order-11">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">人生运程</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="wuxing-card mystic-inner-card clickable" @click="openDetail('人生运程详解', dataObj?.lifespan?.details?.nodes)">
<view v-for="(it, idx) in arr(dataObj?.lifespan?.items)" :key="idx" class="line">
{{ toText(it?.age) }}{{ toText(it?.desc) }}
</view>
</view>
</view>
<!-- 12 诗词出处 -->
<view class="section mystic-panel module-order-12">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">诗词出处</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="meaning-card mystic-inner-card clickable" @click="openDetail('诗词出处详解', dataObj?.poetry_source?.details?.nodes)">
<text class="line">{{ toText(dataObj?.poetry_source?.text) }}</text>
<text class="line">{{ toText(dataObj?.poetry_source?.source) }}</text>
</view>
</view>
<!-- 13 六维格局 -->
<view class="section mystic-panel section--sixdim-compact module-order-13">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">六维格局</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="clickable mystic-inner-card mystic-inner-card--flush" @click="openDetail('六维格局详解', dataObj?.six_dimension?.details?.nodes)">
<SixDimensionRadarDesktopEchart
v-if="desktopLayout"
:labels="arr(dataObj?.six_dimension?.labels)"
:values="arr(dataObj?.six_dimension?.values)"
:remark="toText(dataObj?.six_dimension?.remark)"
/>
<SixDimensionRadar
v-else
:labels="arr(dataObj?.six_dimension?.labels)"
:values="arr(dataObj?.six_dimension?.values)"
:remark="toText(dataObj?.six_dimension?.remark)"
/>
</view>
</view>
<!-- 14 大师寄语 -->
<view class="section mystic-panel module-order-14">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">大师寄语</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="meaning-card mystic-inner-card clickable" @click="openDetail('大师寄语详解', dataObj?.master_message?.details?.nodes)">
<text class="line">{{ toText(dataObj?.master_message?.text) }}</text>
</view>
</view>
<!-- 其余模块置后 -->
<view class="section mystic-panel mystic-panel--extra">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">字义与生肖</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="meaning-card mystic-inner-card clickable" @click="openDetail('字义与生肖详解', dataObj?.meaning_and_zodiac?.details?.nodes)">
<view class="meaning-quote"></view>
<text class="meaning-label">{{ toText(dataObj?.meaning_and_zodiac?.imagery_title) || "组合意象" }}</text>
<text class="meaning-text">{{ toText(dataObj?.meaning_and_zodiac?.imagery_text) }}</text>
<view class="meaning-judge">
<text class="meaning-judge-label">判断</text>
<text class="meaning-judge-text">{{ toText(dataObj?.meaning_and_zodiac?.judgement) }}</text>
</view>
</view>
<view class="zodiac-card mystic-inner-card clickable" @click="openDetail('字义与生肖详解', dataObj?.meaning_and_zodiac?.details?.nodes)">
<view class="zodiac-header">
<view class="zodiac-icon-wrap">
<text class="zodiac-icon">{{ toText(dataObj?.meaning_and_zodiac?.zodiac?.animal) || "🐉" }}</text>
</view>
<view class="zodiac-info">
<text class="zodiac-label">生肖适配</text>
<text class="zodiac-value">{{ toText(dataObj?.meaning_and_zodiac?.zodiac?.label) }}</text>
</view>
<text class="zodiac-score">{{ num(dataObj?.meaning_and_zodiac?.zodiac?.score) }}</text>
</view>
</view>
</view>
<!-- 单字详解已移到五行八卦下面 -->
<view v-if="hasPhonetics" class="section mystic-panel mystic-panel--extra">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">音律音韵</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="meaning-card mystic-inner-card clickable" @click="openDetail('音律音韵详解', dataObj?.phonetics?.details?.nodes)">
<text v-if="toText(dataObj?.phonetics?.pinyin_full)" class="line line-strong">{{ toText(dataObj?.phonetics?.pinyin_full) }}</text>
<text v-if="toText(dataObj?.phonetics?.pingze)" class="line">声调与平仄{{ toText(dataObj?.phonetics?.pingze) }}</text>
<text v-if="toText(dataObj?.phonetics?.rhythm_summary)" class="line">{{ toText(dataObj?.phonetics?.rhythm_summary) }}</text>
<view v-if="toText(dataObj?.phonetics?.judgement)" class="meaning-judge">
<text class="meaning-judge-label">简评</text>
<text class="meaning-judge-text">{{ toText(dataObj?.phonetics?.judgement) }}</text>
</view>
</view>
</view>
<view class="section mystic-panel mystic-panel--extra">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">周易卦象</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="zodiac-card mystic-inner-card clickable" @click="openGuaDetail">
<text class="line line-strong">{{ toText(dataObj?.gua?.name) }}</text>
<text v-if="dataObj?.gua?.hexagram_code !== undefined && dataObj?.gua?.hexagram_code !== null && dataObj?.gua?.hexagram_code !== ''" class="line">卦序{{ toText(dataObj?.gua?.hexagram_code) }}</text>
<text v-if="toText(dataObj?.gua?.bgText)" class="line">卦名{{ toText(dataObj?.gua?.bgText) }}</text>
<text v-if="toText(dataObj?.gua?.badge)" class="line">卦象{{ toText(dataObj?.gua?.badge) }}</text>
<text class="line">{{ toText(dataObj?.gua?.desc) }}</text>
</view>
</view>
<view v-if="hasNamePopularity" class="section mystic-panel mystic-panel--extra">
<view class="section-title mystic-section-title">
<text class="section-glyph"></text>
<text class="section-text">重名与流行度</text>
<text class="section-detail-tip">详解</text>
</view>
<view class="info-card mystic-inner-card clickable" @click="openDetail('重名与流行度详解', dataObj?.name_popularity?.details?.nodes)">
<text v-if="toText(dataObj?.name_popularity?.duplicate_rate_label)" class="line line-strong">
重名提示{{ toText(dataObj?.name_popularity?.duplicate_rate_label) }}
</text>
<text v-if="toText(dataObj?.name_popularity?.popularity_tier)" class="line">
流行度{{ toText(dataObj?.name_popularity?.popularity_tier) }}
</text>
<text v-if="toText(dataObj?.name_popularity?.interpretation)" class="line">{{ toText(dataObj?.name_popularity?.interpretation) }}</text>
</view>
</view>
<view class="bottom-spacer"></view>
</scroll-view>
<view v-if="showModal" class="modal-mask" @click="closeDetail">
<view class="detail-modal" @click.stop="">
<view class="detail-modal-orn detail-modal-orn--tl" />
<view class="detail-modal-orn detail-modal-orn--tr" />
<view class="detail-modal-orn detail-modal-orn--bl" />
<view class="detail-modal-orn detail-modal-orn--br" />
<view class="detail-modal-card">
<view class="modal-title-wrap">
<view class="modal-title-seal"></view>
<text class="modal-title">{{ modalTitle }}</text>
</view>
<view class="close" @click="closeDetail">×</view>
</view>
<scroll-view scroll-y class="detail-modal-body">
<view v-for="(node, idx) in modalNodes" :key="idx" class="node">
<text v-if="node?.type === 'text'" class="line">{{ toText(node.text) }}</text>
<view v-else-if="node?.type === 'list'">
<text v-for="(item, j) in arr(node.items)" :key="j" class="line">- {{ toText(item) }}</text>
</view>
<view v-else-if="node?.type === 'kv'">
<text v-for="(item, j) in arr(node.items)" :key="j" class="line">{{ toText(item?.label) }}{{ toText(item?.value) }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed, ref } from "vue";
import SixDimensionRadar from "../SixDimensionRadar.vue";
import SixDimensionRadarDesktopEchart from "../SixDimensionRadarDesktopEchart.vue";
import pdfBgUrl from "../../utils/pdf/background.png";
const props = withDefaults(
defineProps<{ data: any; desktopLayout?: boolean }>(),
{ desktopLayout: false },
);
const emit = defineEmits<{ back: [] }>();
const tryParse = (v: any) => {
if (v && typeof v === "object") return v;
if (typeof v !== "string") return {};
try {
return JSON.parse(v);
} catch {
return {};
}
};
const dataObj = computed(() => tryParse(props.data));
const toText = (v: any) => String(v ?? "").trim();
const hasPhonetics = computed(() => {
const p = dataObj.value?.phonetics;
if (!p || typeof p !== "object") return false;
return !!(
toText(p.pinyin_full) ||
toText(p.pingze) ||
toText(p.rhythm_summary) ||
toText(p.judgement) ||
arr(p.details?.nodes).length
);
});
const hasBaziNameFit = computed(() => {
const b = dataObj.value?.bazi_name_fit;
if (!b || typeof b !== "object") return false;
const rawScore = b.fit_score;
const scoreNum = Number(rawScore);
const hasScore =
rawScore !== undefined &&
rawScore !== null &&
rawScore !== "" &&
Number.isFinite(scoreNum);
return !!(
toText(b.xiyongshen) ||
toText(b.name_wuxing_profile) ||
toText(b.complement_summary) ||
hasScore ||
arr(b.details?.nodes).length
);
});
const hasNamePopularity = computed(() => {
const n = dataObj.value?.name_popularity;
if (!n || typeof n !== "object") return false;
return !!(
toText(n.duplicate_rate_label) ||
toText(n.popularity_tier) ||
toText(n.interpretation) ||
arr(n.details?.nodes).length
);
});
const hasLiuyao = computed(() => {
const x = dataObj.value?.liuyao;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.hexagram_title) ||
toText(x.changing_summary) ||
toText(x.interpretation) ||
arr(x.yao_lines).length ||
arr(x.details?.nodes).length
);
});
const hasWuxingBagua = computed(() => {
const x = dataObj.value?.wuxing_bagua;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.lacking) ||
toText(x.supplement) ||
toText(x.wuxing_sketch) ||
toText(x.bagua_profile) ||
toText(x.mutual_sketch) ||
toText(x.summary) ||
arr(x.details?.nodes).length
);
});
const hasWuxingSupplement = computed(() => {
const x = dataObj.value?.wuxing_bagua;
if (!x || typeof x !== "object") return false;
return !!(toText(x.lacking) || toText(x.supplement));
});
const hasCharDetail = computed(() => {
const items = arr(dataObj.value?.char_detail?.items);
return items.length > 0;
});
const hasZodiacSign = computed(() => {
const x = dataObj.value?.zodiac_sign;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.animal) ||
toText(x.animal_icon) ||
toText(x.earthly_branch) ||
toText(x.sanhe) ||
toText(x.liuhe) ||
toText(x.xiangchong) ||
toText(x.xianghai) ||
toText(x.xiangxing) ||
toText(x.trait_summary) ||
toText(x.name_harmony) ||
arr(x.details?.nodes).length
);
});
const hasZodiacAdaptation = computed(() => {
const x = dataObj.value?.zodiac_sign;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.sanhe) ||
toText(x.liuhe) ||
toText(x.xiangchong) ||
toText(x.xianghai) ||
toText(x.xiangxing)
);
});
const hasCareerPlan = computed(() => {
const x = dataObj.value?.career_plan;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.summary) ||
arr(x.milestones).length ||
arr(x.details?.nodes).length
);
});
const hasLuckyNumbers = computed(() => {
const x = dataObj.value?.lucky_numbers;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.primary) ||
toText(x.meaning) ||
arr(x.numbers).length ||
arr(x.details?.nodes).length
);
});
const hasLuckyColors = computed(() => {
const x = dataObj.value?.lucky_colors;
if (!x || typeof x !== "object") return false;
return !!(
toText(x.primary) ||
toText(x.meaning) ||
arr(x.colors).length ||
arr(x.details?.nodes).length
);
});
const luckyNumbersDisplay = computed(() => {
const nums = arr(dataObj.value?.lucky_numbers?.numbers);
if (!nums.length) return "";
return nums.map((n: any) => String(n)).join("、");
});
const arr = (v: any) => (Array.isArray(v) ? v : []);
const num = (v: any) => {
const n = Number(v);
return Number.isFinite(n) ? n : 0;
};
const showModal = ref(false);
const modalTitle = ref("详解");
const modalNodes = ref<any[]>([]);
const openDetail = (title: string, nodes: any) => {
modalTitle.value = title;
modalNodes.value = arr(nodes);
showModal.value = true;
};
const closeDetail = () => {
showModal.value = false;
};
// —— 五行八卦:把“每个字的五行/阴阳/卦象”拼到详解弹层最前面 ——
const formatCharDetailLine = (it: any): string => {
const parts: string[] = [];
const char = toText(it?.char);
if (!char) return "";
parts.push(char);
const element = toText(it?.element);
if (element) parts.push(element);
const yinYang = toText(it?.yin_yang_element) || toText(it?.yin_yang);
if (yinYang) parts.push(yinYang);
const sym = toText(it?.bagua_trigram_symbol);
const tri = toText(it?.bagua_trigram);
if (sym || tri) {
const trigram = tri ? tri : "";
parts.push(`卦:${sym}${sym && trigram ? ' ' : ''}${trigram}`.trim());
}
const stroke = toText(it?.stroke);
if (stroke) parts.push(`笔画:${stroke}`);
const tone = toText(it?.tone);
if (tone) parts.push(`声调:${tone}`);
return parts.join(" · ");
};
const charDetailNodes = computed(() => {
const items = arr(dataObj.value?.char_detail?.items);
const nodes = items
.map((it: any) => {
const line = formatCharDetailLine(it);
return line ? { type: "text", text: line } : null;
})
.filter(Boolean) as any[];
return nodes;
});
const openCharDetail = () => {
const fallbackNodes = arr(dataObj.value?.char_detail?.details?.nodes);
const nodes = charDetailNodes.value.length ? charDetailNodes.value : fallbackNodes;
openDetail("五行单字详解", nodes);
};
const openWuxingBaguaDetail = () => {
const x = dataObj.value?.wuxing_bagua || {};
const extraNodes = [
toText(x?.lacking) ? { type: "text", text: `先天偏缺:${toText(x.lacking)}` } : null,
toText(x?.supplement) ? { type: "text", text: `补益建议:${toText(x.supplement)}` } : null,
toText(x?.wuxing_sketch) ? { type: "text", text: `五行:${toText(x.wuxing_sketch)}` } : null,
toText(x?.bagua_profile) ? { type: "text", text: `八卦:${toText(x.bagua_profile)}` } : null,
toText(x?.mutual_sketch) ? { type: "text", text: toText(x.mutual_sketch) } : null,
toText(x?.summary) ? { type: "text", text: toText(x.summary) } : null,
].filter(Boolean) as any[];
const baguaFallbackNodes = arr(dataObj.value?.wuxing_bagua?.details?.nodes);
const nodes = charDetailNodes.value.length
? [...charDetailNodes.value, ...extraNodes, ...baguaFallbackNodes]
: [...extraNodes, ...baguaFallbackNodes];
openDetail("五行八卦详解", nodes);
};
const openZodiacDetail = () => {
const z = dataObj.value?.zodiac_sign || {};
const extraNodes = [
(toText(z?.animal) || toText(z?.earthly_branch))
? { type: "text", text: `属相:${toText(z.animal)} ${toText(z.earthly_branch)}`.trim() }
: null,
toText(z?.trait_summary) ? { type: "text", text: `特性:${toText(z.trait_summary)}` } : null,
toText(z?.name_harmony) ? { type: "text", text: `与名契合:${toText(z.name_harmony)}` } : null,
toText(z?.sanhe) ? { type: "text", text: `三合:${toText(z.sanhe)}` } : null,
toText(z?.liuhe) ? { type: "text", text: `六合:${toText(z.liuhe)}` } : null,
toText(z?.xiangchong) ? { type: "text", text: `相冲:${toText(z.xiangchong)}` } : null,
toText(z?.xianghai) ? { type: "text", text: `相害:${toText(z.xianghai)}` } : null,
toText(z?.xiangxing) ? { type: "text", text: `相刑:${toText(z.xiangxing)}` } : null,
].filter(Boolean) as any[];
openDetail("属相详解", [...extraNodes, ...arr(z?.details?.nodes)]);
};
const openGuaDetail = () => {
const g = dataObj.value?.gua || {};
const extraNodes = [
toText(g?.name) ? { type: "text", text: `卦象:${toText(g.name)}` } : null,
(g?.hexagram_code !== undefined && g?.hexagram_code !== null && g?.hexagram_code !== "")
? { type: "text", text: `卦序:${toText(g.hexagram_code)}` }
: null,
toText(g?.bgText) ? { type: "text", text: `卦名:${toText(g.bgText)}` } : null,
toText(g?.badge) ? { type: "text", text: `卦位:${toText(g.badge)}` } : null,
toText(g?.desc) ? { type: "text", text: toText(g.desc) } : null,
].filter(Boolean) as any[];
openDetail("周易卦象详解", [...extraNodes, ...arr(g?.details?.nodes)]);
};
const handleDownloadPdf = async () => {
// H5/浏览器环境生成 PDF 下载到本地;若非浏览器则直接返回
if (typeof window === "undefined" || typeof document === "undefined") return;
uni.showLoading({ title: "正在生成PDF..." });
const raw = dataObj.value || {};
const header = raw?.header || {};
const measuredName = toText(header?.name);
const score = header?.score ?? "";
const starRating = header?.star_rating ?? header?.stars ?? 0;
const preferredModules: Array<{ key: string; title: string }> = [
{ key: "wuxing_bagua", title: "五行八卦(相生相克)" },
{ key: "char_detail", title: "五行单字详解" },
{ key: "bazi_name_fit", title: "八字与姓名五行" },
{ key: "strokes_wuge_sancai", title: "三才五格" },
{ key: "liuyao", title: "六爻" },
{ key: "zodiac_sign", title: "属相" },
{ key: "wuxing_bagua", title: "五行八卦" },
{ key: "lucky_numbers", title: "幸运数字" },
{ key: "lucky_colors", title: "幸运颜色" },
{ key: "career_plan", title: "事业规划" },
{ key: "lucky_tips", title: "开运锦囊" },
{ key: "lifespan", title: "人生规划" },
{ key: "poetry_source", title: "诗词出处" },
{ key: "six_dimension", title: "六维格局" },
{ key: "master_message", title: "大师寄语" },
];
const keyToTitle = new Map(preferredModules.map((m) => [m.key, m.title]));
const escapeHtml = (s: string) =>
s
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
const flattenNodesToLines = (nodes: any[]): string[] => {
const out: string[] = [];
for (const n of nodes || []) {
if (!n || typeof n !== "object") continue;
if (n.type === "text") {
const t = toText(n.text);
if (t) out.push(t);
} else if (n.type === "list") {
const items = Array.isArray(n.items) ? n.items : [];
for (const it of items) {
const t = toText(it);
if (t) out.push(`- ${t}`);
}
} else if (n.type === "kv") {
const items = Array.isArray(n.items) ? n.items : [];
for (const it of items) {
const k = toText(it?.label);
const v = toText(it?.value);
if (k || v) out.push(`${k ? k : "项"}${v}`);
}
}
// 空行作为模块间隔(避免所有内容挤在一起)
out.push("");
}
// 去掉尾部多余空行
while (out.length && !out[out.length - 1]) out.pop();
return out;
};
const labelForKey = (rawKey: string): string => {
const k = String(rawKey || "").trim();
if (!k) return "";
const dict: Record<string, string> = {
// 通用
header: "基础信息",
details: "详解",
nodes: "详解内容",
items: "条目",
list: "列表",
text: "内容",
title: "标题",
summary: "总结",
remark: "批注",
note: "说明",
result: "结果",
score: "评分",
star_rating: "星级",
stars: "星级",
name: "名称",
label: "标签",
value: "内容",
desc: "描述",
// 单字详解
char: "字",
element: "五行",
stroke: "笔画",
tone: "声调",
pinyin: "拼音",
// 卦象
gua: "周易卦象",
bg: "卦象",
// 五行/生肖
wuxing: "五行",
zodiac: "生肖",
shuxiang: "属相",
animal: "生肖",
earthly_branch: "地支",
// 其它常见
phase: "阶段",
period: "周期",
focus: "重点",
advice: "建议",
colors: "颜色",
hex: "色值",
primary: "主推",
meaning: "含义",
numbers: "数字",
labels: "维度",
values: "分值",
lucky: "吉凶",
is_lucky: "吉凶",
color: "颜色",
wuxing_sketch: "五行概述",
bagua_profile: "八卦画像",
mutual_sketch: "生克互助",
trait_summary: "特性概述",
name_harmony: "与名字契合",
lacking: "先天偏缺",
supplement: "补益建议",
sanhe: "三合",
liuhe: "六合",
xiangchong: "相冲",
xianghai: "相害",
xiangxing: "相刑",
hexagram_code: "卦序",
bgText: "卦名",
badge: "卦位",
};
if (dict[k]) return dict[k];
// snake_case → 简单中文化(仅用于兜底,避免直接露出字段名)
const snake = k.replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`);
const parts = snake.split("_").filter(Boolean);
const partDict: Record<string, string> = {
char: "字",
detail: "详解",
analysis: "分析",
bagua: "八卦",
wuxing: "五行",
zodiac: "生肖",
score: "评分",
name: "名称",
plan: "规划",
lucky: "幸运",
color: "颜色",
number: "数字",
career: "事业",
master: "大师",
message: "寄语",
poetry: "诗词",
source: "出处",
stroke: "笔画",
gua: "卦象",
tips: "锦囊",
};
const mapped = parts.map((p) => partDict[p] || "");
const s = mapped.filter(Boolean).join("");
return s || "信息";
};
const noisyKeys = new Set(["details", "nodes", "type", "meta", "status", "code", "raw"]);
const hiddenPdfKeys = new Set([
"id",
"report_id",
"is_unlocked",
"unlock_price",
"locked",
"lock",
"price",
]);
const isNoisyInfoLine = (label: string, line: string) => {
if (label !== "信息") return false;
const s = String(line || "").trim();
if (!s) return true;
if (/^(true|false)$/i.test(s)) return true;
if (/^[a-z_]+$/i.test(s) && s.length <= 16) return true;
return false;
};
const flattenAnyToLines = (val: any, prefix = "", depth = 0): string[] => {
if (depth > 4) return [];
const out: string[] = [];
if (val === null || val === undefined) return out;
if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
const s = toText(val);
// 布尔值在报告中大多是内部标记位默认不直接导出避免“信息true/false”噪音
if (typeof val === "boolean") return out;
if (s) out.push(prefix ? `${prefix}${s}` : s);
return out;
}
if (Array.isArray(val)) {
val.forEach((it) => {
const child = flattenAnyToLines(it, "", depth + 1);
if (!child.length) return;
child.forEach((line) => out.push(line));
});
return out;
}
if (typeof val === "object") {
Object.keys(val).forEach((k) => {
if (noisyKeys.has(k)) return;
if (hiddenPdfKeys.has(k)) return;
// 隐藏纯容器字段
if (k === "items" && Array.isArray((val as any)[k])) {
const child = flattenAnyToLines((val as any)[k], "", depth + 1);
child.forEach((line) => out.push(line));
return;
}
const label = labelForKey(k);
const child = flattenAnyToLines((val as any)[k], "", depth + 1);
child.forEach((line) => {
if (isNoisyInfoLine(label, line)) return;
const shouldKeepRaw = line.includes("") && (label === "信息" || !label);
out.push(shouldKeepRaw ? line : (label ? `${label}${line}` : line));
});
});
}
return out;
};
const uniqueLines = (lines: string[]) => {
const seen = new Set<string>();
const out: string[] = [];
lines.forEach((line) => {
const s = String(line || "").trim();
if (!s || seen.has(s)) return;
seen.add(s);
out.push(s);
});
return out;
};
const isNumericLike = (s: string) => /^\d+(\.\d+)?$/.test(String(s || "").trim());
const shouldSkipModule = (title: string, lines: string[]) => {
if (title.includes("解锁状态") || title.includes("解锁价格")) return true;
const clean = (lines || []).map((x) => String(x || "").trim()).filter(Boolean);
if (!clean.length) return true;
const numericCount = clean.filter((x) => isNumericLike(x)).length;
// 过滤无语义章节:标题“信息”且内容仅为数字(如 302/337
if (title === "信息" && numericCount === clean.length) return true;
// 兜底:标题“信息”且几乎没有文本语义
if (title === "信息" && clean.length <= 2 && numericCount >= 1) return true;
return false;
};
const linesFromSixDimension = (v: any): string[] => {
if (!v || typeof v !== "object") return [];
const labels = arr(v.labels).map((x: any) => toText(x)).filter(Boolean);
const values = arr(v.values).map((x: any) => toText(x)).filter(Boolean);
const lines: string[] = [];
const len = Math.max(labels.length, values.length);
for (let i = 0; i < len; i++) {
const lb = labels[i] || `维度${i + 1}`;
const val = values[i];
if (!lb && !val) continue;
lines.push(`${lb}${val || "-"}`);
}
if (toText(v.remark)) lines.push(`批注:${toText(v.remark)}`);
return lines;
};
const prettifyKey = (k: string) => keyToTitle.get(k) || labelForKey(k);
// 固定优先模块 + 后端动态模块(确保不漏返回内容)
const usedKeys = new Set<string>();
const moduleOrder: Array<{ key: string; title: string; lines: string[] }> = [];
for (const p of preferredModules) {
const mod = (raw as any)?.[p.key];
if (!mod) continue;
const nodeLines = flattenNodesToLines(arr(mod?.details?.nodes));
const plainLines = p.key === "six_dimension" ? linesFromSixDimension(mod) : flattenAnyToLines(mod);
const merged = uniqueLines([...plainLines, ...nodeLines]);
if (shouldSkipModule(p.title, merged)) continue;
moduleOrder.push({ key: p.key, title: p.title, lines: merged });
usedKeys.add(p.key);
}
Object.keys(raw || {}).forEach((k) => {
if (usedKeys.has(k)) return;
if (hiddenPdfKeys.has(k)) return;
const v = (raw as any)[k];
const nodeLines = v && typeof v === "object" ? flattenNodesToLines(arr((v as any)?.details?.nodes)) : [];
const plainLines = k === "six_dimension" ? linesFromSixDimension(v) : flattenAnyToLines(v);
const merged = uniqueLines([...plainLines, ...nodeLines]);
const title = prettifyKey(k);
if (shouldSkipModule(title, merged)) return;
moduleOrder.push({ key: k, title, lines: merged });
});
if (!moduleOrder.length) return;
const visualLineCost = (line: string) => {
const s = String(line || "").trim();
if (!s) return 1;
return Math.max(1, Math.ceil(s.length / 30));
};
const splitByLines = (lines: string[], maxLinesPerPage: number) => {
if (!lines.length) return [[]];
const pages: string[][] = [];
let bucket: string[] = [];
let cost = 0;
lines.forEach((line) => {
const lc = visualLineCost(line);
if (bucket.length && cost + lc > maxLinesPerPage) {
pages.push(bucket);
bucket = [];
cost = 0;
}
bucket.push(line);
cost += lc;
});
if (bucket.length) pages.push(bucket);
return pages;
};
// 以 96dpi 折算A4 = 793.7 x 1122.5 px
const PAGE_W = 794;
const PAGE_H = 1123;
// 分页容量:区分“单章节切分容量”和“页面总容量”
const MAX_SECTION_LINES_PER_PAGE = 30;
const MAX_PAGE_COST = 42;
// 动态加载 pdf 依赖(减少首屏包体积)
const [{ jsPDF }, html2canvasMod] = await Promise.all([
import("jspdf"),
import("html2canvas"),
]);
const html2canvas = (html2canvasMod as any).default || (html2canvasMod as any);
// 样式只注入一次
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, 0.62);
border: 1px solid rgba(212, 175, 55, 0.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,0.72);
margin-bottom: 12px;
}
.pdf-cover-title{
text-align:center;
font-size: 25px;
letter-spacing: .16em;
font-weight: 700;
color: rgba(212,175,55,0.95);
margin-bottom: 28px;
}
.pdf-name{
text-align:center;
font-size: 50px;
font-weight: 800;
margin-bottom: 16px;
color: rgba(242,230,216,0.98);
text-shadow: 0 0 18px rgba(212,175,55,0.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,0.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,0.95);
letter-spacing: .08em;
}
.pdf-dir-title{
font-size: 20px;
font-weight: 800;
letter-spacing: .14em;
color: rgba(212,175,55,0.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,0.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,0.96);
font-weight: 800;
letter-spacing: .02em;
}
.pdf-dir-name{
flex: 1;
font-size: 16px;
color: rgba(232,232,232,0.96);
font-weight: 700;
letter-spacing: .04em;
}
.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,0.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,0.08);
border: 1px solid rgba(212,175,55,0.24);
display:flex;
align-items:center;
justify-content:flex-start;
padding: 0 22px;
box-sizing:border-box;
}
.pdf-chapter-no{
font-size: 16px;
font-weight: 900;
color: rgba(212,175,55,0.98);
margin-right: 14px;
}
.pdf-chapter-title{
font-size: 16px;
font-weight: 800;
color: rgba(242,230,216,0.98);
}
.pdf-content-pre{
white-space: pre-wrap;
word-break: break-word;
font-size: 13px;
line-height: 1.85;
color: rgba(226,226,226,0.94);
}
.pdf-flow-wrap{
display:block;
}
.pdf-flow-section{
margin-bottom: 10px;
border-radius: 10px;
border: 1px solid rgba(212,175,55,0.16);
background: rgba(11,16,38,0.55);
overflow: hidden;
}
.pdf-flow-section:last-child{
margin-bottom:0;
}
.pdf-flow-head{
height: 38px;
display:flex;
align-items:center;
gap:10px;
padding: 0 14px;
border-bottom: 1px solid rgba(212,175,55,0.16);
background: linear-gradient(90deg, rgba(212,175,55,0.12), rgba(212,175,55,0.03));
}
.pdf-flow-bullet{
color: rgba(212,175,55,0.96);
font-size: 15px;
}
.pdf-flow-title{
color: rgba(245,236,218,0.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,0.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);
try {
const pages: HTMLElement[] = [];
const bgUrl = pdfBgUrl;
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(${bgUrl})`;
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);
};
// 1) 封面页
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(measuredName || "未命名")}</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, Number(starRating) || 0)))}${"☆".repeat(Math.max(0, 5 - Math.max(0, Math.min(5, Number(starRating) || 0))))}</div>
</div>
<div class="pdf-cover-seal">玄</div>
</div>
`;
appendFooter(coverEl, 1, "测名详解报告 · 封面");
// 2) 目录页先占位pageNo 在后面计算后再重建)
const dirEl = createPageEl(1);
dirEl.innerHTML = `
<div class="pdf-gen-panel">
<div class="pdf-dir-title">目录</div>
<div class="pdf-content-pre">${escapeHtml("正在生成目录...")}</div>
</div>
`;
// 3) 分章分页:章节结束后可无缝衔接下一章,避免留白过多
let currentPageNo = 3; // 封面=1目录=2所以正文从 3 开始
const dirItems: Array<{ no: number; title: string; start: number; end: number }> = [];
type ChapterSection = { chapterNo: number; title: string; lines: string[]; continued?: boolean };
type ContentPage = { sections: ChapterSection[]; used: number };
const contentPages: ContentPage[] = [];
const sectionCost = (lines: string[]) => {
const bodyCost = lines.reduce((sum, line) => sum + visualLineCost(line), 0);
// 节标题+边距固定开销
return bodyCost + 6;
};
let chapterCounter = 0;
for (const m of moduleOrder) {
chapterCounter += 1;
const chapterNo = chapterCounter;
const lines = Array.isArray(m.lines) ? m.lines : [];
if (!lines.length) continue;
const chunks = splitByLines(lines, MAX_SECTION_LINES_PER_PAGE);
const startPage = currentPageNo;
chunks.forEach((chunkLines, chunkIdx) => {
const section: ChapterSection = {
chapterNo,
title: m.title,
lines: chunkLines,
continued: chunkIdx > 0,
};
const cost = sectionCost(chunkLines);
const prevPage = contentPages[contentPages.length - 1];
const canAppend =
!!prevPage &&
prevPage.used + cost <= MAX_PAGE_COST;
if (canAppend) {
prevPage.sections.push(section);
prevPage.used += cost;
} else {
contentPages.push({ sections: [section], used: cost });
currentPageNo += 1;
}
});
const endPage = Math.max(startPage, currentPageNo - 1);
dirItems.push({ no: chapterNo, title: m.title, start: startPage, end: endPage });
}
// 重建目录页内容
const dirHtml = `
<div class="pdf-dir-list">
${dirItems
.map((it) => {
const idxText = `${it.no}`;
const pageText = it.start === it.end ? `${it.start}` : `${it.start}-${it.end}`;
return `<div class="pdf-dir-item">
<div class="pdf-dir-idx">${escapeHtml(idxText)}</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>
`;
dirEl.innerHTML = `
<div class="pdf-gen-panel">
<div class="pdf-dir-kicker">卷 一 · 纲 目</div>
<div class="pdf-dir-title">目录</div>
${dirHtml}
</div>
`;
appendFooter(dirEl, 2, "测名详解报告 · 目录");
// 创建内容页 DOM同页可容纳多个章节
contentPages.forEach((page, i) => {
const el = createPageEl(2 + i);
const sectionsHtml = page.sections
.map((section) => {
const sectionTitle = `${section.chapterNo}${section.title}${section.continued ? "(续)" : ""}`;
const linesHtml = section.lines
.map((ln) => `<div class="pdf-flow-line">${escapeHtml(ln || " ")}</div>`)
.join("");
return `<div class="pdf-flow-section">
<div class="pdf-flow-head">
<div class="pdf-flow-bullet">✧</div>
<div class="pdf-flow-title">${escapeHtml(sectionTitle)}</div>
</div>
<div class="pdf-flow-body">${linesHtml}</div>
</div>`;
})
.join("");
el.innerHTML = `
<div class="pdf-gen-panel">
<div class="pdf-flow-wrap">${sectionsHtml}</div>
</div>
`;
appendFooter(el, 3 + i);
});
// 捕获页面并写入 PDF
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;
const renderPageCanvasSafely = async (sourceEl: HTMLElement): Promise<HTMLCanvasElement> => {
try {
return await html2canvas(sourceEl, {
scale: renderScale,
useCORS: true,
allowTaint: true,
backgroundColor: "#0a0a0f",
logging: false, // 关闭 html2canvas 大量控制台日志
});
} catch (err: any) {
const msg = String(err?.message || err || "");
// html2canvas 偶发createPattern 传入 0x0 canvas重试时移除背景图规避
if (!msg.includes("createPattern")) throw err;
const cloned = sourceEl.cloneNode(true) as HTMLElement;
const list = [cloned, ...Array.from(cloned.querySelectorAll("*"))] as HTMLElement[];
list.forEach((node) => {
node.style.backgroundImage = "none";
});
cloned.style.position = "absolute";
cloned.style.left = "-99999px";
cloned.style.top = "0";
document.body.appendChild(cloned);
try {
return await html2canvas(cloned, {
scale: renderScale,
useCORS: true,
allowTaint: true,
backgroundColor: "#0a0a0f",
logging: false,
});
} finally {
cloned.remove();
}
}
};
for (let i = 0; i < pages.length; i++) {
const canvas = await renderPageCanvasSafely(pages[i]);
const imgData = canvas.toDataURL("image/jpeg", jpegQuality);
if (i > 0) doc.addPage();
doc.addImage(imgData, "JPEG", 0, 0, pageW, pageH, undefined, "FAST");
}
// 下载到本地
const safeName = measuredName ? measuredName.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 {
// 清理 DOM
wrapper.remove();
uni.hideLoading();
}
};
const stars = Array.from({ length: 24 }, (_, i) => ({
id: i + 1,
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
size: Math.random() * 2 + 1,
duration: Math.random() * 4 + 3,
delay: Math.random() * 4,
}));
</script>
<style scoped>
.test-name-detail {
min-height: 100vh;
background: #0a0a0f;
position: relative;
color: #e2e2e2;
}
.detail-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(10, 10, 15, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.detail-header-back {
position: relative;
z-index: 2;
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 {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 18px;
font-weight: 700;
color: #d4af37;
letter-spacing: 0.3em;
pointer-events: none;
}
.detail-header-placeholder { width: 80rpx; }
.detail-header-download {
position: relative;
z-index: 2;
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;
}
.detail-content {
position: relative;
z-index: 10;
padding: 180rpx 32rpx 60rpx;
height: 100vh;
box-sizing: border-box;
}
.clickable { cursor: pointer; }
.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-label {
font-size: 10px;
color: #d4af37;
letter-spacing: 0.5em;
text-transform: uppercase;
opacity: 0.8;
margin-bottom: 16rpx;
}
.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-value-wrap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 280rpx;
height: 280rpx;
margin-bottom: 16rpx;
}
.score {
position: relative;
z-index: 2;
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;
z-index: 3;
width: 100%;
height: 100%;
border: 1px dashed rgba(212, 175, 55, 0.2);
border-radius: 50%;
animation: rotate 10s linear infinite;
}
.score-bagua-aura {
position: absolute;
inset: 0;
z-index: 0;
border-radius: 50%;
pointer-events: none;
}
.score-yinyang-bg {
position: absolute;
inset: 22%;
border-radius: 50%;
opacity: 0.11;
background:
radial-gradient(circle at 50% 25%, rgba(12, 14, 28, 0.95) 11%, transparent 11.5%),
radial-gradient(circle at 50% 75%, rgba(212, 175, 55, 0.55) 11%, transparent 11.5%),
linear-gradient(90deg, rgba(12, 14, 28, 0.98) 50%, rgba(212, 175, 55, 0.35) 50%);
box-shadow: inset 0 0 24px rgba(212, 175, 55, 0.08);
animation: scoreYinyangSpin 48s linear infinite reverse;
}
.score-rune-particles {
position: absolute;
inset: 0;
pointer-events: none;
}
.score-rune-dot {
position: absolute;
width: 3px;
height: 3px;
border-radius: 50%;
background: rgba(255, 224, 160, 0.95);
box-shadow:
0 0 6px rgba(255, 210, 120, 0.85),
0 0 14px rgba(212, 175, 55, 0.35);
animation: scoreRuneTwinkle 2.8s ease-in-out infinite;
}
.score-rune-dot:nth-child(1) { top: 12%; left: 38%; animation-delay: 0s; }
.score-rune-dot:nth-child(2) { top: 22%; right: 14%; animation-delay: 0.4s; }
.score-rune-dot:nth-child(3) { top: 48%; left: 8%; animation-delay: 0.8s; }
.score-rune-dot:nth-child(4) { bottom: 18%; left: 22%; animation-delay: 1.1s; }
.score-rune-dot:nth-child(5) { bottom: 12%; right: 28%; animation-delay: 0.2s; }
.score-rune-dot:nth-child(6) { top: 62%; right: 10%; animation-delay: 1.5s; }
.score-rune-dot:nth-child(7) { top: 8%; right: 42%; animation-delay: 0.6s; }
.score-rune-dot:nth-child(8) { bottom: 38%; right: 6%; animation-delay: 1.9s; }
.score-rune-glyph {
position: absolute;
font-size: 9px;
color: rgba(212, 175, 55, 0.55);
text-shadow: 0 0 8px rgba(212, 175, 55, 0.35);
animation: scoreRuneDrift 6s ease-in-out infinite;
}
.score-rune-glyph:nth-child(9) { top: 28%; left: 18%; animation-delay: 0s; }
.score-rune-glyph:nth-child(10) { top: 18%; right: 22%; animation-delay: 1.2s; }
.score-rune-glyph:nth-child(11) { bottom: 28%; right: 16%; animation-delay: 2.1s; }
.score-rune-glyph:nth-child(12) { bottom: 20%; left: 14%; animation-delay: 0.7s; }
.score-bagua-core {
position: absolute;
inset: 18%;
border-radius: 50%;
background:
radial-gradient(circle at 50% 50%, rgba(212, 175, 55, 0.08) 0%, rgba(212, 175, 55, 0.02) 42%, rgba(7, 12, 30, 0) 72%);
box-shadow: inset 0 0 30px rgba(212, 175, 55, 0.06);
}
.score-bagua-ring-2 {
position: absolute;
inset: 6%;
border-radius: 50%;
border: 1px solid rgba(212, 175, 55, 0.14);
box-shadow: 0 0 14px rgba(212, 175, 55, 0.08);
}
.score-bagua-symbols {
position: absolute;
inset: 0;
border-radius: 50%;
animation: rotate 26s linear infinite;
}
.score-bagua-symbol {
position: absolute;
left: 50%;
top: 50%;
color: rgba(212, 175, 55, 0.65);
font-size: 11px;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.18);
transform-origin: 0 -134px;
}
.score-bagua-symbol:nth-child(1) { transform: rotate(0deg) translateY(-134px) rotate(0deg); }
.score-bagua-symbol:nth-child(2) { transform: rotate(45deg) translateY(-134px) rotate(-45deg); }
.score-bagua-symbol:nth-child(3) { transform: rotate(90deg) translateY(-134px) rotate(-90deg); }
.score-bagua-symbol:nth-child(4) { transform: rotate(135deg) translateY(-134px) rotate(-135deg); }
.score-bagua-symbol:nth-child(5) { transform: rotate(180deg) translateY(-134px) rotate(-180deg); }
.score-bagua-symbol:nth-child(6) { transform: rotate(225deg) translateY(-134px) rotate(-225deg); }
.score-bagua-symbol:nth-child(7) { transform: rotate(270deg) translateY(-134px) rotate(-270deg); }
.score-bagua-symbol:nth-child(8) { transform: rotate(315deg) translateY(-134px) rotate(-315deg); }
.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;
}
.name {
display: block;
font-size: 28px;
font-weight: 700;
color: #e2e2e2;
letter-spacing: 0.2em;
margin-top: 16rpx;
margin-bottom: 8rpx;
}
.quote {
display: block;
color: #a0a0a0;
font-size: 14px;
line-height: 1.8;
}
.detail-entry {
margin-top: 16rpx;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8rpx 18rpx;
border-radius: 999rpx;
border: 1px solid rgba(212, 175, 55, 0.4);
background: rgba(212, 175, 55, 0.08);
}
.sixdim-entry {
margin-top: -20rpx;
margin-left: 24rpx;
}
.detail-entry-text {
font-size: 11px;
color: #d4af37;
letter-spacing: 0.08em;
}
.score-mystic-ornaments {
display: none;
}
/* —— 玄幻古风:模块外框 —— */
.mystic-score-card {
position: relative;
overflow: visible;
background:
linear-gradient(155deg, rgba(28, 32, 56, 0.95) 0%, rgba(14, 18, 36, 0.92) 45%, rgba(18, 22, 42, 0.96) 100%);
border: 1px solid rgba(212, 175, 55, 0.35);
border-radius: 16rpx;
box-shadow:
0 0 0 1px rgba(212, 175, 55, 0.12) inset,
0 16rpx 56rpx rgba(0, 0, 0, 0.5),
0 0 40rpx rgba(120, 90, 200, 0.12);
backdrop-filter: blur(14px);
}
.mystic-score-card__glow {
pointer-events: none;
position: absolute;
inset: -2rpx;
border-radius: 18rpx;
background: radial-gradient(ellipse 80% 60% at 50% 0%, rgba(212, 175, 55, 0.18), transparent 55%);
opacity: 0.85;
}
.mystic-panel {
position: relative;
margin-bottom: 48rpx;
padding: 28rpx 24rpx 32rpx;
border-radius: 16rpx;
background: linear-gradient(160deg, rgba(22, 26, 48, 0.92) 0%, rgba(10, 12, 28, 0.94) 100%);
border: 1px solid rgba(212, 175, 55, 0.28);
box-shadow:
0 0 0 1px rgba(212, 175, 55, 0.1) inset,
0 12rpx 48rpx rgba(0, 0, 0, 0.42),
0 0 32rpx rgba(80, 60, 140, 0.1);
backdrop-filter: blur(12px);
}
.mystic-panel::before {
content: "";
position: absolute;
inset: 10rpx;
border-radius: 12rpx;
border: 1px solid rgba(212, 175, 55, 0.14);
pointer-events: none;
}
.mystic-panel--extra {
opacity: 0.98;
border-style: dashed;
border-color: rgba(212, 175, 55, 0.22);
}
.mystic-section-title {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 22rpx;
padding-bottom: 18rpx;
border-bottom: none;
background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.2), transparent) bottom / 100% 1px no-repeat;
}
.section-glyph {
flex-shrink: 0;
min-width: 56rpx;
padding: 6rpx 10rpx;
text-align: center;
font-size: 22rpx;
font-weight: 700;
color: #c9a227;
letter-spacing: 0.12em;
background: linear-gradient(180deg, rgba(212, 175, 55, 0.22), rgba(212, 175, 55, 0.06));
border: 1px solid rgba(212, 175, 55, 0.35);
border-radius: 8rpx;
box-shadow: 0 0 16rpx rgba(212, 175, 55, 0.12);
}
.mystic-inner-card {
position: relative;
z-index: 1;
background: linear-gradient(165deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02)) !important;
border: 1px solid rgba(212, 175, 55, 0.18) !important;
border-radius: 12rpx !important;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.25);
}
.mystic-inner-card--flush {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
.section { margin-bottom: 48rpx; }
.mystic-panel.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 {
flex: 1;
font-size: 16px;
font-weight: 700;
color: #f0e8d8;
letter-spacing: 0.18em;
text-shadow: 0 0 20rpx rgba(212, 175, 55, 0.2);
}
.section-detail-tip {
margin-left: auto;
font-size: 11px;
color: #c9a227;
opacity: 0.9;
letter-spacing: 0.14em;
}
.card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12rpx;
padding: 32rpx;
}
.line {
display: block;
font-size: 14px;
color: #a0a0a0;
line-height: 1.8;
margin-bottom: 10rpx;
}
.line-strong { color: #e2e2e2; font-weight: 700; }
.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: 8rpx;
right: 20rpx;
font-size: 56px;
line-height: 1;
color: rgba(212, 175, 55, 0.18);
font-family: "Songti SC", "SimSun", serif;
}
.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 {
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: 8rpx; }
.zodiac-icon-wrap {
width: 80px;
height: 80px;
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; }
.info-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12rpx;
padding: 32rpx;
}
.wuge-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12rpx;
padding: 32rpx;
}
.wuxing-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12rpx;
padding: 32rpx;
}
.trait-line {
display: block;
font-size: 13px;
color: #a0a0a0;
line-height: 1.65;
margin-top: 8rpx;
}
.wuxing-adapt-grid,
.zodiac-adapt-grid {
margin: 10rpx 0 12rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.wuxing-adapt-item,
.zodiac-adapt-item {
display: block;
font-size: 13px;
line-height: 1.65;
color: #b8b8b8;
background: rgba(212, 175, 55, 0.05);
border: 1px solid rgba(212, 175, 55, 0.14);
border-radius: 12rpx;
padding: 8rpx 12rpx;
}
.wuxing-adapt-label,
.zodiac-adapt-label {
display: inline-block;
margin-right: 10rpx;
color: #d4af37;
font-weight: 700;
font-size: 12px;
}
.career-milestone {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.career-milestone:first-of-type {
margin-top: 0;
padding-top: 0;
border-top: none;
}
.lucky-color-row {
display: flex;
align-items: flex-start;
gap: 16rpx;
margin-bottom: 12rpx;
}
.lucky-color-swatch {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
flex-shrink: 0;
margin-top: 6rpx;
border: 1px solid rgba(255, 255, 255, 0.25);
}
.lucky-color-text {
flex: 1;
margin-bottom: 0;
}
.bottom-spacer { height: 80rpx; }
.modal-mask {
position: fixed;
inset: 0;
background:
radial-gradient(900px 520px at 50% 20%, rgba(212, 175, 55, 0.12), transparent 55%),
radial-gradient(900px 520px at 20% 80%, rgba(88, 120, 210, 0.10), transparent 55%),
rgba(0, 0, 0, 0.72);
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx;
z-index: 1000;
}
.detail-modal {
width: min(720rpx, 92vw);
max-height: 72vh;
border-radius: 12rpx;
overflow: hidden;
border: 1px solid rgba(212, 175, 55, 0.34);
background:
radial-gradient(900px 320px at 50% 0%, rgba(212, 175, 55, 0.14), transparent 60%),
linear-gradient(180deg, rgba(18, 16, 36, 0.985), rgba(8, 10, 18, 0.99));
box-shadow:
0 16rpx 52rpx rgba(0, 0, 0, 0.55),
0 0 0 1px rgba(212, 175, 55, 0.14) inset,
0 0 38rpx rgba(212, 175, 55, 0.08);
backdrop-filter: blur(10px);
position: relative;
}
.detail-modal-orn {
position: absolute;
width: 30rpx;
height: 30rpx;
pointer-events: none;
opacity: 0.65;
background:
radial-gradient(circle at 30% 30%, rgba(212, 175, 55, 0.34), transparent 55%),
linear-gradient(135deg, rgba(212, 175, 55, 0.55), rgba(212, 175, 55, 0.08));
mask:
linear-gradient(#000, #000) content-box,
linear-gradient(#000, #000);
-webkit-mask:
linear-gradient(#000, #000) content-box,
linear-gradient(#000, #000);
padding: 6rpx;
border-radius: 8rpx;
box-shadow: 0 0 14rpx rgba(212, 175, 55, 0.08);
}
.detail-modal-orn--tl { top: 8rpx; left: 8rpx; transform: rotate(0deg); }
.detail-modal-orn--tr { top: 8rpx; right: 8rpx; transform: rotate(90deg); }
.detail-modal-orn--bl { bottom: 8rpx; left: 8rpx; transform: rotate(-90deg); }
.detail-modal-orn--br { bottom: 8rpx; right: 8rpx; transform: rotate(180deg); }
.detail-modal-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14rpx 16rpx 12rpx;
border-bottom: 1px solid rgba(212, 175, 55, 0.22);
background:
linear-gradient(90deg, rgba(212, 175, 55, 0.10), rgba(212, 175, 55, 0.02), rgba(212, 175, 55, 0.10));
position: relative;
}
.detail-modal-card::after {
content: "";
position: absolute;
left: 16rpx;
right: 16rpx;
bottom: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.4), transparent);
opacity: 0.45;
}
.modal-title {
font-size: 14px;
color: #f4e6c8;
font-weight: 700;
letter-spacing: 0.1em;
text-shadow: 0 0 10rpx rgba(212, 175, 55, 0.16);
}
.modal-title-wrap {
display: flex;
align-items: center;
gap: 10rpx;
min-width: 0;
}
.modal-title-seal {
width: 30rpx;
height: 30rpx;
border-radius: 8rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 16rpx;
font-weight: 800;
color: rgba(255, 242, 210, 0.96);
letter-spacing: 0.12em;
background: linear-gradient(180deg, rgba(212, 175, 55, 0.34), rgba(212, 175, 55, 0.10));
border: 1px solid rgba(212, 175, 55, 0.42);
box-shadow:
0 0 0 1px rgba(212, 175, 55, 0.1) inset,
0 6rpx 12rpx rgba(0, 0, 0, 0.18);
}
.close {
width: 30rpx;
height: 30rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: rgba(255, 242, 210, 0.88);
line-height: 1;
border: 1px solid rgba(212, 175, 55, 0.28);
background:
radial-gradient(circle at 30% 30%, rgba(212, 175, 55, 0.14), rgba(212, 175, 55, 0.02) 58%, transparent 72%),
rgba(0, 0, 0, 0.16);
box-shadow:
0 0 0 1px rgba(212, 175, 55, 0.08) inset,
0 4rpx 10rpx rgba(0, 0, 0, 0.18);
transition: all 0.2s ease;
}
.detail-modal-body {
max-height: calc(72vh - 70rpx);
padding: 14rpx 16rpx 18rpx;
}
.node {
margin-bottom: 12rpx;
padding: 10rpx 10rpx 8rpx;
border-radius: 8rpx;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0.005));
border: 1px solid rgba(212, 175, 55, 0.08);
}
.node:last-child {
margin-bottom: 0;
}
.detail-modal-body .line {
color: rgba(228, 232, 244, 0.86);
margin-bottom: 8rpx;
line-height: 1.65;
}
.detail-modal-body .line-strong {
color: rgba(255, 246, 224, 0.94);
}
/* 桌面端:使用固定像素上限,避免 rpx 导致弹窗接近满屏 */
.test-name-detail--desktop .modal-mask {
padding: 20px;
}
.test-name-detail--desktop .detail-modal {
width: min(760px, 68vw);
max-width: 760px;
max-height: 74vh;
border-radius: 14px;
}
.test-name-detail--desktop .detail-modal-card {
padding: 12px 14px 10px;
}
.test-name-detail--desktop .detail-modal-body {
max-height: calc(74vh - 64px);
padding: 12px 14px 16px;
}
.test-name-detail--desktop .close {
width: 28px;
height: 28px;
font-size: 14px;
}
.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;
}
.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%;
}
@keyframes twinkle {
0%, 100% { opacity: 0.1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.2); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes scoreYinyangSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes scoreRuneTwinkle {
0%, 100% { opacity: 0.35; transform: scale(0.85); }
50% { opacity: 1; transform: scale(1.15); }
}
@keyframes scoreRuneDrift {
0%, 100% { opacity: 0.35; transform: translateY(0); }
50% { opacity: 0.85; transform: translateY(-3px); }
}
/* —— 电脑端(/test-name-live双列排版 + 登录页同系玻璃金边,减少纵向滚动 —— */
.test-name-detail--desktop {
min-height: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: linear-gradient(165deg, #070a12 0%, #12102a 42%, #0a1628 100%);
color: #e8e4dc;
}
.test-name-detail--desktop .starry-bg {
opacity: 0.28;
}
.test-name-detail--desktop .detail-header {
background: rgba(15, 23, 42, 0.72);
border-bottom: 1px solid rgba(212, 175, 55, 0.2);
backdrop-filter: blur(12px);
padding: 12px 18px !important;
}
.test-name-detail--desktop .detail-header-title {
color: #f2e6d8;
text-shadow: 0 0 20px rgba(212, 175, 55, 0.22);
}
.test-name-detail--desktop .detail-header-back {
color: #d4af37;
}
.test-name-detail--desktop .detail-content--desktop {
flex: 1;
min-height: 0;
height: auto !important;
max-height: none;
box-sizing: border-box;
padding: 92px 14px 16px !important;
display: flex;
flex-direction: column;
gap: 10px;
align-content: start;
/* 桌面端:内容宽度缩成约一半,居中更舒服 */
max-width: 660px !important;
margin: 0 auto !important;
}
.test-name-detail--desktop .score-card {
grid-column: auto;
margin-bottom: 0 !important;
padding: 12px 14px !important;
border-radius: 12px;
background: rgba(15, 23, 42, 0.55);
border: 1px solid rgba(212, 175, 55, 0.22);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35);
backdrop-filter: blur(10px);
}
/* 桌面端:缩小“分数”模块,避免过大导致空旷 */
.test-name-detail--desktop .score-label {
font-size: 8px !important;
margin-bottom: 6rpx !important;
}
.test-name-detail--desktop .score-value-wrap {
width: 184rpx !important;
height: 184rpx !important;
margin-bottom: 6rpx !important;
}
.test-name-detail--desktop .score-ring {
border-width: 1px !important;
border-style: dashed !important;
border-color: rgba(255, 224, 160, 0.62) !important;
box-shadow:
0 0 18px rgba(212, 175, 55, 0.35),
0 0 36px rgba(212, 175, 55, 0.12),
inset 0 0 14px rgba(255, 224, 160, 0.08);
}
.test-name-detail--desktop .score-bagua-ring-2 {
border-color: rgba(255, 224, 160, 0.42) !important;
box-shadow:
0 0 14px rgba(212, 175, 55, 0.28),
inset 0 0 10px rgba(212, 175, 55, 0.06) !important;
}
.test-name-detail--desktop .score-yinyang-bg {
opacity: 0.15;
inset: 20% !important;
}
.test-name-detail--desktop .score-rune-dot {
width: 2px;
height: 2px;
}
.test-name-detail--desktop .score-rune-glyph {
font-size: 8px;
}
.test-name-detail--desktop .score-bagua-symbol {
font-size: 10px;
transform-origin: 0 -88px;
}
.test-name-detail--desktop .score-bagua-symbol:nth-child(1) { transform: rotate(0deg) translateY(-88px) rotate(0deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(2) { transform: rotate(45deg) translateY(-88px) rotate(-45deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(3) { transform: rotate(90deg) translateY(-88px) rotate(-90deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(4) { transform: rotate(135deg) translateY(-88px) rotate(-135deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(5) { transform: rotate(180deg) translateY(-88px) rotate(-180deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(6) { transform: rotate(225deg) translateY(-88px) rotate(-225deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(7) { transform: rotate(270deg) translateY(-88px) rotate(-270deg); }
.test-name-detail--desktop .score-bagua-symbol:nth-child(8) { transform: rotate(315deg) translateY(-88px) rotate(-315deg); }
.test-name-detail--desktop .score {
font-size: 44px !important;
}
.test-name-detail--desktop .score-stars {
gap: 4rpx !important;
margin-bottom: 8rpx !important;
}
.test-name-detail--desktop .score-star {
font-size: 10px !important;
}
.test-name-detail--desktop .score-divider {
margin: 8rpx 0 !important;
}
.test-name-detail--desktop .name {
font-size: 18px !important;
margin-top: 6rpx !important;
margin-bottom: 4rpx !important;
}
.test-name-detail--desktop .quote {
font-size: 12px !important;
line-height: 1.45 !important;
}
.test-name-detail--desktop .detail-entry {
margin-top: 10rpx !important;
padding: 6rpx 14rpx !important;
}
.test-name-detail--desktop .score-mystic-ornaments {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
margin-top: 8px;
margin-bottom: 2px;
}
.test-name-detail--desktop .score-mystic-line {
width: min(320px, 72%);
height: 1px;
background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.45), transparent);
}
.test-name-detail--desktop .score-mystic-bagua {
display: flex;
align-items: center;
gap: 5px;
color: rgba(212, 175, 55, 0.9);
}
.test-name-detail--desktop .score-mystic-symbol {
font-size: 12px;
letter-spacing: 0.08em;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.2);
}
.test-name-detail--desktop .score-mystic-dot {
font-size: 10px;
color: rgba(212, 175, 55, 0.45);
}
.test-name-detail--desktop .score-mystic-sigil {
width: 24px;
height: 24px;
border-radius: 999px;
border: 1px solid rgba(212, 175, 55, 0.35);
background: radial-gradient(circle at 30% 30%, rgba(212, 175, 55, 0.16), rgba(212, 175, 55, 0.04));
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 14px rgba(212, 175, 55, 0.12);
}
.test-name-detail--desktop .score-mystic-sigil-text {
font-size: 12px;
color: #d4af37;
}
.test-name-detail--desktop .detail-entry-text {
font-size: 10px !important;
}
.test-name-detail--desktop .section {
margin-bottom: 0 !important;
}
.test-name-detail--desktop .detail-content--desktop > .section {
order: 999;
}
.test-name-detail--desktop .detail-content--desktop > .score-card {
order: 1;
}
.test-name-detail--desktop .module-order-1 {
order: 1;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-2 {
order: 2;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-3 {
order: 3;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-4 {
order: 4;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-5 {
order: 5;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-6 {
order: 6;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-7 {
order: 7;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-8 {
order: 8;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-9 {
order: 9;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-10 {
order: 10;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-11 {
order: 11;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-12 {
order: 12;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-13 {
order: 13;
}
.test-name-detail--desktop .detail-content--desktop > .section.module-order-14 {
order: 14;
}
.test-name-detail--desktop .section--desktop-wide {
grid-column: 1 / -1;
}
/* 六维格局:电脑端不再超大占位,保持与其它卡片一致密度 */
.test-name-detail--desktop .section--sixdim-compact {
grid-column: auto;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-section) {
margin-bottom: 0 !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-bar) {
margin-bottom: 6px !important;
gap: 8rpx !important;
padding: 0 2rpx !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-icon-grid) {
width: 20rpx !important;
height: 20rpx !important;
gap: 1.5rpx !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-icon-dot) {
width: 4rpx !important;
height: 4rpx !important;
border-radius: 1.5rpx !important;
opacity: 0.8;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-bar-title) {
font-size: 12px !important;
letter-spacing: 0.1em !important;
color: rgba(246, 236, 209, 0.95) !important;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.22);
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-card) {
padding: 8px 8px 6px !important;
border-radius: 10px !important;
background: rgba(15, 23, 42, 0.48) !important;
border: 1px solid rgba(212, 175, 55, 0.16) !important;
backdrop-filter: blur(8px);
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-radar) {
max-width: 170rpx !important;
padding-top: 0 !important;
height: 236px !important;
border-radius: 12rpx !important;
border: 1px solid rgba(212, 175, 55, 0.14);
background:
radial-gradient(circle at 50% 48%, rgba(245, 186, 54, 0.14), rgba(245, 186, 54, 0.02) 42%, transparent 72%),
linear-gradient(160deg, rgba(255, 255, 255, 0.025), rgba(255, 255, 255, 0));
box-shadow:
inset 0 0 14px rgba(212, 175, 55, 0.08),
0 4px 12px rgba(0, 0, 0, 0.24);
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-echart-inner) {
top: 0 !important;
inset: 0 !important;
height: 100% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-radar-svg) {
opacity: 0.84;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-radar-svg polygon) {
stroke: rgba(212, 220, 236, 0.58) !important;
stroke-width: 0.58 !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-radar-fill) {
inset: 31% !important;
background: linear-gradient(180deg, rgba(255, 205, 96, 0.86), rgba(230, 129, 38, 0.72)) !important;
opacity: 0.9 !important;
filter: drop-shadow(0 0 6px rgba(244, 176, 61, 0.2));
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label) {
font-size: 12rpx !important;
color: rgba(228, 232, 244, 0.84) !important;
text-shadow: 0 0 6px rgba(95, 126, 196, 0.14);
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-0) {
top: 10% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-1),
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-5) {
top: 29% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-1) {
right: 11% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-5) {
left: 11% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-2),
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-4) {
bottom: 30% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-2) {
right: 9% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-4) {
left: 9% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-label-3) {
bottom: 8% !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-remark-center) {
margin-top: 6px !important;
padding: 0 4px !important;
}
.test-name-detail--desktop .section--sixdim-compact :deep(.sixdim-remark) {
font-size: 9.5px !important;
line-height: 1.35 !important;
}
.test-name-detail--desktop .section-title,
.test-name-detail--desktop .mystic-section-title {
margin-bottom: 6px !important;
padding-bottom: 4px !important;
border-bottom: none !important;
background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.22), transparent) bottom / 100% 1px no-repeat !important;
}
.test-name-detail--desktop .section-text {
font-size: 16px !important;
letter-spacing: 0.12em !important;
}
.test-name-detail--desktop .section-detail-tip {
font-size: 12px !important;
opacity: 0.75;
}
.test-name-detail--desktop .meaning-card,
.test-name-detail--desktop .zodiac-card,
.test-name-detail--desktop .info-card,
.test-name-detail--desktop .wuge-card,
.test-name-detail--desktop .wuxing-card,
.test-name-detail--desktop .mystic-inner-card {
padding: 12px 14px !important;
border-radius: 10px;
background: linear-gradient(165deg, rgba(255, 255, 255, 0.07), rgba(15, 23, 42, 0.45)) !important;
border: 1px solid rgba(212, 175, 55, 0.22) !important;
backdrop-filter: blur(8px);
margin-bottom: 0 !important;
}
.test-name-detail--desktop .mystic-inner-card--flush {
padding: 0 !important;
background: transparent !important;
border: none !important;
}
.test-name-detail--desktop .mystic-panel {
padding: 14px 16px 16px !important;
border-radius: 12px;
}
.test-name-detail--desktop .section-glyph {
min-width: 36px;
font-size: 13px;
padding: 4px 8px;
}
.test-name-detail--desktop .line {
font-size: 13px !important;
line-height: 1.55 !important;
}
.test-name-detail--desktop .name {
font-size: 22px !important;
}
.test-name-detail--desktop .quote {
font-size: 13px !important;
line-height: 1.5 !important;
}
.test-name-detail--desktop .bottom-spacer {
display: none;
}
@media (min-width: 1200px) {
.test-name-detail--desktop .detail-content--desktop {
display: flex;
flex-direction: column;
grid-template-columns: none;
}
.test-name-detail--desktop .score-card {
grid-column: auto;
}
/* 六维格局:三列大屏下占两列,避免单列显得突兀/空旷 */
.test-name-detail--desktop .section--sixdim-compact {
grid-column: auto;
}
}
</style>