Skip to content
uAdmin

首个业务模块 ​

本页在 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,不要套用其他业务的返回结构。

Vue 3 · TypeScript · Element Plus