低代码平台完成

This commit is contained in:
aixianling
2022-06-22 16:55:31 +08:00
parent 6a8fa6135a
commit 9a2fac9e6f
8 changed files with 402 additions and 12 deletions

View File

@@ -1,15 +1,6 @@
const dbUtils = require("../../utils/dbUitls");
const checkJson = str => {
if (typeof str == 'string') {
try {
let obj = JSON.parse(str);
return !!(typeof obj == 'object' && obj);
} catch (e) {
return false;
}
}
return false;
}
const {checkJson} = require("../../tools");
module.exports = {
action: "/node/aicode/detail",
method: "post",

View 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
View 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
View 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}

View File

@@ -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
View 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
View 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
View 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>