文件结构调整,支持插件自动化 (#1824)

* Update index.vue

多个详情页之间切换tab,页面没有重新渲染

* feature:新增同步API功能

* feature: 同步表数据

* feature:新增同步API功能

* feature: 增加V2插件注册

* feature:给Enter的依赖结构增加单独的New 方便引用

* feature: 调整关联属性的选择模式

* feature: 增加component组件和name的映射插件,防止keepalive的懒加载失效。

* update: PluginInitializeRouter  && 修复TypePluginInitializeMenu ast 类型错误

* update: 测试文件的astType 类型错误

* feature: 文件变更自动同步componentName.json。

* feature: 文件变更自动同步componentName.json。

* feat: UI美化

* feat: 自动化页面顺序调整

* feature:修改404页面

* update: PluginInitializeMenu

* update: Plugin template

* fixed systemApi 重复声明

* api.vue:update:修改API分组为下拉列表

* update: import添加注释

* update: plugin_enter_test.go 增加测试用例

* update: ast 预览文件路径

* update: config Autocode 新增Module字段以及如果为空的情况下自动获取运行目录下的go.mod文件

* update: auto_code_package.go 完善调用ast工具类的封装使用

* update: auto_code_template.go Create方法和修正SysAutoCodeHistory

* feat:调整自动化package为模板,增加初始化配置信息,调整页面信息。

* update: ast PreviewPath MkdirAll

* update: ast type错误, PluginEnter and PackageModuleEnter add TemplatePath模版路径

* update: autoCodePackage and autoCodeTemplate bug修正

* update: PackageInitializeRouter 传入两个路由组

* update: PackageModuleEnter 处理空变量时与type冲突注入

* update: Package 模版更新

* update: utils/ast 优化统一

* update: 注入内容修复错误

* fix: 修复注释错误

* update: plugin 模版 完成

* feature: 文件watch功能只在development下开启

* update: viper.go.template 因为viper不区分配置的key的大小写所以用package

* update: ast 测试代码规范化

* update: package 删除api和router多余导包

* update: plugin template

* update: auto_code_package 问题修复

* update: ast 测试插件的预览功能

* update: gorm_biz 更新注册方式

* update: go.mod tidy

* remove: plugin template gen main.go.template

* update: ast 重构, 分离读取和写入步骤支持

* update: AutoCodePackageApi 传入参数错误修复

* rename: sys_autocode_history.go => sys_auto_code_history.go

* update: 预览无需落盘, 创建落盘,抽离公共参数

* update: api.go.tpl 导包位置fmt 和package js位置存放错误

* update: 测试用例修复 and PackageInitializeGorm 重构

* update: ast 新增相对路径, 代码生成器历史回滚功能

* update: ast 工具类回滚失败修复以及测试文件

* update: 代码生成器历史 回滚问题修复

* update: 代码生成器模版忽略.DS_Store

* featute: 自动化GORM结构的注入和剔除

* feature: 插件模板调整

* feature: 增加公告插件示例,调整代码模板。

* feature: 自动注册插件V2。

---------

Co-authored-by: zayn <972858472@qq.com>
Co-authored-by: SliverHorn <sliver_horn@qq.com>
Co-authored-by: krank <emosick@qq.com>
Co-authored-by: cjk <wlicjk@126.com>
Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: maxwell <zhong.maxwell@gmail.com>
This commit is contained in:
PiexlMax(奇淼
2024-07-21 11:33:25 +08:00
committed by GitHub
parent 6b3a9024d3
commit 6e4dc10c49
193 changed files with 9410 additions and 2904 deletions

View File

@@ -2,9 +2,9 @@
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*"]
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "gin-vue-admin",
"version": "2.6.7",
"version": "2.7.0",
"private": true,
"scripts": {
"serve": "node openDocument.js && vite --host --mode development",
@@ -20,7 +20,7 @@
"axios": "^1.4.0",
"core-js": "^3.31.1",
"echarts": "5.4.3",
"element-plus": "^2.7.0",
"element-plus": "^2.7.4",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
"marked": "4.3.0",

View File

@@ -117,11 +117,10 @@ export const deletePackageApi = (data) => {
})
}
export const createPlugApi = (data) => {
export const getTemplatesApi = () => {
return service({
url: '/autoCode/createPlug',
method: 'post',
data
url: '/autoCode/getTemplates',
method: 'get'
})
}

BIN
web/src/assets/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -20,7 +20,7 @@ export const viteLogo = (env) => {
)
console.log(
chalk.green(
`> 当前版本:v2.6.7`
`> 当前版本:v2.7.0`
)
)
console.log(

View File

@@ -10,7 +10,7 @@ export default {
register(app)
console.log(`
欢迎使用 Gin-Vue-Admin
当前版本:v2.6.7
当前版本:v2.7.0
加群方式:微信shouzi_1994 QQ群622360840
项目地址https://github.com/flipped-aurora/gin-vue-admin
插件市场:https://plugin.gin-vue-admin.com

48
web/src/pathInfo.json Normal file
View File

@@ -0,0 +1,48 @@
{
"/src/view/about/index.vue": "About",
"/src/view/error/index.vue": "Error",
"/src/view/error/reload.vue": "Reload",
"/src/view/example/breakpoint/breakpoint.vue": "BreakPoint",
"/src/view/example/customer/customer.vue": "Customer",
"/src/view/example/index.vue": "Example",
"/src/view/example/upload/upload.vue": "Upload",
"/src/view/init/index.vue": "Init",
"/src/view/layout/aside/asideComponent/asyncSubmenu.vue": "AsyncSubmenu",
"/src/view/layout/aside/asideComponent/index.vue": "AsideComponent",
"/src/view/layout/aside/asideComponent/menuItem.vue": "MenuItem",
"/src/view/layout/aside/combinationMode.vue": "GvaAside",
"/src/view/layout/aside/headMode.vue": "GvaAside",
"/src/view/layout/aside/normalMode.vue": "GvaAside",
"/src/view/layout/index.vue": "GvaLayout",
"/src/view/layout/screenfull/index.vue": "Screenfull",
"/src/view/layout/search/search.vue": "BtnBox",
"/src/view/layout/setting/index.vue": "GvaSetting",
"/src/view/layout/tabs/index.vue": "HistoryComponent",
"/src/view/login/index.vue": "Login",
"/src/view/person/person.vue": "Person",
"/src/view/routerHolder.vue": "RouterHolder",
"/src/view/superAdmin/api/api.vue": "Api",
"/src/view/superAdmin/authority/authority.vue": "Authority",
"/src/view/superAdmin/authority/components/apis.vue": "Apis",
"/src/view/superAdmin/authority/components/datas.vue": "Datas",
"/src/view/superAdmin/authority/components/menus.vue": "Menus",
"/src/view/superAdmin/dictionary/sysDictionary.vue": "SysDictionary",
"/src/view/superAdmin/dictionary/sysDictionaryDetail.vue": "SysDictionaryDetail",
"/src/view/superAdmin/index.vue": "SuperAdmin",
"/src/view/superAdmin/menu/icon.vue": "Icon",
"/src/view/superAdmin/menu/menu.vue": "Menus",
"/src/view/superAdmin/operation/sysOperationRecord.vue": "SysOperationRecord",
"/src/view/superAdmin/user/user.vue": "User",
"/src/view/system/state.vue": "State",
"/src/view/systemTools/autoCode/component/fieldDialog.vue": "FieldDialog",
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
"/src/view/systemTools/autoCodeAdmin/index.vue": "AutoCodeAdmin",
"/src/view/systemTools/autoPkg/autoPkg.vue": "AutoPkg",
"/src/view/systemTools/exportTemplate/exportTemplate.vue": "ExportTemplate",
"/src/view/systemTools/formCreate/index.vue": "FormGenerator",
"/src/view/systemTools/index.vue": "System",
"/src/view/systemTools/system/system.vue": "Config",
"/src/plugin/announcement/form/info.vue": "InfoForm",
"/src/plugin/announcement/view/info.vue": "Info",
"/src/plugin/email/view/index.vue": "Email"
}

View File

@@ -2,7 +2,8 @@ import { asyncRouterHandle } from '@/utils/asyncRouter'
import { emitter } from '@/utils/bus.js'
import { asyncMenu } from '@/api/menu'
import { defineStore } from 'pinia'
import { ref,watchEffect,onMounted } from 'vue'
import { ref,watchEffect } from 'vue'
import pathInfo from "@/pathInfo.json";
const notLayoutRouterArr = []
const keepAliveRoutersArr = []
@@ -30,10 +31,11 @@ const KeepAliveFilter = (routes) => {
routes && routes.forEach(item => {
// 子菜单中有 keep-alive 的,父菜单也必须 keep-alive否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。
if ((item.children && item.children.some(ch => ch.meta.keepAlive) || item.meta.keepAlive)) {
item.component && item.component().then(val => {
keepAliveRoutersArr.push(val.default.name)
nameMap[item.name] = val.default.name
})
const regex = /\(\) => import\("([^?]+)\??.*"\)/;
const match = String(item.component).match(regex);
const path = match ? match[1] : "";
keepAliveRoutersArr.push(pathInfo[path])
nameMap[item.name] = pathInfo[path]
}
if (item.children && item.children.length > 0) {
KeepAliveFilter(item.children)

View File

@@ -0,0 +1,110 @@
import service from '@/utils/request'
// @Tags Info
// @Summary 创建公告
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body model.Info true "创建公告"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
// @Router /info/createInfo [post]
export const createInfo = (data) => {
return service({
url: '/info/createInfo',
method: 'post',
data
})
}
// @Tags Info
// @Summary 删除公告
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body model.Info true "删除公告"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
// @Router /info/deleteInfo [delete]
export const deleteInfo = (params) => {
return service({
url: '/info/deleteInfo',
method: 'delete',
params
})
}
// @Tags Info
// @Summary 批量删除公告
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body request.IdsReq true "批量删除公告"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
// @Router /info/deleteInfo [delete]
export const deleteInfoByIds = (params) => {
return service({
url: '/info/deleteInfoByIds',
method: 'delete',
params
})
}
// @Tags Info
// @Summary 更新公告
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data body model.Info true "更新公告"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
// @Router /info/updateInfo [put]
export const updateInfo = (data) => {
return service({
url: '/info/updateInfo',
method: 'put',
data
})
}
// @Tags Info
// @Summary 用id查询公告
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data query model.Info true "用id查询公告"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /info/findInfo [get]
export const findInfo = (params) => {
return service({
url: '/info/findInfo',
method: 'get',
params
})
}
// @Tags Info
// @Summary 分页获取公告列表
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param data query request.PageInfo true "分页获取公告列表"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
// @Router /info/getInfoList [get]
export const getInfoList = (params) => {
return service({
url: '/info/getInfoList',
method: 'get',
params
})
}
// @Tags Info
// @Summary 获取数据源
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
// @Router /info/findInfoDataSource [get]
export const getInfoDataSource = () => {
return service({
url: '/info/getInfoDataSource',
method: 'get',
})
}

View File

@@ -0,0 +1,121 @@
<template>
<div>
<div class="gva-form-box">
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
<el-form-item label="标题:" prop="title">
<el-input v-model="formData.title" :clearable="true" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content"/>
</el-form-item>
<el-form-item label="作者:" prop="userID">
<el-select v-model="formData.userID" placeholder="请选择作者" style="width:100%" :clearable="true" >
<el-option v-for="(item,key) in dataSource.userID" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="附件:" prop="attachments">
<SelectFile v-model="formData.attachments" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="back">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import {
getInfoDataSource,
createInfo,
updateInfo,
findInfo
} from '@/plugin/announcement/api/info'
defineOptions({
name: 'InfoForm'
})
// 自动获取字典
import { getDictFunc } from '@/utils/format'
import { useRoute, useRouter } from "vue-router"
import { ElMessage } from 'element-plus'
import { ref, reactive } from 'vue'
import SelectFile from '@/components/selectFile/selectFile.vue'
// 富文本组件
import RichEdit from '@/components/richtext/rich-edit.vue'
const route = useRoute()
const router = useRouter()
const type = ref('')
const formData = ref({
title: '',
content: '',
userID: undefined,
attachments: [],
})
// 验证规则
const rule = reactive({
})
const elFormRef = ref()
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await getInfoDataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
// 初始化方法
const init = async () => {
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
if (route.query.id) {
const res = await findInfo({ ID: route.query.id })
if (res.code === 0) {
formData.value = res.data
type.value = 'update'
}
} else {
type.value = 'create'
}
}
init()
// 保存按钮
const save = async() => {
elFormRef.value?.validate( async (valid) => {
if (!valid) return
let res
switch (type.value) {
case 'create':
res = await createInfo(formData.value)
break
case 'update':
res = await updateInfo(formData.value)
break
default:
res = await createInfo(formData.value)
break
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
}
})
}
// 返回按钮
const back = () => {
router.go(-1)
}
</script>
<style>
</style>

View File

@@ -0,0 +1,418 @@
<template>
<div>
<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>
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
</template>
<el-form-item>
<el-button type="primary" icon="search" @click="onSubmit">
查询
</el-button>
<el-button icon="refresh" @click="onReset">
重置
</el-button>
<el-button v-if="!showAllQuery" link type="primary" icon="arrow-down" @click="showAllQuery=true">
展开
</el-button>
<el-button v-else link type="primary" icon="arrow-up" @click="showAllQuery=false">
收起
</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="日期" prop="createdAt" width="180">
<template #default="scope">
{{ formatDate(scope.row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column align="left" label="标题" prop="title" width="120" />
<el-table-column label="内容" prop="content" width="200">
<template #default="scope">
[富文本内容]
</template>
</el-table-column>
<el-table-column align="left" label="作者" prop="userID" width="120">
<template #default="scope">
<span>{{ filterDataSource(dataSource.userID,scope.row.userID) }}</span>
</template>
</el-table-column>
<el-table-column label="附件" prop="attachments" width="200">
<template #default="scope">
<div class="file-list">
<el-tag v-for="file in scope.row.attachments" :key="file.uid" @click="downloadFile(file.url)">
{{ file.name }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column align="left" label="操作" fixed="right" min-width="240">
<template #default="scope">
<el-button type="primary" link icon="edit" class="table-button" @click="updateInfoFunc(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" destroy-on-close size="800" :show-close="false" :before-close="closeDialog">
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span>
<div>
<el-button type="primary" @click="enterDialog">
</el-button>
<el-button @click="closeDialog">
</el-button>
</div>
</div>
</template>
<el-form ref="elFormRef" :model="formData" label-position="top" :rules="rule" label-width="80px">
<el-form-item label="标题:" prop="title">
<el-input v-model="formData.title" :clearable="true" placeholder="请输入标题" />
</el-form-item>
<el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content" />
</el-form-item>
<el-form-item label="作者:" prop="userID">
<el-select v-model="formData.userID" placeholder="请选择作者" style="width:100%" :clearable="true">
<el-option v-for="(item,key) in dataSource.userID" :key="key" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="附件:" prop="attachments">
<SelectFile v-model="formData.attachments" />
</el-form-item>
</el-form>
</el-drawer>
</div>
</template>
<script setup>
import {
getInfoDataSource,
createInfo,
deleteInfo,
deleteInfoByIds,
updateInfo,
findInfo,
getInfoList
} from '@/plugin/announcement/api/info'
import { getUrl } from '@/utils/image'
// 富文本组件
import RichEdit from '@/components/richtext/rich-edit.vue'
// 文件选择组件
import SelectFile from '@/components/selectFile/selectFile.vue'
// 全量引入格式化工具 请按需保留
import { formatDate, filterDataSource } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
defineOptions({
name: 'Info'
})
// 控制更多查询条件显示/隐藏状态
const showAllQuery = ref(false)
// 自动化生成的字典(可能为空)以及字段
const formData = ref({
title: '',
content: '',
userID: undefined,
attachments: [],
})
const dataSource = ref([])
const getDataSourceFunc = async()=>{
const res = await getInfoDataSource()
if (res.code === 0) {
dataSource.value = res.data
}
}
getDataSourceFunc()
// 验证规则
const rule = reactive({
})
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 onReset = () => {
searchInfo.value = {}
getTableData()
}
// 搜索
const onSubmit = () => {
elSearchFormRef.value?.validate(async(valid) => {
if (!valid) return
page.value = 1
pageSize.value = 10
getTableData()
})
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// 修改页面容量
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async() => {
const table = await getInfoList({ 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(() => {
deleteInfoFunc(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 deleteInfoByIds({ 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 updateInfoFunc = async(row) => {
const res = await findInfo({ ID: row.ID })
type.value = 'update'
if (res.code === 0) {
formData.value = res.data
dialogFormVisible.value = true
}
}
// 删除行
const deleteInfoFunc = async (row) => {
const res = await deleteInfo({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
}
// 弹窗控制标记
const dialogFormVisible = ref(false)
// 打开弹窗
const openDialog = () => {
type.value = 'create'
dialogFormVisible.value = true
}
// 关闭弹窗
const closeDialog = () => {
dialogFormVisible.value = false
formData.value = {
title: '',
content: '',
userID: undefined,
attachments: [],
}
}
// 弹窗确定
const enterDialog = async () => {
elFormRef.value?.validate( async (valid) => {
if (!valid) return
let res
switch (type.value) {
case 'create':
res = await createInfo(formData.value)
break
case 'update':
res = await updateInfo(formData.value)
break
default:
res = await createInfo(formData.value)
break
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: '创建/更改成功'
})
closeDialog()
getTableData()
}
})
}
const downloadFile = (url) => {
window.open(getUrl(url), '_blank')
}
</script>
<style>
.file-list{
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.fileBtn{
margin-bottom: 10px;
}
.fileBtn:last-child{
margin-bottom: 0;
}
</style>

View File

@@ -2,14 +2,10 @@
<div>
<div class="w-full h-screen bg-gray-50 flex items-center justify-center">
<div class="flex flex-col items-center text-2xl gap-4">
<img src="../../assets/notFound.png">
<p>页面被神秘力量吸走了如果您是开源版请联系我们修复</p>
<p style="font-size:18px;line-height:40px;">常见问题为当前此角色无当前路由如果确定要使用本路由请到角色管理进行分配</p>
<p></p>
<img
src="../../assets/qm.png"
class="w-16 h-16 mt-20"
>
<img class="w-1/3" src="../../assets/404.png">
<p class="text-lg">页面被神秘力量吸走了</p>
<p class="text-lg">常见问题为当前此角色无当前路由如果确定要使用本路由请到角色管理进行分配</p>
<p>项目地址<a href="https://github.com/flipped-aurora/gin-vue-admin" target="_blank" class="text-blue-600 ">https://github.com/flipped-aurora/gin-vue-admin</a></p>
<el-button @click="toDashboard">返回首页</el-button>
</div>
</div>

View File

@@ -18,11 +18,19 @@
placeholder="描述"
/>
</el-form-item>
<el-form-item label="API组">
<el-input
v-model="searchInfo.apiGroup"
placeholder="api组"
/>
<el-form-item label="API组">
<el-select
v-model="searchInfo.apiGroup"
clearable
placeholder="请选择"
>
<el-option
v-for="item in apiGroupOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="请求">
<el-select

View File

@@ -1,10 +1,11 @@
<template>
<div>
<div class="w-full">
<el-select
v-model="metaData.icon"
clearable
filterable
placeholder="请选择"
class="w-full"
>
<template #prefix>
<el-icon>

View File

@@ -6,7 +6,9 @@
type="primary"
icon="plus"
@click="addMenu(0)"
>新增根菜单</el-button>
>
新增根菜单
</el-button>
<el-icon
class="cursor-pointer"
@click="
@@ -14,7 +16,9 @@
'https://www.bilibili.com/video/BV1kv4y1g7nT/?p=4&vd_source=f2640257c21e3b547a790461ed94875e'
)
"
><VideoCameraFilled /></el-icon>
>
<VideoCameraFilled />
</el-icon>
</div>
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
@@ -110,19 +114,25 @@
link
icon="plus"
@click="addMenu(scope.row.ID)"
>添加子菜单</el-button>
>
添加子菜单
</el-button>
<el-button
type="primary"
link
icon="edit"
@click="editMenu(scope.row.ID)"
>编辑</el-button>
>
编辑
</el-button>
<el-button
type="primary"
link
icon="delete"
@click="deleteMenu(scope.row.ID)"
>删除</el-button>
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
@@ -137,11 +147,15 @@
<div class="flex justify-between items-center">
<span class="text-lg">{{ dialogTitle }}</span>
<div>
<el-button @click="closeDialog"> </el-button>
<el-button @click="closeDialog">
</el-button>
<el-button
type="primary"
@click="enterDialog"
> </el-button>
>
</el-button>
</div>
</div>
</template>
@@ -154,216 +168,251 @@
:model="form"
:rules="rules"
label-position="top"
label-width="85px"
>
<el-form-item
label="路由Name"
prop="path"
style="width: 30%"
>
<el-input
v-model="form.name"
autocomplete="off"
placeholder="唯一英文字符串"
@change="changeName"
/>
</el-form-item>
<el-form-item
prop="path"
style="width: 30%"
>
<template #label>
<span style="display: inline-flex; align-items: center">
<span>路由Path</span>
<el-checkbox
v-model="checkFlag"
style="margin-left: 12px; height: auto"
>添加参数</el-checkbox>
</span>
</template>
<el-input
v-model="form.path"
:disabled="!checkFlag"
autocomplete="off"
placeholder="建议只在后方拼接参数"
/>
</el-form-item>
<el-form-item
label="是否隐藏"
style="width: 30%"
>
<el-select
v-model="form.hidden"
placeholder="是否在列表隐藏"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
<el-form-item
label="父节点ID"
style="width: 30%"
>
<el-cascader
v-model="form.parentId"
style="width: 100%"
:disabled="!isEdit"
:options="menuOption"
:props="{
checkStrictly: true,
label: 'title',
value: 'ID',
disabled: 'disabled',
emitPath: false,
}"
:show-all-levels="false"
filterable
/>
</el-form-item>
<el-form-item
label="文件路径"
prop="component"
style="width: 60%"
>
<el-input
v-model="form.component"
autocomplete="off"
placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue"
@blur="fmtComponent"
/>
<span style="font-size: 12px; margin-right: 12px">如果菜单包含子菜单请创建router-view二级路由页面或者</span><el-button
style="margin-top: 4px"
@click="form.component = 'view/routerHolder.vue'"
>点我设置</el-button>
</el-form-item>
<el-form-item
label="展示名称"
prop="meta.title"
style="width: 30%"
>
<el-input
v-model="form.meta.title"
autocomplete="off"
/>
</el-form-item>
<el-form-item
label="图标"
prop="meta.icon"
style="width: 30%"
>
<icon
:meta="form.meta"
style="width: 100%"
/>
</el-form-item>
<el-form-item
label="排序标记"
prop="sort"
style="width: 30%"
>
<el-input
v-model.number="form.sort"
autocomplete="off"
/>
</el-form-item>
<el-form-item
prop="meta.activeName"
style="width: 30%"
>
<template #label>
<div>
<span> 高亮菜单 </span>
<el-tooltip
content="注当到达此路由时候指定左侧菜单指定name会处于活跃状态亮起可为空为空则为本路由Name。"
placement="top"
effect="light"
<el-row class="w-full">
<el-col :span="16">
<el-form-item
label="文件路径"
prop="component"
>
<el-select
v-model="form.component"
filterable
allow-create
autocomplete="off"
style="width: 100%"
placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue"
default-first-option
@change="fmtComponent"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-input
v-model="form.meta.activeName"
:placeholder="form.name"
autocomplete="off"
/>
</el-form-item>
<el-form-item
label="KeepAlive"
prop="meta.keepAlive"
style="width: 30%"
>
<el-select
v-model="form.meta.keepAlive"
style="width: 100%"
placeholder="是否keepAlive缓存页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
<el-form-item
label="CloseTab"
prop="meta.closeTab"
style="width: 30%"
>
<el-select
v-model="form.meta.closeTab"
style="width: 100%"
placeholder="是否自动关闭tab"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
<el-form-item style="width: 30%">
<template #label>
<div>
<span> 是否为基础页面 </span>
<el-tooltip
content="此项选择为是,则不会展示左侧菜单以及顶部信息。"
placement="top"
effect="light"
<el-option
v-for="(item,path) in pathOptions"
:key="path"
:label="path"
:value="path"
/>
</el-select>
<span style="font-size: 12px; margin-right: 12px">如果菜单包含子菜单请创建router-view二级路由页面或者</span><el-button
style="margin-top: 4px"
@click="form.component = 'view/routerHolder.vue'"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
点我设置
</el-button>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="展示名称"
prop="meta.title"
>
<el-input
v-model="form.meta.title"
autocomplete="off"
/>
</el-form-item>
</el-col>
</el-row>
<el-row class="w-full">
<el-col :span="8">
<el-form-item
label="路由Name"
prop="path"
>
<el-input
v-model="form.name"
autocomplete="off"
placeholder="唯一英文字符串"
@change="changeName"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
prop="path"
>
<template #label>
<span style="display: inline-flex; align-items: center">
<span>路由Path</span>
<el-checkbox
v-model="checkFlag"
style="margin-left: 12px; height: auto"
>添加参数</el-checkbox>
</span>
</template>
<el-select
v-model="form.meta.defaultMenu"
style="width: 100%"
placeholder="是否为基础页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
<el-input
v-model="form.path"
:disabled="!checkFlag"
autocomplete="off"
placeholder="建议只在后方拼接参数"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="是否隐藏"
>
<el-select
v-model="form.hidden"
style="width: 100%"
placeholder="是否在列表隐藏"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row class="w-full">
<el-col :span="8">
<el-form-item
label="父节点ID"
>
<el-cascader
v-model="form.parentId"
style="width: 100%"
:disabled="!isEdit"
:options="menuOption"
:props="{
checkStrictly: true,
label: 'title',
value: 'ID',
disabled: 'disabled',
emitPath: false,
}"
:show-all-levels="false"
filterable
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="图标"
prop="meta.icon"
>
<icon
:meta="form.meta"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="排序标记"
prop="sort"
>
<el-input
v-model.number="form.sort"
autocomplete="off"
/>
</el-form-item>
</el-col>
</el-row>
<el-row class="w-full">
<el-col :span="8">
<el-form-item
prop="meta.activeName"
>
<template #label>
<div>
<span> 高亮菜单 </span>
<el-tooltip
content="注当到达此路由时候指定左侧菜单指定name会处于活跃状态亮起可为空为空则为本路由Name。"
placement="top"
effect="light"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-input
v-model="form.meta.activeName"
:placeholder="form.name"
autocomplete="off"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="KeepAlive"
prop="meta.keepAlive"
>
<el-select
v-model="form.meta.keepAlive"
style="width: 100%"
placeholder="是否keepAlive缓存页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="CloseTab"
prop="meta.closeTab"
>
<el-select
v-model="form.meta.closeTab"
style="width: 100%"
placeholder="是否自动关闭tab"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row class="w-full">
<el-col :span="8">
<el-form-item>
<template #label>
<div>
<span> 是否为基础页面 </span>
<el-tooltip
content="此项选择为是,则不会展示左侧菜单以及顶部信息。"
placement="top"
effect="light"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-select
v-model="form.meta.defaultMenu"
style="width: 100%"
placeholder="是否为基础页面"
>
<el-option
:value="false"
label="否"
/>
<el-option
:value="true"
label="是"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div>
<div class="flex items-center gap-2">
@@ -371,7 +420,9 @@
type="primary"
icon="edit"
@click="addParameter(form)"
>新增菜单参数</el-button>
>
新增菜单参数
</el-button>
<el-icon
class="cursor-pointer"
@click="
@@ -379,7 +430,9 @@
'https://www.bilibili.com/video/BV1kv4y1g7nT?p=9&vd_source=f2640257c21e3b547a790461ed94875e'
)
"
><VideoCameraFilled /></el-icon>
>
<VideoCameraFilled />
</el-icon>
</div>
<el-table
:data="form.parameters"
@@ -439,7 +492,9 @@
type="danger"
icon="delete"
@click="deleteParameter(form.parameters, scope.$index)"
>删除</el-button>
>
删除
</el-button>
</div>
</template>
</el-table-column>
@@ -450,14 +505,17 @@
type="primary"
icon="edit"
@click="addBtn(form)"
>新增可控按钮
>
新增可控按钮
</el-button>
<el-icon
class="cursor-pointer"
@click="
toDoc('https://www.gin-vue-admin.com/guide/web/button-auth.html')
"
><QuestionFilled /></el-icon>
>
<QuestionFilled />
</el-icon>
<el-icon
class="cursor-pointer"
@click="
@@ -465,7 +523,9 @@
'https://www.bilibili.com/video/BV1kv4y1g7nT?p=11&vd_source=f2640257c21e3b547a790461ed94875e'
)
"
><VideoCameraFilled /></el-icon>
>
<VideoCameraFilled />
</el-icon>
</div>
<el-table
@@ -503,7 +563,9 @@
type="danger"
icon="delete"
@click="deleteBtn(form.menuBtn, scope.$index)"
>删除</el-button>
>
删除
</el-button>
</div>
</template>
</el-table-column>
@@ -524,16 +586,27 @@ import {
import icon from '@/view/superAdmin/menu/icon.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { canRemoveAuthorityBtnApi } from '@/api/authorityBtn'
import { reactive, ref } from 'vue'
import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { QuestionFilled, VideoCameraFilled } from '@element-plus/icons-vue'
import pathInfo from '@/pathInfo.json'
import { toDoc } from '@/utils/doc'
import { toLowerCase } from '@/utils/stringFun'
defineOptions({
name: 'Menus',
})
const pathOptions = reactive({})
onMounted(()=>{
for (let pathInfoKey in pathInfo) {
// 取消掉最前面的 /src/
pathOptions[pathInfoKey.replace(/^\/src\//, '')] = pathInfo[pathInfoKey]
}
})
const rules = reactive({
path: [{ required: true, message: '请输入菜单name', trigger: 'blur' }],
component: [{ required: true, message: '请输入文件路径', trigger: 'blur' }],
@@ -578,6 +651,7 @@ const addParameter = (form) => {
const fmtComponent = () => {
form.value.component = form.value.component.replace(/\\/g, '/')
form.value.name = toLowerCase(pathOptions[form.value.component])
}
// 删除参数

View File

@@ -139,22 +139,22 @@
<el-checkbox v-model="middleDate.primaryKey" />
</el-form-item>
<el-form-item
label="索引类型"
prop="fieldIndexType"
label="索引类型"
prop="fieldIndexType"
>
<el-select
v-model="middleDate.fieldIndexType"
:disabled="middleDate.fieldType === 'json'"
style="width:100%"
placeholder="请选择字段索引类型"
clearable
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)"
v-for="item in typeIndexOptions"
:key="item.value"
:label="item.label"
:value="item.value"
:disabled="canSelect(item.value)"
/>
</el-select>
</el-form-item>
@@ -171,7 +171,7 @@
<el-switch v-model="middleDate.clearable" />
</el-form-item>
<el-form-item label="隐藏查询条件">
<el-switch :disabled="!middleDate.fieldSearchType" v-model="middleDate.fieldSearchHide" />
<el-switch v-model="middleDate.fieldSearchHide" :disabled="!middleDate.fieldSearchType" />
</el-form-item>
<el-form-item label="校验失败文案">
<el-input v-model="middleDate.errorText" />
@@ -202,30 +202,65 @@
</el-select>
</el-col>
<el-col
:span="7"
>
<el-input
v-model="middleDate.dataSource.table"
placeholder="数据源表"
/>
<el-col :span="7">
<el-select
v-model="middleDate.dataSource.table" placeholder="请选择数据源表"
filterable allow-create @focus="getDBTableList" @change="selectDB"
>
<el-option
v-for="item in dbTableList" :key="item.tableName" :label="item.tableName"
:value="item.tableName"
/>
</el-select>
<!-- <el-input v-model="middleDate.dataSource.table" placeholder="数据源表" /> -->
</el-col>
<el-col
:span="7"
>
<el-input
v-model="middleDate.dataSource.label"
placeholder="展示用字段"
/>
<el-col :span="7">
<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-input v-model="middleDate.dataSource.value" placeholder="存储用字段" /> -->
</el-col>
<el-col
:span="7"
>
<el-input
v-model="middleDate.dataSource.value"
placeholder="存储用字端"
/>
<el-col :span="7">
<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>
@@ -239,6 +274,7 @@ import { getSysDictionaryList } from '@/api/sysDictionary'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'
import {getColumn, getTable} from "@/api/autoCode";
defineOptions({
name: 'FieldDialog'
@@ -357,6 +393,45 @@ const associationChange = (val) => {
}
}
const dbTableList = ref([])
const getDBTableList = async () => {
const res = await getTable()
console.log(res);
if (res.code === 0) {
let list = res.data.tables; // 确保这里正确获取到 tables 数组
dbTableList.value = list.map(item => ({
tableName: item.tableName,
value: item.tableName // 这里假设 value 也是 tableName如果不同请调整
}));
}
}
const dbColumnList = ref([])
const selectDB = async (val) => {
middleDate.value.dataSource.table = val
const res = await getColumn({
tableName: val
})
console.log(res)
if (res.code === 0) {
let list = res.data.columns; // 确保这里正确获取到 tables 数组
dbColumnList.value = list.map(item => ({
columnName: item.columnName,
value: item.columnName,
type: item.dataType,
isPrimary: item.primaryKey,
comment: item.columnComment
}));
if (dbColumnList.value.length > 0) {
middleDate.value.dataSource.label = dbColumnList.value[0].columnName
middleDate.value.dataSource.value = dbColumnList.value[0].columnName
}
}
}
const fieldDialogFrom = ref(null)
defineExpose({ fieldDialogFrom })
</script>

View File

@@ -5,6 +5,7 @@
title="此功能为开发环境使用,不建议发布到生产,具体使用效果请点我观看。"
/>
<div class="gva-search-box">
<div class="text-lg mb-2 text-gray-600">使用AI创建</div>
<div class="relative">
<el-input v-model="prompt" type="textarea" :rows="5" :maxlength="100" :placeholder="`【Beta】试试描述你的表让AI帮你完成。\n目前正在测试阶段遇到问题请及时反馈。\n此功能需要到插件市场个人中心获取自己的AI-Path把AI-Path填入config.yaml下的autocode-->ai-path重启项目即可使用。`" resize="none" />
<div class="flex absolute right-2 bottom-2">
@@ -31,29 +32,19 @@
</div>
<!-- 从数据库直接获取字段 -->
<div class="gva-search-box">
<el-collapse
v-model="activeNames"
class="mb-3"
<div class="text-lg mb-2 text-gray-600">从数据库创建</div>
<el-form
ref="getTableForm"
:inline="true"
:model="dbform"
label-width="120px"
>
<el-collapse-item name="1">
<template #title>
<div class="text-xl pl-4 flex items-center">
点这里从现有数据库创建代码
<el-icon>
<pointer />
</el-icon>
</div>
</template>
<el-form
ref="getTableForm"
style="margin-top:24px"
:inline="true"
:model="dbform"
label-width="120px"
>
<el-row class="w-full">
<el-col :span="6">
<el-form-item
label="业务库"
prop="selectDBtype"
class="w-full"
>
<template #label>
<el-tooltip
@@ -69,6 +60,7 @@
clearable
placeholder="选择业务库"
@change="getDbFunc"
class="w-full"
>
<el-option
v-for="item in dbList"
@@ -84,15 +76,19 @@
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="数据库名"
prop="structName"
class="w-full"
>
<el-select
v-model="dbform.dbName"
clearable
filterable
placeholder="请选择数据库"
class="w-full"
@change="getTableFunc"
>
<el-option
@@ -103,13 +99,17 @@
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="表名"
prop="structName"
class="w-full"
>
<el-select
v-model="dbform.tableName"
:disabled="!dbform.dbName"
class="w-full"
filterable
placeholder="请选择表"
>
@@ -121,20 +121,27 @@
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="getColumnFunc"
>
使用此表创建
</el-button>
</el-col>
<el-col :span="6">
<el-form-item
class="w-full"
>
<div class="flex justify-end w-full">
<el-button
type="primary"
@click="getColumnFunc"
>
使用此表
</el-button>
</div>
</el-form-item>
</el-form>
</el-collapse-item>
</el-collapse>
</el-col>
</el-row>
</el-form>
</div>
<div class="gva-search-box">
<!-- 初始版本自动化代码工具 -->
<div class="text-lg mb-2 text-gray-600">自动化结构</div>
<el-form
ref="autoCodeForm"
:rules="rules"
@@ -142,191 +149,230 @@
label-width="120px"
:inline="true"
>
<el-form-item
label="Struct名称"
prop="structName"
>
<el-input
v-model="form.structName"
placeholder="首字母自动转换大写"
/>
</el-form-item>
<el-form-item
label="TableName"
prop="tableName"
>
<el-input
v-model="form.tableName"
placeholder="指定表名(非必填)"
/>
</el-form-item>
<el-form-item
prop="abbreviation"
>
<template #label>
<el-tooltip
content="简称会作为入参对象名和路由group"
placement="bottom"
effect="light"
<el-row class="w-full">
<el-col :span="6">
<el-form-item
label="结构名称"
prop="structName"
class="w-full"
>
<div> Struct简称 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-input
v-model="form.abbreviation"
placeholder="请输入Struct简称"
/>
</el-form-item>
<el-form-item
label="Struct中文名称"
prop="description"
>
<el-input
v-model="form.description"
placeholder="中文描述作为自动api描述"
/>
</el-form-item>
<el-form-item
prop="packageName"
>
<template #label>
<el-tooltip
content="生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)"
placement="bottom"
effect="light"
<el-input
v-model="form.structName"
placeholder="首字母自动转换大写"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="TableName"
class="w-full"
>
<div> 文件名称 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-input
v-model="form.packageName"
placeholder="请输入文件名称"
@blur="toLowerCaseFunc(form,'packageName')"
/>
</el-form-item>
<el-form-item
label="Package"
prop="package"
>
<el-select
v-model="form.package"
>
<el-option
v-for="item in pkgs"
:key="item.ID"
:value="item.packageName"
:label="item.packageName"
/>
</el-select>
<el-icon
class="cursor-pointer ml-2 text-gray-600"
@click="getPkgs"
>
<refresh />
</el-icon>
<el-icon
class="cursor-pointer ml-2 text-gray-600"
@click="goPkgs"
>
<document-add />
</el-icon>
</el-form-item>
<el-form-item
label="业务库"
prop="businessDB"
>
<template #label>
<el-tooltip
content="注需要提前到db-list自行配置多数据库此项为空则会使用gva本库创建自动化代码(global.GVA_DB),填写后则会创建指定库的代码(global.MustGetGlobalDBByDBName(dbname))"
placement="bottom"
effect="light"
<template #label>
<el-tooltip
content="简称会作为入参对象名和路由group"
placement="bottom"
effect="light"
>
<div> 结构简称 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-input
v-model="form.abbreviation"
placeholder="请输入Struct简称"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="中文名称"
prop="description"
class="w-full"
>
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-select
v-model="form.businessDB"
placeholder="选择业务库"
>
<el-option
v-for="item in dbList"
:key="item.aliasName"
:value="item.aliasName"
:label="item.aliasName"
:disabled="item.disable"
<el-input
v-model="form.description"
placeholder="中文描述作为自动api描述"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="表名"
prop="tableName"
class="w-full"
>
<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>
<div>
<el-form-item>
<template #label>
<el-tooltip
content="注会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="bottom"
effect="light"
<el-input
v-model="form.tableName"
placeholder="指定表名(非必填)"
/>
</el-form-item>
</el-col>
</el-row>
<el-row class="w-full">
<el-col :span="6">
<el-form-item
prop="packageName"
class="w-full"
>
<template #label>
<el-tooltip
content="生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)"
placement="bottom"
effect="light"
>
<div> 文件名称 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-input
v-model="form.packageName"
placeholder="请输入文件名称"
@blur="toLowerCaseFunc(form,'packageName')"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="选择模板"
prop="package"
class="w-full relative"
>
<el-select
v-model="form.package"
class="w-full pr-12"
>
<div> 使用GVA结构 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox
v-model="form.gvaModel"
@change="useGva"
/>
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by方便用户进行资源权限控制"
placement="bottom"
effect="light"
<el-option
v-for="item in pkgs"
:key="item.ID"
:value="item.packageName"
:label="item.packageName"
/>
</el-select>
<span class="absolute right-0">
<el-icon
class="cursor-pointer ml-2 text-gray-600"
@click="getPkgs"
>
<refresh />
</el-icon>
<el-icon
class="cursor-pointer ml-2 text-gray-600"
@click="goPkgs"
>
<document-add />
</el-icon>
</span>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item
label="业务库"
prop="businessDB"
class="w-full"
>
<template #label>
<el-tooltip
content="注需要提前到db-list自行配置多数据库此项为空则会使用gva本库创建自动化代码(global.GVA_DB),填写后则会创建指定库的代码(global.MustGetGlobalDBByDBName(dbname))"
placement="bottom"
effect="light"
>
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-select
v-model="form.businessDB"
placeholder="选择业务库"
class="w-full"
>
<div> 创建资源标识 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注把自动生成的API注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建API <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的菜单注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建菜单 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateMenuToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:自动同步数据库表结构,如果不需要可以选择关闭。"
placement="bottom"
effect="light"
>
<div> 同步表结构 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMigrate" />
</el-form-item>
</div>
<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-col>
</el-row>
<el-row>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="bottom"
effect="light"
>
<div> 使用GVA结构 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox
v-model="form.gvaModel"
@change="useGva"
/>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注把自动生成的API注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建API <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的菜单注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建菜单 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateMenuToSql" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:自动同步数据库表结构,如果不需要可以选择关闭。"
placement="bottom"
effect="light"
>
<div> 同步表结构 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMigrate" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by方便用户进行资源权限控制"
placement="bottom"
effect="light"
>
<div> 创建资源标识 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<!-- 组件列表 -->

View File

@@ -10,11 +10,15 @@
type="primary"
icon="plus"
@click="openDialog('addApi')"
>新增</el-button>
>
新增
</el-button>
<el-icon
class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=3&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
>
<VideoCameraFilled />
</el-icon>
</div>
<el-table :data="tableData">
<el-table-column
@@ -29,6 +33,12 @@
width="150"
prop="packageName"
/>
<el-table-column
align="left"
label="模板"
width="150"
prop="template"
/>
<el-table-column
align="left"
label="展示名"
@@ -54,11 +64,12 @@
type="primary"
link
@click="deleteApiFunc(scope.row)"
>删除</el-button>
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-drawer
@@ -66,7 +77,7 @@
size="40%"
:show-close="false"
>
<warning-bar title="新增Pkg用于自动化代码使用" />
<warning-bar title="模板package会创建集成于项目本体中的代码包模板plugin会创建插件包" />
<el-form
ref="pkgForm"
:model="form"
@@ -82,6 +93,17 @@
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"
@@ -105,11 +127,15 @@
<div class="flex justify-between items-center">
<span class="text-lg">创建Package</span>
<div>
<el-button @click="closeDialog"> </el-button>
<el-button @click="closeDialog">
</el-button>
<el-button
type="primary"
@click="enterDialog"
> </el-button>
type="primary"
@click="enterDialog"
>
</el-button>
</div>
</div>
</template>
@@ -122,6 +148,7 @@ import {
createPackageApi,
getPackageApi,
deletePackageApi,
getTemplatesApi
} from '@/api/autoCode'
import { ref } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
@@ -135,9 +162,20 @@ defineOptions({
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 validateNum = (rule, value, callback) => {
if ((/^\d+$/.test(value[0]))) {
@@ -152,6 +190,10 @@ const rules = ref({
{ required: true, message: '请输入包名', trigger: 'blur' },
{ validator: validateNum, trigger: 'blur' }
],
template:[
{ required: true, message: '请选择模板', trigger: 'change' },
{ validator: validateNum, trigger: 'blur' }
]
})
const dialogFormVisible = ref(false)
@@ -163,6 +205,7 @@ const closeDialog = () => {
dialogFormVisible.value = false
form.value = {
packageName: '',
template: '',
label: '',
desc: '',
}

View File

@@ -1,353 +0,0 @@
<template>
<div>
<div class="gva-table-box">
<el-form
label-width="140px"
class="w-[680px]"
>
<el-form-item label="插件名">
<el-input
v-model="form.plugName"
placeholder="必填(英文大写字母开头)"
@blur="titleCase"
/>
</el-form-item>
<el-form-item label="路由组">
<el-input
v-model="form.routerGroup"
placeholder="将会作为插件路由组使用"
/>
</el-form-item>
<el-form-item label="使用全局属性">
<el-checkbox v-model="form.hasGlobal" />
</el-form-item>
<el-form-item
v-if="form.hasGlobal"
label="全局属性"
>
<div
v-for="(i,k) in form.global"
:key="k"
class="plug-row"
>
<span>
<el-input
v-model="i.key"
placeholder="key 必填"
/>
</span>
<span>
<el-select
class="w-32"
v-model="i.type"
placeholder="type 必填"
>
<el-option
label="string"
value="string"
/>
<el-option
label="int"
value="int"
/>
<el-option
label="float32"
value="float32"
/>
<el-option
label="float64"
value="float64"
/>
<el-option
label="bool"
value="bool"
/>
</el-select>
</span>
<span>
<el-input
v-model="i.desc"
placeholder="备注 必填"
/>
</span>
<span>
<el-button
:icon="Plus"
circle
@click="addkv(form.global)"
/>
</span>
<span>
<el-button
:icon="Minus"
circle
@click="minkv(form.global,k)"
/>
</span>
</div>
</el-form-item>
<el-form-item label="使用Request">
<el-checkbox v-model="form.hasRequest" />
</el-form-item>
<el-form-item
v-if="form.hasRequest"
label="Request"
>
<div
v-for="(i,k) in form.request"
:key="k"
class="plug-row"
>
<span>
<el-input
v-model="i.key"
placeholder="key 必填"
/>
</span>
<span>
<el-select
v-model="i.type"
class="w-32"
placeholder="type 必填"
>
<el-option
label="string"
value="string"
/>
<el-option
label="int"
value="int"
/>
<el-option
label="float32"
value="float32"
/>
<el-option
label="float64"
value="float64"
/>
<el-option
label="bool"
value="bool"
/>
</el-select>
</span>
<span>
<el-input
v-model="i.desc"
placeholder="备注 必填"
/>
</span>
<span>
<el-button
:icon="Plus"
circle
@click="addkv(form.request)"
/>
</span>
<span>
<el-button
:icon="Minus"
circle
@click="minkv(form.request,k)"
/>
</span>
</div>
</el-form-item>
<el-form-item label="使用Response">
<el-checkbox v-model="form.hasResponse" />
</el-form-item>
<el-form-item
v-if="form.hasResponse"
label="Response"
>
<div
v-for="(i,k) in form.response"
:key="k"
class="plug-row"
>
<span>
<el-input
v-model="i.key"
placeholder="key 必填"
/>
</span>
<span>
<el-select
v-model="i.type"
class="w-32"
placeholder="type 必填"
>
<el-option
label="string"
value="string"
/>
<el-option
label="int"
value="int"
/>
<el-option
label="float32"
value="float32"
/>
<el-option
label="float64"
value="float64"
/>
<el-option
label="bool"
value="bool"
/>
</el-select>
</span>
<span>
<el-input
v-model="i.desc"
placeholder="备注 必填"
/>
</span>
<span>
<el-button
:icon="Plus"
circle
@click="addkv(form.response)"
/>
</span>
<span>
<el-button
:icon="Minus"
circle
@click="minkv(form.response,k)"
/>
</span>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="createPlug"
>创建</el-button>
<el-icon
class="cursor-pointer ml-3"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=13&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script setup>
import { toUpperCase } from '@/utils/stringFun'
import {
Plus,
Minus, VideoCameraFilled
} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { createPlugApi } from '@/api/autoCode.js'
import { reactive } from 'vue'
import { toDoc } from '@/utils/doc'
const form = reactive({
plugName: '',
routerGroup: '',
hasGlobal: true,
hasRequest: true,
hasResponse: true,
global: [{
key: '',
type: '',
desc: '',
}],
request: [{
key: '',
type: '',
desc: '',
}],
response: [{
key: '',
type: '',
desc: '',
}]
})
const titleCase = () => {
form.plugName = toUpperCase(form.plugName)
}
const createPlug = async() => {
if (!form.plugName || !form.routerGroup) {
ElMessage.error('插件名称和插件路由组为必填项')
return
}
if (form.hasGlobal) {
const intercept = form.global.some(i => {
if (!i.key || !i.type) {
return true
}
})
if (intercept) {
ElMessage.error('全局属性的key和type为必填项')
return
}
}
if (form.hasRequest) {
const intercept = form.request.some(i => {
if (!i.key || !i.type) {
return true
}
})
if (intercept) {
ElMessage.error('请求属性的key和type为必填项')
return
}
}
if (form.hasResponse) {
const intercept = form.response.some(i => {
if (!i.key || !i.type) {
return true
}
})
if (intercept) {
ElMessage.error('响应属性的key和type为必填项')
return
}
}
const res = await createPlugApi(form)
if (res.code === 0) {
ElMessageBox('创建成功插件已自动写入后端plugin目录下请按照自己的逻辑进行创造')
}
}
const addkv = (arr) => {
arr.push({
key: '',
value: '',
})
}
const minkv = (arr, key) => {
if (arr.length === 1) {
ElMessage.warning('至少有一个全局属性')
return
}
arr.splice(key, 1)
}
</script>
<style lang="scss" scoped>
.plug-row{
@apply flex items-center w-full;
&+&{
@apply mt-3;
}
&>span{
@apply ml-2;
}
}
</style>

View File

@@ -11,6 +11,7 @@ import vuePlugin from '@vitejs/plugin-vue'
import GvaPosition from './vitePlugin/gvaPosition'
import GvaPositionServer from './vitePlugin/codeServer'
import fullImportPlugin from './vitePlugin/fullImport/fullImport.js'
import VueFilePathPlugin from './vitePlugin/componentName/index.js'
import { svgBuilder } from 'vite-auto-import-svg'
import { AddSecret } from './vitePlugin/secret'
// @see https://cn.vitejs.dev/config/
@@ -92,7 +93,8 @@ export default ({
}),
vuePlugin(),
svgBuilder('./src/assets/icons/'),
[Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)]
[Banner(`\n Build based on gin-vue-admin \n Time : ${timestamp}`)],
VueFilePathPlugin("./src/pathInfo.json")
],
css: {
preprocessorOptions: {

View File

@@ -0,0 +1,72 @@
import fs from 'fs';
import path from 'path';
// 递归获取目录下所有的 .vue 文件
const getAllVueFiles = (dir, fileList = []) => {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
if (fs.statSync(filePath).isDirectory()) {
getAllVueFiles(filePath, fileList);
} else if (filePath.endsWith('.vue')) {
fileList.push(filePath);
}
});
return fileList;
}
// 从 .vue 文件内容中提取组件名称
const extractComponentName = (fileContent) => {
const regex = /defineOptions\(\s*{\s*name:\s*["']([^"']+)["']/;
const match = fileContent.match(regex);
return match ? match[1] : null;
}
// Vite 插件定义
const vueFilePathPlugin = (outputFilePath) => {
let root;
const generatePathNameMap = () => {
const vueFiles = [
...getAllVueFiles(path.join(root, 'src/view')),
...getAllVueFiles(path.join(root, 'src/plugin'))
];
const pathNameMap = vueFiles.reduce((acc, filePath) => {
const content = fs.readFileSync(filePath, 'utf-8');
const componentName = extractComponentName(content);
if (componentName) {
let relativePath ="/" + path.relative(root, filePath).replace(/\\/g, '/');
acc[relativePath] = componentName;
}
return acc;
}, {});
const outputContent = JSON.stringify(pathNameMap, null, 2);
fs.writeFileSync(outputFilePath, outputContent);
};
const watchDirectoryChanges = () => {
const watchDirectories = [path.join(root, 'src/view'), path.join(root, 'src/plugin')];
watchDirectories.forEach(dir => {
fs.watch(dir, { recursive: true }, (eventType, filename) => {
if (filename) {
generatePathNameMap();
}
});
});
};
return {
name: 'vue-file-path-plugin',
configResolved(resolvedConfig) {
root = resolvedConfig.root;
},
buildEnd() {
generatePathNameMap();
if (process.env.NODE_ENV === 'development') {
watchDirectoryChanges();
}
}
};
}
export default vueFilePathPlugin