初始化版本,引入智慧食堂模块,第三方模块

This commit is contained in:
zjf committed 2024-12-06 18:48:18 +08:00
commit d22f88efd3
677 files changed
+69341

No files matched your search

+151
View File
@@ -0,0 +1,151 @@
<template>
<el-dialog title="新增字典" v-model="visible" width="600">
<el-form :model="dataForm" :rules="dataRules" label-width="100px" ref="dicDialogFormRef" v-loading="loading">
<el-form-item :label="$t('sysdict.systemFlag')" prop="systemFlag">
<el-radio-group v-model="dataForm.systemFlag">
<el-radio border :key="index" :label="item.value" v-for="(item, index) in dict_type">
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('sysdict.dictType')" prop="dictType">
<el-input :placeholder="$t('sysdict.inputDictTypeTip')" :disabled="dataForm.id !== ''" clearable
v-model="dataForm.dictType"></el-input>
</el-form-item>
<el-form-item :label="$t('sysdict.description')" prop="description">
<el-input :placeholder="$t('sysdict.inputDescriptionTip')" clearable v-model="dataForm.description"></el-input>
</el-form-item>
<el-col :span="24" class="mb20">
<el-form-item :label="t('dictItem.name')" prop="columns">
<el-table :data="dataForm.columns" border style="width: 100%" max-height="500">
<el-table-column type="index" :label="t('createTable.index')" width="50">
<template #header>
<el-button icon="Plus" size="small" type="primary" circle @click="onAddItem"></el-button>
</template>
<template #default="scope">
<el-button icon="Minus" size="small" type="danger" circle
@click="handleDelete(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
<el-table-column prop="label" :label="$t('dictItem.label')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.label" :placeholder="t('dictItem.inputLabelTip')"/>
</template>
</el-table-column>
<el-table-column prop="value" :label="$t('dictItem.itemValue')" show-overflow-tooltip>
<template #default="scope">
<el-input v-model="scope.row.value" :placeholder="t('dictItem.inputItemValueTip')"/>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-col>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button @click="onSubmit" type="primary" :disabled="loading">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" name="systemDicDialog" setup>
import {useI18n} from 'vue-i18n';
import {addItemObj, addObj, getObj, validateDictType} from '/@/api/admin/dict';
import {useDict} from '/@/hooks/dict';
import {useMessage} from '/@/hooks/message';
import {rule} from '/@/utils/validate';
// 定义子组件向父组件传值/事件
const emit = defineEmits(['refresh']);
const {dict_type} = useDict('dict_type');
const {t} = useI18n();
// 定义变量内容
const dicDialogFormRef = ref();
const visible = ref(false);
const loading = ref(false);
const selectRow = ref({});
const dataForm = reactive({
id: '',
dictType: '',
description: '',
systemFlag: '0',
remarks: '',
columns: [] as any
});
const dataRules = reactive({
dictType: [
{validator: rule.overLength, trigger: 'blur'},
{required: true, message: '类型不能为空', trigger: 'blur'},
{validator: rule.validatorNameCn, trigger: 'blur'},
{
validator: (rule: any, value: any, callback: any) => {
validateDictType(rule, value, callback, dataForm.id !== '');
},
trigger: 'blur',
},
],
systemFlag: [{required: true, message: '字典类型不能为空', trigger: 'blur'}],
description: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '描述不能为空',
trigger: 'blur'
}],
columns: [{required: true, message: '字典项不能为空', trigger: 'blur'}],
});
// 打开弹窗
const openDialog = () => {
visible.value = true;
dataForm.id = '';
nextTick(() => {
dicDialogFormRef.value?.resetFields();
});
};
// 提交
const onSubmit = async () => {
const valid = await dicDialogFormRef.value.validate().catch(() => {
});
if (!valid) return false;
try {
loading.value = true;
// 添加字典
const {data} = await addObj(dataForm);
// addItemObj
dataForm.columns.forEach(async (item: any) => {
item.dictId = data.id;
item.dictType = dataForm.dictType;
await addItemObj(item);
});
useMessage().success(t('common.addSuccessText'));
visible.value = false;
emit('refresh', dataForm.dictType);
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
let index = 1;
const onAddItem = () => {
dataForm.columns.push({sortOrder: `${index++}`});
}
const handleDelete = (index: number, row: any) => {
dataForm.columns.splice(index, 1)
}
// 暴露变量
defineExpose({
openDialog,
});
</script>
+138
View File
@@ -0,0 +1,138 @@
<template>
<el-dialog :close-on-click-modal="false" title="子表配置" draggable v-model="visible">
<el-form :model="form" :rules="dataRules" ref="dataFormRef">
<el-row :gutter="35">
<el-col :span="8">
<el-form-item prop="childTableName">
<template #label> 子表名<tip content="关联表的子表,例如一对多中的存储多信息的表" /> </template>
<el-select placeholder="请选择子表" v-model="form.childTableName" filterable @change="getChildTableColumnList">
<el-option :key="item" :label="item" :value="item" v-for="item in childTableList"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="mainField">
<template #label> 主表字段<tip content="一般为主表的主键字段" /> </template>
<el-select placeholder="请选关联字段" v-model="form.mainField" filterable>
<el-option :key="item" :label="item" :value="item" v-for="item in mainTableColumnList"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item prop="childField">
<template #label> 子表字段<tip content="子表中对应主表主键的关联字段" /> </template>
<el-select placeholder="请选关联字段" v-model="form.childField" filterable>
<el-option :key="item" :label="item" :value="item" v-for="item in childTableColumnList"> </el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onClear">清空</el-button>
<el-button @click="onSubmit" type="primary">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="child">
import {useListTableApi, useListTableColumnApi, useSyncTableApi} from '/@/api/gen/table';
import {useMessage} from "/@/hooks/message";
const emit = defineEmits(['update:modelValue']);
defineProps({
modelValue: Object,
});
const visible = ref(false);
const dataFormRef = ref();
const form = reactive({
childTableName: '',
mainField: '',
childField: '',
});
const dataRules = ref({
childTableName: [{ required: true, message: '请选择子表', trigger: 'blur' }],
mainField: [{ required: true, message: '请选择关联字段', trigger: 'blur' }],
childField: [{ required: true, message: '请选择关联字段', trigger: 'blur' }],
});
const childTableList = ref();
const childTableColumnList = ref();
const mainTableColumnList = ref();
const currentDsName = ref('');
// 打开弹窗
const openDialog = (row: any) => {
currentDsName.value = row.dsName;
visible.value = true;
getAllTable(row.dsName);
getAllField(row.dsName, row.tableName);
if (row.childTableName) {
form.childTableName = row.childTableName;
form.mainField = row.mainField;
form.childField = row.childField;
}
};
// 获取当前数据下的所有表
const getAllTable = (dsName: string) => {
useListTableApi(dsName).then((res) => {
childTableList.value = res.data;
});
};
// 获取对应表的所有字段
const getAllField = (dsName: string, tableName: string) => {
useListTableColumnApi(dsName, tableName).then((res) => {
mainTableColumnList.value = res.data;
});
};
// 获取子表的全部字段
const getChildTableColumnList = (val: string) => {
form.childField = '';
useListTableColumnApi(currentDsName.value, val).then((res) => {
childTableColumnList.value = res.data;
});
};
// 确认提交
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
// 同步子表数据
useSyncTableApi(currentDsName.value,form.childTableName).then(() => {
useMessage().success('子表信息同步成功')
});
visible.value = false;
};
// 清空子表配置,后台当单表处理
const onClear = () => {
form.childTableName = '';
form.mainField = '';
form.childField = '';
visible.value = false;
};
watch(
() => form,
(val) => {
emit('update:modelValue', val);
},
{ deep: true, immediate: true }
);
onMounted(() => {});
// 暴露变量
defineExpose({
openDialog,
});
</script>
+378
View File
@@ -0,0 +1,378 @@
<template>
<el-tabs v-model="activeName">
<!-- 属性设置面板 -->
<el-tab-pane label="属性设置" name="field">
<sc-form-table ref="fieldTable" v-model="fieldList" :hideAdd="true" :hideDelete="true" drag-sort
placeholder="暂无数据">
<el-table-column label="主键" prop="primaryPk" width="80" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.primaryPk" true-label="1" false-label="0" disabled></el-checkbox>
</template>
</el-table-column>
<el-table-column label="字段名" prop="fieldName" show-overflow-tooltip></el-table-column>
<el-table-column label="说明" prop="fieldComment" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.fieldComment" placeholder="请输入说明"></el-input>
</template>
</el-table-column>
<el-table-column label="字段类型" prop="fieldType" show-overflow-tooltip></el-table-column>
<el-table-column label="属性名" prop="attrName" show-overflow-tooltip>
<template #default="{ row }">
<el-input v-model="row.attrName" placeholder="请输入属性名"></el-input>
</template>
</el-table-column>
<el-table-column label="属性类型" prop="attrType" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.attrType" placeholder="请选择属性类型" @change="handleChangeRow(row)">
<el-option v-for="item in typeList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="自动填充" prop="autoFill" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.autoFill" placeholder="请选择类型">
<el-option v-for="item in fillList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="字典名称" prop="fieldDict" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.fieldDict" placeholder="请选择类型" filterable clearable
:disabled="row.primaryPk === '1'">
<template #prefix>
<el-button icon="Plus" type="primary" link @click.stop="handleAddDict(row)"></el-button>
</template>
<el-option v-for="item in fieldDictList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
</sc-form-table>
</el-tab-pane>
<!-- 列表设置面板 -->
<el-tab-pane label="列表查询" name="third">
<sc-form-table ref="gridTable" v-model="fieldList" :hideAdd="true" :hideDelete="true" placeholder="暂无数据">
<el-table-column label="属性名" prop="attrName" show-overflow-tooltip></el-table-column>
<el-table-column label="说明" prop="fieldComment" show-overflow-tooltip></el-table-column>
<el-table-column label="列表显示" prop="gridItem" width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.gridItem" true-label="1" false-label="0"
:disabled="row.primaryPk === '1'"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="是否排序" prop="gridSort" width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.gridSort" true-label="1" false-label="0"
:disabled="row.primaryPk === '1'"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询显示" prop="gridSort" width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.queryItem" true-label="1" false-label="0"
:disabled="row.primaryPk === '1'"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="查询表单类型" prop="queryFormType" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.queryFormType" placeholder="请选择查询表单类型" :disabled="row.primaryPk === '1'">
<el-option v-for="item in queryTypeList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="查询方式" prop="queryType" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.queryType" placeholder="请选择查询方式" :disabled="row.primaryPk === '1'">
<el-option v-for="item in queryList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
</sc-form-table>
</el-tab-pane>
<el-tab-pane label="表单页面" name="form">
<sc-form-table ref="formTable" v-model="fieldList" :hideAdd="true" :hideDelete="true" placeholder="暂无数据">
<el-table-column label="属性名" prop="attrName" show-overflow-tooltip></el-table-column>
<el-table-column label="说明" prop="fieldComment" show-overflow-tooltip></el-table-column>
<el-table-column label="表单类型" prop="formType" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.formType" placeholder="请选择表单类型" :disabled="row.primaryPk === '1'">
<el-option v-for="item in formTypeList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
<el-table-column label="是否显示" prop="formItem" width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.formItem" true-label="1" false-label="0"
:disabled="row.primaryPk === '1'"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="表单必填" prop="formRequired" width="100" show-overflow-tooltip>
<template #default="{ row }">
<el-checkbox v-model="row.formRequired" true-label="1" false-label="0"
:disabled="row.primaryPk === '1'"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="表单效验" prop="formValidator" show-overflow-tooltip>
<template #default="{ row }">
<el-select v-model="row.formValidator" placeholder="请选择表单效验" :disabled="row.primaryPk === '1'">
<el-option v-for="item in formValidatorList" :key="item.value" :label="item.label" :value="item.value"/>
</el-select>
</template>
</el-table-column>
</sc-form-table>
</el-tab-pane>
</el-tabs>
<!-- 新增字典 -->
<form-dialog ref="formDialogRef" @refresh="handleDictRefresh"/>
</template>
<script setup lang="ts">
import {useTableFieldSubmitApi, useTableApi, fetchDictList} from '/@/api/gen/table';
import {list} from '/@/api/gen/fieldtype';
import Sortable from 'sortablejs';
const scFormTable = defineAsyncComponent(() => import('/@/components/FormTable/index.vue'));
const FormDialog = defineAsyncComponent(() => import('./add-dict.vue'));
const activeName = ref();
const tableId = ref('');
const props = defineProps({
tableName: {
type: String,
},
dsName: {
type: String,
},
});
const visible = ref(false);
const sortable = ref() as any;
const formDialogRef = ref();
const typeList = ref([]) as any;
const fieldDictList = ref([]) as any;
const selectRow = ref();
const dsName = ref();
const tableName = ref();
const fieldList = ref([]);
const fillList = reactive([
{label: 'DEFAULT', value: 'DEFAULT'},
{label: 'INSERT', value: 'INSERT'},
{label: 'UPDATE', value: 'UPDATE'},
{label: 'INSERT_UPDATE', value: 'INSERT_UPDATE'},
]);
const queryList = reactive([
{label: '=', value: '='},
{label: '!=', value: '!='},
{label: '>', value: '>'},
{label: '>=', value: '>='},
{label: '<', value: '<'},
{label: '<=', value: '<='},
{label: 'like', value: 'like'},
{label: 'left like', value: 'left like'},
{label: 'right like', value: 'right like'},
]);
const formTypeList = reactive([
{label: '单行文本', value: 'text'},
{label: '多行文本', value: 'textarea'},
{label: '数字', value: 'number'},
{label: '富文本编辑器', value: 'editor'},
{label: '下拉框', value: 'select'},
{label: '单选按钮', value: 'radio'},
{label: '复选框', value: 'checkbox'},
{label: '日期', value: 'date'},
{label: '日期时间', value: 'datetime'},
{label: '文件上传', value: 'upload-file'},
{label: '图片上传', value: 'upload-img'},
]);
const queryTypeList = reactive([
{label: '单行文本', value: 'text'},
{label: '多行文本', value: 'textarea'},
{label: '数字', value: 'number'},
{label: '下拉框', value: 'select'},
{label: '单选按钮', value: 'radio'},
{label: '复选框', value: 'checkbox'},
{label: '日期', value: 'date'},
{label: '日期时间', value: 'datetime'},
]);
const formValidatorList = reactive([
{label: '去重', value: 'duplicate'},
{label: '数字', value: 'number'},
{label: '字母', value: 'letter'},
{label: '字母和数字', value: 'letterAndNumber'},
{label: '手机号码', value: 'mobilePhone'},
{label: '字母开头,仅可包含数字', value: 'letterStartNumberIncluded'},
{label: '禁止中文输入', value: 'noChinese'},
{label: '必须中文输入', value: 'chinese'},
{label: '电子邮箱', value: 'email'},
{label: 'URL网址', value: 'url'},
]);
const propToType = reactive({
tinyint: 'number',
smallint: 'number',
mediumint: 'number',
int: 'number',
integer: 'number',
bigint: 'number',
float: 'number',
datetime: 'datetime',
LocalDateTime: 'datetime',
date: 'date',
LocalDate: 'date',
Long: 'number',
Float: 'number',
Double: 'number',
BigDecimal: 'number',
text: 'textarea',
String: 'text',
longtext: 'editor',
bit: 'radio',
Boolean: 'radio',
char: 'radio',
varchar: 'text',
});
/**
* 属性修改触发事件
* @param row
*/
const handleChangeRow = (row: any) => {
row.queryFormType = propToType[row.attrType];
row.formType = propToType[row.attrType];
};
/**
* 添加字典
* @param row
*/
const handleAddDict = (row: object) => {
selectRow.value = row;
formDialogRef.value.openDialog();
};
/**
* 刷新字典
* @param dictType
*/
const handleDictRefresh = async (dictType: string) => {
await getDictList();
selectRow.value.fieldDict = dictType;
};
const openDialog = (dName: string, tName: string) => {
visible.value = true;
tableName.value = tName;
dsName.value = dName;
activeName.value = 'field';
rowDrop();
getTable(dName, tName);
getFieldTypeList();
getDictList();
};
onMounted(() => {
tableName.value = String(props.tableName);
dsName.value = String(props.dsName);
activeName.value = 'field';
rowDrop();
getTable(dsName.value, tableName.value);
getFieldTypeList();
getDictList();
});
const rowDrop = () => {
nextTick(() => {
const el: any = window.document.querySelector('#pane-field');
sortable.value = Sortable.create(el, {
handle: '.drag-btn',
onEnd: (e: any) => {
const {newIndex, oldIndex} = e;
const currRow = fieldList.value.splice(oldIndex, 1)[0];
fieldList.value.splice(newIndex, 0, currRow);
},
});
});
};
const loading = ref(false);
const getTable = (dsName: string, tableName: string) => {
fieldList.value = []; // 避免第一次数据初始化, 表格显示历史数据
loading.value = true;
useTableApi(dsName, tableName)
.then((res) => {
tableId.value = res.data.id;
fieldList.value = res.data.fieldList.map((item) => {
item.queryFormType ? item.queryFormType : propToType[item.fieldType];
item.formType ? item.formType : propToType[item.fieldType];
return item;
});
})
.finally(() => {
loading.value = false;
});
};
const getFieldTypeList = async () => {
typeList.value = [];
// 获取数据
const {data} = await list();
// 设置属性类型值
const typeMap = new Map();
data.forEach((item: any) => {
const {attrType, columnType} = item;
if (!typeMap.has(attrType)) {
typeMap.set(attrType, columnType);
typeList.value.push({label: attrType, value: attrType});
}
});
// 增加Object类型
typeList.value.push({label: 'Object', value: 'Object'});
};
const getDictList = () => {
fetchDictList().then((res) => {
for (const item of res.data) {
fieldDictList.value.push({label: item.description, value: item.dictType});
}
});
};
// 表单提交
const submitHandle = () => {
return new Promise((resolve) => {
useTableFieldSubmitApi(dsName.value, tableName.value, fieldList.value).then(() => {
resolve(tableId.value);
});
});
};
defineExpose({
openDialog,
submitHandle,
});
</script>
<style lang="scss">
.sortable-row-gen .drag-btn {
cursor: move;
font-size: 12px;
}
.sortable-row-gen .vxe-body--row.sortable-ghost,
.sortable-row-gen .vxe-body--row.sortable-chosen {
background-color: #dfecfb;
}
.vxe-select–panel {
z-index: 9997 !important;
}
</style>
+381
View File
@@ -0,0 +1,381 @@
<template>
<el-form :model="dataForm" :rules="dataRules" label-width="120px" ref="dataFormRef" v-loading="loading">
<el-row>
<el-col :span="12" class="mb20">
<el-row>
<el-col :span="20">
<el-form-item label="表名" prop="tableName">
<el-input disabled placeholder="表名" :value="tableNameStr"></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-button plain icon="Search" class="ml10" @click="childTableRef.openDialog(dataForm)"> 子表</el-button>
<!-- 配置子表 -->
<child-table-config v-model="childForm" ref="childTableRef"/>
</el-col>
</el-row>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="注释" prop="tableComment">
<el-input placeholder="说明" v-model="dataForm.tableComment"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" class="mb20">
<el-form-item label="类名" prop="className">
<el-input placeholder="类名" v-model="dataForm.className"></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="作者" prop="author">
<el-input placeholder="默认作者" v-model="dataForm.author"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" class="mb20">
<el-form-item label="项目包名" prop="packageName">
<el-input placeholder="项目包名" v-model="dataForm.packageName"></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item prop="moduleName">
<template #label>
<span>模块名</span>
<tip content="所属微服务模块名称,对应微服务路由前缀 (单体固定 admin)"/>
</template>
<el-input placeholder="模块名" v-model="dataForm.moduleName"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" class="mb20">
<el-form-item prop="functionName">
<template #label>
<span>功能名</span>
<tip content="对应生成的Controller @RequestMapping 请求路径"/>
</template>
<el-input placeholder="功能名" v-model="dataForm.functionName"></el-input>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="代码风格" prop="style">
<el-select v-model="dataForm.style">
<el-option :key="index" :label="item.groupName" :value="item.id"
v-for="(item, index) in groupDataList"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" class="mb20">
<el-form-item prop="syncMenuId">
<template #label>
所属菜单
<tip :content="`生成的 【${dataForm.tableComment}管理】菜单挂载在哪个目录下 `"/>
</template>
<el-tree-select
filterable
v-model="dataForm.syncMenuId"
:data="menuData"
:render-after-expand="false"
:props="{ value: 'id', label: 'name', children: 'children' }"
class="w100"
clearable
check-strictly
:placeholder="$t('sysmenu.inputParentIdTip')"
>
</el-tree-select>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item prop="syncRoute">
<template #label>
同步路由
<tip :content="`微服务架构下会自动创建一条【/${dataForm.moduleName}】的网关路由,存在则跳过`"/>
</template>
<el-radio-group v-model="dataForm.syncRoute" >
<el-radio border label="0">手动添加</el-radio>
<el-radio border label="1">自动创建</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12" class="mb20">
<el-form-item label="表单布局" prop="formLayout">
<el-radio-group v-model="dataForm.formLayout">
<el-radio border :label="1">一列</el-radio>
<el-radio border :label="2">两列</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item label="生成方式" prop="generatorType">
<el-radio-group v-model="dataForm.generatorType">
<el-radio border label="1">自定义路径</el-radio>
<el-radio border label="0">ZIP 压缩包</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item prop="backendPath" v-if="dataForm.generatorType === '1'">
<template #label>
<span>后端生成路径</span>
<tip content="后端模块biz所在文件全路径比如:D:\data\pjcloud\pjcloud-upms\pjcloud-upms-biz"/>
</template>
<el-input placeholder="后端生成路径" v-model="dataForm.backendPath"></el-input>
</el-form-item>
<el-form-item prop="frontendPath" v-if="dataForm.generatorType === '1'">
<template #label>
<span>前端生成路径</span>
<tip content="前端所在文件全路径比如:D:\data\pjcloud-ui"/>
</template>
<el-input placeholder="前端生成路径" v-model="dataForm.frontendPath"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script lang="ts" setup>
import {putObj, useTableApi} from '/@/api/gen/table';
import {list as groupList} from '/@/api/gen/group';
import {Local} from '/@/utils/storage';
import {rule} from "/@/utils/validate";
import {useMessage, useMessageBox} from "/@/hooks/message";
import {checkVersion, online} from "/@/api/gen/template";
import mittBus from "/@/utils/mitt";
import {pageList} from "/@/api/admin/menu";
import Tip from "/@/components/Tip/index.vue";
const ChildTableConfig = defineAsyncComponent(() => import('./child.vue'));
const props = defineProps({
tableName: {
type: String,
},
dsName: {
type: String,
},
});
const isMicro = import.meta.env.VITE_IS_MICRO;
const emit = defineEmits(['refreshDataList']);
const route = useRoute();
const visible = ref(false);
const loading = ref(false);
const dataFormRef = ref();
const childTableRef = ref();
const childForm = ref();
const tableNameStr = ref('');
const dataForm = reactive({
id: '',
generatorType: '0',
formLayout: 1,
backendPath: '',
frontendPath: '',
packageName: '',
email: '',
author: '',
version: '',
moduleName: '',
functionName: '',
className: '',
tableComment: '',
tableName: '' as string,
dsName: '' as string,
style: '', // 默认风格 element-plus
childTableName: '',
syncRoute: '0',
syncMenuId: '',
});
const groupDataList = ref([]);
const getTable = (dsName: string, tableName: string) => {
loading.value = true;
useTableApi(dsName, tableName)
.then((res) => {
Object.assign(dataForm, res.data);
let list = res.data.groupList;
dataForm.style = list[0]?.id;
// 如果是保存路径的形式,有限使用本地配置项
const frontendPath = Local.get('frontendPath');
const backendPath = Local.get('backendPath');
if (frontendPath && backendPath) {
dataForm.frontendPath = frontendPath;
dataForm.backendPath = backendPath;
}
}).then(() => {
if (isMicro === 'false') {
dataForm.syncRoute = '0'
}
})
.finally(() => {
loading.value = false;
});
};
const dataRules = ref({
tableName: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
tableComment: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
className: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
packageName: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
author: [{validator: rule.overLength, trigger: 'blur'}, {required: true, message: '必填项不能为空', trigger: 'blur'}],
moduleName: [{validator: rule.overLength, trigger: 'blur'}, {validator: rule.letter, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
functionName: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
generatorType: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
formLayout: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
backendPath: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
frontendPath: [{validator: rule.overLength, trigger: 'blur'}, {
required: true,
message: '必填项不能为空',
trigger: 'blur'
}],
style: [{required: true, message: '必填项不能为空', trigger: 'blur'}],
});
// 保存
const submitHandle = async () => {
try {
const valid = await dataFormRef.value.validate(); // 表单校验
if (!valid) return false;
loading.value = true;
await putObj(Object.assign(dataForm, childForm.value));
visible.value = false;
emit('refreshDataList');
return dataForm;
} catch {
return Promise.reject();
} finally {
//保存路径至Local 中方便下次使用
if (dataForm.generatorType === "1") {
Local.set('frontendPath', dataForm.frontendPath);
Local.set('backendPath', dataForm.backendPath);
}
loading.value = false;
}
};
/**
* 获取风格列表
*/
const genGroupList = () => {
groupList().then(({data}) => {
if (data && data.length > 0) {
groupDataList.value = data.filter((group: { groupName: String }) => !group.groupName.includes('vform'));
}
});
};
/**
* 检查模板版本
*/
const checkTemplateVersion = async () => {
try {
const {data} = await checkVersion();
if (!data) {
const shouldUpdate = await useMessageBox().confirm('模板发现新版本,是否更新?').catch(() => false);
if (shouldUpdate) {
await online();
useMessage().success('更新成功');
genGroupList();
} else {
mittBus.emit('onCurrentContextmenuClick', {contextMenuClickId: 1, ...route});
}
}
} catch (error) {
console.error('检查版本失败', error);
}
};
// 从后端获取菜单信息(含层级)
const menuData = reactive([] as any[])
const getAllMenuData = () => {
pageList({
type: '0',
}).then((res) => {
let menu = {
id: '-1',
name: '根菜单',
children: [],
};
menu.children = res.data;
menuData.push(menu);
});
};
watch(
() => childForm,
() => {
const {childTableName} = childForm.value || {};
tableNameStr.value = childTableName ? `${props.tableName} + ${childTableName}` : props.tableName;
},
{deep: true, immediate: true}
);
onMounted(() => {
// 重置表单数据
if (dataFormRef.value) {
dataFormRef.value.resetFields();
}
dataForm.id = '';
dataForm.tableName = String(props.tableName);
dataForm.dsName = String(props.dsName);
getTable(dataForm.dsName, dataForm.tableName)
genGroupList();
checkTemplateVersion()
getAllMenuData()
});
defineExpose({
submitHandle,
});
</script>
<style lang="scss" scoped>
.generator-code .el-dialog__body {
padding: 15px 30px 0 20px;
}
</style>
+47
View File
@@ -0,0 +1,47 @@
export default {
gen: {
syncBtn: 'sync',
designBtn: 'design',
genBtn: 'gen',
prewBtn: 'preview',
},
table: {
index: '#',
importTableTip: ' import Table',
id: 'id',
tableName: 'tableName',
className: 'className',
tableComment: 'tableComment',
tableDesc: 'comment',
author: 'author',
email: 'email',
packageName: 'packageName',
version: 'version',
generatorType: 'generatorType',
backendPath: 'backendPath',
frontendPath: 'frontendPath',
moduleName: 'moduleName',
functionName: 'functionName',
formLayout: 'formLayout',
datasourceId: 'datasourceId',
baseclassId: 'baseclassId',
createTime: 'createTime',
inputidTip: 'input id',
inputtableNameTip: 'input tableName',
inputclassNameTip: 'input className',
inputtableCommentTip: 'input tableComment',
inputauthorTip: 'input author',
inputemailTip: 'input email',
inputpackageNameTip: 'input packageName',
inputversionTip: 'input version',
inputgeneratorTypeTip: 'input generatorType',
inputbackendPathTip: 'input backendPath',
inputfrontendPathTip: 'input frontendPath',
inputmoduleNameTip: 'input moduleName',
inputfunctionNameTip: 'input functionName',
inputformLayoutTip: 'input formLayout',
inputdatasourceIdTip: 'input datasourceId',
inputbaseclassIdTip: 'input baseclassId',
inputcreateTimeTip: 'input createTime',
},
};
+47
View File
@@ -0,0 +1,47 @@
export default {
gen: {
syncBtn: '同步',
designBtn: '设计',
genBtn: '生成',
prewBtn: '预览',
},
table: {
index: '#',
importTableTip: '导入列属性',
id: 'id',
tableName: '表名',
className: '类名',
tableComment: '说明',
tableDesc: '注释',
author: '作者',
email: '邮箱',
packageName: '项目包名',
version: '项目版本号',
generatorType: '生成方式 0:zip压缩包 1:自定义目录',
backendPath: '后端生成路径',
frontendPath: '前端生成路径',
moduleName: '模块名',
functionName: '功能名',
formLayout: '表单布局 1:一列 2:两列',
datasourceId: '数据源ID',
baseclassId: '基类ID',
createTime: '创建时间',
inputidTip: '请输入id',
inputtableNameTip: '请输入表名',
inputclassNameTip: '请输入类名',
inputtableCommentTip: '请输入说明',
inputauthorTip: '请输入作者',
inputemailTip: '请输入邮箱',
inputpackageNameTip: '请输入项目包名',
inputversionTip: '请输入项目版本号',
inputgeneratorTypeTip: '请输入生成方式 0:zip压缩包 1:自定义目录',
inputbackendPathTip: '请输入后端生成路径',
inputfrontendPathTip: '请输入前端生成路径',
inputmoduleNameTip: '请输入模块名',
inputfunctionNameTip: '请输入功能名',
inputformLayoutTip: '请输入表单布局 1:一列 2:两列',
inputdatasourceIdTip: '请输入数据源ID',
inputbaseclassIdTip: '请输入基类ID',
inputcreateTimeTip: '请输入创建时间',
},
};
+154
View File
@@ -0,0 +1,154 @@
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<el-row class="ml10" v-show="showSearch">
<el-form :inline="true" :model="state.queryForm" @keyup.enter="getDataList" ref="queryRef">
<el-form-item label="数据源" prop="name">
<el-select @change="getDataList" placeholder="请选择数据源" v-model="state.queryForm.dsName">
<el-option label="默认数据源" value="master"></el-option>
<el-option :key="ds.id" :label="ds.name" :value="ds.name" v-for="ds in datasourceList"></el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('table.tableName')" prop="tableName">
<el-input :placeholder="$t('table.inputtableNameTip')" v-model="state.queryForm.tableName"/>
</el-form-item>
<el-form-item>
<el-button @click="getDataList" icon="search" type="primary">
{{ $t('common.queryBtn') }}
</el-button>
<el-button @click="resetQuery" icon="Refresh">{{ $t('common.resetBtn') }}</el-button>
</el-form-item>
</el-form>
</el-row>
<el-row>
<div class="mb8" style="width: 100%">
<right-toolbar
:export="true"
@exportExcel="exportExcel"
@queryTable="getDataList"
class="ml10"
style="float: right; margin-right: 20px"
v-model:showSearch="showSearch"
></right-toolbar>
</div>
</el-row>
<el-table
:data="state.dataList"
style="width: 100%"
v-loading="state.loading"
border
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<el-table-column :label="t('table.index')" type="index" width="60"/>
<el-table-column :label="t('table.tableName')" prop="name" show-overflow-tooltip/>
<el-table-column :label="t('table.tableDesc')" prop="comment" show-overflow-tooltip/>
<el-table-column :label="t('table.createTime')" prop="createTime" show-overflow-tooltip/>
<el-table-column :label="$t('common.action')" width="250">
<template #default="scope">
<el-button icon="Refresh" @click="syncTable(scope.row)" text type="primary">
{{ $t('gen.syncBtn') }}
</el-button>
<el-button icon="FolderOpened" @click="openGen(scope.row)" text type="primary">{{
$t('gen.genBtn')
}}
</el-button>
<el-button icon="MagicStick" @click="openDesign(scope.row)" text type="primary">{{
$t('gen.designBtn')
}}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination @current-change="currentChangeHandle" @size-change="sizeChangeHandle" v-bind="state.pagination"/>
</div>
</div>
</template>
<script lang="ts" name="systemTable" setup>
import {BasicTableProps, useTable} from '/@/hooks/table';
import {fetchList, useSyncTableApi, useTableApi} from '/@/api/gen/table';
import {list} from '/@/api/gen/datasource';
import {useMessage} from '/@/hooks/message';
import {useI18n} from 'vue-i18n';
import {validateNull} from '/@/utils/validate';
// 定义变量内容
const router = useRouter();
// 引入组件
const {t} = useI18n();
// 搜索变量
const queryRef = ref();
const showSearch = ref(true);
// 多选变量
const datasourceList = ref();
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: {
dsName: 'master',
},
pageList: fetchList,
createdIsNeed: false,
});
// table hook
const {getDataList, currentChangeHandle, sizeChangeHandle, downBlobFile, tableStyle} = useTable(state);
// 初始化数据
onMounted(() => {
list().then((res) => {
datasourceList.value = res.data;
// 默认去第一个数据源
if (datasourceList.value.length > 0) {
state.queryForm.dsName = datasourceList.value[0].name;
}
getDataList();
});
});
const openGen = (row) => {
useTableApi(state.queryForm.dsName, row.name)
.then((res) => {
if (validateNull(res.data.fieldList)) {
syncTable(row);
}
})
.finally(() => {
router.push({
path: '/gen/gener/index',
query: {
tableName: row.name,
dsName: state.queryForm.dsName,
},
});
});
};
const openDesign = (row) => {
router.push({
path: '/gen/design/index',
query: {
tableName: row.name,
dsName: state.queryForm.dsName,
},
});
};
// 同步表数据
const syncTable = (row) => {
useSyncTableApi(state.queryForm.dsName, row.name).then(() => {
useMessage().success(t('common.optSuccessText'));
});
};
// 清空搜索条件
const resetQuery = () => {
queryRef.value.resetFields();
getDataList();
};
// 导出excel
const exportExcel = () => {
downBlobFile('/gen/table/export', state.queryForm, 'table.xlsx');
};
</script>
+158
View File
@@ -0,0 +1,158 @@
<template>
<el-dialog fullscreen title="代码预览" v-model="visible" width="90%" top="3vh" append-to-body :close-on-click-modal="false">
<splitpanes>
<pane size="25">
<el-scrollbar height="calc(100vh - 100px)" class="mt20">
<el-tree
ref="treeRef"
node-key="id"
:data="preview.fileTree"
:expand-on-click-node="false"
highlight-current
@node-click="handleNodeClick"
/>
</el-scrollbar>
</pane>
<pane>
<el-tabs v-model="preview.activeName" @tab-click="handleTabClick">
<el-tab-pane
v-for="item in previewCodegen"
:label="item.codePath.substring(item.codePath.lastIndexOf('/') + 1)"
:name="item.codePath"
:key="item.codePath"
>
<SvgIcon name="ele-CopyDocument" :size="25" class="copy_btn" @click="copyText(item.code)" />
</el-tab-pane>
<code-editor ref="codeEditorRef" theme="darcula" v-model="previewCodeStr" mode="go" readOnly height="calc(100vh - 100px)"></code-editor>
</el-tabs>
</pane>
</splitpanes>
</el-dialog>
</template>
<script setup lang="ts" name="preview">
import { useGeneratorPreviewApi } from '/@/api/gen/table';
import { handleTree } from '/@/utils/other';
import commonFunction from '/@/utils/commonFunction';
const CodeEditor = defineAsyncComponent(() => import('/@/components/CodeEditor/index.vue'));
const { copyText } = commonFunction();
const visible = ref(false);
// ======== 显示页面 ========
const preview = reactive({
open: false,
titel: '代码预览',
fileTree: [],
activeName: '',
});
const previewCodegen = ref([]);
const previewCodeStr = ref('');
const fileTreeOriginal = ref([] as any[]);
const openDialog = async (id: string) => {
await getGenCodeFile(id);
visible.value = true;
};
const loading = ref(false);
const codeEditorRef = ref();
/**
* 获取特定资源的代码生成文件,显示在页面上。
* @param id 需要渲染的资源 ID。
*/
const getGenCodeFile = (id: string) => {
loading.value = true;
fileTreeOriginal.value = [];
useGeneratorPreviewApi(id)
.then((res: any) => {
previewCodegen.value = res;
for (let index in res) {
fileTreeOriginal.value.push(res[index].codePath);
}
// 默认选中第一个 选项卡
previewCodeStr.value = res[0].code;
preview.activeName = res[0].codePath;
const files = handleFiles(fileTreeOriginal);
preview.fileTree = handleTree(files, 'id', 'parentId', 'children', '/');
})
.finally(() => {
loading.value = false;
});
};
const handleNodeClick = async (data: any, node: any) => {
if (node && !node.isLeaf) {
return false;
}
preview.activeName = data.id;
const filteredCode = previewCodegen.value.filter((code: any) => code.codePath === data.id);
if (filteredCode.length > 0) {
previewCodeStr.value = filteredCode[0].code;
}
};
const handleTabClick = (item: any) => {
const filteredCode = previewCodegen.value.filter((code: any) => code.codePath === item.paneName);
if (filteredCode.length > 0) {
previewCodeStr.value = filteredCode[0].code;
}
};
/**
* 生成 files 目录
* @param fileTreeOriginal
* @returns {*[]}
*/
const handleFiles = (fileTreeOriginal: any) => {
const exists = {};
const files = [] as any[];
// 遍历每个元素
for (const data of fileTreeOriginal.value) {
let paths = [];
if (data.includes('\\')) {
paths = data.split('\\');
} else {
paths = data.split('/');
}
let fullPath = ''; // 从头开始的路径,用于生成 id
// 遍历每个 path, 拼接成树
for (let i = 0; i < paths.length; i++) {
// 已经添加到 files 中,则跳过
const oldFullPath = fullPath;
// 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i];
if (exists[fullPath]) {
continue;
}
// 添加到 files 中
exists[fullPath] = true;
files.push({
id: fullPath,
label: paths[i],
parentId: oldFullPath || '/',
templateName: data.k,
});
}
}
return files;
};
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
.copy_btn {
position: absolute;
top: 10px;
right: 20px;
z-index: 9;
color: white;
}
</style>