@ruovea/report
v1.0.9
Published
报表模块(图表配置 / 仪表盘 / 数据源 / 报表定义)
Readme
@ruovea/report
报表模块(图表配置 / 仪表盘 / 数据源 / 报表定义),以 pnpm 源码包 形式提供给各宿主项目复用。
- 不打 dist、不发 npm——exports 直指
./src/*,由宿主 Vite 编译 - 依赖注入解耦——request / auth / i18n / 菜单 / 表格组件全部由宿主装配时注入
- 零侵入——包内不含
/@/等宿主别名引用,可独立运行,不绑定特定宿主
一、目录结构
src/
├─ index.ts # 入口:导出 ReportPlugin + API + 类型
├─ plugin.ts # Vue install(校验必填项 / provide / mergeLocaleMessage)
├─ context.ts # InjectionKey + useReportContext()
├─ types.ts # ReportPluginOptions
├─ routes.ts # 8 条静态路由(可选使用)
├─ api/
│ ├─ index.ts # 54 个 API 函数
│ ├─ http.ts # request 注入点
│ └─ endpoints.ts # URL 前缀(可覆盖)
├─ i18n/locales/ # 6 语言(en / fr-fr / ja-jp / vi-vn / zh-cn / zh-tw)
├─ components/scEcharts/ # 自包含 echarts 封装
└─ views/ # 18 个 .vue
├─ chartConfig/ # 图表配置(index / chartView / editChartConfig / ChartPreview)
├─ dashboard/ # 仪表盘(index / edit / admin / ChartCard / ReportChartCard / editDashboard)
├─ dataSource/ # 数据源(index / editDataSource)
└─ definition/ # 报表定义(index / queryView / editDefinition / ChildTables / editColumnDisplay / editSearchConfig)二、宿主接入
2.1 安装
宿主 package.json 用 link: 协议指向本仓库:
{
"dependencies": {
"@ruovea/report": "link:../../Module/report-module/packages/report"
}
}cd D:/app/report-module && pnpm install # 包内依赖(element-plus 等裸导入需要)
cd <宿主> && pnpm install # 建立 link2.2 装配插件
在宿主 main.ts 中、app.use(pinia) 之后调用:
// plugins/report.ts
import { computed, defineAsyncComponent, type App } from 'vue';
import { storeToRefs } from 'pinia';
import { ReportPlugin } from '@ruovea/report';
import request from '/@/utils/request';
import { auth } from '/@/utils/authFunction';
import { i18n } from '/@/i18n/index';
import { useThemeConfig } from '/@/stores/themeConfig';
import { apiSysMenuListGet, apiSysMenuAddMenusPost } from '/@/api/user/menu';
export function setupReport(app: App) {
const { themeConfig } = storeToRefs(useThemeConfig());
app.use(ReportPlugin, {
request: request as any,
auth,
i18n: i18n as any,
isDark: computed(() => themeConfig.value.isIsDark),
menuApi: {
list: () => apiSysMenuListGet(),
addMenus: (data: any) => apiSysMenuAddMenusPost(data),
},
components: {
Table: defineAsyncComponent(() => import('/@/components/table/index.vue')),
TableSearch: defineAsyncComponent(() => import('/@/components/table/search.vue')),
},
});
}// main.ts
import { setupReport } from '/@/plugins/report';
// ... app.use(pinia) 之后
setupReport(app);三、ReportPluginOptions 说明
| 字段 | 必填 | 说明 |
|---|---|---|
| request | ✅ | 宿主 axios 实例(已配 baseURL / token / 拦截器) |
| components.Table | ✅ | 宿主表格组件(见下方契约) |
| components.TableSearch | ➖ | 宿主搜索表单组件,缺省时列表页无搜索区 |
| auth | ➖ | (code: string) => boolean,按钮权限校验,缺省 () => true |
| i18n | ➖ | 宿主 vue-i18n 实例(用于 mergeLocaleMessage)——有结构前提,见下方「i18n 注入约定」 |
| isDark | ➖ | Ref<boolean>,暗色主题(echarts 配色联动) |
| setRouteTitle | ➖ | (routeName, title) => void,queryView 动态改菜单标题用 |
| menuApi | ➖ | { list(), addMenus(data) },dashboard admin 页「添加菜单」能力;缺省时该按钮隐藏 |
| dictApi | ➖ | 字典类型查询,缺省走 GET /SysDictType/List |
| endpoints | ➖ | 覆盖 API URL 前缀:{ chartConfig, dataSource, definition, dashboard },缺省 /RptChartConfig 等 |
| registerI18n | ➖ | 是否自动 merge i18n,默认 true |
i18n 注入约定(重要)
包内所有视图使用的 key 形如 message.report.xxx、message.router.xxx,即带 message. 前缀。同时包的 locale 文件也自带了 router 命名空间(report、reportDataSource、reportDefinition、reportChartConfig、reportDashboard、reportDashboardAdmin、reportDashboardEdit、reportDashboardView、reportQueryByCodeView、reportChartView),插件 merge 时双层注册,菜单翻译无需宿主额外配置。
- 宿主项目的 locale 结构是「顶层平铺 +
message深拷贝 +el」(见Web/src/i18n/index.ts的deepMerge),此时mergeLocaleMessage自动注册正常工作。 - 新宿主若 locale 无
message这一层,自动注册后所有t('message.report.xxx')会原样回显 key(界面丢失多语言)。此时有两种做法:设
registerI18n: false,在createI18n时自行按宿主结构注入:import { reportLocales } from '@ruovea/report/i18n'; import zhCn from 'element-plus/es/locale/lang/zh-cn'; const messages: Record<string, any> = {}; const elLocales: Record<string, any> = { 'zh-cn': zhCn }; for (const [locale, msg] of Object.entries(reportLocales)) { messages[locale] = { ...msg, message: msg, el: elLocales[locale]?.el ?? {} }; } const i18n = createI18n({ legacy: false, locale: 'zh-cn', messages }); // app.use(ReportPlugin, { ..., registerI18n: false })保留
registerI18n: true,同时把视图里的$t('message.report.xxx')视为 key 路径前缀,由宿主在 merge 后补一层message别名。
包的 exports 已暴露 ./i18n 子路径(reportLocales),方便宿主自行组装。
四、Table / TableSearch 组件契约
包内列表页通过 useReportContext().components.Table 引用宿主组件,宿主必须满足以下隐式契约:
Table
Props(v-bind 展开传入):
| Prop | 类型 | 说明 |
|---|---|---|
| columns | ColumnItem[] | 列配置 { prop, label, width, minWidth, align, type, isCheck, hideCheck, showOverflowTooltip } |
| config | object | { isStripe, isBorder, isSerialNo, isSelection, pageSize, hideExport, hideRefresh, hidePrint, hideSet } |
| param | object | 查询参数(分页 + 搜索条件合并) |
| getData | (param) => Promise<{rows, totalRows}> | 数据加载函数 |
Slots: command(顶部操作区)、各列具名 slot(#propName="scope")、#action="scope"(操作列)
Events: sortHeader(columns)、selectionChange(rows)
Expose: handleList()(刷新)、pageReset()(重置到第 1 页)
TableSearch
Props: search: SearchItem[] — { label, prop, placeholder, required, type: 'input'|'select'|'datetime', options? }
Events: search(formData)
五、权限编码清单
宿主 auth(code) 需识别以下编码(对应后端 SysMenu 按钮权限):
| 编码 | 位置 | 说明 |
|---|---|---|
| rptDataSource:add | dataSource | 新增数据源 |
| rptDataSource:edit | dataSource | 编辑 |
| rptDataSource:delete | dataSource | 删除 |
| rptDefinition:add | definition | 新增报表定义 |
| rptDefinition:edit | definition | 编辑 |
| rptDefinition:delete | definition | 删除 |
| rptChartConfig:add | chartConfig | 新增图表 |
| rptChartConfig:edit | chartConfig | 编辑 |
| rptChartConfig:delete | chartConfig | 删除 |
| rptChartConfig:batchDelete | chartConfig | 批量删除 + 控制多选列显示 |
| rptDashboard:add | dashboard/admin | 新增仪表盘 |
| rptDashboard:edit | dashboard/admin、dashboard/edit | 编辑(含设计页保存按钮) |
| rptDashboard:delete | dashboard/admin | 删除 |
未注入 auth 时所有按钮可见(缺省 () => true)。
六、request 拦截器要求
包内 API 假定 request 实例已具备宿主统一拦截器:
- baseURL 指向后端 API 根(如
https://host/api) - 请求头 自动携带
Authorization: Bearer <token>、appKey、Accept-Language - 响应结构 返回
RestfulResult<T>或RestfulResult<PageResult<T>>:{ "code": 200, "message": "", "data": { "rows": [], "totalRows": 0 } } - 非 200 处理 拦截器统一
ElMessage报错并 reject(包内不做二次错误提示) - GET body→query 转换(删除接口走
params)
后端接口由 RuoVea.OmiApi.Reports 模块提供(动态 WebApi 自动暴露),URL 前缀默认 /RptChartConfig / /RptDataSource / /RptDefinition / /RptDashboard,可用 endpoints 覆盖。
七、路由
方式 A:宿主自行注册(推荐,与现有菜单体系一致)
宿主在自己的路由表中注册 8 条路由,component 指向包内视图:
{
path: '/report/chartConfig',
name: 'reportChartConfig',
component: () => import('@ruovea/report/views/chartConfig/index.vue'),
meta: { title: 'message.router.reportChartConfig', isHide: false, isKeepAlive: true, icon: 'ele-PieChart' },
}注意:
import路径必须带.vue扩展名,否则 Rollup 无法解析。
方式 B:展开包内路由
import { reportRoutes } from '@ruovea/report/routes';
const routes = [...reportRoutes];8 条路由一览
| path | name | keepAlive | 说明 |
|---|---|---|---|
| /report/chartConfig | reportChartConfig | ✅ | 图表配置列表 |
| /report/dataSource | reportDataSource | ✅ | 数据源列表 |
| /report/definition | reportDefinition | ✅ | 报表定义列表 |
| /report/dashboard | reportDashboard | ❌ | 仪表盘查看(带 ?code=xxx) |
| /report/dashboard/edit | reportDashboardEdit | ❌ | 仪表盘设计 |
| /report/dashboard/admin | reportDashboardAdmin | ✅ | 仪表盘管理 |
| /report/chart | reportChartView | ✅ | 单图表查看 |
| /report/queryByCode | reportQueryByCodeView | ✅ | 按编码查询报表 |
八、KeepAlive 注意事项
包内 SFC 的 name 需保留用于 keep-alive 缓存:
reportChartConfig/reportDataSource/reportDefinition/reportDashboardAdmin/reportChartView/reportQueryByCodeView→ 需缓存reportDashboard/reportDashboardEdit→ 不缓存(实时数据 / 设计器)
宿主 keepAliveNames store 应包含上述需缓存的 name。
九、常见问题
Q: 报 Rollup failed to resolve import "element-plus"?
A: 未在 report-module 目录执行 pnpm install。link 模式下包内裸导入(element-plus、vue 等)从包自己的 node_modules 解析。
Q: 报 [@ruovea/report] options.request 必填?
A: 宿主未调用 setupReport(app),或调用顺序在 app.use(pinia) 之前。
Q: 列表页空白但无报错?
A: 检查 components.Table 是否注入;Table 组件 getData 返回结构是否为 { rows, totalRows }。
Q: i18n 显示 key 原文?
A: 未注入 i18n 选项,或宿主 locale 不在包支持的 6 种语言内(可设 registerI18n: false 自行 merge)。
Q: 列表页一打开就报『查询失败』、Network 面板却无任何请求?
A: 宿主未将 @ruovea/report 加入 optimizeDeps.exclude。本包是 pnpm 源码包,exports 把 . 与 ./api 都指向 ./src/*,若不排除预构建,Vite 会把主入口预打包成 node_modules/.vite/deps/@ruovea_report.js,而 ./api 子路径仍走源码 src/api/index.ts → src/api/http.ts。两端各持一份模块级 _request:
main.ts中setupReport(app)→ReportPlugin.install→__setRequest(request)写入的是预构建产物里的_request;- 视图层
import { apiDataSourcePages } from '@ruovea/report/api'拿到的是源码 http.ts 的_request,永远是undefined; - 视图调 API →
http()→ throw → catch →ElMessage.error('查询失败'),全程不进 axios,所以无网络请求。
修复方法:
// vite.config.ts
optimizeDeps: {
exclude: ['vue-demi', '@ruovea/report'],
},rm -rf Web/node_modules/.vite/deps # 清除预构建缓存
pnpm dev十、License
MIT
