374 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			374 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <template>
 | |
|   <ai-detail class="dmAdd">
 | |
|     <ai-title slot="title" :title="pageTitle" isShowBottomBorder isShowBack @back="back"/>
 | |
|     <template #content>
 | |
|       <el-form size="small" :model="form" label-width="80px" :rules="rules" ref="DataModelForm">
 | |
|         <ai-card title="模型信息">
 | |
|           <div flex>
 | |
|             <el-form-item label="模型主体" prop="name" class="fill">
 | |
|               <ai-select v-model="form.name" :selectList="entries" placeholder="指定主表实体模型,更换主表会清空设计模型" filterable @select="initMainModel" :disabled="isEdit"/>
 | |
|             </el-form-item>
 | |
|             <el-form-item label="模型别名" prop="alias" class="fill mar-l16">
 | |
|               <el-input v-model="form.alias" clearable placeholder="用于更好的辨别关联模型,并通过别名可以索引该模型"/>
 | |
|             </el-form-item>
 | |
|           </div>
 | |
|         </ai-card>
 | |
|         <ai-card title="设计模型" panel>
 | |
|           <el-button slot="right" type="text" @click="fullscreen=true">全屏</el-button>
 | |
|           <el-form-item prop="config" label-width="0" class="diagram" :class="{fullscreen}">
 | |
|             <div ref="DataModel" class="dataModel"/>
 | |
|             <div class="dndPanel pad-8">
 | |
|               <div class="iconfont iconxinxiguanli pad-h8" v-text="`表实体`" @mousedown="handleAddModel"/>
 | |
|             </div>
 | |
|             <el-button class="fullscreenBtn" v-if="fullscreen" icon="el-icon-close" @click="fullscreen=false"/>
 | |
|           </el-form-item>
 | |
|         </ai-card>
 | |
|       </el-form>
 | |
|       <el-drawer :visible.sync="drawer" @close="current={}">
 | |
|         <div slot="title" v-text="`设置${current.name||'实体对象'}`"/>
 | |
|         <el-form class="pad-h16" :model="current" ref="ModelSettingForm" size="small" label-position="top">
 | |
|           <template v-if="current.type=='model'">
 | |
|             <el-form-item label="实体名称">
 | |
|               <ai-select v-model="current.name" :selectList="entries" placeholder="请选择实体对象" filterable @select="changeModel"/>
 | |
|             </el-form-item>
 | |
|             <el-form-item label="实体属性">
 | |
|               <ai-table :tableData="current.props" :colConfigs="currentCols" :is-show-pagination="false" tableSize="small" height="70vh"/>
 | |
|             </el-form-item>
 | |
|           </template>
 | |
|         </el-form>
 | |
|         <div class="footBar">
 | |
|           <el-button @click="drawer=false">取消</el-button>
 | |
|           <el-button type="primary" @click="handleSaveModel">保存</el-button>
 | |
|         </div>
 | |
|       </el-drawer>
 | |
|     </template>
 | |
|     <template #footer>
 | |
|       <el-button @click="back">返回</el-button>
 | |
|       <el-button type="primary" @click="submit">提交</el-button>
 | |
|     </template>
 | |
|   </ai-detail>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| import {mapActions} from "vuex";
 | |
| import {LogicFlow} from "@logicflow/core"
 | |
| import "@logicflow/core/dist/style/index.css"
 | |
| import {ModelElement} from "./element";
 | |
| 
 | |
| export default {
 | |
|   name: "dmAdd",
 | |
|   props: {
 | |
|     instance: Function,
 | |
|     dict: Object
 | |
|   },
 | |
|   data() {
 | |
|     return {
 | |
|       entries: [],
 | |
|       form: {},
 | |
|       rules: {
 | |
|         name: {required: true, message: "请选择主表模型"},
 | |
|       },
 | |
|       diagram: null,
 | |
|       drawer: false,
 | |
|       current: {},
 | |
|       currentCols: [
 | |
|         {label: "属性", prop: "field"},
 | |
|         {label: "属性名", prop: "fieldName"},
 | |
|       ],
 | |
|       fullscreen: false,
 | |
|     }
 | |
|   },
 | |
|   computed: {
 | |
|     isEdit: v => !!v.$route.query.id,
 | |
|     pageTitle: v => v.isEdit ? "编辑数据关联模型" : "新增数据关联模型",
 | |
|   },
 | |
|   methods: {
 | |
|     ...mapActions(['closePage']),
 | |
|     back() {
 | |
|       this.closePage()
 | |
|       this.$router.push({})
 | |
|     },
 | |
|     getDetail() {
 | |
|       const {id} = this.$route.query
 | |
|       id && this.instance.post("/app/appdatamodel/queryDetailById", null, {params: {id}}).then(res => {
 | |
|         if (res?.data) {
 | |
|           const config = JSON.parse(res.data.config || null)
 | |
|           this.form = {...res.data, config}
 | |
|           this.$load(this.diagram).then(() => {
 | |
|             this.diagram.render(config)
 | |
|             this.diagram.focusOn({id: this.form.name})
 | |
|           })
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     submit() {
 | |
|       const jsonData = this.diagram.getGraphData()
 | |
|       this.form.configs = jsonData.edges.map(e => e.properties)
 | |
|       this.$refs.DataModelForm.validate()
 | |
|       .then(() => this.instance.post("/app/appdatamodel/addOrUpdate", {...this.form, config: JSON.stringify(jsonData)}))
 | |
|       .then(res => {
 | |
|         if (res?.code == 0) {
 | |
|           this.$message.success("提交成功")
 | |
|           this.back()
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     initLf() {
 | |
|       this.diagram = new LogicFlow({
 | |
|         container: this.$refs.DataModel,
 | |
|         plugins: [ModelElement],
 | |
|         edgeType: 'bezier',
 | |
|         animation: true,
 | |
|         grid: true,
 | |
|         keyboard: {
 | |
|           enabled: true
 | |
|         },
 | |
|         style: {
 | |
|           bezier: {
 | |
|             stroke: "#666",
 | |
|             strokeWidth: 1
 | |
|           }
 | |
|         }
 | |
|       })
 | |
|       this.diagram.on('node:click', this.onNodeClick)
 | |
|       this.diagram.on('node:dnd-add', this.onNodeClick)
 | |
|       this.diagram.on('anchor:drop', ({edgeModel}) => {
 | |
|         const {sourceAnchorId, sourceNodeId, targetAnchorId, targetNodeId} = edgeModel
 | |
|         edgeModel.setProperties({
 | |
|           relationField: targetAnchorId.split("@").at(-1),
 | |
|           idField: sourceAnchorId.split("@").at(-1),
 | |
|           relationTableName: targetNodeId,
 | |
|           tableName: sourceNodeId
 | |
|         })
 | |
|       })
 | |
|       this.diagram.render()
 | |
|     },
 | |
|     handleAddModel() {
 | |
|       this.diagram.dnd.startDrag({type: "model"})
 | |
|     },
 | |
|     onNodeClick({data}) {
 | |
|       if (data.id != this.form.name) {
 | |
|         const {id: oldId, type} = data
 | |
|         this.current = this.$copy({props: [], ...data.properties, oldId, type})
 | |
|         this.drawer = true
 | |
|       }
 | |
|     },
 | |
|     handleSaveModel() {
 | |
|       let {id, oldId, props} = this.current
 | |
|       props = props?.filter(e => !!e.prop) || []
 | |
|       this.diagram.changeNodeId(oldId, id)
 | |
|       this.diagram.setProperties(id, this.current)
 | |
|       this.drawer = false
 | |
|     },
 | |
|     initMainModel(v) {
 | |
|       this.diagram.clearData()
 | |
|       if (!!v?.tableName) {
 | |
|         this.getTableFields(v).then(list => {
 | |
|           this.diagram.addNode({
 | |
|             type: 'main',
 | |
|             x: 200,
 | |
|             y: 60,
 | |
|             id: v.tableName,
 | |
|             properties: {id: v.tableName, props: list}
 | |
|           })
 | |
|           this.diagram.focusOn({id: v.tableName})
 | |
|         })
 | |
|       }
 | |
|     },
 | |
|     changeModel(v) {
 | |
|       if (!!v?.tableName) {
 | |
|         this.current.id = v.tableName
 | |
|         this.getTableFields(v).then((list = []) => this.current.props = list)
 | |
|       }
 | |
|     },
 | |
|     getEntries() {
 | |
|       this.instance.get("/app/v2/api-docs").then(res => {
 | |
|         if (res?.definitions) {
 | |
|           this.entries = Object.entries(res.definitions).filter(([id]) => id?.startsWith("App"))
 | |
|           ?.map(([id, e]) => ({...e, id, label: id, tableName: id.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase()})) || []
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     getTableFields(entry) {
 | |
|       const props = []
 | |
|       for (const i in entry.properties) {
 | |
|         props.push({field: i, fieldName: entry.properties[i]?.description})
 | |
|       }
 | |
|       return Promise.resolve(props)
 | |
|     }
 | |
|   },
 | |
|   created() {
 | |
|     this.getEntries()
 | |
|     this.getDetail()
 | |
|   },
 | |
|   mounted() {
 | |
|     this.initLf()
 | |
|   }
 | |
| }
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
| .dmAdd {
 | |
| 
 | |
| 
 | |
|   .diagram {
 | |
|     position: relative;
 | |
| 
 | |
|     .dataModel {
 | |
|       height: 400px;
 | |
|     }
 | |
| 
 | |
|     &.fullscreen {
 | |
|       position: fixed;
 | |
|       top: 0;
 | |
|       left: 0;
 | |
|       right: 0;
 | |
|       bottom: 0;
 | |
|       z-index: 1000;
 | |
| 
 | |
|       .dataModel {
 | |
|         height: 100vh;
 | |
|       }
 | |
| 
 | |
|       .dndPanel {
 | |
|         left: 16px;
 | |
|         top: 16px;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     .dndPanel {
 | |
|       position: absolute;
 | |
|       top: 0;
 | |
|       left: 0;
 | |
|       background-color: #fff;
 | |
|       border: 1px solid #eee;
 | |
| 
 | |
|       .iconfont {
 | |
|         font-size: 14px;
 | |
|         display: flex;
 | |
|         align-items: center;
 | |
|         flex-direction: column;
 | |
|         cursor: pointer;
 | |
|         user-select: none;
 | |
| 
 | |
|         &:before {
 | |
|           font-size: 40px;
 | |
|         }
 | |
| 
 | |
|         &:hover {
 | |
|           color: #26f;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     .fullscreenBtn {
 | |
|       position: absolute;
 | |
|       right: 16px;
 | |
|       top: 16px;
 | |
|     }
 | |
| 
 | |
|     :deep(.modelElement) {
 | |
|       border-radius: 4px;
 | |
|       font-size: 12px;
 | |
|       box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
 | |
|       background-color: #fff;
 | |
|       overflow: hidden;
 | |
|       min-width: 300px;
 | |
| 
 | |
|       & > b {
 | |
|         display: block;
 | |
|         width: 100%;
 | |
|         text-align: center;
 | |
|         line-height: 28px;
 | |
|         font-size: 14px;
 | |
|         background-color: #f5f5f5;
 | |
| 
 | |
|         &:before {
 | |
|           content: " ";
 | |
|           display: block;
 | |
|           width: 100%;
 | |
|           height: 4px;
 | |
|           background-color: #82b366;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.main > b:before {
 | |
|         background-color: #26f;
 | |
|       }
 | |
| 
 | |
|       .content {
 | |
|         min-height: 40px;
 | |
|         line-height: 20px;
 | |
| 
 | |
|         .w160 {
 | |
|           max-width: 160px;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     :deep(.custom-anchor) {
 | |
|       stroke: #999;
 | |
|       stroke-width: 1;
 | |
|       fill: #d9d9d9;
 | |
|       cursor: crosshair;
 | |
| 
 | |
|       &:hover {
 | |
|         fill: #26f;
 | |
|         stroke: #26f;
 | |
|       }
 | |
| 
 | |
|       &.incomming-anchor {
 | |
|         stroke: #d79b00;
 | |
|       }
 | |
| 
 | |
|       & .outgoing-anchor {
 | |
|         stroke: #82b366;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   :deep(.el-drawer) {
 | |
|     .el-drawer__header {
 | |
|       padding: 16px;
 | |
|       border-bottom: 1px solid #ddd;
 | |
|       margin-bottom: 16px;
 | |
|     }
 | |
| 
 | |
|     .el-drawer__body {
 | |
|       position: relative;
 | |
| 
 | |
|       .footBar {
 | |
|         position: absolute;
 | |
|         bottom: 0;
 | |
|         width: 100%;
 | |
|       }
 | |
| 
 | |
|       .el-form-item {
 | |
|         margin-bottom: 0;
 | |
| 
 | |
|         .el-form-item__label {
 | |
|           width: 100%;
 | |
|           font-weight: bold;
 | |
|           color: #333;
 | |
|           padding-bottom: 0;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   .footBar {
 | |
|     padding: 16px 20px;
 | |
|     box-sizing: border-box;
 | |
|     background: #F3F6F9;
 | |
|     display: flex;
 | |
|     align-items: center;
 | |
|     justify-content: center;
 | |
| 
 | |
|     .el-button {
 | |
|       width: 92px !important;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </style>
 |