Core API
@lumal/core 提供后台项目的基础设施和 Lumal 原生组件。
应用启动
import { createLumalAdmin } from '@lumal/core'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { createPinia } from 'pinia'
import { createRouter, createWebHistory } from 'vue-router'
import '@lumal/core/theme-chalk/index.scss'
import '@lumal/core/style.css'
import '@lumal/icons-vue/style.css'
import App from './App.vue'
const router = createRouter({
history: createWebHistory(),
routes: [],
})
const framework = createLumalAdmin({
preset: 'admin-default',
rootComponent: App,
rootProps: {},
router,
pinia: createPinia(),
elementPlus: {
plugin: ElementPlus,
options: {
locale: zhCn,
},
},
dictionary: {
fetcher: async dictionary => ({
items: [],
}),
},
icons: {
localSvg: [],
},
components: ['LumalLayout', 'LumalRouterView'],
setup: ({ app }) => {
// 应用级扩展
},
})
await framework.mount('#app')createLumalAdmin 负责创建 Vue app、注册本地图标、安装字典上下文、接入调用方传入的 router/pinia/Element Plus、选择性注册全局组件,并在挂载前执行应用级 setup。
关键行为:
preset支持admin-default和minimal;两者都不内部创建 Pinia。elementPlus兼容旧 Plugin 写法和{ plugin, options }写法。components支持true、false、组件名数组和组件映射。mount()为异步方法,会等待setup()和router.isReady()。- 返回值暴露
app、router、pinia、permissionStore、use()和mount()。
安装顺序固定为:本地图标注册 -> 字典上下文 -> router -> pinia -> elementPlus -> components;挂载阶段再执行 setup -> router.isReady -> Vue mount。
组件
从 @lumal/core/components 导入:
LumalSchemaFormLumalSchemaTableLumalCrudTableLumalPageLumalPageLayoutLumalPaginationLumalInfoTableLumalIconLumalIconPickerLumalIconPickerDialog
Page 与 Page Layout
LumalPage支持fill、noPadding和contentClass,用于普通卡片页或填充式滚动页面。LumalPageLayout使用查询、工具栏、内容、分页四区结构;fill控制高度填充,contentScrollable控制内容区滚动,surface控制分区表面样式。
Schema Form
<script setup lang="ts">
import type { SchemaFormItem, SchemaFormMode, SchemaFormModel } from '@lumal/core/components'
import { LumalSchemaForm } from '@lumal/core/components'
import { shallowRef } from 'vue'
interface ProjectForm {
enabled: boolean
name: string
score: number
status: string
}
const model = shallowRef<SchemaFormModel<ProjectForm>>({})
const mode = shallowRef<SchemaFormMode>('edit')
const schemas: SchemaFormItem<ProjectForm>[] = [
{
field: 'name',
label: '名称',
component: 'input',
editDisabled: true,
prepend: '项目',
rules: [{ required: true, message: '请输入名称' }],
},
{
field: 'status',
label: '状态',
dictionary: 'status',
},
{
field: 'enabled',
label: '启用',
component: 'switch',
},
{
field: 'score',
label: '评分',
component: 'number',
componentProps: {
min: 0,
max: 100,
},
formatter: value => `${String(value ?? 0)} 分`,
suffix: '分',
},
]
</script>
<template>
<LumalSchemaForm
v-model="model"
:columns="2"
label-width="92px"
:mode="mode"
:schemas="schemas"
show-actions
>
<template #prefix-name>
<span>前缀</span>
</template>
<template #suffix-name>
<span>后缀</span>
</template>
</LumalSchemaForm>
</template>LumalSchemaForm 支持 create、edit、view 三种模式。view 模式使用文本语义展示格式化结果,不再渲染一组禁用输入框。
内置控件包括:input、textarea、select、number、switch、date、datetime、daterange、radio、checkbox、tree-select、upload、hidden。
字段配置推荐使用:
field:模型字段名。label:表单标签。component:内置控件类型。dictionary:从字典上下文加载 options。options:数组或Refoptions。rules:Element Plus 表单校验规则。span/columns/labelWidth:控制布局;未传labelWidth时默认使用auto,同一表单共享统一标签区宽度,保证等宽栅格中的输入区对齐。hidden/disabled/readonly:支持布尔值或(context) => boolean。editDisabled:仅编辑模式禁用。authority/readonlyAuthority:结合canAccess控制隐藏或只读权限。formatter:查看模式格式化。prepend/append/prefix/suffix:复合输入内容。componentProps:按component精确映射到对应 Element Plus props。componentProps、options、required、rules、hidden、disabled、readonly均可使用(context) => value动态解析;上下文包含当前字段、值、模型和setFieldValue。description/help:在控件下方显示持续可见的说明与帮助信息。onChange:字段变化回调,不需要从外部重复监听整个模型。
字段插槽命名规则:
field-${field}:完全接管该字段渲染。prefix-${field}:字段控件前置内容。suffix-${field}:字段控件后置内容。
表单级能力包括 loading、compact、gutter、showReset、submitOnEnter、scrollToFirstError、actionLayout 和 actionPosition。公开控制器提供 setFieldValue、clearValidate、scrollToField 与 getFieldComponent。
Schema Table
<script setup lang="ts">
import type { SchemaTableColumn, SchemaTablePaginationChangePayload } from '@lumal/core/components'
import { LumalSchemaTable } from '@lumal/core/components'
import { shallowRef } from 'vue'
const page = shallowRef(1)
const pageSize = shallowRef(10)
interface ProjectRow {
id: number
name: string
status: string
}
const columns: SchemaTableColumn<ProjectRow>[] = [
{
field: 'name',
label: '名称',
componentProps: {
minWidth: 160,
},
},
{ field: 'status', label: '状态', dictionary: 'status' },
]
const rows: ProjectRow[] = [
{ id: 1, name: 'Lumal', status: 'enabled' },
]
function handleSelectionChange(rows: ProjectRow[], rowKeys: Array<string | number>) {
console.log(rows, rowKeys)
}
function handlePageChange(payload: SchemaTablePaginationChangePayload) {
page.value = payload.page
pageSize.value = payload.pageSize
}
</script>
<template>
<LumalSchemaTable
v-model:page="page"
v-model:page-size="pageSize"
:columns="columns"
:rows="rows"
row-key="id"
selection
show-index
show-column-settings
pagination
:total="36"
:table-props="{ border: true, stripe: true }"
@selection-change="handleSelectionChange"
@page-change="handlePageChange"
>
<template #table-name="{ value }">
<strong>{{ value }}</strong>
</template>
<template #actions="{ row }">
<button type="button">
{{ row.name }}
</button>
</template>
</LumalSchemaTable>
</template>LumalSchemaTable 支持:
selection:渲染选择列,并通过selection-change(rows, rowKeys)同时返回选中行和主键。showIndex:渲染序号列。showColumnSettings:显示可键盘操作的列设置。columnSettings:配置列设置显示、拖动排序和storageKey持久化;至少保留一个可配置列,固定列不会被无效隐藏。defaultExpandAll/treeProps:树表配置,同时兼容旧tableProps写法。autoResize:使用ResizeObserver自动执行表格布局。scaleColumnWidth:关闭时忽略配置宽度并使用流式列宽;不依赖全局 viewport 转换。columnResizable:控制 Element Plus 列宽拖动。loading:表格加载状态。pagination/total/pageSizes:内部分页,配合v-model:page和v-model:page-size。hidden/authority:控制列隐藏和权限;函数形式按行决定单元格显示。componentProps:透传给底层ElTableColumn。tableProps:透传给底层ElTable。rowClassName/cellClassName/headerCellClassName:行、单元格和表头 class。actions插槽:追加操作列。table-${field}插槽:接管指定字段;移动端操作列自动切换为“更多”入口。- 字典项包含
color时渲染“颜色点 + 文本”标签;颜色不作为唯一状态信息。 - 转发
sortChange、filterChange、rowClick、currentChange、expandChange、selectionChange和pageChange。 error/retry提供独立错误恢复状态;公开方法可获取可见列、列顺序和导出数据,并可恢复默认列设置。
复杂表格或自定义渲染可以复用公开 helper:
import { resolveSchemaTableCellDisplay } from '@lumal/core/components'该 helper 统一处理 formatter、空值、数组和字典回显;内部渲染组件不作为公共 API 导出。
CRUD Table
<script setup lang="ts">
import type { CrudDataSource, CrudTableColumn } from '@lumal/core/components'
import { LumalCrudTable } from '@lumal/core/components'
const querySchemas = [
{ component: 'input', field: 'keyword', label: '关键词' },
{ dictionary: 'status', field: 'status', label: '状态' },
]
const columns: CrudTableColumn[] = [
{ field: 'name', label: '名称', required: true },
{ field: 'status', label: '状态', dictionary: 'status' },
{
field: 'remark',
label: '备注',
component: 'textarea',
showInTable: false,
},
]
const dataSource: CrudDataSource = {
create: async (model) => {
await saveProject(model)
},
fetch: async () => {
return {
items: [
{ id: 1, name: 'Lumal', status: 'enabled' },
],
total: 1,
}
},
}
</script>
<template>
<LumalCrudTable
title="项目列表"
:data-source="dataSource"
:query="{
collapsible: true,
schemas: querySchemas,
}"
:table="{
columns,
rowKey: 'id',
selection: true,
showColumnSettings: true,
}"
:toolbar="{ export: true }"
/>
</template>LumalCrudTable 支持两种模式:
- 受控模式:传入
rows/total,由应用侧管理数据。 - 数据源模式:传入
dataSource,组件内部调用fetch、维护rows/total/loading/error。
标准数据源响应固定为:
interface CrudFetchResult<Row> {
items: Row[]
total: number
}非标准响应不自动猜测字段,必须显式提供 parseResponse:
const dataSource: CrudDataSource = {
fetch: params => request('/api/projects', { params }),
parseResponse: response => ({
items: response.records,
total: response.count,
}),
}dataSource 可选动作包括 create、update、remove、removeMany。未显式配置 formSchemas 时,CRUD 会从 columns 自动派生新增、查看、编辑表单:列上的 dictionary / dictType 会自动生成表单 options,同时继续负责表格值翻译和颜色标签。showInForm: false 与 showInTable: false 分别控制字段是否进入派生表单或表格;显式 formSchemas 会整体覆盖自动派生结果。
Form、Table 和 CRUD 的字典规则保持一致:只需提供 dictionary(推荐)或 dictType,组件会从字典上下文加载选项;存在字典键时字典结果优先,手工 options 仅在未指定字典键时使用。
配置 selection 后会提供批量删除入口。删除确认通过 actions.confirmRemove 集中配置,可传函数、文案配置或 false(跳过确认)。
编辑器通过 editor 配置,type 支持 dialog 和 drawer,默认使用 dialog;同时支持宽度、表单列数、标签宽度、Loading、禁用态和关闭确认。工具栏支持标题、新增、批量删除、刷新、字典化 CSV 导出和全屏。
查询区未传 query.columns 时会按实际容器宽度自动使用 1~4 列:小于 640px 为 1 列、640px 起为 2 列、960px 起为 3 列、1280px 起为 4 列。显式传入 query.columns 时以配置值为准,适合需要固定密度的业务页面。
LumalCrudTable 只负责组合查询表单、表格、分页和增删改查,不包含 LumalPage 页面壳。需要页面 header、description 或 loading 遮罩时,由消费方自行包裹 LumalPage。
传入 title 时会在业务操作组左侧显示表格标题,不传则不渲染标题;也可通过 table-title 插槽自定义标题。新增、批量删除、导出、toolbar-actions 与 actions 插槽属于业务操作组,可通过 toolbar.actionsPosition: 'left' | 'right' 设置位置,默认在右侧;筛选区显隐、刷新、全屏、toolbar-tools 插槽和列设置始终位于右侧工具组。筛选、刷新、全屏与列设置使用图标按钮,并保留 aria-label 和 title 供键盘及辅助技术识别。查询配置可用 defaultVisible 设置筛选区初始显隐,工具栏可用 queryToggle: false 关闭显隐按钮。
CRUD 会转发 query-{field}、table-{field}、form-{field}、table-title、toolbar-actions、toolbar-tools、row-actions 和 footer 插槽。查询配置可启用 submitOnChange 与 submitDebounce。
推荐使用六个配置对象组织复杂页面:
query:schemas、列数、折叠、label width 和查询文案。table:columns、rowKey、selection、列设置、自动 resize 和操作列宽度。toolbar:业务操作位置、新增、批量删除、刷新、导出及对应文案。actions:查看、编辑、删除的行级显示函数、文案和删除确认策略。dialog:标题、宽度、提交文案和脏表单关闭确认。pagination:开关和 page sizes。
旧的 querySchemas、columns、rowKey、selection、pageSizes 等扁平 props 在首个稳定版本前继续兼容;新配置与旧 props 同时存在时,新配置优先。
组件会触发 export 和 operation-error 事件。export 返回当前查询模型和已选择行;列表或写操作失败时保留可恢复状态,列表错误区提供重试入口,弹窗保存失败不会自动关闭。
公开方法包括:
reload、isLoadingopenCreate、openView、openEditremoveRow、removeSelectedRowsgetSelectedRows、getSelectedRowKeys、clearSelectiongetQueryForm、getDialogForm、getTable、getCrudElementtoggleQuery
Dictionary
从 @lumal/core/dictionary 导入:
installDictionaryuseDictionaryuseDictionaryMapgetDictionaryLabelnormalizeDictionaryOptionscreateDictionaryFetcherWithFallback
推荐字段名是 dictionary,用于 Schema Form 的下拉 options 生成,以及 Schema Table / CRUD Table 的值回显:
const schemas = [
{ field: 'status', label: '状态', dictionary: 'status' },
]
const columns = [
{ field: 'status', label: '状态', dictionary: 'status' },
]标准响应结构固定为:
interface DictionaryResponse {
items: DictionaryOption[]
}默认解析器只读取 { items: [...] },不会自动兼容 data、result、list 等非标准响应。非标准接口需要显式传入 fieldNames 或 parseResponse:
createLumalAdmin({
rootComponent: App,
dictionary: {
fieldNames: {
items: 'records',
label: 'name',
value: 'code',
},
fetcher: dictionary => fetch(`/api/dictionaries/${dictionary}`).then(res => res.json()),
},
})Layout
从 @lumal/core/layout 导入:
LumalLayoutLumalHeaderLumalSidebarLumalTabsLumalContentLumalRouterViewLumalBreadcrumbLumalGlobalSearch
layout 只负责后台壳层结构和交互,不请求业务数据。LumalLayout 接收完整 menus 与 preferences,并根据 preferences.app.layout 自动决定菜单显示位置:
sidebar-nav:完整菜单树显示在侧栏。top-nav:完整菜单树显示在顶栏。mixed-nav:顶栏显示一级菜单,侧栏自动显示当前一级菜单的子菜单。
应用层不需要创建或传入 topMenus,也不需要自行计算顶栏激活项和侧栏菜单。点击 mixed-nav 一级菜单时,Layout 会自动解析首个可导航子项并通过 menu-select 输出最终路径。
启用 route-driven 后,Layout 还会从当前 Vue Router 路由创建访问标签,并支持:
route-tab-filter/route-tab-resolver:过滤并解析路由标签。fixed-tabs:注入不可关闭的固定标签。tab-max-count:限制标签数量并优先保留固定标签。tab-fallback-path:关闭当前或全部可关闭标签后的回退地址。getTabs()/resetTabs():读取和重置路由驱动标签。
路由驱动模式仍会触发现有 tab-change、tab-remove、tab-refresh 和批量关闭事件,便于应用补充埋点或刷新视图;路由切换和标签集合由 Layout 维护。
Router
从 @lumal/core/router 导入:
normalizeMenuNodescreateRouteRecordscreateRouteRegistrycreateMenuRouteRuntimecreateSidebarMenusfindFirstAccessibleMenu
这些函数用于把后端菜单节点转换为路由记录和侧边栏菜单。
createSidebarMenus 支持通过回调过滤权限和角色,适合与 createPermissionStore 组合:
import { createPermissionStore } from '@lumal/core/permission'
import { createSidebarMenus, normalizeMenuNodes } from '@lumal/core/router'
const permissionStore = createPermissionStore({
permissions: ['dashboard:view'],
roles: ['admin'],
})
const menus = createSidebarMenus(normalizeMenuNodes(menuNodes), {
hasPermission: permissions => permissionStore.hasPermission(permissions, 'every'),
hasRole: roles => permissionStore.hasRole(roles, 'every'),
})菜单节点可通过 meta.externalTarget 指定外链打开策略:_blank 表示新窗口,_self 表示由应用在当前后台壳中承载。createRouteRegistry 负责记录动态注册的具名路由,并提供 reset() 在登出或会话失效时统一移除:
const registry = createRouteRegistry(router)
registry.register(createRouteRecords(normalizeMenuNodes(menuNodes)))
registry.reset()静态菜单和后端菜单并存时,使用 createMenuRouteRuntime 统一完成校验、排序、组件解析和路由注册:
const menuRuntime = createMenuRouteRuntime({
router,
staticMenus,
loadRemoteMenus: () => request.get('/menus'),
componentResolver: component => views[component],
hasPermission: permissions => permissionStore.hasPermission(permissions, 'every'),
hasRole: roles => permissionStore.hasRole(roles, 'every'),
})
await menuRuntime.loadRemote()
menuRuntime.resetRemote() // 登出时仅移除远程路由
menuRuntime.dispose() // 应用释放时移除该运行时注册的全部路由静态菜单可传组件、懒加载器或字符串;远程菜单只接受由 resolver 白名单解析的组件字符串。重复的完整路径或路由名称、空路径、叶子缺组件都会在注册前报错,不会留下部分路由。reload() 使用单飞请求并在失败时保留上一份可用远程菜单。运行时传入的 Router 必须提供 getRoutes(),用于在注册前检查与应用既有路由的 path 冲突;旧 createRouteRegistry 仍兼容不含该方法的最小 Router 适配器。
Permission
从 @lumal/core/permission 导入:
createPermissionStorehasPermissionhasRolecreatePermissionDirectivesetupPermissionGuardLumalAccessControlprovidePermissionStoreusePermissionStore
权限能力只处理通用权限判断,不绑定具体业务接口。LumalAccessControl 同时声明权限和角色时使用 AND,权限与角色各自通过 mode / role-mode 选择 some 或 every;拒绝时可渲染 fallback slot。setupPermissionGuard 对嵌套路由的每级匹配记录分别执行上述模式,并在父子级之间使用 AND,父级约束不能被子级覆盖。向 createLumalAdmin 传入 permissionStore 后会自动注入,组合式 API 和组件可直接消费。
Auth
从 @lumal/core/auth 导入:
createAuthSessionparseAuthSession
标准会话固定为 { accessToken, refreshToken?, expiresAt? }。字段异常由应用 adapter 映射,会话实例负责持久化与一次清除访问、刷新凭据:
const session = createAuthSession({ storage: localStorage })
const data = parseAuthSession(rawResponse, {
fieldNames: {
accessToken: 'access_token',
refreshToken: 'refresh_token',
expiresAt: 'expire_time',
},
})
session.setSession(data)Request
从 @lumal/core/request 导入:
createRequestClientcreateStandardResponseParserparsePageResultRequestError
import { createRequestClient } from '@lumal/core/request'
const request = createRequestClient({
baseURL: '/api',
getToken: () => session.getToken() || undefined,
authRefresh: {
refresh: async () => {
session.setSession(await refreshSession(session.getRefreshToken()))
},
},
onResponse: createStandardResponseParser(),
onSessionExpired: () => session.handleSessionExpired(),
})HTTP 401 或 parser 产生的 kind: 'session' 会进入认证刷新。并发请求共享同一个刷新 Promise;GET 默认允许最多重放一次,POST/PUT/PATCH/DELETE 必须逐请求显式设置 retryOnAuthRefresh: true。RequestError.kind 可用于统一区分 network、http、business、session、duplicate 与 cancelled,业务字段和状态码仍由应用 adapter 决定。
Theme
从 @lumal/core/theme 导入:
createThemeStorecreatePreferencesStoreapplyThemeToElementresolveThemeTokensdefaultThemeState
主题运行时支持明暗模式、主题色和紧凑布局,不包含多语言偏好。
偏好持久化示例:
import { createPreferencesStore } from '@lumal/core/theme'
const preferences = createPreferencesStore({
storage: localStorage,
storageKey: 'admin:preferences',
defaults: {
app: { layout: 'mixed-nav' },
},
})
preferences.patch({
app: { dynamicTitle: true },
breadcrumb: { enable: true, showHome: true },
header: { globalSearch: true },
shortcutKeys: { globalSearch: true },
theme: { fontSize: 16, mode: 'dark' },
})
preferences.reset()
preferences.exportCurrent()
preferences.dispose()Store 会清理损坏缓存、补齐新增默认字段,并只在主题模式为 system 时监听系统主题变化。全局字号限制为 12-20px,会同步 Lumal 和 Element Plus 的字号变量。
可选生态包
相关独立包:
@lumal/icons:框架无关的图标 registry、单色 SVG 校验/合成、改色、渐变和 Data URI 缓存。@lumal/icons-vue:LumalIcon、选择器和 Vue 响应式注册表适配,详见docs/icons.md。@lumal/charts:LumalChart与LumalChartPanel;面板支持查询、表格替代视图、导出、错误恢复和无障碍摘要。@lumal/vben-compat:Vben 高频表单/Grid 写法到 Lumal Schema/CRUD 的迁移适配,详见docs/migration-from-vben.md。@lumal/vite:组件 resolver、工作区 source/dist alias 和可选 Devtools 接线。
import {
createLumalAliases,
createLumalComponentResolver,
resolveLumalDevtoolsPlugin,
} from '@lumal/vite'
const aliases = createLumalAliases({
workspaceRoot: process.cwd(),
target: 'source',
})
const resolver = createLumalComponentResolver({ importStyle: true })
const devtools = resolveLumalDevtoolsPlugin({
enabled: import.meta.env.DEV,
factory: () => installedDevtoolsPlugin(),
})@lumal/vite 不安装自动导入或 Devtools 实现,也不提供 1920px 到 viewport 的全局转换。