将 tool 合并到 infra 模块
This commit is contained in:
22
yudao-ui-admin/src/views/infra/build/App.vue
Normal file
22
yudao-ui-admin/src/views/infra/build/App.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
mounted() {
|
||||
// 取消开始的loading动画
|
||||
const preLoader = document.querySelector('#pre-loader')
|
||||
preLoader.style.display = 'none'
|
||||
|
||||
// fix: firefox 下 拖拽 会新打卡一个选项卡
|
||||
// https://github.com/JakHuang/form-generator/issues/15
|
||||
document.body.ondrop = event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
110
yudao-ui-admin/src/views/infra/build/CodeTypeDialog.vue
Normal file
110
yudao-ui-admin/src/views/infra/build/CodeTypeDialog.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-row :gutter="15">
|
||||
<el-form
|
||||
ref="elForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="生成类型" prop="type">
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in typeOptions"
|
||||
:key="index"
|
||||
:label="item.value"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showFileName" label="文件名" prop="fileName">
|
||||
<el-input v-model="formData.fileName" placeholder="请输入文件名" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handelConfirm">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: ['showFileName'],
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
fileName: undefined,
|
||||
type: 'file'
|
||||
},
|
||||
rules: {
|
||||
fileName: [{
|
||||
required: true,
|
||||
message: '请输入文件名',
|
||||
trigger: 'blur'
|
||||
}],
|
||||
type: [{
|
||||
required: true,
|
||||
message: '生成类型不能为空',
|
||||
trigger: 'change'
|
||||
}]
|
||||
},
|
||||
typeOptions: [{
|
||||
label: '页面',
|
||||
value: 'file'
|
||||
}, {
|
||||
label: '弹窗',
|
||||
value: 'dialog'
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onOpen() {
|
||||
if (this.showFileName) {
|
||||
this.formData.fileName = `${+new Date()}.vue`
|
||||
}
|
||||
},
|
||||
onClose() {
|
||||
},
|
||||
close(e) {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handelConfirm() {
|
||||
this.$refs.elForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.$emit('confirm', { ...this.formData })
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
120
yudao-ui-admin/src/views/infra/build/DraggableItem.vue
Normal file
120
yudao-ui-admin/src/views/infra/build/DraggableItem.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import render from '@/components/render/render'
|
||||
|
||||
const components = {
|
||||
itemBtns(h, currentItem, index, list) {
|
||||
const { copyItem, deleteItem } = this.$listeners
|
||||
return [
|
||||
<span class="drawing-item-copy" title="复制" onClick={event => {
|
||||
copyItem(currentItem, list); event.stopPropagation()
|
||||
}}>
|
||||
<i class="el-icon-copy-document" />
|
||||
</span>,
|
||||
<span class="drawing-item-delete" title="删除" onClick={event => {
|
||||
deleteItem(index, list); event.stopPropagation()
|
||||
}}>
|
||||
<i class="el-icon-delete" />
|
||||
</span>
|
||||
]
|
||||
}
|
||||
}
|
||||
const layouts = {
|
||||
colFormItem(h, currentItem, index, list) {
|
||||
const { activeItem } = this.$listeners
|
||||
const config = currentItem.__config__
|
||||
const child = renderChildren.apply(this, arguments)
|
||||
let className = this.activeId === config.formId ? 'drawing-item active-from-item' : 'drawing-item'
|
||||
if (this.formConf.unFocusedComponentBorder) className += ' unfocus-bordered'
|
||||
let labelWidth = config.labelWidth ? `${config.labelWidth}px` : null
|
||||
if (config.showLabel === false) labelWidth = '0'
|
||||
return (
|
||||
<el-col span={config.span} class={className}
|
||||
nativeOnClick={event => { activeItem(currentItem); event.stopPropagation() }}>
|
||||
<el-form-item label-width={labelWidth}
|
||||
label={config.showLabel ? config.label : ''} required={config.required}>
|
||||
<render key={config.renderKey} conf={currentItem} onInput={ event => {
|
||||
this.$set(config, 'defaultValue', event)
|
||||
}}>
|
||||
{child}
|
||||
</render>
|
||||
</el-form-item>
|
||||
{components.itemBtns.apply(this, arguments)}
|
||||
</el-col>
|
||||
)
|
||||
},
|
||||
rowFormItem(h, currentItem, index, list) {
|
||||
const { activeItem } = this.$listeners
|
||||
const config = currentItem.__config__
|
||||
const className = this.activeId === config.formId
|
||||
? 'drawing-row-item active-from-item'
|
||||
: 'drawing-row-item'
|
||||
let child = renderChildren.apply(this, arguments)
|
||||
if (currentItem.type === 'flex') {
|
||||
child = <el-row type={currentItem.type} justify={currentItem.justify} align={currentItem.align}>
|
||||
{child}
|
||||
</el-row>
|
||||
}
|
||||
return (
|
||||
<el-col span={config.span}>
|
||||
<el-row gutter={config.gutter} class={className}
|
||||
nativeOnClick={event => { activeItem(currentItem); event.stopPropagation() }}>
|
||||
<span class="component-name">{config.componentName}</span>
|
||||
<draggable list={config.children || []} animation={340}
|
||||
group="componentsGroup" class="drag-wrapper">
|
||||
{child}
|
||||
</draggable>
|
||||
{components.itemBtns.apply(this, arguments)}
|
||||
</el-row>
|
||||
</el-col>
|
||||
)
|
||||
},
|
||||
raw(h, currentItem, index, list) {
|
||||
const config = currentItem.__config__
|
||||
const child = renderChildren.apply(this, arguments)
|
||||
return <render key={config.renderKey} conf={currentItem} onInput={ event => {
|
||||
this.$set(config, 'defaultValue', event)
|
||||
}}>
|
||||
{child}
|
||||
</render>
|
||||
}
|
||||
}
|
||||
|
||||
function renderChildren(h, currentItem, index, list) {
|
||||
const config = currentItem.__config__
|
||||
if (!Array.isArray(config.children)) return null
|
||||
return config.children.map((el, i) => {
|
||||
const layout = layouts[el.__config__.layout]
|
||||
if (layout) {
|
||||
return layout.call(this, h, el, i, config.children)
|
||||
}
|
||||
return layoutIsNotFound.call(this)
|
||||
})
|
||||
}
|
||||
|
||||
function layoutIsNotFound() {
|
||||
throw new Error(`没有与${this.currentItem.__config__.layout}匹配的layout`)
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
render,
|
||||
draggable
|
||||
},
|
||||
props: [
|
||||
'currentItem',
|
||||
'index',
|
||||
'drawingList',
|
||||
'activeId',
|
||||
'formConf'
|
||||
],
|
||||
render(h) {
|
||||
const layout = layouts[this.currentItem.__config__.layout]
|
||||
|
||||
if (layout) {
|
||||
return layout.call(this, h, this.currentItem, this.index, this.drawingList)
|
||||
}
|
||||
return layoutIsNotFound.call(this)
|
||||
}
|
||||
}
|
||||
</script>
|
331
yudao-ui-admin/src/views/infra/build/FormDrawer.vue
Normal file
331
yudao-ui-admin/src/views/infra/build/FormDrawer.vue
Normal file
@@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
|
||||
<div style="height:100%">
|
||||
<el-row style="height:100%;overflow:auto">
|
||||
<el-col :md="24" :lg="12" class="left-editor">
|
||||
<div class="setting" title="资源引用" @click="showResource">
|
||||
<el-badge :is-dot="!!resources.length" class="item">
|
||||
<i class="el-icon-setting" />
|
||||
</el-badge>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab" type="card" class="editor-tabs">
|
||||
<el-tab-pane name="html">
|
||||
<span slot="label">
|
||||
<i v-if="activeTab==='html'" class="el-icon-edit" />
|
||||
<i v-else class="el-icon-document" />
|
||||
template
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="js">
|
||||
<span slot="label">
|
||||
<i v-if="activeTab==='js'" class="el-icon-edit" />
|
||||
<i v-else class="el-icon-document" />
|
||||
script
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="css">
|
||||
<span slot="label">
|
||||
<i v-if="activeTab==='css'" class="el-icon-edit" />
|
||||
<i v-else class="el-icon-document" />
|
||||
css
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-show="activeTab==='html'" id="editorHtml" class="tab-editor" />
|
||||
<div v-show="activeTab==='js'" id="editorJs" class="tab-editor" />
|
||||
<div v-show="activeTab==='css'" id="editorCss" class="tab-editor" />
|
||||
</el-col>
|
||||
<el-col :md="24" :lg="12" class="right-preview">
|
||||
<div class="action-bar" :style="{'text-align': 'left'}">
|
||||
<span class="bar-btn" @click="runCode">
|
||||
<i class="el-icon-refresh" />
|
||||
刷新
|
||||
</span>
|
||||
<span class="bar-btn" @click="exportFile">
|
||||
<i class="el-icon-download" />
|
||||
导出vue文件
|
||||
</span>
|
||||
<span ref="copyBtn" class="bar-btn copy-btn">
|
||||
<i class="el-icon-document-copy" />
|
||||
复制代码
|
||||
</span>
|
||||
<span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
|
||||
<i class="el-icon-circle-close" />
|
||||
关闭
|
||||
</span>
|
||||
</div>
|
||||
<iframe
|
||||
v-show="isIframeLoaded"
|
||||
ref="previewPage"
|
||||
class="result-wrapper"
|
||||
frameborder="0"
|
||||
src="preview.html"
|
||||
@load="iframeLoad"
|
||||
/>
|
||||
<div v-show="!isIframeLoaded" v-loading="true" class="result-wrapper" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-drawer>
|
||||
<resource-dialog
|
||||
:visible.sync="resourceVisible"
|
||||
:origin-resource="resources"
|
||||
@save="setResource"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { parse } from '@babel/parser'
|
||||
import ClipboardJS from 'clipboard'
|
||||
import { saveAs } from 'file-saver'
|
||||
import {
|
||||
makeUpHtml, vueTemplate, vueScript, cssStyle
|
||||
} from '@/components/generator/html'
|
||||
import { makeUpJs } from '@/components/generator/js'
|
||||
import { makeUpCss } from '@/components/generator/css'
|
||||
import { exportDefault, beautifierConf, titleCase } from '@/utils/index'
|
||||
import ResourceDialog from './ResourceDialog'
|
||||
import loadMonaco from '@/utils/loadMonaco'
|
||||
import loadBeautifier from '@/utils/loadBeautifier'
|
||||
|
||||
const editorObj = {
|
||||
html: null,
|
||||
js: null,
|
||||
css: null
|
||||
}
|
||||
const mode = {
|
||||
html: 'html',
|
||||
js: 'javascript',
|
||||
css: 'css'
|
||||
}
|
||||
let beautifier
|
||||
let monaco
|
||||
|
||||
export default {
|
||||
components: { ResourceDialog },
|
||||
props: ['formData', 'generateConf'],
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'html',
|
||||
htmlCode: '',
|
||||
jsCode: '',
|
||||
cssCode: '',
|
||||
codeFrame: '',
|
||||
isIframeLoaded: false,
|
||||
isInitcode: false, // 保证open后两个异步只执行一次runcode
|
||||
isRefreshCode: false, // 每次打开都需要重新刷新代码
|
||||
resourceVisible: false,
|
||||
scripts: [],
|
||||
links: [],
|
||||
monaco: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resources() {
|
||||
return this.scripts.concat(this.links)
|
||||
}
|
||||
},
|
||||
watch: {},
|
||||
created() {
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.preventDefaultSave)
|
||||
const clipboard = new ClipboardJS('.copy-btn', {
|
||||
text: trigger => {
|
||||
const codeStr = this.generateCode()
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '代码已复制到剪切板,可粘贴。',
|
||||
type: 'success'
|
||||
})
|
||||
return codeStr
|
||||
}
|
||||
})
|
||||
clipboard.on('error', e => {
|
||||
this.$message.error('代码复制失败')
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.preventDefaultSave)
|
||||
},
|
||||
methods: {
|
||||
preventDefaultSave(e) {
|
||||
if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
onOpen() {
|
||||
const { type } = this.generateConf
|
||||
this.htmlCode = makeUpHtml(this.formData, type)
|
||||
this.jsCode = makeUpJs(this.formData, type)
|
||||
this.cssCode = makeUpCss(this.formData)
|
||||
|
||||
loadBeautifier(btf => {
|
||||
beautifier = btf
|
||||
this.htmlCode = beautifier.html(this.htmlCode, beautifierConf.html)
|
||||
this.jsCode = beautifier.js(this.jsCode, beautifierConf.js)
|
||||
this.cssCode = beautifier.css(this.cssCode, beautifierConf.html)
|
||||
|
||||
loadMonaco(val => {
|
||||
monaco = val
|
||||
this.setEditorValue('editorHtml', 'html', this.htmlCode)
|
||||
this.setEditorValue('editorJs', 'js', this.jsCode)
|
||||
this.setEditorValue('editorCss', 'css', this.cssCode)
|
||||
if (!this.isInitcode) {
|
||||
this.isRefreshCode = true
|
||||
this.isIframeLoaded && (this.isInitcode = true) && this.runCode()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.isInitcode = false
|
||||
this.isRefreshCode = false
|
||||
},
|
||||
iframeLoad() {
|
||||
if (!this.isInitcode) {
|
||||
this.isIframeLoaded = true
|
||||
this.isRefreshCode && (this.isInitcode = true) && this.runCode()
|
||||
}
|
||||
},
|
||||
setEditorValue(id, type, codeStr) {
|
||||
if (editorObj[type]) {
|
||||
editorObj[type].setValue(codeStr)
|
||||
} else {
|
||||
editorObj[type] = monaco.editor.create(document.getElementById(id), {
|
||||
value: codeStr,
|
||||
theme: 'vs-dark',
|
||||
language: mode[type],
|
||||
automaticLayout: true
|
||||
})
|
||||
}
|
||||
// ctrl + s 刷新
|
||||
editorObj[type].onKeyDown(e => {
|
||||
if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
|
||||
this.runCode()
|
||||
}
|
||||
})
|
||||
},
|
||||
runCode() {
|
||||
const jsCodeStr = editorObj.js.getValue()
|
||||
try {
|
||||
const ast = parse(jsCodeStr, { sourceType: 'module' })
|
||||
const astBody = ast.program.body
|
||||
if (astBody.length > 1) {
|
||||
this.$confirm(
|
||||
'js格式不能识别,仅支持修改export default的对象内容',
|
||||
'提示',
|
||||
{
|
||||
type: 'warning'
|
||||
}).catch(() => {});
|
||||
return
|
||||
}
|
||||
if (astBody[0].type === 'ExportDefaultDeclaration') {
|
||||
const postData = {
|
||||
type: 'refreshFrame',
|
||||
data: {
|
||||
generateConf: this.generateConf,
|
||||
html: editorObj.html.getValue(),
|
||||
js: jsCodeStr.replace(exportDefault, ''),
|
||||
css: editorObj.css.getValue(),
|
||||
scripts: this.scripts,
|
||||
links: this.links
|
||||
}
|
||||
}
|
||||
|
||||
this.$refs.previewPage.contentWindow.postMessage(
|
||||
postData,
|
||||
location.origin
|
||||
)
|
||||
} else {
|
||||
this.$message.error('请使用export default')
|
||||
}
|
||||
} catch (err) {
|
||||
this.$message.error(`js错误:${err}`)
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
generateCode() {
|
||||
const html = vueTemplate(editorObj.html.getValue())
|
||||
const script = vueScript(editorObj.js.getValue())
|
||||
const css = cssStyle(editorObj.css.getValue())
|
||||
return beautifier.html(html + script + css, beautifierConf.html)
|
||||
},
|
||||
exportFile() {
|
||||
this.$prompt('文件名:', '导出文件', {
|
||||
inputValue: `${+new Date()}.vue`,
|
||||
closeOnClickModal: false,
|
||||
inputPlaceholder: '请输入文件名'
|
||||
}).then(({ value }) => {
|
||||
if (!value) value = `${+new Date()}.vue`
|
||||
const codeStr = this.generateCode()
|
||||
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
|
||||
saveAs(blob, value)
|
||||
})
|
||||
},
|
||||
showResource() {
|
||||
this.resourceVisible = true
|
||||
},
|
||||
setResource(arr) {
|
||||
const scripts = []; const
|
||||
links = []
|
||||
if (Array.isArray(arr)) {
|
||||
arr.forEach(item => {
|
||||
if (item.endsWith('.css')) {
|
||||
links.push(item)
|
||||
} else {
|
||||
scripts.push(item)
|
||||
}
|
||||
})
|
||||
this.scripts = scripts
|
||||
this.links = links
|
||||
} else {
|
||||
this.scripts = []
|
||||
this.links = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixin.scss';
|
||||
.tab-editor {
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.left-editor {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: #1e1e1e;
|
||||
overflow: hidden;
|
||||
}
|
||||
.setting{
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 3px;
|
||||
color: #a9f122;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
.right-preview {
|
||||
height: 100%;
|
||||
.result-wrapper {
|
||||
height: calc(100vh - 33px);
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
@include action-bar;
|
||||
::v-deep .el-drawer__header {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
123
yudao-ui-admin/src/views/infra/build/IconsDialog.vue
Normal file
123
yudao-ui-admin/src/views/infra/build/IconsDialog.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<div class="icon-dialog">
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
width="980px"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<div slot="title">
|
||||
选择图标
|
||||
<el-input
|
||||
v-model="key"
|
||||
size="mini"
|
||||
:style="{width: '260px'}"
|
||||
placeholder="请输入图标名称"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<ul class="icon-ul">
|
||||
<li
|
||||
v-for="icon in iconList"
|
||||
:key="icon"
|
||||
:class="active===icon?'active-item':''"
|
||||
@click="onSelect(icon)"
|
||||
>
|
||||
<i :class="icon" />
|
||||
<div>{{ icon }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import iconList from '@/utils/icon.json'
|
||||
|
||||
const originList = iconList.map(name => `el-icon-${name}`)
|
||||
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
props: ['current'],
|
||||
data() {
|
||||
return {
|
||||
iconList: originList,
|
||||
active: null,
|
||||
key: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
key(val) {
|
||||
if (val) {
|
||||
this.iconList = originList.filter(name => name.indexOf(val) > -1)
|
||||
} else {
|
||||
this.iconList = originList
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.active = this.current
|
||||
this.key = ''
|
||||
},
|
||||
onClose() {},
|
||||
onSelect(icon) {
|
||||
this.active = icon
|
||||
this.$emit('select', icon)
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.icon-ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
li {
|
||||
list-style-type: none;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
width: 16.66%;
|
||||
box-sizing: border-box;
|
||||
height: 108px;
|
||||
padding: 15px 6px 6px 6px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
background: #f2f2f2;
|
||||
}
|
||||
&.active-item{
|
||||
background: #e1f3fb;
|
||||
color: #7a6df0
|
||||
}
|
||||
> i {
|
||||
font-size: 30px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-dialog {
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 4vh !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 92vh;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
.el-dialog__header {
|
||||
padding-top: 14px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
margin: 0 20px 20px 20px;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
144
yudao-ui-admin/src/views/infra/build/JsonDrawer.vue
Normal file
144
yudao-ui-admin/src/views/infra/build/JsonDrawer.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-bind="$attrs" v-on="$listeners" @opened="onOpen" @close="onClose">
|
||||
<div class="action-bar" :style="{'text-align': 'left'}">
|
||||
<span class="bar-btn" @click="refresh">
|
||||
<i class="el-icon-refresh" />
|
||||
刷新
|
||||
</span>
|
||||
<span ref="copyBtn" class="bar-btn copy-json-btn">
|
||||
<i class="el-icon-document-copy" />
|
||||
复制JSON
|
||||
</span>
|
||||
<span class="bar-btn" @click="exportJsonFile">
|
||||
<i class="el-icon-download" />
|
||||
导出JSON文件
|
||||
</span>
|
||||
<span class="bar-btn delete-btn" @click="$emit('update:visible', false)">
|
||||
<i class="el-icon-circle-close" />
|
||||
关闭
|
||||
</span>
|
||||
</div>
|
||||
<div id="editorJson" class="json-editor" />
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { beautifierConf } from '@/utils/index'
|
||||
import ClipboardJS from 'clipboard'
|
||||
import { saveAs } from 'file-saver'
|
||||
import loadMonaco from '@/utils/loadMonaco'
|
||||
import loadBeautifier from '@/utils/loadBeautifier'
|
||||
|
||||
let beautifier
|
||||
let monaco
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
jsonStr: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.preventDefaultSave)
|
||||
const clipboard = new ClipboardJS('.copy-json-btn', {
|
||||
text: trigger => {
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '代码已复制到剪切板,可粘贴。',
|
||||
type: 'success'
|
||||
})
|
||||
return this.beautifierJson
|
||||
}
|
||||
})
|
||||
clipboard.on('error', e => {
|
||||
this.$message.error('代码复制失败')
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('keydown', this.preventDefaultSave)
|
||||
},
|
||||
methods: {
|
||||
preventDefaultSave(e) {
|
||||
if (e.key === 's' && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
onOpen() {
|
||||
loadBeautifier(btf => {
|
||||
beautifier = btf
|
||||
this.beautifierJson = beautifier.js(this.jsonStr, beautifierConf.js)
|
||||
|
||||
loadMonaco(val => {
|
||||
monaco = val
|
||||
this.setEditorValue('editorJson', this.beautifierJson)
|
||||
})
|
||||
})
|
||||
},
|
||||
onClose() {},
|
||||
setEditorValue(id, codeStr) {
|
||||
if (this.jsonEditor) {
|
||||
this.jsonEditor.setValue(codeStr)
|
||||
} else {
|
||||
this.jsonEditor = monaco.editor.create(document.getElementById(id), {
|
||||
value: codeStr,
|
||||
theme: 'vs-dark',
|
||||
language: 'json',
|
||||
automaticLayout: true
|
||||
})
|
||||
// ctrl + s 刷新
|
||||
this.jsonEditor.onKeyDown(e => {
|
||||
if (e.keyCode === 49 && (e.metaKey || e.ctrlKey)) {
|
||||
this.refresh()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
exportJsonFile() {
|
||||
this.$prompt('文件名:', '导出文件', {
|
||||
inputValue: `${+new Date()}.json`,
|
||||
closeOnClickModal: false,
|
||||
inputPlaceholder: '请输入文件名'
|
||||
}).then(({ value }) => {
|
||||
if (!value) value = `${+new Date()}.json`
|
||||
const codeStr = this.jsonEditor.getValue()
|
||||
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
|
||||
saveAs(blob, value)
|
||||
})
|
||||
},
|
||||
refresh() {
|
||||
try {
|
||||
this.$emit('refresh', JSON.parse(this.jsonEditor.getValue()))
|
||||
} catch (error) {
|
||||
this.$notify({
|
||||
title: '错误',
|
||||
message: 'JSON格式错误,请检查',
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixin.scss';
|
||||
|
||||
::v-deep .el-drawer__header {
|
||||
display: none;
|
||||
}
|
||||
@include action-bar;
|
||||
|
||||
.json-editor{
|
||||
height: calc(100vh - 33px);
|
||||
}
|
||||
</style>
|
0
yudao-ui-admin/src/views/infra/build/README.md
Normal file
0
yudao-ui-admin/src/views/infra/build/README.md
Normal file
116
yudao-ui-admin/src/views/infra/build/ResourceDialog.vue
Normal file
116
yudao-ui-admin/src/views/infra/build/ResourceDialog.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
title="外部资源引用"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-input
|
||||
v-for="(item, index) in resources"
|
||||
:key="index"
|
||||
v-model="resources[index]"
|
||||
class="url-item"
|
||||
placeholder="请输入 css 或 js 资源路径"
|
||||
prefix-icon="el-icon-link"
|
||||
clearable
|
||||
>
|
||||
<el-button
|
||||
slot="append"
|
||||
icon="el-icon-delete"
|
||||
@click="deleteOne(index)"
|
||||
/>
|
||||
</el-input>
|
||||
<el-button-group class="add-item">
|
||||
<el-button
|
||||
plain
|
||||
@click="addOne('https://lib.baomitu.com/jquery/1.8.3/jquery.min.js')"
|
||||
>
|
||||
jQuery1.8.3
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
@click="addOne('https://unpkg.com/http-vue-loader')"
|
||||
>
|
||||
http-vue-loader
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="el-icon-circle-plus-outline"
|
||||
plain
|
||||
@click="addOne('')"
|
||||
>
|
||||
添加其他
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
<div slot="footer">
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handelConfirm"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { deepClone } from '@/utils/index'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
inheritAttrs: false,
|
||||
props: ['originResource'],
|
||||
data() {
|
||||
return {
|
||||
resources: null
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.resources = this.originResource.length ? deepClone(this.originResource) : ['']
|
||||
},
|
||||
onClose() {
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handelConfirm() {
|
||||
const results = this.resources.filter(item => !!item) || []
|
||||
this.$emit('save', results)
|
||||
this.close()
|
||||
if (results.length) {
|
||||
this.resources = results
|
||||
}
|
||||
},
|
||||
deleteOne(index) {
|
||||
this.resources.splice(index, 1)
|
||||
},
|
||||
addOne(url) {
|
||||
if (this.resources.indexOf(url) > -1) {
|
||||
this.$message('资源已存在')
|
||||
} else {
|
||||
this.resources.push(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.add-item{
|
||||
margin-top: 8px;
|
||||
}
|
||||
.url-item{
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
1050
yudao-ui-admin/src/views/infra/build/RightPanel.vue
Normal file
1050
yudao-ui-admin/src/views/infra/build/RightPanel.vue
Normal file
File diff suppressed because it is too large
Load Diff
158
yudao-ui-admin/src/views/infra/build/TreeNodeDialog.vue
Normal file
158
yudao-ui-admin/src/views/infra/build/TreeNodeDialog.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-bind="$attrs"
|
||||
:close-on-click-modal="false"
|
||||
:modal-append-to-body="false"
|
||||
v-on="$listeners"
|
||||
@open="onOpen"
|
||||
@close="onClose"
|
||||
>
|
||||
<el-row :gutter="0">
|
||||
<el-form
|
||||
ref="elForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="small"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
label="选项名"
|
||||
prop="label"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.label"
|
||||
placeholder="请输入选项名"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item
|
||||
label="选项值"
|
||||
prop="value"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.value"
|
||||
placeholder="请输入选项值"
|
||||
clearable
|
||||
>
|
||||
<el-select
|
||||
slot="append"
|
||||
v-model="dataType"
|
||||
:style="{width: '100px'}"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in dataTypeOptions"
|
||||
:key="index"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="item.disabled"
|
||||
/>
|
||||
</el-select>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<div slot="footer">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handelConfirm"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
<el-button @click="close">
|
||||
取消
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { isNumberStr } from '@/utils/index'
|
||||
import { getTreeNodeId, saveTreeNodeId } from '@/utils/db'
|
||||
|
||||
const id = getTreeNodeId()
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
inheritAttrs: false,
|
||||
props: [],
|
||||
data() {
|
||||
return {
|
||||
id,
|
||||
formData: {
|
||||
label: undefined,
|
||||
value: undefined
|
||||
},
|
||||
rules: {
|
||||
label: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入选项名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入选项值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
dataType: 'string',
|
||||
dataTypeOptions: [
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string'
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: 'number'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
// eslint-disable-next-line func-names
|
||||
'formData.value': function (val) {
|
||||
this.dataType = isNumberStr(val) ? 'number' : 'string'
|
||||
},
|
||||
id(val) {
|
||||
saveTreeNodeId(val)
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.formData = {
|
||||
label: undefined,
|
||||
value: undefined
|
||||
}
|
||||
},
|
||||
onClose() {},
|
||||
close() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handelConfirm() {
|
||||
this.$refs.elForm.validate(valid => {
|
||||
if (!valid) return
|
||||
if (this.dataType === 'number') {
|
||||
this.formData.value = parseFloat(this.formData.value)
|
||||
}
|
||||
this.formData.id = this.id++
|
||||
this.$emit('commit', this.formData)
|
||||
this.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
469
yudao-ui-admin/src/views/infra/build/index.vue
Normal file
469
yudao-ui-admin/src/views/infra/build/index.vue
Normal file
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="left-board">
|
||||
<div class="logo-wrapper">
|
||||
<div class="logo">
|
||||
<img :src="logo" alt="logo"> Form Generator
|
||||
<a class="github" href="https://github.com/JakHuang/form-generator" target="_blank">
|
||||
<img src="https://github.githubassets.com/pinned-octocat.svg" alt>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar class="left-scrollbar">
|
||||
<div class="components-list">
|
||||
<div v-for="(item, listIndex) in leftComponents" :key="listIndex">
|
||||
<div class="components-title">
|
||||
<svg-icon icon-class="component" />
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<draggable
|
||||
class="components-draggable"
|
||||
:list="item.list"
|
||||
:group="{ name: 'componentsGroup', pull: 'clone', put: false }"
|
||||
:clone="cloneComponent"
|
||||
draggable=".components-item"
|
||||
:sort="false"
|
||||
@end="onEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(element, index) in item.list"
|
||||
:key="index"
|
||||
class="components-item"
|
||||
@click="addComponent(element)"
|
||||
>
|
||||
<div class="components-body">
|
||||
<svg-icon :icon-class="element.__config__.tagIcon" />
|
||||
{{ element.__config__.label }}
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<div class="center-board">
|
||||
<div class="action-bar">
|
||||
<el-button icon="el-icon-video-play" type="text" @click="run">
|
||||
运行
|
||||
</el-button>
|
||||
<el-button icon="el-icon-view" type="text" @click="showJson">
|
||||
查看json
|
||||
</el-button>
|
||||
<el-button icon="el-icon-download" type="text" @click="download">
|
||||
导出vue文件
|
||||
</el-button>
|
||||
<el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">
|
||||
复制代码
|
||||
</el-button>
|
||||
<el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty">
|
||||
清空
|
||||
</el-button>
|
||||
</div>
|
||||
<el-scrollbar class="center-scrollbar">
|
||||
<el-row class="center-board-row" :gutter="formConf.gutter">
|
||||
<el-form
|
||||
:size="formConf.size"
|
||||
:label-position="formConf.labelPosition"
|
||||
:disabled="formConf.disabled"
|
||||
:label-width="formConf.labelWidth + 'px'"
|
||||
>
|
||||
<draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup">
|
||||
<draggable-item
|
||||
v-for="(item, index) in drawingList"
|
||||
:key="item.renderKey"
|
||||
:drawing-list="drawingList"
|
||||
:current-item="item"
|
||||
:index="index"
|
||||
:active-id="activeId"
|
||||
:form-conf="formConf"
|
||||
@activeItem="activeFormItem"
|
||||
@copyItem="drawingItemCopy"
|
||||
@deleteItem="drawingItemDelete"
|
||||
/>
|
||||
</draggable>
|
||||
<div v-show="!drawingList.length" class="empty-info">
|
||||
从左侧拖入或点选组件进行表单设计
|
||||
</div>
|
||||
</el-form>
|
||||
</el-row>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<right-panel
|
||||
:active-data="activeData"
|
||||
:form-conf="formConf"
|
||||
:show-field="!!drawingList.length"
|
||||
@tag-change="tagChange"
|
||||
@fetch-data="fetchData"
|
||||
/>
|
||||
|
||||
<form-drawer
|
||||
:visible.sync="drawerVisible"
|
||||
:form-data="formData"
|
||||
size="100%"
|
||||
:generate-conf="generateConf"
|
||||
/>
|
||||
<json-drawer
|
||||
size="60%"
|
||||
:visible.sync="jsonDrawerVisible"
|
||||
:json-str="JSON.stringify(formData)"
|
||||
@refresh="refreshJson"
|
||||
/>
|
||||
<code-type-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
title="选择生成类型"
|
||||
:show-file-name="showFileName"
|
||||
@confirm="generate"
|
||||
/>
|
||||
<input id="copyNode" type="hidden">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable'
|
||||
import { debounce } from 'throttle-debounce'
|
||||
import { saveAs } from 'file-saver'
|
||||
import ClipboardJS from 'clipboard'
|
||||
import render from '@/components/render/render'
|
||||
import FormDrawer from './FormDrawer'
|
||||
import JsonDrawer from './JsonDrawer'
|
||||
import RightPanel from './RightPanel'
|
||||
import {
|
||||
inputComponents, selectComponents, layoutComponents, formConf
|
||||
} from '@/components/generator/config'
|
||||
import {
|
||||
exportDefault, beautifierConf, isNumberStr, titleCase, deepClone, isObjectObject
|
||||
} from '@/utils/index'
|
||||
import {
|
||||
makeUpHtml, vueTemplate, vueScript, cssStyle
|
||||
} from '@/components/generator/html'
|
||||
import { makeUpJs } from '@/components/generator/js'
|
||||
import { makeUpCss } from '@/components/generator/css'
|
||||
import drawingDefalut from '@/components/generator/drawingDefalut'
|
||||
import logo from '@/assets/logo/logo.png'
|
||||
import CodeTypeDialog from './CodeTypeDialog'
|
||||
import DraggableItem from './DraggableItem'
|
||||
import {
|
||||
getDrawingList, saveDrawingList, getIdGlobal, saveIdGlobal, getFormConf
|
||||
} from '@/utils/db'
|
||||
import loadBeautifier from '@/utils/loadBeautifier'
|
||||
|
||||
let beautifier
|
||||
const emptyActiveData = { style: {}, autosize: {} }
|
||||
let oldActiveId
|
||||
let tempActiveData
|
||||
const drawingListInDB = getDrawingList()
|
||||
const formConfInDB = getFormConf()
|
||||
const idGlobal = getIdGlobal()
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
render,
|
||||
FormDrawer,
|
||||
JsonDrawer,
|
||||
RightPanel,
|
||||
CodeTypeDialog,
|
||||
DraggableItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
logo,
|
||||
idGlobal,
|
||||
formConf,
|
||||
inputComponents,
|
||||
selectComponents,
|
||||
layoutComponents,
|
||||
labelWidth: 100,
|
||||
drawingList: drawingDefalut,
|
||||
drawingData: {},
|
||||
activeId: drawingDefalut[0].formId,
|
||||
drawerVisible: false,
|
||||
formData: {},
|
||||
dialogVisible: false,
|
||||
jsonDrawerVisible: false,
|
||||
generateConf: null,
|
||||
showFileName: false,
|
||||
activeData: drawingDefalut[0],
|
||||
saveDrawingListDebounce: debounce(340, saveDrawingList),
|
||||
saveIdGlobalDebounce: debounce(340, saveIdGlobal),
|
||||
leftComponents: [
|
||||
{
|
||||
title: '输入型组件',
|
||||
list: inputComponents
|
||||
},
|
||||
{
|
||||
title: '选择型组件',
|
||||
list: selectComponents
|
||||
},
|
||||
{
|
||||
title: '布局型组件',
|
||||
list: layoutComponents
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
watch: {
|
||||
// eslint-disable-next-line func-names
|
||||
'activeData.__config__.label': function (val, oldVal) {
|
||||
if (
|
||||
this.activeData.placeholder === undefined
|
||||
|| !this.activeData.__config__.tag
|
||||
|| oldActiveId !== this.activeId
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
|
||||
},
|
||||
activeId: {
|
||||
handler(val) {
|
||||
oldActiveId = val
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
drawingList: {
|
||||
handler(val) {
|
||||
this.saveDrawingListDebounce(val)
|
||||
if (val.length === 0) this.idGlobal = 100
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
idGlobal: {
|
||||
handler(val) {
|
||||
this.saveIdGlobalDebounce(val)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
|
||||
this.drawingList = drawingListInDB
|
||||
} else {
|
||||
this.drawingList = drawingDefalut
|
||||
}
|
||||
this.activeFormItem(this.drawingList[0])
|
||||
if (formConfInDB) {
|
||||
this.formConf = formConfInDB
|
||||
}
|
||||
loadBeautifier(btf => {
|
||||
beautifier = btf
|
||||
})
|
||||
const clipboard = new ClipboardJS('#copyNode', {
|
||||
text: trigger => {
|
||||
const codeStr = this.generateCode()
|
||||
this.$notify({
|
||||
title: '成功',
|
||||
message: '代码已复制到剪切板,可粘贴。',
|
||||
type: 'success'
|
||||
})
|
||||
return codeStr
|
||||
}
|
||||
})
|
||||
clipboard.on('error', e => {
|
||||
this.$message.error('代码复制失败')
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
setObjectValueReduce(obj, strKeys, data) {
|
||||
const arr = strKeys.split('.')
|
||||
arr.reduce((pre, item, i) => {
|
||||
if (arr.length === i + 1) {
|
||||
pre[item] = data
|
||||
} else if (!isObjectObject(pre[item])) {
|
||||
pre[item] = {}
|
||||
}
|
||||
return pre[item]
|
||||
}, obj)
|
||||
},
|
||||
setRespData(component, resp) {
|
||||
const { dataPath, renderKey, dataConsumer } = component.__config__
|
||||
if (!dataPath || !dataConsumer) return
|
||||
const respData = dataPath.split('.').reduce((pre, item) => pre[item], resp)
|
||||
|
||||
// 将请求回来的数据,赋值到指定属性。
|
||||
// 以el-tabel为例,根据Element文档,应该将数据赋值给el-tabel的data属性,所以dataConsumer的值应为'data';
|
||||
// 此时赋值代码可写成 component[dataConsumer] = respData;
|
||||
// 但为支持更深层级的赋值(如:dataConsumer的值为'options.data'),使用setObjectValueReduce
|
||||
this.setObjectValueReduce(component, dataConsumer, respData)
|
||||
const i = this.drawingList.findIndex(item => item.__config__.renderKey === renderKey)
|
||||
if (i > -1) this.$set(this.drawingList, i, component)
|
||||
},
|
||||
fetchData(component) {
|
||||
const { dataType, method, url } = component.__config__
|
||||
if (dataType === 'dynamic' && method && url) {
|
||||
this.setLoading(component, true)
|
||||
this.$axios({
|
||||
method,
|
||||
url
|
||||
}).then(resp => {
|
||||
this.setLoading(component, false)
|
||||
this.setRespData(component, resp.data)
|
||||
})
|
||||
}
|
||||
},
|
||||
setLoading(component, val) {
|
||||
const { directives } = component
|
||||
if (Array.isArray(directives)) {
|
||||
const t = directives.find(d => d.name === 'loading')
|
||||
if (t) t.value = val
|
||||
}
|
||||
},
|
||||
activeFormItem(currentItem) {
|
||||
this.activeData = currentItem
|
||||
this.activeId = currentItem.__config__.formId
|
||||
},
|
||||
onEnd(obj) {
|
||||
if (obj.from !== obj.to) {
|
||||
this.fetchData(tempActiveData)
|
||||
this.activeData = tempActiveData
|
||||
this.activeId = this.idGlobal
|
||||
}
|
||||
},
|
||||
addComponent(item) {
|
||||
const clone = this.cloneComponent(item)
|
||||
this.fetchData(clone)
|
||||
this.drawingList.push(clone)
|
||||
this.activeFormItem(clone)
|
||||
},
|
||||
cloneComponent(origin) {
|
||||
const clone = deepClone(origin)
|
||||
const config = clone.__config__
|
||||
config.span = this.formConf.span // 生成代码时,会根据span做精简判断
|
||||
this.createIdAndKey(clone)
|
||||
clone.placeholder !== undefined && (clone.placeholder += config.label)
|
||||
tempActiveData = clone
|
||||
return tempActiveData
|
||||
},
|
||||
createIdAndKey(item) {
|
||||
const config = item.__config__
|
||||
config.formId = ++this.idGlobal
|
||||
config.renderKey = `${config.formId}${+new Date()}` // 改变renderKey后可以实现强制更新组件
|
||||
if (config.layout === 'colFormItem') {
|
||||
item.__vModel__ = `field${this.idGlobal}`
|
||||
} else if (config.layout === 'rowFormItem') {
|
||||
config.componentName = `row${this.idGlobal}`
|
||||
!Array.isArray(config.children) && (config.children = [])
|
||||
delete config.label // rowFormItem无需配置label属性
|
||||
}
|
||||
if (Array.isArray(config.children)) {
|
||||
config.children = config.children.map(childItem => this.createIdAndKey(childItem))
|
||||
}
|
||||
return item
|
||||
},
|
||||
AssembleFormData() {
|
||||
this.formData = {
|
||||
fields: deepClone(this.drawingList),
|
||||
...this.formConf
|
||||
}
|
||||
},
|
||||
generate(data) {
|
||||
const func = this[`exec${titleCase(this.operationType)}`]
|
||||
this.generateConf = data
|
||||
func && func(data)
|
||||
},
|
||||
execRun(data) {
|
||||
this.AssembleFormData()
|
||||
this.drawerVisible = true
|
||||
},
|
||||
execDownload(data) {
|
||||
const codeStr = this.generateCode()
|
||||
const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
|
||||
saveAs(blob, data.fileName)
|
||||
},
|
||||
execCopy(data) {
|
||||
document.getElementById('copyNode').click()
|
||||
},
|
||||
empty() {
|
||||
this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
|
||||
() => {
|
||||
this.drawingList = []
|
||||
this.idGlobal = 100
|
||||
}).catch(() => {});
|
||||
},
|
||||
drawingItemCopy(item, list) {
|
||||
let clone = deepClone(item)
|
||||
clone = this.createIdAndKey(clone)
|
||||
list.push(clone)
|
||||
this.activeFormItem(clone)
|
||||
},
|
||||
drawingItemDelete(index, list) {
|
||||
list.splice(index, 1)
|
||||
this.$nextTick(() => {
|
||||
const len = this.drawingList.length
|
||||
if (len) {
|
||||
this.activeFormItem(this.drawingList[len - 1])
|
||||
}
|
||||
})
|
||||
},
|
||||
generateCode() {
|
||||
const { type } = this.generateConf
|
||||
this.AssembleFormData()
|
||||
const script = vueScript(makeUpJs(this.formData, type))
|
||||
const html = vueTemplate(makeUpHtml(this.formData, type))
|
||||
const css = cssStyle(makeUpCss(this.formData))
|
||||
return beautifier.html(html + script + css, beautifierConf.html)
|
||||
},
|
||||
showJson() {
|
||||
this.AssembleFormData()
|
||||
this.jsonDrawerVisible = true
|
||||
},
|
||||
download() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = true
|
||||
this.operationType = 'download'
|
||||
},
|
||||
run() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = false
|
||||
this.operationType = 'run'
|
||||
},
|
||||
copy() {
|
||||
this.dialogVisible = true
|
||||
this.showFileName = false
|
||||
this.operationType = 'copy'
|
||||
},
|
||||
tagChange(newTag) {
|
||||
newTag = this.cloneComponent(newTag)
|
||||
const config = newTag.__config__
|
||||
newTag.__vModel__ = this.activeData.__vModel__
|
||||
config.formId = this.activeId
|
||||
config.span = this.activeData.__config__.span
|
||||
this.activeData.__config__.tag = config.tag
|
||||
this.activeData.__config__.tagIcon = config.tagIcon
|
||||
this.activeData.__config__.document = config.document
|
||||
if (typeof this.activeData.__config__.defaultValue === typeof config.defaultValue) {
|
||||
config.defaultValue = this.activeData.__config__.defaultValue
|
||||
}
|
||||
Object.keys(newTag).forEach(key => {
|
||||
if (this.activeData[key] !== undefined) {
|
||||
newTag[key] = this.activeData[key]
|
||||
}
|
||||
})
|
||||
this.activeData = newTag
|
||||
this.updateDrawingList(newTag, this.drawingList)
|
||||
},
|
||||
updateDrawingList(newTag, list) {
|
||||
const index = list.findIndex(item => item.__config__.formId === this.activeId)
|
||||
if (index > -1) {
|
||||
list.splice(index, 1, newTag)
|
||||
} else {
|
||||
list.forEach(item => {
|
||||
if (Array.isArray(item.__config__.children)) this.updateDrawingList(newTag, item.__config__.children)
|
||||
})
|
||||
}
|
||||
},
|
||||
refreshJson(data) {
|
||||
this.drawingList = deepClone(data.fields)
|
||||
delete data.fields
|
||||
this.formConf = data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '@/styles/home';
|
||||
</style>
|
19
yudao-ui-admin/src/views/infra/build/main.js
Normal file
19
yudao-ui-admin/src/views/infra/build/main.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from '@/router'
|
||||
import '@/styles/index.scss'
|
||||
import '@/icons'
|
||||
import axios from 'axios'
|
||||
import Tinymce from '@/components/tinymce/index.vue'
|
||||
|
||||
Vue.component('tinymce', Tinymce)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$axios = axios
|
||||
|
||||
// add by 芋道源码:引用自 https://github.com/JakHuang/form-generator/tree/dev/src/views/index
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
61
yudao-ui-admin/src/views/infra/codegen/basicInfoForm.vue
Normal file
61
yudao-ui-admin/src/views/infra/codegen/basicInfoForm.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-form ref="basicInfoForm" :model="info" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input placeholder="请输入仓库名称" v-model="info.tableName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input placeholder="请输入" v-model="info.tableComment" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实体类名称" prop="className">
|
||||
<el-input placeholder="请输入" v-model="info.className" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input placeholder="请输入" v-model="info.author" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input type="textarea" :rows="3" v-model="info.remark"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "BasicInfoForm",
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
tableName: [
|
||||
{ required: true, message: "请输入表名称", trigger: "blur" }
|
||||
],
|
||||
tableComment: [
|
||||
{ required: true, message: "请输入表描述", trigger: "blur" }
|
||||
],
|
||||
className: [
|
||||
{ required: true, message: "请输入实体类名称", trigger: "blur" }
|
||||
],
|
||||
author: [
|
||||
{ required: true, message: "请输入作者", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
235
yudao-ui-admin/src/views/infra/codegen/editTable.vue
Normal file
235
yudao-ui-admin/src/views/infra/codegen/editTable.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<basic-info-form ref="basicInfo" :info="table" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="字段信息" name="cloum">
|
||||
<el-table ref="dragTable" :data="columns" row-key="columnId" :max-height="tableHeight">
|
||||
<el-table-column
|
||||
label="字段列名"
|
||||
prop="columnName"
|
||||
min-width="10%"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="字段描述" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.columnComment"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="物理类型"
|
||||
prop="columnType"
|
||||
min-width="10%"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column label="Java类型" min-width="11%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.javaType">
|
||||
<el-option label="Long" value="Long" />
|
||||
<el-option label="String" value="String" />
|
||||
<el-option label="Integer" value="Integer" />
|
||||
<el-option label="Double" value="Double" />
|
||||
<el-option label="BigDecimal" value="BigDecimal" />
|
||||
<el-option label="Date" value="Date" />
|
||||
<el-option label="Boolean" value="Boolean" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="java属性" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.javaField"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="插入" min-width="4%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.createOperation"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="编辑" min-width="4%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.updateOperation"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="列表" min-width="4%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperationResult"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询" min-width="4%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.listOperation"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="查询方式" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.listOperationCondition">
|
||||
<el-option label="=" value="=" />
|
||||
<el-option label="!=" value="!=" />
|
||||
<el-option label=">" value=">" />
|
||||
<el-option label=">=" value=">=" />
|
||||
<el-option label="<" value="<>" />
|
||||
<el-option label="<=" value="<=" />
|
||||
<el-option label="LIKE" value="LIKE" />
|
||||
<el-option label="BETWEEN" value="BETWEEN" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="允许空" min-width="5%">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox true-label="true" false-label="false" v-model="scope.row.nullable"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="显示类型" min-width="12%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.htmlType">
|
||||
<el-option label="文本框" value="input" />
|
||||
<el-option label="文本域" value="textarea" />
|
||||
<el-option label="下拉框" value="select" />
|
||||
<el-option label="单选框" value="radio" />
|
||||
<el-option label="复选框" value="checkbox" />
|
||||
<el-option label="日期控件" value="datetime" />
|
||||
<el-option label="图片上传" value="imageUpload" />
|
||||
<el-option label="文件上传" value="fileUpload" />
|
||||
<el-option label="富文本控件" value="editor" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="字典类型" min-width="12%">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in dictOptions"
|
||||
:key="dict.id"
|
||||
:label="dict.name"
|
||||
:value="dict.type"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="示例" min-width="10%">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.example"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="生成信息" name="genInfo">
|
||||
<gen-info-form ref="genInfo" :info="table" :tables="tables" :menus="menus"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item style="text-align: center;margin-left:-100px;margin-top:10px;">
|
||||
<el-button type="primary" @click="submitForm()">提交</el-button>
|
||||
<el-button @click="close()">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</template>
|
||||
<script>
|
||||
import { getCodegenDetail, updateCodegen } from "@/api/infra/codegen";
|
||||
import { listAllSimple as listAllSimpleDictType } from "@/api/system/dict/type";
|
||||
import { listSimpleMenus } from "@/api/system/menu";
|
||||
import basicInfoForm from "./basicInfoForm";
|
||||
import genInfoForm from "./genInfoForm";
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default {
|
||||
name: "GenEdit",
|
||||
components: {
|
||||
basicInfoForm,
|
||||
genInfoForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 选中选项卡的 name
|
||||
activeName: "cloum",
|
||||
// 表格的高度
|
||||
tableHeight: document.documentElement.scrollHeight - 245 + "px",
|
||||
// 表信息
|
||||
tables: [],
|
||||
// 表列信息
|
||||
columns: [],
|
||||
// 字典信息
|
||||
dictOptions: [],
|
||||
// 菜单信息
|
||||
menus: [],
|
||||
// 表详细信息
|
||||
table: {}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const tableId = this.$route.params && this.$route.params.tableId;
|
||||
if (tableId) {
|
||||
// 获取表详细信息
|
||||
getCodegenDetail(tableId).then(res => {
|
||||
this.table = res.data.table;
|
||||
this.columns = res.data.columns;
|
||||
});
|
||||
/** 查询字典下拉列表 */
|
||||
listAllSimpleDictType().then(response => {
|
||||
this.dictOptions = response.data;
|
||||
});
|
||||
/** 查询菜单下拉列表 */
|
||||
listSimpleMenus().then(response => {
|
||||
this.menus = [];
|
||||
this.menus.push(...this.handleTree(response.data, "id"));
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
const basicForm = this.$refs.basicInfo.$refs.basicInfoForm;
|
||||
const genForm = this.$refs.genInfo.$refs.genInfoForm;
|
||||
Promise.all([basicForm, genForm].map(this.getFormPromise)).then(res => {
|
||||
const validateResult = res.every(item => !!item);
|
||||
if (validateResult) {
|
||||
const genTable = {};
|
||||
genTable.table = Object.assign({}, basicForm.model, genForm.model);
|
||||
genTable.columns = this.columns;
|
||||
genTable.params = {
|
||||
treeCode: genTable.treeCode,
|
||||
treeName: genTable.treeName,
|
||||
treeParentCode: genTable.treeParentCode,
|
||||
parentMenuId: genTable.parentMenuId
|
||||
};
|
||||
updateCodegen(genTable).then(res => {
|
||||
this.$modal.msgSuccess("修改成功!");
|
||||
this.close();
|
||||
});
|
||||
} else {
|
||||
this.$modal.msgError("表单校验未通过,请重新检查提交内容");
|
||||
}
|
||||
});
|
||||
},
|
||||
getFormPromise(form) {
|
||||
return new Promise(resolve => {
|
||||
form.validate(res => {
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
},
|
||||
/** 关闭按钮 */
|
||||
close() {
|
||||
this.$tab.closeOpenPage({
|
||||
path: "/infra/codegen",
|
||||
query: { t: Date.now(), pageNum: this.$route.query.pageNum } }
|
||||
);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const el = this.$refs.dragTable.$el.querySelectorAll(".el-table__body-wrapper > table > tbody")[0];
|
||||
const sortable = Sortable.create(el, {
|
||||
handle: ".allowDrag",
|
||||
onEnd: evt => {
|
||||
const targetRow = this.columns.splice(evt.oldIndex, 1)[0];
|
||||
this.columns.splice(evt.newIndex, 0, targetRow);
|
||||
for (let index in this.columns) {
|
||||
this.columns[index].sort = parseInt(index) + 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
327
yudao-ui-admin/src/views/infra/codegen/genInfoForm.vue
Normal file
327
yudao-ui-admin/src/views/infra/codegen/genInfoForm.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<el-form ref="genInfoForm" :model="info" :rules="rules" label-width="150px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="templateType">
|
||||
<span slot="label">生成模板</span>
|
||||
<el-select v-model="info.templateType" @change="tplSelectChange">
|
||||
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)"
|
||||
:key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="scene">
|
||||
<span slot="label">生成场景</span>
|
||||
<el-select v-model="info.scene">
|
||||
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_CODEGEN_SCENE)"
|
||||
:key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="packageName">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 生成包路径-->
|
||||
<!-- <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input v-model="info.packageName" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="moduleName">
|
||||
<span slot="label">
|
||||
模块名
|
||||
<el-tooltip content="模块名,即一级目录,例如 system、infra、tool 等等" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.moduleName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="businessName">
|
||||
<span slot="label">
|
||||
业务名
|
||||
<el-tooltip content="业务名,即二级目录,例如 user、permission、dict 等等" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.businessName" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">-->
|
||||
<!-- <el-form-item prop="businessPackage">-->
|
||||
<!-- <span slot="label">-->
|
||||
<!-- 业务包-->
|
||||
<!-- <el-tooltip content="业务包,自定义二级目录。例如说,我们希望将 dictType 和 dictData 归类成 dict 业务" placement="top">-->
|
||||
<!-- <i class="el-icon-question"></i>-->
|
||||
<!-- </el-tooltip>-->
|
||||
<!-- </span>-->
|
||||
<!-- <el-input v-model="info.businessPackage" />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="className">
|
||||
<span slot="label">
|
||||
类名称
|
||||
<el-tooltip content="类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.className" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="classComment">
|
||||
<span slot="label">
|
||||
类描述
|
||||
<el-tooltip content="用作类描述,例如 用户" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.classComment" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
上级菜单
|
||||
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<treeselect :append-to-body="true" v-model="info.parentMenuId" :options="menus"
|
||||
:normalizer="normalizer" :show-count="true" placeholder="请选择系统菜单" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" v-if="info.genType == '1'">
|
||||
<el-form-item prop="genPath">
|
||||
<span slot="label">
|
||||
自定义路径
|
||||
<el-tooltip content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-input v-model="info.genPath">
|
||||
<el-dropdown slot="append">
|
||||
<el-button type="primary">
|
||||
最近路径快速选择
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="info.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row v-show="info.tplCategory == 'tree'">
|
||||
<h4 class="form-header">其他信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树编码字段
|
||||
<el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树父编码字段
|
||||
<el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeParentCode" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
树名称字段
|
||||
<el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.treeName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in info.columns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-show="info.tplCategory == 'sub'">
|
||||
<h4 class="form-header">关联信息</h4>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
关联子表的表名
|
||||
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.subTableName" placeholder="请选择" @change="subSelectChange">
|
||||
<el-option
|
||||
v-for="(table, index) in tables"
|
||||
:key="index"
|
||||
:label="table.tableName + ':' + table.tableComment"
|
||||
:value="table.tableName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item>
|
||||
<span slot="label">
|
||||
子表关联的外键名
|
||||
<el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
|
||||
<i class="el-icon-question"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<el-select v-model="info.subTableFkName" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="(column, index) in subColumns"
|
||||
:key="index"
|
||||
:label="column.columnName + ':' + column.columnComment"
|
||||
:value="column.columnName"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
<script>
|
||||
import Treeselect from "@riophae/vue-treeselect";
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
||||
|
||||
export default {
|
||||
name: "BasicInfoForm",
|
||||
components: { Treeselect },
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
tables: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subColumns: [],
|
||||
rules: {
|
||||
templateType: [
|
||||
{ required: true, message: "请选择生成模板", trigger: "blur" }
|
||||
],
|
||||
scene: [
|
||||
{ required: true, message: "请选择生成场景", trigger: "blur" }
|
||||
],
|
||||
// packageName: [
|
||||
// { required: true, message: "请输入生成包路径", trigger: "blur" }
|
||||
// ],
|
||||
moduleName: [
|
||||
{ required: true, message: "请输入生成模块名", trigger: "blur" }
|
||||
],
|
||||
businessName: [
|
||||
{ required: true, message: "请输入生成业务名", trigger: "blur" }
|
||||
],
|
||||
businessPackage: [
|
||||
{ required: true, message: "请输入生成业务包", trigger: "blur" }
|
||||
],
|
||||
className: [
|
||||
{ required: true, message: "请输入生成类名称", trigger: "blur" }
|
||||
],
|
||||
classComment: [
|
||||
{ required: true, message: "请输入生成类描述", trigger: "blur" }
|
||||
],
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {},
|
||||
watch: {
|
||||
'info.subTableName': function(val) {
|
||||
this.setSubTableColumns(val);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 转换菜单数据结构 */
|
||||
normalizer(node) {
|
||||
if (node.children && !node.children.length) {
|
||||
delete node.children;
|
||||
}
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.name,
|
||||
children: node.children
|
||||
};
|
||||
},
|
||||
/** 选择子表名触发 */
|
||||
subSelectChange(value) {
|
||||
this.info.subTableFkName = '';
|
||||
},
|
||||
/** 选择生成模板触发 */
|
||||
tplSelectChange(value) {
|
||||
if (value !== 1) {
|
||||
// TODO 芋艿:暂时不考虑支持树形结构
|
||||
this.$modal.msgError('暂时不考虑支持【树形】和【主子表】的代码生成。原因是:导致 vm 模板过于复杂,不利于胖友二次开发');
|
||||
return false;
|
||||
}
|
||||
if(value !== 'sub') {
|
||||
this.info.subTableName = '';
|
||||
this.info.subTableFkName = '';
|
||||
}
|
||||
},
|
||||
/** 设置关联外键 */
|
||||
setSubTableColumns(value) {
|
||||
for (var item in this.tables) {
|
||||
const name = this.tables[item].tableName;
|
||||
if (value === name) {
|
||||
this.subColumns = this.tables[item].columns;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
106
yudao-ui-admin/src/views/infra/codegen/importTable.vue
Normal file
106
yudao-ui-admin/src/views/infra/codegen/importTable.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<!-- 导入表 -->
|
||||
<el-dialog title="导入表" :visible.sync="visible" width="800px" top="5vh" append-to-body>
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input
|
||||
v-model="queryParams.tableName"
|
||||
placeholder="请输入表名称"
|
||||
clearable
|
||||
size="small"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input
|
||||
v-model="queryParams.tableComment"
|
||||
placeholder="请输入表描述"
|
||||
clearable
|
||||
size="small"
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row>
|
||||
<el-table @row-click="clickRow" ref="table" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
|
||||
<el-table-column type="selection" width="55"></el-table-column>
|
||||
<el-table-column prop="tableSchema" label="数据库" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="handleImportTable">确 定</el-button>
|
||||
<el-button @click="visible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSchemaTableList, createCodegenListFromDB } from "@/api/infra/codegen";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
visible: false,
|
||||
// 选中数组值
|
||||
tables: [],
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表数据
|
||||
dbTableList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
tableName: undefined,
|
||||
tableComment: undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show() {
|
||||
this.getList();
|
||||
this.visible = true;
|
||||
},
|
||||
clickRow(row) {
|
||||
this.$refs.table.toggleRowSelection(row);
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.tables = selection.map(item => item.tableName);
|
||||
},
|
||||
// 查询表数据
|
||||
getList() {
|
||||
getSchemaTableList(this.queryParams).then(res => {
|
||||
this.dbTableList = res.data;
|
||||
});
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 导入按钮操作 */
|
||||
handleImportTable() {
|
||||
createCodegenListFromDB(this.tables.join(",")).then(res => {
|
||||
this.$modal.msgSuccess("导入成功");
|
||||
this.visible = false;
|
||||
this.$emit("ok");
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
376
yudao-ui-admin/src/views/infra/codegen/index.vue
Normal file
376
yudao-ui-admin/src/views/infra/codegen/index.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 操作工作栏 -->
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="表名称" prop="tableName">
|
||||
<el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable size="small"
|
||||
@keyup.enter.native="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="表描述" prop="tableComment">
|
||||
<el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable size="small"
|
||||
@keyup.enter.native="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="yyyy-MM-dd" type="daterange"
|
||||
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工作栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="info" plain icon="el-icon-upload" size="mini" @click="openImportTable"
|
||||
v-hasPermi="['infra:codegen:create']">基于 DB 导入</el-button>
|
||||
<el-button type="info" plain icon="el-icon-upload" size="mini" @click="openImportSQL"
|
||||
v-hasPermi="['infra:codegen:create']">基于 SQL 导入</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="tableList">
|
||||
<el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" width="200"/>
|
||||
<el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" width="120"/>
|
||||
<el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" width="200"/>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" align="center" prop="createTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.updateTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.row)" v-hasPermi="['infra:codegen:preview']">预览</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-edit" @click="handleEditTable(scope.row)" v-hasPermi="['infra:codegen:update']">编辑</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['infra:codegen:delete']">删除</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['infra:codegen:update']">同步</el-button>
|
||||
<el-button type="text" size="small" icon="el-icon-download" @click="handleGenTable(scope.row)" v-hasPermi="['infra:codegen:download']">生成代码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" @pagination="getList"/>
|
||||
|
||||
<!-- 预览界面 -->
|
||||
<el-dialog :title="preview.title" :visible.sync="preview.open" width="90%" top="5vh" append-to-body class="scrollbar">
|
||||
<el-row>
|
||||
<el-col :span="7">
|
||||
<el-tree :data="preview.fileTree" :expand-on-click-node="false" default-expand-all highlight-current
|
||||
@node-click="handleNodeClick"/>
|
||||
</el-col>
|
||||
<el-col :span="17">
|
||||
<el-tabs v-model="preview.activeName">
|
||||
<el-tab-pane v-for="item in preview.data" :label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
|
||||
:name="item.filePath" :key="item.filePath">
|
||||
<el-link :underline="false" icon="el-icon-document-copy" v-clipboard:copy="item.code" v-clipboard:success="clipboardSuccess" style="float:right">复制</el-link>
|
||||
<pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 基于 DB 导入 -->
|
||||
<import-table ref="import" @ok="handleQuery" />
|
||||
|
||||
<!-- 基于 SQL 导入 -->
|
||||
<el-dialog :title="importSQL.title" :visible.sync="importSQL.open" width="800px" append-to-body>
|
||||
<el-form ref="importSQLForm" :model="importSQL.form" :rules="importSQL.rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="建表 SQL 语句" prop="sql">
|
||||
<el-input v-model="importSQL.form.sql" type="textarea" rows="30" style="width: 650px;" placeholder="请输入建 SQL 语句" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitImportSQLForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodegenTablePage, previewCodegen, downloadCodegen, deleteCodegen,
|
||||
syncCodegenFromDB, syncCodegenFromSQL, createCodegenListFromSQL } from "@/api/infra/codegen";
|
||||
|
||||
import importTable from "./importTable";
|
||||
// 代码高亮插件
|
||||
import hljs from "highlight.js/lib/highlight";
|
||||
import "highlight.js/styles/github-gist.css";
|
||||
hljs.registerLanguage("java", require("highlight.js/lib/languages/java"));
|
||||
hljs.registerLanguage("xml", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("html", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("vue", require("highlight.js/lib/languages/xml"));
|
||||
hljs.registerLanguage("javascript", require("highlight.js/lib/languages/javascript"));
|
||||
hljs.registerLanguage("sql", require("highlight.js/lib/languages/sql"));
|
||||
|
||||
export default {
|
||||
name: "Codegen",
|
||||
components: { importTable },
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 唯一标识符
|
||||
uniqueId: "",
|
||||
// 选中表数组
|
||||
tableNames: [],
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 表数据
|
||||
tableList: [],
|
||||
// 日期范围
|
||||
dateRange: "",
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tableName: undefined,
|
||||
tableComment: undefined
|
||||
},
|
||||
// 预览参数
|
||||
preview: {
|
||||
open: false,
|
||||
title: "代码预览",
|
||||
fileTree: [],
|
||||
data: {},
|
||||
activeName: "",
|
||||
},
|
||||
// 基于 SQL 导入
|
||||
importSQL: {
|
||||
open: false,
|
||||
title: "",
|
||||
form: {
|
||||
|
||||
},
|
||||
rules: {
|
||||
sql: [{ required: true, message: "SQL 不能为空", trigger: "blur" }]
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
activated() {
|
||||
const time = this.$route.query.t;
|
||||
if (time != null && time !== this.uniqueId) {
|
||||
this.uniqueId = time;
|
||||
this.resetQuery();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 查询表集合 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
getCodegenTablePage(this.addDateRange(this.queryParams, [
|
||||
this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
|
||||
this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
|
||||
], 'CreateTime')).then(response => {
|
||||
this.tableList = response.data.list;
|
||||
this.total = response.data.total;
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNo = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 生成代码操作 */
|
||||
handleGenTable(row) {
|
||||
downloadCodegen(row.id).then(response => {
|
||||
this.$download.zip(response, 'codegen-' + row.tableName + '.zip');
|
||||
})
|
||||
},
|
||||
/** 同步数据库操作 */
|
||||
handleSynchDb(row) {
|
||||
// 基于 SQL 同步
|
||||
if (row.importType === 2) {
|
||||
this.importSQL.open = true;
|
||||
this.importSQL.form.tableId = row.id;
|
||||
return;
|
||||
}
|
||||
// 基于 DB 同步
|
||||
const tableName = row.tableName;
|
||||
this.$modal.confirm('确认要强制同步"' + tableName + '"表结构吗?').then(function() {
|
||||
return syncCodegenFromDB(row.id);
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("同步成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
/** 打开导入表弹窗 */
|
||||
openImportTable() {
|
||||
this.$refs.import.show();
|
||||
},
|
||||
/** 打开 SQL 导入的弹窗 **/
|
||||
openImportSQL() {
|
||||
this.importSQL.open = true;
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = [];
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 预览按钮 */
|
||||
handlePreview(row) {
|
||||
previewCodegen(row.id).then(response => {
|
||||
this.preview.data = response.data;
|
||||
let files = this.handleFiles(response.data);
|
||||
this.preview.fileTree = this.handleTree(files, "id", "parentId", "children",
|
||||
"/"); // "/" 为根节点
|
||||
// console.log(this.preview.fileTree)
|
||||
this.preview.activeName = response.data[0].filePath;
|
||||
this.preview.open = true;
|
||||
});
|
||||
},
|
||||
/** 高亮显示 */
|
||||
highlightedCode(item) {
|
||||
// const vmName = key.substring(key.lastIndexOf("/") + 1, key.indexOf(".vm"));
|
||||
// var language = vmName.substring(vmName.indexOf(".") + 1, vmName.length);
|
||||
var language = item.filePath.substring(item.filePath.lastIndexOf(".") + 1);
|
||||
const result = hljs.highlight(language, item.code || "", true);
|
||||
return result.value || ' ';
|
||||
},
|
||||
/** 复制代码成功 */
|
||||
clipboardSuccess(){
|
||||
this.$modal.msgSuccess("复制成功");
|
||||
},
|
||||
/** 生成 files 目录 **/
|
||||
handleFiles(datas) {
|
||||
let exists = {}; // key:file 的 id;value:true
|
||||
let files = [];
|
||||
// 遍历每个元素
|
||||
for (const data of datas) {
|
||||
let paths = data.filePath.split('/');
|
||||
let fullPath = ''; // 从头开始的路径,用于生成 id
|
||||
// 特殊处理 java 文件
|
||||
if (paths[paths.length - 1].indexOf('.java') >= 0) {
|
||||
let newPaths = [];
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let path = paths[i];
|
||||
if (path !== 'java') {
|
||||
newPaths.push(path);
|
||||
continue;
|
||||
}
|
||||
newPaths.push(path);
|
||||
// 特殊处理中间的 package,进行合并
|
||||
let tmp = undefined;
|
||||
while (i < paths.length) {
|
||||
path = paths[i + 1];
|
||||
if (path === 'controller'
|
||||
|| path === 'convert'
|
||||
|| path === 'dal'
|
||||
|| path === 'enums'
|
||||
|| path === 'service'
|
||||
|| path === 'vo' // 下面三个,主要是兜底。可能考虑到有人改了包结构
|
||||
|| path === 'mysql'
|
||||
|| path === 'dataobject') {
|
||||
break;
|
||||
}
|
||||
tmp = tmp ? tmp + '.' + path : path;
|
||||
i++;
|
||||
}
|
||||
if (tmp) {
|
||||
newPaths.push(tmp);
|
||||
}
|
||||
}
|
||||
paths = newPaths;
|
||||
}
|
||||
// 遍历每个 path, 拼接成树
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
// 已经添加到 files 中,则跳过
|
||||
let oldFullPath = fullPath;
|
||||
// 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
|
||||
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i];
|
||||
if (exists[fullPath]) {
|
||||
continue;
|
||||
}
|
||||
// 添加到 files 中
|
||||
exists[fullPath] = true;
|
||||
files.push({
|
||||
id: fullPath,
|
||||
label: paths[i],
|
||||
parentId: oldFullPath || '/' // "/" 为根节点
|
||||
});
|
||||
}
|
||||
}
|
||||
return files;
|
||||
},
|
||||
/** 节点单击事件 **/
|
||||
handleNodeClick(data, node) {
|
||||
if (node && !node.isLeaf) {
|
||||
return false;
|
||||
}
|
||||
// 判断,如果非子节点,不允许选中
|
||||
this.preview.activeName = data.id;
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleEditTable(row) {
|
||||
const tableId = row.id;
|
||||
this.$router.push("/codegen/edit/" + tableId);
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const tableIds = row.id;
|
||||
this.$modal.confirm('是否确认删除表名称为"' + row.tableName + '"的数据项?').then(function() {
|
||||
return deleteCodegen(tableIds);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.importSQL.open = false;
|
||||
this.reset();
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.importSQL.form = {
|
||||
tableId: undefined,
|
||||
sql: undefined,
|
||||
};
|
||||
this.resetForm("importSQLForm");
|
||||
},
|
||||
// 提交 import SQL 表单
|
||||
submitImportSQLForm() {
|
||||
this.$refs["importSQLForm"].validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// 修改的提交
|
||||
let form = this.importSQL.form;
|
||||
if (form.tableId != null) {
|
||||
syncCodegenFromSQL(form.tableId, form.sql).then(response => {
|
||||
this.$modal.msgSuccess("同步成功");
|
||||
this.importSQL.open = false;
|
||||
this.getList();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 添加的提交
|
||||
createCodegenListFromSQL(form).then(response => {
|
||||
this.$modal.msgSuccess("导入成功");
|
||||
this.importSQL.open = false;
|
||||
this.getList();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
68
yudao-ui-admin/src/views/infra/dbDoc/index.vue
Normal file
68
yudao-ui-admin/src/views/infra/dbDoc/index.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportHtml">导出 HTML</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportWord">导出 Word</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportMarkdown">导出 Markdown</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 展示文档 -->
|
||||
<div v-loading="loading" :style="'height:'+ height">
|
||||
<i-frame :src="src" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { exportHtml, exportWord, exportMarkdown} from "@/api/infra/dbDoc";
|
||||
import iFrame from "@/components/iFrame/index";
|
||||
|
||||
export default {
|
||||
name: "DBDoc",
|
||||
components: { iFrame },
|
||||
data() {
|
||||
return {
|
||||
height: document.documentElement.clientHeight - 94.5 + "px;",
|
||||
loading: true,
|
||||
src: undefined,
|
||||
};
|
||||
},
|
||||
mounted: function() {
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 230);
|
||||
const that = this;
|
||||
window.onresize = function temp() {
|
||||
that.height = document.documentElement.clientHeight - 94.5 + "px;";
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 加载 Html,进行预览
|
||||
exportHtml().then(response => {
|
||||
let blob = new Blob([response], {type : 'text/html'});
|
||||
this.src = window.URL.createObjectURL(blob);
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/** 处理导出 HTML */
|
||||
handleExportHtml() {
|
||||
exportHtml().then(response => {
|
||||
this.$download.html(response, '数据库文档.html');
|
||||
})
|
||||
},
|
||||
/** 处理导出 Word */
|
||||
handleExportWord() {
|
||||
exportWord().then(response => {
|
||||
this.$download.word(response, '数据库文档.doc');
|
||||
})
|
||||
},
|
||||
/** 处理导出 Markdown */
|
||||
handleExportMarkdown() {
|
||||
exportMarkdown().then(response => {
|
||||
this.$download.markdown(response, '数据库文档.md');
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
15
yudao-ui-admin/src/views/infra/swagger/index.vue
Normal file
15
yudao-ui-admin/src/views/infra/swagger/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<i-frame :src="url" />
|
||||
</template>
|
||||
<script>
|
||||
import iFrame from "@/components/iFrame/index";
|
||||
export default {
|
||||
name: "Druid",
|
||||
components: { iFrame },
|
||||
data() {
|
||||
return {
|
||||
url: process.env.VUE_APP_BASE_API + "/doc.html"
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
262
yudao-ui-admin/src/views/infra/testDemo/index.vue
Executable file
262
yudao-ui-admin/src/views/infra/testDemo/index.vue
Executable file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="queryParams.name" placeholder="请输入名字" clearable size="small" @keyup.enter.native="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable size="small">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-input v-model="queryParams.category" placeholder="请输入分类" clearable size="small" @keyup.enter.native="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="queryParams.remark" placeholder="请输入备注" clearable size="small" @keyup.enter.native="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
|
||||
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
|
||||
v-hasPermi="['infra:test-demo:create']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
|
||||
v-hasPermi="['infra:test-demo:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<!-- 列表 -->
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="编号" align="center" prop="id" />
|
||||
<el-table-column label="名字" align="center" prop="name" />
|
||||
<el-table-column label="状态" align="center" prop="status" />
|
||||
<el-table-column label="类型" align="center" prop="type" />
|
||||
<el-table-column label="分类" align="center" prop="category" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['infra:test-demo:update']">修改</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
|
||||
v-hasPermi="['infra:test-demo:delete']">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页组件 -->
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"/>
|
||||
|
||||
<!-- 对话框(添加 / 修改) -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="名字" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入名字" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio label="1">请选择字典生成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-select v-model="form.type" placeholder="请选择类型">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分类" prop="category">
|
||||
<el-input v-model="form.category" placeholder="请输入分类" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createTestDemo, updateTestDemo, deleteTestDemo, getTestDemo, getTestDemoPage, exportTestDemoExcel } from "@/api/infra/testDemo";
|
||||
|
||||
export default {
|
||||
name: "TestDemo",
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 导出遮罩层
|
||||
exportLoading: false,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 字典类型列表
|
||||
list: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
dateRangeCreateTime: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
status: null,
|
||||
type: null,
|
||||
category: null,
|
||||
remark: null,
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
|
||||
status: [{ required: true, message: "状态不能为空", trigger: "blur" }],
|
||||
type: [{ required: true, message: "类型不能为空", trigger: "change" }],
|
||||
category: [{ required: true, message: "分类不能为空", trigger: "blur" }],
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
},
|
||||
methods: {
|
||||
/** 查询列表 */
|
||||
getList() {
|
||||
this.loading = true;
|
||||
// 处理查询参数
|
||||
let params = {...this.queryParams};
|
||||
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
|
||||
// 执行查询
|
||||
getTestDemoPage(params).then(response => {
|
||||
this.list = response.data.list;
|
||||
this.total = response.data.total;
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false;
|
||||
this.reset();
|
||||
},
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
status: undefined,
|
||||
type: undefined,
|
||||
category: undefined,
|
||||
remark: undefined,
|
||||
};
|
||||
this.resetForm("form");
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNo = 1;
|
||||
this.getList();
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRangeCreateTime = [];
|
||||
this.resetForm("queryForm");
|
||||
this.handleQuery();
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset();
|
||||
this.open = true;
|
||||
this.title = "添加字典类型";
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset();
|
||||
const id = row.id;
|
||||
getTestDemo(id).then(response => {
|
||||
this.form = response.data;
|
||||
this.open = true;
|
||||
this.title = "修改字典类型";
|
||||
});
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// 修改的提交
|
||||
if (this.form.id != null) {
|
||||
updateTestDemo(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 添加的提交
|
||||
createTestDemo(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功");
|
||||
this.open = false;
|
||||
this.getList();
|
||||
});
|
||||
});
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const id = row.id;
|
||||
this.$modal.confirm('是否确认删除字典类型编号为"' + id + '"的数据项?').then(function() {
|
||||
return deleteTestDemo(id);
|
||||
}).then(() => {
|
||||
this.getList();
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
// 处理查询参数
|
||||
let params = {...this.queryParams};
|
||||
params.pageNo = undefined;
|
||||
params.pageSize = undefined;
|
||||
this.addBeginAndEndTime(params, this.dateRangeCreateTime, 'createTime');
|
||||
// 执行导出
|
||||
this.$modal.confirm('是否确认导出所有字典类型数据项?').then(() => {
|
||||
this.exportLoading = true;
|
||||
return exportTestDemoExcel(params);
|
||||
}).then(response => {
|
||||
this.$download.excel(response, '字典类型.xls');
|
||||
this.exportLoading = false;
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
Reference in New Issue
Block a user