2874 lines
94 KiB
Vue
2874 lines
94 KiB
Vue
<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, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
|
||
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>
|