外观
首个业务模块
本页在 apps/demo 中增加一个只读报表模块。完成后,/reports 能加载类型化数据,并由导航和权限控制访问。示例是新增文件的起点,现有任务模块则是分页、编辑和批量操作的完整参考。
1. 定义请求契约
先约定 GET /api/reports 返回 { success: true, data: { items } },每条记录包含 id、name、total。在 apps/demo/src/api/report.ts 创建:
ts
import { http } from '@uadmin/app'
export interface Report {
id: string
name: string
total: number
}
export function getReports() {
return http.get<{ items: Report[] }>('/reports')
}调用方得到的是 data,不需要再访问 response.data.data。这里的 /reports 会与应用 baseURL: '/api' 组合。
2. 提供同契约 Mock
新增 apps/demo/mock/reports.mock.ts:
ts
import { defineMock } from './_define'
import { ok } from './_envelope'
export default defineMock({
url: '/api/reports',
method: 'GET',
body: () =>
ok({
items: [{ id: 'monthly', name: '2026-09', total: 128 }],
}),
})这份数据是接口种子,不写进页面。*.mock.ts 会被浏览器 adapter 自动收集。还要在 _define.ts 的资源权限映射中加入 reports: 'report',让共享处理器校验 report:read;真实服务端也应执行相同授权。
3. 增加页面和中英文词条
在 apps/demo/src/locales/zh-CN.json 合入 reports.title、reports.name、reports.total、reports.loadFailed,并在 en-US.json 提供对应译文。
新增 apps/demo/src/views/reports/index.vue:
vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { UPage } from '@uadmin/ui'
import { getReports, type Report } from '@/api/report'
const { t } = useI18n()
const rows = ref<Report[]>([])
const loading = ref(true)
const failed = ref(false)
onMounted(async () => {
try {
rows.value = (await getReports()).items
} catch {
failed.value = true
} finally {
loading.value = false
}
})
</script>
<template>
<UPage :title="t('reports.title')">
<el-alert v-if="failed" :title="t('reports.loadFailed')" type="error" />
<el-table v-else v-loading="loading" :data="rows">
<el-table-column prop="name" :label="t('reports.name')" />
<el-table-column prop="total" :label="t('reports.total')" />
</el-table>
</UPage>
</template>新增复杂筛选时复用 src/hooks/useRemoteList.ts,写操作参照 useTaskList.ts 的“请求成功后 reload”流程。
4. 注册模块
在 apps/demo/src/modules.ts 声明,并将变量加入文件末尾的 modules 数组:
ts
export const reportsModule = defineModule({
name: 'reports',
routes: [
{
path: '/reports',
name: 'Reports',
component: () => import('@/views/reports/index.vue'),
meta: {
title: 'reports.title',
group: 'Reports',
perms: ['report:read'],
keepAlive: true,
},
},
],
})模块名 reports 决定是否注册,权限 report:read 决定谁能进入。两者不能互相替代。
5. 接上导航
演示使用 nav: 'backend'。在 apps/demo/mock/nav.mock.ts 的模块名称列表中加入 reports,在适当菜单分组新增 /reports 叶子,设置 module: 'reports' 和 perms: ['report:read']。真实后端应在 NavSnapshot.modules 和菜单树中下发相同信息。
admin 的 * 权限可以直接验证;为普通角色授权时,还需把 report:read 加入 packages/modules/src/mock/index.ts 的可选权限列表,并通过角色管理分配。浏览器曾初始化过菜单种子时,新增种子不会覆盖已有编辑,使用演示重置后再验收。
验收与排障
在 src/views/page-nav/entries.ts 登记入口,并更新对应模块文档。执行 pnpm typecheck、pnpm test,再分别检查开发 Mock 和纯前端产物。
- 路由 404: 检查
modules数组及导航快照是否同时包含reports。 - 返回 403: 检查当前快照权限和共享 Mock 资源映射。
- 页面只显示词条键: 确认两份 locale 文件及布局运行时注册,见i18n。
- 数据为空却没有错误: 检查契约是
items还是list,不要套用其他业务的返回结构。