低代码平台完成
This commit is contained in:
@@ -1,15 +1,6 @@
|
|||||||
const dbUtils = require("../../utils/dbUitls");
|
const dbUtils = require("../../utils/dbUitls");
|
||||||
const checkJson = str => {
|
const {checkJson} = require("../../tools");
|
||||||
if (typeof str == 'string') {
|
|
||||||
try {
|
|
||||||
let obj = JSON.parse(str);
|
|
||||||
return !!(typeof obj == 'object' && obj);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
action: "/node/aicode/detail",
|
action: "/node/aicode/detail",
|
||||||
method: "post",
|
method: "post",
|
||||||
|
|||||||
34
src/rest/aicode/getCode.js
Normal file
34
src/rest/aicode/getCode.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const dbUtils = require("../../utils/dbUitls");
|
||||||
|
const fse = require("fs-extra");
|
||||||
|
const execute = require("../../tools/exec");
|
||||||
|
const generate = require("../../tools/generate");
|
||||||
|
module.exports = {
|
||||||
|
action: "/node/aicode/getCode",
|
||||||
|
method: "post",
|
||||||
|
execute: (request, response) => {
|
||||||
|
let id = request.query?.id, sql = `select * from node_aicode where id='${id}'`
|
||||||
|
dbUtils.query(sql).then(res => {
|
||||||
|
let info = res?.[0]
|
||||||
|
if (info?.id) {
|
||||||
|
let path = `/home/deploy/node-service/aicode/${info.id}`, zipPath = `${path}/${info.id}.zip`
|
||||||
|
generate(info, path).then(() => {
|
||||||
|
fse.pathExists(path, (err, exists) => {
|
||||||
|
console.log(`${path}=========>${exists}`)
|
||||||
|
if (exists) {
|
||||||
|
execute(`cd ${path}&&zip -r ${info.id}.zip .`)
|
||||||
|
.then(() => {
|
||||||
|
console.log('压缩完成!')
|
||||||
|
setTimeout(() => {
|
||||||
|
response.download(zipPath)
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
} else response.send({code: 1, err: "没有打包文件!"})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else response.send({code: 1, err: "无法找到应用信息"})
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
response.send({code: 1, err: err.sqlMessage})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/tools/generate.js
Normal file
157
src/tools/generate.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
const fse = require("fs-extra");
|
||||||
|
const {readFile} = require("../utils/fsUtils");
|
||||||
|
const {checkJson} = require("./index");
|
||||||
|
/**
|
||||||
|
* 生成入口页
|
||||||
|
*/
|
||||||
|
const genHome = app => {
|
||||||
|
return readFile('./tpl/AppEntry.vue').then(data => {
|
||||||
|
let file = data.toString(),
|
||||||
|
content = file.replace(/@appName/g, app.appName)
|
||||||
|
.replace(/@name/g, app.name)
|
||||||
|
let dicts = `'yesOrNo'`
|
||||||
|
if (checkJson(app.props)) {
|
||||||
|
let props = JSON.parse(app.props)?.map(e => e.dict).filter(e => !!e)
|
||||||
|
props.length > 0 && (dicts = props.map(e => `'${e}'`).toString())
|
||||||
|
}
|
||||||
|
content = content.replace(/@dicts/g, dicts)
|
||||||
|
return fse.outputFileSync(`./zips/${app.id}/${app.appName}.vue`, content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 生成列表页
|
||||||
|
*/
|
||||||
|
const genList = app => {
|
||||||
|
return readFile('./tpl/list.vue').then(data => {
|
||||||
|
let file = data.toString(),
|
||||||
|
content = file.replace(/@rightCode/g, app.rightCode)
|
||||||
|
.replace(/@name/g, app.name),
|
||||||
|
listProps = "", searchProps = "", btns = "", tableBtns = ""
|
||||||
|
if (checkJson(app.props)) {
|
||||||
|
let props = JSON.parse(app.props)
|
||||||
|
listProps = JSON.stringify(props.filter(e => e.isTable))
|
||||||
|
props.filter(e => e.isSearch && e.dict).map(e => {
|
||||||
|
searchProps += `<ai-select v-model="search.${e.prop}" placeholder="${e.label}" :selectList="dict.getDict('${e.dict}')"/>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (checkJson(app.btns)) {
|
||||||
|
let buttons = JSON.parse(app.btns)
|
||||||
|
buttons.map(e => {
|
||||||
|
if (e == "insertEnable") {
|
||||||
|
btns += `<el-button type="primary" icon="iconfont iconAdd" @click="handleAdd()">添加</el-button>`
|
||||||
|
} else if (e == "importEnable") {
|
||||||
|
btns += `<ai-import :instance="instance" name="${app.name}" title="导入${app.name}" suffixName="xlsx"
|
||||||
|
url="/app/${app.rightCode}/downloadTemplate" importUrl="/app/${app.rightCode}/import"
|
||||||
|
@onSuccess="page.current=1,getTableData()"/>`
|
||||||
|
} else if (e == "exportEnalbe") {
|
||||||
|
btns += `<ai-download url="/app/${app.rightCode}/export" :params="{...search,ids}" :instance="instance" fileName="${app.name}导出文件"/>`
|
||||||
|
} else if (e == "editEnable") {
|
||||||
|
tableBtns += `<el-button type="text" @click="handleAdd(row.id)">编辑</el-button>`
|
||||||
|
} else if (e == "deleteEnable") {
|
||||||
|
tableBtns += `<el-button type="text" @click="handleDelete(row.id)">删除</el-button>`
|
||||||
|
} else if (e == "batchDelEnable") {
|
||||||
|
btns += `<el-button icon="iconfont iconDelete" :disabled="!search.ids" @click="handleDelete(search.ids)">删除</el-button>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!searchProps) {
|
||||||
|
//当没有筛选条件时,按钮替换原筛选条件位置
|
||||||
|
searchProps = btns
|
||||||
|
btns = ""
|
||||||
|
} else {
|
||||||
|
btns = `<ai-search-bar><template #left>${btns}</template></ai-search-bar>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = content.replace(/@listProps/g, listProps)
|
||||||
|
.replace(/@searchProps/g, searchProps)
|
||||||
|
.replace(/@btns/g, btns)
|
||||||
|
.replace(/@tableBtns/g, tableBtns)
|
||||||
|
return fse.outputFileSync(`./zips/${app.id}/list.vue`, content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 生成新增/编辑页
|
||||||
|
*/
|
||||||
|
const genAdd = app => {
|
||||||
|
return readFile('./tpl/add.vue').then(data => {
|
||||||
|
let file = data.toString(),
|
||||||
|
content = file.replace(/@rightCode/g, app.rightCode)
|
||||||
|
.replace(/@name/g, app.name),
|
||||||
|
rules = "", domain = ""
|
||||||
|
if (checkJson(app.detailConfig)) {
|
||||||
|
let detail = JSON.parse(app.detailConfig), props = detail?.map(e => e.column).flat()
|
||||||
|
props.filter(e => e.mustFill == 1).map(e => {
|
||||||
|
rules += `${e.prop}: {required: true, message: "${e.fieldTips}"},`
|
||||||
|
})
|
||||||
|
detail.map(group => {
|
||||||
|
let fields = ``
|
||||||
|
group.column?.map(e => {
|
||||||
|
fields += `<el-form-item label="${e.fieldName}" prop="${e.prop}" ${e.grid == 1 ? '' : 'class="half"'}>${getComp(e)}</el-form-item>`
|
||||||
|
})
|
||||||
|
domain += `<ai-card title="${group.groupName}"><template #content> ${fields}</template></ai-card>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
content = content.replace(/@content/g, domain)
|
||||||
|
.replace(/@rules/g, rules)
|
||||||
|
return fse.outputFileSync(`./zips/${app.id}/add.vue`, content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const getComp = e => {
|
||||||
|
//字典下拉选择
|
||||||
|
if (e.type == 'dict') {
|
||||||
|
return `<ai-select v-model="form.${e.prop}" placeholder="${e.fieldName}" :selectList="dict.getDict('${e.dict}')" ${e.disable == 1 ? 'disabled' : ''}/>`
|
||||||
|
//单选radio
|
||||||
|
} else if (e.type == 'radio') {
|
||||||
|
return `<el-radio-group v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}>
|
||||||
|
<el-radio :label="item.dictValue" v-for="(item, i) in dict.getDict('${e.dict}')" :key="i">{{ item.dictName }}</el-radio>
|
||||||
|
</el-radio-group>`
|
||||||
|
//开关onOff
|
||||||
|
} else if (e.type == 'onOff') {
|
||||||
|
return `<el-switch v-model="form.${e.prop}" active-color="#26f" inactive-color="#ddd" active-value="1" inactive-value="0" ${e.disable == 1 ? 'disabled' : ''}/>`
|
||||||
|
//多选checkbox
|
||||||
|
} else if (e.type == 'checkbox') {
|
||||||
|
return `<el-checkbox-group v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}>
|
||||||
|
<el-checkbox v-for="(item, i) in dict.getDict('${e.dict}')" :label="item.dictValue" :key="i">{{ item.dictName }}</el-checkbox>
|
||||||
|
</el-checkbox-group>`
|
||||||
|
} else if (e.type == 'idNumber') {
|
||||||
|
return `<ai-id v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}/>`
|
||||||
|
//input输入框
|
||||||
|
} else if (['input', 'name', 'phone'].includes(e.type)) {
|
||||||
|
return `<el-input v-model="form.${e.prop}" placeholder="请输入${e.fieldName}" clearable ${e.disable == 1 ? 'disabled' : ''} :maxlength="${e.maxLength}" show-word-limit/>`
|
||||||
|
//number 输入框
|
||||||
|
} else if (e.type == 'number') {
|
||||||
|
return `<el-input-number v-model="form.${e.prop}" label="请输入${e.fieldName}" ${e.disable == 1 ? 'disabled' : ''} :precision="${e.decimalPlaces}" :max="${e.maxValue}" :min="${e.minValue}"/>`
|
||||||
|
//textarea输入框
|
||||||
|
} else if (e.type == 'textarea' || e.type == 'text') {
|
||||||
|
return `<el-input v-model="form.${e.prop}" placeholder="请输入${e.fieldName}" clearable ${e.disable == 1 ? 'disabled' : ''} :maxlength="${e.maxLength}" type="textarea" show-word-limit :rows="3"/>`
|
||||||
|
//日期选择
|
||||||
|
} else if (e.type == 'date') {
|
||||||
|
return `<el-date-picker style="width: 100%;" v-model="form.${e.prop}" type="date" placeholder="请选择" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
|
||||||
|
//日期带时分秒选择
|
||||||
|
} else if (e.type == 'datetime') {
|
||||||
|
return `<el-date-picker v-model="form.${e.prop}" type="datetime" placeholder="选择日期时间" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
|
||||||
|
//时间-时分秒选择
|
||||||
|
} else if (e.type == 'time') {
|
||||||
|
return `<el-time-picker v-model="form.${e.prop}" placeholder="请选择" ${e.disable == 1 ? 'disabled' : ''} :value-format="${e.timePattern}"/>`
|
||||||
|
//附件
|
||||||
|
} else if (e.type == 'upload') {
|
||||||
|
return ` <ai-uploader :instance="instance" isShowTip fileType="file" v-model="form.${e.prop}" ${e.disable == 1 ? 'disabled' : ''}
|
||||||
|
acceptType=".zip,.rar,.doc,.docx,.xls,.ppt,.pptx,.pdf,.txt,.jpg,.png,.xlsx" :limit="${e.fileMaxCount}" :maxSize="${e.fileChoseSize}"/>`
|
||||||
|
//富文本
|
||||||
|
} else if (e.type == 'rtf') {
|
||||||
|
return `<ai-editor v-model="form.${e.prop}" :instance="instance" ${e.disable == 1 ? 'disabled' : ''}/>`
|
||||||
|
//地区选择
|
||||||
|
} else if (e.type == 'area') {
|
||||||
|
return ` <ai-area-get :instance="instance" v-model="form.${e.prop}" :name.sync="form.${e.fieldDbName}_name" ${e.disable == 1 ? 'disabled' : ''}/>`
|
||||||
|
} else if (e.type == 'user') {
|
||||||
|
//人员选择
|
||||||
|
return `<ai-wechat-selecter slot="append" isShowUser :instance="instance" v-model="form.${e.prop}"/>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const generate = (app, dest) => {
|
||||||
|
fse.emptydirSync(dest)
|
||||||
|
fse.emptydirSync(`./zips/${app.id}`)
|
||||||
|
let tasks = [genHome(app), genList(app)]
|
||||||
|
app.detailType == 0 && tasks.push(genAdd(app))
|
||||||
|
return Promise.all(tasks)
|
||||||
|
}
|
||||||
|
module.exports = generate
|
||||||
12
src/tools/index.js
Normal file
12
src/tools/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const checkJson = str => {
|
||||||
|
if (typeof str == 'string') {
|
||||||
|
try {
|
||||||
|
let obj = JSON.parse(str);
|
||||||
|
return !!(typeof obj == 'object' && obj);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
module.exports = {checkJson}
|
||||||
@@ -34,4 +34,5 @@ const findFile = (dir = '.', cb) => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
module.exports = {readdir, stat, findFile}
|
const readFile = promisify(fs.readFile)
|
||||||
|
module.exports = {readdir, stat, findFile, readFile}
|
||||||
|
|||||||
36
tpl/AppEntry.vue
Normal file
36
tpl/AppEntry.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<section class="@appName">
|
||||||
|
<component :is="currentPage" v-bind="$props"/>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import List from "./list";
|
||||||
|
import Add from "./add";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "@appName",
|
||||||
|
components: {Add, List},
|
||||||
|
label: "@name",
|
||||||
|
props: {
|
||||||
|
instance: Function,
|
||||||
|
dict: Object,
|
||||||
|
permissions: Function
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentPage() {
|
||||||
|
let {hash} = this.$route
|
||||||
|
return hash == "#add" ? Add : List
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.dict.load(@dicts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.@appName {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
77
tpl/add.vue
Normal file
77
tpl/add.vue
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<section class="add">
|
||||||
|
<ai-detail>
|
||||||
|
<ai-title slot="title" :title="pageTitle"/>
|
||||||
|
<template #content>
|
||||||
|
<el-form ref="AddForm" :model="form" size="small" label-width="120px" :rules="rules">
|
||||||
|
@content
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="back">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submit">提交</el-button>
|
||||||
|
</template>
|
||||||
|
</ai-detail>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "add",
|
||||||
|
props: {
|
||||||
|
instance: Function,
|
||||||
|
dict: Object,
|
||||||
|
permissions: Function
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isEdit: v => !!v.$route.query.id,
|
||||||
|
pageTitle: v => v.isEdit ? "编辑@name" : "新增@name"
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {},
|
||||||
|
rules: {
|
||||||
|
@rules
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getDetail() {
|
||||||
|
let {id} = this.$route.query
|
||||||
|
id && this.instance.post("/node/@rightCode/detail", null, {
|
||||||
|
params: {id}
|
||||||
|
}).then(res => {
|
||||||
|
if (res?.data) {
|
||||||
|
this.form = res.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
back() {
|
||||||
|
this.$router.push({})
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$refs.AddForm.validate(v => {
|
||||||
|
if (v) {
|
||||||
|
this.instance.post("/node/@rightCode/addOrUpdate", this.form).then(res => {
|
||||||
|
if (res?.code == 0) {
|
||||||
|
this.$message.success("提交成功!")
|
||||||
|
this.back()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getDetail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.add {
|
||||||
|
.half {
|
||||||
|
width: 50%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
tpl/list.vue
Normal file
82
tpl/list.vue
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<section class="list">
|
||||||
|
<ai-list>
|
||||||
|
<ai-title slot="title" title="@name" isShowBottomBorder/>
|
||||||
|
<template #content>
|
||||||
|
<ai-search-bar>
|
||||||
|
<template #left>
|
||||||
|
@searchProps
|
||||||
|
</template>
|
||||||
|
<template #right>
|
||||||
|
<el-input size="small" placeholder="搜索" v-model="search.name" clearable
|
||||||
|
@change="page.current=1,getTableData()"/>
|
||||||
|
</template>
|
||||||
|
</ai-search-bar>
|
||||||
|
@btns
|
||||||
|
<ai-table :tableData="tableData" :total="page.total" :current.sync="page.current" :size.sync="page.size"
|
||||||
|
@getList="getTableData" :col-configs="colConfigs" :dict="dict">
|
||||||
|
<el-table-column slot="options" label="操作" fixed="right" align="center" width="300">
|
||||||
|
<template slot-scope="{row}">
|
||||||
|
@tableBtns
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</ai-table>
|
||||||
|
</template>
|
||||||
|
</ai-list>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "list",
|
||||||
|
props: {
|
||||||
|
instance: Function,
|
||||||
|
dict: Object,
|
||||||
|
permissions: Function
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
search: {name: ""},
|
||||||
|
page: {current: 1, size: 10, total: 0},
|
||||||
|
tableData: [],
|
||||||
|
colConfigs: [@listProps],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getTableData() {
|
||||||
|
this.instance.post("/node/@rightCode/list", null, {
|
||||||
|
params: {...this.page, ...this.search}
|
||||||
|
}).then(res => {
|
||||||
|
if (res?.data) {
|
||||||
|
this.tableData = res.data.records
|
||||||
|
this.page.total = res.data.total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleAdd(id) {
|
||||||
|
this.$router.push({hash: "#add", query: {id}})
|
||||||
|
},
|
||||||
|
handleDelete(ids) {
|
||||||
|
this.$confirm("是否要删除?").then(() => {
|
||||||
|
this.instance.post("/node/@rightCode/delete", null, {
|
||||||
|
params: {ids}
|
||||||
|
}).then(res => {
|
||||||
|
if (res?.code == 0) {
|
||||||
|
this.$message.success("删除成功")
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).catch(() => 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getTableData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user