feat: 更新应用版本和集成腾讯云 COS 上传功能
- 将应用版本更新至 1.0.2,修改相关配置文件 - 集成腾讯云 COS 上传功能,新增相关服务和钩子 - 更新 AI 体态评估页面,支持照片上传和评估结果展示 - 添加雷达图组件以展示评估结果 - 更新样式以适应新功能的展示和交互 - 修改登录页面背景效果,提升用户体验
This commit is contained in:
104
components/RadarChart.tsx
Normal file
104
components/RadarChart.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import Svg, * as SvgLib from 'react-native-svg';
|
||||
|
||||
export type RadarCategory = { key: string; label: string };
|
||||
|
||||
export type RadarChartProps = {
|
||||
categories: RadarCategory[];
|
||||
values: number[]; // 与 categories 一一对应
|
||||
maxValue?: number; // 默认 5
|
||||
size?: number; // 组件宽高,默认 260
|
||||
levels?: number; // 网格层数,默认 5
|
||||
showGrid?: boolean;
|
||||
};
|
||||
|
||||
export function RadarChart({ categories, values, maxValue = 5, size = 260, levels = 5, showGrid = true }: RadarChartProps) {
|
||||
const radius = size * 0.38;
|
||||
const cx = size / 2;
|
||||
const cy = size / 2;
|
||||
const count = categories.length;
|
||||
|
||||
const points = useMemo(() => {
|
||||
return categories.map((_, i) => {
|
||||
const angle = (Math.PI * 2 * i) / count - Math.PI / 2; // 顶部起点
|
||||
return { angle, x: (v: number) => cx + Math.cos(angle) * v, y: (v: number) => cy + Math.sin(angle) * v };
|
||||
});
|
||||
}, [categories, count, cx, cy]);
|
||||
|
||||
const valuePath = useMemo(() => {
|
||||
const rValues = values.map((v) => Math.max(0, Math.min(maxValue, v)) / maxValue * radius);
|
||||
const d = rValues
|
||||
.map((rv, i) => `${i === 0 ? 'M' : 'L'} ${points[i].x(rv)} ${points[i].y(rv)}`)
|
||||
.join(' ');
|
||||
return `${d} Z`;
|
||||
}, [values, maxValue, radius, points]);
|
||||
|
||||
const gridPolygons = useMemo(() => {
|
||||
const polys: string[] = [];
|
||||
for (let l = 1; l <= levels; l++) {
|
||||
const r = (radius / levels) * l;
|
||||
const p = points.map((p) => `${p.x(r)},${p.y(r)}`).join(' ');
|
||||
polys.push(p);
|
||||
}
|
||||
return polys;
|
||||
}, [levels, radius, points]);
|
||||
|
||||
return (
|
||||
<View style={{ width: size, height: size }}>
|
||||
<Svg width={size} height={size}>
|
||||
<SvgLib.Defs>
|
||||
<SvgLib.LinearGradient id="radarFill" x1="0" y1="0" x2="1" y2="1">
|
||||
<SvgLib.Stop offset="0%" stopColor="#BBF246" stopOpacity={0.32} />
|
||||
<SvgLib.Stop offset="100%" stopColor="#59C6FF" stopOpacity={0.28} />
|
||||
</SvgLib.LinearGradient>
|
||||
</SvgLib.Defs>
|
||||
|
||||
{showGrid && (
|
||||
<SvgLib.G>
|
||||
{gridPolygons.map((pointsStr, idx) => (
|
||||
<SvgLib.Polygon
|
||||
key={`grid-${idx}`}
|
||||
points={pointsStr}
|
||||
fill="none"
|
||||
stroke="rgba(25,33,38,0.12)"
|
||||
strokeWidth={1}
|
||||
/>
|
||||
))}
|
||||
{points.map((p, i) => (
|
||||
<SvgLib.Line
|
||||
key={`axis-${i}`}
|
||||
x1={cx}
|
||||
y1={cy}
|
||||
x2={p.x(radius)}
|
||||
y2={p.y(radius)}
|
||||
stroke="rgba(25,33,38,0.12)"
|
||||
strokeWidth={1}
|
||||
/>
|
||||
))}
|
||||
</SvgLib.G>
|
||||
)}
|
||||
|
||||
{categories.map((c, i) => {
|
||||
const r = radius + 18;
|
||||
const x = points[i].x(r);
|
||||
const y = points[i].y(r);
|
||||
const anchor = Math.cos(points[i].angle) > 0.2 ? 'start' : Math.cos(points[i].angle) < -0.2 ? 'end' : 'middle';
|
||||
const dy = Math.sin(points[i].angle) > 0.6 ? 10 : Math.sin(points[i].angle) < -0.6 ? -4 : 4;
|
||||
return (
|
||||
<SvgLib.Text key={`label-${c.key}`} x={x} y={y + dy} fontSize={11} fill="#192126" textAnchor={anchor}>
|
||||
{c.label}
|
||||
</SvgLib.Text>
|
||||
);
|
||||
})}
|
||||
|
||||
<SvgLib.Path d={valuePath} fill="url(#radarFill)" stroke="#A8DB3D" strokeWidth={2} />
|
||||
<SvgLib.Circle cx={cx} cy={cy} r={3} fill="#A8DB3D" />
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user