feat(web): Vitest 测试基建与首批单测(#27)+ 公共格式化函数
This commit is contained in:
Generated
+1185
-1
File diff suppressed because it is too large
Load Diff
+6
-1
@@ -7,6 +7,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "oxlint",
|
"lint": "oxlint",
|
||||||
|
"test": "vitest run",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -24,12 +25,16 @@
|
|||||||
"react-router-dom": "^7.18.1"
|
"react-router-dom": "^7.18.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^7.0.1",
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^24.13.2",
|
"@types/node": "^24.13.2",
|
||||||
"@types/react": "^18.3.27",
|
"@types/react": "^18.3.27",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@vitejs/plugin-react": "^6.0.3",
|
"@vitejs/plugin-react": "^6.0.3",
|
||||||
|
"jsdom": "^30.0.1",
|
||||||
"oxlint": "^1.71.0",
|
"oxlint": "^1.71.0",
|
||||||
"typescript": "~6.0.2",
|
"typescript": "~6.0.2",
|
||||||
"vite": "^8.1.1"
|
"vite": "^8.1.1",
|
||||||
|
"vitest": "^4.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,13 @@ import { Drawer, Image, Tag, Typography } from 'antd';
|
|||||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { listInspections, type AIDetection, type InspectionRecord } from '../dal/inspections';
|
import { listInspections, type AIDetection, type InspectionRecord } from '../dal/inspections';
|
||||||
|
import { aiClassLabel, riskLevelLabel } from '../utils/format';
|
||||||
|
|
||||||
const CLASS_LABELS: Record<string, string> = {
|
const RISK_LABELS: Record<string, { color: string }> = {
|
||||||
healthy: '健康',
|
green: { color: 'green' },
|
||||||
sick: '疑似异常',
|
yellow: { color: 'gold' },
|
||||||
};
|
orange: { color: 'orange' },
|
||||||
|
red: { color: 'red' },
|
||||||
const RISK_LABELS: Record<string, { color: string; text: string }> = {
|
|
||||||
green: { color: 'green', text: '绿' },
|
|
||||||
yellow: { color: 'gold', text: '黄' },
|
|
||||||
orange: { color: 'orange', text: '橙' },
|
|
||||||
red: { color: 'red', text: '红' },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const aiClassOf = (rec: InspectionRecord) =>
|
const aiClassOf = (rec: InspectionRecord) =>
|
||||||
@@ -36,7 +32,7 @@ export default function InspectionsPage() {
|
|||||||
render: (_, r) => {
|
render: (_, r) => {
|
||||||
const c = aiClassOf(r);
|
const c = aiClassOf(r);
|
||||||
return c ? (
|
return c ? (
|
||||||
<Tag color={c === 'sick' ? 'red' : 'green'}>{CLASS_LABELS[c]}</Tag>
|
<Tag color={c === 'sick' ? 'red' : 'green'}>{aiClassLabel(c)}</Tag>
|
||||||
) : (
|
) : (
|
||||||
'-'
|
'-'
|
||||||
);
|
);
|
||||||
@@ -50,7 +46,7 @@ export default function InspectionsPage() {
|
|||||||
const l = RISK_LABELS[r.riskLevel || ''] || RISK_LABELS.green;
|
const l = RISK_LABELS[r.riskLevel || ''] || RISK_LABELS.green;
|
||||||
return r.riskScore !== undefined ? (
|
return r.riskScore !== undefined ? (
|
||||||
<Tag color={l.color}>
|
<Tag color={l.color}>
|
||||||
{l.text}({Math.round(r.riskScore!)} 分)
|
{riskLevelLabel(r.riskLevel || '')}({Math.round(r.riskScore!)} 分)
|
||||||
</Tag>
|
</Tag>
|
||||||
) : (
|
) : (
|
||||||
'-'
|
'-'
|
||||||
@@ -106,13 +102,13 @@ export default function InspectionsPage() {
|
|||||||
状态:{detail.aiStatus === 'done' ? '检测成功' : '检测失败'}
|
状态:{detail.aiStatus === 'done' ? '检测成功' : '检测失败'}
|
||||||
<br />
|
<br />
|
||||||
风险分:{detail.riskScore !== undefined ? Math.round(detail.riskScore) : '-'}(
|
风险分:{detail.riskScore !== undefined ? Math.round(detail.riskScore) : '-'}(
|
||||||
{detail.riskLevel ? RISK_LABELS[detail.riskLevel]?.text || detail.riskLevel : '-'})
|
{detail.riskLevel ? riskLevelLabel(detail.riskLevel) : '-'})
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Typography.Title level={5}>检测结果</Typography.Title>
|
<Typography.Title level={5}>检测结果</Typography.Title>
|
||||||
{detail.detections && detail.detections.length > 0 ? (
|
{detail.detections && detail.detections.length > 0 ? (
|
||||||
detail.detections.map((d: AIDetection, i: number) => (
|
detail.detections.map((d: AIDetection, i: number) => (
|
||||||
<div key={i} style={{ padding: '6px 0', borderBottom: '1px solid #f0f0f0' }}>
|
<div key={i} style={{ padding: '6px 0', borderBottom: '1px solid #f0f0f0' }}>
|
||||||
<Tag color={d.class === 'healthy' ? 'green' : 'red'}>{CLASS_LABELS[d.class] || d.class}</Tag>
|
<Tag color={d.class === 'healthy' ? 'green' : 'red'}>{aiClassLabel(d.class)}</Tag>
|
||||||
<span>置信度 {(d.confidence * 100).toFixed(1)}%</span>
|
<span>置信度 {(d.confidence * 100).toFixed(1)}%</span>
|
||||||
<span style={{ marginLeft: 12, color: '#999' }}>
|
<span style={{ marginLeft: 12, color: '#999' }}>
|
||||||
框 x:{Math.round(d.bbox.x)} y:{Math.round(d.bbox.y)} w:{Math.round(d.bbox.w)} h:{Math.round(d.bbox.h)}
|
框 x:{Math.round(d.bbox.x)} y:{Math.round(d.bbox.y)} w:{Math.round(d.bbox.w)} h:{Math.round(d.bbox.h)}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { aiClassLabel, riskLevelLabel } from './format';
|
||||||
|
|
||||||
|
describe('aiClassLabel', () => {
|
||||||
|
it('映射 healthy/sick 为中文', () => {
|
||||||
|
expect(aiClassLabel('healthy')).toBe('健康');
|
||||||
|
expect(aiClassLabel('sick')).toBe('疑似异常');
|
||||||
|
});
|
||||||
|
it('未知类别原样返回', () => {
|
||||||
|
expect(aiClassLabel('unknown')).toBe('unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('riskLevelLabel', () => {
|
||||||
|
it('映射四级风险', () => {
|
||||||
|
expect(riskLevelLabel('green')).toBe('绿');
|
||||||
|
expect(riskLevelLabel('yellow')).toBe('黄');
|
||||||
|
expect(riskLevelLabel('orange')).toBe('橙');
|
||||||
|
expect(riskLevelLabel('red')).toBe('红');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const AI_CLASS_LABELS: Record<string, string> = {
|
||||||
|
healthy: '健康',
|
||||||
|
sick: '疑似异常',
|
||||||
|
};
|
||||||
|
|
||||||
|
const RISK_LEVEL_LABELS: Record<string, string> = {
|
||||||
|
green: '绿',
|
||||||
|
yellow: '黄',
|
||||||
|
orange: '橙',
|
||||||
|
red: '红',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const aiClassLabel = (cls: string) => AI_CLASS_LABELS[cls] || cls;
|
||||||
|
|
||||||
|
export const riskLevelLabel = (level: string) => RISK_LEVEL_LABELS[level] || level;
|
||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vitest/config';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -27,6 +27,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 5174,
|
port: 5174,
|
||||||
|
|||||||
Reference in New Issue
Block a user