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

1527 lines
39 KiB
Vue
Raw Permalink 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="company-analysis">
<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 class="glow-1" />
<view class="glow-2" />
</view>
<view v-if="stage !== 'result'" class="loading-wrap">
<view class="loading-ring">
<view class="loading-ring-inner" />
<view class="loading-ring-top" />
<image class="loading-icon" :src="icons.building" mode="aspectFit" />
</view>
<text class="loading-title">商道推演</text>
<text class="loading-subtitle">解析行业五行与团队命理...</text>
</view>
<view v-else class="result-wrap">
<view class="top-nav">
<view class="nav-btn" @click="emit('back')">
<image class="nav-icon" :src="icons.chevronLeft" mode="aspectFit" />
<text class="nav-text">重测</text>
</view>
<text class="nav-title">企业运程</text>
<!-- <view class="nav-btn" @click="handleShare">
<image class="nav-icon nav-icon-share" :src="icons.share" mode="aspectFit" />
</view> -->
</view>
<scroll-view scroll-y class="content-scroll">
<view class="content-inner">
<view class="score-card" @click="openModal('header')">
<view class="score-card-deco" />
<view class="score-top">
<view class="score-left">
<text class="score-name">{{ result?.header?.name || '' }}</text>
<view class="score-tags">
<text v-if="result?.header?.tagLeft" class="score-tag">{{ result.header.tagLeft }}</text>
<text v-if="result?.header?.tagRight" class="score-tag score-tag-gold">{{ result.header.tagRight }}</text>
</view>
</view>
<view class="score-right">
<text class="score-label">Score</text>
<text class="score-value">{{ result?.header?.score ?? '' }}</text>
</view>
</view>
<view class="divider" />
<view class="quote-row">
<image class="quote-icon" :src="icons.quote" mode="aspectFit" />
<text class="quote-text">{{ result?.header?.quote || '' }}</text>
</view>
</view>
<view class="section" @click="openModal('characterAnalysis')">
<view class="section-header">
<image class="section-icon" :src="icons.scroll" mode="aspectFit" />
<text class="section-title">字义数理</text>
</view>
<view class="char-row">
<view v-for="(c, i) in result?.characterAnalysis?.characters || []" :key="i" class="char-card">
<view class="char-element-wrap">
<text class="char-element" :class="getElementClass(c.element)">{{ c.element }}</text>
</view>
<text class="char-char">{{ c.char }}</text>
<text class="char-stroke">{{ c.stroke }}</text>
<text class="char-meaning">{{ c.meaning }}</text>
</view>
</view>
<text class="section-paragraph">
<text class="section-paragraph-highlight">解析</text>{{ result?.characterAnalysis?.analysis || '' }}
</text>
</view>
<view class="section" @click="openModal('businessPattern')">
<view class="section-header">
<image class="section-icon" :src="icons.target" mode="aspectFit" />
<text class="section-title">商业格局</text>
</view>
<view class="radar-card">
<view class="biz-sixdim-radar">
<svg class="biz-sixdim-svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<polygon
points="50,6 90,28 90,72 50,94 10,72 10,28"
fill="none"
stroke="rgba(212, 175, 55, 0.22)"
stroke-width="0.8"
/>
<polygon
points="50,22 78,36 78,64 50,78 22,64 22,36"
fill="none"
stroke="rgba(212, 175, 55, 0.14)"
stroke-width="0.6"
/>
</svg>
<view
v-for="(label, idx) in radarLabels"
:key="idx"
class="biz-sixdim-label"
:class="'biz-sixdim-label-' + idx"
>
<text>{{ label }}</text>
</view>
<view class="biz-sixdim-fill" :style="bizSixdimFillStyle" />
</view>
<view class="radar-summary-grid">
<view v-for="(item, i) in result?.businessPattern?.summary || []" :key="i" class="radar-summary-item">
<text class="radar-summary-label">{{ item.label }}</text>
<text class="radar-summary-value">{{ item.value }}</text>
</view>
</view>
</view>
</view>
<view class="section" @click="openModal('gua')">
<view class="section-header">
<image class="section-icon" :src="icons.award" mode="aspectFit" />
<text class="section-title">商号卦象</text>
</view>
<view class="gua-card">
<text class="gua-bg">{{ result?.gua?.bg || '' }}</text>
<view class="gua-top">
<text class="gua-name">{{ result?.gua?.name || '' }}</text>
<text class="gua-badge">{{ result?.gua?.badge || '' }}</text>
</view>
<text class="gua-desc">{{ result?.gua?.desc || '' }}</text>
<view class="gua-tags">
<text v-for="(t, i) in result?.gua?.tags || []" :key="i" class="gua-tag">{{ t }}</text>
</view>
<view class="divider" />
<text class="gua-insight-title">商业启示</text>
<text class="gua-insight-text">{{ result?.gua?.insight || '' }}</text>
</view>
</view>
<view class="section" @click="openModal('team')">
<view class="section-header">
<image class="section-icon" :src="icons.users" mode="aspectFit" />
<text class="section-title">核心团队契合度</text>
</view>
<view class="team-card">
<view v-for="(m, i) in result?.team?.members || []" :key="i" class="team-row">
<view class="team-left">
<view class="team-avatar"><text class="team-avatar-text">{{ (m.role || '').slice(0, 1) }}</text></view>
<view class="team-info">
<text class="team-role">{{ m.role }}</text>
<text class="team-desc">{{ m.desc }}</text>
</view>
</view>
<view class="team-right">
<text class="team-match" :class="{ 'team-match-gold': m.score >= 90 }">{{ m.match }}</text>
</view>
</view>
</view>
<text class="team-note">{{ result?.team?.note || '' }}</text>
</view>
<view class="section" @click="openModal('years')">
<view class="section-header">
<image class="section-icon" :src="icons.sparkles" mode="aspectFit" />
<text class="section-title">流年运势推演</text>
</view>
<scroll-view scroll-x class="year-scroll">
<view class="year-row">
<view v-for="(y, i) in result?.years?.items || []" :key="i" class="year-card">
<text class="year-year">{{ y.year }}</text>
<text class="year-luck" :class="{ 'year-luck-gold': String(y.luck || '').includes('吉') }">{{ y.luck }}</text>
<view class="year-divider" />
<text class="year-text">{{ y.text }}</text>
</view>
</view>
</scroll-view>
</view>
<view class="grid-2">
<view class="small-card" @click="openModal('wealthTrend')">
<view class="small-card-header">
<image class="small-card-icon" :src="icons.trending" mode="aspectFit" />
<text class="small-card-title">财运走势</text>
</view>
<view class="bars">
<view v-for="(h, i) in result?.wealthTrend?.bars || []" :key="i" class="bar-bg">
<view class="bar-fill" :style="{ height: String(h) + '%' }" />
</view>
</view>
<text class="small-card-note">{{ result?.wealthTrend?.note || '' }}</text>
</view>
<view class="small-card" @click="openModal('direction')">
<view class="small-card-header">
<image class="small-card-icon" :src="icons.mapPin" mode="aspectFit" />
<text class="small-card-title">吉凶方位</text>
</view>
<view class="compass">
<text class="compass-n compass-gold"></text>
<text class="compass-s"></text>
<text class="compass-w">西</text>
<text class="compass-e compass-gold"></text>
<view class="compass-dot" :style="goodDotStyle" />
</view>
<text class="small-card-note">{{ result?.direction?.note || '' }}</text>
</view>
</view>
<view class="section" @click="openModal('layout')">
<view class="section-header">
<image class="section-icon" :src="icons.layout" mode="aspectFit" />
<text class="section-title">办公布局指引</text>
</view>
<view class="layout-card">
<view v-for="(it, i) in result?.layout?.items || []" :key="i" class="layout-row">
<view class="layout-dot" />
<text class="layout-text">
<text class="layout-strong">{{ it.strong }}</text>{{ it.textBefore }}
<text v-for="(h, hi) in it.highlights || []" :key="hi" class="layout-highlight">{{ h }}</text>{{ it.textAfter }}
</text>
</view>
</view>
</view>
<view class="exec-card" @click="openModal('execution')">
<view class="exec-row">
<image class="exec-icon" :src="icons.zap" mode="aspectFit" />
<view class="exec-body">
<text class="exec-title">执行建议</text>
<text class="exec-text">
{{ result?.execution?.text || '' }}
</text>
</view>
</view>
</view>
</view>
</scroll-view>
<view v-if="showModal" class="modal-overlay" @click="closeModal">
<view class="modal-container" @click.stop>
<view class="modal-header">
<text class="modal-title">{{ modalTitle }}</text>
<text class="modal-close" @click="closeModal">×</text>
</view>
<scroll-view scroll-y class="modal-body">
<template v-for="(node, i) in modalNodes" :key="i">
<text v-if="node.type === 'text'" class="modal-text">{{ node.text }}</text>
<view v-else-if="node.type === 'list'" class="modal-list">
<view v-for="(it, li) in node.items" :key="li" class="modal-list-item">
<view class="modal-list-dot" />
<text class="modal-list-text">{{ it }}</text>
</view>
</view>
<view v-else-if="node.type === 'kv'" class="modal-kv">
<view v-for="(kv, ki) in node.items" :key="ki" class="modal-kv-row">
<text class="modal-kv-label">{{ kv.label }}</text>
<text class="modal-kv-value">{{ kv.value }}</text>
</view>
</view>
</template>
</scroll-view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { namingApi } from '@/api';
import type { CompanyAnalysisRequest, CompanyAnalysisResponse, CompanyAnalysisDetailNode, CompanyAnalysisDetails } from '@/api';
declare const uni: any;
type ElementType = '金' | '木' | '水' | '火' | '土' | string;
const props = defineProps<{
params?: CompanyAnalysisRequest | CompanyAnalysisResponse | null;
data?: CompanyAnalysisResponse | null;
}>();
const emit = defineEmits<{
back: [];
}>();
const stage = ref<'loading' | 'result'>('loading');
const fetched = ref<CompanyAnalysisResponse | null>(null);
const result = computed(() => props.data || fetched.value || null);
const showModal = ref(false);
const currentModal = ref<
'header' | 'characterAnalysis' | 'businessPattern' | 'gua' | 'team' | 'years' | 'wealthTrend' | 'direction' | 'layout' | 'execution' | ''
>('');
const modalPayload = computed<CompanyAnalysisDetails | null>(() => {
const r = result.value;
if (!r) return null;
if (currentModal.value === 'header') return r.header?.details || null;
if (currentModal.value === 'characterAnalysis') return r.characterAnalysis?.details || null;
if (currentModal.value === 'businessPattern') return r.businessPattern?.details || null;
if (currentModal.value === 'gua') return r.gua?.details || null;
if (currentModal.value === 'team') return r.team?.details || null;
if (currentModal.value === 'years') return r.years?.details || null;
if (currentModal.value === 'wealthTrend') return r.wealthTrend?.details || null;
if (currentModal.value === 'direction') return r.direction?.details || null;
if (currentModal.value === 'layout') return r.layout?.details || null;
if (currentModal.value === 'execution') return r.execution?.details || null;
return null;
});
const modalTitle = computed(() => modalPayload.value?.title || '详情');
const modalNodes = computed<CompanyAnalysisDetailNode[]>(() => modalPayload.value?.nodes || []);
const openModal = (modalType: typeof currentModal.value) => {
currentModal.value = modalType;
if (!modalPayload.value) {
uni.showToast({ title: '暂无详情', icon: 'none' });
return;
}
showModal.value = true;
};
const closeModal = () => {
showModal.value = false;
};
const isCompanyAnalysisResponse = (p: unknown): p is CompanyAnalysisResponse => {
if (!p || typeof p !== 'object') return false;
const obj = p as any;
return !!(obj.header && obj.characterAnalysis && obj.businessPattern && obj.gua);
};
const fetchFromApi = async (params: CompanyAnalysisRequest) => {
const start = Date.now();
stage.value = 'loading';
try {
const res = await namingApi.companyAnalysis(params);
fetched.value = res;
} catch (e: any) {
fetched.value = null;
uni.showToast({ title: e?.msg || '加载失败', icon: 'none' });
} finally {
const elapsed = Date.now() - start;
const remain = Math.max(0, 2500 - elapsed);
setTimeout(() => {
stage.value = 'result';
}, remain);
}
};
const stars = ref(
Array.from({ length: 50 }).map((_, i) => ({
id: i,
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
size: Math.random() * 2 + 1,
duration: Math.random() * 3 + 2,
delay: Math.random() * 2
}))
);
const icons = {
chevronLeft:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTUgMTggOSA xMiAxNSA2IiBzdHJva2U9IiNhMGEwYTAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PC9zdmc+'.replace(/\s/g, ''),
share:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTggMTZBMyAzIDAgMSAwIDE4IDEwQTMgMyAwIDAgMCAxOCAxNlpNMTggMTZMMTggMTZMMTggMTZaTTE4IDEwTDggMTMuNU04IDEzLjVMNiAxMk04IDEzLjVMNiAxNU02IDEyQTMgMyAwIDEgMCA2IDE4QTMgMyAwIDAgMCA2IDEyWk02IDE1QTMgMyAwIDEgMCA2IDlBMyAzIDAgMCAwIDYgMTVaIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=',
building:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMyAyMVY5bDkgLTUgOSA1djEySDEyVjE1SDl2NmgtNnoiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0xMiA5aC4wMSIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+',
quote:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2Q0YWYzNyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTQgMjF2LTNhMiAyIDAgMCAxIDItMmgzYTIgMiAwIDAgMCAyLTJWOWEyIDIgMCAwIDAtMi0yaC00YTIgMiAwIDAgMC0yIDJ2MmEyIDIgMCAwIDEtMiAyaC0xVjVoMTJ2MTBhNiA2IDAgMCAxLTYgNnptLTkgMHYtM2EyIDIgMCAwIDEgMi0yaDNhMiAyIDAgMCAwIDItMlY5YTIgMiAwIDAgMC0yLTJINmEyIDIgMCAwIDAtMiAyvB/vPjwvc3ZnPg=='.replace(/\s/g, ''),
scroll:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTkgM0g4YTIgMiAwIDAgMC0yIDJ2MTRhMiAyIDAgMCAwIDIgMmgxMW0wLTIwYTIgMiAwIDAgMSAyIDJ2MTRhMiAyIDAgMCAxLTIgMkg4bTExLTQgaC05IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=',
target:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSI5IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ii8+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iNSIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjEuNSIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEiIGZpbGw9IiNkNGFmMzciLz48L3N2Zz4=',
award:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMTdhNSA1IDAgMSAwIDAtMTAgNSA1IDAgMCAwIDAgMTB6IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTEyIDE3djQgMi0xIDItMyAyIDEtMSAyLTMgMiAxVjE3IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=',
users:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTcgMjF2LTJhNCA0IDAgMCAwLTQtNEg5YTQgNCAwIDAgMC00IDR2MiIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTExIDEzYTQgNCAwIDEgMCAwLTggNCA0IDAgMCAwIDAgOHoiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48L3N2Zz4=',
sparkles:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMmwyIDEwIDEwIDItMTAgMi0yIDEwLTItMTAtMTAtMiAxMC0yLTIgMTAtMi0xMC0yLTEwIDItMi0xMCAyLTEwIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4=',
trending:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cG9seWxpbmUgcG9pbnRzPSIyMyA2IDEzLjUgMTUuNSA4LjUgMTAuNSA xIDE4IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cG9seWxpbmUgcG9pbnRzPSIxNyA2IDIzIDYgMjMgMTIiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg=='.replace(/\s/g, ''),
mapPin:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMjFzNi00LjM1IDYtMTBhNiA2IDAgMSAwLTEyIDBjMCA1LjY1IDYgMTAgNiAxMHoiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjExIiByPSIyIiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ii8+PC9zdmc+',
layout:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIzIiB5PSIzIiB3aWR0aD0iNyIgaGVpZ2h0PSI3IiBzdHJva2U9IiNkNGFmMzciIHN0cm9rZS13aWR0aD0iMS41Ii8+PHJlY3QgeD0iMTQiIHk9IjMiIHdpZHRoPSI3IiBoZWlnaHQ9IjciIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cmVjdCB4PSIzIiB5PSIxNCIgd2lkdGg9IjciIGhlaWdodD0iNyIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjEuNSIvPjxyZWN0IHg9IjE0IiB5PSIxNCIgd2lkdGg9IjciIGhlaWdodD0iNyIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjEuNSIvPjwvc3ZnPg==',
zap:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cG9seWxpbmUgcG9pbnRzPSIxMyAyIDMgMTQgMTIgMTQgMTEgMjIgMjEgMTAiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjwvc3ZnPg=='
};
const getElementClass = (element: ElementType) => {
if (element === '火') return 'el-fire';
if (element === '金') return 'el-gold';
return 'el-default';
};
const radarLabels = computed(() => result.value?.businessPattern?.radar?.labels || ['财运', '名望', '团队', '机遇', '竞力', '基业']);
const radarValues = computed(() => result.value?.businessPattern?.radar?.values || [0, 0, 0, 0, 0, 0]);
const bizSixdimFillStyle = computed(() => {
const maxValue = 100;
const centerX = 50;
const centerY = 50;
const baseRadius = 44;
const values = radarValues.value;
const points = Array.from({ length: 6 }).map((_, idx) => {
const raw = Number(values[idx] ?? 0);
const value = Math.max(0, Math.min(100, raw));
const angle = (idx * 60 - 90) * (Math.PI / 180);
const radius = (value / maxValue) * baseRadius;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
return { x, y };
});
const path = points.map((p) => `${p.x}% ${p.y}%`).join(', ');
return {
clipPath: `polygon(${path})`
};
});
const goodDotStyle = computed(() => {
const dot = result.value?.direction?.goodDot;
if (!dot) return {};
return {
left: `${dot.x}%`,
top: `${dot.y}%`
};
});
const handleShare = () => {
uni.showToast({ title: '敬请期待', icon: 'none' });
};
watch(
() => props.params,
(p: CompanyAnalysisRequest | CompanyAnalysisResponse | null | undefined) => {
if (p && isCompanyAnalysisResponse(p)) {
fetched.value = p;
stage.value = 'loading';
setTimeout(() => {
stage.value = 'result';
}, 2500);
return;
}
if (p) {
fetchFromApi(p as CompanyAnalysisRequest);
return;
}
stage.value = 'loading';
setTimeout(() => {
stage.value = 'result';
}, 2500);
},
{ immediate: true }
);
watch(
() => props.data,
(d: CompanyAnalysisResponse | null | undefined) => {
if (!d) return;
fetched.value = null;
stage.value = 'loading';
setTimeout(() => {
stage.value = 'result';
}, 2500);
}
);
onMounted(() => {
stage.value = 'loading';
});
</script>
<style scoped>
.company-analysis {
height: 100vh;
position: relative;
overflow: hidden;
font-family: SimSun, "Songti SC", "Songti TC", "Noto Serif SC", STSong, serif;
color: #e2e2e2;
background: #0a0a0f;
}
.starry-bg {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background: linear-gradient(to bottom, #050508, #0f1020, #1a1a2e);
}
.star {
position: absolute;
border-radius: 50%;
background: #fff;
opacity: 0.2;
animation: twinkle 3s ease-in-out infinite;
}
@keyframes twinkle {
0%,
100% {
opacity: 0.1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(1.2);
}
}
.glow-1 {
position: absolute;
top: -10%;
right: -10%;
width: 60%;
height: 60%;
background: #d4af37;
opacity: 0.05;
filter: blur(120px);
border-radius: 50%;
}
.glow-2 {
position: absolute;
bottom: -10%;
left: -10%;
width: 50%;
height: 50%;
background: #2a3d5d;
opacity: 0.1;
filter: blur(100px);
border-radius: 50%;
}
.loading-wrap {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48rpx;
text-align: center;
position: relative;
z-index: 10;
}
.loading-ring {
width: 256rpx;
height: 256rpx;
border: 1px solid rgba(212, 175, 55, 0.3);
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 64rpx;
animation: spin 10s linear infinite;
}
.loading-ring-inner {
position: absolute;
inset: 16rpx;
border: 1px solid rgba(212, 175, 55, 0.1);
border-radius: 999rpx;
}
.loading-ring-top {
position: absolute;
inset: 0;
border-top: 4rpx solid #d4af37;
border-radius: 999rpx;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.loading-icon {
width: 64rpx;
height: 64rpx;
opacity: 0.8;
}
.loading-title {
color: #d4af37;
letter-spacing: 0.4em;
font-size: 40rpx;
font-weight: 700;
}
.loading-subtitle {
margin-top: 24rpx;
color: #a0a0a0;
letter-spacing: 0.2em;
font-size: 22rpx;
}
.result-wrap {
position: relative;
z-index: 10;
height: 100%;
display: flex;
flex-direction: column;
}
.top-nav {
padding: 32rpx;
padding-top: calc(32rpx + env(safe-area-inset-top, 0px));
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(10, 10, 15, 0.5);
backdrop-filter: blur(12px);
}
.nav-btn {
display: flex;
align-items: center;
gap: 8rpx;
min-width: 120rpx;
}
.nav-icon {
width: 36rpx;
height: 36rpx;
}
.nav-icon-share {
width: 38rpx;
height: 38rpx;
}
.nav-text {
font-size: 22rpx;
letter-spacing: 0.2em;
color: #a0a0a0;
}
.nav-title {
font-size: 28rpx;
font-weight: 700;
letter-spacing: 0.3em;
color: #d4af37;
}
.content-scroll {
flex: 1;
height: 0;
}
.content-inner {
padding: 40rpx;
padding-bottom: 160rpx;
}
.score-card {
background: linear-gradient(135deg, #1a1a2e, #10101a);
border: 1px solid rgba(212, 175, 55, 0.2);
padding: 48rpx;
position: relative;
overflow: hidden;
margin-bottom: 48rpx;
border-radius: 8rpx;
}
.score-card-deco {
position: absolute;
top: 0;
right: 0;
width: 200rpx;
height: 200rpx;
background: #d4af37;
opacity: 0.03;
border-bottom-left-radius: 999rpx;
}
.score-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
position: relative;
z-index: 1;
}
.score-name {
font-size: 44rpx;
font-weight: 700;
letter-spacing: 0.2em;
margin-bottom: 16rpx;
}
.score-tags {
display: flex;
gap: 16rpx;
margin-top: 12rpx;
}
.score-tag {
font-size: 18rpx;
color: #a0a0a0;
border: 1px solid rgba(160, 160, 160, 0.3);
padding: 6rpx 12rpx;
border-radius: 8rpx;
}
.score-tag-gold {
color: #d4af37;
border-color: rgba(212, 175, 55, 0.3);
background: rgba(212, 175, 55, 0.05);
}
.score-right {
text-align: right;
}
.score-label {
display: block;
font-size: 18rpx;
color: #d4af37;
letter-spacing: 0.2em;
opacity: 0.8;
text-transform: uppercase;
}
.score-value {
display: block;
font-size: 96rpx;
font-weight: 700;
color: #d4af37;
font-family: serif;
}
.divider {
height: 2rpx;
width: 100%;
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1), transparent);
margin: 32rpx 0;
}
.quote-row {
display: flex;
gap: 16rpx;
align-items: flex-start;
}
.quote-icon {
width: 28rpx;
height: 28rpx;
margin-top: 6rpx;
opacity: 0.5;
}
.quote-text {
font-size: 22rpx;
color: #a0a0a0;
line-height: 1.8;
font-style: italic;
}
.section {
margin-bottom: 64rpx;
}
.section-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
padding-left: 8rpx;
}
.section-icon {
width: 28rpx;
height: 28rpx;
}
.section-title {
font-size: 26rpx;
font-weight: 700;
letter-spacing: 0.24em;
}
.char-row {
display: flex;
gap: 24rpx;
}
.char-card {
flex: 1;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
overflow: hidden;
}
.char-element-wrap {
position: absolute;
top: 0;
right: 0;
padding: 10rpx;
}
.char-element {
font-size: 16rpx;
padding: 4rpx 10rpx;
border-radius: 999rpx;
border: 1px solid rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.6);
}
.el-fire {
border-color: rgba(239, 68, 68, 0.5);
color: rgba(248, 113, 113, 1);
}
.el-gold {
border-color: rgba(234, 179, 8, 0.5);
color: rgba(250, 204, 21, 1);
}
.char-char {
font-size: 64rpx;
font-weight: 700;
margin-top: 8rpx;
margin-bottom: 6rpx;
}
.char-stroke {
font-size: 18rpx;
color: #a0a0a0;
margin-bottom: 18rpx;
}
.char-meaning {
font-size: 18rpx;
color: #a0a0a0;
text-align: center;
line-height: 1.4;
opacity: 0.8;
}
.section-paragraph {
margin-top: 20rpx;
font-size: 18rpx;
color: #a0a0a0;
line-height: 2;
text-align: justify;
padding: 0 8rpx;
}
.section-paragraph-highlight {
color: #d4af37;
}
.radar-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 28rpx;
backdrop-filter: blur(8px);
}
.biz-sixdim-radar {
position: relative;
width: 100%;
max-width: 380rpx;
margin: 0 auto;
padding-top: 65%;
background: radial-gradient(circle at 50% 50%, rgba(212, 175, 55, 0.06), transparent 70%);
border-radius: 24rpx;
overflow: hidden;
}
.biz-sixdim-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 3;
pointer-events: none;
}
.biz-sixdim-fill {
position: absolute;
inset: 24%;
margin: auto;
background: linear-gradient(to bottom, rgba(212, 175, 55, 0.75), rgba(42, 61, 93, 0.55));
opacity: 0.88;
transition: clip-path 0.5s ease-out;
z-index: 2;
}
.biz-sixdim-label {
position: absolute;
font-size: 20rpx;
color: #cfd2dc;
z-index: 4;
}
.biz-sixdim-label-0 {
top: 6%;
left: 50%;
transform: translateX(-50%);
}
.biz-sixdim-label-1 {
top: 26%;
right: 6%;
}
.biz-sixdim-label-2 {
bottom: 28%;
right: 4%;
}
.biz-sixdim-label-3 {
bottom: 4%;
left: 50%;
transform: translateX(-50%);
}
.biz-sixdim-label-4 {
bottom: 28%;
left: 4%;
}
.biz-sixdim-label-5 {
top: 26%;
left: 6%;
}
.radar-wrap {
width: 100%;
height: 380rpx;
display: flex;
align-items: center;
justify-content: center;
}
.radar-svg {
width: 380rpx;
height: 380rpx;
overflow: visible;
}
.radar-summary-grid {
margin-top: 16rpx;
display: flex;
gap: 16rpx;
}
.radar-summary-item {
flex: 1;
background: rgba(212, 175, 55, 0.1);
border: 1px solid rgba(212, 175, 55, 0.2);
border-radius: 12rpx;
padding: 16rpx;
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
}
.radar-summary-label {
font-size: 18rpx;
color: #a0a0a0;
}
.radar-summary-value {
font-size: 22rpx;
font-weight: 700;
color: #d4af37;
margin-top: 8rpx;
}
.gua-card {
background: #1a1a2e;
border-left: 8rpx solid #d4af37;
padding: 40rpx;
border-top-right-radius: 16rpx;
border-bottom-right-radius: 16rpx;
position: relative;
overflow: hidden;
}
.gua-bg {
position: absolute;
right: -20rpx;
bottom: -20rpx;
font-size: 160rpx;
color: rgba(255, 255, 255, 0.05);
}
.gua-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
}
.gua-name {
font-size: 34rpx;
font-weight: 700;
letter-spacing: 0.2em;
}
.gua-badge {
font-size: 18rpx;
color: #d4af37;
background: rgba(212, 175, 55, 0.2);
padding: 8rpx 16rpx;
border-radius: 999rpx;
border: 1px solid rgba(212, 175, 55, 0.3);
}
.gua-desc {
font-size: 22rpx;
color: #a0a0a0;
line-height: 1.8;
text-align: justify;
margin-bottom: 24rpx;
}
.gua-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-bottom: 16rpx;
}
.gua-tag {
font-size: 18rpx;
color: #d4af37;
border: 1px solid rgba(212, 175, 55, 0.3);
background: rgba(212, 175, 55, 0.05);
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
.gua-insight-title {
font-size: 18rpx;
color: #d4af37;
font-weight: 700;
margin-bottom: 8rpx;
}
.gua-insight-text {
font-size: 18rpx;
color: #a0a0a0;
line-height: 1.6;
}
.team-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 24rpx;
}
.team-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx;
border-radius: 12rpx;
}
.team-left {
display: flex;
align-items: center;
gap: 20rpx;
}
.team-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 999rpx;
background: #2a3d5d;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.team-avatar-text {
font-size: 18rpx;
}
.team-info {
display: flex;
flex-direction: column;
}
.team-role {
font-size: 22rpx;
font-weight: 700;
}
.team-desc {
font-size: 18rpx;
color: #a0a0a0;
margin-top: 6rpx;
}
.team-match {
font-size: 22rpx;
font-weight: 700;
color: rgba(255, 255, 255, 0.8);
}
.team-match-gold {
color: #d4af37;
}
.team-note {
margin-top: 16rpx;
font-size: 16rpx;
color: rgba(160, 160, 160, 0.6);
text-align: center;
}
.year-scroll {
width: 100%;
}
.year-row {
display: flex;
gap: 24rpx;
padding-bottom: 16rpx;
}
.year-card {
min-width: 200rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.year-year {
font-size: 22rpx;
color: #a0a0a0;
margin-bottom: 8rpx;
}
.year-luck {
font-size: 26rpx;
font-weight: 700;
margin-bottom: 16rpx;
}
.year-luck-gold {
color: #d4af37;
}
.year-divider {
height: 2rpx;
width: 32rpx;
background: rgba(255, 255, 255, 0.1);
margin-bottom: 16rpx;
}
.year-text {
font-size: 16rpx;
color: #a0a0a0;
text-align: center;
line-height: 1.4;
}
.grid-2 {
display: flex;
gap: 24rpx;
margin-bottom: 64rpx;
}
.small-card {
flex: 1;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 24rpx;
}
.small-card-header {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 18rpx;
}
.small-card-icon {
width: 24rpx;
height: 24rpx;
}
.small-card-title {
font-size: 22rpx;
font-weight: 700;
}
.bars {
height: 160rpx;
display: flex;
gap: 6rpx;
align-items: flex-end;
padding-top: 16rpx;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 8rpx;
}
.bar-bg {
flex: 1;
height: 100%;
align-self: stretch;
background: rgba(212, 175, 55, 0.2);
border-top-left-radius: 6rpx;
border-top-right-radius: 6rpx;
position: relative;
overflow: hidden;
}
.bar-fill {
position: absolute;
bottom: 0;
width: 100%;
background: #d4af37;
opacity: 0.6;
border-top-left-radius: 6rpx;
border-top-right-radius: 6rpx;
}
.small-card-note {
font-size: 18rpx;
color: #a0a0a0;
margin-top: 16rpx;
text-align: center;
}
.compass {
position: relative;
width: 160rpx;
height: 160rpx;
margin: 0 auto;
border: 4rpx solid #5a5a5a;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
}
.compass-n,
.compass-s,
.compass-e,
.compass-w {
position: absolute;
font-size: 18rpx;
padding: 2rpx 6rpx;
background: #1a1a2e;
}
.compass-gold {
color: #d4af37;
}
.compass-n {
top: -10rpx;
}
.compass-s {
bottom: -10rpx;
color: #a0a0a0;
}
.compass-w {
left: -12rpx;
color: #a0a0a0;
}
.compass-e {
right: -12rpx;
}
.compass-dot {
position: absolute;
width: 12rpx;
height: 12rpx;
background: #d4af37;
border-radius: 999rpx;
transform: translate(-50%, -50%);
box-shadow: 0 0 10rpx #d4af37;
}
.layout-card {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 16rpx;
padding: 24rpx;
}
.layout-row {
display: flex;
gap: 16rpx;
margin-bottom: 18rpx;
}
.layout-dot {
width: 10rpx;
height: 10rpx;
border-radius: 999rpx;
background: #d4af37;
margin-top: 10rpx;
}
.layout-text {
font-size: 20rpx;
color: #a0a0a0;
line-height: 1.8;
}
.layout-strong {
color: #e2e2e2;
font-weight: 700;
}
.layout-highlight {
color: #d4af37;
font-weight: 700;
}
.exec-card {
background: rgba(212, 175, 55, 0.1);
border: 1px solid rgba(212, 175, 55, 0.3);
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 64rpx;
}
.exec-row {
display: flex;
gap: 16rpx;
align-items: flex-start;
}
.exec-icon {
width: 32rpx;
height: 32rpx;
margin-top: 4rpx;
}
.exec-body {
flex: 1;
}
.exec-title {
font-size: 22rpx;
font-weight: 700;
color: #d4af37;
margin-bottom: 8rpx;
}
.exec-text {
font-size: 20rpx;
color: #a0a0a0;
line-height: 1.8;
}
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 999;
}
.modal-container {
width: 100%;
max-height: 78vh;
background: rgba(10, 10, 15, 0.96);
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
border: 1px solid rgba(212, 175, 55, 0.2);
overflow: hidden;
}
.modal-header {
padding: 28rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.modal-title {
font-size: 24rpx;
font-weight: 700;
color: #d4af37;
letter-spacing: 0.2em;
}
.modal-close {
width: 64rpx;
text-align: right;
font-size: 44rpx;
line-height: 44rpx;
color: rgba(160, 160, 160, 0.9);
}
.modal-body {
max-height: calc(78vh - 96rpx);
padding: 28rpx 32rpx;
}
.modal-text {
display: block;
font-size: 20rpx;
color: #a0a0a0;
line-height: 1.9;
margin-bottom: 18rpx;
text-align: justify;
}
.modal-list {
margin-bottom: 18rpx;
}
.modal-list-item {
display: flex;
gap: 14rpx;
margin-bottom: 12rpx;
}
.modal-list-dot {
width: 10rpx;
height: 10rpx;
border-radius: 999rpx;
background: #d4af37;
margin-top: 12rpx;
}
.modal-list-text {
flex: 1;
font-size: 20rpx;
color: #a0a0a0;
line-height: 1.8;
}
.modal-kv {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 18rpx;
}
.modal-kv-row {
display: flex;
justify-content: space-between;
gap: 24rpx;
padding: 14rpx 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.modal-kv-row:last-child {
border-bottom: 0;
}
.modal-kv-label {
font-size: 18rpx;
color: rgba(160, 160, 160, 0.9);
}
.modal-kv-value {
font-size: 18rpx;
color: rgba(226, 226, 226, 0.95);
text-align: right;
}
</style>