数据模型完成
This commit is contained in:
		| @@ -2,18 +2,25 @@ | ||||
|   <ai-detail class="dmAdd"> | ||||
|     <ai-title slot="title" :title="pageTitle" isShowBottomBorder isShowBack @back="back"/> | ||||
|     <template #content> | ||||
|       <el-form size="small" :model="form" label-width="60px" :rules="rules" ref="DataModelForm"> | ||||
|       <el-form size="small" :model="form" label-width="80px" :rules="rules" ref="DataModelForm"> | ||||
|         <ai-card title="主表实体"> | ||||
|           <el-form-item label="主表" prop="tableName"> | ||||
|             <ai-select v-model="form.tableName" :selectList="entries" placeholder="指定主表实体模型,更换主表会清空设计模型" filterable @select="initMainModel" :disabled="isEdit"/> | ||||
|           </el-form-item> | ||||
|           <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-form-item prop="json" label-width="0" class="diagram"> | ||||
|           <el-button slot="right" type="text" @click="fullscreen=true">全屏</el-button> | ||||
|           <el-form-item prop="json" 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> | ||||
| @@ -25,12 +32,9 @@ | ||||
|               <ai-select v-model="current.id" :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"/> | ||||
|               <ai-table :tableData="current.props" :colConfigs="currentCols" :is-show-pagination="false" tableSize="small" height="70vh"/> | ||||
|             </el-form-item> | ||||
|           </template> | ||||
|           <template v-else-if="current.type=='polyline'"> | ||||
|  | ||||
|           </template> | ||||
|         </el-form> | ||||
|         <div class="footBar"> | ||||
|           <el-button @click="drawer=false">取消</el-button> | ||||
| @@ -62,19 +66,20 @@ export default { | ||||
|       entries: [], | ||||
|       form: {}, | ||||
|       rules: { | ||||
|         tableName: {required: true, message: "请选择主表模型"}, | ||||
|         name: {required: true, message: "请选择主表模型"}, | ||||
|       }, | ||||
|       diagram: null, | ||||
|       drawer: false, | ||||
|       current: {}, | ||||
|       currentCols: [ | ||||
|         {label: "属性", prop: "columnName"}, | ||||
|         {label: "属性名", prop: "columnComment"}, | ||||
|       ] | ||||
|         {label: "属性", prop: "field"}, | ||||
|         {label: "属性名", prop: "fieldName"}, | ||||
|       ], | ||||
|       fullscreen: false, | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     isEdit: v => !!v.$route.query.tableName, | ||||
|     isEdit: v => !!v.$route.query.name, | ||||
|     pageTitle: v => v.isEdit ? "编辑数据关联模型" : "新增数据关联模型", | ||||
|   }, | ||||
|   methods: { | ||||
| @@ -84,14 +89,14 @@ export default { | ||||
|       this.$router.push({}) | ||||
|     }, | ||||
|     getDetail() { | ||||
|       const {tableName} = this.$route.query | ||||
|       tableName && this.instance.get("/relation/list", {params: {tableName}}).then(res => { | ||||
|       const {id} = this.$route.query | ||||
|       id && this.instance.post("/app/appdatamodelconfig/queryDetailById", null, {params: {id}}).then(res => { | ||||
|         if (res?.data) { | ||||
|           const json = JSON.parse(res.data.json) | ||||
|           this.form = {...res.data, json, tableName} | ||||
|           this.form = {...res.data, json} | ||||
|           this.$load(this.diagram).then(() => { | ||||
|             this.diagram.render(json) | ||||
|             this.diagram.focusOn({id: tableName}) | ||||
|             this.diagram.focusOn({id: this.form.name}) | ||||
|           }) | ||||
|         } | ||||
|       }) | ||||
| @@ -100,9 +105,9 @@ export default { | ||||
|       const jsonData = this.diagram.getGraphData() | ||||
|       this.form.relationNodes = jsonData.edges.map(e => e.properties) | ||||
|       this.$refs.DataModelForm.validate() | ||||
|       .then(() => this.instance.post("/relation/setting", {...this.form, jsonData: JSON.stringify(jsonData)})) | ||||
|       .then(() => this.instance.post("/app/appdatamodelconfig/addOrUpdate", {...this.form, config: JSON.stringify(jsonData)})) | ||||
|       .then(res => { | ||||
|         if (res?.code == 200) { | ||||
|         if (res?.code == 0) { | ||||
|           this.$message.success("提交成功") | ||||
|           this.back() | ||||
|         } | ||||
| @@ -137,7 +142,7 @@ export default { | ||||
|       this.diagram.dnd.startDrag({type: "model"}) | ||||
|     }, | ||||
|     onNodeClick({data}) { | ||||
|       if (data.id != this.form.tableName) { | ||||
|       if (data.id != this.form.name) { | ||||
|         const {id: oldId, type} = data | ||||
|         this.current = this.$copy({props: [], ...data.properties, oldId, type}) | ||||
|         this.drawer = true | ||||
| @@ -153,7 +158,7 @@ export default { | ||||
|     initMainModel(v) { | ||||
|       this.diagram.clearData() | ||||
|       if (!!v?.id) { | ||||
|         this.getTableFields(v.id).then(list => { | ||||
|         this.getTableFields(v).then(list => { | ||||
|           this.diagram.addNode({ | ||||
|             type: 'main', | ||||
|             x: 200, | ||||
| @@ -163,28 +168,27 @@ export default { | ||||
|           }) | ||||
|           this.diagram.focusOn({id: v.id}) | ||||
|         }) | ||||
|  | ||||
|       } | ||||
|     }, | ||||
|     changeModel(v) { | ||||
|       if (!!v?.id) { | ||||
|         this.current.name = v.id | ||||
|         this.getTableFields(v.id).then((list = []) => this.current.props = list) | ||||
|         this.getTableFields(v).then((list = []) => this.current.props = list) | ||||
|       } | ||||
|     }, | ||||
|     getEntries() { | ||||
|       this.instance.get("/relation/table").then(res => { | ||||
|         if (res?.data) { | ||||
|           this.entries = res.data.map(e => ({...e, id: e.name, label: [e.entityName, e.des].filter(Boolean).join(" | ") || e.name})) | ||||
|       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})) || [] | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     getTableFields(tableName) { | ||||
|       return this.instance.get(`/relation/fields`, {params: {tableName}}).then(res => { | ||||
|         if (res?.data) { | ||||
|           return res.data | ||||
|         } | ||||
|       }) | ||||
|     getTableFields(entry) { | ||||
|       const props = [] | ||||
|       for (const i in entry.properties) { | ||||
|         props.push({field: i, fieldName: entry.properties[i]?.description}) | ||||
|       } | ||||
|       return Promise.resolve(props) | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
| @@ -199,13 +203,32 @@ export default { | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| .dmAdd { | ||||
|   .dataModel { | ||||
|     height: 400px; | ||||
|   } | ||||
|  | ||||
|  | ||||
|   .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; | ||||
| @@ -232,13 +255,19 @@ export default { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     .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: 200px; | ||||
|       min-width: 300px; | ||||
|  | ||||
|       & > b { | ||||
|         display: block; | ||||
| @@ -264,6 +293,10 @@ export default { | ||||
|       .content { | ||||
|         min-height: 40px; | ||||
|         line-height: 20px; | ||||
|  | ||||
|         .w160 { | ||||
|           max-width: 160px; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -13,8 +13,8 @@ | ||||
|       <ai-table :tableData="tableData" :isShowPagination="false" :col-configs="colConfigs" :dict="dict"> | ||||
|         <el-table-column slot="options" label="操作" fixed="right" align="center" width="200"> | ||||
|           <template slot-scope="{row}"> | ||||
|             <el-button type="text" @click="handleAdd(row.tableName)">编辑</el-button> | ||||
|             <el-button type="text" @click="handleDelete(row.tableName)">删除</el-button> | ||||
|             <el-button type="text" @click="handleAdd(row.id)">编辑</el-button> | ||||
|             <el-button type="text" @click="handleDelete(row.id)">删除</el-button> | ||||
|           </template> | ||||
|         </el-table-column> | ||||
|       </ai-table> | ||||
| @@ -55,14 +55,13 @@ export default { | ||||
|         } | ||||
|       }) | ||||
|     }, | ||||
|     handleAdd(tableName) { | ||||
|       tableName = tableName?.split(":").at(-1) | ||||
|       this.$router.push({hash: "#add", query: {tableName}}) | ||||
|     handleAdd(id) { | ||||
|       id && this.$router.push({hash: "#add", query: {id}}) | ||||
|     }, | ||||
|     @confirm("是否要删除该模型?") | ||||
|     handleDelete(tableName) { | ||||
|     handleDelete(ids) { | ||||
|       this.instance.post("/app/appdatamodelconfig/delete", null, { | ||||
|         params: {tableName} | ||||
|         params: {ids} | ||||
|       }).then(res => { | ||||
|         if (res?.code == 0) { | ||||
|           this.$message.success("删除成功") | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class DataView extends HtmlNode { | ||||
|     el.innerHTML = ` | ||||
|     <b>${id}</b> | ||||
|     <div class="content pad-8"> | ||||
|       ${props.map(e => `<div flex><b class="fill sherk">${e.columnName}</b><span class="mar-l8">${e.columnComment}</span></div>`).join('')} | ||||
|       ${props.map(e => `<div flex><b class="fill sherk">${e.field}</b><span class="mar-l8 w160 nowrap-text">${e.fieldName||"-"}</span></div>`).join('')} | ||||
|     </div>`; | ||||
|     rootEl.setAttribute('class', "pad-8") | ||||
|     rootEl.style.boxSizing = 'border-box' | ||||
| @@ -58,7 +58,7 @@ class DataModel extends HtmlNodeModel { | ||||
|       anchors.push({ | ||||
|         x: x - width / 2 + 6, | ||||
|         y: y - height / 2 + 58 + index * 20, | ||||
|         id: `${id}@${field.columnName}`, | ||||
|         id: `${id}@${field.field}`, | ||||
|         edgeAddable: false, | ||||
|         type: "left" | ||||
|       }); | ||||
| @@ -90,7 +90,7 @@ class MainModel extends DataModel { | ||||
|       anchors.push({ | ||||
|         x: x + width / 2 - 6, | ||||
|         y: y - height / 2 + 58 + index * 20, | ||||
|         id: `${id}@${field.columnName}`, | ||||
|         id: `${id}@${field.field}`, | ||||
|         type: "right", | ||||
|         field | ||||
|       }); | ||||
|   | ||||
| @@ -64,6 +64,15 @@ $--font-path: '~element-ui/lib/theme-chalk/fonts'; | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| .w100 { | ||||
|   width: 100%; | ||||
| } | ||||
|  | ||||
| .h100 { | ||||
|   height: 100%; | ||||
| } | ||||
|  | ||||
| /** | ||||
|   不换行文本 | ||||
|  */ | ||||
|   | ||||
| @@ -1,32 +1,30 @@ | ||||
| <template> | ||||
|   <div class="ai-select"> | ||||
|     <el-select | ||||
|         style="width: 100%;" | ||||
|         clearable | ||||
|         :value="value" | ||||
|         :size="$attrs.size || 'small'" | ||||
|         :filterable="isAction" | ||||
|         v-bind="$attrs" | ||||
|         v-on="$listeners"> | ||||
|     <el-select clearable class="w100" | ||||
|                :value="value" | ||||
|                :size="$attrs.size || 'small'" | ||||
|                :filterable="isAction||filterable" | ||||
|                :disabled="disabled" | ||||
|                v-bind="$attrs" | ||||
|                v-on="$listeners"> | ||||
|       <template v-if="isAction"> | ||||
|         <el-option v-for="op in actionOps" :key="op.id" | ||||
|                    :label="op[actionProp.label]" :value="op[actionProp.value]"/> | ||||
|         <el-option v-for="op in actionOps" :key="op.id" :label="op.label" :value="op[actionProp.value]"/> | ||||
|       </template> | ||||
|       <template v-else> | ||||
|         <el-option | ||||
|             v-for="(item, index) in selectList" | ||||
|             :key="index" | ||||
|             :label="item.dictName" | ||||
|             :value="item.dictValue"/> | ||||
|         <el-option v-for="(item, index) in ops" :key="index" | ||||
|                    :label="item.dictName||item[actionProp.label]" | ||||
|                    :value="item.dictValue>-1?item.dictValue:item[actionProp.value]" | ||||
|                    :disabled="check(item[actionProp.value])"/> | ||||
|       </template> | ||||
|     </el-select> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Dict from "../../lib/js/dict"; | ||||
|  | ||||
| export default { | ||||
|   name: 'AiSelect', | ||||
|  | ||||
|   model: { | ||||
|     prop: 'value', | ||||
|     event: 'change' | ||||
| @@ -38,9 +36,11 @@ export default { | ||||
|         v && this.isAction && !this.options.toString() && this.getOptions() | ||||
|       } | ||||
|     }, | ||||
|     action() { | ||||
|       this.isAction && this.getOptions() | ||||
|     }, | ||||
|     value(v) { | ||||
|       this.$emit("select", this.isAction ? this.options.find(e => e[this.actionProp.value] == v) : | ||||
|           this.selectList.find(e => e.dictValue == v)) | ||||
|       this.handleSelect(v) | ||||
|     } | ||||
|   }, | ||||
|   props: { | ||||
| @@ -48,35 +48,35 @@ export default { | ||||
|     selectList: { | ||||
|       type: Array | ||||
|     }, | ||||
|  | ||||
|     width: { | ||||
|       type: String, | ||||
|       default: '216' | ||||
|     }, | ||||
|     dict: String, | ||||
|     instance: Function, | ||||
|     action: {default: ""}, | ||||
|     prop: { | ||||
|       default: () => ({}) | ||||
|     } | ||||
|     }, | ||||
|     filterable: Boolean, | ||||
|     disabled: Boolean | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       options: [], | ||||
|       filter: "" | ||||
|       filter: "", | ||||
|       Dict | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     selectWidth() { | ||||
|       if (this.width.indexOf('px') > -1) { | ||||
|         return this.width | ||||
|       } | ||||
|       return `${this.width}px` | ||||
|     }, | ||||
|     isAction() { | ||||
|       return !!this.action | ||||
|     }, | ||||
|     isAction: v => !!v.action, | ||||
|     actionOps() { | ||||
|       return this.options.filter(e => !this.filter || e[this.actionProp.label].indexOf(this.filter) > -1) | ||||
|       const {filter} = this | ||||
|       const LABEL = this.actionProp.label | ||||
|       return this.options.map(e => ({ | ||||
|         ...e, | ||||
|         label: typeof LABEL == "function" ? LABEL?.(e) : e[LABEL] | ||||
|       })).filter(e => !filter || e.label.indexOf(filter) > -1) | ||||
|     }, | ||||
|     actionProp() { | ||||
|       return { | ||||
| @@ -84,7 +84,8 @@ export default { | ||||
|         value: 'id', | ||||
|         ...this.prop | ||||
|       } | ||||
|     } | ||||
|     }, | ||||
|     ops: v => v.dict ? v.Dict.getDict(v.dict) : v.selectList | ||||
|   }, | ||||
|   methods: { | ||||
|     getOptions() { | ||||
| @@ -94,7 +95,14 @@ export default { | ||||
|         if (res?.data) { | ||||
|           this.options = res.data.records || res.data | ||||
|         } | ||||
|       }) | ||||
|       }).then(() => this.handleSelect()) | ||||
|     }, | ||||
|     handleSelect(v = this.value) { | ||||
|       this.disabled || this.$emit("select", this.isAction ? this.options.find(e => e[this.actionProp.value] == v) : | ||||
|           this.ops.find(e => this.dict ? e.dictValue == v : e[this.actionProp.value] == v)) | ||||
|     }, | ||||
|     check(v) { | ||||
|       return this.actionProp.disabled && [this.actionProp.disabled].flat().includes(v) | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
| @@ -102,9 +110,3 @@ export default { | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| :deep( .ai-select .el-select ) { | ||||
|   width: 100%; | ||||
| } | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user