feat(UI): 四 Tab 头部统一 + 清单模板分类 + 宏观 tab 顺序与溢出提示

头部统一
- 新增 PageHeader 组件: 高度 23px (原 NavBar 45px 的一半), 标题 12px 主色加粗, 背景取页面底色
- 打卡 / 宏观 / 监控 三页各自引用, 互不影响; 宏观与监控补齐「宏观」「监控」标题
- 「我」页移除 NavBar 空白条, 蓝色头像区直接贴顶 (不引用 PageHeader, 与其余三页解耦)
- 清理失效样式 .me-navbar

清单模板分类
- 分段由 2 项扩为 3 项: 进行中 / 已归档 / 模板; 右上角按钮随分段切换
- 模板独立增删改查: 新增 GET /templates/:id、PUT /templates/:id; 预置模板拒绝改删
- 模板按名称字母顺序排序 (Intl.Collator zh-Hans-CN, 中文按拼音/数字按数值), 前后端同口径
- 新建清单去掉场景选择器, 仅保留名称; scene 改为沿用模板自带场景
- 模板改为从右侧滑出 78vw 面板选择, 首项「不使用模板」

宏观 tab
- 顺序改为 报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章, 默认选中新闻
- /macro 由路由直接重定向到 /macro/news, activeFromPath 兜底同样返回 news
- SwipeTabs 增加右侧溢出提示: 渐变遮罩 + 主色圆底箭头, 可点击向右翻页, 滚到末尾自动消失
- 切换 tab 时自动把激活项滚到可视区中央
This commit is contained in:
cheney
2026-10-23 16:40:32 +08:00
parent 5f41cb05b7
commit c765755906
11 changed files with 623 additions and 136 deletions
+49 -6
View File
@@ -73,25 +73,40 @@ function withProgress(list) {
// ---------- 模板 ----------
// GET /api/checklists/templates
// 模板按名称字母顺序 (中文按拼音) 排序, 前后端共用同一比较器语义
const NAME_COLLATOR = new Intl.Collator('zh-Hans-CN', { numeric: true, sensitivity: 'base' })
const byName = (a, b) => NAME_COLLATOR.compare(String(a.name || ''), String(b.name || ''))
// GET /api/checklists/templates 模板列表 (含系统预置, 按字母顺序)
router.get('/templates', async (_req, res, next) => {
try {
await ensureBuiltin()
const rows = await prisma.checkListTemplate.findMany({
where: { OR: [{ builtin: true }, { userId: _req.user.sub }] },
orderBy: [{ builtin: 'desc' }, { id: 'asc' }],
})
res.json({ items: rows })
res.json({ items: rows.sort(byName) })
} catch (e) { next(e) }
})
// POST /api/checklists/templates 另存为模板 { name, scene, items: [{title, note}] }
// GET /api/checklists/templates/:id 模板详情
router.get('/templates/:id', async (req, res, next) => {
try {
const row = await prisma.checkListTemplate.findFirst({
where: { id: Number(req.params.id), OR: [{ builtin: true }, { userId: req.user.sub }] },
})
if (!row) return res.status(404).json({ error: '模板不存在' })
res.json({ item: row })
} catch (e) { next(e) }
})
// POST /api/checklists/templates 另存为模板 { name, scene?, items: [{title, note}] }
router.post('/templates', async (req, res, next) => {
try {
const userId = req.user.sub
const { name, scene = '', items = [] } = req.body || {}
const cleanName = String(name || '').trim()
if (!cleanName) return res.status(400).json({ error: '模板名称不能为空' })
if (cleanName.length > 20) return res.status(400).json({ error: '模板名称不能超过 20 个字' })
const list = (Array.isArray(items) ? items : [])
.map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') }))
.filter((i) => i.title)
@@ -103,12 +118,40 @@ router.post('/templates', async (req, res, next) => {
} catch (e) { next(e) }
})
// DELETE /api/checklists/templates/:id
// PUT /api/checklists/templates/:id 改名称 / 检查项 (系统预置模板不可改)
router.put('/templates/:id', async (req, res, next) => {
try {
const userId = req.user.sub
const id = Number(req.params.id)
const exist = await prisma.checkListTemplate.findFirst({ where: { id, userId, builtin: false } })
if (!exist) return res.status(404).json({ error: '模板不存在、无权限修改或是系统预置模板' })
const { name, scene, items } = req.body || {}
const data = {}
if (name !== undefined) {
const n = String(name).trim()
if (!n) return res.status(400).json({ error: '模板名称不能为空' })
if (n.length > 20) return res.status(400).json({ error: '模板名称不能超过 20 个字' })
data.name = n
}
if (scene !== undefined) data.scene = String(scene)
if (items !== undefined) {
const list = (Array.isArray(items) ? items : [])
.map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') }))
.filter((i) => i.title)
if (!list.length) return res.status(400).json({ error: '模板至少需要一个检查项' })
data.items = list
}
const row = await prisma.checkListTemplate.update({ where: { id }, data })
res.json({ item: row })
} catch (e) { next(e) }
})
// DELETE /api/checklists/templates/:id 仅自建模板可删
router.delete('/templates/:id', async (req, res, next) => {
try {
const userId = req.user.sub
const id = Number(req.params.id)
const exist = await prisma.checkListTemplate.findFirst({ where: { id, userId } })
const exist = await prisma.checkListTemplate.findFirst({ where: { id, userId, builtin: false } })
if (!exist) return res.status(404).json({ error: '模板不存在或无权删除' })
await prisma.checkListTemplate.delete({ where: { id } })
res.json({ ok: true })