低代码平台完成
This commit is contained in:
@@ -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",
|
||||
|
||||
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