从零做一个业务页
这篇文档面向第一次写 web-admin 应用的人。目标很简单:你照着做,最后能在左侧菜单里点开一个自己新增的页面。
本示例选择新增一个“客户管理”页面,路径是 /customers。
1. 先准备一个可运行应用
如果你还没有自己的脚手架项目,先在仓库里生成一个:
pnpm --filter @stratix/cli build
mkdir -p examples
cd examples
node ../packages/cli/dist/bin/stratix.js init app web-admin my-admin --no-install
cd my-admin
pnpm install --ignore-workspace
开发时常用命令:
pnpm dev
pnpm build
pnpm test
pnpm preview --host 127.0.0.1 --port 4273
如果你只是先理解模板,也可以直接参考仓库现成样例:
examples/web-admin-preview
2. 第一步:写页面组件
先新增页面文件:
src/features/customers/pages/customers-page.tsx
第一版不要一上来就接后端。先把页面骨架、标题和一组本地假数据跑起来,确认路由和菜单是通的。
import { Plus } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
const mockCustomers = [
{
id: 'cust_001',
level: 'A',
name: '华北一公司',
owner: '张三',
status: '跟进中',
},
{
id: 'cust_002',
level: 'B',
name: '华东示范客户',
owner: '李四',
status: '待签约',
},
]
export function CustomersPage() {
return (
<div className='flex min-h-[calc(100svh-11.5rem)] flex-col gap-4'>
<section className='flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between'>
<div>
<h2 className='text-2xl font-semibold tracking-tight text-foreground'>
客户管理
</h2>
<p className='mt-2 text-sm leading-6 text-muted-foreground'>
第一版先确认页面、菜单和路由已经完整接通。
</p>
</div>
<Button className='rounded-xl px-4'>
<Plus className='size-4' />
新建客户
</Button>
</section>
<Card>
<CardHeader>
<CardTitle>客户列表</CardTitle>
<CardDescription>
先用本地假数据占位,等页面走通后再接接口。
</CardDescription>
</CardHeader>
<CardContent className='space-y-3'>
{mockCustomers.map((customer) => (
<div
key={customer.id}
className='flex items-center justify-between rounded-xl border px-4 py-3'
>
<div>
<div className='font-medium'>{customer.name}</div>
<div className='text-sm text-muted-foreground'>
负责人:{customer.owner} · 等级:{customer.level}
</div>
</div>
<div className='text-sm text-muted-foreground'>
{customer.status}
</div>
</div>
))}
</CardContent>
</Card>
</div>
)
}
为什么先这样做:
- 你先验证“能不能进页面”,而不是一开始就把问题混到接口、状态管理和表单里。
- 小步推进时,出错位置更容易判断。
3. 第二步:注册路由
新增路由文件:
src/routes/_authenticated/customers.tsx
内容如下:
import { createFileRoute } from '@tanstack/react-router'
import { CustomersPage } from '@/features/customers/pages/customers-page'
export const Route = createFileRoute('/_authenticated/customers')({
component: CustomersPage,
})
这里要注意两件事:
- 路由文件路径和
createFileRoute()里的路径要一致。 routeTree.gen.ts是生成文件,不要手改。通常跑一次pnpm dev或pnpm build后会自动更新。
4. 第三步:把页面放进左侧菜单
打开:
src/app/config/navigation.ts
在合适的分组里新增一个菜单项。最简单的方式,是先放到“系统管理”或“运营中心”下面:
{
kind: 'item',
title: '客户管理',
to: '/customers',
description: '查看客户列表、负责人和跟进状态',
icon: 'users',
keywords: ['客户', 'crm', 'customers'],
}
做到这里,你应该已经能从左侧菜单进入 /customers 页面了。
5. 第四步:跑起来,确认链路真的通了
在项目目录执行:
pnpm dev
你应该能看到下面结果:
- 左侧导航出现“客户管理”
- 点击后能进入新页面
- 页面能看到两条 mock 数据
- 浏览器没有路由报错
如果这里还没通,不要继续往下加接口。先把最小链路打通。
6. 想把“页面”升级成“功能”时,下一步改哪里
当你的页面已经能打开,接下来通常有三种演进方向。
6.1 只做静态展示页
直接参考这些现成模块:
src/features/dashboard/pages/dashboard-page.tsxsrc/features/reports/pages/reports-page.tsxsrc/features/settings/pages/settings-page.tsx
适用场景:
- 首页概览
- 只读报表
- 配置说明页
6.2 做列表查询页
优先参考:
src/features/users/pages/users-page.tsxsrc/features/users/hooks/use-users.tssrc/features/users/api/users.tssrc/features/users/lib/search.ts
适用场景:
- 列表
- 搜索
- 分页
- 排序
- 详情侧边栏
6.3 做表单和 CRUD
继续参考 users 模块里的这些文件:
src/features/users/components/user-form-sheet.tsxsrc/features/users/components/user-detail-sheet.tsxsrc/features/users/lib/schema.ts
适用场景:
- 新建
- 编辑
- 状态切换
- 表单校验
最实用的经验是:不要从 0 发明一套新写法,先复制一个最接近的现成模块,再改成你的业务名词。
7. 新手最容易踩的坑
7.1 菜单有了,但点开 404
优先检查:
navigation.ts里的to是否是/customers- 路由文件名是否是
src/routes/_authenticated/customers.tsx createFileRoute('/_authenticated/customers')是否写对
7.2 页面文件写好了,但没有显示
优先检查:
- 页面组件是否正确导出
CustomersPage - 路由文件是否正确引入页面组件
pnpm dev是否已经重新生成routeTree.gen.ts
7.3 一上来就想接真实后端,结果哪里都报错
先回到“本地假数据 + 页面骨架”这一步。模板开发最怕把问题混在一起。你应当按这个顺序推进:
- 页面能打开
- 菜单能进入
- 假数据能显示
- 再接
api/ - 再补
hooks/ - 最后再做表单和复杂交互
8. 完成这个练习后,你应该达到的标准
至少确认下面四件事:
- 你能自己新增一个页面
- 你知道菜单、路由、页面分别改哪里
- 你知道复杂页面优先参考
features/users/ - 你能跑通下面这组验收命令
pnpm build
pnpm test
pnpm preview --host 127.0.0.1 --port 4273
如果这一步你已经做通,再去做“接真实接口”和“拆分独立业务模块”,难度会明显下降。