切换主题
YEditorPanel
适用范围:Legacy Admin Kit / Schema(
@yoyoboot/yoyo-admin-kit、旧admin-vue)。新admin-nuxtui使用 Nuxt UI;本页不是新宿主的必经开发路线。
定位 / Owner
YEditorPanel 是标准编辑任务的 Application Composition。它组合 Dialog/Drawer、TaskActions、dirty-close confirm、loading/error/empty、busy、submit/cancel 与可选的“保存后继续编辑”,不是新的 Form state owner。
页面级选型、owner 与真实 consumer 见 Editor Task Application UI。本文只维护 YEditorPanel 的组件合同。
什么时候使用
- 标准 Create/Edit task。
- 需要 dirty close、saving lock、loading/error/retry、empty state。
- feature 不应重复 footer 与 discard confirm。
基础用法
vue
<YEditorPanel
v-model:open="open"
title="编辑租户"
:dirty="dirty"
:submitting="saving"
@submit="save"
>
<TenantForm />
</YEditorPanel>文档内可运行示例
下面的记录、保存等待和结果提示都是确定性的浏览器内存演示,不连接 Admin API 或数据库。运行示例与“查看同源 Vue 代码”读取同一个 BasicEditor.vue;实际业务仍应由自己的 Form、mutation 和权限 owner 提供字段与写入。
Public API 摘要
可运行示例
校验、写入与回读失败
无 formId 的事件提交;可分别体验 Modal / Drawer。观察重试只增加回读次数。
内存写入 0 次,回读 0 次;当前记录:演示记录
查看同源 Vue 代码
<script setup lang="ts">
import { computed, ref } from 'vue'
import { YButton, YCheckbox, YEditorPanel, YInput } from '@yoyoboot/yoyo-admin-kit'
// This fixture is intentionally local. A real consumer delegates these calls
// to its existing Form/session and mutation owner, not to EditorPanel.
const open = ref(false)
const surface = ref<'modal' | 'drawer'>('drawer')
const serverName = ref('演示记录')
const baseline = ref(serverName.value)
const draft = ref(serverName.value)
const dirty = computed(() => draft.value !== baseline.value)
const submitting = ref(false)
const continueEditing = ref(true)
const saved = ref(false)
const failWrite = ref(false)
const failRead = ref(true)
const error = ref('')
const postSaveError = ref<string | null>(null)
const writes = ref(0)
const reads = ref(0)
const delay = () => new Promise(resolve => setTimeout(resolve, 250))
function begin(value: 'modal' | 'drawer') {
surface.value = value
draft.value = baseline.value = serverName.value
error.value = ''
postSaveError.value = null
saved.value = false
open.value = true
}
async function readBack() {
reads.value++
await delay()
if (failRead.value) {
postSaveError.value = '演示回读失败。取消“回读失败”后重新同步,不会再次写入。'
return
}
draft.value = baseline.value = serverName.value
postSaveError.value = null
if (!continueEditing.value) open.value = false
}
async function retryRead() {
if (submitting.value) return
submitting.value = true
try { await readBack() } finally { submitting.value = false }
}
async function save() {
if (submitting.value || postSaveError.value || !dirty.value) return
error.value = ''
if (!draft.value.trim()) { error.value = '名称不能为空。'; return }
submitting.value = true
try {
await delay()
if (failWrite.value) { error.value = '演示写入失败,草稿仍保留。'; return }
serverName.value = draft.value
writes.value++
saved.value = true
await readBack()
} finally { submitting.value = false }
}
</script>
<template>
<div style="display: grid; gap: 0.75rem">
<YButton variant="outline" @click="begin('drawer')">抽屉失败恢复</YButton>
<YButton variant="outline" @click="begin('modal')">弹窗失败恢复</YButton>
<p role="status">内存写入 {{ writes }} 次,回读 {{ reads }} 次;当前记录:{{ serverName }}</p>
</div>
<YEditorPanel v-model:open="open" v-model:continue-editing="continueEditing" title="保存与回读恢复" :surface="surface" :dirty="dirty" :saved="saved" :submitting="submitting" :submit-disabled="!dirty" :post-save-error="postSaveError" allow-save-and-continue @submit="save" @retry="retryRead">
<!-- No formId: Enter and the panel event explicitly share this one handler. -->
<form style="display: grid; gap: 1rem" @submit.prevent="save">
<label>名称<YInput v-model="draft" aria-label="恢复示例名称" /></label>
<p v-if="error" role="alert">{{ error }}</p>
<YCheckbox v-model="failWrite" label="模拟写入失败" :disabled="submitting" />
<YCheckbox v-model="failRead" label="模拟回读失败" :disabled="submitting" />
<p role="status">成功写入 {{ writes }} 次 / 回读 {{ reads }} 次</p>
<p>先修改名称并保存。默认回读失败;取消模拟回读失败,再点重新同步。</p>
</form>
</YEditorPanel>
</template>
选型与接入
普通详情用 YModal;标准编辑任务用 YEditorPanel,不在 feature 重写 dirty/busy/footer。只需要侧边详情可直接用 YDrawer。上述两个示例全部是内存演示,不代表真实 API 验收。
从 @yoyoboot/yoyo-admin-kit 包根导入,并在宿主入口加载 styles.css 与 theme.css。Panel 不要求业务 provider,不拥有字段、校验或 mutation。BasicEditor 使用 HTML required/email 校验和 native form submit;RecoveryEditor 显式校验名称并展示恢复路径,示例辅助代码都在同源 Vue 文件中。
| 提交方式 | 按钮 | Enter |
|---|---|---|
| formId | 只触发关联 form 的 submit,不 emit submit | 交给同一 form handler |
| 无 formId | 只 emit submit | consumer 的 form @submit.prevent 绑定同一 handler |
保存 handler 仍须维护自身 busy/重复写入保护。requestClose() 是安全离开入口:busy 拒绝,dirty 只显示一次确认。父级直接 open=false 是权威覆盖,不触发 dirty 确认;不要拿它代替用户取消。
Public API 摘要(分组)
- models:
open、discardConfirmationOpen - optional model:
continueEditing - 基础内容:
title、description - surface:
surface: modal | drawer(默认 modal,旧 dialog 输入仍兼容)、size: sm | md | lg、allowFullscreen - task text:
submitText、saveAndContinueText、submittingText、cancelText、closeText - save continuation:
allowSaveAndContinue(默认关闭)、saved、postSaveError、postSaveErrorTitle - dirty confirmation:
discardTitle、discardDescription、discardConfirmText、discardCancelText、discardTestIdPrefix - state:
loading、loadingTitle、loadingDescription、error、errorTitle、empty、emptyTitle、emptyDescription、submitting、submitDisabled、dirty - form integration:
formId - focus:
restoreFocusTo - test hooks:
testIds.root/loading/error/cancel/submit - events:
submit、retry - slots:default、
header-actions、actions-prefix
Composition
Props / Models / Events / Slots / Expose
| Prop | 类型 | 默认值/说明 |
|---|---|---|
| title | string | 必填 |
| description | string | 未设置 |
| surface | modal / drawer / dialog(deprecated) | modal |
| size | sm / md / lg | 未设置,跟随容器 |
| allowFullscreen | boolean | false |
| formId | string | 未设置;设置后只走原生表单提交 |
| submitText | string | 保存 |
| saveAndContinueText | string | 保存并继续 |
| submittingText | string | 保存中... |
| cancelText | string | 取消 |
| closeText | string | 关闭 |
| allowSaveAndContinue | boolean | false |
| saved | boolean | false;consumer 提供成功事实 |
| postSaveError | string / null | null;锁重复保存但保留字段 |
| postSaveErrorTitle | string | 已保存,编辑信息同步失败 |
| restoreFocusTo | HTMLElement / null | null;跟随容器恢复策略 |
| discardTitle | string | 放弃未保存的修改? |
| discardDescription | string | 关闭后,本次未保存的编辑内容将丢失。 |
| discardConfirmText | string | 放弃修改 |
| discardCancelText | string | 继续编辑 |
| discardTestIdPrefix | string | y-editor-discard |
| loading | boolean | false |
| loadingTitle | string | 正在加载编辑信息 |
| loadingDescription | string | 请稍候,系统正在同步当前条目的详情数据。 |
| error | string / null | null;加载错误,不是写入错误万能入口 |
| errorTitle | string | 编辑信息加载失败 |
| empty | boolean | false |
| emptyTitle | string | 暂无可编辑记录 |
| emptyDescription | string | 请返回列表后重新选择一条记录。 |
| submitting | boolean | false;锁关闭与提交 |
| submitDisabled | boolean | false;consumer 决定是否可提交 |
| dirty | boolean | false;Form/session 拥有判断 |
| testIds | 未设置;值均为 string |
| 接口 | 类型/默认值 | 时机与职责 |
|---|---|---|
| v-model:open | boolean / false | 权威显隐 |
| v-model:discardConfirmationOpen | boolean / false | 放弃确认显隐,主任务关闭会同步关闭 |
| v-model:continueEditing | boolean / false | 保存后意图,不执行写入 |
| submit | 无参数 | 仅无 formId 时的主按钮操作 |
| retry | 无参数 | 加载重试或写后重新同步;consumer 区分,禁止回读重试重复写 |
| default | slot | 字段与写入错误 UI |
| header-actions | slot | 容器标题次级操作 |
| actions-prefix | slot | 操作区辅助信息 |
| requestClose() | expose / void | 共用 busy/dirty 离开流程 |
旧 surface="dialog" 仍映射为 modal。非模态窗口、拖动缩放、全局弹窗服务后置。本页是当前源码合同与内存示例,不表示新版本已发布或真实业务已验收。
YEditorPanel 拥有 task surface;TanStack Form/feature 拥有字段值与 validation;Data Workbench 可以打开 EditorPanel,但 Data Workbench state 仍是 authoritative orchestration owner。
“保存后继续编辑”只在 consumer 明确 opt-in 的既有记录编辑中使用。它不是第二个提交按钮:checkbox 只改变唯一主按钮的文案与保存后意图,键盘原生 form submit 使用同一选择。保存成功后,Data Workbench 必须重新调用 getForEdit 获取权威字段和最新并发令牌,再把新值设为 clean baseline。
如果写入失败,保留 dirty draft 并显示写入错误;如果写入已经成功但权威回读失败,必须显示“已保存但同步失败”,锁住再次保存,并且重试只能回读、不能重复写入。列表刷新失败只影响列表反馈,不能把已成功的保存伪装成写入失败。
Production Example
接入时从包根导入,并加载 styles.css 与 theme.css;演示保存不连接数据库,也不表示当前源码已经发布 npm。
Identity Users 资料编辑(显式启用保存后继续)、Tenant Create/Edit、Edition editor、Data Workbench CRUD editor。计量单位等未 opt-in consumer 仍保持保存后关闭。
Live Workbench
Admin Vue 稳定路径:
/frontend-module-demo/component-workbench/editor-panel
该路径只标识 live scenario,不固化部署 Host;官方文档站与 Admin Vue 可以独立部署。
Verification / Source
packages/yoyo-admin/admin-kit/src/ui/components/editor-panel/YEditorPanel.vuetests/unit/ui/task-surface.spec.ts- Live Component Workbench