diff --git a/packages/extension/src/components/app-preview-content.tsx b/packages/extension/src/components/app-preview-content.tsx index 1213ed5511074e61ea5f268929c97a1bd756a2e2..d53b5e5af159318b371196e9f7d37ca3f5d1511b 100644 --- a/packages/extension/src/components/app-preview-content.tsx +++ b/packages/extension/src/components/app-preview-content.tsx @@ -1,9 +1,10 @@ import { createSignal, For, Show } from "solid-js" import type { AppLayerItem, AppPreviewModel } from "../lib/app-preview-model" +import { OPENABLE_LAYER_KINDS } from "../lib/app-preview-model" import { tryOpenAppLayerItem } from "../lib/app-layer-navigation" function isOpenableLayerItem(item: AppLayerItem): boolean { - return item.kind === "page" || item.kind === "vo" || item.kind === "eapi" || item.kind === "fe-eapi" || item.kind === "entity" + return OPENABLE_LAYER_KINDS.has(item.kind) } export function AppPreviewContent(props: { model: AppPreviewModel }) { @@ -11,7 +12,7 @@ export function AppPreviewContent(props: { model: AppPreviewModel }) { function onLayerItemClick(item: AppLayerItem) { if (!isOpenableLayerItem(item)) return - const result = tryOpenAppLayerItem(item) + const result = tryOpenAppLayerItem(item, props.model.sources) setNavMessage(result.ok ? null : (result.message ?? "无法打开模型")) } diff --git a/packages/extension/src/lib/app-layer-navigation.ts b/packages/extension/src/lib/app-layer-navigation.ts index eccd7d77641e6dfb36cc2581908d6dd0a86cc012..1a4739a2de42ee70142f66c92f15202ea32fa03a 100644 --- a/packages/extension/src/lib/app-layer-navigation.ts +++ b/packages/extension/src/lib/app-layer-navigation.ts @@ -1,5 +1,6 @@ import type { AppLayerItem } from "./app-preview-model" -import { navigateUbmlModelRef } from "./ubml-model-bridge" +import type { AppPreviewSources } from "./app-preview-model" +import { navigateUbmlModelRef, openUbmlModelTab } from "./ubml-model-bridge" import type { UbmlModelKind } from "./ubml-model-tab-store" function layerKindToModelKind(kind: AppLayerItem["kind"]): UbmlModelKind | undefined { @@ -10,7 +11,24 @@ function layerKindToModelKind(kind: AppLayerItem["kind"]): UbmlModelKind | undef return undefined } -export function tryOpenAppLayerItem(item: AppLayerItem): { ok: boolean; message?: string } { +export function tryOpenAppLayerItem( + item: AppLayerItem, + sources?: AppPreviewSources, +): { ok: boolean; message?: string } { + if (item.kind === "query" || item.kind === "pageflow") { + const yaml = item.kind === "query" ? sources?.queryYamls[item.code] : sources?.pageflowYaml + if (!yaml) { + return { ok: false, message: `${item.kind}/${item.code} 源码不可用,请重新生成应用预览` } + } + openUbmlModelTab({ + kind: item.kind, + key: `${item.kind}:${item.code}`, + title: item.code, + yaml, + viewMode: "file", + }) + return { ok: true } + } const modelKind = layerKindToModelKind(item.kind) if (!modelKind) { return { ok: false, message: `${item.kind}/${item.code} 暂不支持在模型页签打开` } diff --git a/packages/extension/src/lib/app-preview-model.test.ts b/packages/extension/src/lib/app-preview-model.test.ts index f8ade2d94110546adf11c0f872b1e19cc07e3ebe..05c894d1dbc98d741ef209b8c9cb8d9fef3d536d 100644 --- a/packages/extension/src/lib/app-preview-model.test.ts +++ b/packages/extension/src/lib/app-preview-model.test.ts @@ -70,4 +70,29 @@ describe("app-preview-model", () => { expect(backendKinds).toContain("entity") expect(backendKinds).toContain("eapi") }) + + test("buildAppPreviewModel carries sources and counts query/pageflow as openable", async () => { + const yaml = await readFile(EXEMPLAR, "utf8") + const result = await generateAppFromYaml(yaml) + const sources = { queryYamls: { bxdqo: "query: {}" }, pageflowYaml: "pageflow: {}" } + const model = buildAppPreviewModel(result, sources) + expect(model.sources).toEqual(sources) + + const backendKinds = model.layers.find((l) => l.id === "backend")?.groups.map((g) => g.kind) ?? [] + expect(backendKinds).toContain("query") + expect(backendKinds).toContain("pageflow") + + const withoutSources = buildAppPreviewModel(result) + expect(withoutSources.sources).toBeUndefined() + + const openableKinds = new Set(["page", "vo", "eapi", "fe-eapi", "entity", "query", "pageflow"]) + const expectedOpenable = model.layers + .flatMap((l) => l.groups) + .filter((g) => openableKinds.has(g.kind)) + .reduce((n, g) => n + g.items.length, 0) + expect(model.layerSummary.openModelCount).toBe(expectedOpenable) + const queryCount = + model.layers.flatMap((l) => l.groups).find((g) => g.kind === "query")?.items.length ?? 0 + expect(queryCount).toBeGreaterThan(0) + }) }) diff --git a/packages/extension/src/lib/app-preview-model.ts b/packages/extension/src/lib/app-preview-model.ts index 9f2a3547ab826ff4b739d968d38eec31d6e9128f..e3d0e1a9312e3ab2a82e2bb4ceb248bbfd17aa03 100644 --- a/packages/extension/src/lib/app-preview-model.ts +++ b/packages/extension/src/lib/app-preview-model.ts @@ -37,6 +37,19 @@ export type AppLayer = { groups: AppLayerGroup[] } +export type AppPreviewSources = import("../ubml-app/browser-generate").AppGenerateSources + +/** 可在模型页签打开查看的分层条目类型(含 query/pageflow 只读源码)。 */ +export const OPENABLE_LAYER_KINDS: ReadonlySet = new Set([ + "page", + "vo", + "eapi", + "fe-eapi", + "entity", + "query", + "pageflow", +]) + export type AppPreviewModel = { bizobject: string namespace: string @@ -44,6 +57,7 @@ export type AppPreviewModel = { stages: { id: string; valid: boolean; issueCount: number }[] artifacts: { kind: string; code: string }[] layers: AppLayer[] + sources?: AppPreviewSources layerSummary: { frontendCount: number backendCount: number @@ -240,10 +254,7 @@ export function countOpenableLayerModels(layers: AppLayer[]): number { let count = 0 for (const layer of layers) { for (const group of layer.groups) { - if (group.kind === "page" || group.kind === "vo" || group.kind === "eapi" || group.kind === "entity") { - count += group.items.length - } - if (group.kind === "fe-eapi") count += group.items.length + if (OPENABLE_LAYER_KINDS.has(group.kind)) count += group.items.length } } return count @@ -251,12 +262,14 @@ export function countOpenableLayerModels(layers: AppLayer[]): number { export function buildAppPreviewModel( result: import("@code-agent/ubml-codegen/app-browser").AppGenerateResult, + sources?: AppPreviewSources, ): AppPreviewModel { const layers = buildAppLayers(result) return { bizobject: result.dsl.app.bizobject, namespace: result.dsl.app.namespace, valid: result.validation.valid, + ...(sources ? { sources } : {}), stages: result.stages.map((s) => ({ id: s.id, valid: s.valid, diff --git a/packages/extension/src/lib/model-preview-model.ts b/packages/extension/src/lib/model-preview-model.ts index f89d45ed4cb96fd099371ae6d574264c7aaa73a7..1a22250e7032dd21001675cfa3f64f571bf77c70 100644 --- a/packages/extension/src/lib/model-preview-model.ts +++ b/packages/extension/src/lib/model-preview-model.ts @@ -20,9 +20,18 @@ const KIND_LABELS: Record = { page: "页面 pages/", vo: "视图对象 vo/", eapi: "API eapi/", + query: "查询对象 qo/", + pageflow: "页面流 pf/", } export function navItemFromEntry(entry: UbmlModelEntry): ModelNavItem { + if (entry.kind === "query" || entry.kind === "pageflow") { + return { + key: entry.key, + code: entry.label, + name: entry.kind === "query" ? "查询对象(只读)" : "页面流(只读)", + } + } if (entry.kind === "page") { return { key: entry.key, @@ -84,7 +93,7 @@ export function buildModelNavigation( list.push(navItemFromEntry(entry)) groups.set(entry.kind, list) } - const order: UbmlModelKind[] = ["page", "vo", "eapi", "entity"] + const order: UbmlModelKind[] = ["page", "vo", "eapi", "entity", "query", "pageflow"] const navGroups = order .filter((kind) => groups.has(kind)) .map((kind) => ({ @@ -124,5 +133,6 @@ export function graphViewLabel(kind: UbmlModelKind): string { if (kind === "entity") return "关系图" if (kind === "vo") return "映射" if (kind === "eapi") return "接口" + if (kind === "query" || kind === "pageflow") return "文件" return "结构" } diff --git a/packages/extension/src/lib/ubml-model-bridge.ts b/packages/extension/src/lib/ubml-model-bridge.ts index 36ed5b2bab3100e9158957a1dfd3c16a13a4c581..fd993b1cc48366c537582a01a990802b7654caf9 100644 --- a/packages/extension/src/lib/ubml-model-bridge.ts +++ b/packages/extension/src/lib/ubml-model-bridge.ts @@ -65,11 +65,25 @@ export type OpenUbmlEapiModelTabInput = { relativePath?: string } +type OpenUbmlSourceModelTabInputBase = { + key: string + title: string + yaml: string + viewMode?: UbmlModelViewMode + logicalKey?: string + relativePath?: string +} + +export type OpenUbmlSourceModelTabInput = + | (OpenUbmlSourceModelTabInputBase & { kind: "query" }) + | (OpenUbmlSourceModelTabInputBase & { kind: "pageflow" }) + export type OpenUbmlModelTabInput = | OpenUbmlEntityModelTabInput | OpenUbmlPageModelTabInput | OpenUbmlVoModelTabInput | OpenUbmlEapiModelTabInput + | OpenUbmlSourceModelTabInput export type UbmlModelRef = { kind: UbmlModelKind @@ -171,6 +185,16 @@ function toEntry(input: OpenUbmlModelTabInput): UbmlModelEntry { ...pathField, } } + if (input.kind === "query" || input.kind === "pageflow") { + return { + key: input.key.trim(), + label: input.title.trim() || input.key.trim(), + kind: input.kind, + yaml: input.yaml, + ...(logicalKey ? { logicalKey } : {}), + ...pathField, + } + } return { key: input.key.trim(), label: input.title.trim() || input.model.entityCode, diff --git a/packages/extension/src/lib/ubml-model-tab-store.ts b/packages/extension/src/lib/ubml-model-tab-store.ts index 3bbbf259070d860cb415a629a40f603b4547bc53..ad7931ba0304a3ac1042635bc7159aeea5f9f098 100644 --- a/packages/extension/src/lib/ubml-model-tab-store.ts +++ b/packages/extension/src/lib/ubml-model-tab-store.ts @@ -3,7 +3,7 @@ import type { EapiPreviewModel } from "./eapi-preview-model" import type { PagePreviewModel } from "./page-preview-model" import type { VoPreviewModel } from "./vo-preview-model" -export type UbmlModelKind = "entity" | "page" | "vo" | "eapi" +export type UbmlModelKind = "entity" | "page" | "vo" | "eapi" | "query" | "pageflow" export type UbmlEntityModelEntry = { key: string @@ -49,7 +49,25 @@ export type UbmlEapiModelEntry = { relativePath?: string } -export type UbmlModelEntry = UbmlEntityModelEntry | UbmlPageModelEntry | UbmlVoModelEntry | UbmlEapiModelEntry +/** 只读源码类产物(query/pageflow):无结构化预览模型,仅在“文件”视图展示 YAML。 */ +type UbmlSourceModelEntryBase = { + key: string + label: string + yaml: string + logicalKey?: string + relativePath?: string +} + +export type UbmlSourceModelEntry = + | (UbmlSourceModelEntryBase & { kind: "query" }) + | (UbmlSourceModelEntryBase & { kind: "pageflow" }) + +export type UbmlModelEntry = + | UbmlEntityModelEntry + | UbmlPageModelEntry + | UbmlVoModelEntry + | UbmlEapiModelEntry + | UbmlSourceModelEntry export type UbmlModelViewMode = "graph" | "preview" | "actions" | "file" diff --git a/packages/extension/src/pages/workbench/ubml-app-editor.tsx b/packages/extension/src/pages/workbench/ubml-app-editor.tsx index 5bf48a32c5935d16c13bbe7bf8f1d1c866341d2e..c4ca3505395a4cd1ecff9b617ad113c7931377c9 100644 --- a/packages/extension/src/pages/workbench/ubml-app-editor.tsx +++ b/packages/extension/src/pages/workbench/ubml-app-editor.tsx @@ -17,7 +17,7 @@ import { repairLimitMessage, } from "../../lib/ubml-appgen-repair-loop" import { consumeDevLabPendingYaml } from "../../lib/ubml-dev-lab-bridge" -import { generateAppFromYamlInBrowser } from "../../ubml-app/browser-generate" +import { generateAppWithSourcesInBrowser } from "../../ubml-app/browser-generate" import { getAppExemplar, listAppExemplarIds } from "../../ubml-app/catalog" import { openUbmlAppWritebackDialog, UbmlAppWritebackDialog } from "../../components/ubml-app-writeback-dialog" import type { AppPreviewModel } from "../../lib/app-preview-model" @@ -142,7 +142,7 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { async function runGenerate() { try { - const result = await generateAppFromYamlInBrowser(yaml()) + const { result, sources } = await generateAppWithSourcesInBrowser(yaml()) const manifest = buildAppManifest(result) as Record setLastResult(result) setLastManifest(manifest) @@ -152,7 +152,7 @@ export function UbmlAppEditor(props: UbmlAppEditorProps = {}) { setStatus(formatStatus(result)) setIssues(JSON.stringify(result.validation.issues, null, 2)) if (props.onPreview) { - props.onPreview(buildAppPreviewModel(result)) + props.onPreview(buildAppPreviewModel(result, sources)) } } catch (e) { setStatus(e instanceof Error ? e.message : String(e)) diff --git a/packages/extension/src/pages/workbench/ubml-model-panel.tsx b/packages/extension/src/pages/workbench/ubml-model-panel.tsx index ff2b9da5683431dffb6645336a5c9b4e91e6cc12..efae74d155807dde26a4196e208a1eb856ef579b 100644 --- a/packages/extension/src/pages/workbench/ubml-model-panel.tsx +++ b/packages/extension/src/pages/workbench/ubml-model-panel.tsx @@ -115,6 +115,11 @@ export function UbmlModelPanel(props: { tabActive?: boolean } = {}) { buildModelNavigation(state().entries, getBundleNavigationRoot()), ) + const sourceOnly = createMemo(() => { + const kind = selected()?.kind + return kind === "query" || kind === "pageflow" + }) + createEffect(() => { const entry = selected() const mode = state().viewMode @@ -124,6 +129,10 @@ export function UbmlModelPanel(props: { tabActive?: boolean } = {}) { if (entry?.kind === "eapi" && (mode === "preview" || mode === "actions")) { setUbmlModelViewMode("graph") } + // UI 侧兜底:query/pageflow 无结构化预览模型,无论入口如何设置都收敛到文件视图 + if ((entry?.kind === "query" || entry?.kind === "pageflow") && mode !== "file") { + setUbmlModelViewMode("file") + } }) const selectedKey = createMemo(() => selected()?.key) @@ -205,17 +214,19 @@ export function UbmlModelPanel(props: { tabActive?: boolean } = {}) {
- + + +