upload project source code
This commit is contained in:
255
前端源码/uni-app/components/SixDimensionRadarDesktopEchart.vue
Normal file
255
前端源码/uni-app/components/SixDimensionRadarDesktopEchart.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user