init Project

This commit is contained in:
2025-04-09 12:10:46 +08:00
parent 505d08443c
commit 75a1447d66
207 changed files with 26387 additions and 13 deletions

View File

@@ -0,0 +1,502 @@
<template>
<div>
<warning-bar
title="id , created_at , updated_at , deleted_at 会自动生成请勿重复创建。搜索时如果条件为LIKE只支持字符串"
/>
<el-form
ref="fieldDialogForm"
:model="middleDate"
label-width="120px"
label-position="right"
:rules="rules"
class="grid grid-cols-2"
>
<el-form-item label="字段名称" prop="fieldName">
<el-input
v-model="middleDate.fieldName"
autocomplete="off"
style="width: 80%"
/>
<el-button style="width: 18%; margin-left: 2%" @click="autoFill">
<span style="font-size: 12px">自动填充</span>
</el-button>
</el-form-item>
<el-form-item label="字段中文名" prop="fieldDesc">
<el-input v-model="middleDate.fieldDesc" autocomplete="off" />
</el-form-item>
<el-form-item label="字段JSON" prop="fieldJson">
<el-input v-model="middleDate.fieldJson" autocomplete="off" />
</el-form-item>
<el-form-item label="数据库字段名" prop="columnName">
<el-input v-model="middleDate.columnName" autocomplete="off" />
</el-form-item>
<el-form-item label="数据库字段描述" prop="comment">
<el-input v-model="middleDate.comment" autocomplete="off" />
</el-form-item>
<el-form-item label="字段类型" prop="fieldType">
<el-select
v-model="middleDate.fieldType"
style="width: 100%"
placeholder="请选择字段类型"
clearable
@change="clearOther"
>
<el-option
v-for="item in typeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item
:label="middleDate.fieldType === 'enum' ? '枚举值' : '类型长度'"
prop="dataTypeLong"
>
<el-input
v-model="middleDate.dataTypeLong"
:placeholder="
middleDate.fieldType === 'enum'
? `例:'北京','天津'`
: '数据库类型长度'
"
/>
</el-form-item>
<el-form-item label="字段查询条件" prop="fieldSearchType">
<el-select
v-model="middleDate.fieldSearchType"
:disabled="middleDate.fieldType === 'json'"
style="width: 100%"
placeholder="请选择字段查询条件"
clearable
>
<el-option
v-for="item in typeSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="canSelect(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="关联字典" prop="dictType">
<el-select
v-model="middleDate.dictType"
style="width: 100%"
:disabled="middleDate.fieldType !== 'string' && middleDate.fieldType !== 'array'"
placeholder="请选择字典"
clearable
>
<el-option
v-for="item in dictOptions"
:key="item.type"
:label="`${item.type}(${item.name})`"
:value="item.type"
/>
</el-select>
</el-form-item>
<el-form-item label="默认值">
<el-input
v-model="middleDate.defaultValue"
placeholder="请输入默认值"
/>
</el-form-item>
<el-form-item label="主键">
<el-checkbox v-model="middleDate.primaryKey" />
</el-form-item>
<el-form-item label="索引类型" prop="fieldIndexType">
<el-select
v-model="middleDate.fieldIndexType"
:disabled="middleDate.fieldType === 'json'"
style="width: 100%"
placeholder="请选择字段索引类型"
clearable
>
<el-option
v-for="item in typeIndexOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="canSelect(item.value)"
/>
</el-select>
</el-form-item>
<el-form-item label="前端新建/编辑">
<el-switch v-model="middleDate.form" />
</el-form-item>
<el-form-item label="前端表格列">
<el-switch v-model="middleDate.table" />
</el-form-item>
<el-form-item label="前端详情">
<el-switch v-model="middleDate.desc" />
</el-form-item>
<el-form-item label="导入/导出">
<el-switch v-model="middleDate.excel" />
</el-form-item>
<el-form-item label="是否排序">
<el-switch v-model="middleDate.sort" />
</el-form-item>
<el-form-item label="是否必填">
<el-switch v-model="middleDate.require" />
</el-form-item>
<el-form-item label="是否可清空">
<el-switch v-model="middleDate.clearable" />
</el-form-item>
<el-form-item label="隐藏查询条件">
<el-switch
v-model="middleDate.fieldSearchHide"
:disabled="!middleDate.fieldSearchType"
/>
</el-form-item>
<el-form-item label="校验失败文案">
<el-input v-model="middleDate.errorText" />
</el-form-item>
</el-form>
<el-collapse v-model="activeNames">
<el-collapse-item
title="数据源配置(此配置为高级配置,如编程基础不牢,可能导致自动化代码不可用)"
name="1"
>
<el-row :gutter="8">
<el-col :span="4">
<el-select
v-model="middleDate.dataSource.dbName"
placeholder="数据库【不填则为GVA库】"
@change="dbNameChange"
clearable
>
<el-option
v-for="item in dbList"
:key="item.aliasName"
:value="item.aliasName"
:label="item.aliasName"
:disabled="item.disable"
>
<div>
<span>{{ item.aliasName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{
item.dbName
}}</span>
</div>
</el-option>
</el-select>
</el-col>
<el-col :span="4">
<el-select
v-model="middleDate.dataSource.association"
placeholder="关联模式"
@change="associationChange"
>
<el-option label="一对一" :value="1" />
<el-option label="一对多" :value="2" />
</el-select>
</el-col>
<el-col :span="5">
<el-select
v-model="middleDate.dataSource.table"
placeholder="请选择数据源表"
filterable
allow-create
clearable
@focus="getDBTableList"
@change="selectDB"
@clear="clearAccress"
>
<el-option
v-for="item in dbTableList"
:key="item.tableName"
:label="item.tableName"
:value="item.tableName"
/>
</el-select>
</el-col>
<el-col :span="5">
<el-select
v-model="middleDate.dataSource.value"
placeholder="请先选择需要存储的数据"
>
<template #label="{ value }">
<span>存储: </span>
<span style="font-weight: bold">{{ value }}</span>
</template>
<el-option
v-for="item in dbColumnList"
:key="item.columnName"
:value="item.columnName"
>
<span style="float: left">
<el-tag :type="item.isPrimary ? 'primary' : 'info'">
{{ item.isPrimary ? '主&emsp;键' : '非主键' }}
</el-tag>
{{ item.columnName }}</span
>
<span
style="
float: right;
margin-left: 5px;
color: var(--el-text-color-secondary);
font-size: 13px;
"
>
类型{{ item.type }}
<block v-if="item.comment != ''"
>字段说明{{ item.comment }}</block
>
</span>
</el-option>
</el-select>
</el-col>
<el-col :span="5">
<el-select
v-model="middleDate.dataSource.label"
placeholder="请先选择需要展示的数据"
>
<template #label="{ value }">
<span>展示: </span>
<span style="font-weight: bold">{{ value }}</span>
</template>
<el-option
v-for="item in dbColumnList"
:key="item.columnName"
:value="item.columnName"
>
<span style="float: left">
<el-tag :type="item.isPrimary ? 'primary' : 'info'">
{{ item.isPrimary ? '主&emsp;键' : '非主键' }}
</el-tag>
{{ item.columnName }}</span
>
<span
style="
float: right;
margin-left: 5px;
color: var(--el-text-color-secondary);
font-size: 13px;
"
>
类型{{ item.type }}
<span v-if="item.comment != ''"
>字段说明{{ item.comment }}</span
>
</span>
</el-option>
</el-select>
<!-- <el-input v-model="middleDate.dataSource.label" placeholder="展示用字段" /> -->
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
</div>
</template>
<script setup>
import { toLowerCase, toSQLLine } from '@/utils/stringFun'
import { getSysDictionaryList } from '@/api/sysDictionary'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref, onMounted } from 'vue'
import { ElMessageBox } from 'element-plus'
import { getColumn, getDB, getTable } from '@/api/autoCode'
defineOptions({
name: 'FieldDialog'
})
const props = defineProps({
dialogMiddle: {
type: Object,
default: function () {
return {}
}
},
typeOptions: {
type: Array,
default: function () {
return []
}
},
typeSearchOptions: {
type: Array,
default: function () {
return []
}
},
typeIndexOptions: {
type: Array,
default: function () {
return []
}
}
})
const activeNames = ref([])
const middleDate = ref({})
const dictOptions = ref([])
const dbList = ref([])
const getDbFunc = async () => {
const res = await getDB()
if (res.code === 0) {
dbList.value = res.data.dbList
}
}
const validateDataTypeLong = (rule, value, callback) => {
const regex = /^('([^']*)'(?:,'([^']+)'*)*)$/
if (middleDate.value.fieldType == 'enum' && !regex.test(value)) {
callback(new Error('枚举值校验错误'))
} else {
callback()
}
}
const rules = ref({
fieldName: [
{ required: true, message: '请输入字段英文名', trigger: 'blur' }
],
fieldDesc: [
{ required: true, message: '请输入字段中文名', trigger: 'blur' }
],
fieldJson: [
{ required: true, message: '请输入字段格式化json', trigger: 'blur' }
],
columnName: [
{ required: true, message: '请输入数据库字段', trigger: 'blur' }
],
fieldType: [{ required: true, message: '请选择字段类型', trigger: 'blur' }],
dataTypeLong: [{ validator: validateDataTypeLong, trigger: 'blur' }]
})
const init = async () => {
middleDate.value = props.dialogMiddle
const dictRes = await getSysDictionaryList({
page: 1,
pageSize: 999999
})
dictOptions.value = dictRes.data
}
init()
const autoFill = () => {
middleDate.value.fieldJson = toLowerCase(middleDate.value.fieldName)
middleDate.value.columnName = toSQLLine(middleDate.value.fieldJson)
}
const canSelect = (item) => {
const fieldType = middleDate.value.fieldType;
if (fieldType === 'richtext') {
return item !== 'LIKE';
}
if (fieldType !== 'string' && item === 'LIKE') {
return true;
}
const nonNumericTypes = ['int', 'time.Time', 'float64'];
if (!nonNumericTypes.includes(fieldType) && ['BETWEEN', 'NOT BETWEEN'].includes(item)) {
return true;
}
return false;
}
const clearOther = () => {
middleDate.value.fieldSearchType = ''
middleDate.value.dictType = ''
}
const associationChange = (val) => {
if (val === 2) {
ElMessageBox.confirm(
'一对多关联模式下数据类型会改变为数组后端表现为json具体表现为数组模式是否继续',
'提示',
{
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
middleDate.value.fieldType = 'array'
})
.catch(() => {
middleDate.value.dataSource.association = 1
})
}
}
const clearAccress = () => {
middleDate.value.dataSource.value = ''
middleDate.value.dataSource.label = ''
}
const clearDataSourceTable = () => {
middleDate.value.dataSource.table = ''
}
const dbNameChange = () => {
getDBTableList()
clearDataSourceTable()
clearAccress()
}
const dbTableList = ref([])
const getDBTableList = async () => {
const res = await getTable({
businessDB: middleDate.value.dataSource.dbName
})
if (res.code === 0) {
let list = res.data.tables // 确保这里正确获取到 tables 数组
dbTableList.value = list.map((item) => ({
tableName: item.tableName,
value: item.tableName // 这里假设 value 也是 tableName如果不同请调整
}))
}
clearAccress()
}
const dbColumnList = ref([])
const selectDB = async (val, isInit) => {
middleDate.value.dataSource.hasDeletedAt = false
middleDate.value.dataSource.table = val
const res = await getColumn({
businessDB: middleDate.value.dataSource.dbName,
tableName: val
})
if (res.code === 0) {
let list = res.data.columns // 确保这里正确获取到 tables 数组
dbColumnList.value = list.map((item) => {
if (item.columnName === 'deleted_at') {
middleDate.value.dataSource.hasDeletedAt = true
}
return {
columnName: item.columnName,
value: item.columnName,
type: item.dataType,
isPrimary: item.primaryKey,
comment: item.columnComment
}
})
if (dbColumnList.value.length > 0 && !isInit) {
middleDate.value.dataSource.label = dbColumnList.value[0].columnName
middleDate.value.dataSource.value = dbColumnList.value[0].columnName
}
}
}
const fieldDialogForm = ref(null)
defineExpose({ fieldDialogForm })
onMounted(() => {
getDbFunc()
if (middleDate.value.dataSource.table) {
selectDB(middleDate.value.dataSource.table, true)
}
})
</script>

View File

@@ -0,0 +1,119 @@
<template>
<el-tabs
v-model="activeName"
tab-position="left"
class="h-[calc(100vh-110px)]"
>
<el-tab-pane
v-for="(item, key) in useCode"
:key="key"
:label="key"
:name="key"
>
<div :id="key" class="h-[calc(100vh-110px)] px-5 overflow-y-scroll"></div>
</el-tab-pane>
</el-tabs>
</template>
<script setup>
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import { ElMessage } from 'element-plus'
import { onMounted, ref, watchEffect } from 'vue'
import { useAppStore } from '@/pinia'
const appStore = useAppStore()
const useCode = ref({})
const createKey = [
'enter.go',
'gorm_biz.go',
'router_biz.go',
'api',
'router',
'initialize',
'gen.go'
]
onMounted(() => {
const isDarkMode = appStore.config.darkMode === 'dark'
if (isDarkMode) {
import('highlight.js/styles/atom-one-dark.css')
} else {
import('highlight.js/styles/atom-one-light.css')
}
})
const props = defineProps({
previewCode: {
type: Object,
default() {
return {}
}
},
isAdd: {
type: Boolean,
default: false
}
})
watchEffect(() => {
for (const key in props.previewCode) {
if (
props.isAdd &&
createKey.some((createKeyItem) => key.includes(createKeyItem))
) {
continue
}
useCode.value[key] = props.previewCode[key]
}
})
const activeName = ref('')
onMounted(() => {
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
if (lang === 'vue') {
return hljs.highlight(code, { language: 'html' }).value
}
return hljs.highlight(code, { language }).value
}
})
)
for (const key in useCode.value) {
if (activeName.value === '') {
activeName.value = key
}
document.getElementById(key).innerHTML = marked.parse(useCode.value[key])
}
})
const selectText = () => {
const element = document.getElementById(activeName.value)
if (document.body.createTextRange) {
const range = document.body.createTextRange()
range.moveToElementText(element)
range.select()
} else if (window.getSelection) {
const selection = window.getSelection()
const range = document.createRange()
range.selectNodeContents(element)
selection.removeAllRanges()
selection.addRange(range)
} else {
alert('none')
}
}
const copy = () => {
selectText()
document.execCommand('copy')
ElMessage.success('复制成功')
}
defineExpose({ copy, selectText })
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,620 @@
<template>
<div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="goAutoCode(null)">
新增
</el-button>
</div>
<el-table :data="tableData">
<el-table-column type="selection" width="55" />
<el-table-column align="left" label="id" width="60" prop="ID" />
<el-table-column align="left" label="日期" width="180">
<template #default="scope">
{{ formatDate(scope.row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column
align="left"
label="结构体名"
min-width="150"
prop="structName"
/>
<el-table-column
align="left"
label="结构体描述"
min-width="150"
prop="description"
/>
<el-table-column
align="left"
label="表名称"
min-width="150"
prop="tableName"
/>
<el-table-column
align="left"
label="回滚标记"
min-width="150"
prop="flag"
>
<template #default="scope">
<el-tag v-if="scope.row.flag" type="danger" effect="dark">
已回滚
</el-tag>
<el-tag v-else type="success" effect="dark"> 未回滚 </el-tag>
</template>
</el-table-column>
<el-table-column align="left" label="操作" min-width="240">
<template #default="scope">
<div>
<el-button
type="primary"
link
:disabled="scope.row.flag === 1"
@click="addFuncBtn(scope.row)"
>
增加方法
</el-button>
<el-button type="primary" link @click="goAutoCode(scope.row, 1)">
增加字段
</el-button>
<el-button
type="primary"
link
:disabled="scope.row.flag === 1"
@click="openDialog(scope.row)"
>
回滚
</el-button>
<el-button type="primary" link @click="goAutoCode(scope.row)">
复用
</el-button>
<el-button type="primary" link @click="deleteRow(scope.row)">
删除
</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<el-dialog
v-model="dialogFormVisible"
:title="dialogFormTitle"
:before-close="closeDialog"
width="600px"
>
<el-form :inline="true" :model="formData" label-width="80px">
<el-form-item label="选项:">
<el-checkbox v-model="formData.deleteApi" label="删除接口" />
<el-checkbox v-model="formData.deleteMenu" label="删除菜单" />
<el-checkbox
v-model="formData.deleteTable"
label="删除表"
@change="deleteTableCheck"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog"> </el-button>
<el-popconfirm
title="此操作将回滚生成文件和勾选项目, 是否继续?"
@confirm="enterDialog"
>
<template #reference>
<el-button type="primary"> </el-button>
</template>
</el-popconfirm>
</div>
</template>
</el-dialog>
<el-drawer
v-model="funcFlag"
size="60%"
:show-close="false"
:close-on-click-modal="false"
>
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">操作栏</span>
<div>
<el-button type="primary" @click="runFunc" :loading="aiLoading">
生成
</el-button>
<el-button type="primary" @click="closeFunc" :loading="aiLoading">
取消
</el-button>
</div>
</div>
</template>
<div class="">
<el-form
v-loading="aiLoading"
label-position="top"
element-loading-text="小淼正在思考请稍候..."
:model="autoFunc"
label-width="80px"
>
<el-row :gutter="12">
<el-col :span="8">
<el-form-item label="包名:">
<el-input
v-model="autoFunc.package"
placeholder="请输入包名"
disabled
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="结构体名:">
<el-input
v-model="autoFunc.structName"
placeholder="请输入结构体名"
disabled
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="前端文件名:">
<el-input
v-model="autoFunc.packageName"
placeholder="请输入文件名"
disabled
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :span="8">
<el-form-item label="后端文件名:">
<el-input
v-model="autoFunc.humpPackageName"
placeholder="请输入文件名"
disabled
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="描述:">
<el-input
v-model="autoFunc.description"
placeholder="请输入描述"
disabled
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="缩写:">
<el-input
v-model="autoFunc.abbreviation"
placeholder="请输入缩写"
disabled
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="是否AI填充">
<el-switch v-model="autoFunc.isAi" />
<span class="text-sm text-red-600 p-2"
>当前ai帮写存在不稳定因素生成代码后请注意手动调整部分内容</span
>
</el-form-item>
<template v-if="autoFunc.isAi">
<el-form-item label="Ai帮写:">
<div class="relative w-full">
<el-input
type="textarea"
placeholder="AI帮写功能输入提示信息自动生成代码"
v-model="autoFunc.prompt"
:rows="5"
@input="autoFunc.router = autoFunc.router.replace(/\//g, '')"
/>
<el-button
@click="aiAddFunc"
type="primary"
class="absolute right-2 bottom-2"
><ai-gva />帮写</el-button
>
</div>
</el-form-item>
<el-form-item label="Api方法:">
<v-ace-editor
v-model:value="autoFunc.apiFunc"
lang="golang"
theme="github_dark"
class="h-80 w-full"
/>
</el-form-item>
<el-form-item label="Server方法:">
<v-ace-editor
v-model:value="autoFunc.serverFunc"
lang="golang"
theme="github_dark"
class="h-80 w-full"
/>
</el-form-item>
<el-form-item label="前端JSAPI方法:">
<v-ace-editor
v-model:value="autoFunc.jsFunc"
lang="javascript"
theme="github_dark"
class="h-80 w-full"
/>
</el-form-item>
</template>
<el-form-item label="方法介绍:">
<div class="flex w-full gap-2">
<el-input
class="flex-1"
v-model="autoFunc.funcDesc"
placeholder="请输入方法介绍"
/>
<el-button type="primary" @click="autoComplete"
><ai-gva />补全</el-button
>
</div>
</el-form-item>
<el-form-item label="方法名:">
<el-input
@blur="autoFunc.funcName = toUpperCase(autoFunc.funcName)"
v-model="autoFunc.funcName"
placeholder="请输入方法名"
/>
</el-form-item>
<el-form-item label="方法:">
<el-select v-model="autoFunc.method" placeholder="请选择方法">
<el-option
v-for="item in ['GET', 'POST', 'PUT', 'DELETE']"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="是否鉴权:">
<el-switch
v-model="autoFunc.isAuth"
active-text=""
inactive-text=""
/>
</el-form-item>
<el-form-item label="路由path:">
<el-input
v-model="autoFunc.router"
placeholder="路由path"
@input="autoFunc.router = autoFunc.router.replace(/\//g, '')"
/>
<div>
API路径: [{{ autoFunc.method }}] /{{ autoFunc.abbreviation }}/{{
autoFunc.router
}}
</div>
</el-form-item>
</el-form>
</div>
</el-drawer>
</div>
</template>
<script setup>
import {
getSysHistory,
rollback,
delSysHistory,
addFunc,
butler
} from '@/api/autoCode.js'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
import { formatDate } from '@/utils/format'
import { toUpperCase } from '@/utils/stringFun'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-javascript'
import 'ace-builds/src-noconflict/mode-golang'
import 'ace-builds/src-noconflict/theme-github_dark'
defineOptions({
name: 'AutoCodeAdmin'
})
const aiLoading = ref(false)
const formData = ref({
id: undefined,
deleteApi: true,
deleteMenu: true,
deleteTable: false
})
const router = useRouter()
const dialogFormVisible = ref(false)
const dialogFormTitle = ref('')
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const activeInfo = ref('')
const autoFunc = ref({
package: '',
funcName: '',
structName: '',
packageName: '',
description: '',
abbreviation: '',
humpPackageName: '',
businessDB: '',
method: '',
funcDesc: '',
isAuth: false,
isAi: false,
apiFunc: '',
serverFunc: '',
jsFunc: ''
})
const addFuncBtn = (row) => {
const req = JSON.parse(row.request)
activeInfo.value = row.request
autoFunc.value.package = req.package
autoFunc.value.structName = req.structName
autoFunc.value.packageName = req.packageName
autoFunc.value.description = req.description
autoFunc.value.abbreviation = req.abbreviation
autoFunc.value.humpPackageName = req.humpPackageName
autoFunc.value.businessDB = req.businessDB
autoFunc.value.method = ''
autoFunc.value.funcName = ''
autoFunc.value.router = ''
autoFunc.value.funcDesc = ''
autoFunc.value.isAuth = false
autoFunc.value.isAi = false
autoFunc.value.apiFunc = ''
autoFunc.value.serverFunc = ''
autoFunc.value.jsFunc = ''
funcFlag.value = true
}
const funcFlag = ref(false)
const closeFunc = () => {
funcFlag.value = false
}
const runFunc = async () => {
// 首字母自动转换为大写
autoFunc.value.funcName = toUpperCase(autoFunc.value.funcName)
if (!autoFunc.value.funcName) {
ElMessage.error('请输入方法名')
return
}
if (!autoFunc.value.method) {
ElMessage.error('请选择方法')
return
}
if (!autoFunc.value.router) {
ElMessage.error('请输入路由')
return
}
if (!autoFunc.value.funcDesc) {
ElMessage.error('请输入方法介绍')
return
}
if (autoFunc.value.isAi) {
if (
!autoFunc.value.apiFunc ||
!autoFunc.value.serverFunc ||
!autoFunc.value.jsFunc
) {
ElMessage.error('请先使用AI帮写完成基础代码如果生成失败请重新调用')
return
}
}
const res = await addFunc(autoFunc.value)
if (res.code === 0) {
ElMessage.success('增加方法成功')
closeFunc()
}
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async () => {
const table = await getSysHistory({
page: page.value,
pageSize: pageSize.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const deleteRow = async (row) => {
ElMessageBox.confirm('此操作将删除本历史, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await delSysHistory({ id: Number(row.ID) })
if (res.code === 0) {
ElMessage.success('删除成功')
getTableData()
}
})
}
// 打开弹窗
const openDialog = (row) => {
dialogFormTitle.value = '回滚:' + row.structName
formData.value.id = row.ID
dialogFormVisible.value = true
}
// 关闭弹窗
const closeDialog = () => {
dialogFormVisible.value = false
formData.value = {
id: undefined,
deleteApi: true,
deleteMenu: true,
deleteTable: false
}
}
// 确认删除表
const deleteTableCheck = (flag) => {
if (flag) {
ElMessageBox.confirm(
`此操作将删除自动创建的文件和api会删除表, 是否继续?`,
'提示',
{
closeOnClickModal: false,
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
ElMessageBox.confirm(
`此操作将删除自动创建的文件和api会删除表, 请继续确认!!!`,
'会删除表',
{
closeOnClickModal: false,
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(() => {
formData.value.deleteTable = false
})
})
.catch(() => {
formData.value.deleteTable = false
})
}
}
const enterDialog = async () => {
const res = await rollback(formData.value)
if (res.code === 0) {
ElMessage.success('回滚成功')
getTableData()
}
}
const goAutoCode = (row, isAdd) => {
if (row) {
router.push({
name: 'autoCodeEdit',
params: {
id: row.ID
},
query: {
isAdd: isAdd
}
})
} else {
router.push({ name: 'autoCode' })
}
}
const aiAddFunc = async () => {
aiLoading.value = true
autoFunc.value.apiFunc = ''
autoFunc.value.serverFunc = ''
autoFunc.value.jsFunc = ''
if (!autoFunc.value.prompt) {
ElMessage.error('请输入提示信息')
return
}
const res = await addFunc({ ...autoFunc.value, isPreview: true })
if (res.code !== 0) {
aiLoading.value = false
ElMessage.error(res.msg)
return
}
const aiRes = await butler({
structInfo: activeInfo.value,
template: JSON.stringify(res.data),
prompt: autoFunc.value.prompt,
command: 'addFunc'
})
aiLoading.value = false
if (aiRes.code === 0) {
try {
const aiData = JSON.parse(aiRes.data)
autoFunc.value.apiFunc = aiData.api
autoFunc.value.serverFunc = aiData.server
autoFunc.value.jsFunc = aiData.js
autoFunc.value.method = aiData.method
autoFunc.value.funcName = aiData.funcName
const routerArr = aiData.router.split('/')
autoFunc.value.router = routerArr[routerArr.length - 1]
autoFunc.value.funcDesc = autoFunc.value.prompt
} catch (_) {
ElMessage.error('小淼忙碌,请重新调用')
}
}
}
const autoComplete = async () => {
aiLoading.value = true
const aiRes = await butler({
prompt: autoFunc.value.funcDesc,
command: 'autoCompleteFunc'
})
aiLoading.value = false
if (aiRes.code === 0) {
try {
const aiData = JSON.parse(aiRes.data)
autoFunc.value.method = aiData.method
autoFunc.value.funcName = aiData.funcName
autoFunc.value.router = aiData.router
autoFunc.value.prompt = autoFunc.value.funcDesc
} catch (_) {
ElMessage.error('小淼开小差了,请重新调用')
}
}
}
</script>

View File

@@ -0,0 +1,205 @@
<template>
<div>
<warning-bar
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
title="此功能为开发环境使用不建议发布到生产具体使用效果请看视频https://www.bilibili.com/video/BV1kv4y1g7nT?p=3"
/>
<div class="gva-table-box">
<div class="gva-btn-list gap-3 flex items-center">
<el-button type="primary" icon="plus" @click="openDialog('addApi')">
新增
</el-button>
</div>
<el-table :data="tableData">
<el-table-column align="left" label="id" width="120" prop="ID" />
<el-table-column
align="left"
label="包名"
width="150"
prop="packageName"
/>
<el-table-column
align="left"
label="模板"
width="150"
prop="template"
/>
<el-table-column align="left" label="展示名" width="150" prop="label" />
<el-table-column
align="left"
label="描述"
min-width="150"
prop="desc"
/>
<el-table-column align="left" label="操作" width="200">
<template #default="scope">
<el-button
icon="delete"
type="primary"
link
@click="deleteApiFunc(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-drawer v-model="dialogFormVisible" size="40%" :show-close="false">
<warning-bar
title="模板package会创建集成于项目本体中的代码包模板plugin会创建插件包"
/>
<el-form ref="pkgForm" :model="form" :rules="rules" label-width="80px">
<el-form-item label="包名" prop="packageName">
<el-input v-model="form.packageName" autocomplete="off" />
</el-form-item>
<el-form-item label="模板" prop="template">
<el-select v-model="form.template">
<el-option
v-for="template in templatesOptions"
:label="template"
:value="template"
:key="template"
/>
</el-select>
</el-form-item>
<el-form-item label="展示名" prop="label">
<el-input v-model="form.label" autocomplete="off" />
</el-form-item>
<el-form-item label="描述" prop="desc">
<el-input v-model="form.desc" autocomplete="off" />
</el-form-item>
</el-form>
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">创建Package</span>
<div>
<el-button @click="closeDialog"> </el-button>
<el-button type="primary" @click="enterDialog"> </el-button>
</div>
</div>
</template>
</el-drawer>
</div>
</template>
<script setup>
import {
createPackageApi,
getPackageApi,
deletePackageApi,
getTemplatesApi
} from '@/api/autoCode'
import { ref } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'AutoPkg'
})
const form = ref({
packageName: '',
template: '',
label: '',
desc: ''
})
const templatesOptions = ref([])
const getTemplates = async () => {
const res = await getTemplatesApi()
if (res.code === 0) {
templatesOptions.value = res.data
}
}
getTemplates()
const validateData = (rule, value, callback) => {
if (/[\u4E00-\u9FA5]/g.test(value)) {
callback(new Error('不能为中文'))
} else if (/^\d+$/.test(value[0])) {
callback(new Error('不能够以数字开头'))
} else {
callback()
}
}
const rules = ref({
packageName: [
{ required: true, message: '请输入包名', trigger: 'blur' },
{ validator: validateData, trigger: 'blur' }
],
template: [
{ required: true, message: '请选择模板', trigger: 'change' },
{ validator: validateData, trigger: 'blur' }
]
})
const dialogFormVisible = ref(false)
const openDialog = () => {
dialogFormVisible.value = true
}
const closeDialog = () => {
dialogFormVisible.value = false
form.value = {
packageName: '',
template: '',
label: '',
desc: ''
}
}
const pkgForm = ref(null)
const enterDialog = async () => {
pkgForm.value.validate(async (valid) => {
if (valid) {
const res = await createPackageApi(form.value)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '添加成功',
showClose: true
})
}
getTableData()
closeDialog()
}
})
}
const tableData = ref([])
const getTableData = async () => {
const table = await getPackageApi()
if (table.code === 0) {
tableData.value = table.data.pkgs
}
}
const deleteApiFunc = async (row) => {
ElMessageBox.confirm(
'此操作仅删除数据库中的pkg存储后端相应目录结构请自行删除与数据库保持一致',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(async () => {
const res = await deletePackageApi(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
getTableData()
}
})
}
getTableData()
</script>

View File

@@ -0,0 +1,32 @@
export const getCode = (templateID) => {
return `<template>
<!-- 导出组件 -->
<ExportExcel templateId="${templateID}" :condition="condition" :limit="limit" :offset="offset" :order="order" />
<!-- 导入组件 handleSuccess为导入成功后的回调函数 -->
<ImportExcel templateId="${templateID}" @on-success="handleSuccess" />
<!-- 导出模板 -->
<ExportTemplate templateId="${templateID}" />
</template>
<script setup>
import { ref } from 'vue';
// 导出组件
import ExportExcel from '@/components/exportExcel/exportExcel.vue';
// 导入组件
import ImportExcel from '@/components/exportExcel/importExcel.vue';
// 导出模板组件
import ExportTemplate from '@/components/exportExcel/exportTemplate.vue';
const condition = ref({}); // 查询条件
const limit = ref(10); // 最大条数限制
const offset = ref(0); // 偏移量
const order = ref('id desc'); // 排序条件
const handleSuccess = (res) => {
console.log(res);
// 导入成功的回调函数
};
</script>`
}

View File

@@ -0,0 +1,996 @@
<template>
<div>
<WarningBar
title="本功能提供同步的表格导出功能,大数据量的异步表格导出功能,可以选择点我定制"
href="https://flipped-aurora.feishu.cn/docx/KwjxdnvatozgwIxGV0rcpkZSn4d"
/>
<div class="gva-search-box">
<el-form
ref="elSearchFormRef"
:inline="true"
:model="searchInfo"
class="demo-form-inline"
:rules="searchRule"
@keyup.enter="onSubmit"
>
<el-form-item label="创建日期" prop="createdAt">
<template #label>
<span>
创建日期
<el-tooltip
content="搜索范围是开始日期(包含)至结束日期(不包含)"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</template>
<el-date-picker
v-model="searchInfo.startCreatedAt"
type="datetime"
placeholder="开始日期"
:disabled-date="
(time) =>
searchInfo.endCreatedAt
? time.getTime() > searchInfo.endCreatedAt.getTime()
: false
"
/>
<el-date-picker
v-model="searchInfo.endCreatedAt"
type="datetime"
placeholder="结束日期"
:disabled-date="
(time) =>
searchInfo.startCreatedAt
? time.getTime() < searchInfo.startCreatedAt.getTime()
: false
"
/>
</el-form-item>
<el-form-item label="模板名称" prop="name">
<el-input v-model="searchInfo.name" placeholder="搜索条件" />
</el-form-item>
<el-form-item label="表名称" prop="tableName">
<el-input v-model="searchInfo.tableName" placeholder="搜索条件" />
</el-form-item>
<el-form-item label="模板标识" prop="templateID">
<el-input v-model="searchInfo.templateID" placeholder="搜索条件" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit"
>查询</el-button
>
<el-button icon="refresh" @click="onReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="openDialog"
>新增</el-button
>
<el-button
icon="delete"
style="margin-left: 10px"
:disabled="!multipleSelection.length"
@click="onDelete"
>删除</el-button
>
</div>
<el-table
ref="multipleTable"
style="width: 100%"
tooltip-effect="dark"
:data="tableData"
row-key="ID"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column align="left" label="日期" width="180">
<template #default="scope">{{
formatDate(scope.row.CreatedAt)
}}</template>
</el-table-column>
<el-table-column align="left" label="数据库" width="120">
<template #default="scope">
<span>{{ scope.row.dbName || 'GVA库' }}</span>
</template>
</el-table-column>
<el-table-column
align="left"
label="模板标识"
prop="templateID"
width="120"
/>
<el-table-column
align="left"
label="模板名称"
prop="name"
width="120"
/>
<el-table-column
align="left"
label="表名称"
prop="tableName"
width="120"
/>
<el-table-column
align="left"
label="模板信息"
prop="templateInfo"
min-width="120"
show-overflow-tooltip
/>
<el-table-column align="left" label="操作" min-width="280">
<template #default="scope">
<el-button
type="primary"
link
icon="documentCopy"
class="table-button"
@click="copyFunc(scope.row)"
>复制</el-button
>
<el-button
type="primary"
link
icon="edit-pen"
class="table-button"
@click="showCode(scope.row)"
>代码</el-button
>
<el-button
type="primary"
link
icon="edit"
class="table-button"
@click="updateSysExportTemplateFunc(scope.row)"
>变更</el-button
>
<el-button
type="primary"
link
icon="delete"
@click="deleteRow(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
<el-drawer
v-model="dialogFormVisible"
size="60%"
:before-close="closeDialog"
:title="type === 'create' ? '添加' : '修改'"
:show-close="false"
destroy-on-close
>
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{ type === 'create' ? '添加' : '修改' }}</span>
<div>
<el-button @click="closeDialog">取 消</el-button>
<el-button type="primary" @click="enterDialog">确 定</el-button>
</div>
</div>
</template>
<el-form
ref="elFormRef"
:model="formData"
label-position="right"
:rules="rule"
label-width="100px"
v-loading="aiLoading"
element-loading-text="小淼正在思考..."
>
<el-form-item label="业务库" prop="dbName">
<template #label>
<el-tooltip
content="需要提前到db-list自行配置多数据库如未配置需配置后重启服务方可使用若无法选择请到config.yaml中设置disabled:false选择导入导出的目标库"
placement="bottom"
effect="light"
>
<div>
业务库 <el-icon><QuestionFilled /></el-icon>
</div>
</el-tooltip>
</template>
<el-select
v-model="formData.dbName"
clearable
@change="dbNameChange"
placeholder="选择业务库"
>
<el-option
v-for="item in dbList"
:key="item.aliasName"
:value="item.aliasName"
:label="item.aliasName"
:disabled="item.disable"
>
<div>
<span>{{ item.aliasName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{
item.dbName
}}</span>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="需用到的表" prop="tables">
<el-select
multiple
v-model="tables"
clearable
placeholder="使用AI的情况下请选择"
>
<el-option
v-for="item in tableOptions"
:key="item.tableName"
:label="item.tableName"
:value="item.tableName"
/>
</el-select>
</el-form-item>
<el-form-item label="AI帮写:" prop="ai">
<div class="relative w-full">
<el-input
type="textarea"
v-model="prompt"
:clearable="true"
:rows="5"
placeholder="试试描述你要做的导出功能让AI帮你完成在此之前请选择你需要导出的表所在的业务库如不做选择则默认使用gva库"
/>
<el-button
class="absolute bottom-2 right-2"
type="primary"
@click="autoExport"
><el-icon><ai-gva /></el-icon>帮写</el-button
>
</div>
</el-form-item>
<el-form-item label="表名称:" clearable prop="tableName">
<div class="w-full flex gap-4">
<el-select
v-model="formData.tableName"
class="flex-1"
filterable
placeholder="请选择表"
>
<el-option
v-for="item in tableOptions"
:key="item.tableName"
:label="item.tableName"
:value="item.tableName"
/>
</el-select>
<el-button
:disabled="!formData.tableName"
type="primary"
@click="getColumnFunc(true)"
><el-icon><ai-gva /></el-icon>自动补全</el-button
>
<el-button
:disabled="!formData.tableName"
type="primary"
@click="getColumnFunc(false)"
>自动生成模板</el-button
>
</div>
</el-form-item>
<el-form-item label="模板名称:" prop="name">
<el-input
v-model="formData.name"
:clearable="true"
placeholder="请输入模板名称"
/>
</el-form-item>
<el-form-item label="模板标识:" prop="templateID">
<el-input
v-model="formData.templateID"
:clearable="true"
placeholder="模板标识为前端组件需要挂在的标识属性"
/>
</el-form-item>
<el-form-item label="关联条件:">
<div
v-for="(join, key) in formData.joinTemplate"
:key="key"
class="flex gap-4 w-full mb-2"
>
<el-select v-model="join.joins" placeholder="请选择关联方式">
<el-option label="LEFT JOIN" value="LEFT JOIN" />
<el-option label="INNER JOIN" value="INNER JOIN" />
<el-option label="RIGHT JOIN" value="RIGHT JOIN" />
</el-select>
<el-input v-model="join.table" placeholder="请输入关联表" />
<el-input
v-model="join.on"
placeholder="关联条件 table1.a = table2.b"
/>
<el-button
type="danger"
icon="delete"
@click="() => formData.joinTemplate.splice(key, 1)"
>删除</el-button
>
</div>
<div class="flex justify-end w-full">
<el-button type="primary" icon="plus" @click="addJoin"
>添加条件</el-button
>
</div>
</el-form-item>
<el-form-item label="模板信息:" prop="templateInfo">
<el-input
v-model="formData.templateInfo"
type="textarea"
:rows="12"
:clearable="true"
:placeholder="templatePlaceholder"
/>
</el-form-item>
<el-form-item label="默认导出条数:">
<el-input-number
v-model="formData.limit"
:step="1"
:step-strictly="true"
:precision="0"
/>
</el-form-item>
<el-form-item label="默认排序条件:">
<el-input v-model="formData.order" placeholder=":id desc" />
</el-form-item>
<el-form-item label="导出条件:">
<div
v-for="(condition, key) in formData.conditions"
:key="key"
class="flex gap-4 w-full mb-2"
>
<el-input
v-model="condition.from"
placeholder="需要从查询条件取的json key"
/>
<el-input v-model="condition.column" placeholder="表对应的column" />
<el-select
v-model="condition.operator"
placeholder="请选择查询条件"
>
<el-option
v-for="item in typeSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button
type="danger"
icon="delete"
@click="() => formData.conditions.splice(key, 1)"
>删除</el-button
>
</div>
<div class="flex justify-end w-full">
<el-button type="primary" icon="plus" @click="addCondition"
>添加条件</el-button
>
</div>
</el-form-item>
</el-form>
</el-drawer>
<el-drawer
v-model="codeVisible"
size="60%"
:before-close="closeDialog"
:title="type === 'create' ? '添加' : '修改'"
:show-close="false"
destroy-on-close
>
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">模板</span>
<div>
<el-button type="primary" @click="closeDialog">确 定</el-button>
</div>
</div>
</template>
<v-ace-editor
v-model:value="webCode"
lang="vue"
theme="github_dark"
class="h-full"
/>
</el-drawer>
</div>
</template>
<script setup>
import {
createSysExportTemplate,
deleteSysExportTemplate,
deleteSysExportTemplateByIds,
updateSysExportTemplate,
findSysExportTemplate,
getSysExportTemplateList
} from '@/api/exportTemplate.js'
// 全量引入格式化工具 请按需保留
import { formatDate } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { getDB, getTable, getColumn, butler } from '@/api/autoCode'
import { getCode } from './code'
import { VAceEditor } from 'vue3-ace-editor'
import 'ace-builds/src-noconflict/mode-vue'
import 'ace-builds/src-noconflict/theme-github_dark'
defineOptions({
name: 'ExportTemplate'
})
const templatePlaceholder = `模板信息格式key标识数据库column列名称在join模式下需要写为 table.columnvalue标识导出excel列名称如key为数据库关键字或函数请按照关键字的处理模式处理当前以mysql为例如下
{
"table_column1":"第一列",
"table_column3":"第三列",
"table_column4":"第四列",
"\`rows\`":"我属于数据库关键字或函数",
}
如果增加了JOINS导出key应该列为 {table_name1.table_column1:"第一列",table_name2.table_column2:"第二列"}
如果有重复的列名导出格式应为 {table_name1.table_column1 as key:"第一列",table_name2.table_column2 as key2:"第二列"}
JOINS模式下不支持导入
`
// 自动化生成的字典(可能为空)以及字段
const formData = ref({
name: '',
tableName: '',
dbName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
joinTemplate: []
})
const prompt = ref('')
const tables = ref([])
const typeSearchOptions = ref([
{
label: '=',
value: '='
},
{
label: '<>',
value: '<>'
},
{
label: '>',
value: '>'
},
{
label: '<',
value: '<'
},
{
label: 'LIKE',
value: 'LIKE'
},
{
label: 'BETWEEN',
value: 'BETWEEN'
},
{
label: 'NOT BETWEEN',
value: 'NOT BETWEEN'
}
])
const addCondition = () => {
formData.value.conditions.push({
from: '',
column: '',
operator: ''
})
}
const addJoin = () => {
formData.value.joinTemplate.push({
joins: 'LEFT JOIN',
table: '',
on: ''
})
}
// 验证规则
const rule = reactive({
name: [
{
required: true,
message: '',
trigger: ['input', 'blur']
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur']
}
],
tableName: [
{
required: true,
message: '',
trigger: ['input', 'blur']
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur']
}
],
templateID: [
{
required: true,
message: '',
trigger: ['input', 'blur']
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur']
}
],
templateInfo: [
{
required: true,
message: '',
trigger: ['input', 'blur']
},
{
whitespace: true,
message: '不能只输入空格',
trigger: ['input', 'blur']
}
]
})
const searchRule = reactive({
createdAt: [
{
validator: (rule, value, callback) => {
if (
searchInfo.value.startCreatedAt &&
!searchInfo.value.endCreatedAt
) {
callback(new Error('请填写结束日期'))
} else if (
!searchInfo.value.startCreatedAt &&
searchInfo.value.endCreatedAt
) {
callback(new Error('请填写开始日期'))
} else if (
searchInfo.value.startCreatedAt &&
searchInfo.value.endCreatedAt &&
(searchInfo.value.startCreatedAt.getTime() ===
searchInfo.value.endCreatedAt.getTime() ||
searchInfo.value.startCreatedAt.getTime() >
searchInfo.value.endCreatedAt.getTime())
) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
},
trigger: 'change'
}
]
})
const elFormRef = ref()
const elSearchFormRef = ref()
// =========== 表格控制部分 ===========
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
const dbList = ref([])
const tableOptions = ref([])
const aiLoading = ref(false)
const getTablesCloumn = async () => {
const tablesMap = {}
const promises = tables.value.map(async (item) => {
const res = await getColumn({
businessDB: formData.value.dbName,
tableName: item
})
if (res.code === 0) {
tablesMap[item] = res.data.columns
}
})
await Promise.all(promises)
return tablesMap
}
const autoExport = async () => {
if (tables.value.length === 0) {
ElMessage({
type: 'error',
message: '请先选择需要参与导出的表'
})
return
}
aiLoading.value = true
const tableMap = await getTablesCloumn()
const aiRes = await butler({
prompt: prompt.value,
businessDB: formData.value.dbName || '',
tableMap: tableMap,
command: 'autoExportTemplate'
})
aiLoading.value = false
if (aiRes.code === 0) {
const aiData = JSON.parse(aiRes.data)
formData.value.name = aiData.name
formData.value.tableName = aiData.tableName
formData.value.templateID = aiData.templateID
formData.value.templateInfo = JSON.stringify(aiData.templateInfo, null, 2)
formData.value.joinTemplate = aiData.joinTemplate
}
}
const getDbFunc = async () => {
const res = await getDB()
if (res.code === 0) {
dbList.value = res.data.dbList
}
}
getDbFunc()
const dbNameChange = () => {
formData.value.tableName = ''
formData.value.templateInfo = ''
tables.value = []
getTableFunc()
}
const getTableFunc = async () => {
const res = await getTable({ businessDB: formData.value.dbName })
if (res.code === 0) {
tableOptions.value = res.data.tables
}
formData.value.tableName = ''
}
getTableFunc()
const getColumnFunc = async (aiFLag) => {
if (!formData.value.tableName) {
ElMessage({
type: 'error',
message: '请先选择业务库及选择表后再进行操作'
})
return
}
formData.value.templateInfo = ''
aiLoading.value = true
const res = await getColumn({
businessDB: formData.value.dbName,
tableName: formData.value.tableName
})
if (res.code === 0) {
if (aiFLag) {
const aiRes = await butler({
data: res.data.columns,
command: 'exportCompletion'
})
if (aiRes.code === 0) {
const aiData = JSON.parse(aiRes.data)
aiLoading.value = false
formData.value.templateInfo = JSON.stringify(
aiData.templateInfo,
null,
2
)
formData.value.name = aiData.name
formData.value.templateID = aiData.templateID
return
}
ElMessage.warning('AI自动补全失败已调整为逻辑填写')
}
// 把返回值的data.columns做尊换制作一组JSON数据columnName做keycolumnComment做value
const templateInfo = {}
res.data.columns.forEach((item) => {
templateInfo[item.columnName] = item.columnComment || item.columnName
})
formData.value.templateInfo = JSON.stringify(templateInfo, null, 2)
}
aiLoading.value = false
}
// 重置
const onReset = () => {
searchInfo.value = {}
getTableData()
}
// 搜索
const onSubmit = () => {
elSearchFormRef.value?.validate(async (valid) => {
if (!valid) return
page.value = 1
getTableData()
})
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// 修改页面容量
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async () => {
const table = await getSysExportTemplateList({
page: page.value,
pageSize: pageSize.value,
...searchInfo.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
// ============== 表格控制部分结束 ===============
// 获取需要的字典 可能为空 按需保留
const setOptions = async () => {}
// 获取需要的字典 可能为空 按需保留
setOptions()
// 多选数据
const multipleSelection = ref([])
// 多选
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
// 删除行
const deleteRow = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteSysExportTemplateFunc(row)
})
}
// 多选删除
const onDelete = async () => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const ids = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
multipleSelection.value &&
multipleSelection.value.map((item) => {
ids.push(item.ID)
})
const res = await deleteSysExportTemplateByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
const type = ref('')
// 复制
const copyFunc = async (row) => {
let copyData
const res = await findSysExportTemplate({ ID: row.ID })
if (res.code === 0) {
copyData = JSON.parse(JSON.stringify(res.data.resysExportTemplate))
if (!copyData.conditions) {
copyData.conditions = []
}
if (!copyData.joinTemplate) {
copyData.joinTemplate = []
}
delete copyData.ID
delete copyData.CreatedAt
delete copyData.UpdatedAt
copyData.templateID = copyData.templateID + '_copy'
copyData.name = copyData.name + '_copy'
formData.value = copyData
dialogFormVisible.value = true
}
}
// 更新行
const updateSysExportTemplateFunc = async (row) => {
const res = await findSysExportTemplate({ ID: row.ID })
type.value = 'update'
if (res.code === 0) {
formData.value = res.data.resysExportTemplate
if (!formData.value.conditions) {
formData.value.conditions = []
}
if (!formData.value.joinTemplate) {
formData.value.joinTemplate = []
}
dialogFormVisible.value = true
}
}
// 删除行
const deleteSysExportTemplateFunc = async (row) => {
const res = await deleteSysExportTemplate({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
}
const codeVisible = ref(false)
// 弹窗控制标记
const dialogFormVisible = ref(false)
const webCode = ref('')
const showCode = (row) => {
webCode.value = getCode(row.templateID)
codeVisible.value = true
}
// 打开弹窗
const openDialog = () => {
type.value = 'create'
dialogFormVisible.value = true
}
// 关闭弹窗
const closeDialog = () => {
codeVisible.value = false
dialogFormVisible.value = false
formData.value = {
name: '',
tableName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
joinTemplate: []
}
}
// 弹窗确定
const enterDialog = async () => {
// 判断 formData.templateInfo 是否为标准json格式 如果不是标准json 则辅助调整
try {
JSON.parse(formData.value.templateInfo)
} catch (_) {
ElMessage({
type: 'error',
message: '模板信息格式不正确,请检查'
})
return
}
const reqData = JSON.parse(JSON.stringify(formData.value))
for (let i = 0; i < reqData.conditions.length; i++) {
if (
!reqData.conditions[i].from ||
!reqData.conditions[i].column ||
!reqData.conditions[i].operator
) {
ElMessage({
type: 'error',
message: '请填写完整的导出条件'
})
return
}
reqData.conditions[i].templateID = reqData.templateID
}
for (let i = 0; i < reqData.joinTemplate.length; i++) {
if (!reqData.joinTemplate[i].joins || !reqData.joinTemplate[i].on) {
ElMessage({
type: 'error',
message: '请填写完整的关联'
})
return
}
reqData.joinTemplate[i].templateID = reqData.templateID
}
elFormRef.value?.validate(async (valid) => {
if (!valid) return
let res
switch (type.value) {
case 'create':
res = await createSysExportTemplate(reqData)
break
case 'update':
res = await updateSysExportTemplate(reqData)
break
default:
res = await createSysExportTemplate(reqData)
break
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
closeDialog()
getTableData()
}
})
}
</script>
<style></style>

View File

@@ -0,0 +1,18 @@
<template>
<fc-designer ref="designer" :config="config" height="calc(100vh - 160px)" />
</template>
<script setup>
import { ref } from 'vue'
import FcDesigner from '@form-create/designer'
defineOptions({
name: 'FormGenerator'
})
const designer = ref(null)
const config = {
fieldReadonly: false
}
</script>

View File

@@ -0,0 +1,20 @@
<template>
<div>
<router-view v-slot="{ Component }">
<transition mode="out-in" name="el-fade-in-linear">
<keep-alive :include="routerStore.keepAliveRouters">
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</div>
</template>
<script setup>
import { useRouterStore } from '@/pinia/modules/router'
const routerStore = useRouterStore()
defineOptions({
name: 'System'
})
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div class="gva-form-box">
<el-upload
drag
:action="`${getBaseUrl()}/autoCode/installPlugin`"
:show-file-list="false"
:on-success="handleSuccess"
:on-error="handleSuccess"
:headers="{'x-token': token}"
name="plug"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">拖拽或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">请把安装包的zip拖拽至此处上传</div>
</template>
</el-upload>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { getBaseUrl } from '@/utils/format'
import { useUserStore } from "@/pinia";
const userStore = useUserStore()
const token = userStore.token
const handleSuccess = (res) => {
if (res.code === 0) {
let msg = ``
res.data &&
res.data.forEach((item, index) => {
msg += `${index + 1}.${item.msg}\n`
})
alert(msg)
} else {
ElMessage.error(res.msg)
}
}
</script>

View File

@@ -0,0 +1,225 @@
<template>
<div class="gva-form-box">
<div class="p-4 bg-white dark:bg-slate-900">
<WarningBar
title="目前只支持标准插件(通过插件模板生成的标准目录插件),非标准插件请自行打包"
/>
<div class="flex items-center gap-3">
<el-input v-model="plugName" placeholder="插件模板处填写的【插件名】" />
</div>
<el-card class="mt-2 text-center">
<WarningBar title="穿梭框请只选择子级菜单即可" />
<el-input
v-model="parentMenu"
placeholder="请输入菜单组名,例:公告管理"
class="mb-2"
></el-input>
<el-transfer
v-model="menus"
:props="{
key: 'ID'
}"
class="plugin-transfer"
:data="menusData"
filterable
:filter-method="filterMenuMethod"
filter-placeholder="请输入菜单名称/路径"
:titles="['可选菜单', '使用菜单']"
:button-texts="['移除', '选中']"
>
<template #default="{ option }">
{{ option.meta.title }} {{ option.component }}
</template>
</el-transfer>
<div class="flex justify-end mt-2">
<el-button type="primary" @click="fmtInitMenu">
定义安装菜单
</el-button>
</div>
</el-card>
<el-card class="mt-2 text-center">
<el-transfer
v-model="apis"
:props="{
key: 'ID'
}"
class="plugin-transfer"
:data="apisData"
filterable
:filter-method="filterApiMethod"
filter-placeholder="请输入API描述/PATH"
:titles="['可选API', '使用API']"
:button-texts="['移除', '选中']"
>
<template #default="{ option }">
{{ option.description }} {{ option.path }}
</template>
</el-transfer>
<div class="flex justify-end mt-2">
<el-button type="primary" @click="fmtInitAPI">
定义安装API
</el-button>
</div>
</el-card>
</div>
<div class="flex justify-end">
<el-button type="primary" @click="pubPlugin"> 打包插件 </el-button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { pubPlug, initMenu, initAPI } from '@/api/autoCode.js'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getAllApis } from '@/api/api'
import { getMenuList } from '@/api/menu'
const plugName = ref('')
const menus = ref([])
const menusData = ref([])
const apis = ref([])
const apisData = ref([])
const parentMenu = ref('')
const fmtMenu = (menus) => {
// 如果menu存在children递归展开到一级
const res = []
menus.forEach((item) => {
if (item.children) {
res.push(...fmtMenu(item.children))
} else {
res.push(item)
}
})
return res
}
const initData = async () => {
const menuRes = await getMenuList()
if (menuRes.code === 0) {
menusData.value = fmtMenu(menuRes.data)
}
const apiRes = await getAllApis()
if (apiRes.code === 0) {
apisData.value = apiRes.data.apis
}
}
const filterMenuMethod = (query, item) => {
return (
item.meta.title.indexOf(query) > -1 || item.component.indexOf(query) > -1
)
}
const filterApiMethod = (query, item) => {
return item.description.indexOf(query) > -1 || item.path.indexOf(query) > -1
}
initData()
const pubPlugin = async () => {
ElMessageBox.confirm(
`请检查server下的/plugin/${plugName.value}/plugin.go是否已放开需要的 initialize.Api(ctx) 和 initialize.Menu(ctx)?`,
'打包',
{
confirmButtonText: '打包',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(async () => {
const res = await pubPlug({ plugName: plugName.value })
if (res.code === 0) {
ElMessage.success(res.msg)
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '关闭打包'
})
})
}
const fmtInitMenu = () => {
if (!parentMenu.value) {
ElMessage.error('请填写菜单组名')
return
}
if (menus.value.length === 0) {
ElMessage.error('请至少选择一个菜单')
return
}
if (plugName.value === '') {
ElMessage.error('请填写插件名')
return
}
ElMessageBox.confirm(
`点击后将会覆盖server下的/plugin/${plugName.value}/initialize/menu. 是否继续?`,
'生成初始菜单',
{
confirmButtonText: '生成',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
const req = {
plugName: plugName.value,
parentMenu: parentMenu.value,
menus: menus.value
}
initMenu(req)
})
.catch(() => {
ElMessage({
type: 'info',
message: '关闭生成菜单'
})
})
}
const fmtInitAPI = () => {
if (apis.value.length === 0) {
ElMessage.error('请至少选择一个API')
return
}
if (plugName.value === '') {
ElMessage.error('请填写插件名')
return
}
ElMessageBox.confirm(
`点击后将会覆盖server下的/plugin/${plugName.value}/initialize/api. 是否继续?`,
'生成初始API',
{
confirmButtonText: '生成',
cancelButtonText: '取消',
type: 'warning'
}
)
.then(() => {
const req = {
plugName: plugName.value,
apis: apis.value
}
initAPI(req)
console.log(req)
})
.catch(() => {
ElMessage({
type: 'info',
message: '关闭生成API'
})
})
}
</script>
<style lang="scss">
.plugin-transfer {
.el-transfer-panel {
width: 400px !important;
}
}
</style>

File diff suppressed because it is too large Load Diff