!734 代码生成:重构 vue2 代码生成模版,使用 async await 优化代码层次,适配树表和主子表

Merge pull request !734 from puhui999/feature/sub-table
This commit is contained in:
芋道源码 2023-11-27 08:53:41 +00:00 committed by Gitee
commit 12bcee2a72
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
120 changed files with 10755 additions and 802 deletions

View File

@ -104,6 +104,18 @@ public class CodegenEngine {
vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue")) vueFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"), .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("api/api.js"),
vueFilePath("api/${table.moduleName}/${classNameVar}.js")) vueFilePath("api/${table.moduleName}/${classNameVar}.js"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/form.vue"),
vueFilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_normal.vue"), // 特殊主子表专属逻辑
vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_inner.vue"), // 特殊主子表专属逻辑
vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/form_sub_erp.vue"), // 特殊主子表专属逻辑
vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_inner.vue"), // 特殊主子表专属逻辑
vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
.put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath("views/components/list_sub_erp.vue"), // 特殊主子表专属逻辑
vueFilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
// Vue3 标准模版 // Vue3 标准模版
.put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"), .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("views/index.vue"),
vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
@ -285,6 +297,10 @@ public class CodegenEngine {
if (StrUtil.count(content, "dateFormatter") == 1) { if (StrUtil.count(content, "dateFormatter") == 1) {
content = StrUtils.removeLineContains(content, "dateFormatter"); content = StrUtils.removeLineContains(content, "dateFormatter");
} }
// Vue2 界面修正 $refs
if (StrUtil.count(content, "this.refs") >= 1) {
content = content.replace("this.refs", "this.$refs");
}
// Vue 界面去除多的 dict 相关只有一个的情况下说明没使用到 // Vue 界面去除多的 dict 相关只有一个的情况下说明没使用到
if (StrUtil.count(content, "getIntDictOptions") == 1) { if (StrUtil.count(content, "getIntDictOptions") == 1) {
content = content.replace("getIntDictOptions, ", ""); content = content.replace("getIntDictOptions, ", "");
@ -452,7 +468,7 @@ public class CodegenEngine {
} }
private static String vueFilePath(String path) { private static String vueFilePath(String path) {
return "yudao-ui-${sceneEnum.basePackage}/" + // 顶级目录 return "yudao-ui-${sceneEnum.basePackage}-vue2/" + // 顶级目录
"src/" + path; "src/" + path;
} }

View File

@ -35,21 +35,113 @@ export function get${simpleClassName}(id) {
}) })
} }
#if ( $table.templateType != 2 )
// 获得${table.classComment}分页 // 获得${table.classComment}分页
export function get${simpleClassName}Page(query) { export function get${simpleClassName}Page(params) {
return request({ return request({
url: '${baseURL}/page', url: '${baseURL}/page',
method: 'get', method: 'get',
params: query params
}) })
} }
#else
// 获得${table.classComment}列表
export function get${simpleClassName}List(params) {
return request({
url: '${baseURL}/list',
method: 'get',
params
})
}
#end
// 导出${table.classComment} Excel // 导出${table.classComment} Excel
export function export${simpleClassName}Excel(query) { export function export${simpleClassName}Excel(params) {
return request({ return request({
url: '${baseURL}/export-excel', url: '${baseURL}/export-excel',
method: 'get', method: 'get',
params: query, params,
responseType: 'blob' responseType: 'blob'
}) })
} }
## 特殊:主子表专属逻辑
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
#set ($subClassNameVar = $subClassNameVars.get($index))
// ==================== 子表($subTable.classComment ====================
## 情况一MASTER_ERP 时,需要分查询页子表
#if ( $table.templateType == 11 )
// 获得${subTable.classComment}分页
export function get${subSimpleClassName}Page(params) {
return request({
url: '${baseURL}/${subSimpleClassName_strikeCase}/page',
method: 'get',
params
})
}
## 情况二:非 MASTER_ERP 时,需要列表查询子表
#else
#if ( $subTable.subJoinMany )
// 获得${subTable.classComment}列表
export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
method: 'get'
})
}
#else
// 获得${subTable.classComment}
export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField},
method: 'get'
})
}
#end
#end
## 特殊MASTER_ERP 时,支持单个的新增、修改、删除操作
#if ( $table.templateType == 11 )
// 新增${subTable.classComment}
export function create${subSimpleClassName}(data) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/create`,
method: 'post',
data
})
}
// 修改${subTable.classComment}
export function update${subSimpleClassName}(data) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/update`,
method: 'post',
data
})
}
// 删除${subTable.classComment}
export function delete${subSimpleClassName}(id) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id,
method: 'delete'
})
}
// 获得${subTable.classComment}
export function get${subSimpleClassName}(id) {
return request({
url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id,
method: 'get'
})
}
#end
#end

View File

@ -0,0 +1,205 @@
#set ($subTable = $subTables.get($subIndex))##当前表
#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
#foreach($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}" prop="${javaField}">
<ImageUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}" prop="${javaField}">
<FileUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-form-item label="${comment}" prop="${javaField}">
<editor v-model="formData.${javaField}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable v-model="formData.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
#if ($hasImageUploadColumn)
import ImageUpload from '@/components/ImageUpload';
#end
#if ($hasFileUploadColumn)
import FileUpload from '@/components/FileUpload';
#end
#if ($hasEditorColumn)
import Editor from '@/components/Editor';
#end
export default {
name: "${subSimpleClassName}Form",
components: {
#if ($hasImageUploadColumn)
ImageUpload,
#end
#if ($hasFileUploadColumn)
FileUpload,
#end
#if ($hasEditorColumn)
Editor,
#end
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
#foreach ($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
},
// 表单校验
formRules: {
#foreach ($column in $subColumns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
#end
#end
},
};
},
methods: {
/** 打开弹窗 */
async open(id, ${subJoinColumn.javaField}) {
this.dialogVisible = true;
this.reset();
this.formData.${subJoinColumn.javaField} = ${subJoinColumn.javaField};
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
const res = await ${simpleClassName}Api.get${subSimpleClassName}(id);
this.formData = res.data;
this.dialogTitle = "修改${subTable.classComment}";
} finally {
this.formLoading = false;
}
}
this.dialogTitle = "新增${subTable.classComment}";
},
/** 提交按钮 */
async submitForm() {
await this.#[[$]]#refs["formRef"].validate();
this.formLoading = true;
try {
const data = this.formData;
// 修改的提交
if (data.${primaryColumn.javaField}) {
await ${simpleClassName}Api.update${subSimpleClassName}(data);
this.#[[$modal]]#.msgSuccess("修改成功");
this.dialogVisible = false;
this.#[[$]]#emit('success');
return;
}
// 添加的提交
await ${simpleClassName}Api.create${subSimpleClassName}(data);
this.#[[$modal]]#.msgSuccess("新增成功");
this.dialogVisible = false;
this.#[[$]]#emit('success');
}finally {
this.formLoading = false;
}
},
/** 表单重置 */
reset() {
this.formData = {
#foreach ($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,2 @@
## 主表的 normal 和 inner 使用相同的 form 表单
#parse("codegen/vue/views/components/form_sub_normal.vue.vm")

View File

@ -0,0 +1,347 @@
#set ($subTable = $subTables.get($subIndex))##当前表
#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
<template>
<div class="app-container">
#if ( $subTable.subJoinMany )## 情况一一对多table + form
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" width="100" />
#foreach($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-table-column label="${comment}" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-input v-model="row.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-table-column label="${comment}" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<ImageUpload v-model="row.${javaField}"/>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-table-column label="${comment}" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<FileUpload v-model="row.${javaField}"/>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-table-column label="${comment}" min-width="400">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<Editor v-model="row.${javaField}" :min-height="192"/>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "select")## 下拉框
<el-table-column label="${comment}" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-select v-model="row.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "checkbox")## 多选框
<el-table-column label="${comment}" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-checkbox-group v-model="row.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "radio")## 单选框
<el-table-column label="${comment}" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-radio-group v-model="row.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "datetime")## 时间框
<el-table-column label="${comment}" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-date-picker clearable v-model="row.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
</el-form-item>
</template>
</el-table-column>
#elseif($column.htmlType == "textarea")## 文本框
<el-table-column label="${comment}" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.${javaField}`" :rules="formRules.${javaField}" class="mb-0px!">
<el-input v-model="row.${javaField}" type="textarea" placeholder="请输入${comment}" />
</el-form-item>
</template>
</el-table-column>
#end
#end
#end
<el-table-column align="center" fixed="right" label="操作" width="60">
<template v-slot="{ $index }">
<el-link @click="handleDelete($index)">—</el-link>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3">
<el-button @click="handleAdd" round>+ 添加${subTable.classComment}</el-button>
</el-row>
#else## 情况二一对一form
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
#foreach($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
#elseif ($column.htmlType == "input" && !$column.primaryKey)
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}">
<ImageUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}">
<FileUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-form-item label="${comment}">
<Editor v-model="formData.${javaField}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable v-model="formData.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
</el-form-item>
#end
#end
#end
</el-form>
#end
</div>
</template>
<script>
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
#if ($hasImageUploadColumn)
import ImageUpload from '@/components/ImageUpload';
#end
#if ($hasFileUploadColumn)
import FileUpload from '@/components/FileUpload';
#end
#if ($hasEditorColumn)
import Editor from '@/components/Editor';
#end
export default {
name: "${subSimpleClassName}Form",
components: {
#if ($hasImageUploadColumn)
ImageUpload,
#end
#if ($hasFileUploadColumn)
FileUpload,
#end
#if ($hasEditorColumn)
Editor,
#end
},
props:[
'${subJoinColumn.javaField}'
],// ${subJoinColumn.columnComment}(主表的关联字段)
data() {
return {
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: [],
// 表单校验
formRules: {
#foreach ($column in $subColumns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
#end
#end
},
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
${subJoinColumn.javaField}:{
handler(val) {
// 1. 重置表单
#if ( $subTable.subJoinMany )
this.formData = []
#else
this.formData = {
#foreach ($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
}
#end
// 2. val 非空,则加载数据
if (!val) {
return;
}
try {
this.formLoading = true;
// 这里还是需要获取一下 this 的不然取不到 formData
const that = this;
#if ( $subTable.subJoinMany )
${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(val).then(function (res){
that.formData = res.data;
})
#else
${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(val).then(function (res){
const data = res.data;
if (!data) {
return
}
that.formData = data;
})
#end
} finally {
this.formLoading = false;
}
},
immediate: true
}
},
methods: {
#if ( $subTable.subJoinMany )
/** 新增按钮操作 */
handleAdd() {
const row = {
#foreach ($column in $subColumns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
}
row.${subJoinColumn.javaField} = this.${subJoinColumn.javaField};
this.formData.push(row);
},
/** 删除按钮操作 */
handleDelete(index) {
this.formData.splice(index, 1);
},
#end
/** 表单校验 */
validate(){
return this.#[[$]]#refs["formRef"].validate();
},
/** 表单值 */
getData(){
return this.formData;
}
}
};
</script>

View File

@ -0,0 +1,165 @@
#set ($subTable = $subTables.get($subIndex))##当前表
#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
<template>
<div class="app-container">
#if ($table.templateType == 11)
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
</el-col>
</el-row>
#end
## 列表
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
#foreach($column in $subColumns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ( $column.id == $subJoinColumn.id) ## 特殊:忽略主子表的 join 字段,不用填写
#elseif ($column.javaType == "LocalDateTime")## 时间类型
<el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.${javaField}) }}</span>
</template>
</el-table-column>
#elseif($column.dictType && "" != $column.dictType)## 数据字典
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
</template>
</el-table-column>
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.${primaryColumn.javaField})"
v-hasPermi="['${permissionPrefix}:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['${permissionPrefix}:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
#if ($table.templateType == 11)
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<${subSimpleClassName}Form ref="formRef" @success="getList" />
#end
</div>
</template>
<script>
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
#if ($table.templateType == 11)
import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'
#end
export default {
name: "${subSimpleClassName}List",
#if ($table.templateType == 11)
components: {
${subSimpleClassName}Form
},
#end
props:[
'${subJoinColumn.javaField}'
],// ${subJoinColumn.columnComment}(主表的关联字段)
data() {
return {
// 遮罩层
loading: true,
// 列表的数据
list: [],
#if ($table.templateType == 11)
// 列表的总页数
total: 0,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
${subJoinColumn.javaField}: undefined
}
#end
};
},
#if ($table.templateType != 11)
created() {
this.getList();
},
#end
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
${subJoinColumn.javaField}:{
handler(val) {
this.queryParams.${subJoinColumn.javaField} = val;
if (val){
this.handleQuery();
}
},
immediate: true
}
},
methods: {
/** 查询列表 */
async getList() {
try {
this.loading = true;
#if ($table.templateType == 11)
const res = await ${simpleClassName}Api.get${subSimpleClassName}Page(this.queryParams);
this.list = res.data.list;
this.total = res.data.total;
#else
#if ( $subTable.subJoinMany )
const res = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(this.${subJoinColumn.javaField});
this.list = res.data;
#else
const res = await ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(this.${subJoinColumn.javaField});
const data = res.data;
if (!data) {
return;
}
this.list.push(data);
#end
#end
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
#if ($table.templateType == 11)
/** 添加/修改操作 */
openForm(id) {
if (!this.${subJoinColumn.javaField}) {
this.#[[$modal]]#.msgError('请选择一个${table.classComment}');
return;
}
this.#[[$]]#refs["formRef"].open(id, this.${subJoinColumn.javaField});
},
/** 删除按钮操作 */
async handleDelete(row) {
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
await this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?');
try {
await ${simpleClassName}Api.delete${subSimpleClassName}(${primaryColumn.javaField});
await this.getList();
this.#[[$modal]]#.msgSuccess("删除成功");
} catch {}
},
#end
}
};
</script>

View File

@ -0,0 +1,4 @@
## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
## 1inner 使用 list 不分页erp 使用 page 分页
## 2erp 支持单个子表的新增、修改、删除inner 不支持
#parse("codegen/vue/views/components/list_sub_erp.vue.vm")

View File

@ -0,0 +1,320 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
#foreach($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment)
#if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
<el-form-item label="${comment}" prop="${javaField}">
<TreeSelect
v-model="formData.${javaField}"
:options="${classNameVar}Tree"
:normalizer="normalizer"
placeholder="请选择${comment}"
/>
</el-form-item>
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}">
<ImageUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}">
<FileUpload v-model="formData.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-form-item label="${comment}">
<Editor v-model="formData.${javaField}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"
#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable v-model="formData.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
<!-- 子表的表单 -->
<el-tabs v-model="subTabsName">
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
</el-tab-pane>
#end
</el-tabs>
#end
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
#if ($hasImageUploadColumn)
import ImageUpload from '@/components/ImageUpload';
#end
#if ($hasFileUploadColumn)
import FileUpload from '@/components/FileUpload';
#end
#if ($hasEditorColumn)
import Editor from '@/components/Editor';
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
import TreeSelect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#foreach ($subSimpleClassName in $subSimpleClassNames)
import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
#end
#end
export default {
name: "${simpleClassName}Form",
components: {
#if ($hasImageUploadColumn)
ImageUpload,
#end
#if ($hasFileUploadColumn)
FileUpload,
#end
#if ($hasEditorColumn)
Editor,
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
TreeSelect,
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#foreach ($subSimpleClassName in $subSimpleClassNames)
${subSimpleClassName}Form,
#end
#end
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
},
// 表单校验
formRules: {
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
#end
#end
},
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
${classNameVar}Tree: [], // 树形结构
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
/** 子表的表单 */
subTabsName: '$subClassNameVars.get(0)'
#end
#end
};
},
methods: {
/** 打开弹窗 */
async open(id) {
this.dialogVisible = true;
this.reset();
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
const res = await ${simpleClassName}Api.get${simpleClassName}(id);
this.formData = res.data;
this.title = "修改${table.classComment}";
} finally {
this.formLoading = false;
}
}
this.title = "新增${table.classComment}";
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
this.get${simpleClassName}Tree();
#end
},
/** 提交按钮 */
async submitForm() {
// 校验主表
await this.$refs["formRef"].validate();
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
// 校验子表
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
try {
## 代码生成后会替换为正确的 refs
await this.refs['${subClassNameVar}FormRef'].validate();
} catch (e) {
this.subTabsName = '${subClassNameVar}';
return;
}
#end
#end
#end
this.formLoading = true;
try {
const data = this.formData;
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
// 拼接子表的数据
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = this.refs['${subClassNameVar}FormRef'].getData();
#end
#end
#end
// 修改的提交
if (data.${primaryColumn.javaField}) {
await ${simpleClassName}Api.update${simpleClassName}(data);
this.#[[$modal]]#.msgSuccess("修改成功");
this.dialogVisible = false;
this.#[[$]]#emit('success');
return;
}
// 添加的提交
await ${simpleClassName}Api.create${simpleClassName}(data);
this.#[[$modal]]#.msgSuccess("新增成功");
this.dialogVisible = false;
this.#[[$]]#emit('success');
}finally {
this.formLoading = false;
}
},
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 获得${table.classComment}树 */
async get${simpleClassName}Tree() {
this.${classNameVar}Tree = [];
const res = await ${simpleClassName}Api.get${simpleClassName}List();
const root = { id: 0, name: '顶级${table.classComment}', children: [] };
root.children = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}')
this.${classNameVar}Tree.push(root)
},
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 转换${table.classComment}数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
#if ($treeNameColumn.javaField == "name")
return {
id: node.id,
label: node.name,
children: node.children
};
#else
return {
id: node.id,
label: node['$treeNameColumn.javaField'],
children: node.children
};
#end
},
#end
/** 表单重置 */
reset() {
this.formData = {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
};
this.resetForm("formRef");
}
}
};
</script>

View File

@ -47,18 +47,68 @@
<!-- 操作工具栏 --> <!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['${permissionPrefix}:create']">新增</el-button> v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading" <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['${permissionPrefix}:export']">导出</el-button> v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
</el-col> </el-col>
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
<el-col :span="1.5">
<el-button type="danger" plain icon="el-icon-sort" size="mini" @click="toggleExpandAll">
展开/折叠
</el-button>
</el-col>
#end
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
<!-- 列表 --> ## 特殊:主子表专属逻辑
<el-table v-loading="loading" :data="list"> #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:highlight-current-row="true"
:show-overflow-tooltip="true"
@current-change="handleCurrentChange"
>
## 特殊:树表专属逻辑
#elseif ( $table.templateType == 2 )
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
v-if="refreshTable"
row-key="id"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
#else
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
<!-- 子表的列表 -->
<el-table-column type="expand">
<template #default="scope">
<el-tabs value="$subClassNameVars.get(0)">
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
</el-tab-pane>
#end
</el-tabs>
</template>
</el-table-column>
#end
#foreach($column in $columns) #foreach($column in $columns)
#if ($column.listOperationResult) #if ($column.listOperationResult)
#set ($dictType=$column.dictType) #set ($dictType=$column.dictType)
@ -84,102 +134,42 @@
#end #end
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope"> <template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" <el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.${primaryColumn.javaField})"
v-hasPermi="['${permissionPrefix}:update']">修改</el-button> v-hasPermi="['${permissionPrefix}:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['${permissionPrefix}:delete']">删除</el-button> v-hasPermi="['${permissionPrefix}:delete']">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
## 特殊:树表专属逻辑(树不需要分页)
#if ( $table.templateType != 2 )
<!-- 分页组件 --> <!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize" <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/> @pagination="getList"/>
#end
<!-- 对话框(添加 / 修改) --> <!-- 对话框(添加 / 修改) -->
<el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body> <${simpleClassName}Form ref="formRef" @success="getList" />
<el-form ref="form" :model="form" :rules="rules" label-width="80px"> ## 特殊:主子表专属逻辑
#foreach($column in $columns) #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
#if ($column.createOperation || $column.updateOperation) <!-- 子表的列表 -->
#set ($dictType = $column.dictType) <el-tabs v-model="subTabsName">
#set ($javaField = $column.javaField) #foreach ($subTable in $subTables)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set ($index = $foreach.count - 1)
#set ($comment = $column.columnComment) #set ($subClassNameVar = $subClassNameVars.get($index))
#if ($column.htmlType == "input") #set ($subSimpleClassName = $subSimpleClassNames.get($index))
#if (!$column.primaryKey)## 忽略主键,不用在表单里 #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-form-item label="${comment}" prop="${javaField}"> <el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<el-input v-model="form.${javaField}" placeholder="请输入${comment}" /> <${subSimpleClassName}List v-if="currentRow.id" :${subJoinColumn_strikeCase}="currentRow.id" />
</el-form-item> </el-tab-pane>
#end
</el-tabs>
#end #end
#elseif($column.htmlType == "imageUpload")## 图片上传
#set ($hasImageUploadColumn = true)
<el-form-item label="${comment}">
<imageUpload v-model="form.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
#set ($hasFileUploadColumn = true)
<el-form-item label="${comment}">
<fileUpload v-model="form.${javaField}"/>
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
#set ($hasEditorColumn = true)
<el-form-item label="${comment}">
<editor v-model="form.${javaField}" :min-height="192"/>
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="form.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" :label="dict.label" #if ($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end />
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="form.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="form.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value" #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end>{{dict.label}}</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker clearable v-model="form.${javaField}" type="date" value-format="timestamp" placeholder="选择${comment}" />
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="form.${javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
#end
#end
#end
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { create${simpleClassName}, update${simpleClassName}, delete${simpleClassName}, get${simpleClassName}, get${simpleClassName}Page, export${simpleClassName}Excel } from "@/api/${table.moduleName}/${classNameVar}"; import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';
import ${simpleClassName}Form from './${simpleClassName}Form.vue';
#if ($hasImageUploadColumn) #if ($hasImageUploadColumn)
import ImageUpload from '@/components/ImageUpload'; import ImageUpload from '@/components/ImageUpload';
#end #end
@ -189,10 +179,26 @@ import FileUpload from '@/components/FileUpload';
#if ($hasEditorColumn) #if ($hasEditorColumn)
import Editor from '@/components/Editor'; import Editor from '@/components/Editor';
#end #end
## 特殊:主子表专属逻辑
#if ( $table.templateType != 10 )
#if ( $subTables && $subTables.size() > 0 )
#foreach ($subSimpleClassName in $subSimpleClassNames)
import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
#end
#end
#end
export default { export default {
name: "${simpleClassName}", name: "${simpleClassName}",
components: { components: {
${simpleClassName}Form,
## 特殊:主子表专属逻辑
#if ( $table.templateType != 10 )
#if ( $subTables && $subTables.size() > 0 )
#foreach ($subSimpleClassName in $subSimpleClassNames)
${subSimpleClassName}List,
#end
#end
#end
#if ($hasImageUploadColumn) #if ($hasImageUploadColumn)
ImageUpload, ImageUpload,
#end #end
@ -211,40 +217,44 @@ export default {
exportLoading: false, exportLoading: false,
// 显示搜索条件 // 显示搜索条件
showSearch: true, showSearch: true,
// 总条数 ## 特殊:树表专属逻辑(树不需要分页接口)
total: 0, #if ( $table.templateType != 2 )
// 总条数
total: 0,
#end
// ${table.classComment}列表 // ${table.classComment}列表
list: [], list: [],
// 弹出层标题 // 是否展开,默认全部展开
title: "", isExpandAll: true,
// 是否显示弹出层 // 重新渲染表格状态
open: false, refreshTable: true,
// 选中行
currentRow: {},
// 查询参数 // 查询参数
queryParams: { queryParams: {
pageNo: 1, ## 特殊:树表专属逻辑(树不需要分页接口)
pageSize: 10, #if ( $table.templateType != 2 )
pageNo: 1,
pageSize: 10,
#end
#foreach ($column in $columns) #foreach ($column in $columns)
#if ($column.listOperation) #if ($column.listOperation)
#if ($column.listOperationCondition != 'BETWEEN') #if ($column.listOperationCondition != 'BETWEEN')
$column.javaField: null, $column.javaField: null,
#end #end
#if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN") #if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
$column.javaField: [], $column.javaField: [],
#end #end
#end #end
#end #end
}, },
// 表单参数 ## 特殊:主子表专属逻辑-erp
form: {}, #if ( $table.templateType == 11)
// 表单校验 #if ( $subTables && $subTables.size() > 0 )
rules: { /** 子表的列表 */
#foreach ($column in $columns) subTabsName: '$subClassNameVars.get(0)'
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 #end
#set($comment=$column.columnComment) #end
$column.javaField: [{ required: true, message: "${comment}不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }],
#end
#end
}
}; };
}, },
created() { created() {
@ -252,34 +262,21 @@ export default {
}, },
methods: { methods: {
/** 查询列表 */ /** 查询列表 */
getList() { async getList() {
try {
this.loading = true; this.loading = true;
// 执行查询 ## 特殊:树表专属逻辑(树不需要分页接口)
get${simpleClassName}Page(this.queryParams).then(response => { #if ( $table.templateType == 2 )
this.list = response.data.list; const res = await ${simpleClassName}Api.get${simpleClassName}List(this.queryParams);
this.total = response.data.total; this.list = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}');
#else
const res = await ${simpleClassName}Api.get${simpleClassName}Page(this.queryParams);
this.list = res.data.list;
this.total = res.data.total;
#end
} finally {
this.loading = false; this.loading = false;
}); }
},
/** 取消按钮 */
cancel() {
this.open = false;
this.reset();
},
/** 表单重置 */
reset() {
this.form = {
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
};
this.resetForm("form");
}, },
/** 搜索按钮操作 */ /** 搜索按钮操作 */
handleQuery() { handleQuery() {
@ -291,79 +288,54 @@ export default {
this.resetForm("queryForm"); this.resetForm("queryForm");
this.handleQuery(); this.handleQuery();
}, },
/** 新增按钮操作 */ /** 添加/修改操作 */
handleAdd() { openForm(id) {
this.reset(); this.#[[$]]#refs["formRef"].open(id);
this.open = true;
this.title = "添加${table.classComment}";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
get${simpleClassName}(${primaryColumn.javaField}).then(response => {
this.form = response.data;
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")## checkbox 特殊处理
this.form.$column.javaField = this.form.${column.javaField}.split(",");
#end
#end
this.open = true;
this.title = "修改${table.classComment}";
});
},
/** 提交按钮 */
submitForm() {
this.#[[$]]#refs["form"].validate(valid => {
if (!valid) {
return;
}
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
this.form.$column.javaField = this.form.${column.javaField}.join(",");
#end
#end
// 修改的提交
if (this.form.${primaryColumn.javaField} != null) {
update${simpleClassName}(this.form).then(response => {
this.#[[$modal]]#.msgSuccess("修改成功");
this.open = false;
this.getList();
});
return;
}
// 添加的提交
create${simpleClassName}(this.form).then(response => {
this.#[[$modal]]#.msgSuccess("新增成功");
this.open = false;
this.getList();
});
});
}, },
/** 删除按钮操作 */ /** 删除按钮操作 */
handleDelete(row) { async handleDelete(row) {
const ${primaryColumn.javaField} = row.${primaryColumn.javaField}; const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?').then(function() { await this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为"' + ${primaryColumn.javaField} + '"的数据项?')
return delete${simpleClassName}(${primaryColumn.javaField}); try {
}).then(() => { await ${simpleClassName}Api.delete${simpleClassName}(${primaryColumn.javaField});
this.getList(); this.getList();
this.#[[$modal]]#.msgSuccess("删除成功"); this.#[[$modal]]#.msgSuccess("删除成功");
}).catch(() => {}); } catch {}
}, },
/** 导出按钮操作 */ /** 导出按钮操作 */
handleExport() { async handleExport() {
// 处理查询参数 await this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?');
let params = {...this.queryParams}; try {
params.pageNo = undefined; this.exportLoading = true;
params.pageSize = undefined; const res = await ${simpleClassName}Api.export${simpleClassName}Excel(this.queryParams);
this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?').then(() => { this.#[[$]]#download.excel(res.data, '${table.classComment}.xls');
this.exportLoading = true; } catch {
return export${simpleClassName}Excel(params); } finally {
}).then(response => { this.exportLoading = false;
this.#[[$]]#download.excel(response, '${table.classComment}.xls'); }
this.exportLoading = false; },
}).catch(() => {}); ## 特殊:主子表专属逻辑
} #if ( $table.templateType == 11 )
/** 选中行操作 */
handleCurrentChange(row) {
this.currentRow = row;
#if ( $subTables && $subTables.size() > 0 )
/** 子表的列表 */
this.subTabsName = '$subClassNameVars.get(0)';
#end
},
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 展开/折叠操作 */
toggleExpandAll() {
this.refreshTable = false
this.isExpandAll = !this.isExpandAll
this.$nextTick(function () {
this.refreshTable = true
})
}
#end
} }
}; };
</script> </script>

View File

@ -1,137 +1,137 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible"> <Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="100px"
v-loading="formLoading" v-loading="formLoading"
> >
#foreach($column in $columns) #foreach($column in $columns)
#if ($column.createOperation || $column.updateOperation) #if ($column.createOperation || $column.updateOperation)
#set ($dictType = $column.dictType) #set ($dictType = $column.dictType)
#set ($javaField = $column.javaField) #set ($javaField = $column.javaField)
#set ($javaType = $column.javaType) #set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment) #set ($comment = $column.columnComment)
#set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法 #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
#set ($dictMethod = "getIntDictOptions") #set ($dictMethod = "getIntDictOptions")
#elseif ($javaType == "String") #elseif ($javaType == "String")
#set ($dictMethod = "getStrDictOptions") #set ($dictMethod = "getStrDictOptions")
#elseif ($javaType == "Boolean") #elseif ($javaType == "Boolean")
#set ($dictMethod = "getBoolDictOptions") #set ($dictMethod = "getBoolDictOptions")
#end
#if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
<el-form-item label="${comment}" prop="${javaField}">
<el-tree-select
v-model="formData.${javaField}"
:data="${classNameVar}Tree"
#if ($treeNameColumn.javaField == "name")
:props="defaultProps"
#else
:props="{...defaultProps, label: '$treeNameColumn.javaField'}"
#end
check-strictly
default-expand-all
placeholder="请选择${comment}"
/>
</el-form-item>
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
<el-form-item label="${comment}" prop="${javaField}">
<UploadImg v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
<el-form-item label="${comment}" prop="${javaField}">
<UploadFile v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
<el-form-item label="${comment}" prop="${javaField}">
<Editor v-model="formData.${javaField}" height="150px" />
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="formData.${javaField}"
type="date"
value-format="x"
placeholder="选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
</el-form-item>
#end
#end
#end #end
#if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
<el-form-item label="${comment}" prop="${javaField}">
<el-tree-select
v-model="formData.${javaField}"
:data="${classNameVar}Tree"
#if ($treeNameColumn.javaField == "name")
:props="defaultProps"
#else
:props="{...defaultProps, label: '$treeNameColumn.javaField'}"
#end
check-strictly
default-expand-all
placeholder="请选择${comment}"
/>
</el-form-item>
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" placeholder="请输入${comment}" />
</el-form-item>
#elseif($column.htmlType == "imageUpload")## 图片上传
<el-form-item label="${comment}" prop="${javaField}">
<UploadImg v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "fileUpload")## 文件上传
<el-form-item label="${comment}" prop="${javaField}">
<UploadFile v-model="formData.${javaField}" />
</el-form-item>
#elseif($column.htmlType == "editor")## 文本编辑器
<el-form-item label="${comment}" prop="${javaField}">
<Editor v-model="formData.${javaField}" height="150px" />
</el-form-item>
#elseif($column.htmlType == "select")## 下拉框
<el-form-item label="${comment}" prop="${javaField}">
<el-select v-model="formData.${javaField}" placeholder="请选择${comment}">
#if ("" != $dictType)## 有数据字典
<el-option
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
#else##没数据字典
<el-option label="请选择字典生成" value="" />
#end
</el-select>
</el-form-item>
#elseif($column.htmlType == "checkbox")## 多选框
<el-form-item label="${comment}" prop="${javaField}">
<el-checkbox-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-checkbox
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-checkbox>
#else##没数据字典
<el-checkbox>请选择字典生成</el-checkbox>
#end
</el-checkbox-group>
</el-form-item>
#elseif($column.htmlType == "radio")## 单选框
<el-form-item label="${comment}" prop="${javaField}">
<el-radio-group v-model="formData.${javaField}">
#if ("" != $dictType)## 有数据字典
<el-radio
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
#else##没数据字典
<el-radio label="1">请选择字典生成</el-radio>
#end
</el-radio-group>
</el-form-item>
#elseif($column.htmlType == "datetime")## 时间框
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="formData.${javaField}"
type="date"
value-format="x"
placeholder="选择${comment}"
/>
</el-form-item>
#elseif($column.htmlType == "textarea")## 文本框
<el-form-item label="${comment}" prop="${javaField}">
<el-input v-model="formData.${javaField}" type="textarea" placeholder="请输入${comment}" />
</el-form-item>
#end
#end
#end
</el-form> </el-form>
## 特殊:主子表专属逻辑 ## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 ) #if ( $table.templateType == 10 || $table.templateType == 12 )
<!-- 子表的表单 --> <!-- 子表的表单 -->
<el-tabs v-model="subTabsName"> <el-tabs v-model="subTabsName">
#foreach ($subTable in $subTables) #foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1) #set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index)) #set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index)) #set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar"> <el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" /> <${subSimpleClassName}Form ref="${subClassNameVar}FormRef" :${subJoinColumn_strikeCase}="formData.id" />
</el-tab-pane> </el-tab-pane>
#end #end
</el-tabs> </el-tabs>
#end #end
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
@ -139,160 +139,160 @@
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict' import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}' import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
## 特殊:树表专属逻辑 ## 特殊:树表专属逻辑
#if ( $table.templateType == 2 ) #if ( $table.templateType == 2 )
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
#end #end
## 特殊:主子表专属逻辑 ## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 ) #if ( $table.templateType == 10 || $table.templateType == 12 )
#foreach ($subSimpleClassName in $subSimpleClassNames) #foreach ($subSimpleClassName in $subSimpleClassNames)
import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue' import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'
#end #end
#end
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改
const formData = ref({
#foreach ($column in $columns)
#if ($column.createOperation || $column.updateOperation)
#if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end #end
#end
#end
})
const formRules = reactive({
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
#end
#end
})
const formRef = ref() // 表单 Ref
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
const ${classNameVar}Tree = ref() // 树形结构
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
/** 子表的表单 */ const { t } = useI18n() // 国际化
const subTabsName = ref('$subClassNameVars.get(0)') const message = useMessage() // 消息弹窗
#foreach ($subClassNameVar in $subClassNameVars)
const ${subClassNameVar}FormRef = ref()
#end
#end
#end
/** 打开弹窗 */ const dialogVisible = ref(false) // 弹窗的是否展示
const open = async (type: string, id?: number) => { const dialogTitle = ref('') // 弹窗的标题
dialogVisible.value = true const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
dialogTitle.value = t('action.' + type) const formType = ref('') // 表单的类型create - 新增update - 修改
formType.value = type const formData = ref({
resetForm() #foreach ($column in $columns)
// 修改时,设置数据 #if ($column.createOperation || $column.updateOperation)
if (id) { #if ($column.htmlType == "checkbox")
$column.javaField: [],
#else
$column.javaField: undefined,
#end
#end
#end
})
const formRules = reactive({
#foreach ($column in $columns)
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
#set($comment=$column.columnComment)
$column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],
#end
#end
})
const formRef = ref() // 表单 Ref
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
const ${classNameVar}Tree = ref() // 树形结构
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
/** 子表的表单 */
const subTabsName = ref('$subClassNameVars.get(0)')
#foreach ($subClassNameVar in $subClassNameVars)
const ${subClassNameVar}FormRef = ref()
#end
#end
#end
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ${simpleClassName}Api.get${simpleClassName}(id)
} finally {
formLoading.value = false
}
}
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
await get${simpleClassName}Tree()
#end
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
// 校验子表单
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
try {
await ${subClassNameVar}FormRef.value.validate()
} catch (e) {
subTabsName.value = '${subClassNameVar}'
return
}
#end
#end
#end
// 提交请求
formLoading.value = true formLoading.value = true
try { try {
formData.value = await ${simpleClassName}Api.get${simpleClassName}(id) const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
// 拼接子表的数据
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()
#end
#end
#end
if (formType.value === 'create') {
await ${simpleClassName}Api.create${simpleClassName}(data)
message.success(t('common.createSuccess'))
} else {
await ${simpleClassName}Api.update${simpleClassName}(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} }
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
await get${simpleClassName}Tree()
#end
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */ /** 重置表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const resetForm = () => {
const submitForm = async () => { formData.value = {
// 校验表单 #foreach ($column in $columns)
await formRef.value.validate() #if ($column.createOperation || $column.updateOperation)
## 特殊:主子表专属逻辑 #if ($column.htmlType == "checkbox")
#if ( $table.templateType == 10 || $table.templateType == 12 ) $column.javaField: [],
#if ( $subTables && $subTables.size() > 0 ) #else
// 校验子表单 $column.javaField: undefined,
#foreach ($subTable in $subTables) #end
#set ($index = $foreach.count - 1) #end
#set ($subClassNameVar = $subClassNameVars.get($index)) #end
try {
await ${subClassNameVar}FormRef.value.validate()
} catch (e) {
subTabsName.value = '${subClassNameVar}'
return
}
#end
#end
#end
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ${simpleClassName}Api.${simpleClassName}VO
## 特殊:主子表专属逻辑
#if ( $table.templateType == 10 || $table.templateType == 12 )
#if ( $subTables && $subTables.size() > 0 )
// 拼接子表的数据
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()
#end
#end
#end
if (formType.value === 'create') {
await ${simpleClassName}Api.create${simpleClassName}(data)
message.success(t('common.createSuccess'))
} else {
await ${simpleClassName}Api.update${simpleClassName}(data)
message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false formRef.value?.resetFields()
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
} }
} ## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 重置表单 */ /** 获得${table.classComment}树 */
const resetForm = () => { const get${simpleClassName}Tree = async () => {
formData.value = { ${classNameVar}Tree.value = []
#foreach ($column in $columns) const data = await ${simpleClassName}Api.get${simpleClassName}List()
#if ($column.createOperation || $column.updateOperation) const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
#if ($column.htmlType == "checkbox") root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
$column.javaField: [], ${classNameVar}Tree.value.push(root)
#else }
$column.javaField: undefined,
#end #end
#end
#end
}
formRef.value?.resetFields()
}
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 获得${table.classComment}树 */
const get${simpleClassName}Tree = async () => {
${classNameVar}Tree.value = []
const data = await ${simpleClassName}Api.get${simpleClassName}List()
const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }
root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')
${classNameVar}Tree.value.push(root)
}
#end
</script> </script>

View File

@ -2,372 +2,375 @@
<ContentWrap> <ContentWrap>
<!-- 搜索工作栏 --> <!-- 搜索工作栏 -->
<el-form <el-form
class="-mb-15px" class="-mb-15px"
:model="queryParams" :model="queryParams"
ref="queryFormRef" ref="queryFormRef"
:inline="true" :inline="true"
label-width="68px" label-width="68px"
> >
#foreach($column in $columns) #foreach($column in $columns)
#if ($column.listOperation) #if ($column.listOperation)
#set ($dictType = $column.dictType) #set ($dictType = $column.dictType)
#set ($javaField = $column.javaField) #set ($javaField = $column.javaField)
#set ($javaType = $column.javaType) #set ($javaType = $column.javaType)
#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment = $column.columnComment) #set ($comment = $column.columnComment)
#set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法 #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
#set ($dictMethod = "getIntDictOptions") #set ($dictMethod = "getIntDictOptions")
#elseif ($javaType == "String") #elseif ($javaType == "String")
#set ($dictMethod = "getStrDictOptions") #set ($dictMethod = "getStrDictOptions")
#elseif ($javaType == "Boolean") #elseif ($javaType == "Boolean")
#set ($dictMethod = "getBoolDictOptions") #set ($dictMethod = "getBoolDictOptions")
#end
#if ($column.htmlType == "input")
<el-form-item label="${comment}" prop="${javaField}">
<el-input
v-model="queryParams.${javaField}"
placeholder="请输入${comment}"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
#elseif ($column.htmlType == "select" || $column.htmlType == "radio")
<el-form-item label="${comment}" prop="${javaField}">
<el-select
v-model="queryParams.${javaField}"
placeholder="请选择${comment}"
clearable
class="!w-240px"
>
#if ("" != $dictType)## 设置了 dictType 数据字典的情况
<el-option
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
#else## 未设置 dictType 数据字典的情况
<el-option label="请选择字典生成" value="" />
#end #end
</el-select> #if ($column.htmlType == "input")
</el-form-item> <el-form-item label="${comment}" prop="${javaField}">
#elseif($column.htmlType == "datetime") <el-input
#if ($column.listOperationCondition != "BETWEEN")## 非范围 v-model="queryParams.${javaField}"
<el-form-item label="${comment}" prop="${javaField}"> placeholder="请输入${comment}"
<el-date-picker clearable
v-model="queryParams.${javaField}" @keyup.enter="handleQuery"
value-format="YYYY-MM-DD" class="!w-240px"
type="date" />
placeholder="选择${comment}" </el-form-item>
clearable #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
class="!w-240px" <el-form-item label="${comment}" prop="${javaField}">
/> <el-select
</el-form-item> v-model="queryParams.${javaField}"
#else## 范围 placeholder="请选择${comment}"
<el-form-item label="${comment}" prop="${javaField}"> clearable
<el-date-picker class="!w-240px"
v-model="queryParams.${javaField}" >
value-format="YYYY-MM-DD HH:mm:ss" #if ("" != $dictType)## 设置了 dictType 数据字典的情况
type="daterange" <el-option
start-placeholder="开始日期" v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
end-placeholder="结束日期" :key="dict.value"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" :label="dict.label"
class="!w-240px" :value="dict.value"
/> />
</el-form-item> #else## 未设置 dictType 数据字典的情况
#end <el-option label="请选择字典生成" value="" />
#end #end
#end </el-select>
#end </el-form-item>
#elseif($column.htmlType == "datetime")
#if ($column.listOperationCondition != "BETWEEN")## 非范围
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="queryParams.${javaField}"
value-format="YYYY-MM-DD"
type="date"
placeholder="选择${comment}"
clearable
class="!w-240px"
/>
</el-form-item>
#else## 范围
<el-form-item label="${comment}" prop="${javaField}">
<el-date-picker
v-model="queryParams.${javaField}"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
#end
#end
#end
#end
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button> <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button> <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button <el-button
type="primary" type="primary"
plain plain
@click="openForm('create')" @click="openForm('create')"
v-hasPermi="['${permissionPrefix}:create']" v-hasPermi="['${permissionPrefix}:create']"
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button <el-button
type="success" type="success"
plain plain
@click="handleExport" @click="handleExport"
:loading="exportLoading" :loading="exportLoading"
v-hasPermi="['${permissionPrefix}:export']" v-hasPermi="['${permissionPrefix}:export']"
> >
<Icon icon="ep:download" class="mr-5px" /> 导出 <Icon icon="ep:download" class="mr-5px" /> 导出
</el-button> </el-button>
## 特殊:树表专属逻辑 ## 特殊:树表专属逻辑
#if ( $table.templateType == 2 ) #if ( $table.templateType == 2 )
<el-button type="danger" plain @click="toggleExpandAll"> <el-button type="danger" plain @click="toggleExpandAll">
<Icon icon="ep:sort" class="mr-5px" /> 展开/折叠 <Icon icon="ep:sort" class="mr-5px" /> 展开/折叠
</el-button> </el-button>
#end #end
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
## 特殊:主子表专属逻辑 ## 特殊:主子表专属逻辑
#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 ) #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="list" :data="list"
:stripe="true" :stripe="true"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
highlight-current-row highlight-current-row
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
> >
## 特殊:树表专属逻辑 ## 特殊:树表专属逻辑
#elseif ( $table.templateType == 2 ) #elseif ( $table.templateType == 2 )
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="list" :data="list"
:stripe="true" :stripe="true"
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
row-key="id" row-key="id"
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
v-if="refreshTable" v-if="refreshTable"
> >
#else #else
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
#end
## 特殊:主子表专属逻辑
#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
<!-- 子表的列表 -->
<el-table-column type="expand">
<template #default="scope">
<el-tabs model-value="$subClassNameVars.get(0)">
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
</el-tab-pane>
#end
</el-tabs>
</template>
</el-table-column>
#end
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ($column.javaType == "LocalDateTime")## 时间类型
<el-table-column
label="${comment}"
align="center"
prop="${javaField}"
:formatter="dateFormatter"
width="180px"
/>
#elseif($column.dictType && "" != $column.dictType)## 数据字典
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
</template>
</el-table-column>
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end #end
#end ## 特殊:主子表专属逻辑
<el-table-column label="操作" align="center"> #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
<template #default="scope"> <!-- 子表的列表 -->
<el-button <el-table-column type="expand">
<template #default="scope">
<el-tabs model-value="$subClassNameVars.get(0)">
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
</el-tab-pane>
#end
</el-tabs>
</template>
</el-table-column>
#end
#foreach($column in $columns)
#if ($column.listOperationResult)
#set ($dictType=$column.dictType)
#set ($javaField = $column.javaField)
#set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set ($comment=$column.columnComment)
#if ($column.javaType == "LocalDateTime")## 时间类型
<el-table-column
label="${comment}"
align="center"
prop="${javaField}"
:formatter="dateFormatter"
width="180px"
/>
#elseif($column.dictType && "" != $column.dictType)## 数据字典
<el-table-column label="${comment}" align="center" prop="${javaField}">
<template #default="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.${column.javaField}" />
</template>
</el-table-column>
#else
<el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
#end
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link link
type="primary" type="primary"
@click="openForm('update', scope.row.id)" @click="openForm('update', scope.row.id)"
v-hasPermi="['${permissionPrefix}:update']" v-hasPermi="['${permissionPrefix}:update']"
> >
编辑 编辑
</el-button> </el-button>
<el-button <el-button
link link
type="danger" type="danger"
@click="handleDelete(scope.row.id)" @click="handleDelete(scope.row.id)"
v-hasPermi="['${permissionPrefix}:delete']" v-hasPermi="['${permissionPrefix}:delete']"
> >
删除 删除
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
## 特殊:树表专属逻辑(树不需要分页)
#if ( $table.templateType != 2 )
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
:total="total" :total="total"
v-model:page="queryParams.pageNo" v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize" v-model:limit="queryParams.pageSize"
@pagination="getList" @pagination="getList"
/> />
#end
</ContentWrap> </ContentWrap>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<${simpleClassName}Form ref="formRef" @success="getList" /> <${simpleClassName}Form ref="formRef" @success="getList" />
## 特殊:主子表专属逻辑 ## 特殊:主子表专属逻辑
#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 ) #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
<!-- 子表的列表 --> <!-- 子表的列表 -->
<ContentWrap> <ContentWrap>
<el-tabs model-value="$subClassNameVars.get(0)"> <el-tabs model-value="$subClassNameVars.get(0)">
#foreach ($subTable in $subTables) #foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1) #set ($index = $foreach.count - 1)
#set ($subClassNameVar = $subClassNameVars.get($index)) #set ($subClassNameVar = $subClassNameVars.get($index))
#set ($subSimpleClassName = $subSimpleClassNames.get($index)) #set ($subSimpleClassName = $subSimpleClassNames.get($index))
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index)) #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
<el-tab-pane label="${subTable.classComment}" name="$subClassNameVar"> <el-tab-pane label="${subTable.classComment}" name="$subClassNameVar">
<${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" /> <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
</el-tab-pane> </el-tab-pane>
#end #end
</el-tabs> </el-tabs>
</ContentWrap> </ContentWrap>
#end #end
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict' import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
## 特殊:树表专属逻辑 ## 特殊:树表专属逻辑
#if ( $table.templateType == 2 ) #if ( $table.templateType == 2 )
import { handleTree } from '@/utils/tree' import { handleTree } from '@/utils/tree'
#end #end
import download from '@/utils/download' import download from '@/utils/download'
import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}' import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
import ${simpleClassName}Form from './${simpleClassName}Form.vue' import ${simpleClassName}Form from './${simpleClassName}Form.vue'
## 特殊:主子表专属逻辑 ## 特殊:主子表专属逻辑
#if ( $table.templateType != 10 ) #if ( $table.templateType != 10 )
#foreach ($subSimpleClassName in $subSimpleClassNames) #foreach ($subSimpleClassName in $subSimpleClassNames)
import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue' import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'
#end #end
#end
defineOptions({ name: '${table.className}' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
## 特殊:树表专属逻辑(树不需要分页接口)
#if ( $table.templateType != 2 )
const total = ref(0) // 列表的总页数
#end
const queryParams = reactive({
## 特殊:树表专属逻辑(树不需要分页接口)
#if ( $table.templateType != 2 )
pageNo: 1,
pageSize: 10,
#end
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.listOperationCondition != 'BETWEEN')
$column.javaField: null,
#end
#if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
$column.javaField: [],
#end #end
#end
#end
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */ defineOptions({ name: '${table.className}' })
const getList = async () => {
loading.value = true const message = useMessage() // 消息弹窗
try { const { t } = useI18n() // 国际化
## 特殊:树表专属逻辑(树不需要分页接口)
#if ( $table.templateType == 2 ) const loading = ref(true) // 列表的加载中
const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams) const list = ref([]) // 列表的数据
list.value = handleTree(data, 'id', '${treeParentColumn.javaField}') ## 特殊:树表专属逻辑(树不需要分页接口)
#else #if ( $table.templateType != 2 )
const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams) const total = ref(0) // 列表的总页数
list.value = data.list #end
total.value = data.total const queryParams = reactive({
#end ## 特殊:树表专属逻辑(树不需要分页接口)
} finally { #if ( $table.templateType != 2 )
loading.value = false pageNo: 1,
pageSize: 10,
#end
#foreach ($column in $columns)
#if ($column.listOperation)
#if ($column.listOperationCondition != 'BETWEEN')
$column.javaField: null,
#end
#if ($column.htmlType == "datetime" || $column.listOperationCondition == "BETWEEN")
$column.javaField: [],
#end
#end
#end
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
## 特殊:树表专属逻辑(树不需要分页接口)
#if ( $table.templateType == 2 )
const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams)
list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')
#else
const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)
list.value = data.list
total.value = data.total
#end
} finally {
loading.value = false
}
} }
}
/** 搜索按钮操作 */ /** 搜索按钮操作 */
const handleQuery = () => { const handleQuery = () => {
queryParams.pageNo = 1 queryParams.pageNo = 1
getList() getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ${simpleClassName}Api.delete${simpleClassName}(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)
download.excel(data, '${table.classComment}.xls')
} catch {
} finally {
exportLoading.value = false
} }
}
## 特殊:主子表专属逻辑
#if ( $table.templateType == 11 )
/** 选中行操作 */ /** 重置按钮操作 */
const currentRow = ref({}) // 选中行 const resetQuery = () => {
const handleCurrentChange = (row) => { queryFormRef.value.resetFields()
currentRow.value = row handleQuery()
} }
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 展开/折叠操作 */ /** 添加/修改操作 */
const isExpandAll = ref(true) // 是否展开,默认全部展开 const formRef = ref()
const refreshTable = ref(true) // 重新渲染表格状态 const openForm = (type: string, id?: number) => {
const toggleExpandAll = async () => { formRef.value.open(type, id)
refreshTable.value = false }
isExpandAll.value = !isExpandAll.value
await nextTick()
refreshTable.value = true
}
#end
/** 初始化 **/ /** 删除按钮操作 */
onMounted(() => { const handleDelete = async (id: number) => {
getList() try {
}) // 删除的二次确认
await message.delConfirm()
// 发起删除
await ${simpleClassName}Api.delete${simpleClassName}(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)
download.excel(data, '${table.classComment}.xls')
} catch {
} finally {
exportLoading.value = false
}
}
## 特殊:主子表专属逻辑
#if ( $table.templateType == 11 )
/** 选中行操作 */
const currentRow = ref({}) // 选中行
const handleCurrentChange = (row) => {
currentRow.value = row
}
#end
## 特殊:树表专属逻辑
#if ( $table.templateType == 2 )
/** 展开/折叠操作 */
const isExpandAll = ref(true) // 是否展开,默认全部展开
const refreshTable = ref(true) // 重新渲染表格状态
const toggleExpandAll = async () => {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
await nextTick()
refreshTable.value = true
}
#end
/** 初始化 **/
onMounted(() => {
getList()
})
</script> </script>

View File

@ -0,0 +1,202 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Spy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link CodegenEngine} 的单元测试
*
* @author 芋道源码
*/
public class CodegenEngineVue2Test extends BaseMockitoUnitTest {
@InjectMocks
private CodegenEngine codegenEngine;
@Spy
private CodegenProperties codegenProperties = new CodegenProperties()
.setBasePackage("cn.iocoder.yudao");
@BeforeEach
public void setUp() {
codegenEngine.initGlobalBindingMap();
}
@Test
public void testExecute_vue2_one() {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE2.getType())
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 调用
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
// 断言
assertResult(result, "codegen/vue2_one");
// writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_one");
}
@Test
public void testExecute_vue2_tree() {
// 准备参数
CodegenTableDO table = getTable("category")
.setFrontType(CodegenFrontTypeEnum.VUE2.getType())
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
List<CodegenColumnDO> columns = getColumnList("category");
// 调用
Map<String, String> result = codegenEngine.execute(table, columns, null, null);
// 断言
assertResult(result, "codegen/vue2_tree");
// writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/codegen/vue2_tree");
// writeFile(result, "/Users/yunai/test/demo66.zip");
}
@Test
public void testExecute_vue2_master_normal() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "codegen/vue2_master_normal");
}
@Test
public void testExecute_vue2_master_erp() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_ERP, "codegen/vue2_master_erp");
}
@Test
public void testExecute_vue2_master_inner() {
testExecute_vue2_master(CodegenTemplateTypeEnum.MASTER_INNER, "codegen/vue2_master_inner");
}
private void testExecute_vue2_master(CodegenTemplateTypeEnum templateType,
String path) {
// 准备参数
CodegenTableDO table = getTable("student")
.setFrontType(CodegenFrontTypeEnum.VUE2.getType())
.setTemplateType(templateType.getType());
List<CodegenColumnDO> columns = getColumnList("student");
// 准备参数子表
CodegenTableDO contactTable = getTable("contact")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE2.getType())
.setSubJoinColumnId(100L).setSubJoinMany(true);
List<CodegenColumnDO> contactColumns = getColumnList("contact");
// 准备参数班主任
CodegenTableDO teacherTable = getTable("teacher")
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
.setFrontType(CodegenFrontTypeEnum.VUE2.getType())
.setSubJoinColumnId(200L).setSubJoinMany(false);
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
// 调用
Map<String, String> result = codegenEngine.execute(table, columns,
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
// 断言
assertResult(result, path);
// writeResult(result, "/root/ruoyi-vue-pro/yudao-module-infra/yudao-module-infra-biz/src/test/resources/" + path);
// writeFile(result, "/Users/yunai/test/demo11.zip");
}
private static CodegenTableDO getTable(String name) {
String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
return JsonUtils.parseObject(content, "table", CodegenTableDO.class);
}
private static List<CodegenColumnDO> getColumnList(String name) {
String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
List<CodegenColumnDO> list = JsonUtils.parseArray(content, "columns", CodegenColumnDO.class);
list.forEach(column -> {
if (column.getNullable() == null) {
column.setNullable(false);
}
if (column.getCreateOperation() == null) {
column.setCreateOperation(false);
}
if (column.getUpdateOperation() == null) {
column.setUpdateOperation(false);
}
if (column.getListOperation() == null) {
column.setListOperation(false);
}
if (column.getListOperationResult() == null) {
column.setListOperationResult(false);
}
});
return list;
}
@SuppressWarnings("rawtypes")
private static void assertResult(Map<String, String> result, String path) {
String assertContent = ResourceUtil.readUtf8Str(path + "/assert.json");
List<HashMap> asserts = JsonUtils.parseArray(assertContent, HashMap.class);
assertEquals(asserts.size(), result.size());
// 校验每个文件
asserts.forEach(assertMap -> {
String contentPath = (String) assertMap.get("contentPath");
String filePath = (String) assertMap.get("filePath");
String content = ResourceUtil.readUtf8Str(path + "/" + contentPath);
assertEquals(content, result.get(filePath), filePath + ":不匹配");
});
}
// ==================== 调试专用 ====================
/**
* 调试使用将生成的代码写入到文件
*
* @param result 生成的代码
* @param path 写入文件的路径
*/
private void writeFile(Map<String, String> result, String path) {
// 生成压缩包
String[] paths = result.keySet().toArray(new String[0]);
ByteArrayInputStream[] ins = result.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipUtil.zip(outputStream, paths, ins);
// 写入文件
FileUtil.writeBytes(outputStream.toByteArray(), path);
}
/**
* 调试使用将生成的结果写入到文件
*
* @param result 生成的代码
* @param basePath 写入文件的路径绝对路径
*/
private void writeResult(Map<String, String> result, String basePath) {
// 写入文件内容
List<Map<String, String>> asserts = new ArrayList<>();
result.forEach((filePath, fileContent) -> {
String lastFilePath = StrUtil.subAfter(filePath, '/', true);
String contentPath = StrUtil.subAfter(lastFilePath, '.', true)
+ '/' + StrUtil.subBefore(lastFilePath, '.', true);
asserts.add(MapUtil.<String, String>builder().put("filePath", filePath)
.put("contentPath", contentPath).build());
FileUtil.writeUtf8String(fileContent, basePath + "/" + contentPath);
});
// 写入 assert.json 文件
FileUtil.writeUtf8String(JsonUtils.toJsonPrettyString(asserts), basePath +"/assert.json");
}
}

View File

@ -0,0 +1,73 @@
[ {
"contentPath" : "java/InfraStudentPageReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
}, {
"contentPath" : "java/InfraStudentRespVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
}, {
"contentPath" : "java/InfraStudentSaveReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
}, {
"contentPath" : "java/InfraStudentController",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
}, {
"contentPath" : "java/InfraStudentDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
}, {
"contentPath" : "java/InfraStudentContactDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
}, {
"contentPath" : "java/InfraStudentTeacherDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
}, {
"contentPath" : "java/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
}, {
"contentPath" : "java/InfraStudentContactMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
}, {
"contentPath" : "java/InfraStudentTeacherMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
}, {
"contentPath" : "xml/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
}, {
"contentPath" : "java/InfraStudentServiceImpl",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
}, {
"contentPath" : "java/InfraStudentService",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
}, {
"contentPath" : "java/InfraStudentServiceImplTest",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
}, {
"contentPath" : "java/ErrorCodeConstants_手动操作",
"filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
}, {
"contentPath" : "sql/sql",
"filePath" : "sql/sql.sql"
}, {
"contentPath" : "sql/h2",
"filePath" : "sql/h2.sql"
}, {
"contentPath" : "vue/index",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
}, {
"contentPath" : "js/student",
"filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
}, {
"contentPath" : "vue/StudentForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
}, {
"contentPath" : "vue/StudentContactForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
}, {
"contentPath" : "vue/StudentTeacherForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
}, {
"contentPath" : "vue/StudentContactList",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactList.vue"
}, {
"contentPath" : "vue/StudentTeacherList",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherList.vue"
} ]

View File

@ -0,0 +1,6 @@
// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意请给“TODO 补充编号”设置一个错误码编号!!!
// ========== 学生 TODO 补充编号 ==========
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
ErrorCode STUDENT_CONTACT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生联系人不存在");
ErrorCode STUDENT_TEACHER_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任不存在");
ErrorCode STUDENT_TEACHER_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任已存在");

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生联系人 DO
*
* @author 芋道源码
*/
@TableName("infra_student_contact")
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentContactDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生联系人 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
default PageResult<InfraStudentContactDO> selectPage(PageParam reqVO, Long studentId) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentContactDO>()
.eq(InfraStudentContactDO::getStudentId, studentId)
.orderByDesc(InfraStudentContactDO::getId));
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentContactDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,183 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
@Tag(name = "管理后台 - 学生")
@RestController
@RequestMapping("/infra/student")
@Validated
public class InfraStudentController {
@Resource
private InfraStudentService studentService;
@PostMapping("/create")
@Operation(summary = "创建学生")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
return success(studentService.createStudent(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新学生")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
studentService.updateStudent(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除学生")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
studentService.deleteStudent(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得学生")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
InfraStudentDO student = studentService.getStudent(id);
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得学生分页")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出学生 Excel")
@PreAuthorize("@ss.hasPermission('infra:student:export')")
@OperateLog(type = EXPORT)
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
BeanUtils.toBean(list, InfraStudentRespVO.class));
}
// ==================== 子表(学生联系人) ====================
@GetMapping("/student-contact/page")
@Operation(summary = "获得学生联系人分页")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentContactDO>> getStudentContactPage(PageParam pageReqVO,
@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentContactPage(pageReqVO, studentId));
}
@PostMapping("/student-contact/create")
@Operation(summary = "创建学生联系人")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
return success(studentService.createStudentContact(studentContact));
}
@PutMapping("/student-contact/update")
@Operation(summary = "更新学生联系人")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
studentService.updateStudentContact(studentContact);
return success(true);
}
@DeleteMapping("/student-contact/delete")
@Parameter(name = "id", description = "编号", required = true)
@Operation(summary = "删除学生联系人")
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudentContact(@RequestParam("id") Long id) {
studentService.deleteStudentContact(id);
return success(true);
}
@GetMapping("/student-contact/get")
@Operation(summary = "获得学生联系人")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentContactDO> getStudentContact(@RequestParam("id") Long id) {
return success(studentService.getStudentContact(id));
}
// ==================== 子表(学生班主任) ====================
@GetMapping("/student-teacher/page")
@Operation(summary = "获得学生班主任分页")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentTeacherDO>> getStudentTeacherPage(PageParam pageReqVO,
@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentTeacherPage(pageReqVO, studentId));
}
@PostMapping("/student-teacher/create")
@Operation(summary = "创建学生班主任")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
return success(studentService.createStudentTeacher(studentTeacher));
}
@PutMapping("/student-teacher/update")
@Operation(summary = "更新学生班主任")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
studentService.updateStudentTeacher(studentTeacher);
return success(true);
}
@DeleteMapping("/student-teacher/delete")
@Parameter(name = "id", description = "编号", required = true)
@Operation(summary = "删除学生班主任")
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudentTeacher(@RequestParam("id") Long id) {
studentService.deleteStudentTeacher(id);
return success(true);
}
@GetMapping("/student-teacher/get")
@Operation(summary = "获得学生班主任")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentTeacherDO> getStudentTeacher(@RequestParam("id") Long id) {
return success(studentService.getStudentTeacher(id));
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生 DO
*
* @author 芋道源码
*/
@TableName("infra_student")
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
/**
* 学生 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(InfraStudentDO::getId));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 学生分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InfraStudentPageReqVO extends PageParam {
@Schema(description = "名字", example = "芋头")
private String name;
@Schema(description = "出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", example = "1")
private Integer sex;
@Schema(description = "是否有效", example = "true")
private Boolean enabled;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@Schema(description = "管理后台 - 学生 Response VO")
@Data
@ExcelIgnoreUnannotated
public class InfraStudentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("编号")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@ExcelProperty("名字")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@ExcelProperty("简介")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@ExcelProperty("头像")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@ExcelProperty("附件")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@ExcelProperty("备注")
private String memo;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
@Data
public class InfraStudentSaveReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@NotEmpty(message = "名字不能为空")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@NotEmpty(message = "简介不能为空")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "出生日期不能为空")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "性别不能为空")
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否有效不能为空")
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@NotEmpty(message = "头像不能为空")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@NotEmpty(message = "附件不能为空")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@NotEmpty(message = "备注不能为空")
private String memo;
}

View File

@ -0,0 +1,139 @@
package cn.iocoder.yudao.module.infra.service.demo;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 学生 Service 接口
*
* @author 芋道源码
*/
public interface InfraStudentService {
/**
* 创建学生
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
/**
* 更新学生
*
* @param updateReqVO 更新信息
*/
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
/**
* 删除学生
*
* @param id 编号
*/
void deleteStudent(Long id);
/**
* 获得学生
*
* @param id 编号
* @return 学生
*/
InfraStudentDO getStudent(Long id);
/**
* 获得学生分页
*
* @param pageReqVO 分页查询
* @return 学生分页
*/
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
// ==================== 子表(学生联系人) ====================
/**
* 获得学生联系人分页
*
* @param pageReqVO 分页查询
* @param studentId 学生编号
* @return 学生联系人分页
*/
PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId);
/**
* 创建学生联系人
*
* @param studentContact 创建信息
* @return 编号
*/
Long createStudentContact(@Valid InfraStudentContactDO studentContact);
/**
* 更新学生联系人
*
* @param studentContact 更新信息
*/
void updateStudentContact(@Valid InfraStudentContactDO studentContact);
/**
* 删除学生联系人
*
* @param id 编号
*/
void deleteStudentContact(Long id);
/**
* 获得学生联系人
*
* @param id 编号
* @return 学生联系人
*/
InfraStudentContactDO getStudentContact(Long id);
// ==================== 子表(学生班主任) ====================
/**
* 获得学生班主任分页
*
* @param pageReqVO 分页查询
* @param studentId 学生编号
* @return 学生班主任分页
*/
PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId);
/**
* 创建学生班主任
*
* @param studentTeacher 创建信息
* @return 编号
*/
Long createStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
/**
* 更新学生班主任
*
* @param studentTeacher 更新信息
*/
void updateStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
/**
* 删除学生班主任
*
* @param id 编号
*/
void deleteStudentTeacher(Long id);
/**
* 获得学生班主任
*
* @param id 编号
* @return 学生班主任
*/
InfraStudentTeacherDO getStudentTeacher(Long id);
}

View File

@ -0,0 +1,180 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* 学生 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class InfraStudentServiceImpl implements InfraStudentService {
@Resource
private InfraStudentMapper studentMapper;
@Resource
private InfraStudentContactMapper studentContactMapper;
@Resource
private InfraStudentTeacherMapper studentTeacherMapper;
@Override
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
// 插入
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
studentMapper.insert(student);
// 返回
return student.getId();
}
@Override
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
// 校验存在
validateStudentExists(updateReqVO.getId());
// 更新
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
studentMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteStudent(Long id) {
// 校验存在
validateStudentExists(id);
// 删除
studentMapper.deleteById(id);
// 删除子表
deleteStudentContactByStudentId(id);
deleteStudentTeacherByStudentId(id);
}
private void validateStudentExists(Long id) {
if (studentMapper.selectById(id) == null) {
throw exception(STUDENT_NOT_EXISTS);
}
}
@Override
public InfraStudentDO getStudent(Long id) {
return studentMapper.selectById(id);
}
@Override
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
return studentMapper.selectPage(pageReqVO);
}
// ==================== 子表(学生联系人) ====================
@Override
public PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId) {
return studentContactMapper.selectPage(pageReqVO, studentId);
}
@Override
public Long createStudentContact(InfraStudentContactDO studentContact) {
studentContactMapper.insert(studentContact);
return studentContact.getId();
}
@Override
public void updateStudentContact(InfraStudentContactDO studentContact) {
// 校验存在
validateStudentContactExists(studentContact.getId());
// 更新
studentContactMapper.updateById(studentContact);
}
@Override
public void deleteStudentContact(Long id) {
// 校验存在
validateStudentContactExists(id);
// 删除
studentContactMapper.deleteById(id);
}
@Override
public InfraStudentContactDO getStudentContact(Long id) {
return studentContactMapper.selectById(id);
}
private void validateStudentContactExists(Long id) {
if (studentContactMapper.selectById(id) == null) {
throw exception(STUDENT_CONTACT_NOT_EXISTS);
}
}
private void deleteStudentContactByStudentId(Long studentId) {
studentContactMapper.deleteByStudentId(studentId);
}
// ==================== 子表(学生班主任) ====================
@Override
public PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId) {
return studentTeacherMapper.selectPage(pageReqVO, studentId);
}
@Override
public Long createStudentTeacher(InfraStudentTeacherDO studentTeacher) {
// 校验是否已经存在
if (studentTeacherMapper.selectByStudentId(studentTeacher.getStudentId()) != null) {
throw exception(STUDENT_TEACHER_EXISTS);
}
// 插入
studentTeacherMapper.insert(studentTeacher);
return studentTeacher.getId();
}
@Override
public void updateStudentTeacher(InfraStudentTeacherDO studentTeacher) {
// 校验存在
validateStudentTeacherExists(studentTeacher.getId());
// 更新
studentTeacherMapper.updateById(studentTeacher);
}
@Override
public void deleteStudentTeacher(Long id) {
// 校验存在
validateStudentTeacherExists(id);
// 删除
studentTeacherMapper.deleteById(id);
}
@Override
public InfraStudentTeacherDO getStudentTeacher(Long id) {
return studentTeacherMapper.selectById(id);
}
private void validateStudentTeacherExists(Long id) {
if (studentTeacherMapper.selectById(id) == null) {
throw exception(STUDENT_TEACHER_NOT_EXISTS);
}
}
private void deleteStudentTeacherByStudentId(Long studentId) {
studentTeacherMapper.deleteByStudentId(studentId);
}
}

View File

@ -0,0 +1,146 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link InfraStudentServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(InfraStudentServiceImpl.class)
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
@Resource
private InfraStudentServiceImpl studentService;
@Resource
private InfraStudentMapper studentMapper;
@Test
public void testCreateStudent_success() {
// 准备参数
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
// 调用
Long studentId = studentService.createStudent(createReqVO);
// 断言
assertNotNull(studentId);
// 校验记录的属性是否正确
InfraStudentDO student = studentMapper.selectById(studentId);
assertPojoEquals(createReqVO, student, "id");
}
@Test
public void testUpdateStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
o.setId(dbStudent.getId()); // 设置更新的 ID
});
// 调用
studentService.updateStudent(updateReqVO);
// 校验是否更新正确
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, student);
}
@Test
public void testUpdateStudent_notExists() {
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
}
@Test
public void testDeleteStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbStudent.getId();
// 调用
studentService.deleteStudent(id);
// 校验数据不存在了
assertNull(studentMapper.selectById(id));
}
@Test
public void testDeleteStudent_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
public void testGetStudentPage() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
o.setName(null);
o.setBirthday(null);
o.setSex(null);
o.setEnabled(null);
o.setCreateTime(null);
});
studentMapper.insert(dbStudent);
// 测试 name 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
// 测试 birthday 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
// 测试 sex 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
// 测试 enabled 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
// 测试 createTime 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
// 准备参数
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
reqVO.setName(null);
reqVO.setBirthday(null);
reqVO.setSex(null);
reqVO.setEnabled(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbStudent, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生班主任 DO
*
* @author 芋道源码
*/
@TableName("infra_student_teacher")
@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentTeacherDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生班主任 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> {
default PageResult<InfraStudentTeacherDO> selectPage(PageParam reqVO, Long studentId) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentTeacherDO>()
.eq(InfraStudentTeacherDO::getStudentId, studentId)
.orderByDesc(InfraStudentTeacherDO::getId));
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentTeacherDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,141 @@
import request from '@/utils/request'
// 创建学生
export function createStudent(data) {
return request({
url: '/infra/student/create',
method: 'post',
data: data
})
}
// 更新学生
export function updateStudent(data) {
return request({
url: '/infra/student/update',
method: 'put',
data: data
})
}
// 删除学生
export function deleteStudent(id) {
return request({
url: '/infra/student/delete?id=' + id,
method: 'delete'
})
}
// 获得学生
export function getStudent(id) {
return request({
url: '/infra/student/get?id=' + id,
method: 'get'
})
}
// 获得学生分页
export function getStudentPage(params) {
return request({
url: '/infra/student/page',
method: 'get',
params
})
}
// 导出学生 Excel
export function exportStudentExcel(params) {
return request({
url: '/infra/student/export-excel',
method: 'get',
params,
responseType: 'blob'
})
}
// ==================== 子表(学生联系人) ====================
// 获得学生联系人分页
export function getStudentContactPage(params) {
return request({
url: '/infra/student/student-contact/page',
method: 'get',
params
})
}
// 新增学生联系人
export function createStudentContact(data) {
return request({
url: `/infra/student/student-contact/create`,
method: 'post',
data
})
}
// 修改学生联系人
export function updateStudentContact(data) {
return request({
url: `/infra/student/student-contact/update`,
method: 'post',
data
})
}
// 删除学生联系人
export function deleteStudentContact(id) {
return request({
url: `/infra/student/student-contact/delete?id=` + id,
method: 'delete'
})
}
// 获得学生联系人
export function getStudentContact(id) {
return request({
url: `/infra/student/student-contact/get?id=` + id,
method: 'get'
})
}
// ==================== 子表(学生班主任) ====================
// 获得学生班主任分页
export function getStudentTeacherPage(params) {
return request({
url: '/infra/student/student-teacher/page',
method: 'get',
params
})
}
// 新增学生班主任
export function createStudentTeacher(data) {
return request({
url: `/infra/student/student-teacher/create`,
method: 'post',
data
})
}
// 修改学生班主任
export function updateStudentTeacher(data) {
return request({
url: `/infra/student/student-teacher/update`,
method: 'post',
data
})
}
// 删除学生班主任
export function deleteStudentTeacher(id) {
return request({
url: `/infra/student/student-teacher/delete?id=` + id,
method: 'delete'
})
}
// 获得学生班主任
export function getStudentTeacher(id) {
return request({
url: `/infra/student/student-teacher/get?id=` + id,
method: 'get'
})
}

View File

@ -0,0 +1,17 @@
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "infra_student" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"description" varchar NOT NULL,
"birthday" varchar NOT NULL,
"sex" int NOT NULL,
"enabled" bit NOT NULL,
"avatar" varchar NOT NULL,
"video" varchar NOT NULL,
"memo" varchar NOT NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id")
) COMMENT '学生表';
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
DELETE FROM "infra_student";

View File

@ -0,0 +1,55 @@
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'学生管理', '', 2, 0, 888,
'student', '', 'infra/demo/index', 0, 'InfraStudent'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生查询', 'infra:student:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生创建', 'infra:student:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生更新', 'infra:student:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生删除', 'infra:student:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生导出', 'infra:student:export', 3, 5, @parentId,
'', '', '', 0
);

View File

@ -0,0 +1,159 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像" prop="avatar">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件" prop="video">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注" prop="memo">
<editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentContactForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
methods: {
/** 打开弹窗 */
open(id, studentId) {
this.dialogVisible = true;
this.reset();
const that = this;
this.formData.studentId = studentId;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudentContact(id).then(res=>{
that.formData = res.data;
that.dialogTitle = "修改学生联系人";
})
} finally {
this.formLoading = false;
}
}
this.dialogTitle = "新增学生联系人";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
let data = this.formData;
this.$refs["formRef"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudentContact(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.dialogVisible = false;
this.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudentContact(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.dialogVisible = false;
this.$emit('success');
});
});
}finally {
this.formLoading = false
}
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,134 @@
<template>
<div class="app-container">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['infra:student:create']">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="简介" align="center" prop="description" />
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar" />
<el-table-column label="附件" align="center" prop="video" />
<el-table-column label="备注" align="center" prop="memo" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<StudentContactForm ref="formRef" @success="getList" />
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import StudentContactForm from './StudentContactForm.vue'
export default {
name: "StudentContactList",
components: {
StudentContactForm
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 遮罩层
loading: true,
// 列表的数据
list: [],
// 列表的总页数
total: 0,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
studentId: undefined
}
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
this.queryParams.studentId = val;
if (val){
this.handleQuery();
}
},
immediate: true
}
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
const that = this;
StudentApi.getStudentContactPage(this.queryParams).then(response => {
that.list = response.data.list;
that.total = response.data.total;
});
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 添加/修改操作 */
openForm(id) {
if (!this.studentId) {
that.$modal.msgError('请选择一个学生');
return;
}
this.$refs["formRef"].open(id, this.studentId);
},
/** 删除按钮操作 */
handleDelete(row) {
const that = this;
try {
const id = row.id;
this.$modal.confirm('是否确认删除学生编号为"' + id + '"的数据项?').then(()=>{
return StudentApi.deleteStudentContact(id);
}).then(() => {
that.getList();
that.$modal.msgSuccess("删除成功");
}).catch(() => {});
} catch {}
},
}
};
</script>

View File

@ -0,0 +1,164 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
sex: [{ required: true, message: '性别不能为空', trigger: 'change' }],
enabled: [{ required: true, message: '是否有效不能为空', trigger: 'blur' }],
avatar: [{ required: true, message: '头像不能为空', trigger: 'blur' }],
video: [{ required: true, message: '附件不能为空', trigger: 'blur' }],
memo: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
},
};
},
methods: {
/** 打开弹窗 */
open(id) {
this.dialogVisible = true;
this.reset();
const that = this;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudent(id).then(res=>{
that.formData = res.data;
that.title = "修改学生";
})
} finally {
this.formLoading = false;
}
}
this.title = "新增学生";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
const that = this;
let data = this.formData;
let validate = false;
// 校验主表
this.getRef("formRef").validate(valid => {
validate = valid;
});
// 所有表单校验通过后方可提交
if (!validate) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudent(data).then(response => {
that.$modal.msgSuccess("修改成功");
that.dialogVisible = false;
that.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudent(data).then(response => {
that.$modal.msgSuccess("新增成功");
that.dialogVisible = false;
that.$emit('success');
});
}finally {
this.formLoading = false;
}
},
getRef(refName){
return this.$refs[refName];
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,159 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像" prop="avatar">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件" prop="video">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注" prop="memo">
<editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentTeacherForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
methods: {
/** 打开弹窗 */
open(id, studentId) {
this.dialogVisible = true;
this.reset();
const that = this;
this.formData.studentId = studentId;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudentTeacher(id).then(res=>{
that.formData = res.data;
that.dialogTitle = "修改学生班主任";
})
} finally {
this.formLoading = false;
}
}
this.dialogTitle = "新增学生班主任";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
let data = this.formData;
this.$refs["formRef"].validate(valid => {
if (!valid) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudentTeacher(data).then(response => {
this.$modal.msgSuccess("修改成功");
this.dialogVisible = false;
this.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudentTeacher(data).then(response => {
this.$modal.msgSuccess("新增成功");
this.dialogVisible = false;
this.$emit('success');
});
});
}finally {
this.formLoading = false
}
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,134 @@
<template>
<div class="app-container">
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['infra:student:create']">新增</el-button>
</el-col>
</el-row>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="简介" align="center" prop="description" />
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar" />
<el-table-column label="附件" align="center" prop="video" />
<el-table-column label="备注" align="center" prop="memo" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<StudentTeacherForm ref="formRef" @success="getList" />
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import StudentTeacherForm from './StudentTeacherForm.vue'
export default {
name: "StudentTeacherList",
components: {
StudentTeacherForm
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 遮罩层
loading: true,
// 列表的数据
list: [],
// 列表的总页数
total: 0,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
studentId: undefined
}
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
this.queryParams.studentId = val;
if (val){
this.handleQuery();
}
},
immediate: true
}
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
const that = this;
StudentApi.getStudentTeacherPage(this.queryParams).then(response => {
that.list = response.data.list;
that.total = response.data.total;
});
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 添加/修改操作 */
openForm(id) {
if (!this.studentId) {
that.$modal.msgError('请选择一个学生');
return;
}
this.$refs["formRef"].open(id, this.studentId);
},
/** 删除按钮操作 */
handleDelete(row) {
const that = this;
try {
const id = row.id;
this.$modal.confirm('是否确认删除学生编号为"' + id + '"的数据项?').then(()=>{
return StudentApi.deleteStudentTeacher(id);
}).then(() => {
that.getList();
that.$modal.msgSuccess("删除成功");
}).catch(() => {});
} catch {}
},
}
};
</script>

View File

@ -0,0 +1,241 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名字" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名字" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="queryParams.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-select v-model="queryParams.enabled" placeholder="请选择是否有效" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['infra:student:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:student:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:highlight-current-row="true"
:show-overflow-tooltip="true"
@current-change="handleCurrentChange"
>
<el-table-column label="编号" align="center" prop="id">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.id" />
</template>
</el-table-column>
<el-table-column label="名字" align="center" prop="name">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.name" />
</template>
</el-table-column>
<el-table-column label="简介" align="center" prop="description">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.description" />
</template>
</el-table-column>
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="附件" align="center" prop="video">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.video" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="memo">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.memo" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<StudentForm ref="formRef" @success="getList" />
<!-- 子表的列表 -->
<el-tabs v-model="subTabsName">
<el-tab-pane label="学生联系人" name="studentContact">
<StudentContactList v-if="currentRow.id" :student-id="currentRow.id" />
</el-tab-pane>
<el-tab-pane label="学生班主任" name="studentTeacher">
<StudentTeacherList v-if="currentRow.id" :student-id="currentRow.id" />
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo';
import StudentForm from './StudentForm.vue';
import StudentContactList from './components/StudentContactList.vue'
import StudentTeacherList from './components/StudentTeacherList.vue'
export default {
name: "Student",
components: {
StudentForm,
StudentContactList,
StudentTeacherList,
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 学生列表
list: [],
// 是否展开,默认全部展开
isExpandAll: true,
// 重新渲染表格状态
refreshTable: true,
// 选中行
currentRow: {},
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
birthday: null,
sex: null,
enabled: null,
createTime: [],
},
/** 子表的列表 */
subTabsName: 'studentContact'
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
StudentApi.getStudentPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
});
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 添加/修改操作 */
openForm(id) {
this.$refs["formRef"].open(id);
},
/** 删除按钮操作 */
handleDelete(row) {
const that = this;
try {
const id = row.id;
this.$modal.confirm('是否确认删除学生编号为"' + id + '"的数据项?').then(()=>{
return StudentApi.deleteStudent(id);
}).then(() => {
that.getList();
that.$modal.msgSuccess("删除成功");
}).catch(() => {});
} catch {}
},
/** 导出按钮操作 */
handleExport() {
const that = this;
try {
this.$modal.confirm('是否确认导出所有学生数据项?').then(() => {
that.exportLoading = true;
return StudentApi.exportStudentExcel(params);
}).then(response => {
that.$download.excel(response, '学生.xls');
});
} catch {
} finally {
that.exportLoading = false;
}
},
/** 选中行操作 */
handleCurrentChange(row) {
this.currentRow = row;
/** 子表的列表 */
this.subTabsName = 'studentContact';
},
}
};
</script>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,73 @@
[ {
"contentPath" : "java/InfraStudentPageReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
}, {
"contentPath" : "java/InfraStudentRespVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
}, {
"contentPath" : "java/InfraStudentSaveReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
}, {
"contentPath" : "java/InfraStudentController",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
}, {
"contentPath" : "java/InfraStudentDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
}, {
"contentPath" : "java/InfraStudentContactDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
}, {
"contentPath" : "java/InfraStudentTeacherDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
}, {
"contentPath" : "java/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
}, {
"contentPath" : "java/InfraStudentContactMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
}, {
"contentPath" : "java/InfraStudentTeacherMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
}, {
"contentPath" : "xml/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
}, {
"contentPath" : "java/InfraStudentServiceImpl",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
}, {
"contentPath" : "java/InfraStudentService",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
}, {
"contentPath" : "java/InfraStudentServiceImplTest",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
}, {
"contentPath" : "java/ErrorCodeConstants_手动操作",
"filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
}, {
"contentPath" : "sql/sql",
"filePath" : "sql/sql.sql"
}, {
"contentPath" : "sql/h2",
"filePath" : "sql/h2.sql"
}, {
"contentPath" : "vue/index",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
}, {
"contentPath" : "js/student",
"filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
}, {
"contentPath" : "vue/StudentForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
}, {
"contentPath" : "vue/StudentContactForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
}, {
"contentPath" : "vue/StudentTeacherForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
}, {
"contentPath" : "vue/StudentContactList",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactList.vue"
}, {
"contentPath" : "vue/StudentTeacherList",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherList.vue"
} ]

View File

@ -0,0 +1,3 @@
// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意请给“TODO 补充编号”设置一个错误码编号!!!
// ========== 学生 TODO 补充编号 ==========
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生联系人 DO
*
* @author 芋道源码
*/
@TableName("infra_student_contact")
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentContactDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生联系人 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
default List<InfraStudentContactDO> selectListByStudentId(Long studentId) {
return selectList(InfraStudentContactDO::getStudentId, studentId);
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentContactDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
@Tag(name = "管理后台 - 学生")
@RestController
@RequestMapping("/infra/student")
@Validated
public class InfraStudentController {
@Resource
private InfraStudentService studentService;
@PostMapping("/create")
@Operation(summary = "创建学生")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
return success(studentService.createStudent(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新学生")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
studentService.updateStudent(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除学生")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
studentService.deleteStudent(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得学生")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
InfraStudentDO student = studentService.getStudent(id);
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得学生分页")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出学生 Excel")
@PreAuthorize("@ss.hasPermission('infra:student:export')")
@OperateLog(type = EXPORT)
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
BeanUtils.toBean(list, InfraStudentRespVO.class));
}
// ==================== 子表(学生联系人) ====================
@GetMapping("/student-contact/list-by-student-id")
@Operation(summary = "获得学生联系人列表")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<List<InfraStudentContactDO>> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentContactListByStudentId(studentId));
}
// ==================== 子表(学生班主任) ====================
@GetMapping("/student-teacher/get-by-student-id")
@Operation(summary = "获得学生班主任")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentTeacherDO> getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentTeacherByStudentId(studentId));
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生 DO
*
* @author 芋道源码
*/
@TableName("infra_student")
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
/**
* 学生 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(InfraStudentDO::getId));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 学生分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InfraStudentPageReqVO extends PageParam {
@Schema(description = "名字", example = "芋头")
private String name;
@Schema(description = "出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", example = "1")
private Integer sex;
@Schema(description = "是否有效", example = "true")
private Boolean enabled;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@Schema(description = "管理后台 - 学生 Response VO")
@Data
@ExcelIgnoreUnannotated
public class InfraStudentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("编号")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@ExcelProperty("名字")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@ExcelProperty("简介")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@ExcelProperty("头像")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@ExcelProperty("附件")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@ExcelProperty("备注")
private String memo;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
@Data
public class InfraStudentSaveReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@NotEmpty(message = "名字不能为空")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@NotEmpty(message = "简介不能为空")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "出生日期不能为空")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "性别不能为空")
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否有效不能为空")
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@NotEmpty(message = "头像不能为空")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@NotEmpty(message = "附件不能为空")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@NotEmpty(message = "备注不能为空")
private String memo;
@Schema(description = "学生联系人列表")
private List<InfraStudentContactDO> studentContacts;
@Schema(description = "学生班主任")
private InfraStudentTeacherDO studentTeacher;
}

View File

@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.infra.service.demo;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 学生 Service 接口
*
* @author 芋道源码
*/
public interface InfraStudentService {
/**
* 创建学生
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
/**
* 更新学生
*
* @param updateReqVO 更新信息
*/
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
/**
* 删除学生
*
* @param id 编号
*/
void deleteStudent(Long id);
/**
* 获得学生
*
* @param id 编号
* @return 学生
*/
InfraStudentDO getStudent(Long id);
/**
* 获得学生分页
*
* @param pageReqVO 分页查询
* @return 学生分页
*/
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
// ==================== 子表(学生联系人) ====================
/**
* 获得学生联系人列表
*
* @param studentId 学生编号
* @return 学生联系人列表
*/
List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId);
// ==================== 子表(学生班主任) ====================
/**
* 获得学生班主任
*
* @param studentId 学生编号
* @return 学生班主任
*/
InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId);
}

View File

@ -0,0 +1,147 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* 学生 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class InfraStudentServiceImpl implements InfraStudentService {
@Resource
private InfraStudentMapper studentMapper;
@Resource
private InfraStudentContactMapper studentContactMapper;
@Resource
private InfraStudentTeacherMapper studentTeacherMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
// 插入
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
studentMapper.insert(student);
// 插入子表
createStudentContactList(student.getId(), createReqVO.getStudentContacts());
createStudentTeacher(student.getId(), createReqVO.getStudentTeacher());
// 返回
return student.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
// 校验存在
validateStudentExists(updateReqVO.getId());
// 更新
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
studentMapper.updateById(updateObj);
// 更新子表
updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts());
updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteStudent(Long id) {
// 校验存在
validateStudentExists(id);
// 删除
studentMapper.deleteById(id);
// 删除子表
deleteStudentContactByStudentId(id);
deleteStudentTeacherByStudentId(id);
}
private void validateStudentExists(Long id) {
if (studentMapper.selectById(id) == null) {
throw exception(STUDENT_NOT_EXISTS);
}
}
@Override
public InfraStudentDO getStudent(Long id) {
return studentMapper.selectById(id);
}
@Override
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
return studentMapper.selectPage(pageReqVO);
}
// ==================== 子表(学生联系人) ====================
@Override
public List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId) {
return studentContactMapper.selectListByStudentId(studentId);
}
private void createStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
list.forEach(o -> o.setStudentId(studentId));
studentContactMapper.insertBatch(list);
}
private void updateStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
deleteStudentContactByStudentId(studentId);
list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下1id 冲突2updateTime 不更新
createStudentContactList(studentId, list);
}
private void deleteStudentContactByStudentId(Long studentId) {
studentContactMapper.deleteByStudentId(studentId);
}
// ==================== 子表(学生班主任) ====================
@Override
public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) {
return studentTeacherMapper.selectByStudentId(studentId);
}
private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
if (studentTeacher == null) {
return;
}
studentTeacher.setStudentId(studentId);
studentTeacherMapper.insert(studentTeacher);
}
private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
if (studentTeacher == null) {
return;
}
studentTeacher.setStudentId(studentId);
studentTeacher.setUpdater(null).setUpdateTime(null); // 解决更新情况下updateTime 不更新
studentTeacherMapper.insertOrUpdate(studentTeacher);
}
private void deleteStudentTeacherByStudentId(Long studentId) {
studentTeacherMapper.deleteByStudentId(studentId);
}
}

View File

@ -0,0 +1,146 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link InfraStudentServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(InfraStudentServiceImpl.class)
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
@Resource
private InfraStudentServiceImpl studentService;
@Resource
private InfraStudentMapper studentMapper;
@Test
public void testCreateStudent_success() {
// 准备参数
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
// 调用
Long studentId = studentService.createStudent(createReqVO);
// 断言
assertNotNull(studentId);
// 校验记录的属性是否正确
InfraStudentDO student = studentMapper.selectById(studentId);
assertPojoEquals(createReqVO, student, "id");
}
@Test
public void testUpdateStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
o.setId(dbStudent.getId()); // 设置更新的 ID
});
// 调用
studentService.updateStudent(updateReqVO);
// 校验是否更新正确
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, student);
}
@Test
public void testUpdateStudent_notExists() {
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
}
@Test
public void testDeleteStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbStudent.getId();
// 调用
studentService.deleteStudent(id);
// 校验数据不存在了
assertNull(studentMapper.selectById(id));
}
@Test
public void testDeleteStudent_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
public void testGetStudentPage() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
o.setName(null);
o.setBirthday(null);
o.setSex(null);
o.setEnabled(null);
o.setCreateTime(null);
});
studentMapper.insert(dbStudent);
// 测试 name 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
// 测试 birthday 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
// 测试 sex 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
// 测试 enabled 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
// 测试 createTime 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
// 准备参数
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
reqVO.setName(null);
reqVO.setBirthday(null);
reqVO.setSex(null);
reqVO.setEnabled(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbStudent, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生班主任 DO
*
* @author 芋道源码
*/
@TableName("infra_student_teacher")
@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentTeacherDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生班主任 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> {
default InfraStudentTeacherDO selectByStudentId(Long studentId) {
return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentTeacherDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,74 @@
import request from '@/utils/request'
// 创建学生
export function createStudent(data) {
return request({
url: '/infra/student/create',
method: 'post',
data: data
})
}
// 更新学生
export function updateStudent(data) {
return request({
url: '/infra/student/update',
method: 'put',
data: data
})
}
// 删除学生
export function deleteStudent(id) {
return request({
url: '/infra/student/delete?id=' + id,
method: 'delete'
})
}
// 获得学生
export function getStudent(id) {
return request({
url: '/infra/student/get?id=' + id,
method: 'get'
})
}
// 获得学生分页
export function getStudentPage(params) {
return request({
url: '/infra/student/page',
method: 'get',
params
})
}
// 导出学生 Excel
export function exportStudentExcel(params) {
return request({
url: '/infra/student/export-excel',
method: 'get',
params,
responseType: 'blob'
})
}
// ==================== 子表(学生联系人) ====================
// 获得学生联系人列表
export function getStudentContactListByStudentId(studentId) {
return request({
url: `/infra/student/student-contact/list-by-student-id?studentId=` + studentId,
method: 'get'
})
}
// ==================== 子表(学生班主任) ====================
// 获得学生班主任
export function getStudentTeacherByStudentId(studentId) {
return request({
url: `/infra/student/student-teacher/get-by-student-id?studentId=` + studentId,
method: 'get'
})
}

View File

@ -0,0 +1,17 @@
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "infra_student" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"description" varchar NOT NULL,
"birthday" varchar NOT NULL,
"sex" int NOT NULL,
"enabled" bit NOT NULL,
"avatar" varchar NOT NULL,
"video" varchar NOT NULL,
"memo" varchar NOT NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id")
) COMMENT '学生表';
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
DELETE FROM "infra_student";

View File

@ -0,0 +1,55 @@
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'学生管理', '', 2, 0, 888,
'student', '', 'infra/demo/index', 0, 'InfraStudent'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生查询', 'infra:student:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生创建', 'infra:student:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生更新', 'infra:student:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生删除', 'infra:student:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生导出', 'infra:student:export', 3, 5, @parentId,
'', '', '', 0
);

View File

@ -0,0 +1,176 @@
<template>
<div class="app-container">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" width="100" />
<el-table-column label="名字" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
<el-input v-model="row.name" placeholder="请输入名字" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="简介" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.description`" :rules="formRules.description" class="mb-0px!">
<el-input v-model="row.description" type="textarea" placeholder="请输入简介" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="出生日期" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.birthday`" :rules="formRules.birthday" class="mb-0px!">
<el-date-picker clearable v-model="row.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="性别" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.sex`" :rules="formRules.sex" class="mb-0px!">
<el-select v-model="row.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="是否有效" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.enabled`" :rules="formRules.enabled" class="mb-0px!">
<el-radio-group v-model="row.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="头像" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.avatar`" :rules="formRules.avatar" class="mb-0px!">
<ImageUpload v-model="row.avatar"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="附件" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.video`" :rules="formRules.video" class="mb-0px!">
<FileUpload v-model="row.video"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="备注" min-width="400">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.memo`" :rules="formRules.memo" class="mb-0px!">
<Editor v-model="row.memo" :min-height="192"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template v-slot="{ $index }">
<el-link @click="handleDelete($index)">—</el-link>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3">
<el-button @click="handleAdd" round>+ 添加学生联系人</el-button>
</el-row>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentContactForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: [],
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
// 1. 重置表单
this.formData = []
// 2. val 非空,则加载数据
if (!val) {
return;
}
try {
this.formLoading = true;
const that = this;
StudentApi.getStudentContactListByStudentId(val).then(res=>{
that.formData = res.data;
})
} finally {
this.formLoading = false;
}
},
immediate: true
}
},
methods: {
/** 新增按钮操作 */
handleAdd() {
const row = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
}
row.studentId = this.studentId
this.formData.push(row)
},
/** 删除按钮操作 */
handleDelete(index) {
this.formData.splice(index, 1)
},
/** 表单校验 */
validate(){
return this.$refs["formRef"].validate()
},
/** 表单值 */
getData(){
return this.formData
}
}
};
</script>

View File

@ -0,0 +1,91 @@
<template>
<div class="app-container">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="简介" align="center" prop="description" />
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar" />
<el-table-column label="附件" align="center" prop="video" />
<el-table-column label="备注" align="center" prop="memo" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
export default {
name: "StudentContactList",
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 遮罩层
loading: true,
// 列表的数据
list: [],
};
},
created() {
this.getList();
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
this.queryParams.studentId = val;
if (val){
this.handleQuery();
}
},
immediate: true
}
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
const that = this;
StudentApi.getStudentContactListByStudentId(this.studentId).then(response=>{
that.list = response.data;
})
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
}
};
</script>

View File

@ -0,0 +1,221 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<!-- 子表的表单 -->
<el-tabs v-model="subTabsName">
<el-tab-pane label="学生联系人" name="studentContact">
<StudentContactForm ref="studentContactFormRef" :student-id="formData.id" />
</el-tab-pane>
<el-tab-pane label="学生班主任" name="studentTeacher">
<StudentTeacherForm ref="studentTeacherFormRef" :student-id="formData.id" />
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
import StudentContactForm from './components/StudentContactForm.vue'
import StudentTeacherForm from './components/StudentTeacherForm.vue'
export default {
name: "StudentForm",
components: {
ImageUpload,
FileUpload,
Editor,
StudentContactForm,
StudentTeacherForm,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
sex: [{ required: true, message: '性别不能为空', trigger: 'change' }],
enabled: [{ required: true, message: '是否有效不能为空', trigger: 'blur' }],
avatar: [{ required: true, message: '头像不能为空', trigger: 'blur' }],
video: [{ required: true, message: '附件不能为空', trigger: 'blur' }],
memo: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
},
/** 子表的表单 */
subTabsName: 'studentContact'
};
},
methods: {
/** 打开弹窗 */
open(id) {
this.dialogVisible = true;
this.reset();
const that = this;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudent(id).then(res=>{
that.formData = res.data;
that.title = "修改学生";
})
} finally {
this.formLoading = false;
}
}
this.title = "新增学生";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
const that = this;
let data = this.formData;
let validate = false;
// 校验主表
this.getRef("formRef").validate(valid => {
validate = valid;
});
// 校验子表
this.validateSubFrom01().then(() => {
// 全部校验通过-拼接子表的数据
// 拼接子表的数据
data.studentContacts = that.getRef('studentContactFormRef').getData();
data.studentTeacher = that.getRef('studentTeacherFormRef').getData();
}).catch((err) => {
validate = false;
that.subTabsName = err.replace("FormRef", ""); // 定位到没有校验通过的子表单
})
// 所有表单校验通过后方可提交
if (!validate) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudent(data).then(response => {
that.$modal.msgSuccess("修改成功");
that.dialogVisible = false;
that.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudent(data).then(response => {
that.$modal.msgSuccess("新增成功");
that.dialogVisible = false;
that.$emit('success');
});
}finally {
this.formLoading = false;
}
},
getRef(refName){
return this.$refs[refName];
},
/** 校验子表单 */
validateSubFrom(item) {
return new Promise((resolve, reject) => {
this.getRef(item).validate()
.then(() => {
resolve();
})
.catch(() => {
reject(item);
})
})
},
/** 校验所有子表单 */
validateSubFrom01() {
// 需要校验的表单 ref
const validFormRefArr = [
"studentContactFormRef",
"studentTeacherFormRef",
];
const validArr = []; // 校验
for (const item of validFormRefArr) {
validArr.push(this.validateSubFrom(item));
}
return new Promise((resolve, reject) => {
// 校验所有
Promise.all(validArr).then(() => {
resolve();
}).catch((err) => {
reject(err);
})
})
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,126 @@
<template>
<div class="app-container">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentTeacherForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: [],
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
// 1. 重置表单
this.formData = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
}
// 2. val 非空,则加载数据
if (!val) {
return;
}
try {
this.formLoading = true;
const that = this;
StudentApi.getStudentTeacherByStudentId(val).then(res=>{
const data = res.data;
if (!data) {
return
}
that.formData = data;
})
} finally {
this.formLoading = false;
}
},
immediate: true
}
},
methods: {
/** 表单校验 */
validate(){
return this.$refs["formRef"].validate()
},
/** 表单值 */
getData(){
return this.formData
}
}
};
</script>

View File

@ -0,0 +1,95 @@
<template>
<div class="app-container">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="简介" align="center" prop="description" />
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar" />
<el-table-column label="附件" align="center" prop="video" />
<el-table-column label="备注" align="center" prop="memo" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
export default {
name: "StudentTeacherList",
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 遮罩层
loading: true,
// 列表的数据
list: [],
};
},
created() {
this.getList();
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
this.queryParams.studentId = val;
if (val){
this.handleQuery();
}
},
immediate: true
}
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
const that = this;
StudentApi.getStudentTeacherByStudentId(this.studentId).then(response=>{
const data = response.data;
if (!data) {
return
}
that.list.push(data)
})
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
}
};
</script>

View File

@ -0,0 +1,230 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名字" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名字" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="queryParams.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-select v-model="queryParams.enabled" placeholder="请选择是否有效" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['infra:student:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:student:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<!-- 子表的列表 -->
<el-table-column type="expand">
<template #default="scope">
<el-tabs value="studentContact">
<el-tab-pane label="学生联系人" name="studentContact">
<StudentContactList :student-id="scope.row.id" />
</el-tab-pane>
<el-tab-pane label="学生班主任" name="studentTeacher">
<StudentTeacherList :student-id="scope.row.id" />
</el-tab-pane>
</el-tabs>
</template>
</el-table-column>
<el-table-column label="编号" align="center" prop="id">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.id" />
</template>
</el-table-column>
<el-table-column label="名字" align="center" prop="name">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.name" />
</template>
</el-table-column>
<el-table-column label="简介" align="center" prop="description">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.description" />
</template>
</el-table-column>
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="附件" align="center" prop="video">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.video" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="memo">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.memo" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<StudentForm ref="formRef" @success="getList" />
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo';
import StudentForm from './StudentForm.vue';
import StudentContactList from './components/StudentContactList.vue'
import StudentTeacherList from './components/StudentTeacherList.vue'
export default {
name: "Student",
components: {
StudentForm,
StudentContactList,
StudentTeacherList,
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 学生列表
list: [],
// 是否展开,默认全部展开
isExpandAll: true,
// 重新渲染表格状态
refreshTable: true,
// 选中行
currentRow: {},
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
birthday: null,
sex: null,
enabled: null,
createTime: [],
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
StudentApi.getStudentPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
});
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 添加/修改操作 */
openForm(id) {
this.$refs["formRef"].open(id);
},
/** 删除按钮操作 */
handleDelete(row) {
const that = this;
try {
const id = row.id;
this.$modal.confirm('是否确认删除学生编号为"' + id + '"的数据项?').then(()=>{
return StudentApi.deleteStudent(id);
}).then(() => {
that.getList();
that.$modal.msgSuccess("删除成功");
}).catch(() => {});
} catch {}
},
/** 导出按钮操作 */
handleExport() {
const that = this;
try {
this.$modal.confirm('是否确认导出所有学生数据项?').then(() => {
that.exportLoading = true;
return StudentApi.exportStudentExcel(params);
}).then(response => {
that.$download.excel(response, '学生.xls');
});
} catch {
} finally {
that.exportLoading = false;
}
},
}
};
</script>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,67 @@
[ {
"contentPath" : "java/InfraStudentPageReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
}, {
"contentPath" : "java/InfraStudentRespVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
}, {
"contentPath" : "java/InfraStudentSaveReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
}, {
"contentPath" : "java/InfraStudentController",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
}, {
"contentPath" : "java/InfraStudentDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
}, {
"contentPath" : "java/InfraStudentContactDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
}, {
"contentPath" : "java/InfraStudentTeacherDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
}, {
"contentPath" : "java/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
}, {
"contentPath" : "java/InfraStudentContactMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
}, {
"contentPath" : "java/InfraStudentTeacherMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
}, {
"contentPath" : "xml/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
}, {
"contentPath" : "java/InfraStudentServiceImpl",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
}, {
"contentPath" : "java/InfraStudentService",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
}, {
"contentPath" : "java/InfraStudentServiceImplTest",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
}, {
"contentPath" : "java/ErrorCodeConstants_手动操作",
"filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
}, {
"contentPath" : "sql/sql",
"filePath" : "sql/sql.sql"
}, {
"contentPath" : "sql/h2",
"filePath" : "sql/h2.sql"
}, {
"contentPath" : "vue/index",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
}, {
"contentPath" : "js/student",
"filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
}, {
"contentPath" : "vue/StudentForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
}, {
"contentPath" : "vue/StudentContactForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentContactForm.vue"
}, {
"contentPath" : "vue/StudentTeacherForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/components/StudentTeacherForm.vue"
} ]

View File

@ -0,0 +1,3 @@
// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意请给“TODO 补充编号”设置一个错误码编号!!!
// ========== 学生 TODO 补充编号 ==========
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生联系人 DO
*
* @author 芋道源码
*/
@TableName("infra_student_contact")
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentContactDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生联系人 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
default List<InfraStudentContactDO> selectListByStudentId(Long studentId) {
return selectList(InfraStudentContactDO::getStudentId, studentId);
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentContactDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
@Tag(name = "管理后台 - 学生")
@RestController
@RequestMapping("/infra/student")
@Validated
public class InfraStudentController {
@Resource
private InfraStudentService studentService;
@PostMapping("/create")
@Operation(summary = "创建学生")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
return success(studentService.createStudent(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新学生")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
studentService.updateStudent(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除学生")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
studentService.deleteStudent(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得学生")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
InfraStudentDO student = studentService.getStudent(id);
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得学生分页")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出学生 Excel")
@PreAuthorize("@ss.hasPermission('infra:student:export')")
@OperateLog(type = EXPORT)
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
BeanUtils.toBean(list, InfraStudentRespVO.class));
}
// ==================== 子表(学生联系人) ====================
@GetMapping("/student-contact/list-by-student-id")
@Operation(summary = "获得学生联系人列表")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<List<InfraStudentContactDO>> getStudentContactListByStudentId(@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentContactListByStudentId(studentId));
}
// ==================== 子表(学生班主任) ====================
@GetMapping("/student-teacher/get-by-student-id")
@Operation(summary = "获得学生班主任")
@Parameter(name = "studentId", description = "学生编号")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentTeacherDO> getStudentTeacherByStudentId(@RequestParam("studentId") Long studentId) {
return success(studentService.getStudentTeacherByStudentId(studentId));
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生 DO
*
* @author 芋道源码
*/
@TableName("infra_student")
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
/**
* 学生 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(InfraStudentDO::getId));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 学生分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InfraStudentPageReqVO extends PageParam {
@Schema(description = "名字", example = "芋头")
private String name;
@Schema(description = "出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", example = "1")
private Integer sex;
@Schema(description = "是否有效", example = "true")
private Boolean enabled;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@Schema(description = "管理后台 - 学生 Response VO")
@Data
@ExcelIgnoreUnannotated
public class InfraStudentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("编号")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@ExcelProperty("名字")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@ExcelProperty("简介")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@ExcelProperty("头像")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@ExcelProperty("附件")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@ExcelProperty("备注")
private String memo;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
@Data
public class InfraStudentSaveReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@NotEmpty(message = "名字不能为空")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@NotEmpty(message = "简介不能为空")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "出生日期不能为空")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "性别不能为空")
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否有效不能为空")
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@NotEmpty(message = "头像不能为空")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@NotEmpty(message = "附件不能为空")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@NotEmpty(message = "备注不能为空")
private String memo;
@Schema(description = "学生联系人列表")
private List<InfraStudentContactDO> studentContacts;
@Schema(description = "学生班主任")
private InfraStudentTeacherDO studentTeacher;
}

View File

@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.infra.service.demo;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 学生 Service 接口
*
* @author 芋道源码
*/
public interface InfraStudentService {
/**
* 创建学生
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
/**
* 更新学生
*
* @param updateReqVO 更新信息
*/
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
/**
* 删除学生
*
* @param id 编号
*/
void deleteStudent(Long id);
/**
* 获得学生
*
* @param id 编号
* @return 学生
*/
InfraStudentDO getStudent(Long id);
/**
* 获得学生分页
*
* @param pageReqVO 分页查询
* @return 学生分页
*/
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
// ==================== 子表(学生联系人) ====================
/**
* 获得学生联系人列表
*
* @param studentId 学生编号
* @return 学生联系人列表
*/
List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId);
// ==================== 子表(学生班主任) ====================
/**
* 获得学生班主任
*
* @param studentId 学生编号
* @return 学生班主任
*/
InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId);
}

View File

@ -0,0 +1,147 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* 学生 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class InfraStudentServiceImpl implements InfraStudentService {
@Resource
private InfraStudentMapper studentMapper;
@Resource
private InfraStudentContactMapper studentContactMapper;
@Resource
private InfraStudentTeacherMapper studentTeacherMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
// 插入
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
studentMapper.insert(student);
// 插入子表
createStudentContactList(student.getId(), createReqVO.getStudentContacts());
createStudentTeacher(student.getId(), createReqVO.getStudentTeacher());
// 返回
return student.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
// 校验存在
validateStudentExists(updateReqVO.getId());
// 更新
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
studentMapper.updateById(updateObj);
// 更新子表
updateStudentContactList(updateReqVO.getId(), updateReqVO.getStudentContacts());
updateStudentTeacher(updateReqVO.getId(), updateReqVO.getStudentTeacher());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteStudent(Long id) {
// 校验存在
validateStudentExists(id);
// 删除
studentMapper.deleteById(id);
// 删除子表
deleteStudentContactByStudentId(id);
deleteStudentTeacherByStudentId(id);
}
private void validateStudentExists(Long id) {
if (studentMapper.selectById(id) == null) {
throw exception(STUDENT_NOT_EXISTS);
}
}
@Override
public InfraStudentDO getStudent(Long id) {
return studentMapper.selectById(id);
}
@Override
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
return studentMapper.selectPage(pageReqVO);
}
// ==================== 子表(学生联系人) ====================
@Override
public List<InfraStudentContactDO> getStudentContactListByStudentId(Long studentId) {
return studentContactMapper.selectListByStudentId(studentId);
}
private void createStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
list.forEach(o -> o.setStudentId(studentId));
studentContactMapper.insertBatch(list);
}
private void updateStudentContactList(Long studentId, List<InfraStudentContactDO> list) {
deleteStudentContactByStudentId(studentId);
list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下1id 冲突2updateTime 不更新
createStudentContactList(studentId, list);
}
private void deleteStudentContactByStudentId(Long studentId) {
studentContactMapper.deleteByStudentId(studentId);
}
// ==================== 子表(学生班主任) ====================
@Override
public InfraStudentTeacherDO getStudentTeacherByStudentId(Long studentId) {
return studentTeacherMapper.selectByStudentId(studentId);
}
private void createStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
if (studentTeacher == null) {
return;
}
studentTeacher.setStudentId(studentId);
studentTeacherMapper.insert(studentTeacher);
}
private void updateStudentTeacher(Long studentId, InfraStudentTeacherDO studentTeacher) {
if (studentTeacher == null) {
return;
}
studentTeacher.setStudentId(studentId);
studentTeacher.setUpdater(null).setUpdateTime(null); // 解决更新情况下updateTime 不更新
studentTeacherMapper.insertOrUpdate(studentTeacher);
}
private void deleteStudentTeacherByStudentId(Long studentId) {
studentTeacherMapper.deleteByStudentId(studentId);
}
}

View File

@ -0,0 +1,146 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link InfraStudentServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(InfraStudentServiceImpl.class)
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
@Resource
private InfraStudentServiceImpl studentService;
@Resource
private InfraStudentMapper studentMapper;
@Test
public void testCreateStudent_success() {
// 准备参数
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
// 调用
Long studentId = studentService.createStudent(createReqVO);
// 断言
assertNotNull(studentId);
// 校验记录的属性是否正确
InfraStudentDO student = studentMapper.selectById(studentId);
assertPojoEquals(createReqVO, student, "id");
}
@Test
public void testUpdateStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
o.setId(dbStudent.getId()); // 设置更新的 ID
});
// 调用
studentService.updateStudent(updateReqVO);
// 校验是否更新正确
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, student);
}
@Test
public void testUpdateStudent_notExists() {
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
}
@Test
public void testDeleteStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbStudent.getId();
// 调用
studentService.deleteStudent(id);
// 校验数据不存在了
assertNull(studentMapper.selectById(id));
}
@Test
public void testDeleteStudent_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
public void testGetStudentPage() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
o.setName(null);
o.setBirthday(null);
o.setSex(null);
o.setEnabled(null);
o.setCreateTime(null);
});
studentMapper.insert(dbStudent);
// 测试 name 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
// 测试 birthday 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
// 测试 sex 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
// 测试 enabled 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
// 测试 createTime 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
// 准备参数
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
reqVO.setName(null);
reqVO.setBirthday(null);
reqVO.setSex(null);
reqVO.setEnabled(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbStudent, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生班主任 DO
*
* @author 芋道源码
*/
@TableName("infra_student_teacher")
@KeySequence("infra_student_teacher_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentTeacherDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 学生编号
*/
private Long studentId;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 学生班主任 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentTeacherMapper extends BaseMapperX<InfraStudentTeacherDO> {
default InfraStudentTeacherDO selectByStudentId(Long studentId) {
return selectOne(InfraStudentTeacherDO::getStudentId, studentId);
}
default int deleteByStudentId(Long studentId) {
return delete(InfraStudentTeacherDO::getStudentId, studentId);
}
}

View File

@ -0,0 +1,74 @@
import request from '@/utils/request'
// 创建学生
export function createStudent(data) {
return request({
url: '/infra/student/create',
method: 'post',
data: data
})
}
// 更新学生
export function updateStudent(data) {
return request({
url: '/infra/student/update',
method: 'put',
data: data
})
}
// 删除学生
export function deleteStudent(id) {
return request({
url: '/infra/student/delete?id=' + id,
method: 'delete'
})
}
// 获得学生
export function getStudent(id) {
return request({
url: '/infra/student/get?id=' + id,
method: 'get'
})
}
// 获得学生分页
export function getStudentPage(params) {
return request({
url: '/infra/student/page',
method: 'get',
params
})
}
// 导出学生 Excel
export function exportStudentExcel(params) {
return request({
url: '/infra/student/export-excel',
method: 'get',
params,
responseType: 'blob'
})
}
// ==================== 子表(学生联系人) ====================
// 获得学生联系人列表
export function getStudentContactListByStudentId(studentId) {
return request({
url: `/infra/student/student-contact/list-by-student-id?studentId=` + studentId,
method: 'get'
})
}
// ==================== 子表(学生班主任) ====================
// 获得学生班主任
export function getStudentTeacherByStudentId(studentId) {
return request({
url: `/infra/student/student-teacher/get-by-student-id?studentId=` + studentId,
method: 'get'
})
}

View File

@ -0,0 +1,17 @@
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "infra_student" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"description" varchar NOT NULL,
"birthday" varchar NOT NULL,
"sex" int NOT NULL,
"enabled" bit NOT NULL,
"avatar" varchar NOT NULL,
"video" varchar NOT NULL,
"memo" varchar NOT NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id")
) COMMENT '学生表';
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
DELETE FROM "infra_student";

View File

@ -0,0 +1,55 @@
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'学生管理', '', 2, 0, 888,
'student', '', 'infra/demo/index', 0, 'InfraStudent'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生查询', 'infra:student:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生创建', 'infra:student:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生更新', 'infra:student:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生删除', 'infra:student:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生导出', 'infra:student:export', 3, 5, @parentId,
'', '', '', 0
);

View File

@ -0,0 +1,176 @@
<template>
<div class="app-container">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" width="100" />
<el-table-column label="名字" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.name`" :rules="formRules.name" class="mb-0px!">
<el-input v-model="row.name" placeholder="请输入名字" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="简介" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.description`" :rules="formRules.description" class="mb-0px!">
<el-input v-model="row.description" type="textarea" placeholder="请输入简介" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="出生日期" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.birthday`" :rules="formRules.birthday" class="mb-0px!">
<el-date-picker clearable v-model="row.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="性别" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.sex`" :rules="formRules.sex" class="mb-0px!">
<el-select v-model="row.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="是否有效" min-width="150">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.enabled`" :rules="formRules.enabled" class="mb-0px!">
<el-radio-group v-model="row.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="头像" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.avatar`" :rules="formRules.avatar" class="mb-0px!">
<ImageUpload v-model="row.avatar"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="附件" min-width="200">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.video`" :rules="formRules.video" class="mb-0px!">
<FileUpload v-model="row.video"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="备注" min-width="400">
<template v-slot="{ row, $index }">
<el-form-item :prop="`${$index}.memo`" :rules="formRules.memo" class="mb-0px!">
<Editor v-model="row.memo" :min-height="192"/>
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template v-slot="{ $index }">
<el-link @click="handleDelete($index)">—</el-link>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3">
<el-button @click="handleAdd" round>+ 添加学生联系人</el-button>
</el-row>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentContactForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: [],
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
// 1. 重置表单
this.formData = []
// 2. val 非空,则加载数据
if (!val) {
return;
}
try {
this.formLoading = true;
const that = this;
StudentApi.getStudentContactListByStudentId(val).then(res=>{
that.formData = res.data;
})
} finally {
this.formLoading = false;
}
},
immediate: true
}
},
methods: {
/** 新增按钮操作 */
handleAdd() {
const row = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
}
row.studentId = this.studentId
this.formData.push(row)
},
/** 删除按钮操作 */
handleDelete(index) {
this.formData.splice(index, 1)
},
/** 表单校验 */
validate(){
return this.$refs["formRef"].validate()
},
/** 表单值 */
getData(){
return this.formData
}
}
};
</script>

View File

@ -0,0 +1,221 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<!-- 子表的表单 -->
<el-tabs v-model="subTabsName">
<el-tab-pane label="学生联系人" name="studentContact">
<StudentContactForm ref="studentContactFormRef" :student-id="formData.id" />
</el-tab-pane>
<el-tab-pane label="学生班主任" name="studentTeacher">
<StudentTeacherForm ref="studentTeacherFormRef" :student-id="formData.id" />
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
import StudentContactForm from './components/StudentContactForm.vue'
import StudentTeacherForm from './components/StudentTeacherForm.vue'
export default {
name: "StudentForm",
components: {
ImageUpload,
FileUpload,
Editor,
StudentContactForm,
StudentTeacherForm,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
sex: [{ required: true, message: '性别不能为空', trigger: 'change' }],
enabled: [{ required: true, message: '是否有效不能为空', trigger: 'blur' }],
avatar: [{ required: true, message: '头像不能为空', trigger: 'blur' }],
video: [{ required: true, message: '附件不能为空', trigger: 'blur' }],
memo: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
},
/** 子表的表单 */
subTabsName: 'studentContact'
};
},
methods: {
/** 打开弹窗 */
open(id) {
this.dialogVisible = true;
this.reset();
const that = this;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudent(id).then(res=>{
that.formData = res.data;
that.title = "修改学生";
})
} finally {
this.formLoading = false;
}
}
this.title = "新增学生";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
const that = this;
let data = this.formData;
let validate = false;
// 校验主表
this.getRef("formRef").validate(valid => {
validate = valid;
});
// 校验子表
this.validateSubFrom01().then(() => {
// 全部校验通过-拼接子表的数据
// 拼接子表的数据
data.studentContacts = that.getRef('studentContactFormRef').getData();
data.studentTeacher = that.getRef('studentTeacherFormRef').getData();
}).catch((err) => {
validate = false;
that.subTabsName = err.replace("FormRef", ""); // 定位到没有校验通过的子表单
})
// 所有表单校验通过后方可提交
if (!validate) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudent(data).then(response => {
that.$modal.msgSuccess("修改成功");
that.dialogVisible = false;
that.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudent(data).then(response => {
that.$modal.msgSuccess("新增成功");
that.dialogVisible = false;
that.$emit('success');
});
}finally {
this.formLoading = false;
}
},
getRef(refName){
return this.$refs[refName];
},
/** 校验子表单 */
validateSubFrom(item) {
return new Promise((resolve, reject) => {
this.getRef(item).validate()
.then(() => {
resolve();
})
.catch(() => {
reject(item);
})
})
},
/** 校验所有子表单 */
validateSubFrom01() {
// 需要校验的表单 ref
const validFormRefArr = [
"studentContactFormRef",
"studentTeacherFormRef",
];
const validArr = []; // 校验
for (const item of validFormRefArr) {
validArr.push(this.validateSubFrom(item));
}
return new Promise((resolve, reject) => {
// 校验所有
Promise.all(validArr).then(() => {
resolve();
}).catch((err) => {
reject(err);
})
})
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

View File

@ -0,0 +1,126 @@
<template>
<div class="app-container">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入简介" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentTeacherForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
props:[
'studentId'
],// 学生编号(主表的关联字段)
data() {
return {
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: [],
// 表单校验
formRules: {
studentId: [{ required: true, message: "学生编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "名字不能为空", trigger: "blur" }],
description: [{ required: true, message: "简介不能为空", trigger: "blur" }],
birthday: [{ required: true, message: "出生日期不能为空", trigger: "blur" }],
sex: [{ required: true, message: "性别不能为空", trigger: "change" }],
enabled: [{ required: true, message: "是否有效不能为空", trigger: "blur" }],
avatar: [{ required: true, message: "头像不能为空", trigger: "blur" }],
memo: [{ required: true, message: "备注不能为空", trigger: "blur" }],
},
};
},
watch:{/** 监听主表的关联字段的变化,加载对应的子表数据 */
studentId:{
handler(val) {
// 1. 重置表单
this.formData = {
id: undefined,
studentId: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
}
// 2. val 非空,则加载数据
if (!val) {
return;
}
try {
this.formLoading = true;
const that = this;
StudentApi.getStudentTeacherByStudentId(val).then(res=>{
const data = res.data;
if (!data) {
return
}
that.formData = data;
})
} finally {
this.formLoading = false;
}
},
immediate: true
}
},
methods: {
/** 表单校验 */
validate(){
return this.$refs["formRef"].validate()
},
/** 表单值 */
getData(){
return this.formData
}
}
};
</script>

View File

@ -0,0 +1,213 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="名字" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入名字" clearable @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="queryParams.birthday" type="date" value-format="yyyy-MM-dd" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="queryParams.sex" placeholder="请选择性别" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-select v-model="queryParams.enabled" placeholder="请选择是否有效" clearable size="small">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker v-model="queryParams.createTime" style="width: 240px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作工具栏 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
v-hasPermi="['infra:student:create']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
v-hasPermi="['infra:student:export']">导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.id" />
</template>
</el-table-column>
<el-table-column label="名字" align="center" prop="name">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.name" />
</template>
</el-table-column>
<el-table-column label="简介" align="center" prop="description">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.description" />
</template>
</el-table-column>
<el-table-column label="出生日期" align="center" prop="birthday" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.birthday) }}</span>
</template>
</el-table-column>
<el-table-column label="性别" align="center" prop="sex">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="scope.row.sex" />
</template>
</el-table-column>
<el-table-column label="是否有效" align="center" prop="enabled">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.enabled" />
</template>
</el-table-column>
<el-table-column label="头像" align="center" prop="avatar">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="附件" align="center" prop="video">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.video" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="memo">
<template v-slot="scope">
<dict-tag :type="DICT_TYPE.$dictType.toUpperCase()" :value="scope.row.memo" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template v-slot="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template v-slot="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
v-hasPermi="['infra:student:update']">修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['infra:student:delete']">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList"/>
<!-- 对话框(添加 / 修改) -->
<StudentForm ref="formRef" @success="getList" />
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo';
import StudentForm from './StudentForm.vue';
export default {
name: "Student",
components: {
StudentForm,
},
data() {
return {
// 遮罩层
loading: true,
// 导出遮罩层
exportLoading: false,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// 学生列表
list: [],
// 是否展开,默认全部展开
isExpandAll: true,
// 重新渲染表格状态
refreshTable: true,
// 选中行
currentRow: {},
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
name: null,
birthday: null,
sex: null,
enabled: null,
createTime: [],
},
};
},
created() {
this.getList();
},
methods: {
/** 查询列表 */
getList() {
try {
this.loading = true;
StudentApi.getStudentPage(this.queryParams).then(response => {
this.list = response.data.list;
this.total = response.data.total;
});
} finally {
this.loading = false;
}
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNo = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 添加/修改操作 */
openForm(id) {
this.$refs["formRef"].open(id);
},
/** 删除按钮操作 */
handleDelete(row) {
const that = this;
try {
const id = row.id;
this.$modal.confirm('是否确认删除学生编号为"' + id + '"的数据项?').then(()=>{
return StudentApi.deleteStudent(id);
}).then(() => {
that.getList();
that.$modal.msgSuccess("删除成功");
}).catch(() => {});
} catch {}
},
/** 导出按钮操作 */
handleExport() {
const that = this;
try {
this.$modal.confirm('是否确认导出所有学生数据项?').then(() => {
that.exportLoading = true;
return StudentApi.exportStudentExcel(params);
}).then(response => {
that.$download.excel(response, '学生.xls');
});
} catch {
} finally {
that.exportLoading = false;
}
},
}
};
</script>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -0,0 +1,49 @@
[ {
"contentPath" : "java/InfraStudentPageReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
}, {
"contentPath" : "java/InfraStudentRespVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
}, {
"contentPath" : "java/InfraStudentSaveReqVO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
}, {
"contentPath" : "java/InfraStudentController",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
}, {
"contentPath" : "java/InfraStudentDO",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
}, {
"contentPath" : "java/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
}, {
"contentPath" : "xml/InfraStudentMapper",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/resources/mapper/demo/InfraStudentMapper.xml"
}, {
"contentPath" : "java/InfraStudentServiceImpl",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
}, {
"contentPath" : "java/InfraStudentService",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
}, {
"contentPath" : "java/InfraStudentServiceImplTest",
"filePath" : "yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
}, {
"contentPath" : "java/ErrorCodeConstants_手动操作",
"filePath" : "yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
}, {
"contentPath" : "sql/sql",
"filePath" : "sql/sql.sql"
}, {
"contentPath" : "sql/h2",
"filePath" : "sql/h2.sql"
}, {
"contentPath" : "vue/index",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/index.vue"
}, {
"contentPath" : "js/student",
"filePath" : "yudao-ui-admin-vue2/src/api/infra/student.js"
}, {
"contentPath" : "vue/StudentForm",
"filePath" : "yudao-ui-admin-vue2/src/views/infra/demo/StudentForm.vue"
} ]

View File

@ -0,0 +1,3 @@
// TODO 待办:请将下面的错误码复制到 yudao-module-infra-api 模块的 ErrorCodeConstants 类中。注意请给“TODO 补充编号”设置一个错误码编号!!!
// ========== 学生 TODO 补充编号 ==========
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");

View File

@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Operation;
import javax.validation.constraints.*;
import javax.validation.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.IOException;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
@Tag(name = "管理后台 - 学生")
@RestController
@RequestMapping("/infra/student")
@Validated
public class InfraStudentController {
@Resource
private InfraStudentService studentService;
@PostMapping("/create")
@Operation(summary = "创建学生")
@PreAuthorize("@ss.hasPermission('infra:student:create')")
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
return success(studentService.createStudent(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新学生")
@PreAuthorize("@ss.hasPermission('infra:student:update')")
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
studentService.updateStudent(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除学生")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
studentService.deleteStudent(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得学生")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
InfraStudentDO student = studentService.getStudent(id);
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得学生分页")
@PreAuthorize("@ss.hasPermission('infra:student:query')")
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出学生 Excel")
@PreAuthorize("@ss.hasPermission('infra:student:export')")
@OperateLog(type = EXPORT)
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
BeanUtils.toBean(list, InfraStudentRespVO.class));
}
}

View File

@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 学生 DO
*
* @author 芋道源码
*/
@TableName("infra_student")
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class InfraStudentDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 名字
*/
private String name;
/**
* 简介
*/
private String description;
/**
* 出生日期
*/
private LocalDateTime birthday;
/**
* 性别
*
* 枚举 {@link TODO system_user_sex 对应的类}
*/
private Integer sex;
/**
* 是否有效
*
* 枚举 {@link TODO infra_boolean_string 对应的类}
*/
private Boolean enabled;
/**
* 头像
*/
private String avatar;
/**
* 附件
*/
private String video;
/**
* 备注
*/
private String memo;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
/**
* 学生 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(InfraStudentDO::getId));
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 学生分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class InfraStudentPageReqVO extends PageParam {
@Schema(description = "名字", example = "芋头")
private String name;
@Schema(description = "出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", example = "1")
private Integer sex;
@Schema(description = "是否有效", example = "true")
private Boolean enabled;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@Schema(description = "管理后台 - 学生 Response VO")
@Data
@ExcelIgnoreUnannotated
public class InfraStudentRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("编号")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@ExcelProperty("名字")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@ExcelProperty("简介")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("出生日期")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "性别", converter = DictConvert.class)
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@ExcelProperty("头像")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@ExcelProperty("附件")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@ExcelProperty("备注")
private String memo;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
@Data
public class InfraStudentSaveReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
@NotEmpty(message = "名字不能为空")
private String name;
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
@NotEmpty(message = "简介不能为空")
private String description;
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "出生日期不能为空")
private LocalDateTime birthday;
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "性别不能为空")
private Integer sex;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否有效不能为空")
private Boolean enabled;
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
@NotEmpty(message = "头像不能为空")
private String avatar;
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
@NotEmpty(message = "附件不能为空")
private String video;
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
@NotEmpty(message = "备注不能为空")
private String memo;
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.infra.service.demo;
import java.util.*;
import javax.validation.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
/**
* 学生 Service 接口
*
* @author 芋道源码
*/
public interface InfraStudentService {
/**
* 创建学生
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
/**
* 更新学生
*
* @param updateReqVO 更新信息
*/
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
/**
* 删除学生
*
* @param id 编号
*/
void deleteStudent(Long id);
/**
* 获得学生
*
* @param id 编号
* @return 学生
*/
InfraStudentDO getStudent(Long id);
/**
* 获得学生分页
*
* @param pageReqVO 分页查询
* @return 学生分页
*/
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* 学生 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class InfraStudentServiceImpl implements InfraStudentService {
@Resource
private InfraStudentMapper studentMapper;
@Override
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
// 插入
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
studentMapper.insert(student);
// 返回
return student.getId();
}
@Override
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
// 校验存在
validateStudentExists(updateReqVO.getId());
// 更新
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
studentMapper.updateById(updateObj);
}
@Override
public void deleteStudent(Long id) {
// 校验存在
validateStudentExists(id);
// 删除
studentMapper.deleteById(id);
}
private void validateStudentExists(Long id) {
if (studentMapper.selectById(id) == null) {
throw exception(STUDENT_NOT_EXISTS);
}
}
@Override
public InfraStudentDO getStudent(Long id) {
return studentMapper.selectById(id);
}
@Override
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
return studentMapper.selectPage(pageReqVO);
}
}

View File

@ -0,0 +1,146 @@
package cn.iocoder.yudao.module.infra.service.demo;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import javax.annotation.Resource;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import javax.annotation.Resource;
import org.springframework.context.annotation.Import;
import java.util.*;
import java.time.LocalDateTime;
import static cn.hutool.core.util.RandomUtil.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* {@link InfraStudentServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(InfraStudentServiceImpl.class)
public class InfraStudentServiceImplTest extends BaseDbUnitTest {
@Resource
private InfraStudentServiceImpl studentService;
@Resource
private InfraStudentMapper studentMapper;
@Test
public void testCreateStudent_success() {
// 准备参数
InfraStudentSaveReqVO createReqVO = randomPojo(InfraStudentSaveReqVO.class).setId(null);
// 调用
Long studentId = studentService.createStudent(createReqVO);
// 断言
assertNotNull(studentId);
// 校验记录的属性是否正确
InfraStudentDO student = studentMapper.selectById(studentId);
assertPojoEquals(createReqVO, student, "id");
}
@Test
public void testUpdateStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class, o -> {
o.setId(dbStudent.getId()); // 设置更新的 ID
});
// 调用
studentService.updateStudent(updateReqVO);
// 校验是否更新正确
InfraStudentDO student = studentMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, student);
}
@Test
public void testUpdateStudent_notExists() {
// 准备参数
InfraStudentSaveReqVO updateReqVO = randomPojo(InfraStudentSaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> studentService.updateStudent(updateReqVO), STUDENT_NOT_EXISTS);
}
@Test
public void testDeleteStudent_success() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class);
studentMapper.insert(dbStudent);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbStudent.getId();
// 调用
studentService.deleteStudent(id);
// 校验数据不存在了
assertNull(studentMapper.selectById(id));
}
@Test
public void testDeleteStudent_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> studentService.deleteStudent(id), STUDENT_NOT_EXISTS);
}
@Test
@Disabled // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
public void testGetStudentPage() {
// mock 数据
InfraStudentDO dbStudent = randomPojo(InfraStudentDO.class, o -> { // 等会查询到
o.setName(null);
o.setBirthday(null);
o.setSex(null);
o.setEnabled(null);
o.setCreateTime(null);
});
studentMapper.insert(dbStudent);
// 测试 name 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setName(null)));
// 测试 birthday 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setBirthday(null)));
// 测试 sex 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setSex(null)));
// 测试 enabled 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setEnabled(null)));
// 测试 createTime 不匹配
studentMapper.insert(cloneIgnoreId(dbStudent, o -> o.setCreateTime(null)));
// 准备参数
InfraStudentPageReqVO reqVO = new InfraStudentPageReqVO();
reqVO.setName(null);
reqVO.setBirthday(null);
reqVO.setSex(null);
reqVO.setEnabled(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbStudent, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,53 @@
import request from '@/utils/request'
// 创建学生
export function createStudent(data) {
return request({
url: '/infra/student/create',
method: 'post',
data: data
})
}
// 更新学生
export function updateStudent(data) {
return request({
url: '/infra/student/update',
method: 'put',
data: data
})
}
// 删除学生
export function deleteStudent(id) {
return request({
url: '/infra/student/delete?id=' + id,
method: 'delete'
})
}
// 获得学生
export function getStudent(id) {
return request({
url: '/infra/student/get?id=' + id,
method: 'get'
})
}
// 获得学生分页
export function getStudentPage(params) {
return request({
url: '/infra/student/page',
method: 'get',
params
})
}
// 导出学生 Excel
export function exportStudentExcel(params) {
return request({
url: '/infra/student/export-excel',
method: 'get',
params,
responseType: 'blob'
})
}

View File

@ -0,0 +1,17 @@
-- 将该建表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/create_tables.sql 文件里
CREATE TABLE IF NOT EXISTS "infra_student" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"description" varchar NOT NULL,
"birthday" varchar NOT NULL,
"sex" int NOT NULL,
"enabled" bit NOT NULL,
"avatar" varchar NOT NULL,
"video" varchar NOT NULL,
"memo" varchar NOT NULL,
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("id")
) COMMENT '学生表';
-- 将该删表 SQL 语句,添加到 yudao-module-infra-biz 模块的 test/resources/sql/clean.sql 文件里
DELETE FROM "infra_student";

View File

@ -0,0 +1,55 @@
-- 菜单 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status, component_name
)
VALUES (
'学生管理', '', 2, 0, 888,
'student', '', 'infra/demo/index', 0, 'InfraStudent'
);
-- 按钮父菜单ID
-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话,需要手动修改 @parentId 的部分的代码
SELECT @parentId := LAST_INSERT_ID();
-- 按钮 SQL
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生查询', 'infra:student:query', 3, 1, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生创建', 'infra:student:create', 3, 2, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生更新', 'infra:student:update', 3, 3, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生删除', 'infra:student:delete', 3, 4, @parentId,
'', '', '', 0
);
INSERT INTO system_menu(
name, permission, type, sort, parent_id,
path, icon, component, status
)
VALUES (
'学生导出', 'infra:student:export', 3, 5, @parentId,
'', '', '', 0
);

View File

@ -0,0 +1,164 @@
<template>
<div class="app-container">
<!-- 对话框(添加 / 修改) -->
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag append-to-body>
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="100px">
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="简介" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="出生日期" prop="birthday">
<el-date-picker clearable v-model="formData.birthday" type="date" value-format="timestamp" placeholder="选择出生日期" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<el-select v-model="formData.sex" placeholder="请选择性别">
<el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_USER_SEX)"
:key="dict.value" :label="dict.label" :value="parseInt(dict.value)" />
</el-select>
</el-form-item>
<el-form-item label="是否有效" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio v-for="dict in this.getDictDatas(DICT_TYPE.INFRA_BOOLEAN_STRING)"
:key="dict.value" :label="dict.value">{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像">
<ImageUpload v-model="formData.avatar"/>
</el-form-item>
<el-form-item label="附件">
<FileUpload v-model="formData.video"/>
</el-form-item>
<el-form-item label="备注">
<Editor v-model="formData.memo" :min-height="192"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as StudentApi from '@/api/infra/demo'
import ImageUpload from '@/components/ImageUpload';
import FileUpload from '@/components/FileUpload';
import Editor from '@/components/Editor';
export default {
name: "StudentForm",
components: {
ImageUpload,
FileUpload,
Editor,
},
data() {
return {
// 弹出层标题
dialogTitle: "",
// 是否显示弹出层
dialogVisible: false,
// 表单的加载中1修改时的数据加载2提交的按钮禁用
formLoading: false,
// 表单参数
formData: {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
},
// 表单校验
formRules: {
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
description: [{ required: true, message: '简介不能为空', trigger: 'blur' }],
birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],
sex: [{ required: true, message: '性别不能为空', trigger: 'change' }],
enabled: [{ required: true, message: '是否有效不能为空', trigger: 'blur' }],
avatar: [{ required: true, message: '头像不能为空', trigger: 'blur' }],
video: [{ required: true, message: '附件不能为空', trigger: 'blur' }],
memo: [{ required: true, message: '备注不能为空', trigger: 'blur' }],
},
};
},
methods: {
/** 打开弹窗 */
open(id) {
this.dialogVisible = true;
this.reset();
const that = this;
// 修改时,设置数据
if (id) {
this.formLoading = true;
try {
StudentApi.getStudent(id).then(res=>{
that.formData = res.data;
that.title = "修改学生";
})
} finally {
this.formLoading = false;
}
}
this.title = "新增学生";
},
/** 提交按钮 */
submitForm() {
this.formLoading = true;
try {
const that = this;
let data = this.formData;
let validate = false;
// 校验主表
this.getRef("formRef").validate(valid => {
validate = valid;
});
// 所有表单校验通过后方可提交
if (!validate) {
return;
}
// 修改的提交
if (data.id) {
StudentApi.updateStudent(data).then(response => {
that.$modal.msgSuccess("修改成功");
that.dialogVisible = false;
that.$emit('success');
});
return;
}
// 添加的提交
StudentApi.createStudent(data).then(response => {
that.$modal.msgSuccess("新增成功");
that.dialogVisible = false;
that.$emit('success');
});
}finally {
this.formLoading = false;
}
},
getRef(refName){
return this.$refs[refName];
},
/** 表单重置 */
reset() {
this.formData = {
id: undefined,
name: undefined,
description: undefined,
birthday: undefined,
sex: undefined,
enabled: undefined,
avatar: undefined,
video: undefined,
memo: undefined,
};
this.resetForm("formRef");
},
}
};
</script>

Some files were not shown because too many files have changed in this diff Show More