From 18239a59ca1de148767bea0bf27f8ace6669051b Mon Sep 17 00:00:00 2001 From: 0264408 Date: Tue, 29 Sep 2026 15:22:59 +0800 Subject: [PATCH] feat: split Ordo designer into library monorepo Extract ordo-designer-vue package with shadcn UI and context-based state; add playground app, library-api docs, and README. Co-authored-by: Cursor --- README.md | 66 +- env.d.ts => apps/playground/env.d.ts | 0 index.html => apps/playground/index.html | 10 +- apps/playground/package.json | 30 + apps/playground/src/App.vue | 5 + apps/playground/src/api/createOrdoClient.ts | 64 + apps/playground/src/composables/useAsync.ts | 21 + apps/playground/src/main.ts | 8 + {src => apps/playground/src}/router/index.ts | 4 +- apps/playground/src/stores/drafts.ts | 73 ++ apps/playground/src/styles/main.css | 8 + .../src/views/DefinitionListView.vue | 246 ++++ apps/playground/src/views/DesignerPage.vue | 56 + apps/playground/tsconfig.app.json | 11 + apps/playground/tsconfig.json | 4 + apps/playground/tsconfig.node.json | 8 + apps/playground/vite.config.ts | 29 + docs/library-api.md | 69 ++ docs/roadmap.md | 4 +- package.json | 35 +- packages/ordo-designer-vue/env.d.ts | 7 + packages/ordo-designer-vue/package.json | 51 + .../ordo-designer-vue/src/OrdoDesigner.vue | 346 +++--- packages/ordo-designer-vue/src/client.ts | 11 + .../designer/ConditionWhenEditor.vue | 247 ++++ .../designer/DraftConflictModal.vue | 46 + .../src/components/designer/DurationAfter.vue | 53 + .../components/designer/GraphErrorList.vue | 37 + .../src}/components/designer/NodePalette.vue | 28 +- .../components/designer/PredicateBuilder.vue | 192 +++ .../src/components/designer/PropertyPanel.vue | 368 ++++++ .../components/designer/edges/BranchEdge.vue | 16 +- .../components/designer/nodes/ActionNode.vue | 24 +- .../designer/nodes/ApprovalNode.vue | 32 +- .../src/components/designer/nodes/EndNode.vue | 26 + .../components/designer/nodes/NodeHandles.vue | 0 .../designer/nodes/ParallelNode.vue | 53 +- .../src/components/ui/Alert.vue | 25 + .../src/components/ui/Badge.vue | 24 + .../src/components/ui/Button.vue | 48 + .../src/components/ui/ConfirmDialog.vue | 35 + .../src/components/ui/DialogPanel.vue | 30 + .../src/components/ui/FormField.vue | 13 + .../src/components/ui/Input.vue | 18 + .../src/components/ui/Label.vue | 9 + .../src/components/ui/SegmentedControl.vue | 31 + .../src/components/ui/SelectField.vue | 26 + .../src/components/ui/SheetPanel.vue | 30 + .../src/components/ui/Spinner.vue | 17 + .../src/components/ui/Switch.vue | 21 + .../src/components/ui/TokenListInput.vue | 146 +++ .../src/components/ui/select/Select.vue | 15 + .../components/ui/select/SelectContent.vue | 41 + .../src/components/ui/select/SelectGroup.vue | 16 + .../src/components/ui/select/SelectItem.vue | 33 + .../src/components/ui/select/SelectLabel.vue | 14 + .../ui/select/SelectScrollDownButton.vue | 21 + .../ui/select/SelectScrollUpButton.vue | 21 + .../components/ui/select/SelectSeparator.vue | 14 + .../components/ui/select/SelectTrigger.vue | 29 + .../src/components/ui/select/SelectValue.vue | 10 + .../src/components/ui/select/index.ts | 10 + .../src}/composables/useAsync.ts | 0 .../src}/composables/useCatalogLabels.ts | 8 +- .../src/composables/useConfirm.ts | 37 + .../src/context/designerContext.ts | 51 + packages/ordo-designer-vue/src/drafts.ts | 25 + packages/ordo-designer-vue/src/index.ts | 25 + .../ordo-designer-vue/src/lib/selectEmpty.ts | 11 + .../ordo-designer-vue/src/lib/tokenList.ts | 23 + packages/ordo-designer-vue/src/lib/utils.ts | 6 + .../ordo-designer-vue/src}/mapper/duration.ts | 0 .../ordo-designer-vue/src}/mapper/edgePath.ts | 0 .../ordo-designer-vue/src}/mapper/graph.ts | 0 .../ordo-designer-vue/src}/mapper/layout.ts | 0 .../ordo-designer-vue/src}/mapper/parallel.ts | 0 .../ordo-designer-vue/src}/mapper/when.ts | 0 packages/ordo-designer-vue/src/shims-css.d.ts | 6 + .../ordo-designer-vue/src}/stores/catalog.ts | 16 +- .../ordo-designer-vue/src}/stores/designer.ts | 5 +- .../ordo-designer-vue/src/styles/designer.css | 97 ++ .../ordo-designer-vue/src/styles/index.css | 4 + .../ordo-designer-vue/src/styles/theme.css | 149 +++ .../src}/types/definition.ts | 0 .../ordo-designer-vue/tsconfig.build.json | 16 + packages/ordo-designer-vue/tsconfig.json | 4 + packages/ordo-designer-vue/vite.config.ts | 39 + pnpm-lock.yaml | 1042 +++++++++++++---- pnpm-workspace.yaml | 4 + src/App.vue | 11 - src/api/ordo.ts | 75 -- .../designer/ConditionWhenEditor.vue | 235 ---- .../designer/DraftConflictModal.vue | 44 - src/components/designer/DurationAfter.vue | 44 - src/components/designer/GraphErrorList.vue | 30 - src/components/designer/PredicateBuilder.vue | 175 --- src/components/designer/PropertyPanel.vue | 388 ------ src/components/designer/nodes/EndNode.vue | 26 - src/main.ts | 19 - src/stores/drafts.ts | 82 -- src/styles/main.css | 38 - src/views/DefinitionListView.vue | 237 ---- tsconfig.app.json | 18 - tsconfig.json | 8 +- tsconfig.node.json | 27 - vite.config.ts | 28 - 106 files changed, 4000 insertions(+), 2038 deletions(-) rename env.d.ts => apps/playground/env.d.ts (100%) rename index.html => apps/playground/index.html (58%) create mode 100644 apps/playground/package.json create mode 100644 apps/playground/src/App.vue create mode 100644 apps/playground/src/api/createOrdoClient.ts create mode 100644 apps/playground/src/composables/useAsync.ts create mode 100644 apps/playground/src/main.ts rename {src => apps/playground/src}/router/index.ts (84%) create mode 100644 apps/playground/src/stores/drafts.ts create mode 100644 apps/playground/src/styles/main.css create mode 100644 apps/playground/src/views/DefinitionListView.vue create mode 100644 apps/playground/src/views/DesignerPage.vue create mode 100644 apps/playground/tsconfig.app.json create mode 100644 apps/playground/tsconfig.json create mode 100644 apps/playground/tsconfig.node.json create mode 100644 apps/playground/vite.config.ts create mode 100644 docs/library-api.md create mode 100644 packages/ordo-designer-vue/env.d.ts create mode 100644 packages/ordo-designer-vue/package.json rename src/views/DesignerView.vue => packages/ordo-designer-vue/src/OrdoDesigner.vue (62%) create mode 100644 packages/ordo-designer-vue/src/client.ts create mode 100644 packages/ordo-designer-vue/src/components/designer/ConditionWhenEditor.vue create mode 100644 packages/ordo-designer-vue/src/components/designer/DraftConflictModal.vue create mode 100644 packages/ordo-designer-vue/src/components/designer/DurationAfter.vue create mode 100644 packages/ordo-designer-vue/src/components/designer/GraphErrorList.vue rename {src => packages/ordo-designer-vue/src}/components/designer/NodePalette.vue (65%) create mode 100644 packages/ordo-designer-vue/src/components/designer/PredicateBuilder.vue create mode 100644 packages/ordo-designer-vue/src/components/designer/PropertyPanel.vue rename {src => packages/ordo-designer-vue/src}/components/designer/edges/BranchEdge.vue (81%) rename {src => packages/ordo-designer-vue/src}/components/designer/nodes/ActionNode.vue (61%) rename {src => packages/ordo-designer-vue/src}/components/designer/nodes/ApprovalNode.vue (56%) create mode 100644 packages/ordo-designer-vue/src/components/designer/nodes/EndNode.vue rename {src => packages/ordo-designer-vue/src}/components/designer/nodes/NodeHandles.vue (100%) rename {src => packages/ordo-designer-vue/src}/components/designer/nodes/ParallelNode.vue (63%) create mode 100644 packages/ordo-designer-vue/src/components/ui/Alert.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Badge.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Button.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/ConfirmDialog.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/DialogPanel.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/FormField.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Input.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Label.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/SegmentedControl.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/SelectField.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/SheetPanel.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Spinner.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/Switch.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/TokenListInput.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/Select.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectContent.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectGroup.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectItem.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectLabel.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectScrollDownButton.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectScrollUpButton.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectSeparator.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectTrigger.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/SelectValue.vue create mode 100644 packages/ordo-designer-vue/src/components/ui/select/index.ts rename {src => packages/ordo-designer-vue/src}/composables/useAsync.ts (100%) rename {src => packages/ordo-designer-vue/src}/composables/useCatalogLabels.ts (75%) create mode 100644 packages/ordo-designer-vue/src/composables/useConfirm.ts create mode 100644 packages/ordo-designer-vue/src/context/designerContext.ts create mode 100644 packages/ordo-designer-vue/src/drafts.ts create mode 100644 packages/ordo-designer-vue/src/index.ts create mode 100644 packages/ordo-designer-vue/src/lib/selectEmpty.ts create mode 100644 packages/ordo-designer-vue/src/lib/tokenList.ts create mode 100644 packages/ordo-designer-vue/src/lib/utils.ts rename {src => packages/ordo-designer-vue/src}/mapper/duration.ts (100%) rename {src => packages/ordo-designer-vue/src}/mapper/edgePath.ts (100%) rename {src => packages/ordo-designer-vue/src}/mapper/graph.ts (100%) rename {src => packages/ordo-designer-vue/src}/mapper/layout.ts (100%) rename {src => packages/ordo-designer-vue/src}/mapper/parallel.ts (100%) rename {src => packages/ordo-designer-vue/src}/mapper/when.ts (100%) create mode 100644 packages/ordo-designer-vue/src/shims-css.d.ts rename {src => packages/ordo-designer-vue/src}/stores/catalog.ts (71%) rename {src => packages/ordo-designer-vue/src}/stores/designer.ts (99%) create mode 100644 packages/ordo-designer-vue/src/styles/designer.css create mode 100644 packages/ordo-designer-vue/src/styles/index.css create mode 100644 packages/ordo-designer-vue/src/styles/theme.css rename {src => packages/ordo-designer-vue/src}/types/definition.ts (100%) create mode 100644 packages/ordo-designer-vue/tsconfig.build.json create mode 100644 packages/ordo-designer-vue/tsconfig.json create mode 100644 packages/ordo-designer-vue/vite.config.ts delete mode 100644 src/App.vue delete mode 100644 src/api/ordo.ts delete mode 100644 src/components/designer/ConditionWhenEditor.vue delete mode 100644 src/components/designer/DraftConflictModal.vue delete mode 100644 src/components/designer/DurationAfter.vue delete mode 100644 src/components/designer/GraphErrorList.vue delete mode 100644 src/components/designer/PredicateBuilder.vue delete mode 100644 src/components/designer/PropertyPanel.vue delete mode 100644 src/components/designer/nodes/EndNode.vue delete mode 100644 src/main.ts delete mode 100644 src/stores/drafts.ts delete mode 100644 src/styles/main.css delete mode 100644 src/views/DefinitionListView.vue delete mode 100644 tsconfig.app.json delete mode 100644 tsconfig.node.json delete mode 100644 vite.config.ts diff --git a/README.md b/README.md index d1e393e..f1ff86d 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,52 @@ -# ordo-designer-demo +# ordo-designer-vue -This template should help get you started developing with Vue 3 in Vite. +Vue 3 流程定义设计器:可嵌入宿主应用的 `OrdoDesigner` 组件库,以及对接 Ordo REST 的本地 playground。 -## Recommended IDE Setup +## 仓库结构 -[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). +| 路径 | 说明 | +|------|------| +| `packages/ordo-designer-vue` | 设计器库(Vue Flow、shadcn 风格 UI、provide/inject 状态,无 Pinia) | +| `apps/playground` | 开发/演示应用:定义列表、设计页、本地草稿 | +| `docs/library-api.md` | 库集成、主题、`OrdoClient` / `DraftStorage` 说明 | -## Recommended Browser Setup +## 环境 -- Chromium-based browsers (Chrome, Edge, Brave, etc.): - - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) - - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) -- Firefox: - - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) - - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) +- Node `^22.18.0` 或 `>=24.12.0` +- [pnpm](https://pnpm.io/) -## Type Support for `.vue` Imports in TS - -TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. - -## Customize configuration - -See [Vite Configuration Reference](https://vite.dev/config/). - -## Project Setup +## 开发 ```sh pnpm install -``` - -### Compile and Hot-Reload for Development - -```sh pnpm dev ``` -### Type-Check, Compile and Minify for Production +`pnpm dev` 会先构建 `ordo-designer-vue`,再启动 playground(默认 Vite 将 `ordo-designer-vue` 指向库产物 `dist/`)。 -```sh -pnpm build +Playground 需 Ordo 后端;开发时通过 Vite 代理访问 `/ordo`(见 `apps/playground/vite.config.ts`)。 + +## 脚本 + +| 命令 | 作用 | +|------|------| +| `pnpm dev` | 构建库 + playground 热更新 | +| `pnpm build` | 构建库 + playground 生产包 | +| `pnpm type-check` | 全 workspace 类型检查 | +| `pnpm -F ordo-designer-vue build` | 仅构建库 | +| `pnpm -F playground dev` | 仅启动 playground(需已构建库) | + +## 在宿主中使用库 + +```ts +import { OrdoDesigner } from 'ordo-designer-vue' +import 'ordo-designer-vue/style.css' ``` + +实现 `OrdoClient` 与 `DraftStorage`,传入 `OrdoDesigner`。可选导出:`DraftConflictModal`、`Button`、`Badge`、`Alert`、`DialogPanel` 等,便于列表页与库 UI 一致。 + +详见 [docs/library-api.md](./docs/library-api.md)。 + +## IDE + +[VS Code](https://code.visualstudio.com/) + [Vue - Official (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.volar)。 diff --git a/env.d.ts b/apps/playground/env.d.ts similarity index 100% rename from env.d.ts rename to apps/playground/env.d.ts diff --git a/index.html b/apps/playground/index.html similarity index 58% rename from index.html rename to apps/playground/index.html index edd03c5..4b2236d 100644 --- a/index.html +++ b/apps/playground/index.html @@ -1,10 +1,10 @@ - + - - - - Ordo Designer + + + + Ordo Designer Playground
diff --git a/apps/playground/package.json b/apps/playground/package.json new file mode 100644 index 0000000..cd19bb8 --- /dev/null +++ b/apps/playground/package.json @@ -0,0 +1,30 @@ +{ + "name": "playground", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "ordo-designer-vue": "workspace:*", + "vue": "^3.5.40", + "vue-router": "^5.2.0" + }, + "devDependencies": { + "@types/node": "^24.6.0", + "@tsconfig/node24": "^24.0.4", + "@tailwindcss/vite": "^4.3.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "npm-run-all2": "^9.0.2", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-vue-devtools": "^8.1.5", + "vue-tsc": "^3.3.7" + } +} diff --git a/apps/playground/src/App.vue b/apps/playground/src/App.vue new file mode 100644 index 0000000..9f7a593 --- /dev/null +++ b/apps/playground/src/App.vue @@ -0,0 +1,5 @@ + + + diff --git a/apps/playground/src/api/createOrdoClient.ts b/apps/playground/src/api/createOrdoClient.ts new file mode 100644 index 0000000..0895dad --- /dev/null +++ b/apps/playground/src/api/createOrdoClient.ts @@ -0,0 +1,64 @@ +import type { CatalogItem, OrdoClient, PageResponse, ProcessDefinitionDocument } from 'ordo-designer-vue' + +async function request(baseUrl: string, url: string, init?: RequestInit): Promise { + const res = await fetch(`${baseUrl}${url}`, { + ...init, + headers: { + 'Content-Type': 'application/json', + ...(init?.headers ?? {}), + }, + }) + if (!res.ok) { + let message = res.statusText + try { + const body = (await res.json()) as { message?: string } + if (body.message) { + message = body.message + } + } catch { + /* ignore */ + } + throw new Error(message) + } + if (res.status === 204) { + return undefined as T + } + return (await res.json()) as T +} + +export function createOrdoClient(baseUrl = '/ordo'): OrdoClient { + return { + listConditions: () => request(baseUrl, '/catalog/conditions'), + listActions: () => request(baseUrl, '/catalog/actions'), + listAssignees: () => request(baseUrl, '/catalog/assignees'), + getDefinition: (id) => request(baseUrl, `/definitions/${encodeURIComponent(id)}`), + getDefinitionVersion: (id, version) => + request(baseUrl, `/definitions/${encodeURIComponent(id)}/versions/${version}`), + parseDefinition: (doc) => + request(baseUrl, '/definitions/parse', { + method: 'POST', + body: JSON.stringify(doc), + }), + publishDefinition: (doc) => + request(baseUrl, '/definitions', { + method: 'POST', + body: JSON.stringify(doc), + }), + } +} + +export function listDefinitions(baseUrl: string, page = 0, size = 50) { + return request>( + baseUrl, + `/definitions?page=${page}&size=${size}`, + ) +} + +export function listDefinitionVersions(baseUrl: string, id: string, page = 0, size = 50) { + return request>( + baseUrl, + `/definitions/${encodeURIComponent(id)}/versions?page=${page}&size=${size}`, + ) +} + +export type { PageResponse, ProcessDefinitionDocument } diff --git a/apps/playground/src/composables/useAsync.ts b/apps/playground/src/composables/useAsync.ts new file mode 100644 index 0000000..abf66b2 --- /dev/null +++ b/apps/playground/src/composables/useAsync.ts @@ -0,0 +1,21 @@ +import { ref } from 'vue' + +export function useAsync() { + const loading = ref(false) + const error = ref(null) + + async function run(fn: () => Promise): Promise { + loading.value = true + error.value = null + try { + return await fn() + } catch (e) { + error.value = e instanceof Error ? e.message : 'Request failed' + throw e + } finally { + loading.value = false + } + } + + return { loading, error, run } +} diff --git a/apps/playground/src/main.ts b/apps/playground/src/main.ts new file mode 100644 index 0000000..b5b2d23 --- /dev/null +++ b/apps/playground/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import 'ordo-designer-vue/style.css' +import './styles/main.css' + +import App from './App.vue' +import router from './router' + +createApp(App).use(router).mount('#app') diff --git a/src/router/index.ts b/apps/playground/src/router/index.ts similarity index 84% rename from src/router/index.ts rename to apps/playground/src/router/index.ts index a5f4ed2..4b7eb77 100644 --- a/src/router/index.ts +++ b/apps/playground/src/router/index.ts @@ -1,12 +1,12 @@ import { createRouter, createWebHistory } from 'vue-router' import DefinitionListView from '@/views/DefinitionListView.vue' -import DesignerView from '@/views/DesignerView.vue' +import DesignerPage from '@/views/DesignerPage.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'definitions', component: DefinitionListView }, - { path: '/designer/:definitionId?', name: 'designer', component: DesignerView }, + { path: '/designer/:definitionId?', name: 'designer', component: DesignerPage }, ], }) diff --git a/apps/playground/src/stores/drafts.ts b/apps/playground/src/stores/drafts.ts new file mode 100644 index 0000000..55f8a11 --- /dev/null +++ b/apps/playground/src/stores/drafts.ts @@ -0,0 +1,73 @@ +import { ref, type Ref } from 'vue' +import type { DesignerDraft, DraftStorage } from 'ordo-designer-vue' +import { layoutFromNodes } from 'ordo-designer-vue' +import type { DesignerEdge, DesignerNode, ProcessDefinitionDocument } from 'ordo-designer-vue' + +const STORAGE_KEY = 'ordo-designer-drafts' + +function readAll(): Record { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) { + return {} + } + return JSON.parse(raw) as Record + } catch { + return {} + } +} + +function writeAll(drafts: Record) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(drafts)) +} + +export function createDraftStorage(): DraftStorage { + const revision = ref(0) + + return { + revision, + get(id: string) { + return readAll()[id] + }, + save(params: { + id: string + name: string + startStepId: string + nodes: DesignerNode[] + edges: DesignerEdge[] + }) { + const drafts = readAll() + drafts[params.id] = { + id: params.id, + name: params.name, + startStepId: params.startStepId, + nodes: params.nodes, + edges: params.edges, + layout: layoutFromNodes(params.nodes), + updatedAt: Date.now(), + } + writeAll(drafts) + revision.value += 1 + }, + remove(id: string) { + const drafts = readAll() + delete drafts[id] + writeAll(drafts) + revision.value += 1 + }, + } +} + +const shared = createDraftStorage() + +export function useDraftStorage(): DraftStorage { + return shared +} + +export function listLocalDrafts(): DesignerDraft[] { + return Object.values(readAll()).sort((a, b) => b.updatedAt - a.updatedAt) +} + +export function fromPublished(doc: ProcessDefinitionDocument): DesignerDraft | undefined { + return shared.get(doc.id) +} diff --git a/apps/playground/src/styles/main.css b/apps/playground/src/styles/main.css new file mode 100644 index 0000000..dfadae9 --- /dev/null +++ b/apps/playground/src/styles/main.css @@ -0,0 +1,8 @@ +@import "tailwindcss"; + +html, +body, +#app { + height: 100%; + margin: 0; +} diff --git a/apps/playground/src/views/DefinitionListView.vue b/apps/playground/src/views/DefinitionListView.vue new file mode 100644 index 0000000..84a759c --- /dev/null +++ b/apps/playground/src/views/DefinitionListView.vue @@ -0,0 +1,246 @@ + + + diff --git a/apps/playground/src/views/DesignerPage.vue b/apps/playground/src/views/DesignerPage.vue new file mode 100644 index 0000000..ba15a3a --- /dev/null +++ b/apps/playground/src/views/DesignerPage.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/playground/tsconfig.app.json b/apps/playground/tsconfig.app.json new file mode 100644 index 0000000..2b0e595 --- /dev/null +++ b/apps/playground/tsconfig.app.json @@ -0,0 +1,11 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "compilerOptions": { + "noUncheckedIndexedAccess": true, + "paths": { + "@/*": ["./src/*"] + }, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo" + } +} diff --git a/apps/playground/tsconfig.json b/apps/playground/tsconfig.json new file mode 100644 index 0000000..d32ff68 --- /dev/null +++ b/apps/playground/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/apps/playground/tsconfig.node.json b/apps/playground/tsconfig.node.json new file mode 100644 index 0000000..79b2a97 --- /dev/null +++ b/apps/playground/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": ["vite.config.ts"], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" + } +} diff --git a/apps/playground/vite.config.ts b/apps/playground/vite.config.ts new file mode 100644 index 0000000..61045de --- /dev/null +++ b/apps/playground/vite.config.ts @@ -0,0 +1,29 @@ +import path from 'node:path' +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' +import tailwindcss from '@tailwindcss/vite' + +const pkgDist = fileURLToPath(new URL('../../packages/ordo-designer-vue/dist/ordo-designer-vue.js', import.meta.url)) +const pkgStyle = fileURLToPath(new URL('../../packages/ordo-designer-vue/dist/style.css', import.meta.url)) + +export default defineConfig({ + plugins: [vue(), vueDevTools(), tailwindcss()], + resolve: { + alias: [ + { find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }, + { find: 'ordo-designer-vue/style.css', replacement: pkgStyle }, + // Exact entry only — avoid breaking `ordo-designer-vue/style.css` subpath. + { find: /^ordo-designer-vue$/, replacement: pkgDist }, + ], + }, + server: { + proxy: { + '/ordo': { + target: 'http://localhost:8080', + changeOrigin: true, + }, + }, + }, +}) diff --git a/docs/library-api.md b/docs/library-api.md new file mode 100644 index 0000000..44a96bc --- /dev/null +++ b/docs/library-api.md @@ -0,0 +1,69 @@ +# ordo-designer-vue 库 API + +## 安装与样式 + +```ts +import { OrdoDesigner } from 'ordo-designer-vue' +import 'ordo-designer-vue/style.css' +``` + +## 主题定制 + +设计器根节点为 `.ordo-designer`,亮色/暗色通过 **`theme` prop** 或 CSS 变量控制。 + +### 组件 prop + +```vue + +``` + +`theme="dark"` 会在根上添加 class `ordo-designer dark`,启用暗色 token。 + +### CSS 变量(宿主覆盖) + +在引入 `style.css` **之后**覆盖(作用域建议挂在 `.ordo-designer` 或父级包一层 class): + +```css +.ordo-designer { + --ordo-primary: #7c3aed; + --ordo-ring: #7c3aed; + --ordo-background: #fafafa; +} +``` + +常用变量见 `packages/ordo-designer-vue/src/styles/theme.css`(`--ordo-background`、`--ordo-foreground`、`--ordo-card`、`--ordo-border`、`--ordo-primary`、`--ordo-canvas` 等)。 + +也可单独引入变量表做扩展: + +```ts +import 'ordo-designer-vue/theme.css' +``` + +### 说明 + +- 工具栏、属性面板、弹层等已接 token;流程节点卡片仍保留类型色(sky/amber 等)以便区分步骤种类。 +- 画布网格与背景随 `--ordo-canvas` / `--ordo-canvas-grid` 变化。 + +## OrdoDesigner + +| Prop | 类型 | 说明 | +|------|------|------| +| `client` | `OrdoClient` | 必填,后端 API | +| `drafts` | `DraftStorage` | 必填,本地草稿存储 | +| `definitionId` | `string?` | 流程 id | +| `version` | `number?` | 历史版本(只读) | +| `source` | `string?` | `draft` / `published` | +| `readOnly` | `boolean?` | 只读,默认可由 `version` 推导 | +| `theme` | `'light' \| 'dark'?` | 默认 light | + +| Event | 说明 | +|-------|------| +| `back` | 返回列表 | +| `navigate` | 切换 `definitionId` | +| `published` | 发布成功 | + +`defineExpose`: `confirmLeave`, `isDirty`, `flushAutosave` + +## OrdoClient + +见 `packages/ordo-designer-vue/src/client.ts`。 diff --git a/docs/roadmap.md b/docs/roadmap.md index bf4dffd..7d6cdd6 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -7,7 +7,7 @@ - 节点卡片展示候选人 / policy / action / due - 校验错误挂到节点和边上,不只 toast 第一条 - 草稿 vs 已发布:可丢弃本地、对比后再打开 -- 切定义不清空 Pinia,避免闪旧图 +- 切定义不清空设计器 context,避免闪旧图 - 脏标记 + 离开未保存提示 ## P1 好用 @@ -29,5 +29,5 @@ - 引擎 JSON 预览 / 导出 - 列表里同一 id 的草稿与已发布关系 - 统一 loading / 网络错误 -- 属性面板 n-form 校验态 +- 属性面板表单校验态 - 自动保存草稿 diff --git a/package.json b/package.json index ef0bb48..c6aaffb 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,11 @@ { - "name": "ordo-designer-demo", - "version": "0.0.0", + "name": "ordo-designer-vue-root", "private": true, "type": "module", "scripts": { - "dev": "vite", - "build": "run-p type-check \"build-only {@}\" --", - "preview": "vite preview", - "build-only": "vite build", - "type-check": "vue-tsc --build" - }, - "dependencies": { - "@lucide/vue": "^1.46.0", - "@tailwindcss/vite": "^4.3.3", - "@vue-flow/background": "^1.3.2", - "@vue-flow/controls": "^1.1.3", - "@vue-flow/core": "^1.48.2", - "@vue-flow/minimap": "^1.5.4", - "naive-ui": "^2.45.3", - "pinia": "^4.0.2", - "tailwindcss": "^4.3.3", - "vue": "^3.5.40", - "vue-router": "^5.2.0" - }, - "devDependencies": { - "@tsconfig/node24": "^24.0.4", - "@types/node": "^24.13.3", - "@vitejs/plugin-vue": "^6.0.8", - "@vue/tsconfig": "^0.9.1", - "npm-run-all2": "^9.0.2", - "typescript": "~6.0.0", - "vite": "^8.1.5", - "vite-plugin-vue-devtools": "^8.1.5", - "vue-tsc": "^3.3.7" + "dev": "pnpm -F ordo-designer-vue build && pnpm -F playground dev", + "build": "pnpm -F ordo-designer-vue build && pnpm -F playground build", + "type-check": "pnpm -r type-check" }, "engines": { "node": "^22.18.0 || >=24.12.0" diff --git a/packages/ordo-designer-vue/env.d.ts b/packages/ordo-designer-vue/env.d.ts new file mode 100644 index 0000000..65c7311 --- /dev/null +++ b/packages/ordo-designer-vue/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent + export default component +} diff --git a/packages/ordo-designer-vue/package.json b/packages/ordo-designer-vue/package.json new file mode 100644 index 0000000..45640d5 --- /dev/null +++ b/packages/ordo-designer-vue/package.json @@ -0,0 +1,51 @@ +{ + "name": "ordo-designer-vue", + "version": "0.1.0", + "type": "module", + "files": [ + "dist" + ], + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/ordo-designer-vue.js", + "development": "./src/index.ts" + }, + "./style.css": "./dist/style.css", + "./styles/index.css": "./src/styles/index.css", + "./theme.css": "./src/styles/theme.css" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vite build", + "type-check": "vue-tsc --build" + }, + "peerDependencies": { + "vue": "^3.5.0" + }, + "dependencies": { + "@lucide/vue": "^1.46.0", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vue-flow/minimap": "^1.5.4", + "@vueuse/core": "^13.9.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "reka-ui": "^2.10.5", + "tailwind-merge": "^3.3.1", + "vue-sonner": "^2.0.9" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.3", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "tailwindcss": "^4.3.3", + "typescript": "~6.0.0", + "vite": "^8.1.5", + "vite-plugin-dts": "^4.5.4", + "vue": "^3.5.40", + "vue-tsc": "^3.3.7" + } +} diff --git a/src/views/DesignerView.vue b/packages/ordo-designer-vue/src/OrdoDesigner.vue similarity index 62% rename from src/views/DesignerView.vue rename to packages/ordo-designer-vue/src/OrdoDesigner.vue index df8d3f6..f3d048d 100644 --- a/src/views/DesignerView.vue +++ b/packages/ordo-designer-vue/src/OrdoDesigner.vue @@ -1,7 +1,5 @@ diff --git a/packages/ordo-designer-vue/src/client.ts b/packages/ordo-designer-vue/src/client.ts new file mode 100644 index 0000000..c870e62 --- /dev/null +++ b/packages/ordo-designer-vue/src/client.ts @@ -0,0 +1,11 @@ +import type { CatalogItem, ProcessDefinitionDocument } from '@/types/definition' + +export interface OrdoClient { + listConditions(): Promise + listActions(): Promise + listAssignees(): Promise + getDefinition(id: string): Promise + getDefinitionVersion(id: string, version: number): Promise + parseDefinition(doc: ProcessDefinitionDocument): Promise + publishDefinition(doc: ProcessDefinitionDocument): Promise +} diff --git a/packages/ordo-designer-vue/src/components/designer/ConditionWhenEditor.vue b/packages/ordo-designer-vue/src/components/designer/ConditionWhenEditor.vue new file mode 100644 index 0000000..4655913 --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/ConditionWhenEditor.vue @@ -0,0 +1,247 @@ + + + diff --git a/packages/ordo-designer-vue/src/components/designer/DraftConflictModal.vue b/packages/ordo-designer-vue/src/components/designer/DraftConflictModal.vue new file mode 100644 index 0000000..bd84c82 --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/DraftConflictModal.vue @@ -0,0 +1,46 @@ + + + diff --git a/packages/ordo-designer-vue/src/components/designer/DurationAfter.vue b/packages/ordo-designer-vue/src/components/designer/DurationAfter.vue new file mode 100644 index 0000000..9d7905f --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/DurationAfter.vue @@ -0,0 +1,53 @@ + + + diff --git a/packages/ordo-designer-vue/src/components/designer/GraphErrorList.vue b/packages/ordo-designer-vue/src/components/designer/GraphErrorList.vue new file mode 100644 index 0000000..d67f8af --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/GraphErrorList.vue @@ -0,0 +1,37 @@ + + + diff --git a/src/components/designer/NodePalette.vue b/packages/ordo-designer-vue/src/components/designer/NodePalette.vue similarity index 65% rename from src/components/designer/NodePalette.vue rename to packages/ordo-designer-vue/src/components/designer/NodePalette.vue index d084bf8..57681e1 100644 --- a/src/components/designer/NodePalette.vue +++ b/packages/ordo-designer-vue/src/components/designer/NodePalette.vue @@ -8,8 +8,8 @@ const items = [ label: 'Approval', hint: 'Human review step', accent: 'bg-sky-500', - badge: 'bg-sky-50 text-sky-700', - border: 'border-slate-200 hover:border-sky-300', + badge: 'bg-sky-500/20 text-sky-700', + border: 'border-border hover:border-sky-300', icon: ShieldCheck, }, { @@ -17,8 +17,8 @@ const items = [ label: 'Action', hint: 'System action step', accent: 'bg-amber-500', - badge: 'bg-amber-50 text-amber-700', - border: 'border-slate-200 hover:border-amber-300', + badge: 'bg-amber-500/20 text-amber-700', + border: 'border-border hover:border-amber-300', icon: Bolt, }, { @@ -26,8 +26,8 @@ const items = [ label: 'Parallel', hint: 'Join all branches', accent: 'bg-violet-500', - badge: 'bg-violet-50 text-violet-700', - border: 'border-slate-200 hover:border-violet-300', + badge: 'bg-violet-500/20 text-violet-700', + border: 'border-border hover:border-violet-300', icon: GitBranch, }, { @@ -35,8 +35,8 @@ const items = [ label: 'End', hint: 'Process terminal', accent: 'bg-slate-500', - badge: 'bg-slate-100 text-slate-600', - border: 'border-slate-200 hover:border-slate-400', + badge: 'bg-muted text-muted-foreground', + border: 'border-border hover:border-muted-foreground', icon: Flag, }, ] @@ -51,13 +51,13 @@ function onDragStart(event: DragEvent, type: string) { diff --git a/packages/ordo-designer-vue/src/components/designer/PredicateBuilder.vue b/packages/ordo-designer-vue/src/components/designer/PredicateBuilder.vue new file mode 100644 index 0000000..92803b3 --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/PredicateBuilder.vue @@ -0,0 +1,192 @@ + + + diff --git a/packages/ordo-designer-vue/src/components/designer/PropertyPanel.vue b/packages/ordo-designer-vue/src/components/designer/PropertyPanel.vue new file mode 100644 index 0000000..3c91feb --- /dev/null +++ b/packages/ordo-designer-vue/src/components/designer/PropertyPanel.vue @@ -0,0 +1,368 @@ + + + diff --git a/src/components/designer/edges/BranchEdge.vue b/packages/ordo-designer-vue/src/components/designer/edges/BranchEdge.vue similarity index 81% rename from src/components/designer/edges/BranchEdge.vue rename to packages/ordo-designer-vue/src/components/designer/edges/BranchEdge.vue index 51043e0..4b757af 100644 --- a/src/components/designer/edges/BranchEdge.vue +++ b/packages/ordo-designer-vue/src/components/designer/edges/BranchEdge.vue @@ -3,10 +3,10 @@ import { computed } from 'vue' import { BaseEdge, EdgeLabelRenderer, getBezierPath, useVueFlow, type EdgeProps } from '@vue-flow/core' import type { DesignerEdgeData } from '@/types/definition' import { formatWhenKind } from '@/mapper/when' -import { useDesignerStore } from '@/stores/designer' +import { useDesignerState } from '@/context/designerContext' const props = defineProps>() -const designer = useDesignerStore() +const designer = useDesignerState() const { addSelectedEdges, findEdge } = useVueFlow() const route = computed(() => { @@ -23,8 +23,8 @@ const route = computed(() => { const kind = computed(() => formatWhenKind(props.data?.when) || 'default') const caption = computed(() => (typeof props.label === 'string' && props.label.trim() ? props.label : kind.value)) -const picked = computed(() => props.selected || designer.selectedEdgeId === props.id) -const hasError = computed(() => designer.graphErrors.some((e) => e.edgeId === props.id)) +const picked = computed(() => props.selected || designer.selectedEdgeId.value === props.id) +const hasError = computed(() => designer.graphErrors.value.some((e) => e.edgeId === props.id)) const edgeStyle = computed(() => { if (hasError.value) { @@ -43,12 +43,14 @@ const labelClass = computed(() => { return 'border-red-200 bg-red-50 text-red-600' } if (kind.value === 'host') { - return 'border-amber-300 bg-amber-50 text-amber-800' + return 'border-amber-500/40 bg-amber-500/15 text-amber-800 shadow-sm' } if (kind.value === 'predicate') { - return 'border-sky-300 bg-sky-50 text-sky-800' + return 'border-sky-500/40 bg-sky-500/15 text-sky-800 shadow-sm' } - return picked.value ? 'border-slate-400 bg-white text-slate-800' : 'border-slate-200 bg-white/95 text-slate-600' + return picked.value + ? 'border-border bg-card text-card-foreground shadow-sm ring-1 ring-ring/30' + : 'border-border bg-card/95 text-muted-foreground shadow-sm' }) function selectEdge(event: MouseEvent) { diff --git a/src/components/designer/nodes/ActionNode.vue b/packages/ordo-designer-vue/src/components/designer/nodes/ActionNode.vue similarity index 61% rename from src/components/designer/nodes/ActionNode.vue rename to packages/ordo-designer-vue/src/components/designer/nodes/ActionNode.vue index 412a135..6543d0a 100644 --- a/src/components/designer/nodes/ActionNode.vue +++ b/packages/ordo-designer-vue/src/components/designer/nodes/ActionNode.vue @@ -3,7 +3,7 @@ import { computed } from 'vue' import { Bolt, CirclePlay } from '@lucide/vue' import type { ActionNodeData } from '@/types/definition' import { useCatalogLabels } from '@/composables/useCatalogLabels' -import { useDesignerStore } from '@/stores/designer' +import { useDesignerState } from '@/context/designerContext' import NodeHandles from '@/components/designer/nodes/NodeHandles.vue' const props = defineProps<{ @@ -11,30 +11,30 @@ const props = defineProps<{ data: ActionNodeData }>() -const designer = useDesignerStore() +const designer = useDesignerState() const { action } = useCatalogLabels() -const hasError = computed(() => designer.graphErrors.some((e) => e.nodeId === props.id)) +const hasError = computed(() => designer.graphErrors.value.some((e) => e.nodeId === props.id)) const showStart = computed( - () => Boolean(props.data.isStart) && !designer.nodes.some((node) => node.id === props.id && node.parentNode), + () => Boolean(props.data.isStart) && !designer.nodes.value.some((node) => node.id === props.id && node.parentNode), )