diff --git a/packages/model-design/src/components/design-code-editor/design-code-editor.tsx b/packages/model-design/src/components/design-code-editor/design-code-editor.tsx index d65231e02f87f282529942877c6dd7059f00d81c..33900a2c2d55262afca2c27c6e44385a5a1be4a0 100644 --- a/packages/model-design/src/components/design-code-editor/design-code-editor.tsx +++ b/packages/model-design/src/components/design-code-editor/design-code-editor.tsx @@ -32,6 +32,10 @@ export default defineComponent({ type: Boolean, default: true, }, + showToolbar: { + type: Boolean, + default: true, + }, }, emits: { 'update:modelValue': (_value: string) => true, @@ -225,36 +229,38 @@ export default defineComponent({ class={[this.ns.b(), this.ns.is('full-screen', this.isFullScreen)]} ref='containerRef' > -
-
- - {this.languages.map(item => { - return ; - })} - -
-
- {this.$slots.rightToolbar?.()} - {this.isFullScreen ? ( - this.onToolBarClick(false)} - > - ) : ( - this.onToolBarClick(true)} - > - )} + {this.showToolbar && ( +
+
+ + {this.languages.map(item => { + return ; + })} + +
+
+ {this.$slots.rightToolbar?.()} + {this.isFullScreen ? ( + this.onToolBarClick(false)} + > + ) : ( + this.onToolBarClick(true)} + > + )} +
-
+ )}
); diff --git a/packages/model-design/src/components/index.ts b/packages/model-design/src/components/index.ts index 0dba73e70644ff4d0202d16d35386de58e4f4689..927dba4787492079f1b90d78bdb4ecf6d972eb5c 100644 --- a/packages/model-design/src/components/index.ts +++ b/packages/model-design/src/components/index.ts @@ -2,15 +2,19 @@ import { App } from 'vue'; import IBizModelClipboard from './model-clipboard/model-clipboard'; import IBizModelClipboardItem from './model-clipboard-item/model-clipboard-item'; import IBizModelClipboardImportView from './model-clipboard-import-view/model-clipboard-import-view'; +import IBizModelClipboardPasteView from './model-clipboard-paste-view/model-clipboard-paste-view'; import IBizDesignCodeEditor from './design-code-editor/design-code-editor'; import IBizModelEditView from './model-edit-view/model-edit-view'; +import IBizModelTree from './model-tree/model-tree'; export default { install(app: App) { app.component('IBizModelClipboard', IBizModelClipboard); app.component('IBizModelClipboardItem', IBizModelClipboardItem); app.component('IBizModelClipboardImportView', IBizModelClipboardImportView); + app.component('IBizModelClipboardPasteView', IBizModelClipboardPasteView); app.component('IBizDesignCodeEditor', IBizDesignCodeEditor); app.component('IBizModelEditView', IBizModelEditView); + app.component('IBizModelTree', IBizModelTree); }, }; diff --git a/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.scss b/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.scss index 39106b94da1c34d1507081fa58b8aa628222b774..4cd9ba810e854c4fa980290c0af2ac3888be5a00 100644 --- a/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.scss +++ b/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.scss @@ -174,11 +174,3 @@ font-size: 24px; font-weight: 700; } - -@include b(model-clipboard-import-view-drawer) { - .el-drawer__header { - .el-drawer__close-btn { - display: none; - } - } -} diff --git a/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.tsx b/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.tsx index fd8aea17e93aa946c3569c3336d4862739c58013..888e7e5c667598256dc30c014b1bd29e3779d493 100644 --- a/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.tsx +++ b/packages/model-design/src/components/model-clipboard-import-view/model-clipboard-import-view.tsx @@ -3,7 +3,7 @@ import { IModal } from '@ibiz-template/runtime'; import { PropType, computed, defineComponent } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; import draggable from 'vuedraggable'; -import { ModelData } from '../../model'; +import { ClipboardData } from '../../model'; import './model-clipboard-import-view.scss'; export default defineComponent({ @@ -86,7 +86,8 @@ export default defineComponent({ ibiz.message.warning('编辑框内,模型内容已重新计算!'); c.state.models = []; c.state.items.forEach(item => { - c.state.models.push(...item.models); + const models = item.models.map(m => m.model).filter(m => !!m); + c.state.models.push(...models); }); try { initModel(JSON.stringify(c.state.models, null, 2)); @@ -122,6 +123,7 @@ export default defineComponent({ const materialItem = computed(() => { return modelClipboardController.state.items.filter(item => { if ( + item.type !== 'default' || !Object.is(c.state.params.codeName, item.codeName) || c.state.items.findIndex(self => Object.is(self.uuid, item.uuid)) !== -1 @@ -168,7 +170,7 @@ export default defineComponent({ params, props.params, ); - if (res.status === 200) { + if (res.ok && res.data) { c.state.isModelChange = false; c.state.importItems.push(item); } else { @@ -258,7 +260,7 @@ export default defineComponent({ element, index, }: { - element: ModelData; + element: ClipboardData; index: number; }) => { const item = element; @@ -352,12 +354,7 @@ export default defineComponent({ modelValue={this.materialItem} > {{ - item: ({ - element, - }: { - element: ModelData; - index: number; - }) => { + item: ({ element }: { element: ClipboardData }) => { const item = element; return ( diff --git a/packages/model-design/src/components/model-clipboard-item/model-clipboard-item.tsx b/packages/model-design/src/components/model-clipboard-item/model-clipboard-item.tsx index 7a56f162de7178999f7f916b972460c5eccea341..738fe0d078473bcf660ca3f77c2386371dca3398 100644 --- a/packages/model-design/src/components/model-clipboard-item/model-clipboard-item.tsx +++ b/packages/model-design/src/components/model-clipboard-item/model-clipboard-item.tsx @@ -1,9 +1,11 @@ +/* eslint-disable no-continue */ /* eslint-disable no-await-in-loop */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable vue/no-mutating-props */ -import { PropType, defineComponent, onUnmounted, ref } from 'vue'; +import { PropType, computed, defineComponent, onUnmounted, ref } from 'vue'; import { useNamespace } from '@ibiz-template/vue3-util'; -import { ModelData } from '../../model'; +import { ClipboardData } from '../../model'; +import { IModelData } from '../../interface'; import './model-clipboard-item.scss'; export default defineComponent({ @@ -12,7 +14,7 @@ export default defineComponent({ context: { type: Object as PropType, default: () => ({}) }, params: { type: Object as PropType, default: () => ({}) }, data: { - type: Object as PropType, + type: Object as PropType, required: true, }, }, @@ -28,6 +30,18 @@ export default defineComponent({ const isDestroyed = ref(false); + const stateType = computed(() => { + let type: string = 'info'; + if (props.data.isError) { + type = 'danger'; + } else if (props.data.isExport) { + type = 'success'; + } else if (isLoading.value) { + type = 'warning'; + } + return type; + }); + // 初始化 const init = async () => { if (props.data && !props.data.isExport) { @@ -45,15 +59,15 @@ export default defineComponent({ entity.id!, ); const res = await service.exec( - 'ExportModelV2', + props.data.type === 'advanced' ? 'CopyModel' : 'ExportModelV2', { ...props.context, [entity.deapicodeName!]: item.srfkey, }, props.params, ); - if (res && res.status === 200 && res.data.model) { - props.data.models.push(res.data.model); + if (res && res.status === 200 && res.data) { + props.data.models.push(res.data as IModelData); } } catch (err: any) { props.data.isError = true; @@ -103,26 +117,19 @@ export default defineComponent({ return { ns, - modelClipboardController, + stateType, isLoading, + modelClipboardController, remove, copy, }; }, render() { - let type: string = 'info'; - if (this.data.isExport) { - type = 'success'; - } else if (this.data.isError) { - type = 'danger'; - } else if (this.isLoading) { - type = 'warning'; - } return ( diff --git a/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.scss b/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.scss new file mode 100644 index 0000000000000000000000000000000000000000..83800af308178809f3156898a4d2185e8dae0d4c --- /dev/null +++ b/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.scss @@ -0,0 +1,187 @@ +/* stylelint-disable selector-class-pattern */ +@include b(model-clipboard-paste-view) { + width: 100%; + height: 100%; + font-size: 14px; + line-height: 1.5; + color: getCssVar(color, text, 1); + + .el-card__header { + padding: 0 10px; + } + + .el-card__body { + height: calc(100% - 40px); + padding: 6px; + } + + .el-loading-mask { + color: var(--el-color-primary); + + svg { + fill: currentcolor; + } + } +} + +@include b(model-clipboard-paste-view-header) { + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; +} + +@include b(model-clipboard-paste-view-content) { + display: flex; + height: 100%; +} + +@include b(model-clipboard-paste-view-paste-warp) { + width: calc(100% - 200px); + padding: 0 6px 0 0; +} + +@include b(model-clipboard-paste-view-drop-area) { + position: relative; + height: 100px; + border: 1px solid getCssVar(color, border); + border-radius: 5px; +} + +@include b(model-clipboard-paste-view-drop-area-draggable) { + display: flex; + height: 100%; + padding: 6px; + overflow: auto; + + @include b(model-clipboard-paste-view-model-material-item) { + flex: 0 0 auto; + height: 100%; + padding: 3px 24px 3px 3px; + margin-right: 6px; + margin-bottom: 0; + + @include b(model-clipboard-paste-view-model-material-item-title) { + font-weight: normal; + } + } +} + +@include b(model-clipboard-paste-view-model-drag-item) { + position: relative; + flex: 0 0 auto; + height: 100%; + padding: 3px 24px 3px 3px; + margin-right: 6px; + border: 1px solid getCssVar(color, border); + border-radius: 5px; +} + +@include b(model-clipboard-paste-view-model-drag-item-actions) { + position: absolute; + top: 6px; + right: 5px; + display: flex; + align-items: center; +} + +@include b(model-clipboard-paste-view-model-drag-item-action-item) { + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + cursor: pointer; +} + +@include b(model-clipboard-paste-view-drop-tooltip) { + position: absolute; + top: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + font-size: 24px; + font-weight: 700; +} + +@include b(model-clipboard-paste-view-model-edit) { + position: relative; + height: calc(100% - 106px); + margin-top: 6px; + border: 1px solid getCssVar(color, border); + border-radius: 5px; + @include e(tabs) { + height: 100%; + .el-tabs__content { + height: calc(100% - 56px); + .el-tab-pane { + height: 100%; + } + } + } +} + +@include b(model-clipboard-paste-view-edit-actions) { + margin: 0 8px 0 5px; + position: absolute; + top: 8px; + right: 0; + .el-button { + --el-button-text-color: #{getCssVar(color, text, 3)}; + + + .el-button { + margin-left: 5px; + } + } +} + +@include b(model-clipboard-paste-view-model-material-warp) { + position: relative; + display: flex; + flex-direction: column; + flex-shrink: 0; + width: 200px; + border: 1px solid getCssVar(color, border); + border-radius: 5px; +} + +@include b(model-clipboard-paste-view-model-material-title) { + flex: 0 0 auto; + padding: 6px; + font-weight: 700; + text-align: center; + border-bottom: 1px solid getCssVar(color, border); +} + +@include b(model-clipboard-paste-view-model-material-content) { + flex: 1 1 0; + padding: 6px; + overflow: auto; +} + +@include b(model-clipboard-paste-view-model-material-item) { + padding: 3px 5px; + margin-bottom: 5px; + cursor: move; + border: 1px solid getCssVar(color, border); + border-radius: 5px; +} + +@include b(model-clipboard-paste-view-model-material-item-title) { + font-weight: 700; +} + +@include b(model-clipboard-paste-view-model-material-tooltip) { + position: absolute; + top: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + font-size: 24px; + font-weight: 700; +} diff --git a/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.tsx b/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3d32cc4827b22343e99d4e0a9f3ba4b17f45f2de --- /dev/null +++ b/packages/model-design/src/components/model-clipboard-paste-view/model-clipboard-paste-view.tsx @@ -0,0 +1,398 @@ +/* eslint-disable no-await-in-loop */ +import { IModal } from '@ibiz-template/runtime'; +import { PropType, computed, defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import draggable from 'vuedraggable'; +import { ClipboardData } from '../../model'; +import './model-clipboard-paste-view.scss'; + +export default defineComponent({ + name: 'IBizModelClipboardPasteView', + components: { + draggable, + }, + props: { + context: { type: Object as PropType, default: () => ({}) }, + params: { type: Object as PropType, default: () => ({}) }, + modal: { type: Object as PropType }, + }, + setup(props) { + const ns = useNamespace('model-clipboard-paste-view'); + // 模型粘贴板控制器 + const modelClipboardController = ibiz.modelClipboard; + // 模型导入视图控制器 + const c = ibiz.modelClipboardPasteView; + + // 当前激活分页 + const activeName = ref<'ModeTree' | 'JSON'>('ModeTree'); + + // 重置模型 + const initModel = (val: string) => { + c.state.modelStr = val; + c.state.isModelChange = false; + }; + + // 清除当前已计算模型 + const clearModel = async () => { + if (c.state.isModelChange) { + const result = await ibiz.confirm.warning({ + title: '确认清除?', + desc: '模型数据已变更,将同已拖入模型一同清空,确认清除吗?', + }); + if (result) { + c.clearAllData(); + } + } else { + c.clearAllData(); + } + }; + + // 关闭当前视图 + const closeView = async () => { + if (c.state.isModelChange) { + const result = await ibiz.confirm.warning({ + title: '确认关闭', + desc: '模型数据已变更,确认关闭吗?', + }); + if (result) { + props.modal?.dismiss(); + c.clearAllData(); + } + } else { + props.modal?.dismiss(); + } + }; + + // 格式化数据 + const formatData = () => { + try { + c.state.modelStr = JSON.stringify( + JSON.parse(c.state.modelStr), + null, + 2, + ); + } catch (error) { + ibiz.message.error('格式有误无法格式化,请检查!', 2.5); + } + }; + + // 模型编辑变更 + const modelChange = (val: string) => { + if (!Object.is(c.state.modelStr, val)) { + c.state.modelStr = val; + c.state.isModelChange = true; + } + }; + + // 计算模型文本内容 + const calcModeStr = () => { + ibiz.message.warning('编辑框内,模型内容已重新计算!'); + c.state.models = []; + c.state.items.forEach(item => { + c.state.models.push(...item.models); + }); + try { + initModel(JSON.stringify(c.state.models, null, 2)); + } catch (error) { + ibiz.log.error(error); + } + }; + + // 删除拖拽项 + const deleteDropItem = (i: number) => { + c.state.items.splice(i, 1); + calcModeStr(); + }; + + // 拖入模型变更 + const dropModelChange = async (e: IData) => { + if (c.state.isModelChange) { + const result = await ibiz.confirm.warning({ + title: '确认操作?', + desc: '检测到输入框内容已手动修改,该操作后输入框内容将重新计算,确认操作吗?', + }); + if (result) { + calcModeStr(); + } else if (e && e.added && e.added.newIndex != null) { + c.state.items.splice(e.added.newIndex, 1); + } + } else { + calcModeStr(); + } + }; + + // 素材项 + const materialItem = computed(() => { + return modelClipboardController.state.items.filter(item => { + if ( + item.type !== 'advanced' || + !Object.is(c.state.params.codeName, item.codeName) || + c.state.items.findIndex(self => Object.is(self.uuid, item.uuid)) !== + -1 + ) { + return false; + } + return item; + }); + }); + + // 保存模型 + const saveModel = async () => { + try { + const models = JSON.parse(c.state.modelStr); + if (Array.isArray(models)) { + c.state.models = models; + } else { + ibiz.message.error('模型数据格式异常,请检查!'); + return; + } + } catch (error) { + ibiz.message.error('模型数据格式异常,请检查!'); + return; + } + c.state.isLoading = true; + c.state.importItems = []; + let isError: boolean = false; + const entity = await ibiz.hub.getAppDataEntity( + c.state.params.codeName, + props.context.srfappid, + ); + const app = ibiz.hub.getApp(props.context.srfappid); + const service = await app.deService.getService(props.context, entity.id!); + for (let i = 0; i < c.state.models.length; i++) { + const item = c.state.models[i]; + const params: IParams = { + ...item, + srfmodelv2scope: props.context.srfmodelv2scope, + }; + try { + const res = await service.exec( + 'PasteModel', + props.context, + params, + props.params, + ); + if (res.ok && res.data) { + c.state.isModelChange = false; + c.state.importItems.push(item); + } else { + ibiz.message.error(res.statusText); + isError = true; + break; + } + } catch (err) { + const error = err as IData; + ibiz.message.error( + error?.message || + error?.data?.message || + error?.statusText || + ibiz.i18n.t('core.error.networkAbnormality'), + ); + isError = true; + break; + } + } + if (!isError) { + const result = await ibiz.confirm.warning({ + title: '关闭导入界面?', + desc: '已导入完毕,确认关闭导入界面!', + }); + if (result) { + props.modal?.dismiss(); + } + } + setTimeout(() => { + c.state.isLoading = false; + }, 300); + }; + + return { + c, + ns, + activeName, + materialItem, + modelClipboardController, + closeView, + clearModel, + formatData, + modelChange, + deleteDropItem, + dropModelChange, + saveModel, + }; + }, + render() { + const { isLoading, items, importItems, models, modelStr } = this.c.state; + return ( + + {{ + header: () => { + return ( +
+ 模型导入 + this.closeView()} + > + 关闭 + +
+ ); + }, + default: () => { + return ( +
+
+
+ this.dropModelChange(evt)} + > + {{ + item: ({ + element, + index, + }: { + element: ClipboardData; + index: number; + }) => { + const item = element; + + return ( +
+
+
this.deleteDropItem(index)} + > + +
+
+
+ {item.title} +
+
+ {item.createdDate} +
+
+ ); + }, + }} +
+
+ 请从右侧素材区拖入 +
+
+
+ + + + + + + this.modelChange(val) + } + > + + +
+ this.clearModel()} + > + 清空 + + this.formatData()} + > + 格式化 + + this.saveModel()} + > + 导入 + +
+
+
+
+
模型素材
+ + {{ + item: ({ element }: { element: ClipboardData }) => { + const item = element; + + return ( +
+
+ {item.title} +
+
+ {item.createdDate} +
+
+ ); + }, + }} +
+
+ 暂无数据 +
+
+
+ ); + }, + }} +
+ ); + }, +}); diff --git a/packages/model-design/src/components/model-clipboard/model-clipboard.scss b/packages/model-design/src/components/model-clipboard/model-clipboard.scss index cdf8266e70b0744eb91ea1015ff14887c19f6399..1d96d0e11ec35acc4f0e39f2eb9c6a2fe711a740 100644 --- a/packages/model-design/src/components/model-clipboard/model-clipboard.scss +++ b/packages/model-design/src/components/model-clipboard/model-clipboard.scss @@ -37,12 +37,4 @@ .el-timeline { padding: 0; } -} - -@include b(model-clipboard-drawer) { - .el-drawer__header { - .el-drawer__close-btn { - display: none; - } - } } \ No newline at end of file diff --git a/packages/model-design/src/components/model-clipboard/model-clipboard.tsx b/packages/model-design/src/components/model-clipboard/model-clipboard.tsx index fb5fb41876e8b5498fecb58ec58361085c84dadb..45515a0699ee1261c93a7b1f3a192fa3fe4dd513 100644 --- a/packages/model-design/src/components/model-clipboard/model-clipboard.tsx +++ b/packages/model-design/src/components/model-clipboard/model-clipboard.tsx @@ -9,6 +9,10 @@ export default defineComponent({ context: { type: Object as PropType, default: () => ({}) }, params: { type: Object as PropType, default: () => ({}) }, modal: { type: Object as PropType }, + type: { + type: String as PropType<'default' | 'advanced'>, + default: 'default', + }, }, setup(props) { const ns = useNamespace('model-clipboard'); @@ -23,7 +27,7 @@ export default defineComponent({ desc: '确认删除当前粘贴板的数据?', }); if (result) { - controller.clearAll(); + controller.clearAll(props.type); } } }; @@ -43,7 +47,9 @@ export default defineComponent({ return (
-
模型粘贴板
+
+ {`${this.type === 'advanced' ? '高级' : ''}模型粘贴板`} +
- {this.controller.state.items.map((item, i) => { - return ( - this.controller.removeByIndex(i)} - > - ); - })} + {this.controller.state.items + .filter(item => item.type === this.type) + .map(item => { + return ( + this.controller.removeById(item.uuid)} + > + ); + })}
diff --git a/packages/model-design/src/components/model-edit-view/model-edit-view.scss b/packages/model-design/src/components/model-edit-view/model-edit-view.scss index 5d1dd1b680e24f2f6a8090e07a15466a8b329019..67018bec5aa83ccd2964a6fb179069db07f98ccb 100644 --- a/packages/model-design/src/components/model-edit-view/model-edit-view.scss +++ b/packages/model-design/src/components/model-edit-view/model-edit-view.scss @@ -56,11 +56,3 @@ @include b(model-edit-view-content) { flex: 1 1 0; } - -@include b(model-edit-view-drawer) { - .el-drawer__header { - .el-drawer__close-btn { - display: none; - } - } -} diff --git a/packages/model-design/src/components/model-tree/interface/i-json-schema-field.ts b/packages/model-design/src/components/model-tree/interface/i-json-schema-field.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac3b007ecc69d78b160668ab9bc6d438dd4c634e --- /dev/null +++ b/packages/model-design/src/components/model-tree/interface/i-json-schema-field.ts @@ -0,0 +1,47 @@ +/** + * JsonScheam 属性接口 + * + * @export + * @interface IJsonScheamField + */ +export interface IJsonScheamField { + /** + * 类型 + * + * @type {string} + * @memberof IJsonScheamField + */ + type: string; + + /** + * 实体属性标识 + * + * @type {string} + * @memberof IJsonScheamField + */ + appDEFieldId: string; + + /** + * 属性名称 + * + * @type {string} + * @memberof IJsonScheamField + */ + caption: string; + + /** + * 代码表标识 + * + * @type {string} + * @memberof IJsonScheamField + */ + appCodeListId: string; + + /** + * 引用实体标识 + * + * @type {string} + * @memberof IJsonScheamField + */ + appDataEntityId?: string; +} diff --git a/packages/model-design/src/components/model-tree/interface/i-model-tree-node-data.ts b/packages/model-design/src/components/model-tree/interface/i-model-tree-node-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b739db421eaf276f34f07accb70b21b35d311c5 --- /dev/null +++ b/packages/model-design/src/components/model-tree/interface/i-model-tree-node-data.ts @@ -0,0 +1,105 @@ +import { IEditor } from '@ibiz/model-core'; + +/** + * 模型树节点数据接口 + * + * @export + * @interface IModelTreeNodeData + */ +export interface IModelTreeNodeData { + /** + * 数据唯一标识 + * + * @type {string} + * @memberof IModelTreeNodeData + */ + _id: string; + + /** + * 节点显示名称 + * + * @type {string} + * @memberof IModelTreeNodeData + */ + _text: string; + + /** + * 节点标签 + * + * @type {string} + * @memberof IModelTreeNodeData + */ + _label?: string; + + /** + * 节点类型 + * + * @type {('STATIC' | 'DE' | 'JSONSCHEMA')} (静态 | 应用实体 | JSONSCHEMA) + * @memberof IModelTreeNodeData + */ + _nodeType: 'STATIC' | 'DE' | 'JSONSCHEMA'; + + /** + * 节点图标 + * + * @type {('VARIABLE' | 'ARRAY' | 'OBJECT')}(变量 | 数组 | 对象) + * @memberof IModelTreeNodeData + */ + _icon: 'VARIABLE' | 'ARRAY' | 'OBJECT'; + + /** + * 是否为叶子节点 + * + * @type {boolean} + * @memberof IModelTreeNodeData + */ + _leaf: boolean; + + /** + * 原始数据 + * + * @type {IData | IData[]} + * @memberof IModelTreeNodeData + */ + _data: IData | IData[]; + + /** + * 节点的值 + * + * @type {(string | IData | IData[])} + * @memberof IModelTreeNodeData + */ + _value?: string | IData | IData[]; + + /** + * 编辑器 + * + * @type {IEditor} + * @memberof IModelTreeNodeData + */ + _editor?: IEditor; + + /** + * 节点关联实体标识 + * + * @type {string} + * @memberof IModelTreeNodeData + */ + _entityId?: string; + + /** + * 父节点数据 + * + * @type {IModelTreeNodeData} + * @memberof IModelTreeNodeData + */ + _parent?: IModelTreeNodeData; + + /** + * 子节点集合 + * + * @type {IModelTreeNodeData[]} + * @memberof IModelTreeNodeData + */ + _children?: IModelTreeNodeData[]; +} diff --git a/packages/model-design/src/components/model-tree/interface/i-model-tree-node-opt.ts b/packages/model-design/src/components/model-tree/interface/i-model-tree-node-opt.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbe91c103a2967d708ba3f7155060dd2ffe1d730 --- /dev/null +++ b/packages/model-design/src/components/model-tree/interface/i-model-tree-node-opt.ts @@ -0,0 +1,38 @@ +import { IEditor } from '@ibiz/model-core'; + +/** + * 模型树节点配置 + * + * @export + * @interface IModelTreeNodeOpt + */ +export interface IModelTreeNodeOpt { + /** + * 节点标识 + * + * @type {string} + * @memberof IModelTreeNodeOpt + */ + id: string; + /** + * 节点label + * + * @type {string} + * @memberof IModelTreeNodeOpt + */ + label?: string; + /** + * 节点关联实体标识 + * + * @type {string} + * @memberof IModelTreeNodeOpt + */ + entityId?: string; + /** + * 编辑器 + * + * @type {IEditor} + * @memberof IModelTreeNodeOpt + */ + editor?: IEditor; +} diff --git a/packages/model-design/src/components/model-tree/interface/index.ts b/packages/model-design/src/components/model-tree/interface/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b4f3b040314fb9cfc488d9cec5d7ef673265469 --- /dev/null +++ b/packages/model-design/src/components/model-tree/interface/index.ts @@ -0,0 +1,3 @@ +export type { IModelTreeNodeData } from './i-model-tree-node-data'; +export type { IJsonScheamField } from './i-json-schema-field'; +export type { IModelTreeNodeOpt } from './i-model-tree-node-opt'; diff --git a/packages/model-design/src/components/model-tree/model-tree.scss b/packages/model-design/src/components/model-tree/model-tree.scss new file mode 100644 index 0000000000000000000000000000000000000000..299e325a04065fa7c121c08516fdac66e8a4e396 --- /dev/null +++ b/packages/model-design/src/components/model-tree/model-tree.scss @@ -0,0 +1,40 @@ +@include b(model-tree) { + height: 100%; + width: 100%; + overflow: auto; + + .el-tree { + font-size: 16px; + } + + .el-tree-node__content { + height: 36px; + } + + @include e(node) { + display: flex; + align-items: center; + justify-content: center; + gap: getCssVar(spacing, tight); + + .icon { + width: 16px; + height: 16px; + fill: getCssVar(color, text-0); + } + + @include m(caption) { + display: flex; + align-items: center; + gap: getCssVar(spacing, tight); + min-width: 310px; + } + + @include m(editor) { + display: flex; + align-items: center; + justify-content: center; + gap: getCssVar(spacing, tight); + } + } +} diff --git a/packages/model-design/src/components/model-tree/model-tree.tsx b/packages/model-design/src/components/model-tree/model-tree.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ce78e673b1355e12734d00d87e159e0b1b074244 --- /dev/null +++ b/packages/model-design/src/components/model-tree/model-tree.tsx @@ -0,0 +1,209 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { useNamespace } from '@ibiz-template/vue3-util'; +import { recursiveIterate } from '@ibiz-template/core'; +import { PropType, defineComponent, ref, watch } from 'vue'; +import { createUUID } from 'qx-util'; +import { ModelTreeService } from './service'; +import { IModelTreeNodeData } from './interface'; +import { IModelData } from '../../interface'; +import { nodeIcon } from './svg/svg'; +import './model-tree.scss'; + +export default defineComponent({ + name: 'IBizModelTree', + props: { + context: { type: Object as PropType, default: () => ({}) }, + modelStr: { type: String, required: true }, + }, + emits: { + change: (_value: string) => true, + }, + setup(props, { emit }) { + const ns = useNamespace('model-tree'); + // 所有节点 + const nodes = ref([]); + // 根节点 + const rootNodes = ref([]); + // 树绑定的key + const UUID = ref(createUUID()); + // 模型数据 + const models = ref([]); + // 模型JSON + let modelStr = ''; + // 展开节点集合 + const expandedKeys = ref([]); + // 数据服务 + const service = new ModelTreeService(props.context, models); + + /** + * 根据节点id查找节点数据 + * + * @param {string} id + * @return {*} {(IModelTreeNodeData | undefined)} + */ + const findNodeData = (id: string): IModelTreeNodeData | undefined => { + return nodes.value.find(node => node._id === id); + }; + + /** + * 加载节点 + * + * @param {IModelTreeNodeData} [parent] + * @return {*} {Promise} + */ + const loadNodes = async ( + parent?: IModelTreeNodeData, + ): Promise => { + const children = await service.fetchChildNodes(parent); + if (parent) { + parent._children = children; + } else { + rootNodes.value = children; + } + nodes.value = []; + recursiveIterate( + { _children: rootNodes.value }, + (node: IModelTreeNodeData) => { + nodes.value.push(node); + }, + { childrenFields: ['_children'] }, + ); + return children; + }; + + /** + * 树加载 + * + * @param {IData} item + * @param {(_nodes: IModelTreeNodeData[]) => void} callback + */ + const load = async ( + item: IData, + callback: (_nodes: IModelTreeNodeData[]) => void, + ) => { + let children: IModelTreeNodeData[] = []; + const nodeData = findNodeData(item.data?._id); + if (nodeData?._children) { + children = nodeData._children; + } else { + children = await loadNodes(nodeData); + } + callback(children); + }; + + watch( + () => props.modelStr, + () => { + // 监听外部变更刷新树 + if (props.modelStr !== modelStr) { + try { + modelStr = props.modelStr; + models.value = JSON.parse(props.modelStr); + } catch (error) { + modelStr = ''; + models.value = []; + expandedKeys.value = []; + } finally { + UUID.value = createUUID(); + } + } + }, + { + immediate: true, + }, + ); + + /** + * 输入值变化 + * + * @param {string} _value + */ + const onInput = (_value: string) => { + modelStr = JSON.stringify(models.value, null, 2); + emit('change', modelStr); + }; + + /** + * 展开状态变更 + * + * @param {string} id + * @param {boolean} isExpand + */ + const onExpandChange = (id: string, isExpand: boolean) => { + const hasKey = expandedKeys.value.includes(id); + if (isExpand && !hasKey) { + expandedKeys.value.push(id); + } else if (!isExpand && hasKey) { + const index = expandedKeys.value.indexOf(id); + if (index !== -1) { + expandedKeys.value.splice(index, 1); + } + } + }; + + return { + ns, + UUID, + load, + expandedKeys, + onInput, + onExpandChange, + }; + }, + render() { + return ( +
+ { + this.onExpandChange(data._id, true); + }} + onNodeCollapse={(data: IModelTreeNodeData) => { + this.onExpandChange(data._id, false); + }} + default-expanded-keys={this.expandedKeys} + > + {{ + default: ({ data }: { node: IData; data: IModelTreeNodeData }) => { + return ( +
+ {nodeIcon(data._icon)} +
+
{data._text}
+
+ {data._label ? `#${data._label}` : ''} +
+
+ {data._icon !== 'OBJECT' ? ( +
+ + {`==>`} + + {data._icon === 'ARRAY' ? ( + {`[${data._value?.length}]`} + ) : ( + + )} +
+ ) : null} +
+ ); + }, + }} +
+
+ ); + }, +}); diff --git a/packages/model-design/src/components/model-tree/service/index.ts b/packages/model-design/src/components/model-tree/service/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a4174fa241ad87226a4f9d39635b597c48ac8b6 --- /dev/null +++ b/packages/model-design/src/components/model-tree/service/index.ts @@ -0,0 +1,2 @@ +export { ModelTreeService } from './model-tree.service'; +export { ModelTreeNodeData } from './model-tree-node-data'; diff --git a/packages/model-design/src/components/model-tree/service/model-tree-node-data.ts b/packages/model-design/src/components/model-tree/service/model-tree-node-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..e46c2cfc58da4cfd91d2763325e48e4cb23201da --- /dev/null +++ b/packages/model-design/src/components/model-tree/service/model-tree-node-data.ts @@ -0,0 +1,246 @@ +/* eslint-disable no-constructor-return */ +import { IEditor } from '@ibiz/model-core'; +import { IModelTreeNodeData, IModelTreeNodeOpt } from '../interface'; + +/** + * 模型树节点数据 + * + * @export + * @class ModelTreeNodeData + * @implements {IModelTreeNodeData} + */ +export class ModelTreeNodeData implements IModelTreeNodeData { + /** + * 数据唯一标识 + * + * @type {string} + * @memberof ModelTreeNodeData + */ + _id: string; + + /** + * 节点显示名称 + * + * @type {string} + * @memberof ModelTreeNodeData + */ + _text!: string; + + /** + * 节点类型 + * + * @type {('STATIC' | 'DE' | 'JSONSCHEMA')} + * @memberof ModelTreeNodeData + */ + _nodeType!: 'STATIC' | 'DE' | 'JSONSCHEMA'; + + /** + * 是否为叶子节点 + * + * @type {boolean} + * @memberof ModelTreeNodeData + */ + _leaf!: boolean; + + /** + * 原始数据 + * + * @type {(IData | IData[])} + * @memberof ModelTreeNodeData + */ + _data: IData | IData[]; + + /** + * 节点标签 + * + * @type {string} + * @memberof ModelTreeNodeData + */ + _label?: string; + + /** + * 节点图标 + * + * @type {('VARIABLE' | 'ARRAY' | 'OBJECT')} + * @memberof ModelTreeNodeData + */ + _icon: 'VARIABLE' | 'ARRAY' | 'OBJECT'; + + /** + * 节点的值 + * + * @type {(string | IData | IData[])} + * @memberof ModelTreeNodeData + */ + _value?: string | IData | IData[]; + + /** + * 编辑器 + * + * @type {IEditor} + * @memberof ModelTreeNodeData + */ + _editor?: IEditor; + + /** + * 节点关联实体标识 + * + * @type {string} + * @memberof ModelTreeNodeData + */ + _entityId?: string; + + /** + * 父节点 + * + * @type {IModelTreeNodeData} + * @memberof ModelTreeNodeData + */ + _parent?: IModelTreeNodeData; + + /** + * 子节点集合 + * + * @type {IModelTreeNodeData[]} + * @memberof ModelTreeNodeData + */ + _children?: IModelTreeNodeData[]; + + /** + * Creates an instance of ModelTreeNodeData. + * @param {(IData | IData[])} data + * @param {(IModelTreeNodeData | undefined)} prentNodeData + * @param {IModelTreeNodeOpt} opts + * @memberof ModelTreeNodeData + */ + constructor( + data: IData | IData[], + prentNodeData: IModelTreeNodeData | undefined, + opts: IModelTreeNodeOpt, + ) { + const { id, editor, label, entityId } = opts; + this._text = id; + this._data = data; + this._editor = editor; + this._parent = prentNodeData; + this._id = this._parent ? `${this._parent._id}.${id}` : `ROOT.${id}`; + + // value值 + Object.defineProperty(this, '_value', { + get() { + return this._data[this._text]; + }, + set(v) { + this._data[this._text] = v; + }, + enumerable: true, + configurable: true, + }); + + // leaf + Object.defineProperty(this, '_leaf', { + get() { + let isLeaf: boolean = true; + if ( + Array.isArray(this._value) || + (this._value !== null && typeof this._value === 'object') + ) { + isLeaf = false; + } + return isLeaf; + }, + set(_v) {}, + enumerable: true, + configurable: true, + }); + + // nodeType + Object.defineProperty(this, '_nodeType', { + get() { + let nodeType: 'STATIC' | 'DE' | 'JSONSCHEMA' = 'STATIC'; + switch (this._text) { + case 'model': + nodeType = 'JSONSCHEMA'; + break; + case 'requires': + case '_children': + nodeType = 'DE'; + break; + default: + if ( + this._parent?._nodeType === 'DE' || + this._parent?._nodeType === 'JSONSCHEMA' + ) { + nodeType = 'JSONSCHEMA'; + } + break; + } + return nodeType; + }, + set(_v) {}, + enumerable: true, + configurable: true, + }); + + this._label = this.calcLabel(label); + this._icon = this.calcIcon(); + this._entityId = + this._text === 'model' ? (this._data as IData).modeltype : entityId; + + return new Proxy(this, { + set(target, p, value): boolean { + target[p] = value; + return true; + }, + get(target, p, _receiver): unknown { + return target[p]; + }, + }); + } + + /** + * 计算节点标签 + * + * @protected + * @return {*} {string} + * @memberof ModelTreeNodeData + */ + protected calcLabel(_label?: string): string | undefined { + let label: string | undefined = ''; + switch (this._text) { + case 'modeltype': + label = '模型类型'; + break; + case 'model': + label = '当前模型'; + break; + case 'requires': + label = '关联模型'; + break; + case '_children': + label = '子模型'; + break; + default: + label = _label; + break; + } + return label; + } + + /** + * 计算节点数据类型 + * + * @protected + * @return {*} {('VARIABLE' | 'ARRAY' | 'OBJECT')} + * @memberof ModelTreeNodeData + */ + protected calcIcon(): 'VARIABLE' | 'ARRAY' | 'OBJECT' { + let icon: 'VARIABLE' | 'ARRAY' | 'OBJECT' = 'VARIABLE'; + if (Array.isArray(this._value)) { + icon = 'ARRAY'; + } else if (this._value !== null && typeof this._value === 'object') { + icon = 'OBJECT'; + } + return icon; + } +} diff --git a/packages/model-design/src/components/model-tree/service/model-tree.service.ts b/packages/model-design/src/components/model-tree/service/model-tree.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..918e0994a0793f9bd5e0511d41c657be2395127b --- /dev/null +++ b/packages/model-design/src/components/model-tree/service/model-tree.service.ts @@ -0,0 +1,204 @@ +/* eslint-disable no-empty-function */ +/* eslint-disable no-useless-constructor */ +/* eslint-disable no-return-await */ +import qs from 'qs'; +import { Ref } from 'vue'; +import { IModelData } from '../../../interface'; +import { + IModelTreeNodeData, + IJsonScheamField, + IModelTreeNodeOpt, +} from '../interface'; +import { ModelTreeNodeData } from './model-tree-node-data'; + +/** + * 模型树服务 + * + * @export + * @class ModelTreeService + */ +export class ModelTreeService { + /** + * jsonSchema 缓存map + * + * @private + * @type {Map}Map<实体标识, schema属性集合> + * @memberof ModelTreeService + */ + private jsonSchemaMap: Map = new Map(); + + /** + * Creates an instance of ModelTreeService. + * @param {IModelData[]} data + * @memberof ModelTreeService + */ + constructor( + private context: IContext, + private data: Ref, + ) {} + + /** + * 获取子节点数据 + * + * @return {*} {(ITreeNode[])} + * @memberof ModelTreeService + */ + async fetchChildNodes( + prentNodeData?: IModelTreeNodeData, + ): Promise { + let nodes: IModelTreeNodeData[] = []; + if (!prentNodeData) { + nodes = await this.getRootNodeData(); + } else { + nodes = await this.getChildrenNodeData(prentNodeData); + } + return nodes; + } + + /** + * 获取根节点数据 + * + * @return {*} {ITreeNode[]} + * @memberof ModelTreeService + */ + protected async getRootNodeData(): Promise { + return await Promise.all( + this.data.value.map((_item, index) => + this.getModelTreeNodeData(index, this.data.value), + ), + ); + } + + /** + * 获取子项数据 + * + * @param {IModelTreeNodeData} prentNodeData + * @return {*} {IModelTreeNodeData[]} + * @memberof ModelTreeService + */ + protected async getChildrenNodeData( + prentNodeData: IModelTreeNodeData, + ): Promise { + const data = prentNodeData._value; + let nodes: IModelTreeNodeData[] = []; + if (Array.isArray(data)) { + nodes = await Promise.all( + data.map((_item, index) => + this.getModelTreeNodeData(index, data, prentNodeData), + ), + ); + } else if (data !== null && typeof data === 'object') { + nodes = await Promise.all( + Object.keys(data).map(key => + this.getModelTreeNodeData(key, data, prentNodeData), + ), + ); + } + return nodes; + } + + /** + * 获取模型节点数据 + * + * @protected + * @param {(string | number)} key + * @param {(IData | IData[])} data + * @param {IModelTreeNodeData} [prentNodeData] + * @return {*} {Promise} + * @memberof ModelTreeService + */ + protected async getModelTreeNodeData( + key: string | number, + data: IData | IData[], + prentNodeData?: IModelTreeNodeData, + ): Promise { + const nodeType = prentNodeData?._nodeType || 'STATIC'; + const refEntityId = prentNodeData?._entityId; + const opt: IModelTreeNodeOpt = { + id: `${key}`, + }; + // 如果是JSONSCHEMA节点则根据引用实体获取JSONSCHEMA数据 + if (nodeType === 'JSONSCHEMA' && refEntityId) { + if (Array.isArray(data)) { + opt.entityId = refEntityId; + } else if (data !== null && typeof data === 'object') { + const jsonSchema = await this.loadJsonSchema(refEntityId, this.context); + const field = jsonSchema.find( + j => j.appDEFieldId.toLowerCase() === key, + ); + if (field) { + opt.label = field.caption; + opt.entityId = field.appDataEntityId; + } + } + } + // 如果是实体节点则加载实体JSON + if (nodeType === 'DE') { + const entity = await ibiz.hub.getAppDataEntity( + `${key}`, + this.context.srfappid, + ); + opt.entityId = `${key}`; + opt.label = entity.logicName; + } + return new ModelTreeNodeData(data, prentNodeData, opt); + } + + /** + * 加载JsonSchema + * + * @param {string} entityId + * @param {IContext} context + * @param {IParams} [params={}] + * @memberof ModelTreeService + */ + async loadJsonSchema( + entityId: string, + context: IContext, + params: IParams = {}, + ): Promise { + let result: IJsonScheamField[] = []; + try { + if (this.jsonSchemaMap.has(entityId)) { + result = this.jsonSchemaMap.get(entityId)!; + } else { + const strParams = qs.stringify(params); + const app = ibiz.hub.getApp(context.srfappid); + const entity = await ibiz.hub.getAppDataEntity( + entityId, + context.srfappid, + ); + let url = `/jsonschema/${entity.name}`; + if (entity.dynaSysMode === 0 && ibiz.appData) { + url += `?dynamodeltag=${ibiz.appData.dynamodeltag}${ + strParams ? `&${strParams}` : '' + }`; + } else { + url += `${strParams ? `?${strParams}` : ''}`; + } + const res = await app.net.get(url); + const { data } = res; + if (!data.properties || !(Object.keys(data.properties).length > 0)) { + return result; + } + Object.keys(data.properties).forEach(key => { + const item = data.properties[key]; + result.push({ + type: item.type, + appDEFieldId: key, + caption: item.description, + appCodeListId: item.enumSource, + appDataEntityId: + item.type === 'array' + ? item.items?.$ref?.split('.')[0] + : item.$ref?.split('.')[0], + }); + }); + this.jsonSchemaMap.set(entityId, result); + } + } catch (error) { + ibiz.log.error(error); + } + return result; + } +} diff --git a/packages/model-design/src/components/model-tree/svg/svg.tsx b/packages/model-design/src/components/model-tree/svg/svg.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dd41f8f12a47cc1160a4e31b997945d1797d64e5 --- /dev/null +++ b/packages/model-design/src/components/model-tree/svg/svg.tsx @@ -0,0 +1,58 @@ +// 变量 +const VARIABLE = ( + + + +); + +// 数组 +const ARRAY = ( + + + +); + +// 对象 +const OBJECT = ( + + + +); + +export function nodeIcon(icon: string) { + switch (icon) { + case 'ARRAY': + return ARRAY; + case 'OBJECT': + return OBJECT; + default: + return VARIABLE; + } +} diff --git a/packages/model-design/src/controller/index.ts b/packages/model-design/src/controller/index.ts index 77e7764aa9ade17ed772535b53e2c4834bc54a16..a44150b56d03810c24bad055a4d7d5ae41fe140d 100644 --- a/packages/model-design/src/controller/index.ts +++ b/packages/model-design/src/controller/index.ts @@ -1,2 +1,3 @@ export { modelClipboardController } from './model-clipboard-controller/model-clipboard-controller'; export { modelClipboardImportViewController } from './model-clipboard-import-view-controller/model-clipboard-import-view-controller'; +export { modelClipboardPasteViewController } from './model-clipboard-paste-view-controller/model-clipboard-paste-view-controller'; diff --git a/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-controller.ts b/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-controller.ts index 83a39235f6c0e8ea7e4dbd93b47b9d9be0f3df5f..754d36d37afa94bff6691f7df1cc374e4d707822 100644 --- a/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-controller.ts +++ b/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-controller.ts @@ -1,5 +1,5 @@ -import { IModelParams } from '../../interface'; -import { ModelData } from '../../model'; +import { IClipboardParams } from '../../interface'; +import { ClipboardData } from '../../model'; import { modelClipboardService } from '../../service'; import { ModelClipboardState } from './model-clipboard-state'; @@ -21,10 +21,10 @@ export class ModelClipboardController { * * @author zhanghengfeng * @date 2024-10-25 20:10:44 - * @param {IModelParams} data + * @param {IClipboardDataParams} data */ - createItem(data: IModelParams): void { - this.state.items.splice(0, 0, new ModelData(data)); + createItem(data: IClipboardParams): void { + this.state.items.splice(0, 0, new ClipboardData(data)); this.saveToStore(); } @@ -39,13 +39,13 @@ export class ModelClipboardController { } /** - * 根据下标删除数据 + * 根据唯一标识删除数据 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:35 - * @param {number} index + * @param {string} uuid + * @memberof ModelClipboardController */ - removeByIndex(index: number): void { + removeById(uuid: string): void { + const index = this.state.items.findIndex(item => item.uuid === uuid); this.state.items.splice(index, 1); this.saveToStore(); } @@ -53,12 +53,16 @@ export class ModelClipboardController { /** * 清除所有数据 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:52 + * @param {string} [type] 粘贴板数据类型 + * @memberof ModelClipboardController */ - clearAll(): void { - this.state.items = []; - modelClipboardService.clearModelDataItems(); + clearAll(type?: string): void { + this.state.items = this.state.items.filter(item => item.type !== type); + if (!this.state.items.length) { + modelClipboardService.clearModelDataItems(); + } else { + this.saveToStore(); + } } } diff --git a/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-state.ts b/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-state.ts index 1eedc5d9a675a6191967e857a1837133d76cf2d8..42c45ceb0fc5f2d1ed9a4f1d6d1179c77e945c7b 100644 --- a/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-state.ts +++ b/packages/model-design/src/controller/model-clipboard-controller/model-clipboard-state.ts @@ -1,4 +1,4 @@ -import { ModelData } from '../../model'; +import { ClipboardData } from '../../model'; export class ModelClipboardState { /** @@ -8,5 +8,13 @@ export class ModelClipboardState { * @date 2024-10-25 20:10:59 * @type {ModelData[]} */ - items: ModelData[] = []; + items: ClipboardData[] = []; + + /** + * 粘贴板类型 + * + * @type {('default' | 'advanced')}(默认 | 高级) + * @memberof ModelClipboardState + */ + type: 'default' | 'advanced' = 'default'; } diff --git a/packages/model-design/src/controller/model-clipboard-import-view-controller/model-clipboard-import-view-state.ts b/packages/model-design/src/controller/model-clipboard-import-view-controller/model-clipboard-import-view-state.ts index 66334ae3c8f2c7ac3f48ac8f0a660f3c1648d0b5..ded29f106568d8c8bef649521a68856d07491c8a 100644 --- a/packages/model-design/src/controller/model-clipboard-import-view-controller/model-clipboard-import-view-state.ts +++ b/packages/model-design/src/controller/model-clipboard-import-view-controller/model-clipboard-import-view-state.ts @@ -1,4 +1,4 @@ -import { ModelData } from '../../model'; +import { ClipboardData } from '../../model'; export class ModelClipboardImportViewState { /** @@ -11,13 +11,13 @@ export class ModelClipboardImportViewState { isLoading: boolean = false; /** - * 已拖入的模型 + * 已拖入的剪切板数据 * * @author zhanghengfeng * @date 2024-10-25 20:10:22 - * @type {ModelData[]} + * @type {ClipboardData[]} */ - items: ModelData[] = []; + items: ClipboardData[] = []; /** * 已导入的模型 @@ -29,7 +29,7 @@ export class ModelClipboardImportViewState { importItems: IData[] = []; /** - * 所有模型 + * 当前编辑的模型 * * @author zhanghengfeng * @date 2024-10-25 20:10:13 diff --git a/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-controller.ts b/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..1f6d06edba977642a30e30e9191e048e70209103 --- /dev/null +++ b/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-controller.ts @@ -0,0 +1,46 @@ +import { ModelClipboardPasteViewState } from './model-clipboard-paste-view-state'; + +export class ModelClipboardPasteViewController { + /** + * 模型粘贴视图状态 + * + * @author zhanghengfeng + * @date 2024-10-25 20:10:00 + */ + state = new ModelClipboardPasteViewState(); + + /** + * 更新参数 + * + * @author zhanghengfeng + * @date 2024-10-25 20:10:30 + * @param {IParams} params + */ + updateParams(params: IParams): void { + if ( + params && + !Object.is(JSON.stringify(params), JSON.stringify(this.state.params)) + ) { + this.state.params = params; + this.clearAllData(); + } + } + + /** + * 清理数据 + * + * @author zhanghengfeng + * @date 2024-10-25 20:10:09 + */ + clearAllData(): void { + this.state.isLoading = false; + this.state.items = []; + this.state.importItems = []; + this.state.models = []; + this.state.modelStr = ''; + this.state.isModelChange = false; + } +} + +export const modelClipboardPasteViewController = + new ModelClipboardPasteViewController(); diff --git a/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-state.ts b/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-state.ts new file mode 100644 index 0000000000000000000000000000000000000000..25b62027201ffe61ea608d5382c5e945f6bed0a7 --- /dev/null +++ b/packages/model-design/src/controller/model-clipboard-paste-view-controller/model-clipboard-paste-view-state.ts @@ -0,0 +1,60 @@ +import { IModelData } from '../../interface'; +import { ClipboardData } from '../../model'; + +export class ModelClipboardPasteViewState { + /** + * 是否正在加载 + * + * @type {boolean} + * @memberof ModelClipboardPasteViewState + */ + isLoading: boolean = false; + + /** + * 已拖入的剪切板数据 + * + * @type {ClipboardData[]} + * @memberof ModelClipboardPasteViewState + */ + items: ClipboardData[] = []; + + /** + * 已导入的模型 + * + * @type {IData[]} + * @memberof ModelClipboardPasteViewState + */ + importItems: IData[] = []; + + /** + * 当前编辑的模型 + * + * @type {IModelData[]} + * @memberof ModelClipboardPasteViewState + */ + models: IModelData[] = []; + + /** + * 模型json + * + * @type {string} + * @memberof ModelClipboardPasteViewState + */ + modelStr: string = ''; + + /** + * 是否在代码编辑器更改过模型json + * + * @type {boolean} + * @memberof ModelClipboardPasteViewState + */ + isModelChange: boolean = false; + + /** + * 参数 + * + * @type {IData} + * @memberof ModelClipboardPasteViewState + */ + params: IData = {}; +} diff --git a/packages/model-design/src/index.ts b/packages/model-design/src/index.ts index 3caecfa7046a7ac3f9fb35aa18f5e3c3f3c79dce..a4c14c39baef5afe6302d78f2c79816710881987 100644 --- a/packages/model-design/src/index.ts +++ b/packages/model-design/src/index.ts @@ -4,6 +4,7 @@ import UIAction from './ui-action'; import { modelClipboardController, modelClipboardImportViewController, + modelClipboardPasteViewController, } from './controller'; declare module '@ibiz-template/core' { @@ -25,6 +26,14 @@ declare module '@ibiz-template/core' { * @type {typeof modelClipboardImportViewController} */ modelClipboardImportView: typeof modelClipboardImportViewController; + + /** + * 模型粘贴视图控制器 + * + * @type {typeof modelClipboardPasteViewController} + * @memberof IBizSys + */ + modelClipboardPasteView: typeof modelClipboardPasteViewController; } } @@ -42,5 +51,11 @@ export default { ); ibiz.modelClipboardImportView = modelClipboardImportViewController; } + if (!ibiz.modelClipboardPasteView) { + modelClipboardPasteViewController.state = reactive( + modelClipboardPasteViewController.state, + ); + ibiz.modelClipboardPasteView = modelClipboardPasteViewController; + } }, }; diff --git a/packages/model-design/src/interface/i-model-params.ts b/packages/model-design/src/interface/i-clipboard-params.ts similarity index 40% rename from packages/model-design/src/interface/i-model-params.ts rename to packages/model-design/src/interface/i-clipboard-params.ts index d32a7952ad80167599582e8ed6d9ed91a2f134d9..33ae472682f0956689a06a239ffd5dfb934f75bb 100644 --- a/packages/model-design/src/interface/i-model-params.ts +++ b/packages/model-design/src/interface/i-clipboard-params.ts @@ -1,28 +1,40 @@ -export interface IModelParams { +/** + * 剪切板参数 + * + * @export + * @interface IClipboardParams + */ +export interface IClipboardParams { /** - * 呈现标题 + * 剪切板标题 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:41 * @type {string} + * @memberof IClipboardParams */ title: string; /** * 实体codeName * - * @author zhanghengfeng - * @date 2024-10-25 20:10:06 * @type {string} + * @memberof IClipboardParams */ codeName: string; /** - * 拷贝源数据 + * 剪切板源数据 * * @author zhanghengfeng * @date 2024-10-25 20:10:35 * @type {IData[]} */ items: IData[]; + + /** + * 类型 + * + * @type {('default' | 'advanced')}(默认 | 高级) + * @memberof IModelParams + */ + type?: 'default' | 'advanced'; } diff --git a/packages/model-design/src/interface/i-model-data.ts b/packages/model-design/src/interface/i-model-data.ts new file mode 100644 index 0000000000000000000000000000000000000000..52b89982e3e842b861571ab91393ef46b55349d7 --- /dev/null +++ b/packages/model-design/src/interface/i-model-data.ts @@ -0,0 +1,29 @@ +/** + * 模型数据 + * + * @export + * @interface IModelData + */ +export interface IModelData { + /** + * 当前模型实体 + * + * @type {string} + * @memberof ICopyModel + */ + modeltype: string; + /** + * 当前模型数据 + * + * @type {IData} + * @memberof ICopyModel + */ + model: IData; + /** + * 当前模型关联数据 + * + * @type {IData} + * @memberof ICopyModel + */ + requires: IData; +} diff --git a/packages/model-design/src/interface/index.ts b/packages/model-design/src/interface/index.ts index 4a3facff1054a668f7aa9e861f773f3916c421c9..d5101b29eebb50dd6208cac94342cf2dc609a685 100644 --- a/packages/model-design/src/interface/index.ts +++ b/packages/model-design/src/interface/index.ts @@ -1 +1,2 @@ -export type { IModelParams } from './i-model-params'; +export type { IClipboardParams } from './i-clipboard-params'; +export type { IModelData } from './i-model-data'; diff --git a/packages/model-design/src/model/model-data.ts b/packages/model-design/src/model/clipboard-data.ts similarity index 51% rename from packages/model-design/src/model/model-data.ts rename to packages/model-design/src/model/clipboard-data.ts index 21073a181defd0f842220d0132096fe28c2e0882..030cca5b728c1dab83097cc31c4a6f03084a7833 100644 --- a/packages/model-design/src/model/model-data.ts +++ b/packages/model-design/src/model/clipboard-data.ts @@ -1,91 +1,102 @@ import { createUUID } from 'qx-util'; -import { IModelParams } from '../interface'; +import { IClipboardParams, IModelData } from '../interface'; -export class ModelData { +/** + * 剪切板数据 + * + * @export + * @class ClipboardData + */ +export class ClipboardData { /** * 唯一标识 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:29 * @type {string} + * @memberof ClipboardData */ uuid: string = createUUID(); /** * 创建时间 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:43 * @type {string} + * @memberof ClipboardData */ createdDate: string = new Date().toLocaleString(undefined, { hour12: false }); /** * 呈现标题 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:57 * @type {string} + * @memberof ClipboardData */ title: string = ''; /** * 是否已经导出模型至当前数据 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:10 * @type {boolean} + * @memberof ClipboardData */ isExport: boolean = false; /** * 是否导出报错 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:20 * @type {boolean} + * @memberof ClipboardData */ isError: boolean = false; /** * 导出异常时的错误信息 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:47 * @type {string} + * @memberof ClipboardData */ errorInfo?: string; /** * 实体codeName * - * @author zhanghengfeng - * @date 2024-10-25 20:10:00 * @type {string} + * @memberof ClipboardData */ codeName: string; /** - * 拷贝源数据 + * 剪切板源数据 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:25 * @type {IData[]} + * @memberof ClipboardData */ items: IData[] = []; /** - * 导出的模型 + * 模型数据 * - * @author zhanghengfeng - * @date 2024-10-25 20:10:37 - * @type {IData[]} + * @type {IModelData[]} + * @memberof ClipboardData */ - models: IData[] = []; + models: IModelData[] = []; - constructor(data: IModelParams) { + /** + * 类型 + * + * @type {('default' | 'advanced')}(默认 | 高级) + * @memberof ModelData + */ + type: 'default' | 'advanced' = 'default'; + + /** + * Creates an instance of ModelData. + * @param {IClipboardParams} data + * @memberof ModelData + */ + constructor(data: IClipboardParams) { this.title = data.title; this.codeName = data.codeName; + this.type = data.type || 'default'; if (data.items) { this.items = data.items; } diff --git a/packages/model-design/src/model/index.ts b/packages/model-design/src/model/index.ts index 6ebf55777b47be61cc49011933d4f676475bca68..ff8249091c5237a2b9c55af3c85597fc81de375e 100644 --- a/packages/model-design/src/model/index.ts +++ b/packages/model-design/src/model/index.ts @@ -1 +1 @@ -export { ModelData } from './model-data'; +export { ClipboardData } from './clipboard-data'; diff --git a/packages/model-design/src/ui-action/index.ts b/packages/model-design/src/ui-action/index.ts index 5785863545964a11ec77bc64e9d9907696328a53..10b5d5babc69e7bcd93b0b89fc24f0eab1e653d9 100644 --- a/packages/model-design/src/ui-action/index.ts +++ b/packages/model-design/src/ui-action/index.ts @@ -2,6 +2,8 @@ import { registerUIActionProvider } from '@ibiz-template/runtime'; import { ModelExportProvider } from './model-export-provider'; import { ModelImportProvider } from './model-import-provider'; import { ModelEditProvider } from './model-edit-provider'; +import { ModelCopyProvider } from './model-copy-provider'; +import { ModelPasteProvider } from './model-paste-provider'; export default { install(): void { @@ -17,5 +19,12 @@ export default { ); const modelEditProvider = new ModelEditProvider(); registerUIActionProvider('DEUIACTION_MODEL_EDIT', () => modelEditProvider); + const modelCopyProvider = new ModelCopyProvider(); + registerUIActionProvider('DEUIACTION_MODEL_COPY', () => modelCopyProvider); + const modelPasteProvider = new ModelPasteProvider(); + registerUIActionProvider( + 'DEUIACTION_MODEL_PASTE', + () => modelPasteProvider, + ); }, }; diff --git a/packages/model-design/src/ui-action/model-copy-provider.ts b/packages/model-design/src/ui-action/model-copy-provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..024f596bb439b9192e21e4b525aa51fbd9eff1ba --- /dev/null +++ b/packages/model-design/src/ui-action/model-copy-provider.ts @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { h, resolveComponent } from 'vue'; +import { + IModal, + IUIActionResult, + IUILogicParams, + UIActionProviderBase, + convertNavData, + formatMultiData, +} from '@ibiz-template/runtime'; +import { IAppDEUIAction } from '@ibiz/model-core'; +import { notNilEmpty } from 'qx-util'; + +export class ModelCopyProvider extends UIActionProviderBase { + async execAction( + action: IAppDEUIAction, + args: IUILogicParams, + ): Promise { + const { context, params, data, view } = args; + if (!ibiz.modelClipboard) { + return {}; + } + if (!Array.isArray(data) || data.length === 0) { + return {}; + } + // 是否是多数据 + const isMultiData = + ['MULTIKEY', 'MULTIDATA'].includes(action.actionTarget!) && + data.length > 0; + // 处理自定义导航上下文 + const resultContext = context.clone(); + const navContexts = action.navigateContexts; + if (notNilEmpty(navContexts)) { + Object.assign( + resultContext, + convertNavData( + navContexts, + isMultiData ? formatMultiData(navContexts, data) : data[0] || {}, + params, + context, + ), + ); + } + // 处理自定义导航参数 + const resultParams = {}; + const navParams = action.navigateParams; + if (notNilEmpty(navParams)) { + Object.assign( + resultParams, + convertNavData( + navParams, + isMultiData ? formatMultiData(navParams, data) : data[0] || {}, + params, + context, + ), + ); + } + const entity = await ibiz.hub.getAppDataEntity( + view.model.appDataEntityId!, + view.model.appId, + ); + ibiz.modelClipboard.createItem({ + type: 'advanced', + title: `${entity.logicName}-${entity.codeName}`, + codeName: entity.codeName!, + items: data, + }); + const overlay = ibiz.overlay.createDrawer( + (modal: IModal) => { + return h(resolveComponent('IBizModelClipboard'), { + modal, + type: 'advanced', + context: resultContext, + params: resultParams, + }); + }, + undefined, + { placement: 'right', width: 500, showClose: false } as any, + ); + overlay.present(); + await overlay.onWillDismiss(); + return {}; + } +} diff --git a/packages/model-design/src/ui-action/model-edit-provider.ts b/packages/model-design/src/ui-action/model-edit-provider.ts index 8072d72a79c946741694e4fecb2ac0ca5c692cf5..307540939a626695ca39f10049c5661e2f2ad9b6 100644 --- a/packages/model-design/src/ui-action/model-edit-provider.ts +++ b/packages/model-design/src/ui-action/model-edit-provider.ts @@ -12,7 +12,6 @@ import { } from '@ibiz-template/runtime'; import { IAppDEUIAction } from '@ibiz/model-core'; import { createUUID, notNilEmpty } from 'qx-util'; -import { useNamespace } from '@ibiz-template/vue3-util'; export class ModelEditProvider extends UIActionProviderBase { async execAction( @@ -63,9 +62,6 @@ export class ModelEditProvider extends UIActionProviderBase { view.model.appDataEntityId!, view.model.appId, ); - const className = `${useNamespace('drawer').b()} ${useNamespace( - 'model-edit-view', - ).b('drawer')}`; const overlay = ibiz.overlay.createDrawer( (modal: IModal) => { return h(resolveComponent('IBizModelEditView'), { @@ -77,7 +73,7 @@ export class ModelEditProvider extends UIActionProviderBase { }); }, undefined, - { placement: 'top', class: className } as any, + { placement: 'top', showClose: false } as any, ); overlay.present(); const result: IModalData = await overlay.onWillDismiss(); diff --git a/packages/model-design/src/ui-action/model-export-provider.ts b/packages/model-design/src/ui-action/model-export-provider.ts index a6edfc86aaf8eb1f89b51e2867bd222cedacc416..edbe1c075da8eef26345323c2e4ac455ea752d2f 100644 --- a/packages/model-design/src/ui-action/model-export-provider.ts +++ b/packages/model-design/src/ui-action/model-export-provider.ts @@ -9,7 +9,6 @@ import { formatMultiData, } from '@ibiz-template/runtime'; import { IAppDEUIAction } from '@ibiz/model-core'; -import { useNamespace } from '@ibiz-template/vue3-util'; import { notNilEmpty } from 'qx-util'; export class ModelExportProvider extends UIActionProviderBase { @@ -65,9 +64,6 @@ export class ModelExportProvider extends UIActionProviderBase { codeName: entity.codeName!, items: data, }); - const className = `${useNamespace('drawer').b()} ${useNamespace( - 'model-clipboard', - ).b('drawer')}`; const overlay = ibiz.overlay.createDrawer( (modal: IModal) => { return h(resolveComponent('IBizModelClipboard'), { @@ -77,7 +73,7 @@ export class ModelExportProvider extends UIActionProviderBase { }); }, undefined, - { placement: 'right', width: 500, class: className } as any, + { placement: 'right', width: 500, showClose: false } as any, ); overlay.present(); await overlay.onWillDismiss(); diff --git a/packages/model-design/src/ui-action/model-import-provider.ts b/packages/model-design/src/ui-action/model-import-provider.ts index 1055cdc104ba53bae9bfbbb85ae5a6295ba02af4..2417f1706281f923aa5bcae84b04f4d91fd73777 100644 --- a/packages/model-design/src/ui-action/model-import-provider.ts +++ b/packages/model-design/src/ui-action/model-import-provider.ts @@ -9,7 +9,6 @@ import { formatMultiData, } from '@ibiz-template/runtime'; import { IAppDEUIAction } from '@ibiz/model-core'; -import { useNamespace } from '@ibiz-template/vue3-util'; import { notNilEmpty } from 'qx-util'; export class ModelImportProvider extends UIActionProviderBase { @@ -61,9 +60,6 @@ export class ModelImportProvider extends UIActionProviderBase { codeName: entity.codeName, srfmodelv2scope: resultContext.srfmodelv2scope, }); - const className = `${useNamespace('drawer').b()} ${useNamespace( - 'model-clipboard-import-view', - ).b('drawer')}`; const overlay = ibiz.overlay.createDrawer( (modal: IModal) => { return h(resolveComponent('IBizModelClipboardImportView'), { @@ -73,7 +69,7 @@ export class ModelImportProvider extends UIActionProviderBase { }); }, undefined, - { placement: 'top', class: className } as any, + { placement: 'top', showClose: false } as any, ); overlay.present(); await overlay.onWillDismiss(); diff --git a/packages/model-design/src/ui-action/model-paste-provider.ts b/packages/model-design/src/ui-action/model-paste-provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..88807a8c5ea64debfc0f6114be30517c4d549449 --- /dev/null +++ b/packages/model-design/src/ui-action/model-paste-provider.ts @@ -0,0 +1,78 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { h, resolveComponent } from 'vue'; +import { + IModal, + IUIActionResult, + IUILogicParams, + UIActionProviderBase, + convertNavData, + formatMultiData, +} from '@ibiz-template/runtime'; +import { IAppDEUIAction } from '@ibiz/model-core'; +import { notNilEmpty } from 'qx-util'; + +export class ModelPasteProvider extends UIActionProviderBase { + async execAction( + action: IAppDEUIAction, + args: IUILogicParams, + ): Promise { + const { context, params, data, view } = args; + if (!ibiz.modelClipboardImportView) { + return {}; + } + // 是否是多数据 + const isMultiData = + ['MULTIKEY', 'MULTIDATA'].includes(action.actionTarget!) && + data.length > 0; + // 处理自定义导航上下文 + const resultContext = context.clone(); + const navContexts = action.navigateContexts; + if (notNilEmpty(navContexts)) { + Object.assign( + resultContext, + convertNavData( + navContexts, + isMultiData ? formatMultiData(navContexts, data) : data[0] || {}, + params, + context, + ), + ); + } + // 处理自定义导航参数 + const resultParams = {}; + const navParams = action.navigateParams; + if (notNilEmpty(navParams)) { + Object.assign( + resultParams, + convertNavData( + navParams, + isMultiData ? formatMultiData(navParams, data) : data[0] || {}, + params, + context, + ), + ); + } + const entity = await ibiz.hub.getAppDataEntity( + view.model.appDataEntityId!, + view.model.appId, + ); + ibiz.modelClipboardPasteView.updateParams({ + codeName: entity.codeName, + srfmodelv2scope: resultContext.srfmodelv2scope, + }); + const overlay = ibiz.overlay.createDrawer( + (modal: IModal) => { + return h(resolveComponent('IBizModelClipboardPasteView'), { + modal, + context: resultContext, + params: resultParams, + }); + }, + undefined, + { placement: 'top', showClose: false } as any, + ); + overlay.present(); + await overlay.onWillDismiss(); + return { refresh: true, refreshMode: 1 }; + } +}