- 数据: Habit/CheckIn/CheckList/CheckListItem/CheckListTemplate + 迁移 + 重新生成 client
- 后端: server/checkin/period.js 周期计算(日/周/月/年, 周一起始, Asia/Shanghai)
/api/checkin/* 打卡项CRUD/打卡/撤销/补打/某日详情/月历
/api/checklists/* 清单与检查项CRUD/勾选/全选/复制/模板(系统预置出差·回老家)
- 前端: 底部新增「打卡」Tab -> /checkin, 页内三视图
环形进度 RingProgress + 完成特效(粒子/光晕/触感, 可关闭)
打卡视图: 待办列表/乐观更新/撤销/新建编辑
日历视图: 月历网格 + 今天·昨天·明天 + 日期标记/完成率
清单视图: 场景清单/勾选进度条/模板生成/另存模板/归档
- 文档: docs/V0.2/需求文档.md 补充实现约定(附录B)与接口清单
74 lines
2.6 KiB
JavaScript
74 lines
2.6 KiB
JavaScript
import 'dotenv/config'
|
|
import path from 'node:path'
|
|
import fs from 'node:fs'
|
|
import express from 'express'
|
|
import cors from 'cors'
|
|
import authRouter from './routes/auth.js'
|
|
import dataRouter from './routes/data.js'
|
|
import newsRouter from './routes/news.js'
|
|
import alertsRouter from './routes/alerts.js'
|
|
import articlesRouter from './routes/articles.js'
|
|
import otaRouter from './routes/ota.js'
|
|
import apizeroRouter from './routes/apizero.js'
|
|
import monitorsRouter from './routes/monitors.js'
|
|
import mihomeRouter from './routes/mihome.js'
|
|
import checkinRouter from './routes/checkin.js'
|
|
import checklistsRouter from './routes/checklists.js'
|
|
|
|
const PORT = Number(process.env.PORT) || 3001
|
|
const DIST_DIR = path.join(process.cwd(), 'dist')
|
|
const HAS_DIST = fs.existsSync(DIST_DIR)
|
|
|
|
const app = express()
|
|
app.use(cors())
|
|
app.use(express.json())
|
|
|
|
// 健康检查
|
|
app.get('/api/health', (_req, res) => res.json({ ok: true, ts: Date.now() }))
|
|
|
|
// 业务路由
|
|
app.use('/api/auth', authRouter)
|
|
// OTA 接口是公开的 (检查/下载更新不需要登录), 必须挂载在带 authRequired 的 router 之前,
|
|
// 否则 /api/ota/* 请求会先进入 dataRouter 等并被 authRequired 以 401 拦截
|
|
app.use('/api', otaRouter)
|
|
app.use('/api', dataRouter)
|
|
app.use('/api', newsRouter)
|
|
app.use('/api', alertsRouter)
|
|
app.use('/api', articlesRouter)
|
|
app.use('/api', apizeroRouter)
|
|
app.use('/api', monitorsRouter)
|
|
app.use('/api/mihome', mihomeRouter)
|
|
// V0.2 打卡 / 日历 / 清单
|
|
app.use('/api/checkin', checkinRouter)
|
|
app.use('/api/checklists', checklistsRouter)
|
|
|
|
// 静态资源 (Vite build 产物)
|
|
if (HAS_DIST) {
|
|
app.use(express.static(DIST_DIR, { index: false, maxAge: '1h', etag: true }))
|
|
|
|
// SPA fallback: 非 /api/* 的请求一律返回 index.html, 由前端路由处理
|
|
app.get(/^\/(?!api\/).*/, (_req, res, next) => {
|
|
const indexHtml = path.join(DIST_DIR, 'index.html')
|
|
if (!fs.existsSync(indexHtml)) return next()
|
|
res.sendFile(indexHtml)
|
|
})
|
|
}
|
|
|
|
// 统一错误处理
|
|
app.use((err, _req, res, _next) => {
|
|
console.error('[api error]', err)
|
|
res.status(500).json({ error: err.message || '服务器内部错误' })
|
|
})
|
|
|
|
// 启动定时任务: 每天凌晨 3 点拉取新闻
|
|
import('./cron.js').catch((e) => console.error('[cron] 加载失败:', e.message))
|
|
|
|
// 米家环境监控定时调度 (独立注册, 不与 cron.js 绑定, 见 server/mihome/scheduler.js)
|
|
import('./mihome/scheduler.js').catch((e) => console.error('[cron] 米家调度注册失败:', e.message))
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`[iboard-api] listening on http://127.0.0.1:${PORT}`)
|
|
console.log(`[iboard-api] static dist: ${HAS_DIST ? DIST_DIR : '(not built, API only)'}`)
|
|
})
|
|
|