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

377 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="mystic-compass-wrapper" :class="{ 'mystic-compass-wrapper--desktop': desktop }">
<!-- 手机端可退出电脑端测名仅等待结果不展示退出 -->
<view v-if="!desktop" class="compass-back-btn" @click="handleBack">
<text class="compass-back-icon">×</text>
</view>
<view class="mystic-compass">
<view class="compass-glow"></view>
<view class="compass-svg-wrap">
<!-- 外圈虚线圆 -->
<view class="circle-outer"></view>
<view class="circle-main"></view>
<!-- 天干地支 -->
<view v-for="(char, i) in runes" :key="'rune-' + i" class="rune-char" :class="{ active: i === activeCharIndex }"
:style="getRuneStyle(i)">
{{ char }}
</view>
<!-- 八卦符号 - 旋转圈 -->
<view class="bagua-circle" :style="{ transform: 'rotate(' + baGuaRotation + 'deg)' }">
<view v-for="(gua, i) in baGua" :key="'gua-' + i" class="bagua-char" :style="getBaGuaStyle(i)">
{{ gua }}
</view>
</view>
<!-- 中心罗盘指针 -->
<view class="compass-pointer" :style="{ transform: 'rotate(' + pointerRotation + 'deg)' }">
<view class="pointer-bar"></view>
<view class="pointer-dot"></view>
</view>
</view>
</view>
<!-- 加载文本 -->
<view class="loading-text">
<view class="loading-title">{{ title }}</view>
<view class="loading-subtitle">{{ subtitle }}</view>
<view v-if="!desktop" class="loading-tip">
分析时间预计1-2分钟可点击返回按钮退出并在我的方案中查看结果
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
interface Props {
title?: string;
subtitle?: string;
/** 电脑端网页:不显示退出按钮与「预计时间 / 我的方案」提示 */
desktop?: boolean;
}
withDefaults(defineProps<Props>(), {
title: '天机推演中',
subtitle: '易经数理 · 五行生克 · 三才五格',
desktop: false,
});
const emit = defineEmits<{
back: [];
}>();
const handleBack = () => {
emit('back');
};
const runes = [
"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸",
"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"
];
const baGua = ["☰", "☱", "☲", "☳", "☴", "☵", "☶", "☷"];
const activeCharIndex = ref(-1);
const baGuaRotation = ref(0);
const pointerRotation = ref(0);
let runeInterval: number | null = null;
let baGuaInterval: number | null = null;
let pointerInterval: number | null = null;
const getRuneStyle = (index: number) => {
const angle = (index * 360) / runes.length;
const radius = 120; // px
const angleRad = (angle - 90) * (Math.PI / 180);
const x = 150 + radius * Math.cos(angleRad);
const y = 150 + radius * Math.sin(angleRad);
return {
left: x + 'px',
top: y + 'px',
transform: `translate(-50%, -50%) rotate(${angle}deg)`
};
};
const getBaGuaStyle = (index: number) => {
const angle = (index * 360) / 8;
const radius = 72.5; // px
const angleRad = (angle - 90) * (Math.PI / 180);
// .bagua-circle 的宽高是 180px中心点在 (90, 90)
const x = 90 + radius * Math.cos(angleRad);
const y = 90 + radius * Math.sin(angleRad);
return {
left: x + 'px',
top: y + 'px',
transform: `translate(-50%, -50%) rotate(${angle}deg)`
};
};
onMounted(() => {
// 天干地支闪烁
let count = 0;
runeInterval = setInterval(() => {
activeCharIndex.value = count % runes.length;
count++;
}, 100);
// 八卦旋转
baGuaInterval = setInterval(() => {
baGuaRotation.value += 0.25;
}, 30);
// 指针旋转
let pointerCount = 0;
pointerInterval = setInterval(() => {
pointerCount += 1;
pointerRotation.value = pointerCount * 1.2;
}, 10);
});
onUnmounted(() => {
if (runeInterval) clearInterval(runeInterval);
if (baGuaInterval) clearInterval(baGuaInterval);
if (pointerInterval) clearInterval(pointerInterval);
});
</script>
<style scoped>
.mystic-compass-wrapper--desktop {
background: radial-gradient(ellipse 80% 60% at 50% 30%, rgba(212, 175, 55, 0.06), #0a0a0a 55%);
}
.mystic-compass-wrapper {
position: fixed;
inset: 0;
z-index: 9999;
width: 100vw;
height: 100vh;
min-height: -webkit-fill-available;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 48px;
background: #0a0a0a;
overflow: hidden;
}
.compass-back-btn {
position: absolute;
top: 24px;
left: 24px;
top: calc(24px + env(safe-area-inset-top, 0px));
width: 48px;
height: 48px;
border-radius: 50%;
background: rgba(212, 175, 55, 0.1);
border: 1px solid rgba(212, 175, 55, 0.3);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
cursor: pointer;
transition: all 0.3s;
}
.compass-back-btn:active {
background: rgba(212, 175, 55, 0.2);
transform: scale(0.95);
}
.compass-back-icon {
font-size: 32px;
color: #d4af37;
font-weight: 300;
line-height: 1;
}
.mystic-compass {
position: relative;
width: min(300px, 80vw);
height: min(300px, 80vw);
display: flex;
align-items: center;
justify-content: center;
min-width: 260px;
min-height: 260px;
}
.compass-glow {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #d4af37;
filter: blur(30px);
opacity: 0.15;
animation: pulse 4s ease-in-out infinite;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
@keyframes pulse {
0%,
100% {
opacity: 0.1;
transform: translate(-50%, -50%) scale(0.95);
}
50% {
opacity: 0.3;
transform: translate(-50%, -50%) scale(1.05);
}
}
.compass-svg-wrap {
position: relative;
width: 300px;
height: 300px;
transform: scale(min(1, calc(80vw / 300)));
transform-origin: center center;
}
.circle-outer {
position: absolute;
left: 5px;
top: 5px;
width: 290px;
height: 290px;
border-radius: 50%;
border: 1px dashed #d4af37;
opacity: 0.2;
}
.circle-main {
position: absolute;
left: 10px;
top: 10px;
width: 280px;
height: 280px;
border-radius: 50%;
border: 2px solid #d4af37;
opacity: 0.3;
}
.rune-char {
position: absolute;
font-size: 12px;
color: #5a5a5a;
font-family: SimSun, serif;
transition: all 0.2s;
}
.rune-char.active {
color: #d4af37;
font-weight: bold;
text-shadow: 0 0 5px #d4af37;
}
.bagua-circle {
position: absolute;
left: 60px;
top: 60px;
width: 180px;
height: 180px;
border-radius: 50%;
border: 2px solid rgba(212, 175, 55, 0.1);
transition: transform 0.3s linear;
}
.bagua-char {
position: absolute;
font-size: 24px;
color: #d4af37;
opacity: 0.6;
font-family: SimSun, serif;
}
.compass-pointer {
position: absolute;
left: 150px;
top: 150px;
width: 0;
height: 0;
transition: transform 0.05s linear;
}
.pointer-bar {
position: absolute;
left: 50%;
top: -80px;
width: 2px;
height: 80px;
background-color: #9c2a2a;
transform: translateX(-50%);
}
.pointer-dot {
position: absolute;
left: 50%;
top: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #d4af37;
transform: translate(-50%, -50%);
}
/* Loading Text */
.loading-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
animation: fade 2.5s ease-in-out infinite;
}
@keyframes fade {
0%,
100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}
.loading-title {
color: #d4af37;
letter-spacing: 0.4em;
font-size: 20px;
font-weight: bold;
text-shadow: 0 0 10px rgba(212, 175, 55, 0.5);
font-family: SimSun, "Songti SC", serif;
}
.loading-subtitle {
color: #a0a0a0;
letter-spacing: 0.2em;
font-size: 12px;
text-align: center;
flex-wrap: wrap;
padding: 0 20px;
font-family: SimSun, "Songti SC", serif;
}
.loading-tip {
margin-top: 6px;
max-width: min(520px, 86vw);
text-align: center;
color: rgba(226, 226, 226, 0.72);
letter-spacing: 0.06em;
font-size: 11px;
line-height: 1.6;
font-family: SimSun, "Songti SC", serif;
}
</style>