PDManer数据库建模工具一键生成前后端代码
PDManer
PDManer是一款开源的数据库建模工具,它可以帮助开发人员快速建立和维护数据库模型。PDManer支持多种数据库管理系统,包括MySQL、Oracle、Microsoft SQL Server等。使用PDManer,用户可以通过可视化界面轻松地设计数据库模型,并且可以自动生成SQL脚本和数据库表结构。此外,PDManer还支持代码自动生成,非常适合快速开发。
官网:http://www.pdmaas.cn/home
Gitee:https://gitee.com/robergroup/pdmaner
相关文章:OSChina | 知乎
功能介绍
本文主要讲使用PDManer进行快速开发,一键生成代码,主键介绍一下涉及到的功能模块
资源下载
安装包已经上传到CSDN,也可在Gitee上下载源码自行打包
[PDManer-win-4.5.1下载]
[代码生成器配置]
项目基础框架
以上功能简单了解完后,来介绍一下项目的基础框架
后端框架:
框架 | 描述 | 文档 |
---|---|---|
SpringBoot | 基础框架 | |
Mybatis | 数据库连接 | |
Mybatis-Plus | 增强 | https://baomidou.com/ |
Mybatis-Plus-Join | 用于联表查询 | https://ylctmh.com/ |
前端框架:
框架 | 描述 | 文档 |
---|---|---|
Vue 3 | 基础框架 | |
Vue-Router | 路由库 | |
Pinia | 状态管理 | |
Axios | 网络请求库 | |
Element-Plus | 组件库 | https://element-plus.gitee.io/zh-CN/ |
操作及注意事项
- 数据库代码:直接复制执行就可以了
- 代码生成:如下图配置文件路径及基本参数即可在指定目录生成文件
重点来了!!!
不编了,直接贴上我的配置,如有遗漏可以私信我,补上代码
更新日志
2023-03-31 初版模板发布
2023-04-12 增加逻辑删除字段标识;增加Vue表格及表单屏蔽字段设置;增加表单回车事件
Java代码生成模板
entity.java
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
var exclude = [];
var autoInsert = ['created_by','created_time'];
var autoUpdate = ['updated_by','updated_time'];
var version="version";
var deleted='deleted';
var extend="";
}}package {{=pkgName}}.entity;
$blankline
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
$blankline
/**
* {{=it.entity.defName}}{{=it.entity.comment}}
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("{{=it.entity.defKey}}")
public class {{= beanClass}}Entity {{? extend != ""}}extends {{=extend}} {{?}}{
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
/**
* {{=it.func.join(field.defName,field.comment,'\n * ')}}
*/
{{? field.defKey == version}}@Version{{?}}{{? field.defKey == deleted}}@TableLogic{{?}}
{{? field.primaryKey }}@TableId{{??}}@TableField{{?}}(value = "{{=field.defKey}}"{{? field.primaryKey }}, type = IdType.ASSIGN_UUID{{?}}{{? autoInsert.indexOf(field.defKey) != -1}}, fill = FieldFill.INSERT{{?}}{{? autoUpdate.indexOf(field.defKey) != -1}},fill = FieldFill.UPDATE{{?}})
{{? field.type=="LocalDateTime"}}@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8"){{?}}
private {{? field.type == 'ENUM'}}{{=beanClass}}{{=it.func.camel(field.defKey,true)}}Enum{{??}}{{=field.type}}{{?}} {{=it.func.camel(field.defKey,false)}};{{?}}{{~}}
$blankline
}
controller.java
// controller.java
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
var pkVarName = "undefinedId";
it.entity.fields.forEach(function(field){
if(field.primaryKey){
pkVarName = it.func.camel(field.defKey,true);
return;
}
});
}}package {{=pkgName}}.controller;
$blankline
import {{=pkgName}}.entity.{{=beanClass}}Entity;
import {{=pkgName}}.service.{{=beanClass}}Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
$blankline
/**
* {{=it.entity.defName}}{{=it.entity.comment}}API接口
*/
@RestController
@RequestMapping("/{{=appName}}/{{=it.func.camel(it.entity.defKey,false)}}")
public class {{=beanClass}}Controller {
$blankline
@Resource
private {{=beanClass}}Service {{=beanName}}Service;
$blankline
/**
* 根据主键获取{{=it.entity.defName}}
*/
@PostMapping("get{{=beanClass}}")
public Res<{{=beanClass}}Entity> get{{=beanClass}}(@RequestBody {{=beanClass}}Entity entity) {
entity = {{=beanName}}Service.getById(entity.get{{=pkVarName}}());
return Res.success(entity);
}
$blankline
/**
* 分页获取{{=it.entity.defName}}
*/
@PostMapping("get{{=beanClass}}Page")
public Res<IPage<{{=beanClass}}DTO>> get{{=beanClass}}Page(@RequestBody BaseQuery<{{=beanClass}}DTO> request) {
MPJLambdaWrapper<{{=beanClass}}Entity> wrapper = new MPJLambdaWrapper<{{=beanClass}}Entity>();
IPage<{{=beanClass}}DTO> page = {{=beanName}}Service.selectJoinListPage(request.getPage(), {{=beanClass}}DTO.class, wrapper);
return Res.success(page);
}
$blankline
/**
* 获取全部{{=it.entity.defName}}
*/
@PostMapping("get{{=beanClass}}List")
public Res<List<{{=beanClass}}Entity>> get{{=beanClass}}List() {
List<{{=beanClass}}Entity> list = {{=beanName}}Service.list();
return Res.success(list);
}
$blankline
/**
* 新增{{=it.entity.defName}}
*/
@PostMapping("save{{=beanClass}}")
public Res<{{=beanClass}}Entity> save{{=beanClass}}(@RequestBody {{=beanClass}}Entity entity) {
{{=beanName}}Service.save(entity);
return Res.success(entity);
}
$blankline
/**
* 更新{{=it.entity.defName}}
*/
@PostMapping("update{{=beanClass}}")
public Res<Object> update{{=beanClass}}(@RequestBody {{=beanClass}}Entity entity) {
boolean res = {{=beanName}}Service.updateById(entity);
return Res.success(res);
}
$blankline
/**
* 删除{{=it.entity.defName}}
*/
@PostMapping("delete{{=beanClass}}")
public Res<Object> delete{{=beanClass}}(@RequestBody {{=beanClass}}Entity entity) {
boolean res = {{=beanName}}Service.removeById(entity);
return Res.success(res);
}
$blankline
/**
* 批量新增{{=it.entity.defName}}
*/
@PostMapping("save{{=beanClass}}Batch")
public Res<Object> save{{=beanClass}}Batch(@RequestBody BaseBatch<{{=beanClass}}Entity> batch) {
boolean res = {{=beanName}}Service.saveBatch(batch.getList());
return Res.success(res);
}
$blankline
/**
* 批量更新{{=it.entity.defName}}
*/
@PostMapping("update{{=beanClass}}Batch")
public Res<Object> update{{=beanClass}}Batch(@RequestBody BaseBatch<{{=beanClass}}Entity> batch) {
boolean res = {{=beanName}}Service.updateBatchById(batch.getList());
return Res.success(res);
}
$blankline
/**
* 批量删除{{=it.entity.defName}}
*/
@PostMapping("delete{{=beanClass}}Batch")
public Res<Object> delete{{=beanClass}}Batch(@RequestBody BaseBatch<{{=beanClass}}Entity> batch) {
boolean res = {{=beanName}}Service.removeBatchByIds(batch.getList());
return Res.success(res);
}
$blankline
}
mapper.java
// mapper.java
{{ var pkgName = it.entity.env.base.nameSpace;
var beanClass = it.func.camel(it.entity.defKey,true);
}}package {{=pkgName}}.mapper;
$blankline
import com.github.yulichang.base.MPJBaseMapper;
import {{=pkgName}}.entity.{{=beanClass}}Entity;
import org.apache.ibatis.annotations.Mapper;
$blankline
/**
* {{=it.entity.defName}}({{=it.entity.defKey}})表数据库访问层
*/
@Mapper
public interface {{=beanClass}}Mapper extends MPJBaseMapper<{{=beanClass}}Entity> {
}
service.java
// service.java
{{ var pkgName = it.entity.env.base.nameSpace;
var beanClass = it.func.camel(it.entity.defKey,true);
}}package {{=pkgName}}.service;
$blankline
import com.github.yulichang.base.MPJBaseService;
import {{=pkgName}}.entity.{{=beanClass}}Entity;
$blankline
/**
* {{=it.entity.defName}}({{=it.entity.defKey}})表服务接口
*/
public interface {{=beanClass}}Service extends MPJBaseService<{{=beanClass}}Entity> {
}
impl.java
// impl.java
{{ var pkgName = it.entity.env.base.nameSpace;
var beanClass = it.func.camel(it.entity.defKey,true);
}}package {{=pkgName}}.service.impl;
$blankline
import com.github.yulichang.base.MPJBaseServiceImpl;
import {{=pkgName}}.entity.{{=beanClass}}Entity;
import {{=pkgName}}.mapper.{{=beanClass}}Mapper;
import {{=pkgName}}.service.{{=beanClass}}Service;
import org.springframework.stereotype.Service;
$blankline
/**
* {{=it.entity.defName}}({{=it.entity.defKey}})表服务实现类
*/
@Service
public class {{=beanClass}}ServiceImpl extends MPJBaseServiceImpl<{{=beanClass}}Mapper, {{=beanClass}}Entity> implements {{=beanClass}}Service {
}
dto.java
// dto.java
{{ var pkgName = it.entity.env.base.nameSpace;
var beanClass = it.func.camel(it.entity.defKey,true);
}}package {{=pkgName}}.dto;
$blankline
import {{=pkgName}}.entity.{{=beanClass}}Entity;
import lombok.Data;
$blankline
/**
* {{=it.entity.defName}}({{=it.entity.defKey}})数据传输对象
*/
@Data
public class {{=beanClass}}DTO extends {{=beanClass}}Entity {
}
Vue代码生成模板
api.ts
// api.ts
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
var pkVarName = "undefinedId";
var pkDataType = "string";
it.entity.fields.forEach(function(field){
if(field.primaryKey){
pkVarName = it.func.camel(field.defKey,false);
pkDataType = field.type;
return;
}
});
}}import { createAxios } from '@/utils/axios'
$blankline
// 接口地址
export const uri = '/{{=appName}}/{{=it.func.camel(it.entity.defKey,false)}}'
$blankline
// {{=it.entity.defName}}{{=it.entity.comment}}类型定义
export interface {{=beanClass}} {
{{~it.entity.fields:field:index}}
{{=it.func.camel(field.defKey,false)}}?: {{=field.type}}, // {{=field.defName}}{{~}}
}
$blankline
// 根据主键获取{{=it.entity.defName}}
export const get{{=beanClass}} = ( {{=pkVarName}}: {{=pkDataType}} ) => createAxios({ data: { {{=pkVarName}} }, url: uri + '/get{{=beanClass}}' })
$blankline
// 分页获取{{=it.entity.defName}}
export const get{{=beanClass}}Page = (query: any = {}, page: any = {}) => createAxios({data:{ query, page }, url: uri + '/get{{=beanClass}}Page' })
$blankline
// 获取全部{{=it.entity.defName}}
export const get{{=beanClass}}List = () => createAxios({ url: uri + '/get{{=beanClass}}List' })
$blankline
// 新增{{=it.entity.defName}}
export const save{{=beanClass}} = (data: {{=beanClass}}) => createAxios({ data, url: uri + '/save{{=beanClass}}' }, { successMsg: '新增成功' })
$blankline
// 更新{{=it.entity.defName}}
export const update{{=beanClass}} = (data: {{=beanClass}}) => createAxios({ data, url: uri + '/update{{=beanClass}}' },{ successMsg: '修改成功!' })
$blankline
// 删除{{=it.entity.defName}}
export const delete{{=beanClass}} = (data: {{=beanClass}}) => createAxios({ data, url: uri + '/delete{{=beanClass}}' },{ successMsg: '删除成功!' })
$blankline
// 批量新增{{=it.entity.defName}}
export const save{{=beanClass}}Batch = (list: {{=beanClass}}[]) => createAxios({ data : { list }, url: uri + '/save{{=beanClass}}Batch' }, { successMsg: '新增成功' })
$blankline
// 批量更新{{=it.entity.defName}}
export const update{{=beanClass}}Batch = (list: {{=beanClass}}[]) => createAxios({ data : { list }, url: uri + '/update{{=beanClass}}Batch' },{ successMsg: '修改成功!' })
$blankline
// 批量删除{{=it.entity.defName}}
export const delete{{=beanClass}}Batch = (list: {{=beanClass}}[]) => createAxios({ data : { list }, url: uri + '/delete{{=beanClass}}Batch' },{ successMsg: '删除成功!' })
$blankline
Table.vue
// table.vue
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
var exclude = ['uid','created_by','created_time','updated_by','updated_time','deleted','sort'];
var pkVarName = "undefinedId";
it.entity.fields.forEach(function(field){
if(field.primaryKey){
pkVarName = it.func.camel(field.defKey,false);
return;
}
});
}}<script setup lang="ts">
import {
{{=beanClass}},
get{{=beanClass}},
get{{=beanClass}}Page,
save{{=beanClass}},
update{{=beanClass}},
delete{{=beanClass}}Batch,
} from '@/apis/{{=appName}}/{{=beanClass}}'
import {{=beanClass}}Form from './{{=beanClass}}Form.vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { ElTable } from 'element-plus'
import pinyin from 'pinyin-match'
import { replaceKeyword } from '@/utils/common'
import { useLayout } from '@/stores/layout'
$blankline
// 主题样式
const layout = useLayout()
$blankline
const emits = defineEmits([''])
$blankline
const formRef = ref<InstanceType<typeof {{=beanClass}}Form>>()
const tableRef = ref<InstanceType<typeof ElTable>>()
// 加载状态
const loading = ref<boolean>(false)
// 表格数据定义
const data = ref({
current: 1,
size: 50,
total: 0,
records: [] as {{=beanClass}}[],
})
// 查询表单
const query = reactive({
search: ''
})
// 弹出表单配置
const dialog = reactive({
show: false,
title: '',
form: {} as {{=beanClass}},
})
$blankline
// 表格数据过滤
const tableData = computed(() =>
data.value.records.filter(
(e) =>
!query.search ||
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
{{? field.type == 'string'}}(e.{{=it.func.camel(field.defKey,false)}} && pinyin.match(e.{{=it.func.camel(field.defKey,false)}}, query.search)){{? index < it.entity.fields.length - 1}} ||{{?}}{{?}}{{?}}{{~}}
)
)
$blankline
// 获取表格数据
const getData = () => {
loading.value = true
get{{=beanClass}}Page(query, { current: data.value.current, size: data.value.size }).then((res) => {
setTimeout(() => { loading.value = false }, 500)
data.value = res.data
})
}
$blankline
// 新增按钮事件
const handleAdd = () => {
dialog.show = true
dialog.title = '新增{{=it.entity.defName}}'
dialog.form = {
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
{{=it.func.camel(field.defKey,false)}} : {{=field.defaultValue || "''"}} ,{{?}}{{~}}
}
}
$blankline
// 删除按钮事件
const handleDel = () => {
const list = tableRef.value?.getSelectionRows()
loading.value = true
delete{{=beanClass}}Batch(list).then(() => {
getData()
})
}
$blankline
// 查询事件
const handleQuery = () => {
getData()
}
$blankline
// 表格行双击事件
const handleTableDbClick = (row: {{=beanClass}}) => {
dialog.title = '{{=it.entity.defName}}'
if (row.{{=pkVarName}}) {
loading.value = true
get{{=beanClass}}(row.{{=pkVarName}}).then((res) => {
dialog.form = res.data
setTimeout(() => {
loading.value = false
dialog.show = true
}, 500)
})
}
}
$blankline
// 弹出表单提交事件
const dialogConfirm = async () => {
await formRef.value?.formRef?.validate((valid) => {
if (valid) {
loading.value = true
const fun = dialog.form.id ? update{{=beanClass}} : save{{=beanClass}}
fun(dialog.form).then(() => { getData() }).finally(() => { setTimeout(() => { dialog.show = false }, 500) })
}
})
}
$blankline
onMounted(() => {
getData()
})
</script>
$blankline
<template>
<el-card>
<template #header>
<el-row justify="space-between">
<!-- 快捷按钮 -->
<el-col :span="6">
<el-button-group>
<table-button :loading="loading" type="add" @confirm="handleAdd" />
<table-button :loading="loading" type="del" tip @confirm="handleDel" />
<table-button :loading="loading" type="refresh" mini @confirm="getData" />
</el-button-group>
</el-col>
<!-- 查询表单 -->
<el-col :span="18">
<el-form inline :model="query" class="query-form">
<el-form-item>
<el-input v-model="query.search" placeholder="筛选" clearable prefix-icon="el-icon-Search" />
</el-form-item>
<el-form-item>
<el-button :loading="loading" icon="el-icon-Search" circle title="查询" @click="handleQuery" />
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
$blankline
<!-- 表格主体 -->
<el-table ref="tableRef" v-loading="loading" class="app-table" row-key="{{=pkVarName}}" :data="tableData" :height="layout.getMainHeight(77 + 40 + 20 + 32).height" @row-dblclick="handleTableDbClick">
<el-table-column type="selection" align="center" />
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
<el-table-column prop="{{=it.func.camel(field.defKey,false)}}" label="{{=field.defName}}" show-overflow-tooltip align="center">
<template #default="scope">
<span :title="scope.row.{{=it.func.camel(field.defKey,false)}}" v-html="replaceKeyword(scope.row.{{=it.func.camel(field.defKey,false)}}!, query.search)" />
</template>
</el-table-column>{{?}}{{~}}
</el-table>
$blankline
<el-pagination v-model:page-size="data.size" v-model:current-page="data.current" :total="data.total" layout="sizes, prev, pager, next, jumper, ->, total, slot" @size-change="getData" @current-change="getData" />
</el-card>
$blankline
<!-- 表单弹出框 -->
<el-dialog v-model="dialog.show" :title="dialog.title" width="30%" draggable align-center destroy-on-close @close="dialog.form = {}">
<el-scrollbar max-height="calc(100vh - 180px - 120px)">
<{{=beanClass}}Form ref="formRef" v-model="dialog.form" @confirm="dialogConfirm" />
</el-scrollbar>
<template #footer>
<el-button @click="formRef?.formRef?.resetFields()">重置</el-button>
<el-button v-if="dialog.form.{{=pkVarName}}" type="primary" @click="dialogConfirm">修改</el-button>
<el-button v-else type="primary" @click="dialogConfirm">新增</el-button>
</template>
</el-dialog>
$blankline
</template>
$blankline
<style scoped lang="scss">
// 查询表单靠右
.query-form {
display: flex;
justify-content: flex-end;
// 隐藏查询表单校验提示
.el-form-item {
margin-bottom: 0;
}
}
</style>
Form.vue
// form.vue
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
var exclude = ['uid','created_by','created_time','updated_by','updated_time','deleted','sort'];
}}<script setup lang="ts">
import { {{=beanClass}} } from '@/apis/{{=appName}}/{{=beanClass}}'
import { reactive, ref } from 'vue'
import { FormInstance, FormRules } from 'element-plus'
$blankline
// 接收传入的参数
interface Props {
modelValue?: {{=beanClass}}
}
const props = defineProps<Props>()
const form = ref<{{=beanClass}}>(props.modelValue || {})
const formRef = ref<FormInstance>()
// 表单校验规则
const rules = reactive<FormRules>({
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
{{=it.func.camel(field.defKey,false)}} : [{ required: true, message: '请输入{{=field.defName}}', trigger: 'change' }],{{?}}{{~}}
})
// 向父组件暴露
defineExpose({ form, formRef })
</script>
$blankline
<template>
<!-- 表单主体 -->
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" @keyup.enter="emits('confirm')">
{{~it.entity.fields:field:index}}{{? exclude.indexOf(field.defKey) == -1}}
<el-form-item label="{{=field.defName}}" prop="{{=it.func.camel(field.defKey,false)}}">
<{{? field.type == 'boolean'}}el-switch{{?? field.type == 'Date'}}el-date-picker{{??}}el-input{{?}} v-model="form.{{=it.func.camel(field.defKey,false)}}" placeholder="请输入{{=field.defName}}" />
</el-form-item>{{?}}{{~}}
</el-form>
</template>
$blankline
<style scoped lang="scss"></style>
index.vue
// index.vue
{{ var pkgName = it.entity.env.base.nameSpace;
var appName = pkgName.split('.')[pkgName.split('.').length - 1];
var beanName = it.func.camel(it.entity.defKey,false);
var beanClass = it.func.camel(it.entity.defKey,true);
}}<script setup lang="ts">
import {{=beanClass}}Table from './components/{{=beanClass}}Table.vue'
</script>
$blankline
<template>
<{{=beanClass}}Table />
</template>
$blankline
<style scoped lang="scss"></style>
其他代码
Res.Java
import com.simplo.mes.base.enums.ResEnum;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@Getter
@Setter
@SuppressWarnings({"AlibabaClassNamingShouldBeCamel"})
@Accessors(chain = true)
public class Res<T> {
/**
* 响应码
*/
private String code;
/**
* 调用结果
*/
private T data;
/**
* 提示信息
*/
private String msg;
private Res() {
super();
}
private Res(String code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
}
/**
* 成功返回(无返回参数)
*/
public static <E> Res<E> success() {
return new Res<>(ResEnum.SUCCESS.getCode(), null, ResEnum.SUCCESS.getMsg());
}
/**
* 成功返回
*/
public static <E> Res<E> success(E data) {
return new Res<>(ResEnum.SUCCESS.getCode(), data, ResEnum.SUCCESS.getMsg());
}
/**
* 异常返回
*/
public static <E> Res<E> error(ResEnum resEnum) {
return new Res<>(resEnum.getCode(), null, resEnum.getMsg());
}
/**
* 异常返回
*/
public static <E> Res<E> error(ResEnum resEnum, E data) {
return new Res<>(resEnum.getCode(), data, resEnum.getMsg());
}
/**
* 异常返回
*/
public static <E> Res<E> error(ResEnum resEnum, String msg, E data) {
return new Res<>(resEnum.getCode(), data, msg);
}
}
ResEnum.java
public enum ResEnum {
/**
* 返回码
*/
SUCCESS("0", "成功"),
ERROR("-1", "系统繁忙,请稍后再试"),
CUS_ERROR("999", "请求失败"),
NOT_LOGIN("10000", "账号未登录或已失效,请重新登录"),
SA_EXCEPTION("20001", "鉴权异常");
private String code;
private String msg;
ResEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
BaseBatch.java
import lombok.Data;
import java.util.List;
@Data
public class BaseBatch<T> {
List<T> list;
}
BaseQuery.java
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
@Data
public class BaseQuery<T> {
private T query;
private Page<T> page;
}
RequestPage.java
import lombok.Data;
@Data
public class RequestPage {
private Integer pageNumber;
private Integer pageSize;
}
TableButton.vue
<script setup lang="ts">
import { ref } from 'vue'
const emits = defineEmits(['confirm'])
interface Props {
type: 'add' | 'edit' | 'del' | 'refresh'
loading?: boolean
text?: string
icon?: string
disabled?: boolean
mini?: boolean
tip?: boolean
tipTitle?: string
tipIcon?: string
tipIconColor?: string
}
const props = withDefaults(defineProps<Props>(), {
type: 'add',
loading: false,
text: '',
icon: '',
disabled: false,
mini: false,
tip: false,
tipTitle: '确认删除?',
tipIcon: 'el-icon-WarningFilled',
tipIconColor: 'var(--el-color-error)',
})
const buttonRef = ref()
const popoverRef = ref()
const btnClick = () => {
if (!props.tip) {
emits('confirm')
}
}
const btn = {
add: {
type: 'primary',
text: props.text || '新增',
icon: props.icon || 'el-icon-Plus',
loading: 'el-icon-Loading',
},
edit: {
type: 'warning',
text: props.text || '编辑',
icon: props.icon || 'el-icon-EditPen',
loading: 'el-icon-Loading',
},
del: {
type: 'danger',
text: props.text || '删除',
icon: props.icon || 'el-icon-Delete',
loading: 'el-icon-Loading',
},
refresh: {
type: '' as any,
text: props.text || '刷新',
icon: props.icon || 'el-icon-Refresh',
loading: 'el-icon-Refresh',
},
opt: {
type: 'success',
text: props.text || '操作',
icon: props.icon || 'el-icon-Operation',
loading: 'el-icon-Loading',
},
}[props.type]
</script>
<template>
<el-button
ref="buttonRef"
:type="btn.type"
:title="btn.text"
:icon="btn.icon"
:loading="loading"
:loading-icon="btn.loading"
:disabled="disabled"
auto-insert-space
@click="btnClick"
>
{{ !mini ? btn.text : '' }}
</el-button>
<el-popconfirm
ref="popoverRef"
:virtual-ref="buttonRef"
:title="tipTitle"
:icon="tipIcon"
:icon-color="tipIconColor"
virtual-triggering
:disabled="!tip"
@confirm="emits('confirm')"
/>
</template>
<style lang="scss">
.el-button [class*='el-icon'] + span {
margin-left: v-bind('mini ? 0 : "6px"');
}
</style>
layout.ts
import { defineStore } from 'pinia'
import { CSSProperties } from 'vue'
const name = 'layout'
export const useLayout = defineStore(name, {
state: () => ({
headerHeight: 0, // 顶部高度
}),
actions: {
getMainHeight(extra = 0): CSSProperties {
return {
height:
'calc(100vh - ' +
this.headerHeight +
'px - ' +
extra.toString() +
'px)',
}
},
initHeaderHeight() {
this.headerHeight = 0
},
addHeaderHeight(height = 0) {
this.headerHeight += height
},
}
})
axios.ts
import router from '@/router'
import { CONFIG } from '@/config'
import axios, { AxiosRequestConfig } from 'axios'
import { ElLoading, ElNotification } from 'element-plus'
import { isEmpty } from 'lodash'
let loadingInstance: any
const pendingMap = new Map()
export interface AxiosOptions {
loading?: boolean // 是否开启loading层效果, 默认为false
timeout?: number // 超时时间
successMsg?: string // 请求成功提示信息
showErrorMsg?: boolean // 是否提示异常信息
}
/*
* 创建Axios
*/
export const createAxios = (
axiosConfig: AxiosRequestConfig,
options: AxiosOptions = {}
) => {
const Axios = axios.create({
baseURL: CONFIG.apiHost,
timeout: options.timeout || 1000 * 10,
headers: {
'Content-Type': 'application/json',
},
responseType: 'json',
method: 'POST',
})
options = Object.assign(
{
loading: false,
successMsg: '',
showErrorMsg: true,
},
options
)
// 请求拦截
Axios.interceptors.request.use(
(config) => {
removePending(config)
addPending(config)
// 创建loading实例
if (options.loading) {
loadingInstance = ElLoading.service()
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截
Axios.interceptors.response.use(
(response) => {
removePending(response.config)
options.loading && loadingInstance.close() // 关闭loading
if (response.config.responseType == 'json') {
if (response.data && response.data.code !== '0') {
if (response.data.code == '10000') {
// logout()
}
if (options.showErrorMsg) {
ElNotification({
offset: 55,
type: 'error',
message: response.data.msg,
})
}
// 自动跳转到路由name或path,仅限server端返回302的情况
if (response.data.code == '302') {
if (response.data.data.name) {
router.push({ name: response.data.data.name })
} else if (response.data.data.path) {
router.push({ path: response.data.data.path })
}
}
// code不等于0, 页面then内的具体逻辑就不执行了
return Promise.reject(response.data)
} else if (
!isEmpty(options.successMsg) &&
response.data.code == '0'
) {
ElNotification({
offset: 55,
message: options.successMsg
? options.successMsg
: response.data.msg,
type: 'success',
})
}
}
return response.data
},
(error) => {
error.config && removePending(error.config)
options.loading && loadingInstance.close() // 关闭loading
options.showErrorMsg && httpErrorStatusHandle(error) // 处理错误状态码
return Promise.reject(error) // 错误继续返回给到具体页面
}
)
return Axios(axiosConfig)
}
/**
* 处理异常
*/
const httpErrorStatusHandle = (error: any) => {
// 处理被取消的请求
if (axios.isCancel(error))
return console.error(`因为请求重复被自动取消:${error.message}`)
let message = ''
if (error && error.response) {
switch (error.response.status) {
case 302:
message = '接口重定向了!'
break
case 400:
message = '参数不正确!'
break
case 401:
message = '您没有权限操作!'
break
case 403:
message = '您没有权限操作!'
break
case 404:
message = `请求地址出错:${error.response.config.url}`
break
case 408:
message = '请求超时!'
break
case 409:
message = '系统已存在相同数据!'
break
case 500:
message = '服务器内部错误!'
break
case 501:
message = '服务未实现!'
break
case 502:
message = '网关错误!'
break
case 503:
message = '服务不可用!'
break
case 504:
message = '服务暂时无法访问,请稍后再试!'
break
case 505:
message = 'HTTP版本不受支持!'
break
default:
message = '异常问题,请联系网站管理员!'
break
}
}
if (error.message.includes('timeout')) {
message = '网络请求超时!'
}
if (error.message.includes('Network')) {
message = window.navigator.onLine ? '服务端异常!' : '您断网了!'
}
ElNotification({
offset: 55,
type: 'error',
message,
})
}
/**
* 储存每个请求的唯一cancel回调, 以此为标识
*/
const addPending = (config: AxiosRequestConfig) => {
const pendingKey = getPendingKey(config)
config.cancelToken =
config.cancelToken ||
new axios.CancelToken((cancel) => {
if (!pendingMap.has(pendingKey)) {
pendingMap.set(pendingKey, cancel)
}
})
}
/**
* 删除重复的请求
*/
const removePending = (config: AxiosRequestConfig) => {
const pendingKey = getPendingKey(config)
if (pendingMap.has(pendingKey)) {
const cancelToken = pendingMap.get(pendingKey)
cancelToken(pendingKey)
pendingMap.delete(pendingKey)
}
}
/**
* 生成每个请求的唯一key
*/
const getPendingKey = (config: AxiosRequestConfig) => {
let { data } = config
const { url, method, params } = config
if (typeof data === 'string') data = JSON.parse(data)
return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}
common.ts
// 关键字高亮替换
export const replaceKeyword = (origin: any, target: string) => {
if (origin && typeof origin == 'string') {
const m = pinyin.match(origin, target)
if (m) {
target = origin.substring(
(m as Array<number>)[0],
(m as Array<number>)[1] + 1
)
}
return origin.replace(
target,
`<span class="highlight">${target}</span>`
)
} else {
return origin
}
}