feat: 建立整改 Task 1 多端质量基线
This commit is contained in:
@@ -26,6 +26,7 @@ __pycache__/
|
|||||||
/wvp/*.sh
|
/wvp/*.sh
|
||||||
*.sh
|
*.sh
|
||||||
*.ps1
|
*.ps1
|
||||||
|
!scripts/verify.ps1
|
||||||
*.py
|
*.py
|
||||||
*.sql
|
*.sql
|
||||||
# ai-service(Python 推理服务)文件例外
|
# ai-service(Python 推理服务)文件例外
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ python -m venv .venv
|
|||||||
MODEL_MODE=mock .venv/Scripts/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
MODEL_MODE=mock .venv/Scripts/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 质量检查
|
||||||
|
|
||||||
|
仓库根目录的 `scripts/verify.ps1` 会使用 `ai-service/.venv/Scripts/python.exe` 运行 pytest;如果该虚拟环境不存在,会回退到 `python`。
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts/verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
## 环境变量
|
## 环境变量
|
||||||
|
|
||||||
| 变量 | 默认 | 说明 |
|
| 变量 | 默认 | 说明 |
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: '@react-native',
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'warn',
|
||||||
|
},
|
||||||
|
};
|
||||||
Generated
+3292
-274
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -17,14 +17,13 @@
|
|||||||
"@react-navigation/native-stack": "^6.10.0",
|
"@react-navigation/native-stack": "^6.10.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"react": "18.3.1",
|
"react": "18.2.0",
|
||||||
"react-native": "0.74.5",
|
"react-native": "0.74.5",
|
||||||
"react-native-paper": "^5.12.3",
|
"react-native-paper": "^5.12.3",
|
||||||
"react-native-safe-area-context": "^4.10.5",
|
"react-native-safe-area-context": "^4.10.5",
|
||||||
"react-native-screens": "^3.32.0",
|
"react-native-screens": "^3.32.0",
|
||||||
"react-native-vector-icons": "^10.1.0",
|
"react-native-vector-icons": "^10.1.0",
|
||||||
"react-native-video": "^6.2.0",
|
"react-native-video": "^6.2.0",
|
||||||
"victory-native": "^41.6.0",
|
|
||||||
"zustand": "^4.5.4"
|
"zustand": "^4.5.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -32,9 +31,12 @@
|
|||||||
"@babel/preset-env": "^7.25.3",
|
"@babel/preset-env": "^7.25.3",
|
||||||
"@babel/runtime": "^7.25.0",
|
"@babel/runtime": "^7.25.0",
|
||||||
"@react-native/babel-preset": "0.74.85",
|
"@react-native/babel-preset": "0.74.85",
|
||||||
|
"@react-native/eslint-config": "0.74.85",
|
||||||
"@react-native/metro-config": "^0.74.85",
|
"@react-native/metro-config": "^0.74.85",
|
||||||
"@react-native/typescript-config": "0.74.85",
|
"@react-native/typescript-config": "0.74.85",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
"react-native-dotenv": "^3.4.11",
|
"react-native-dotenv": "^3.4.11",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, StyleSheet, Text, Dimensions } from 'react-native';
|
import { View, StyleSheet, Text } from 'react-native';
|
||||||
|
|
||||||
interface MiniChartProps {
|
interface MiniChartProps {
|
||||||
data: { time: string; temp?: number; humidity?: number; co2?: number }[];
|
data: { time: string; temp?: number; humidity?: number; co2?: number }[];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
|||||||
import { NavigationContainer, useNavigation } from '@react-navigation/native';
|
import { NavigationContainer, useNavigation } from '@react-navigation/native';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||||
import { Provider as PaperProvider, useTheme, MD3LightTheme } from 'react-native-paper';
|
import { Provider as PaperProvider, MD3LightTheme } from 'react-native-paper';
|
||||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import { setNavigationRef } from '../api/client';
|
import { setNavigationRef } from '../api/client';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
@@ -25,7 +25,7 @@ import SettingsScreen from '../screens/SettingsScreen';
|
|||||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||||
const Tab = createBottomTabNavigator<MainTabParamList>();
|
const Tab = createBottomTabNavigator<MainTabParamList>();
|
||||||
|
|
||||||
const TabIcon: React.FC<{ name: string; activeName: string; color: string; size: number; focused: boolean }> = ({ name, activeName, color, size, focused }) => (
|
const TabIcon: React.FC<{ name: string; activeName: string; color: string; size: number; focused: boolean }> = ({ name, activeName, color, focused }) => (
|
||||||
<Icon name={focused ? activeName : name} size={focused ? 26 : 22} color={color} />
|
<Icon name={focused ? activeName : name} size={focused ? 26 : 22} color={color} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { StyleSheet, View, FlatList, RefreshControl, Alert } from 'react-native';
|
import { StyleSheet, View, FlatList, RefreshControl, Alert } from 'react-native';
|
||||||
import { Text, Card, useTheme, ActivityIndicator, Surface, SegmentedButtons, Button, Snackbar } from 'react-native-paper';
|
import { Text, Card, ActivityIndicator, Surface, SegmentedButtons, Button, Snackbar } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
|
||||||
import { getAlarms, ackAlarm, getAlarmClip } from '../api/alarms';
|
import { getAlarms, ackAlarm, getAlarmClip } from '../api/alarms';
|
||||||
import { extractErrorMessage } from '../api/client';
|
import { extractErrorMessage } from '../api/client';
|
||||||
import { formatRelativeTime, getSeverityLabel } from '../utils/format';
|
import { formatRelativeTime, getSeverityLabel } from '../utils/format';
|
||||||
import type { Alarm } from '../types';
|
import type { Alarm } from '../types';
|
||||||
|
|
||||||
export default function AlertsScreen() {
|
export default function AlertsScreen() {
|
||||||
const theme = useTheme();
|
|
||||||
const navigation = useNavigation<any>();
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
const [alarms, setAlarms] = useState<Alarm[]>([]);
|
const [alarms, setAlarms] = useState<Alarm[]>([]);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { StyleSheet, View, ScrollView, Alert } from 'react-native';
|
import { StyleSheet, View, ScrollView } from 'react-native';
|
||||||
import { Text, Card, Button, Switch, useTheme, ActivityIndicator, TextInput, SegmentedButtons, Divider, Snackbar } from 'react-native-paper';
|
import { Text, Card, Button, Switch, ActivityIndicator, TextInput, SegmentedButtons, Divider, Snackbar } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useRoute } from '@react-navigation/native';
|
import { useRoute } from '@react-navigation/native';
|
||||||
import { sendControl } from '../api/devices';
|
import { sendControl } from '../api/devices';
|
||||||
@@ -10,7 +10,6 @@ import { formatNumber } from '../utils/format';
|
|||||||
import type { RealtimeMetric } from '../types';
|
import type { RealtimeMetric } from '../types';
|
||||||
|
|
||||||
export default function DeviceControlScreen() {
|
export default function DeviceControlScreen() {
|
||||||
const theme = useTheme();
|
|
||||||
const route = useRoute<any>();
|
const route = useRoute<any>();
|
||||||
const { deviceKey, deviceName } = route.params;
|
const { deviceKey, deviceName } = route.params;
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { StyleSheet, View, FlatList, RefreshControl } from 'react-native';
|
import { StyleSheet, View, FlatList, RefreshControl } from 'react-native';
|
||||||
import { Text, Card, useTheme, ActivityIndicator, Surface, Searchbar, IconButton } from 'react-native-paper';
|
import { Text, Card, ActivityIndicator, Surface, Searchbar, IconButton } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { getDevices } from '../api/devices';
|
import { getDevices } from '../api/devices';
|
||||||
@@ -8,7 +8,6 @@ import { formatRelativeTime } from '../utils/format';
|
|||||||
import type { Device } from '../types';
|
import type { Device } from '../types';
|
||||||
|
|
||||||
export default function DevicesScreen() {
|
export default function DevicesScreen() {
|
||||||
const theme = useTheme();
|
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
import { StyleSheet, View, ScrollView, RefreshControl } from 'react-native';
|
import { StyleSheet, View, ScrollView, RefreshControl } from 'react-native';
|
||||||
import { Text, Card, useTheme, ActivityIndicator, Surface } from 'react-native-paper';
|
import { Text, Card, ActivityIndicator, Surface } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useRoute } from '@react-navigation/native';
|
import { useRoute } from '@react-navigation/native';
|
||||||
import { getRoom } from '../api/rooms';
|
import { getRoom } from '../api/rooms';
|
||||||
@@ -11,7 +11,6 @@ import { formatRelativeTime } from '../utils/format';
|
|||||||
import type { Room, RealtimeMetric, Device, TrendPoint } from '../types';
|
import type { Room, RealtimeMetric, Device, TrendPoint } from '../types';
|
||||||
|
|
||||||
export default function RoomDetailScreen() {
|
export default function RoomDetailScreen() {
|
||||||
const theme = useTheme();
|
|
||||||
const route = useRoute<any>();
|
const route = useRoute<any>();
|
||||||
const { roomId, roomName } = route.params;
|
const { roomId, roomName } = route.params;
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import React, { useEffect, useState, useCallback } from 'react';
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
import { StyleSheet, View, FlatList, RefreshControl } from 'react-native';
|
import { StyleSheet, View, FlatList, RefreshControl } from 'react-native';
|
||||||
import { Text, Card, useTheme, ActivityIndicator, IconButton } from 'react-native-paper';
|
import { Text, Card, ActivityIndicator, IconButton } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { getRooms } from '../api/rooms';
|
import { getRooms } from '../api/rooms';
|
||||||
import { formatRelativeTime } from '../utils/format';
|
|
||||||
import type { Room } from '../types';
|
import type { Room } from '../types';
|
||||||
|
|
||||||
export default function RoomsScreen() {
|
export default function RoomsScreen() {
|
||||||
const theme = useTheme();
|
|
||||||
const navigation = useNavigation<any>();
|
const navigation = useNavigation<any>();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import React, { useState } from 'react';
|
|||||||
import { StyleSheet, View, Alert, ScrollView } from 'react-native';
|
import { StyleSheet, View, Alert, ScrollView } from 'react-native';
|
||||||
import { Text, Card, Button, useTheme, Divider, TextInput, List, Avatar } from 'react-native-paper';
|
import { Text, Card, Button, useTheme, Divider, TextInput, List, Avatar } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { API_BASE_URL } from '@env';
|
import { API_BASE_URL } from '@env';
|
||||||
|
|
||||||
export default function SettingsScreen() {
|
export default function SettingsScreen() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigation = useNavigation<any>();
|
|
||||||
const { user, logout } = useAuthStore();
|
const { user, logout } = useAuthStore();
|
||||||
const [apiUrl, setApiUrl] = useState(API_BASE_URL || 'http://localhost:3000/api/v1');
|
const [apiUrl, setApiUrl] = useState(API_BASE_URL || 'http://localhost:3000/api/v1');
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { StyleSheet, View, StatusBar, Alert, ActivityIndicator } from 'react-native';
|
import { StyleSheet, View, StatusBar, ActivityIndicator } from 'react-native';
|
||||||
import { Text, IconButton, Surface } from 'react-native-paper';
|
import { Text, IconButton, Surface } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { StyleSheet, View, FlatList, RefreshControl, Alert } from 'react-native'
|
|||||||
import { Text, Card, Button, useTheme, ActivityIndicator, Surface, IconButton, SegmentedButtons } from 'react-native-paper';
|
import { Text, Card, Button, useTheme, ActivityIndicator, Surface, IconButton, SegmentedButtons } from 'react-native-paper';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { getCameras, getClips, playCamera } from '../api/video';
|
import { getCameras, getClips } from '../api/video';
|
||||||
import { extractErrorMessage, resolveUrl } from '../api/client';
|
import { resolveUrl } from '../api/client';
|
||||||
import { formatDateTime, formatDuration, formatFileSize } from '../utils/format';
|
import { formatDateTime, formatDuration, formatFileSize } from '../utils/format';
|
||||||
import type { Camera, VideoClip } from '../types';
|
import type { Camera, VideoClip } from '../types';
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"framework": "React"
|
"framework": "React"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"build:weapp": "taro build --type weapp",
|
"build:weapp": "taro build --type weapp",
|
||||||
"build:swan": "taro build --type swan",
|
"build:swan": "taro build --type swan",
|
||||||
"build:alipay": "taro build --type alipay",
|
"build:alipay": "taro build --type alipay",
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useDidShow, useDidHide } from '@tarojs/taro';
|
import { useDidShow, useDidHide } from '@tarojs/taro';
|
||||||
import { useStore } from '@/store/useStore';
|
import { useStore } from '@/store/useStore';
|
||||||
import './app.scss';
|
import './app.scss';
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ const AlertsPage: React.FC = () => {
|
|||||||
<View className={styles.alarmHeader}>
|
<View className={styles.alarmHeader}>
|
||||||
<View className={styles.alarmTitleRow}>
|
<View className={styles.alarmTitleRow}>
|
||||||
<Text className={styles.alarmIcon}>
|
<Text className={styles.alarmIcon}>
|
||||||
{alarm.severity === 'critical' || alarm.severity === 1 ? '🔴' : '🟡'}
|
{alarm.severity === 'critical' || alarm.severity === '1' ? '🔴' : '🟡'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className={styles.alarmTitle}>
|
<Text className={styles.alarmTitle}>
|
||||||
{alarm.title || alarm.code || '环境告警'}
|
{alarm.title || alarm.code || '环境告警'}
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ const DashboardPage: React.FC = () => {
|
|||||||
className={styles.sectionAction}
|
className={styles.sectionAction}
|
||||||
onClick={() => handleRoomDetail(selectedRoom.id)}
|
onClick={() => handleRoomDetail(selectedRoom.id)}
|
||||||
>
|
>
|
||||||
详情 >
|
详情 {'>'}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -347,7 +347,7 @@ const DashboardPage: React.FC = () => {
|
|||||||
<View className={styles.section}>
|
<View className={styles.section}>
|
||||||
<View className={styles.sectionHeader}>
|
<View className={styles.sectionHeader}>
|
||||||
<Text className={styles.sectionTitle}>最近告警</Text>
|
<Text className={styles.sectionTitle}>最近告警</Text>
|
||||||
<Text className={styles.sectionAction} onClick={handleAlarmTap}>全部 ></Text>
|
<Text className={styles.sectionAction} onClick={handleAlarmTap}>全部 {'>'}</Text>
|
||||||
</View>
|
</View>
|
||||||
{recentAlarms.length > 0 ? (
|
{recentAlarms.length > 0 ? (
|
||||||
<View className={styles.alarmPreview}>
|
<View className={styles.alarmPreview}>
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ const ControlPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = (newVal: boolean) => {
|
const handleToggle = (event: { detail: { value: boolean } }) => {
|
||||||
handleSend('power', newVal ? 'on' : 'off');
|
handleSend('power', event.detail.value ? 'on' : 'off');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMetricColor = (status: string) => {
|
const getMetricColor = (status: string) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Image, ScrollView, Text, View } from '@tarojs/components';
|
import { Image, ScrollView, Text, View } from '@tarojs/components';
|
||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import { useRouter } from '@tarojs/taro';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import {
|
import {
|
||||||
getArticle,
|
getArticle,
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ const LampPage: React.FC = () => {
|
|||||||
const [stepsMap, setStepsMap] = useState<Record<string, LampTestStep[]>>({});
|
const [stepsMap, setStepsMap] = useState<Record<string, LampTestStep[]>>({});
|
||||||
const [expandedId, setExpandedId] = useState('');
|
const [expandedId, setExpandedId] = useState('');
|
||||||
const [result, setResult] = useState('');
|
const [result, setResult] = useState('');
|
||||||
const [imagePath, setImagePath] = useState('');
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
@@ -71,7 +70,6 @@ const LampPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
setExpandedId(t.id);
|
setExpandedId(t.id);
|
||||||
setResult(t.result || '');
|
setResult(t.result || '');
|
||||||
setImagePath('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStepToggle = async (t: LampTest, s: LampTestStep, done: boolean) => {
|
const handleStepToggle = async (t: LampTest, s: LampTestStep, done: boolean) => {
|
||||||
@@ -88,7 +86,6 @@ const LampPage: React.FC = () => {
|
|||||||
sizeType: ['compressed'],
|
sizeType: ['compressed'],
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
const path = res.tempFilePaths[0];
|
const path = res.tempFilePaths[0];
|
||||||
setImagePath(path);
|
|
||||||
try {
|
try {
|
||||||
const up = await uploadLampResultImage(t.id, path);
|
const up = await uploadLampResultImage(t.id, path);
|
||||||
setTests((prev) =>
|
setTests((prev) =>
|
||||||
@@ -155,11 +152,11 @@ const LampPage: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text className={styles.sectionLabel}>检测结果(天蓝=阳性,紫罗兰=阴性)</Text>
|
<Text className={styles.sectionLabel}>检测结果(天蓝=阳性,紫罗兰=阴性)</Text>
|
||||||
<RadioGroup value={result} onChange={(e) => setResult(e.detail.value)}>
|
<RadioGroup onChange={(e) => setResult(e.detail.value[0])}>
|
||||||
<View className={styles.radioRow}>
|
<View className={styles.radioRow}>
|
||||||
<Radio value="positive">阳性</Radio>
|
<Radio value="positive" checked={result === 'positive'}>阳性</Radio>
|
||||||
<Radio value="negative">阴性</Radio>
|
<Radio value="negative" checked={result === 'negative'}>阴性</Radio>
|
||||||
<Radio value="invalid">无效</Radio>
|
<Radio value="invalid" checked={result === 'invalid'}>无效</Radio>
|
||||||
</View>
|
</View>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
{t.result ? <Text className={styles.resultHint}>当前结果:{RESULT_LABELS[t.result]}</Text> : null}
|
{t.result ? <Text className={styles.resultHint}>当前结果:{RESULT_LABELS[t.result]}</Text> : null}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const NotificationPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const res = await Taro.requestSubscribeMessage({
|
const res = await Taro.requestSubscribeMessage({
|
||||||
tmplIds: configured.map((t) => t.id),
|
tmplIds: configured.map((t) => t.id),
|
||||||
|
entityIds: configured.map((t) => t.id),
|
||||||
});
|
});
|
||||||
for (const t of configured) {
|
for (const t of configured) {
|
||||||
if (res[t.id] === 'accept') {
|
if (res[t.id] === 'accept') {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { View, Text, Video } from '@tarojs/components';
|
import { View, Text, Video } from '@tarojs/components';
|
||||||
import Taro, { useRouter } from '@tarojs/taro';
|
import { useRouter } from '@tarojs/taro';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import { getCameras, playCamera } from '@/api/video';
|
import { getCameras } from '@/api/video';
|
||||||
import { resolveUrl } from '@/api/config';
|
import { resolveUrl } from '@/api/config';
|
||||||
import type { Camera, VideoPlayResponse } from '@/types';
|
import type { Camera } from '@/types';
|
||||||
|
|
||||||
const VideoPlayerPage: React.FC = () => {
|
const VideoPlayerPage: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": "",
|
"baseUrl": "",
|
||||||
"rootDir": "",
|
"rootDir": "",
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$root = Split-Path -Parent $PSScriptRoot
|
||||||
|
$script:failed = @()
|
||||||
|
|
||||||
|
function Resolve-Go {
|
||||||
|
$cmd = Get-Command go -ErrorAction SilentlyContinue
|
||||||
|
if ($cmd) {
|
||||||
|
return $cmd.Source
|
||||||
|
}
|
||||||
|
foreach ($candidate in @('C:\Program Files\Go\bin\go.exe', 'C:\Go\bin\go.exe')) {
|
||||||
|
if (Test-Path -LiteralPath $candidate) {
|
||||||
|
return $candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw 'Go executable not found. Install Go 1.23 or add it to PATH.'
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-Check {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Name,
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[scriptblock]$Command
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "[$Name]"
|
||||||
|
try {
|
||||||
|
& $Command
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "FAILED: $Name (exit $LASTEXITCODE)"
|
||||||
|
$script:failed += $Name
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "FAILED: $Name - $($_.Exception.Message)"
|
||||||
|
$script:failed += $Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$goExe = Resolve-Go
|
||||||
|
|
||||||
|
Push-Location (Join-Path $root 'server-go')
|
||||||
|
try {
|
||||||
|
Invoke-Check 'server-go test' { & $goExe test ./... }
|
||||||
|
Invoke-Check 'server-go vet' { & $goExe vet ./... }
|
||||||
|
Invoke-Check 'server-go build' { & $goExe build ./... }
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location (Join-Path $root 'web')
|
||||||
|
try {
|
||||||
|
Invoke-Check 'web test' { npm test }
|
||||||
|
Invoke-Check 'web lint' { npm run lint }
|
||||||
|
Invoke-Check 'web build' { npm run build }
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location (Join-Path $root 'miniapp')
|
||||||
|
try {
|
||||||
|
Invoke-Check 'miniapp typecheck' { npm run typecheck }
|
||||||
|
Invoke-Check 'miniapp build:weapp' { npm run build:weapp }
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location (Join-Path $root 'app')
|
||||||
|
try {
|
||||||
|
Invoke-Check 'app typecheck' { npm run tsc }
|
||||||
|
Invoke-Check 'app lint' { npm run lint }
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
$aiPython = Join-Path $root 'ai-service\.venv\Scripts\python.exe'
|
||||||
|
if (-not (Test-Path -LiteralPath $aiPython)) {
|
||||||
|
$aiPython = 'python'
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location (Join-Path $root 'ai-service')
|
||||||
|
try {
|
||||||
|
Invoke-Check 'ai-service pytest' { & $aiPython -m pytest }
|
||||||
|
} finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:failed.Count -gt 0) {
|
||||||
|
Write-Error "Verification failed for: $($script:failed -join ', ')"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host 'All verification checks passed.'
|
||||||
|
exit 0
|
||||||
@@ -743,3 +743,38 @@ MVP 沿用 IoTDB(现状);TDengine 作为生产规模化候选(先基准
|
|||||||
|
|
||||||
- 删除索引:`codebase-memory-mcp cli delete_project '{"project":"D-silk-master-84e4ddc"}'`;
|
- 删除索引:`codebase-memory-mcp cli delete_project '{"project":"D-silk-master-84e4ddc"}'`;
|
||||||
- 恢复 Codex 配置:用备份 `config.toml.cbm-bak-20260813-213259` 覆盖 `config.toml` 即可;本次未改业务代码、数据库或服务器。
|
- 恢复 Codex 配置:用备份 `config.toml.cbm-bak-20260813-213259` 覆盖 `config.toml` 即可;本次未改业务代码、数据库或服务器。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-08-13 整改 Task 1:恢复可复现的多端质量基线
|
||||||
|
|
||||||
|
### 做了什么
|
||||||
|
|
||||||
|
- 修复小程序 `dashboard` 两处 TS1382 裸 `>`,新增 `npm run typecheck`;
|
||||||
|
- 小程序 tsconfig 增加 `skipLibCheck`,修复 alerts、设备控制、LAMP、通知、视频播放、app、知识详情等源码类型问题,使 `tsc --noEmit` 通过;
|
||||||
|
- APP 依赖对齐 RN 0.74:`react` 调整为 `18.2.0`,移除未使用的 `victory-native`,补充 `@react-native/eslint-config`、`eslint`、`prettier`,新增 `.eslintrc.js`;
|
||||||
|
- APP 清理未使用导入/变量,`npm run tsc` 通过,`npm run lint` 无 error;
|
||||||
|
- 新增 `scripts/verify.ps1` 单命令质量门禁,覆盖 Go、Web、小程序、APP、ai-service;`ai-service/README.md` 增加质量检查说明;
|
||||||
|
- `.gitignore` 增加 `scripts/verify.ps1` 例外。CI 接入未做,等待 Task 0 确认平台。
|
||||||
|
|
||||||
|
### 设计思路与决策依据
|
||||||
|
|
||||||
|
- Task 1 目标是建立可复现门禁,因此以“最小修复、不重排全仓”为原则:Taro 依赖声明文件用 `skipLibCheck` 隔离,真实源码错误逐个修正;
|
||||||
|
- APP 不重排全仓:将 `prettier/prettier` 降为 warning,保留 1032 个风格 warning 待后续单独格式化任务处理,不让既有格式差异阻塞 lint 门禁;
|
||||||
|
- APP 安装失败根因是 RN 0.74 要求 `react@18.2.0`,以及 `victory-native` 最新依赖链引入 React 19 的 Skia;因源码未引用 `victory-native`,直接移除;
|
||||||
|
- `verify.ps1` 显式解析 Go 路径,并在存在 `.venv` 时使用 `ai-service/.venv/Scripts/python.exe`,解决本机 PATH 与系统 Python 缺依赖问题。
|
||||||
|
|
||||||
|
### 验证结果
|
||||||
|
|
||||||
|
- `powershell -ExecutionPolicy Bypass -File scripts/verify.ps1` 最终 exit 0;
|
||||||
|
- Go:`go test ./...`、`go vet ./...`、`go build ./...` 通过;
|
||||||
|
- Web:`npm test` 3/3 通过,`npm run lint` 有 5 个 warning,`npm run build` 通过;
|
||||||
|
- 小程序:`npm run typecheck`、`npm run build:weapp` 通过;
|
||||||
|
- APP:`npm run tsc` 通过,`npm run lint` 0 error / 1032 warning;
|
||||||
|
- ai-service:pytest 8/8 通过,仅保留 Starlette/httpx deprecation warning;
|
||||||
|
- 未执行 CI、服务器部署、真实模型或容量验收,这些仍等待 Task 0 决策与后续任务。
|
||||||
|
|
||||||
|
### 回滚点
|
||||||
|
|
||||||
|
- 本任务前基线提交为 `1b8cab1`;回滚可删除/还原本 Task 1 分支提交,恢复 `.gitignore`、APP 依赖锁文件与小程序类型修复;
|
||||||
|
- 无数据库、环境变量、服务器或生成产物变更;APP `node_modules` 为忽略目录,不影响回滚。
|
||||||
|
|||||||
Reference in New Issue
Block a user