lzq 2025-12-07 14:26:51 +08:00
parent cfb0631a0d
commit ac49a92e08
17 changed files with 268 additions and 181 deletions

View File

@ -2,10 +2,9 @@ import {
ElMessage,
type FormInstance,
} from 'element-plus'
import type { R } from '@/common/utils/http-util.ts'
export default {
submit<T>(form: Ref<FormInstance | undefined, FormInstance | undefined>, then: () => Promise<R<T>>) {
submit<T>(form: ShallowRef<FormInstance | null>, then: () => Promise<T>) {
return form.value!.validate()
.then(
then,

View File

@ -5,6 +5,7 @@
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import { GlobalComponents } from 'vue'
export {}

8
src/dts/g.d.ts vendored
View File

@ -26,10 +26,10 @@ declare global {
}
interface Pagination {
total: number
pages: number
current: number
size: number
total?: number
pages?: number
current?: number
size?: number
}
}
}

View File

@ -12,7 +12,7 @@ import Strings from '@/common/utils/strings.ts'
import FormUtil from '@/common/utils/formUtil.ts'
import { loadUserInfo } from '@/common/app'
const loginFormIns = ref<FormInstance>()
const loginFormIns = useTemplateRef<FormInstance>('loginForm')
const loginForm = reactive<LoginTypes.LoginForm>({
account: '',
@ -76,7 +76,7 @@ function loginSubmitHandler() {
{{ appName }}
</div>
<ElForm
ref="loginFormIns"
ref="loginForm"
:model="loginForm"
:rules="rules"
class="form-content"

View File

@ -2,30 +2,30 @@
<ElDialog v-model="showDialog"
:close-on-click-modal="false"
destroy-on-close
width="50vw"
width="fit-content"
@close="dialogCloseHandler"
>
<div class="content">
<ElForm :model="formData"
class="sys_user-form"
ref="codeForm"
:rules="rules"
label-width="auto">
<ElFormItem label="表名称">
<ElTooltip
:content="currentTable.comment"
placement="top"
>
:content="currentTable.tableComment"
placement="top">
<ElInput
v-model="currentTable.name"
v-model="currentTable.tableName"
readonly
/>
</ElTooltip>
</ElFormItem>
<ElFormItem label="前端/后端">
<ElFormItem label="前端/后端" prop="lang">
<ElSelect
v-model="formData.lang"
placeholder="前端/后端"
@change="formData.tplNames = []"
>
@change="changeTplHandler">
<ElOption
label="前端"
value="ts"/>
@ -34,48 +34,43 @@
value="java"/>
</ElSelect>
</ElFormItem>
<ElFormItem label="模板">
<ElSelect
v-model="formData.tplNames"
collapse-tags
collapse-tags-tooltip
multiple
placeholder="模板"
>
<ElOption
v-for="item in currentTpls"
:key="item.id"
:label="item.tplName"
:value="item.tplName!"/>
</ElSelect>
</ElFormItem>
<ElFormItem label="表前缀">
<ElFormItem label="表前缀" prop="data.prefix">
<ElInput
v-model="formData.data.prefix"
placeholder=""/>
placeholder="表前缀"/>
</ElFormItem>
<ElFormItem v-if="formData.lang === 'java'" label="基础包名称">
<ElFormItem v-if="formData.lang === 'java'" label="基础包名称" prop="data.basePackage">
<ElInput
v-model="formData.data.basePackage"
placeholder=""/>
placeholder="基础包名称"/>
</ElFormItem>
<ElFormItem label="模块名称">
<ElFormItem v-if="formData.lang === 'java'" label="模块名称" prop="data.moduleName">
<ElInput
v-model="formData.data.moduleName"
placeholder=""/>
placeholder="模块名称"/>
</ElFormItem>
<ElFormItem v-if="formData.lang === 'ts'" label="子模块">
<ElFormItem v-if="formData.lang === 'ts'" label="目录名称" prop="data.moduleName">
<ElTooltip
content="相对于 pages 目录"
placement="top">
<ElInput
v-model="formData.data.moduleName"
placeholder="目录名称"/>
</ElTooltip>
</ElFormItem>
<ElFormItem v-if="formData.lang === 'ts'" label="子目录名称" prop="data.subModuleName">
<ElInput
v-model="formData.data.subModuleName"
placeholder=""/>
placeholder="子目录名称"/>
</ElFormItem>
</ElForm>
<ElDivider direction="vertical"/>
<ElTabs v-model="activeName">
<ElTabPane v-for="tab in tabsPanes" :label="tab.tplName!" :name="tab.tplName!">
<pre>
{{ codeInfo[tab.tplName!]?.content }}
</pre>
<ElTabs v-model="activeName" class="code-panel">
<ElTabPane v-for="tab in tpls" :label="tab.tplName!" :name="tab.tplName!">
<div style="margin-bottom: 10px;">
文件名{{ codeInfo[tab.tplName!]?.path }}
</div>
<ElInput :model-value="codeInfo[tab.tplName!]?.content" :rows="12" :spellcheck="false" resize="none" type="textarea"/>
</ElTabPane>
</ElTabs>
</div>
@ -87,43 +82,37 @@
</ElDialog>
</template>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
import GenApi from '@/pages/sys/gen/gen-api.ts'
import {
ElMessage,
type FormInstance,
type FormRules,
} from 'element-plus'
import TplApi from '@/pages/sys/gen/tpl/tpl-api.ts'
import Strings from '@/common/utils/strings.ts'
import FormUtil from '@/common/utils/formUtil.ts'
const activeName = ref('')
const showDialog = ref(false)
const downloading = ref(false)
const previewing = ref(false)
const codeInfo = ref<GenTypes.CodeInfo>({})
const tpls = ref<GenTypes.TplInfo[]>([])
const currentTpls = computed(() => {
return tpls.value.filter(it => it.lang === formData.value.lang)
})
const codeInfo = ref<TplTypes.CodeInfo>({})
const tpls = ref<TplTypes.SearchTplResult[]>([])
const codeFormIns = useTemplateRef<FormInstance>('codeForm')
const tabsPanes = computed(() => {
const d = currentTpls.value.filter(it => formData.value.tplNames.includes(it.tplName!))
if (d.length > 0) {
activeName.value = d[0].tplName!
} else {
activeName.value = ''
}
return d
})
const currentTable = reactive<DbTableTypes.TableInfo>({})
const currentTable = reactive<GenTypes.TableInfo>({})
const formData = ref<{
// 1-->2-->
interface FormType {
lang: 'java' | 'ts'
tplNames: string[]
data: {
prefix: string
basePackage: string
moduleName: string
subModuleName: string
}
}>({
lang: 'java',
tplNames: [],
}
const formData = ref<FormType>({
lang: 'ts',
data: {
prefix: '',
basePackage: '',
@ -132,25 +121,85 @@ const formData = ref<{
},
})
const rules = reactive<FormRules<FormType>>({
'data.prefix': [ {
validator(_: any, value: string, callback: any) {
if (Strings.isBlank(value)) {
callback(new Error('请填写表前缀'))
} else if (!value.endsWith('_')) {
callback(new Error('表前缀要以下划线结束'))
} else {
callback()
}
}, trigger: 'blur',
} ],
'data.basePackage': [ {
validator(_: any, value: string, callback: any) {
if (formData.value.lang != 'java') {
callback()
return
}
if (Strings.isBlank(value)) {
callback(new Error('请填写基础包名'))
} else {
callback()
}
}, trigger: 'blur',
} ],
'data.moduleName': [ {
validator(_: any, value: string, callback: any) {
if (Strings.isBlank(value)) {
callback(new Error(formData.value.lang === 'ts' ? '请填写目录名称' : '请填写模块名称'))
} else {
callback()
}
}, trigger: 'blur',
} ],
'data.subModuleName': [ {
validator(_: any, value: string, callback: any) {
if (formData.value.lang != 'ts') {
callback()
return
}
if (Strings.isBlank(value)) {
callback(new Error('请填写子目录名称'))
} else {
callback()
}
}, trigger: 'blur',
} ],
})
function changeTplHandler() {
TplApi.listAll(formData.value.lang)
.then(res => {
tpls.value = res.data
if (tpls.value.length > 0) {
activeName.value = tpls.value[0].tplName!
} else {
activeName.value = ''
}
})
}
function dialogCloseHandler() {
Object.assign(currentTable, {})
tpls.value = []
codeInfo.value = {}
formData.value = {
lang: 'java',
tplNames: [],
lang: 'ts',
data: {
prefix: '',
basePackage: '',
moduleName: '',
subModuleName: '',
...formData.value.data,
},
}
}
function downloadHandler() {
downloading.value = true
GenApi.download(formData.value.tplNames, currentTable.name!, formData.value.data)
FormUtil.submit(codeFormIns, () => TplApi.download(formData.value.lang, currentTable.tableName!, formData.value.data)
.then(() => {
ElMessage.success('下载成功')
})
}))
.finally(() => {
downloading.value = false
})
@ -158,7 +207,7 @@ function downloadHandler() {
function previewHandler() {
previewing.value = true
GenApi.preview(formData.value.tplNames, currentTable.name!, formData.value.data)
FormUtil.submit(codeFormIns, () => TplApi.preview(formData.value.lang, currentTable.tableName!, formData.value.data))
.then(res => {
codeInfo.value = res.data
})
@ -168,21 +217,25 @@ function previewHandler() {
}
defineExpose({
open(data?: GenTypes.TableInfo) {
open(data?: DbTableTypes.TableInfo) {
Object.assign(currentTable, data)
showDialog.value = true
GenApi.list()
.then(res => {
tpls.value = res.data
})
formData.value = {
lang: 'ts',
data: {
...formData.value.data,
},
}
changeTplHandler()
},
})
</script>
<style lang="stylus" scoped>
.content {
display: flex;
width: 100%;
box-sizing: border-box;
width: 800px;
height: 400px;
& > form, & > div:nth-child(3) {
padding 20px
@ -190,15 +243,22 @@ defineExpose({
}
& > form {
width 270px;
width: 270px;
flex-shrink: 0;
box-sizing: border-box;
}
& > div:nth-child(2) {
height auto
width: 2px;
flex-shrink: 0;
box-sizing: border-box;
}
& > div:nth-child(3) {
flex 1
width: calc(800px - 272px);
height: 100%;
box-sizing: border-box;
}
}
</style>

View File

@ -20,8 +20,9 @@
empty-text="暂无数据"
header-row-class-name="table-header"
row-key="id">
<ElTableColumn label="表名称" prop="name"/>
<ElTableColumn label="备注" prop="comment"/>
<ElTableColumn label="数据库名称" prop="tableSchema"/>
<ElTableColumn label="表名称" prop="tableName"/>
<ElTableColumn label="备注" prop="tableComment"/>
<ElTableColumn label="操作" width="180">
<template #default="scope">
<div class="action-btn">
@ -30,6 +31,12 @@
</template>
</ElTableColumn>
</ElTable>
<ElPagination
:page-size="pagination.size"
:total="pagination.total"
class="pagination"
layout="prev, pager, next"
@change="pageChangeHandler"/>
<CodePreview ref="codePreview"/>
</Page>
</template>
@ -37,21 +44,35 @@
import Page from '@/components/page/Page.vue'
import { elIcons } from '@/common/element/element.ts'
import CodePreview from '@/pages/sys/gen/db-table/CodePreview.vue'
import GenApi from '@/pages/sys/gen/gen-api.ts'
import DbTableApi from '@/pages/sys/gen/db-table/db-table-api.ts'
const tableData = ref<GenTypes.TableInfo[]>([])
const searchForm = reactive<{
tableName?: string
}>({})
const tableData = ref<DbTableTypes.TableInfo[]>([])
const searchForm = reactive<DbTableTypes.SearchTplParam>({
current: 1,
size: 20,
})
const searching = ref(false)
const showSearchForm = ref(true)
const codePreviewIns = useTemplateRef<InstanceType<typeof CodePreview>>('codePreview')
function showDialog(data?: GenTypes.TableInfo) {
const pagination = reactive<G.Pagination>({
total: 0,
pages: 0,
current: 1,
size: 20,
})
function pageChangeHandler(currentPage: number, pageSize: number) {
searchForm.current = currentPage
searchForm.size = pageSize
paging()
}
function showDialog(data?: DbTableTypes.TableInfo) {
codePreviewIns.value?.open(data)
}
function previewHandler({row}: { row: GenTypes.TableInfo }) {
function previewHandler({row}: { row: DbTableTypes.TableInfo }) {
showDialog(row)
}
@ -63,10 +84,15 @@ function reset() {
function paging() {
searching.value = true
GenApi.listTable(searchForm.tableName)
DbTableApi.tablePaing(searchForm)
.then(res => {
tableData.value = res.data ?? []
tableData.value = res.data.records ?? []
Object.assign(pagination, {
total: res.data?.total ?? 0,
pages: res.data?.pages ?? 0,
current: res.data?.current ?? 1,
size: res.data?.size ?? 20,
})
})
.finally(() => {
searching.value = false
@ -130,15 +156,9 @@ onMounted(() => {
margin 0 0 20px 0
}
.avatar {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
:deep(.el-icon) {
font-size 25px
}
.pagination {
justify-content: end;
margin: 8px;
}
</style>

View File

@ -0,0 +1,8 @@
import { get } from '@/common/utils/http-util.ts'
export default {
tablePaing(data?: DbTableTypes.SearchTplParam) {
return get<G.PageResult<DbTableTypes.TableInfo>>('/tpl/table/paging', data)
},
}

View File

@ -0,0 +1,18 @@
export {}
declare global {
namespace DbTableTypes {
interface SearchTplParam extends G.PageParam {
tableName?: string
tableSchema?: string
}
interface TableInfo {
tableSchema?: string
tableName?: string
tableComment?: string
}
}
}

View File

@ -1,39 +0,0 @@
import {
download,
get,
post,
} from '@/common/utils/http-util.ts'
export default {
listTable(tableName?: string) {
return get<GenTypes.TableInfo[]>('/tpl/list_table', {tableName})
},
list() {
return get<GenTypes.TplInfo[]>('/tpl/list')
},
preview(tplNames: string[], tableName: string, data: Record<string, any>) {
return post<GenTypes.CodeInfo>('/tpl/preview', data, {tplNames, tableName})
},
download(tplNames: string[], tableName: string, data: Record<string, any>) {
return download('/tpl/download', data, {tplNames, tableName})
.then(res => {
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(res.data.data)
// 创建a标签用于跳转至下载链接
const tempLink = document.createElement('a')
tempLink.style.display = 'none'
tempLink.href = blobURL
tempLink.setAttribute('download', decodeURI(res.data.filename))
// 兼容某些浏览器不支持HTML5的download属性
if (typeof tempLink.download === 'undefined') {
tempLink.setAttribute('target', '_blank')
}
// 挂载a标签
document.body.appendChild(tempLink)
tempLink.click()
document.body.removeChild(tempLink)
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL)
})
},
}

View File

@ -1,29 +0,0 @@
export {}
declare global {
namespace GenTypes {
interface TableInfo {
name?: string
comment?: string
}
interface TplInfo {
id?: string
tplName?: string
lang?: string
tpl: {
content?: string
dir?: string
filename?: string
}
modelData: Record<string, any>
}
interface CodeInfo {
[key: string]: {
content: string
path: string
}
}
}
}

View File

@ -91,6 +91,8 @@ const tableData = ref<TplTypes.SearchTplResult[]>([])
const searchForm = reactive<TplTypes.SearchTplParam>({
current: 1,
size: 20,
orders: 'lang,tpl_name',
tpl: {},
})
const searching = ref(false)
const deling = ref(false)
@ -98,7 +100,6 @@ const showSearchForm = ref(true)
const tplFormIns = useTemplateRef<InstanceType<typeof TplForm>>('tplForm')
const pagination = reactive<G.Pagination>({
total: 0,
pages: 0,
current: 1,
size: 1,
})

View File

@ -30,6 +30,7 @@
<ElFormItem label="模板内容">
<ElInput
v-model="tplFormData.tpl.content"
:spellcheck="false"
:disabled="status === 'view'"
placeholder="模板内容"
resize="none"
@ -38,6 +39,7 @@
<ElFormItem label="文件路径">
<ElInput
:spellcheck="false"
v-model="tplFormData.tpl.dir"
:disabled="status === 'view'"
placeholder="文件路径"
@ -47,6 +49,7 @@
<ElFormItem label="文件名称">
<ElInput
:spellcheck="false"
v-model="tplFormData.tpl.filename"
:disabled="status === 'view'"
placeholder="文件名称"

View File

@ -1,4 +1,5 @@
import {
download,
get,
post,
} from '@/common/utils/http-util.ts'
@ -19,4 +20,33 @@ export default {
del(ids: string[]) {
return post('/tpl/del', ids)
},
listAll(lang: string) {
return get<TplTypes.SearchTplResult[]>('/tpl/list_all', {lang})
},
preview(lang: string, tableName: string, data: Record<string, any>) {
return post<TplTypes.CodeInfo>('/tpl/preview', data, {lang, tableName})
},
download(lang: string, tableName: string, data: Record<string, any>) {
return download('/tpl/download', data, {lang, tableName})
.then(res => {
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(res.data.data)
// 创建a标签用于跳转至下载链接
const tempLink = document.createElement('a')
tempLink.style.display = 'none'
tempLink.href = blobURL
tempLink.setAttribute('download', decodeURI(res.data.filename))
// 兼容某些浏览器不支持HTML5的download属性
if (typeof tempLink.download === 'undefined') {
tempLink.setAttribute('target', '_blank')
}
// 挂载a标签
document.body.appendChild(tempLink)
tempLink.click()
document.body.removeChild(tempLink)
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL)
})
},
}

View File

@ -60,5 +60,12 @@ declare global {
filename?: string
}
}
interface CodeInfo {
[key: string]: {
content: string
path: string
}
}
}
}

View File

@ -78,6 +78,8 @@ import {
} from '@/components/a-icon/iconfont.ts'
import AIcon from '@/components/a-icon/AIcon.tsx'
const emits = defineEmits([ 'editSucc' ])
const pagination = reactive<G.Pagination>({
total: icons.glyphs.length,
pages: Math.ceil(icons.glyphs.length / 5),
@ -91,7 +93,6 @@ function dialogCloseHandler() {
}
}
function pageChangeHandler(currentPage: number, pageSize: number) {
pagination.current = currentPage
pagination.size = pageSize
@ -113,7 +114,6 @@ const iconTableDataSource = ref<IconGlyphs[]>(
)
function currentChangeHandler(val?: IconGlyphs) {
console.log(val)
if (val == null) return
menuForm.value.icon = val.font_class
menuForm.value.iconName = val.name
@ -143,6 +143,8 @@ function submitHandler() {
MenuApi.modify(menuForm.value)
.then(() => {
ElMessage.success('修改成功')
showDialog.value = false
emits(('editSucc'))
})
.finally(() => {
submiting.value = false
@ -151,6 +153,8 @@ function submitHandler() {
MenuApi.add(menuForm.value)
.then(() => {
ElMessage.success('添加成功')
showDialog.value = false
emits(('editSucc'))
})
.finally(() => {
submiting.value = false

View File

@ -27,14 +27,14 @@
lazy
row-key="id">
<!-- <ElTableColumn type="expand" width="60"/> -->
<ElTableColumn label="图标" prop="icon" width="60">
<ElTableColumn label="图标" prop="icon" width="100">
<template #default="scope">
<AIcon :name="scope.row.icon"/>
</template>
</ElTableColumn>
<ElTableColumn label="类型" prop="menuCategoryTxt" width="140"/>
<ElTableColumn label="菜单名称" prop="title"/>
<ElTableColumn label="编码" prop="sn" width="80"/>
<ElTableColumn label="编码" prop="sn" width="180"/>
<!-- <ElTableColumn label="路径" prop="breadcrumb"/> -->
<ElTableColumn label="路由名称" prop="routeName">
<template #default="scope">
@ -71,7 +71,7 @@
</ElTableColumn>
</ElTable>
<MenuForm ref="menuForm"/>
<MenuForm ref="menuForm" @editSucc="editSuccHandler"/>
</Page>
</template>
@ -120,10 +120,15 @@ function reset() {
listAll()
}
function editSuccHandler() {
listAll()
}
function listAll() {
searching.value = true
MenuApi.listAll({...searchForm, pid: '0'})
.then(res => {
tableData.value = []
tableData.value = res.data?.map(it => {
it.hasChildren = true
return it

View File

@ -102,7 +102,6 @@ function submitHandler() {
defineExpose({
open(data: TaskTypes.SearchTaskResult = {}) {
showDialog.value = true
console.log(!Strings.isBlank(data.id), 'dataa')
if (!Strings.isBlank(data.id)) {
status.value = 'modify'
TaskApi.detail(data.id!).then((res) => {