@qynpm/admin-patterns
v1.0.5
Published
起印后台通用页面模式组件包。
Readme
@qynpm/admin-patterns
起印后台通用页面模式组件包。
当前公开 QyPage、QyStatusTag、QyDictTag、QyDetailPanel、QySearchPanel、QySearchField、QyPagination、QyListActionBar、QyDrawerForm 和 QyFormPanel。
主题前置条件
本包消费 Qy CSS variables;消费项目应在应用入口导入 @qynpm/admin-theme。本包不复制 --qy-* tokens,也不提供页面级 Element Plus 全局覆盖。
QySearchPanel 与 QySearchField 必须成组使用:前者通过 provide 提供折叠和标签宽度 context,后者通过 inject 消费同一协议。默认查询按钮来自 @qynpm/ui/qy-button;@qynpm/ui 在本包中同时作为 peer dependency 和源码构建 dev dependency,构建产物不捆绑 UI 实现。
QySearchPanel / QySearchField
QySearchPanelprops:columns?: 2 | 3 | 4、labelWidth?: string(默认auto,显式传入时可固定标签宽度)、受控/非受控collapsed、defaultCollapsed、collapsible、disabled、loading、queryText、resetTextQySearchPanelemits:query、reset、update:collapsed(boolean)QySearchPanelactions slot:collapsed、disabled、loading、queryDisabled、query、reset、toggleCollapsedQySearchFieldprops:label、span?: 1 | 2 | 'full'、advanced、labelWidth、width?: QySearchFieldWidth
QySearchFieldWidth 的完整类型为 'sm' | 'md' | 'lg' | 'xl' | 'flex',默认 md。宽度只控制控件区域的紧凑目标宽度:sm=112px、md=144px、lg=176px、xl=232px;固定宽度控件会在受限容器中继续收缩,flex 则在 160px 到 360px 之间占用剩余空间。span 继续表达跨行或全宽布局,不替代控件宽度。
组件只负责查询区布局、折叠和事件,不持有查询 model,不做 schema、自动请求或参数清洗。桌面端字段按照各自标签和控件的实际宽度连续排列并自然换行,不再平均分配整行宽度;操作区参与同一布局流并紧跟最后一个可见筛选项。columns 保留为兼容布局提示,继续影响两列场景下 span=2 的全宽语义,但不强制桌面字段等分。响应式依据组件容器宽度而不是只依据 viewport:容器小于 580px 时切换为单列,侧栏、抽屉或嵌套布局改变可用宽度时也会同步重排。标签、控件文字和默认操作按钮统一使用 12px 紧凑字号,默认控件高度为 32px;最窄容器中标签上置、控件提升到 40px,操作区占满一行并左对齐换行。
查询 loading 时保留主按钮底色和可读文案。Enter 查询会排除 textarea、IME composing 和 actions 区域;高级字段在收起时保持挂载但隐藏。QySearchField 会把字段标签关联到 slot 内未显式命名的 input、textarea、select、combobox 和 textbox;消费方已有的 aria-label、aria-labelledby 或原生 label 关联保持权威,不会被覆盖。
状态语义固定为:disabled 时查询、重置和折叠均不可操作;loading 时查询与重置不可重复触发,但折叠仍可操作。默认按钮和 actions slot 暴露的 query、reset、toggleCollapsed 共用同一组守卫。collapsed 传入时为受控模式,只派发 update:collapsed;未传入时由 defaultCollapsed 初始化内部状态。collapsible=false 始终展开且不渲染折叠入口。
QyPage
- props:无
- slots:默认 slot 页面正文
- DOM:
section.qy-page > div.qy-page__body
QyPage 是后台 Shell 内的页面语义与正文节奏门面。根节点负责原生 attrs 透传、宽度收缩和占用父级提供的可用空间;qy-page__body 是唯一的页面纵向间距来源,并允许 flex 子内容在受限高度中正确收缩。默认间距为 0,使查询、操作栏、表格和分页连续衔接;普通内容页需要分段时通过页面专属的 --qy-page-body-gap 统一设置,不复用面板内部的 --qy-panel-gap。普通内容页、列表页和详情编辑组合页共用同一契约。
页面标题、说明、面包屑、Tabs 和唯一 <main> 由后台 Shell 负责;列表操作由 QyListActionBar 负责。组件不负责 workspace 背景、边框、圆角、外层 padding、页面类型、路由、权限或请求。当前真实消费者没有独立的 fill 模式需求,因此不公开 fill / full-height prop;后续只有在自然流与内部滚动页面确实需要不同语义时才评估向后兼容的布尔能力。
<QyPage aria-label="菜单管理">
<!-- 页面正文 -->
</QyPage>QyPagination
QyPagination 公开 total、page、pageSize、pageSizeOptions、hideOnSinglePage、disabled、loading,以及 page-change(page)、page-size-change(pageSize) 两个事件。它是受控门面:page、pageSize 始终以外部 props 为权威,用户操作只发出意图事件;外部 props 更新不发事件,disabled 或 loading 时不发事件。请求、URL query、回第一页和越界重查仍由业务页面负责。Element Plus 只位于包内 ElementPaginationRenderer,并作为 peer/dev dependency 提供。
分页栏直接衔接表格:左侧只渲染一份“共 N 条”与“第 X / Y 页”,右侧渲染 pageSize、上一页、页码、下一页,不使用独立卡片。total <= 0 时不渲染;hideOnSinglePage 默认 true,总页数不超过 1 时整栏隐藏;特殊消费者可传 false 保留单页分页。pageSize 选项会过滤非法值、去重、排序并补入当前 pageSize;只有一个有效选项时不显示 pageSize 下拉。移动端允许分页内容换行,不依赖全局横向裁剪。
import {
QyPagination,
type QyPaginationEmits,
type QyPaginationProps
} from '@qynpm/admin-patterns'QyStatusTag
- props:
label: string、tone?: 'success' | 'warning' | 'danger' | 'info' | 'neutral' - 默认
tone:neutral - slots:无
- emits:无
QyStatusTag 只负责按页面传入的 label 和视觉 tone 展示状态标签,不内置启用/停用等业务枚举,也不负责接口状态解析或状态切换。五种 tone 只使用 admin-theme 语义 token;组件不依赖 ElTag。单根节点自然透传原生 class、title、aria-* 和 data-* attrs。label='' 时保留空标签节点,但不显示背景和占位文案。
import { QyStatusTag, type QyStatusTagProps } from '@qynpm/admin-patterns'QyDictTag
- props:
value?: string | number | boolean | readonly (string | number | boolean)[]、options?: readonly QyDictTagOption[]、unknownLabel?: string、hideUnknown?: boolean - option:
{ value, label, tone?, disabled? };disabled只保留数据,不改变只读展示
QyDictTag 按传入顺序把单值或数组值字符串化匹配到 options,并为每个唯一值组合一个 QyStatusTag。未知值默认显示原值并使用 neutral,可用 unknownLabel 统一文案或用 hideUnknown 隐藏;null、undefined、空字符串不会渲染标签。
<QyDictTag
:value="['pending', 2, 'missing']"
:options="[
{ value: 'pending', label: '待处理', tone: 'warning' },
{ value: 2, label: '已完成', tone: 'success' }
]"
unknown-label="未知"
/>import {
QyDictTag,
type QyDictTagOption,
type QyDictTagProps
} from '@qynpm/admin-patterns'QyListActionBar
QyListActionBar 是标准后台列表页操作栏。它从 @qynpm/admin-framework/actions 消费统一动作协议,负责主操作、次操作、权限/隐藏过滤、稳定排序和动作状态渲染;请求、确认弹窗和批量业务逻辑仍由页面动作负责。
- props:
actions、secondaryActions、permissions、context - emits:
action(action, context) - slots:
left、right,slot scope 提供已经解析的actions - DOM 标记:
data-testid="qy-action-bar"、data-action-key、data-action-danger
动作统一按“权限过滤 → hidden 过滤 → order 稳定排序 → danger/disabled/loading 状态解析”执行;context 的响应式字段变化会立即触发重新解析。left 接收解析后的主操作,right 接收解析后的次操作,slot 与默认按钮使用同一份结果。主次操作均为空且无覆盖 slot 时自动隐藏;单侧 slot 不生成另一侧空区域。
点击可用动作时,组件先同步调用一次 onClick(currentContext),随后同步 emit 一次 action(action, currentContext);两者同时使用点击当下的 context。同步抛错会终止后续 emit。异步 onClick 的 Promise 会返回 Vue 事件处理链,使未捕获的 reject 进入 Vue errorHandler,组件不吞掉业务错误,也不新增重复的 error 事件;可预期业务错误仍应由 action 自己处理。disabled/loading 动作既不调用 onClick 也不 emit。
桌面单行时保持 40px 最小高度,动作较多时整栏自然增高并换行;640px 以下按左、右顺序改为单列,按钮至少 40px 高且长文案可换行。工具栏透明,并以底部分隔线直接衔接表格;危险按钮颜色只来自主题 token。列设置等列表辅助能力属于 secondaryActions 或 right slot,不应作为表格数据列。
import {
QyListActionBar,
type QyListActionBarContext,
type QyListActionBarProps
} from '@qynpm/admin-patterns'QyDetailPanel
QyDetailPanel 是只读详情展示组件。它支持 record / 兼容入口 data、fields / sections、加载/错误/空态优先级、字段可见性、跨列布局、字段格式化、枚举 options、状态标签和页面 slot 覆盖。
- props:
record?: TRecord | null、data?: TRecord | null、fields?: readonly QyFieldSchema<TRecord>[]、sections?: readonly QyFieldSection<TRecord>[]、loading?: boolean、error?: boolean | string | Error | null、columns?: 1 | 2 | 3、labelWidth?: string | number、emptyText?: string、editable?: boolean - valueType:
text、textarea、number、money、date、date-range、select、tree-select、status、boolean、custom - slots:
loading、error、empty、section、section-{key}、field、field-{key}、默认 slot、actions
QyFieldSchema 和 QyFieldSection 由本包唯一导出;QyDetailPanel 不负责详情请求、远程 options、字段权限、编辑模式或 schema CRUD。status 字段默认使用本包的 QyStatusTag,页面可通过 field slot 覆盖。
import {
QyDetailPanel,
type QyDetailPanelProps,
type QyFieldSchema,
type QyFieldSection
} from '@qynpm/admin-patterns'QyDrawerForm
QyDrawerForm 是后台新增、编辑、详情和授权流程使用的抽屉表单门面。它只负责抽屉工作流的可见性、标题、加载/错误状态、Footer 和确认/取消/关闭事件,不负责表单字段、校验、请求、payload 或业务数据清理。
公开 props:visible、modelValue、title、subtitle、description、width?: string | number、size?: 'small' | 'medium' | 'large' | 'wide'、loading、confirmLoading、error?: boolean | string | Error | null、errorText、footer、confirmText、cancelText、closeOnMask、closeOnEscape 和兼容入口 showFooter。closeOnMask、closeOnEscape 默认均为 true,可分别关闭遮罩点击和 Escape 关闭行为;modelValue 优先于 visible,width 优先于 size 预设;small/medium/large/wide 分别为 480/560/680/720px,默认 medium。
公开 emits:update:modelValue、update:visible、confirm、cancel、close。确认只派发 confirm,不会自动关闭或提交;默认取消、遮罩关闭依次广播两个 update、cancel、close;Header 关闭和 Escape 依次广播两个 update、close;父级外部关闭只派发一次 close,不反向制造 update 或 cancel。两个 update 是兼容广播,不代表存在两个独立状态源。footer=false 用于详情模式,showFooter=false 仅作为兼容入口;header-extra 优先于兼容回退 extra,两者不会重复渲染。
公开 slots:默认内容、loading、error、footer、header-extra 和兼容入口 extra。error 接收 { error, message };footer 接收 { loading, confirmLoading, hasError, error, errorMessage, confirm, cancel }。默认操作使用 @qynpm/ui/qy-button,不会改动业务表单数据。
组件内部保持两层边界:QyDrawerForm 门面只处理公共 API 和业务事件,ElementDrawerRenderer 独占 Element Plus ElDrawer、Teleport、DOM/class、遮罩/Escape、关闭动画、内部滚动和 Element 专属样式。当前只提供 Element Plus renderer,未来 Naive UI / React 只能增加内部 adapter,不增加 renderer 选择 prop,也不改变页面 API。
loading 只禁用确认并保留业务内容;confirmLoading 表示保存进行中,此时默认/自定义取消、Header、遮罩和 Escape 都不能关闭,父级仍可在请求完成或业务中止后主动关闭。renderer 内部通过 qy-overlay-parent--hidden 引用计数锁定 body;每个抽屉拥有独立 release,重复 release 安全,多个抽屉和 Element Dialog 共存时不会清理彼此的锁,重叠 Drawer 也只允许最上层实例响应自己的遮罩和 Escape。抽屉关闭时等待 Element Plus closed 事件再卸载和释放自己的锁,关闭动画期间重新打开不会提前卸载或闪烁。
import {
QyDrawerForm,
type QyDrawerFormError,
type QyDrawerFormFooterSlotProps,
type QyDrawerFormProps
} from '@qynpm/admin-patterns'QyFormPanel
QyFormPanel 是标准管理页新增、编辑和只读字段面板。它管理受控 model、字段/分组 schema、布局、动态字段状态、同步校验、重置基线和公开方法;接口请求、payload、抽屉开关、远程 options、业务权限和页面复合控件仍由消费方负责。
公开 props:modelValue、fields、sections、mode?: 'create' | 'edit' | 'readonly'、columns?: 1 | 2 | 3、labelWidth、loading、error、errorText、disabled、readonly、baselineKey。
公开 valueType:text、textarea、number、select、tree-select、checkbox、switch、boolean、custom。options 支持静态数组或 options(model);校验规则支持 required/pattern/min/max/len/message、同步 validator(value, model, field) 和 change/blur/submit trigger,不请求远程数据,也不执行异步 validator。
公开 emits:update:modelValue(nextModel)、field-change(key, value, nextModel)、validate(valid, errors)、submit(model)。modelValue 完全受控,字段更新始终创建新 model;readonly 模式和 disabled 字段不会写入。
公开 slots:默认 slot、loading、error、field-${key}、field.slot 指定的具名 slot、通用 field。字段 slot 优先级固定为 field-${key}、field.slot、通用 field、默认 renderer,并接收 { field, value, model, mode, disabled, readonly, error, setValue }。
公开方法:validate()、clearValidate(keys?)、resetFields(nextModel?)、getModel()。外部 modelValue 或 baselineKey 变化会更新重置基线;组件自身 emit 的受控回写不会覆盖基线。
内部保持两层边界:QyFormPanel 门面只管理公共表单协议;ElementFormFieldRenderer 独占默认字段控件和所有 Element Plus DOM/class 样式。text/textarea 使用 QyInput,number 使用 QyInputNumber,select 使用 QySelect,tree-select 使用 QyTreeSelect,checkbox/boolean 使用 QyCheckbox,switch 使用 QySwitch 并保持 boolean 归一化和 blur 校验语义。当前不提供 renderer 选择 prop,也不实现 Naive renderer。
import {
QyFormPanel,
type QyFormFieldSchema,
type QyFormPanelExpose,
type QyFormPanelProps,
type QyFormRecord,
type QyFormSection
} from '@qynpm/admin-patterns'