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

256 lines
6.2 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="sixdim-section">
<view class="sixdim-card">
<view class="sixdim-info">
<view class="sixdim-info-icon">i</view>
</view>
<!-- 电脑端雷达图ECharts 渲染 -->
<view class="sixdim-radar">
<view ref="chartRef" class="sixdim-echart-inner" />
</view>
<view class="sixdim-remark sixdim-remark-center">
<text class="sixdim-remark-label">大师批注</text>
<text class="sixdim-remark-text">{{ remark }}</text>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from "vue";
import * as echarts from "echarts";
interface Props {
labels: string[];
values: number[];
remark: string;
}
const props = defineProps<Props>();
const chartRef = ref<HTMLElement | null>(null);
let chart: echarts.ECharts | null = null;
const getSafeValues = (vals: number[]) => (Array.isArray(vals) ? vals.map((v) => (Number.isFinite(v as any) ? Number(v) : 0)) : []);
const getSafeLabels = (labs: string[]) => (Array.isArray(labs) ? labs.map((s) => String(s ?? "")) : []);
/** 雷达图 indicator 与 value 必须等长且非空,否则 ECharts radarLayout 会报 push undefined */
function alignRadarData(rawLabels: string[], rawValues: number[]): { labels: string[]; values: number[] } {
let labels = getSafeLabels(rawLabels).map((s) => s.trim());
let values = getSafeValues(rawValues);
let n = Math.max(labels.length, values.length);
// 个人测名默认六维:事业、财运、健康、家庭、社交、智慧
const defaultSix = ["事业", "财运", "健康", "家庭", "社交", "智慧"];
if (n === 0) {
labels = defaultSix;
values = [0, 0, 0, 0, 0, 0];
n = 6;
} else if (n <= 6) {
// 不足 6 维时,补足到 6 维并强制使用默认中文维度,确保「家庭」等字段一定出现
n = 6;
while (values.length < n) values.push(0);
labels = defaultSix;
} else {
while (labels.length < n) labels.push(`维度${labels.length + 1}`);
while (values.length < n) values.push(0);
labels = labels.slice(0, n);
values = values.slice(0, n);
}
// 再次截断到 n防御性处理
labels = labels.slice(0, n).map((s) => s || "—");
values = values.slice(0, n);
return { labels, values };
}
const buildOption = () => {
const { labels, values } = alignRadarData(props.labels, props.values);
const radarIndicator = labels.map((name) => ({ name: name || "—", max: 100 }));
return {
animationDuration: 700,
animationEasing: "cubicOut" as any,
tooltip: {
trigger: "item",
formatter: (params: any) => {
const valueArr: number[] = params?.value ?? [];
const parts = labels.map((lab, idx) => `${lab}${valueArr[idx] ?? 0}`);
return parts.join("<br/>");
},
},
radar: {
indicator: radarIndicator,
splitNumber: 4,
shape: "polygon",
center: ["50%", "48%"],
// 轴文字可能在较小容器内被裁切:适当缩小半径,让 label 落在视窗内
radius: "62%",
axisName: {
color: "rgba(207, 210, 220, 0.88)",
fontSize: 9,
padding: 6,
},
axisLine: {
lineStyle: {
color: "rgba(212, 220, 236, 0.32)",
width: 1,
},
},
splitLine: {
lineStyle: {
color: "rgba(255, 255, 255, 0.12)",
width: 1,
},
},
splitArea: {
areaStyle: {
color: ["rgba(255, 255, 255, 0.02)", "rgba(255, 193, 7, 0.015)"],
},
},
},
series: [
{
type: "radar",
data: [
{
value: [...values],
},
],
lineStyle: {
color: "rgba(255, 193, 7, 0.92)",
width: 2,
},
itemStyle: {
color: "rgba(255, 193, 7, 0.95)",
},
symbol: "circle",
symbolSize: 3,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(255, 193, 7, 0.78)" },
{ offset: 1, color: "rgba(243, 138, 43, 0.52)" },
]),
opacity: 0.95,
},
},
],
// 包含所有轴文字(避免 label 在容器内被裁切)
grid: { containLabel: true },
};
};
const render = () => {
if (!chartRef.value) return;
if (!chart) {
// 使用 svg 渲染器,避免 canvas getContext 在某些环境出错
chart = echarts.init(chartRef.value, undefined, { renderer: "svg" });
}
chart.setOption(buildOption(), { notMerge: true, lazyUpdate: true });
};
onMounted(() => {
render();
// 适配父容器缩放(桌面布局下尤其必要)
const onResize = () => chart?.resize();
window.addEventListener("resize", onResize);
onUnmounted(() => window.removeEventListener("resize", onResize));
});
watch(
() => [props.labels, props.values],
() => {
render();
},
{ deep: true }
);
onUnmounted(() => {
if (chart) {
chart.dispose();
chart = null;
}
});
</script>
<style scoped>
.sixdim-section {
margin-bottom: 64rpx;
}
.sixdim-card {
position: relative;
background: rgba(255, 255, 255, 0.05);
border-radius: 20rpx;
border: 1px solid rgba(255, 255, 255, 0.06);
padding: 28rpx 24rpx 32rpx;
backdrop-filter: blur(12px);
overflow: hidden;
}
.sixdim-info {
position: absolute;
top: 16px;
right: 16rpx;
}
.sixdim-info-icon {
width: 28px;
height: 28px;
border-radius: 999rpx;
border: 1px solid rgba(255, 255, 255, 0.16);
color: rgba(255, 255, 255, 0.45);
font-size: 18px;
text-align: center;
}
.sixdim-radar {
position: relative;
width: 100%;
max-width: 380rpx;
margin: 0 auto;
padding-top: 0;
background: radial-gradient(circle at 50% 50%, rgba(255, 193, 7, 0.04), transparent 70%);
border-radius: 24rpx;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
}
.sixdim-echart-inner {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
top: 0;
}
.sixdim-remark {
font-size: 20rpx;
line-height: 1.8;
color: #a0a4b8;
}
.sixdim-remark-center {
margin-top: 24rpx;
text-align: center;
padding: 0 24rpx;
}
.sixdim-remark-label {
color: #fdd835;
}
.sixdim-remark-text {
color: #c0c3d0;
}
</style>