模板生成逻辑基本完成

This commit is contained in:
bunny 2025-06-29 16:14:36 +08:00
parent d50e19609a
commit bc17aa3dd4
6 changed files with 18951 additions and 255 deletions

View File

@ -129,11 +129,8 @@ const MainCard = defineComponent({
</div>
`,
props: {
// 原始表列表数据,由父组件传入
rawTableList: {
type: Array,
default: () => []
},
/* 原始表列表数据,由父组件传入 */
rawTableList: {type: Array,},
},
data() {
return {
@ -163,7 +160,7 @@ const MainCard = defineComponent({
try {
const response = await axiosInstance.get("/table/databaseInfoMetaData");
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return

View File

@ -1,135 +1,264 @@
const MainForm = {
name: "MainForm", template: `
name: "MainForm",
template: `
<div class="card shadow-sm mt-2 bg-body-secondary">
<!-- 表单标题和折叠控制 -->
<div class="card-header p-3">
<a aria-controls="generatorFormCollapse"
aria-expanded="false"
class="d-flex align-items-center text-decoration-none"
data-bs-toggle="collapse"
href="#generatorFormCollapse">
<a aria-controls="generatorFormCollapse" aria-expanded="false"
class="d-flex align-items-center text-decoration-none" data-bs-toggle="collapse"
href="#generatorFormCollapse">
<i class="bi bi-pencil me-2"></i>
<span class="fw-semibold">填写生成表单信息</span>
<i class="bi bi-chevron-down ms-auto transition-transform rotate-180"></i>
</a>
</div>
<!-- 表单内容区域 -->
<div class="collapse" :class="{ 'show': defaultCollapse }" id="generatorFormCollapse">
<form class="card-body row">
<!-- 基本信息输入 -->
<div class="col-md-4 mb-3">
<form class="card-body row" @submit.prevent="handleSubmit" novalidate>
<!-- 基本信息输入区域 -->
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="authorName">作者名称</label>
<input class="form-control border-secondary" id="authorName" placeholder="输入作者名称" required
v-model="form.authorName" type="text">
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.authorName }"
id="authorName" placeholder="输入作者名称" v-model="form.authorName" type="text"
@input="validateField('authorName')">
<div class="invalid-feedback">
{{ errors.authorName || '请输入作者名称' }}
</div>
</div>
<div class="col-md-4 mb-3">
<label class="form-label fw-medium"
for="requestMapping">requestMapping名称</label>
<input class="form-control border-secondary" id="requestMapping" placeholder="输入requestMapping名称" required
v-model="form.requestMapping" type="text">
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="requestMapping">requestMapping名称</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.requestMapping }"
id="requestMapping" placeholder="输入requestMapping名称" v-model="form.requestMapping" type="text"
@input="validateField('requestMapping')">
<div class="invalid-feedback">
{{ errors.requestMapping || '请输入requestMapping名称' }}
</div>
</div>
<div class="col-md-4 mb-3">
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="packageName">包名称</label>
<input class="form-control border-secondary" id="packageName" placeholder="输入包名称" required
v-model="form.packageName" type="text">
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.packageName }"
id="packageName" placeholder="输入包名称" v-model="form.packageName" type="text"
@input="validateField('packageName')">
<div class="invalid-feedback">
{{ errors.packageName || '请输入包名称' }}
</div>
</div>
<div class="col-md-4 mb-3">
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="simpleDateFormat">时间格式</label>
<input class="form-control border-secondary" id="simpleDateFormat" placeholder="输入时间格式" required
v-model="form.simpleDateFormat" type="text">
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.simpleDateFormat }"
id="simpleDateFormat" placeholder="输入时间格式" v-model="form.simpleDateFormat" type="text"
@input="validateField('simpleDateFormat')">
<div class="invalid-feedback">
{{ errors.simpleDateFormat || '请输入时间格式' }}
</div>
</div>
<div class="col-md-4 mb-3 has-validation">
<label class="form-label fw-medium" for="tablePrefixes">去除开头前缀</label>
<input class="form-control border-secondary" :class="{ 'is-invalid': errors.tablePrefixes }"
id="tablePrefixes" placeholder="去除开头前缀" v-model="form.tablePrefixes" type="text"
@input="validateField('tablePrefixes')">
<div class="invalid-feedback">
{{ errors.tablePrefixes || '请输入去除开头前缀' }}
</div>
</div>
<div class="col-md-4 mb-3">
<label class="form-label fw-medium" for="tablePrefixes">去除开头前缀</label>
<input class="form-control border-secondary" id="tablePrefixes" placeholder="去除开头前缀" required
v-model="form.tablePrefixes" type="text">
</div>
<div class="col-md-4 mb-3">
<label class="form-label fw-medium" for="comment">注释内容</label>
<input class="form-control border-secondary" id="comment" placeholder="注释内容" required
v-model="form.comment" type="text">
<input class="form-control border-secondary" id="comment" placeholder="注释内容" v-model="form.comment"
type="text" />
</div>
<div class="col-md-12" v-show="form.tableNames.length > 0">
<label class="form-label fw-medium" for="comment">已选择的要生成表({{form.tableNames.length}})</label>
<span class="badge rounded-pill text-bg-dark me-1" v-for="(item,index) in form.tableNames" :ket="index">{{item}}</span>
</div>
<!-- 前端模板选项 -->
<div class="col-12 mt-3 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成前端模板</label>
<div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index">
<input class="form-check-input border-secondary" :id="web.id" type="checkbox"
:value="web.name" v-model="web.checked">
<label class="form-check-label" :for="web.id">{{web.label}}</label>
<!-- 前端模板选择区域 -->
<div class="col-12 mt-3 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成前端模板</label>
<div class="form-check form-check-inline" v-for="(web,index) in webList" :key="index">
<input class="form-check-input border-secondary" :class="{ 'is-invalid': errors.webTemplates }"
:id="web.id" type="checkbox" v-model="web.checked"
@change="validateTemplates">
<label class="form-check-label" :for="web.id" :title="web.name">{{web.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(webList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(webList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(webList)">全不选</button>
</div>
<div v-if="errors.webTemplates" class="invalid-feedback d-block">
{{ errors.webTemplates }}
</div>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button" @click="onTableSelectAll(webList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button" @click="onTableInvertSelection(webList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button" @click="onTableClearAll(webList)">全不选</button>
<!-- 后端模板选择区域 -->
<div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
<div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<input class="form-check-input border-secondary"
:class="{ 'is-invalid': errors.serverTemplates }" :id="server.id" type="checkbox"
v-model="server.checked" @change="validateTemplates">
<label class="form-check-label" :title="server.name" :for="server.id">{{server.label}}</label>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button"
@click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button"
@click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button"
@click="onTableClearAll(serverList)">全不选</button>
</div>
<div v-if="errors.serverTemplates" class="invalid-feedback d-block">
{{ errors.serverTemplates }}
</div>
</div>
</div>
<!-- 后端模板选项 -->
<div class="col-12 mt-2 mb-3 p-3 bg-light rounded">
<label class="form-check-inline col-form-label fw-medium">生成后端模板</label>
<div class="form-check form-check-inline" v-for="(server,index) in serverList" :key="index">
<input class="form-check-input border-secondary" :id="server.id" type="checkbox"
:value="server.name" v-model="server.checked">
<label class="form-check-label" :for="server.id" :data-bs-title="server.name"
data-bs-toggle="tooltip">{{server.label}}</label>
<!-- 操作按钮区域 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容" data-bs-toggle="tooltip"
type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择" data-bs-toggle="tooltip"
type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary" data-bs-title="生成全部已经选择的数据表" data-bs-toggle="tooltip"
type="submit">
生成选中表
</button>
<button class="btn btn-warning" type="button" data-bs-title="取消全部已经选择的数据表" data-bs-toggle="tooltip"
@click="onClearGeneratorData">清空生成记录</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary text-white" type="button">下载ZIP</button>
</div>
</div>
<div class="form-check form-check-inline btn-group ms-2">
<button class="btn btn-outline-primary btn-sm" type="button" @click="onTableSelectAll(serverList)">全选</button>
<button class="btn btn-outline-secondary btn-sm" type="button" @click="onTableInvertSelection(serverList)">反选</button>
<button class="btn btn-outline-danger btn-sm" type="button" @click="onTableClearAll(serverList)">全不选</button>
</div>
</div>
<!-- 操作按钮 -->
<div class="row mt-4">
<div class="col-md-4 btn-group">
<button class="btn btn-outline-primary" data-bs-title="选择数据表中所有的内容"
data-bs-toggle="tooltip" type="button" @click="onSelectAll">
全部选择
</button>
<button class="btn btn-outline-secondary" data-bs-title="将选择的表格内容反向选择"
data-bs-toggle="tooltip" type="button" @click="onInvertSelection">
全部反选
</button>
<button class="btn btn-outline-danger" data-bs-title="取消全部已经选择的数据表"
data-bs-toggle="tooltip" type="button" @click="onClearAll">
全部取消
</button>
</div>
<div class="col-md-4 btn-group">
<button class="btn btn-primary" data-bs-title="取消全部已经选择的数据表"
data-bs-toggle="tooltip" type="button">
开始生成
</button>
<button class="btn btn-warning" type="button">清空生成记录</button>
<button class="btn btn-success" type="button">下载全部</button>
</div>
<div class="col-md-4 d-grid gap-2">
<button class="btn btn-primary text-white" type="button">下载ZIP</button>
</div>
</div>
</form>
</div>
</div>
`,
props: {
form: {
type: Object,
default: {}
}
// 表单数据对象,包含生成代码所需的各种参数
form: {type: Object, required: true},
// 生成代码的回调函数
onGeneratorCode: {type: Function, required: true},
// 清空生成记录
onClearGeneratorData: {type: Function, required: true},
},
data() {
return {
// 默认是否折叠编辑表单
// 控制表单默认是否展开
defaultCollapse: true,
// 后端路径列表
// 后端模板选项列表
serverList: ref([]),
// 前端路径列表
// 前端模板选项列表
webList: ref([]),
// 错误信息对象
errors: {
authorName: '',
requestMapping: '',
packageName: '',
simpleDateFormat: '',
tablePrefixes: '',
webTemplates: '',
serverTemplates: ''
}
}
},
methods: {
/* 获取所有vms下的文件路径 */
/**
* 验证表单字段
* @param {string} field - 字段名
*/
validateField(field) {
if (!this.form[field] || this.form[field].trim() === '') {
this.errors[field] = '此字段为必填项';
return false;
}
this.errors[field] = '';
return true;
},
/* 验证模板选择 */
validateTemplates() {
// 检查列表是否有一个选中的
const hasWebSelected = this.webList.some(item => item.checked);
const hasServerSelected = this.serverList.some(item => item.checked);
// 列表都没有选中
if (!hasWebSelected && !hasServerSelected) {
this.errors.webTemplates = '请至少选择一个前端或后端模板';
this.errors.serverTemplates = '请至少选择一个前端或后端模板';
return false;
}
// 列表选中让错误提示小时
this.errors.webTemplates = '';
this.errors.serverTemplates = '';
// 发生选择变化时,同时父级更新表单
this.updateForm();
return true;
},
/* 验证整个表单 */
validateForm() {
let isValid = true;
// 验证文本字段
const textFields = ['authorName', 'requestMapping', 'packageName', 'simpleDateFormat', 'tablePrefixes'];
textFields.forEach(field => {
if (!this.validateField(field)) {
isValid = false;
}
});
// 验证模板选择
if (!this.validateTemplates()) {
isValid = false;
}
return isValid;
},
/* 更新父级表单 */
updateForm() {
const webList = this.webList.filter(item => item.checked).map(item => item.name);
const serverList = this.serverList.filter(item => item.checked).map(item => item.name);
const newForm = {...this.form, path: [...webList, ...serverList,]};
this.$emit("update:form", newForm);
},
/* 处理表单提交 */
handleSubmit() {
if (this.validateForm()) {
this.updateForm();
// 如果验证通过,调用父组件提供的生成代码方法
this.onGeneratorCode();
} else {
// 验证失败,可以在这里添加额外的处理逻辑
antd.message.error("表单验证失败")
}
},
/**
* 获取VMS资源路径列表
* 从服务器获取前端和后端模板的路径列表
* @async
* @returns {Promise<void>}
*/
async getVmsResourcePathList() {
const response = await axiosInstance.get("/vms/vmsResourcePathList");
const {data, code, message} = response;
@ -138,52 +267,58 @@ const MainForm = {
antd.message.error(message);
return;
}
this.serverList = data.server;
this.webList = data.web;
// 初始化模板选择状态
this.serverList = data.server.map(item => ({...item, checked: false}));
this.webList = data.web.map(item => ({...item, checked: false}));
},
/* 表格选择全部 */
/**
* 全选指定列表
* @param {Array} list - 要处理的列表
*/
onTableSelectAll(list) {
list.forEach((item) => item.checked = true)
.filter((item) => {
console.log(item);
})
list.forEach(item => item.checked = true);
this.validateTemplates();
},
/* 表格反选所选的 */
/**
* 反选指定列表
* @param {Array} list - 要处理的列表
*/
onTableInvertSelection(list) {
list.forEach((item) => item.checked = !item.checked);
list.forEach(item => item.checked = !item.checked);
this.validateTemplates();
},
/* 表格全不选 */
/**
* 清空指定列表的选择
* @param {Array} list - 要处理的列表
*/
onTableClearAll(list) {
list.forEach((item) => item.checked = false);
list.forEach(item => item.checked = false);
this.validateTemplates();
},
/* 全部选择 */
/* 全选所有模板 */
onSelectAll() {
this.webList.forEach((item) => item.checked = true);
this.serverList.forEach((item) => item.checked = true);
this.onTableSelectAll(this.webList);
this.onTableSelectAll(this.serverList);
},
/* 反选 */
/* 反选所有模板 */
onInvertSelection() {
this.webList.forEach((item) => item.checked = !item.checked);
this.serverList.forEach((item) => item.checked = !item.checked);
this.onTableInvertSelection(this.webList);
this.onTableInvertSelection(this.serverList);
},
/* 清除所选中的 */
/* 清空所有模板的选择 */
onClearAll() {
this.webList.forEach((item) => item.checked = false);
this.serverList.forEach((item) => item.checked = false);
},
/* 开始生成 */
onGenerator() {
},
this.onTableClearAll(this.webList);
this.onTableClearAll(this.serverList);
}
},
async mounted() {
// 组件挂载时获取模板列表
await this.getVmsResourcePathList();
}
}

View File

@ -0,0 +1,159 @@
const MainGeneratorPage = defineComponent({
name: "MainGeneratorPage",
template: `
<div :class="{'show': generatorPageFlag}" class="offcanvas offcanvas-start w-50" data-bs-scroll="false">
<!-- 侧边栏头部 -->
<div class="offcanvas-header bg-primary text-white">
<div>
<h5 class="offcanvas-title mb-1" id="offcanvasWithBothOptionsLabel">
<i class="bi bi-file-earmark-code me-2"></i>
</h5>
<p class="small mb-0 opacity-75">已生成的文件列表</p>
</div>
<button @click="onGeneratorPageFlagClick" aria-label="Close" class="btn-close btn-close-white" type="button"></button>
</div>
<!-- 侧边栏内容区域 -->
<div class="offcanvas-body p-0">
<ol class="list-group list-group-numbered rounded-0">
<li class="list-group-item border-0 p-3" v-for="[table, value] in Object.entries(generatorData)" :key="table">
<h6 class="mb-0 fw-bold text-primary d-inline-block"><i class="bi bi-table me-2"></i>{{table}}</h6>
<span class="badge bg-primary rounded-pill float-end">{{value.length}} 模板</span>
<!-- 生成的文件列表 -->
<ul class="list-group list-group-flush">
<!-- 单个文件卡片 -->
<li class="list-group-item p-0 mb-2 border-0" v-for="(item, index) in value" :key="item.id">
<div class="card shadow-sm border-0">
<!-- 文件标题 - 可折叠 -->
<div class="card-header bg-light d-flex justify-content-between align-items-center p-3"
role="button" data-bs-toggle="collapse" :data-bs-target="'#collapse-' + item.id" aria-expanded="false">
<div class="d-flex align-items-center">
<i class="bi bi-bi-file-earmark-code me-2 text-primary fs-5"></i>
<span class="text-truncate" style="max-width: 80%">
{{item.comment}}{{item.path}}
</span>
</div>
<i class="bi bi-chevron-down transition-all"></i>
</div>
<!-- 文件内容 -->
<div class="collapse" :id="'collapse-' + item.id">
<div class="card-body p-0 bg-dark">
<div class="position-relative">
<!-- 使用条件渲染显示不同图标 -->
<i v-if="!copied"
class="bi bi-clipboard position-absolute text-white fs-4"
role="button" style="top: 10px; right: 10px" @click="onCopyToClipboard(item.code)"></i>
<i v-else
class="bi bi-clipboard-check position-absolute text-success fs-4"
role="button" style="top: 10px; right: 10px"></i>
</div>
<pre>
<code class="language-javascript hljs">
{{item.code}}
</code>
</pre>
</div>
</div>
</div>
</li>
</ul>
</li>
</ol>
</div>
<!-- 侧边栏底部操作按钮 -->
<div class="offcanvas-footer p-3 bg-light border-top">
<div class="d-grid gap-2">
<button class="btn btn-primary" @click="onDownloadAllFile">
<i class="bi bi-download me-2"></i>
</button>
</div>
</div>
</div>
`,
props: {
// 是否显示生成页面
generatorPageFlag: {type: Boolean, default: true},
// 生成模板数据
generatorData: {type: Object},
},
data() {
return {
// 控制图标状态的响应式变量
copied: false
}
},
methods: {
/**
* 点击复制图表
* 几秒后恢复原状
*/
onCopyToClipboard(code) {
const textarea = document.createElement('textarea');
textarea.value = code;
// 避免滚动到页面底部
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
textarea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
this.copied = true;
antd.notification.open({
type: 'success',
message: '复制成功',
description: '已将内容复制至剪切板',
duration: 3,
});
// 几秒后恢复原状
setTimeout(() => {
this.copied = false;
}, 2000);
}
} catch (err) {
antd.notification.open({
type: 'success',
message: '复制失败',
description: err.message,
duration: 3,
});
}
document.body.removeChild(textarea);
},
/* 下载全部文件 */
onDownloadAllFile() {
// 遍历所有文件并下载
Object.values(this.generatorData).flat().forEach(item => {
const blob = new Blob([item.code], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
// 从路径中提取文件名
const fileName = item.path.split('/').pop();
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
// 清理
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
});
},
/* 关闭窗口 */
onGeneratorPageFlagClick() {
this.$emit("update:generatorPageFlag", !this.generatorPageFlag)
},
},
});

View File

@ -1,138 +1,219 @@
const MainTable = defineComponent({
name: "MainTable",
props: ["tableList", "rawTableList", "loading"],
template: `
<div class="card mt-2 shadow-sm">
<!-- 表格标题 -->
<div class="card-header bg-primary bg-opacity-10">
<h5 class="card-title mb-0">
<i class="bi bi-table me-2"></i>
</h5>
</div>
<div class="card mt-2 shadow-sm">
<!-- 表格标题和选择选项 -->
<div class="d-flex justify-content-between card-header bg-primary bg-opacity-10">
<h5 class="card-title mb-0">
<i class="bi bi-table me-2"></i>
</h5>
<!-- 表格体 -->
<div class="card-body p-0">
<div class="table-responsive">
<!-- 加载中... -->
<div class="p-5 text-center" v-if="loading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- 展示当前数据库所有的表 -->
<table class="table table-striped table-bordered table-hover mb-0" v-else>
<thead class="table-light">
<tr>
<th scope="col" width="2%">
<input class="form-check-input border-secondary" type="checkbox" value="option2">
</th>
<th scope="col" width="5%">#</th>
<th scope="col" width="30%">表名</th>
<th scope="col" width="35%">注释</th>
<th scope="col" width="18%">所属数据库</th>
<th class="text-center" scope="col" width="10%">操作</th>
</tr>
</thead>
<tbody>
<tr :key="index" v-for="(table,index) in paginatedTableList">
<th scope="row">
<input class="form-check-input border-secondary" type="checkbox" value="option2">
</th>
<th scope="row">{{index + 1}}</th>
<td><a class="text-decoration-none" href="#">{{ table.tableName}}</a></td>
<td>{{ table.comment }}</td>
<td>{{ table.tableCat }}</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-primary" @click="onGeneratorCodeClick">
<i class="bi bi-gear"></i>
</button>
</td>
</tr>
</tbody>
</table>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectAll"
value="all" v-model="selectedOption">
<label class="form-check-label" for="radioSelectAll">选择全部</label>
</div>
</div>
<!-- 底部分页 -->
<div class="card-footer bg-light">
<div class="d-flex justify-content-between align-items-center">
<div class="form-text"> {{ rawTableList.length }} </div>
<nav aria-label="Page navigation">
<ul class="pagination mb-0">
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="currentPage = 1" class="page-link" href="#">
<i class="bi bi-chevron-double-left"></i>
</a>
</li>
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="currentPage--" class="page-link" href="#">
<i class="bi bi-chevron-left"></i>
</a>
</li>
<!-- 显示页码 -->
<li :class="{ active: currentPage === page }" :key="page" class="page-item"
v-for="page in visiblePages">
<a @click.prevent="currentPage = page" class="page-link" href="#">{{ page }}</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="currentPage++" class="page-link" href="#">
<i class="bi bi-chevron-right"></i>
</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="currentPage = totalPages" class="page-link" href="#">
<i class="bi bi-chevron-double-right"></i>
</a>
</li>
</ul>
</nav>
<div class="dropdown">
<button aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown" id="itemsPerPageDropdown" type="button">
每页 {{ itemsPerPage }}
</button>
<ul aria-labelledby="itemsPerPageDropdown" class="dropdown-menu">
<li :key="index" v-for="(table,index) in tablePageOptions">
<a @click.prevent="itemsPerPage = table" class="dropdown-item" href="JavaScript:">
{{table}} /
</a>
</li>
</ul>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" name="TableSelectRadios" type="radio" id="radioSelectCurrentPage"
value="current" v-model="selectedOption">
<label class="form-check-label" for="radioSelectCurrentPage">选择当前页</label>
</div>
</div>
</div>
<!-- 表格内容区域 -->
<div class="card-body p-0">
<div class="table-responsive">
<!-- 加载状态 -->
<div class="p-5 text-center" v-if="loading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<!-- 空状态提示 -->
<div class="p-5 text-center" v-else-if="tableList.length === 0">
<i class="bi bi-exclamation-circle fs-1 text-muted"></i>
<p class="mt-2 text-muted">没有找到数据表</p>
</div>
<!-- 数据表格 -->
<table class="table table-striped table-bordered table-hover mb-0" v-else>
<thead class="table-light">
<tr>
<th scope="col" width="2%">
<input class="form-check-input border-secondary" type="checkbox"
v-model="tableSelectAllChecked" @change="onSelectAllTable">
</th>
<th scope="col" width="3%">#</th>
<th scope="col" width="30%">表名</th>
<th scope="col" width="35%">注释</th>
<th scope="col" width="20%">所属数据库</th>
<th class="text-center" scope="col" width="10%">操作</th>
</tr>
</thead>
<tbody>
<tr :key="table.tableName" v-for="(table, index) in paginatedTableList">
<td>
<input class="form-check-input border-secondary" type="checkbox"
v-model="table.checked" @change="onSelectTable($event, table)">
</td>
<td>{{ (currentPage - 1) * itemsPerPage + index + 1 }}</td>
<td>
<a class="text-decoration-none" href="#" @click.prevent="onGeneratorCode(table)">
{{ table.tableName }}
</a>
</td>
<td>{{ table.comment || '无注释' }}</td>
<td>{{ table.tableCat }}</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-primary" @click="onGeneratorCode(table)">
<i class="bi bi-gear"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 分页控件 -->
<div class="card-footer bg-light" v-if="tableList.length > 0">
<div class="d-flex justify-content-between align-items-center">
<div class="form-text">
显示 {{ (currentPage - 1) * itemsPerPage + 1 }}~{{ Math.min(currentPage * itemsPerPage, rawTableList.length) }} {{ rawTableList.length }}
</div>
<nav aria-label="Page navigation">
<ul class="pagination mb-0">
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="goToPage(1)" class="page-link" href="#">
<i class="bi bi-chevron-double-left"></i>
</a>
</li>
<li :class="{ disabled: currentPage === 1 }" class="page-item">
<a @click.prevent="goToPage(currentPage - 1)" class="page-link" href="#">
<i class="bi bi-chevron-left"></i>
</a>
</li>
<!-- 显示页码 -->
<li v-for="page in visiblePages" :key="page"
:class="{ active: currentPage === page }" class="page-item">
<a @click.prevent="goToPage(page)" class="page-link" href="#">{{ page }}</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="goToPage(currentPage + 1)" class="page-link" href="#">
<i class="bi bi-chevron-right"></i>
</a>
</li>
<li :class="{ disabled: currentPage === totalPages }" class="page-item">
<a @click.prevent="goToPage(totalPages)" class="page-link" href="#">
<i class="bi bi-chevron-double-right"></i>
</a>
</li>
</ul>
</nav>
<div class="dropdown">
<button aria-expanded="false" class="btn btn-outline-secondary dropdown-toggle"
data-bs-toggle="dropdown" id="itemsPerPageDropdown" type="button">
每页 {{ itemsPerPage }}
</button>
<ul aria-labelledby="itemsPerPageDropdown" class="dropdown-menu">
<li v-for="option in tablePageOptions" :key="option">
<a @click.prevent="changeItemsPerPage(option)" class="dropdown-item" href="#">
{{ option }} /
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
`,
props: {
// 加载状态
loading: {type: Boolean, default: false},
// 处理后的表格数据包含checked状态
tableList: {type: Array, required: true, default: () => []},
// 原始表格数据不包含checked状态
rawTableList: {type: Array, required: true, default: () => []},
// 生成代码的回调函数
onGeneratorCode: {type: Function, required: true},
// 表单数据
form: {type: Object, required: true},
// 数据库选择发生变化
dbSelect: {type: String, required: true},
},
data() {
return {
// 分页相关数据
currentPage: ref(1),
// 每页数
itemsPerPage: ref(30),
// 表格选项
tablePageOptions: [5, 10, 15, 30, 50, 100, 150, 200]
// 当前页码
currentPage: 1,
// 每页显示条数
itemsPerPage: 30,
// 每页条数选项
tablePageOptions: [5, 10, 15, 30, 50, 100, 150, 200],
// 选择模式all-全选 current-当前页
selectedOption: "current",
// 表头全选状态
tableSelectAllChecked: false
}
},
computed: {
// 计算总页数
/**
* 计算总页数
* @returns {number} 总页数
*/
totalPages() {
const totalItems = this.rawTableList.length;
return Math.ceil(totalItems / this.itemsPerPage);
return Math.ceil(this.rawTableList.length / this.itemsPerPage);
},
// 分页后的数据
/**
* 当前页的数据
* @returns {Array} 分页后的数据
*/
paginatedTableList() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.tableList.slice(start, end);
},
/**
* 当前选中的表名数组
* @returns {Array} 选中的表名
*/
selectedTableNames() {
return this.tableList.filter(table => table.checked).map(table => table.tableName);
}
},
methods: {
/* 计算可见的页码范围 */
/**
* 跳转到指定页码
* @param {number} page - 目标页码
*/
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
/**
* 更改每页显示条数
* @param {number} size - 每页条数
*/
changeItemsPerPage(size) {
this.itemsPerPage = size;
// 重置到第一页
this.currentPage = 1;
},
/**
* 计算可见的页码范围
* @returns {Array} 可见页码数组
*/
visiblePages() {
// 显示当前页前后各2页
const range = 2;
@ -146,9 +227,91 @@ const MainTable = defineComponent({
return pages;
},
/* 点击生成代码 */
onGeneratorCodeClick() {
console.log(6655)
/**
* 表头全选/取消全选
* 当前选择的是所有书库操作列表为 this.tableList
* 当前选择的是当前页的数据库表操作列表为 this.paginatedTableList
*/
onSelectAllTable() {
const checked = this.tableSelectAllChecked;
// 选择是否是所有的数据库
const tablesToUpdate = this.selectedOption === "all"
? this.tableList
: this.paginatedTableList;
// 将数据库变为当前选中的状态
tablesToUpdate.forEach(table => table.checked = checked);
// 更新父级列表状态
this.updateFormSelectedTables();
},
/**
* 选择单个表
* @param {Event} event - 事件对象
* @param {Object} table - 表数据对象
*/
onSelectTable(event, table) {
table.checked = event.target.checked;
this.updateFormSelectedTables();
// 更新表头全选状态
if (!table.checked) {
this.tableSelectAllChecked = false;
} else {
this.tableSelectAllChecked = this.paginatedTableList.every(t => t.checked);
}
},
/* 更新表单中选中的表名 */
updateFormSelectedTables() {
const payload = {
...this.form,
tableNames: this.selectedTableNames
}
// 更新父级 form 的内容
this.$emit("update:form", payload);
},
/**
* 重置父级表单
* 如果要同时更新会变得很复杂所以在这里讲逻辑定义为
* 选型变化时直接取消全部之后重新选择
*/
restForm() {
this.tableSelectAllChecked = false;
this.tableList.forEach(table => table.checked = false);
this.updateFormSelectedTables();
}
},
watch: {
/**
* 监听选择模式变化
*/
selectedOption() {
this.restForm();
},
/**
* 监听当前页变化
*/
currentPage() {
this.restForm();
},
/**
* 监听每页条数变化
*/
itemsPerPage() {
this.restForm();
},
/**
* 数据库选择发生变化也重置表单
*/
dbSelect() {
this.restForm();
}
}
})
});

View File

@ -7,8 +7,10 @@
<link rel="stylesheet" th:href="@{/src/lib/css/bootstrap.min.css}">
<!-- 本地引入 Bootstrap Icons -->
<link rel="stylesheet" th:href="@{/src/lib/css/bootstrap-icons.min.css}">
<!-- 引入Highlight.js的CSS -->
<link rel="stylesheet" th:href="@{/src/lib/css/atom-one-dark.min.css}">
<!-- 本地引入 Vue.js -->
<script th:src="@{/src/lib/js/vue/vue.global.prod.js}"></script>
<script th:src="@{/src/lib/js/vue/vue.global.js}"></script>
<!-- 本地引入 Bootstrap JS -->
<script th:src="@{/src/lib/js/boostrap/bootstrap.bundle.min.js}"></script>
<!-- 本地引入 popper JS -->
@ -39,23 +41,38 @@
v-model:db-select="dbSelect" v-model:table-select="tableSelect"></main-card>
<!-- 填写生成表单 -->
<main-form :form="form"></main-form>
<main-form :on-clear-generator-data="onClearGeneratorData" :on-generator-code="onGeneratorCode"
ref="mainFormRef"
v-model:form="form"></main-form>
<!-- 表格显示 -->
<main-table :loading="loading" :raw-table-list="rawTableList" :table-list="tableList"></main-table>
<main-table :db-select="dbSelect" :loading="loading" :on-generator-code="onGeneratorCode"
:raw-table-list="rawTableList" :table-list="tableList" v-model:form="form"></main-table>
<!-- 模板生成页面 -->
<main-generator-page :generator-data="generatorData"
v-model:generator-page-flag="generatorPageFlag"></main-generator-page>
</div>
</div>
</body>
<!-- 引入Highlight.js -->
<script th:src="@{/src/lib/js/highlightjs/highlight.min.js}"></script>
<script th:src="@{/src/lib/js/highlightjs/javascript.min.js}"></script>
<!-- 初始化 Highlight.js -->
<script th:src="@{/src/config/highlight-config.js}"></script>
<!-- 设置 popper 提示框 -->
<script th:src="@{/src/config/popper-config.js}"></script>
<!-- 加载全局axios配置 -->
<script th:src="@{/src/config/axios-config.js}"></script>
<!-- 引入组件 -->
<script th:src="@{/src/components/AppHeader.js}"></script>
<script th:src="@{/src/views/home/MainCard.js}"></script>
<script th:src="@{/src/views/home/MainForm.js}"></script>
<script th:src="@{/src/views/home/MainTable.js}"></script>
<script th:src="@{/src/views/home/MainGeneratorPage.js}"></script>
<script>
const {createApp, ref} = Vue
@ -73,15 +90,13 @@
// 是否加载
loading: ref(false),
// 提交的表单
form: {
form: ref({
// 作者名称
authorName: "Bunny",
// requestMapping名称
requestMapping: "/api",
// 表名称
tableName: "",
// 类名称
className: "",
tableNames: [],
// 包名称
packageName: "cn.bunny",
// 时间格式
@ -90,21 +105,24 @@
tablePrefixes: "t_,sys_,qrtz_,log_",
// 生成代码路径
path: []
},
}),
// 是否显示生成页面
generatorPageFlag: ref(false),
// 生成的数据
generatorData: ref({}),
};
},
methods: {
/* 获取[当前/所有]数据库表 */
async getDatabaseTableList() {
this.loading = true;
// 查询数据库表
const response = await axiosInstance.get("/table/databaseTableList", {params: {dbName: this.dbSelect}});
const {data, code, message} = response;
if (code !== 200) {
antd.message.error(message);
return
return;
}
// 设置数据库列表
@ -116,6 +134,36 @@
this.loading = false;
},
/**
* 生成代码
* @returns {Promise<void>}
*/
async onGeneratorCode() {
// 验证表单是否通过
const isValidate = this.$refs.mainFormRef.validateForm();
if (!isValidate) return;
// 请求生成模板
const {data, code, message} = await axiosInstance.post("/vms/generator", this.form);
// 判断是否请求成功
if (code !== 200) return;
else antd.message.success(message);
// 显示生成的页面
this.generatorPageFlag = true;
this.generatorData = data;
// 等待 DOM 更新,之后手动更新代码高亮
await this.$nextTick();
hljs.highlightAll();
},
/* 清空已经生成的内容 */
onClearGeneratorData() {
this.generatorData = {};
},
},
watch: {
/* 数据表选择 */
@ -132,7 +180,7 @@
// 根据表名进行过滤筛选或者根据注释内容进行筛选
this.tableList = this.tableList.filter(table => table.tableName.includes(val) || table.tablePrefixes.includes(val));
}
}
},
}
});
@ -141,6 +189,7 @@
app.component('MainCard', MainCard);
app.component('MainForm', MainForm);
app.component('MainTable', MainTable);
app.component('MainGeneratorPage', MainGeneratorPage);
app.mount('#app');
</script>