From b1c88fc5e66ee3025be4e8a990412c8a1175f1f3 Mon Sep 17 00:00:00 2001 From: eafonyang Date: Thu, 30 Jul 2026 15:08:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=90=8E=E7=AB=AF=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=96=B9=E5=BC=8F=EF=BC=8C=E4=BF=AE=E6=94=B9=E9=A6=96?= =?UTF-8?q?=E9=A1=B5=E6=8A=A5=E8=A1=A8=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dashboard/backend/Dockerfile | 19 ++++--------- dashboard/backend/package.json | 28 +++++++++---------- dashboard/backend/pnpm-lock.yaml | 10 +++---- dashboard/backend/restart.sh | 2 +- dashboard/backend/src/routes/blacklist.ts | 4 +-- dashboard/backend/src/routes/dashboard.ts | 14 ++++++++-- dashboard/backend/src/routes/ota.ts | 4 +-- .../backend/src/routes/otaTargetDevice.ts | 4 +-- dashboard/backend/start.sh | 2 +- dashboard/backend/tsconfig.json | 1 - dashboard/frontend/src/views/home/index.vue | 17 +++++------ dashboard/scripts/upload.sh | 5 ++-- 12 files changed, 56 insertions(+), 54 deletions(-) diff --git a/dashboard/backend/Dockerfile b/dashboard/backend/Dockerfile index 52babb3..d082292 100644 --- a/dashboard/backend/Dockerfile +++ b/dashboard/backend/Dockerfile @@ -11,22 +11,15 @@ WORKDIR /app # Create logs directory RUN mkdir -p /app/logs -# Install dependencies (including devDependencies for build) -COPY package.json pnpm-lock.yaml ./ -RUN corepack enable pnpm && pnpm install --frozen-lockfile +# Install production dependencies only +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ +RUN corepack enable pnpm && pnpm install --frozen-lockfile --prod -# Copy application code -COPY tsconfig.json drizzle.config.ts ./ -COPY src/ ./src/ - -# Build TypeScript → dist/ -RUN pnpm build - -# Remove devDependencies after build -RUN pnpm prune --prod +# Copy pre-built application +COPY dist/ ./dist/ # Expose port EXPOSE 8000 # Run the compiled application -CMD ["node", "--conditions", "@zod/source", "dist/app.js"] +CMD ["node", "dist/app.js"] diff --git a/dashboard/backend/package.json b/dashboard/backend/package.json index 0153de9..6b140f9 100644 --- a/dashboard/backend/package.json +++ b/dashboard/backend/package.json @@ -6,36 +6,36 @@ "type": "module", "main": "dist/app.js", "scripts": { - "dev": "tsx watch --conditions @zod/source src/app.ts", + "dev": "tsx watch src/app.ts", "build": "tsc", - "start": "node --conditions @zod/source dist/app.js", + "start": "node dist/app.js", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio" }, "dependencies": { - "express": "^5.2", + "@aws-sdk/client-s3": "^3.1098", + "axios": "^1.19", "cors": "^2.8", - "mysql2": "^3.23", + "dotenv": "^17.4", "drizzle-orm": "^0.45", + "express": "^5.2", + "ioredis": "^5.11", "jsonwebtoken": "^9.0", "multer": "^2.2", - "axios": "^1.19", - "dotenv": "^17.4", - "zod": "^3.25", - "@aws-sdk/client-s3": "^3.1098", + "mysql2": "^3.23", "winston": "^3.19", - "ioredis": "^5.11" + "zod": "^4.4.3" }, "devDependencies": { - "tsx": "^4.23", - "typescript": "^7.0", - "drizzle-kit": "^0.31", - "@types/express": "^5.0", "@types/cors": "^2.8", + "@types/express": "^5.0", "@types/jsonwebtoken": "^9.0", "@types/multer": "^2.2", - "@types/node": "^22.0" + "@types/node": "^22.0", + "drizzle-kit": "^0.31", + "tsx": "^4.23", + "typescript": "^7.0" } } diff --git a/dashboard/backend/pnpm-lock.yaml b/dashboard/backend/pnpm-lock.yaml index fc5ab76..56308ed 100644 --- a/dashboard/backend/pnpm-lock.yaml +++ b/dashboard/backend/pnpm-lock.yaml @@ -42,8 +42,8 @@ importers: specifier: ^3.19 version: 3.19.0 zod: - specifier: ^3.25 - version: 3.25.0 + specifier: ^4.4.3 + version: 4.4.3 devDependencies: '@types/cors': specifier: ^2.8 @@ -1482,8 +1482,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - zod@3.25.0: - resolution: {integrity: sha512-ficnZKUW0mlNivqeJkosTEkGbJ6NKCtSaOHGx5aXbtfeWMdRyzXLbAIn19my4C/KB7WPY/p9vlGPt+qpOp6c4Q==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: @@ -2778,4 +2778,4 @@ snapshots: wrappy@1.0.2: {} - zod@3.25.0: {} + zod@4.4.3: {} diff --git a/dashboard/backend/restart.sh b/dashboard/backend/restart.sh index 65f6ef7..72a25cc 100755 --- a/dashboard/backend/restart.sh +++ b/dashboard/backend/restart.sh @@ -46,4 +46,4 @@ echo "按 Ctrl+C 停止服务" echo "================================================" echo "" -exec node --conditions @zod/source dist/app.js +exec node dist/app.js diff --git a/dashboard/backend/src/routes/blacklist.ts b/dashboard/backend/src/routes/blacklist.ts index 23b54aa..b3173e8 100644 --- a/dashboard/backend/src/routes/blacklist.ts +++ b/dashboard/backend/src/routes/blacklist.ts @@ -139,7 +139,7 @@ router.post('/api/blacklist/', async (req: Request, res: Response) => { try { const parsed = BlackListCreateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } @@ -174,7 +174,7 @@ router.put('/api/blacklist/:id', async (req: Request, res: Response) => { const parsed = BlackListUpdateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } diff --git a/dashboard/backend/src/routes/dashboard.ts b/dashboard/backend/src/routes/dashboard.ts index db78692..8fe4123 100644 --- a/dashboard/backend/src/routes/dashboard.ts +++ b/dashboard/backend/src/routes/dashboard.ts @@ -8,6 +8,14 @@ import { authMiddleware } from '../middleware/auth.js'; const router: RouterType = Router(); +/** 格式化为本地日期字符串 YYYY-MM-DD(避免 toISOString 的 UTC 偏移) */ +function toLocalDateStr(d: Date): string { + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + return `${y}-${m}-${day}`; +} + // 所有 dashboard 接口需登录 router.use(authMiddleware); @@ -135,7 +143,7 @@ router.get('/api/dashboard/device-daily', async (req: Request, res: Response) => const dateList: string[] = []; const tmp = new Date(startDate); while (tmp <= now) { - dateList.push(tmp.toISOString().slice(0, 10)); + dateList.push(toLocalDateStr(tmp)); tmp.setDate(tmp.getDate() + 1); } @@ -228,14 +236,14 @@ router.get('/api/dashboard/active-daily', async (req: Request, res: Response) => const dateList: string[] = []; const tmp = new Date(startDate); while (tmp <= now) { - dateList.push(tmp.toISOString().slice(0, 10)); + dateList.push(toLocalDateStr(tmp)); tmp.setDate(tmp.getDate() + 1); } const modelSet = new Set(); const dataMap = new Map>(); for (const row of rows) { - const dateStr = String(row.active_date).slice(0, 10); + const dateStr = toLocalDateStr(new Date(row.active_date)); modelSet.add(row.model); if (!dataMap.has(dateStr)) dataMap.set(dateStr, {}); dataMap.get(dateStr)![row.model] = Number(row.count); diff --git a/dashboard/backend/src/routes/ota.ts b/dashboard/backend/src/routes/ota.ts index 2e97009..5d46b62 100644 --- a/dashboard/backend/src/routes/ota.ts +++ b/dashboard/backend/src/routes/ota.ts @@ -184,7 +184,7 @@ router.post('/api/ota/', async (req: Request, res: Response) => { try { const parsed = OtaCreateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } @@ -241,7 +241,7 @@ router.put('/api/ota/:ota_id', async (req: Request, res: Response) => { const parsed = OtaUpdateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } diff --git a/dashboard/backend/src/routes/otaTargetDevice.ts b/dashboard/backend/src/routes/otaTargetDevice.ts index 1c0945b..18d47b4 100644 --- a/dashboard/backend/src/routes/otaTargetDevice.ts +++ b/dashboard/backend/src/routes/otaTargetDevice.ts @@ -139,7 +139,7 @@ router.post('/api/ota-target-device/', async (req: Request, res: Response) => { try { const parsed = OtaTargetDeviceCreateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } @@ -185,7 +185,7 @@ router.put('/api/ota-target-device/:id', async (req: Request, res: Response) => const parsed = OtaTargetDeviceUpdateSchema.safeParse(req.body); if (!parsed.success) { - const errors = parsed.error.errors.map((e) => e.message).join('; '); + const errors = parsed.error.issues.map((e: { message: string }) => e.message).join('; '); res.json(ApiResponse.error(errors)); return; } diff --git a/dashboard/backend/start.sh b/dashboard/backend/start.sh index de88a2b..ab3ee46 100755 --- a/dashboard/backend/start.sh +++ b/dashboard/backend/start.sh @@ -20,4 +20,4 @@ echo "Building TypeScript..." pnpm build echo "Starting server..." -exec node --conditions @zod/source dist/app.js +exec node dist/app.js diff --git a/dashboard/backend/tsconfig.json b/dashboard/backend/tsconfig.json index 4315b71..7663138 100644 --- a/dashboard/backend/tsconfig.json +++ b/dashboard/backend/tsconfig.json @@ -3,7 +3,6 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", - "customConditions": ["@zod/source"], "esModuleInterop": true, "strict": true, "outDir": "dist", diff --git a/dashboard/frontend/src/views/home/index.vue b/dashboard/frontend/src/views/home/index.vue index 708b460..2689cda 100644 --- a/dashboard/frontend/src/views/home/index.vue +++ b/dashboard/frontend/src/views/home/index.vue @@ -173,12 +173,7 @@ watch(deviceDays, () => { // ===== 设备日活趋势 ===== const activeDays = ref(30); const activeLoading = ref(false); -const activeLatest = ref({ date: '', total: 0 }); - -const activeLatestLabel = computed(() => { - if (activeDays.value === 1) return `${activeLatest.value.date} 总活跃`; - return `${activeLatest.value.date.slice(5)} 总日活`; -}); +const activeTodayTotal = ref(0); function buildActiveChartOptions(dates: string[], series: Array<{ name: string; data: number[] }>): ECOption { const isHourly = activeDays.value === 1; @@ -257,7 +252,13 @@ async function loadActiveDaily() { activeLoading.value = true; const { data, error } = await fetchGetActiveDaily(activeDays.value); if (!error && data) { - activeLatest.value = data.latest; + // 今天模式(按小时):累加所有小时;多天模式:取末尾(今天) + const isHourly = activeDays.value === 1; + const lastIdx = (data.dates?.length || 1) - 1; + activeTodayTotal.value = (data.series || []).reduce((sum: number, s: { data: number[] }) => { + if (isHourly) return sum + s.data.reduce((a: number, b: number) => a + b, 0); + return sum + (s.data[lastIdx] || 0); + }, 0); lastActiveData.value = { dates: data.dates, series: data.series }; await updateActiveChart(() => buildActiveChartOptions(data.dates, data.series)); } @@ -359,7 +360,7 @@ onMounted(() => {