基于react ant design pro typescript 技术框架已经重磅推出
前言
本框架是基于十多年项目开发经验积累,用最佳实践和流行技术开发现代前后端分离的通用项目模板。适合web应用,微信、手机应用管理端及服务端。
预览地址:http://47.94.229.181:81/jrtechapp/
服务器CPU1核内存1G带宽1M,访问比较慢,望谅解
系统更多截图:
后端多模块
后端映射请求自动生成前端JS方法调用
减少后端和前端的调用阻抗
后端处理映射
@RestController
@RequestMapping("/authapi/base_log")
@Display("操作日志")
public class Base_LogController {
@PostMapping("/get")
@Log(disabled = true)
@PreAuthorize("hasAuthority('menu_base_log')")
public Base_Log get(String id) {
Base_Log result = service.get(id);
return result;
}
}
自动注册为前端JS函数,前端调用方式如下:
tapp.services.base_Log.get(id).then(function(result) {
self.model = result;
});
数据字典组件
后端数据字典自动生成为前端JS对象,前端提供数据字典组件,仅需要指定数据字典类别就能完成选择功能。
效果:
前端代码:
<el-col :span="8">
<!-- 性别选择组件-->
<el-form-item label="性别" prop="sexId" verify >
<t-dic-select dicType="public_sex" v-model="headerEntity.sexId"></t-dic-select>
</el-form-item>
</el-col>
统一的异常处理
前端几乎不需要自己写异常处理代码
后端服务方法抛出UserFriendlyException(用户异常友好化)异常:
@Service
public class Base_UserServiceImpl extends BaseServiceImpl implements Base_UserService {
@Override
@Transactional
public void delete(String id) {
Base_User entity = userRepository.selectById(id);
if (entity == null) {
return;
}
if (!entity.canDelete()) {
throw new UserFriendlyException(MessageFormat.format("用户{0}禁止删除,无法删除!", entity.getName()));
}
...
}
}
前端代码:
<!--无须异常处理-->
tapp.services.base_User.batchDelete(ids).then(function(result) {
self.$notify.success({
title: '系统删除成功',
message: '用户信息已删除成功!'
});
self.$refs.searchReulstList.refresh();
})
前端会直接的显示给用户
其它系统错,系统会在后端记录,前端提示用户重试或联系系统管理员
后端日志记录
后端定义菜单自动注册到Vue-router
后端定义的菜单自动会显示在系统导航,无须在vue-router中注册,无须前端重复处理
功能强大的grid组件
前端提供功能强大的grid组件,仅需要调用 后端映射请求自动生成的JS方法,即能完成获取数据,分页,排序,导出等功能
左边树,右边列表
效果
代码
<template>
<div>
<el-row :gutter="20">
<el-col :span="8">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>表名</span>
</div>
<div class="text item">
<!-- 定义树组件 -->
<t-tree ref="categoryTree" :options="categoryTreeOptons" @node-click="handleNodeClick">
</t-tree>
</div>
</el-card>
</el-col>
<el-col :span="16">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>{{selectedCategoryItemName}}-列</span>
<div style="float: right; padding: 3px 0">
<el-button icon="el-icon-download" @click="doExportExcel()">导出</el-button>
</div>
</div>
<div class="text item">
<!-- 定义列表组件 -->
<t-grid ref="searchReulstList" :options="gridOptions">
</t-grid>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data() {
return {
selectedCategoryItem: null,
data: [],
categoryTreeOptons: { //定义树组件选项
tree: {
data: [], //数据源
showCheckbox: false,, //不允许多选
defaultCheckedKeys: [] //默认选中ids
}
},
gridOptions: { //定列表组件选项
dataSource: [], //数据源
grid: {
pageable: false, //不分页
columns: [{ //定义列
prop: 'columnName',
label: '列名',
sortable: true,
width: 200
},
{
prop: 'columnType',
label: '数据类型',
sortable: true,
width: 120
},
{
prop: 'isNullable',
label: '允许非空',
sortable: false,
width: 60
},
{
prop: 'columnKey',
label: '主键约束',
sortable: false,
width: 60
},
{
prop: 'columnComment',
label: '备注',
sortable: false,
},
], // 需要展示的列
defaultSort: {
prop: 'ordinalPosition',
order: 'ascending'
},
}
}
}
},
components: {
},
created() {
let self = this;
//加载数据
tapp.services.base_DBDictionary.queryDBDictionary().then(function(result) {
self.gridOptions.dataSource = result; //指定列表组件数据源
let categoryTreeData = result.map(p => {
return {
id: p.tableName,
name: p.tableComment + '(' + p.tableName + ')',
parentId: null,
level: 1,
items: [],
}
});
//设置树组件数据源
self.categoryTreeOptons.tree.data = categoryTreeData;
self.$nextTick(() => {
self.$refs.categoryTree.refresh();
});
});
},
methods: {
handleNodeClick(dataItem, node, el) {
this.selectedCategoryItem = dataItem;
let selectedCategoryData = this.data.find(p => p.tableName === dataItem.id);
let gridData = selectedCategoryData.columns;
this.gridOptions.dataSource = gridData;
},
doSearch() {
this.$refs.searchReulstList.refresh();
},
//列表数据导出
doExportExcel() {
this.$refs.searchReulstList.exportCSV(this.selectedCategoryItemName + '-列');
},
}
}
</script>
<style >
</style>
分页查询,删除,导出
效果
代码
<template>
<div class="mod-role">
<!-- 定义查询条件 -->
<el-form :inline="true" @keyup.enter.native="doSearch()">
<el-form-item>
<el-input prefix-icon="el-icon-search" v-model="gridOptions.dataSource.serviceInstanceInputParameters.searchKey" placeholder="登陆名或者姓名" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button @click="doSearch()" icon="el-icon-search">查询</el-button>
<el-button icon="el-icon-plus" type="primary" @click="doNew()">新增</el-button>
<el-button icon="el-icon-delete" type="danger" @click="doBatchDelete()" :disabled="selectedRows.length <= 0">批量删除</el-button>
<el-button icon="el-icon-download" @click="doExportExcel()">导出</el-button>
<el-button icon="el-icon-upload2" @click="doExportExcel()" v-if="false">导入</el-button>
</el-form-item>
</el-form>
<!-- 定义列表组件 -->
<t-grid ref="searchReulstList" :options="gridOptions" @selection-change="handleSelectionChange">
</t-grid>
</div>
</template>
<script>
export default {
data() {
return {
selectedRows: [],
gridOptions: {//定列表组件选项
dataSource: { //数据源
//定义要调用的后端映射请求自动生成的JS方法
serviceInstance: tapp.services.base_User.getAllUsers,
//定义要调用的后端映射请求自动生成的JS方法参数
serviceInstanceInputParameters: {
searchKey: null,
}
},
grid: {
operates: { //定义列表组件的操作按钮
width: 120,
fixed: 'left',
list: [{
type: 'text',
show: true,
label: '查看',
method: this.doEdit,
},
{
type: 'text',
show: true,
label: '修改密码',
method: this.doAdminChangePassword,
},
]
}, // 定义列表组件的列
columns: [{
prop: 'loginId',
label: '登陆名',
sortable: true,
width: 120
},
...
},
{
prop: 'departmentNames',
label: '所属营业部',
sortable: true,
},
], // 需要展示的列
defaultSort: {
prop: 'id',
order: 'ascending'
},
}
}
}
},
components: {
},
created() {
},
methods: {
handleSelectionChange(val) {
this.selectedRows = val;
},
doExportExcel() {
this.$refs.searchReulstList.exportCSV('用户列表');
},
doSearch() {
this.$refs.searchReulstList.refresh();
}
}
}
</script>
合计
效果
代码
<template>
<div>
<el-form :inline="true">
<el-form-item>
<el-button icon="el-icon-download" @click="doExportExcel()">
<i class="fa fa-lg fa-level-down"></i>导出
</el-button>
</el-form-item>
</el-form>
<t-grid ref="repaymentScheduleReulstList" :options="repaymentScheduleGridOptions">
</t-grid>
</div>
</template>
<script>
import util from '@/util'
export default {
components: {},
props: {
repaymentScheduleList: null,
},
data() {
return {
repaymentScheduleGridOptions: {
dataSource: [],
grid: {
mutiSelect: false,
pageable: false,
reduceMethod: this.getRepaymentScheduleSummaries,
columns: [{
prop: 'planSettleDate',
label: '结算日期',
sortable: true,
width: 120,
formatter: (row, column, cellValue) => {
return this.$util.dateFormat(row.planSettleDate);
}
},
...
], // 需要展示的列
defaultSort: {
prop: 'id',
order: 'ascending'
},
}
}
}
},
watch: {
repaymentScheduleList: {
handler(newValue, oldValue) {
this.repaymentScheduleGridOptions.dataSource = newValue;
},
deep: true
}
},
created() {},
mounted() {},
computed: {},
mounted() {},
methods: {
doExportExcel() {
this.$refs.repaymentScheduleReulstList.exportCSV('还款计划表');
},
getRepaymentScheduleSummaries(param) {
const {
columns,
data
} = param;
if (data == null || data.length == 0) {
return [];
}
const sums = [];
sums[0] = '合计';
let repaymentScheduleList = data;
let sumPlanCapitalAmount = repaymentScheduleList.map(function(item) {
return item.planCapitalAmount;
}).reduce(function(a, b, index, arr) {
return Number((a || 0)) + Number((b || 0));
});
....
return sums;
},
}
}
</script>
多列头及合计
效果
代码
<template>
<t-grid ref="loanRecoveryDocImplList" :options="loanRecoveryDocImplListGridOptions">
<template slot="columnDataHeader">
<el-table-column
prop="businessDate"
label="还款日期"
width="160" :formatter="dateFormat">
</el-table-column>
<el-table-column prop="docOperator" label="操作人" width="100">
</el-table-column>
<el-table-column prop="returnMoneyReturnModeId" label="业务类型" width="100" :formatter="returnMoneyReturnModeFormat">
</el-table-column>
<el-table-column prop="overdueDays" label="逾期天数" width="100">
</el-table-column>
<el-table-column label="本金">
<el-table-column prop="planCapitalAmount" label="应还" width="120" :formatter="moneyFormat">
</el-table-column>
<el-table-column prop="returnCapitalAmount" label="实还" width="120" :formatter="moneyFormat">
</el-table-column>
<el-table-column prop="remainCapitalAmount" label="剩余" width="120" :formatter="moneyFormat">
</el-table-column>
</el-table-column>
...
</el-table-column>
</template>
</t-grid>
</template>
<script>
import util from '@/util'
export default {
components: {},
props: {
loanDocId: {
type: String,
default: null,
},
},
data() {
return {
loanRecoveryDocImplListGridOptions: {
dataSource: {
loadDataOnFirst: false,
serviceInstance: tapp.services.PL_LoanRecoveryDoc.getImplListByLoanDocId,
serviceInstanceInputParameters: this.loanDocId,
},
grid: {
customColumnDataHeader: true,
reduceMethod: this.getRecoveryDocImplSummaries,
pageable: false,
mutiSelect: false,
defaultSort: {
prop: 'id',
order: 'ascending'
},
}
}
}
},
watch: {
loanDocId(value) {
if (!value) {
return;
}
this.loanRecoveryDocImplListGridOptions.dataSource.serviceInstanceInputParameters = value;
}
},
created() {
},
mounted() {},
computed: {},
mounted() {},
methods: {
refresh() {
if (!this.loanDocId) {
return;
}
this.$refs.loanRecoveryDocImplList.refresh();
},
getRecoveryDocImplSummaries(param) {
const {
columns,
data
} = param;
if (data == null || data.length == 0) {
return [];
}
const sums = [];
sums[0] = '合计';
let recoveryDocImplList = data;
let sumReturnCapitalAmount = recoveryDocImplList.map(function(item) {
return item.returnCapitalAmount;
}).reduce(function(a, b, index, arr) {
return Number((a || 0)) + Number((b || 0));
});
sums[6] = util.moneyFormat(sumReturnCapitalAmount);
...
return sums;
},
}
}
</script>
分页及服务器端计算合计
效果
代码
<template>
<div class="mod-role">
<el-form ref="ruleForm" @keyup.enter.native="doSearch()" label-width="120px">
<el-row :gutter="20">
<el-col :span="9">
<el-form-item label="组织机构">
<base-organization-select v-model="gridOptions.dataSource.serviceInstanceInputParameters.organizationId" placeholder="请选择">
</base-organization-select>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="产品类别">
<pl-loanProducttype-select v-model="gridOptions.dataSource.serviceInstanceInputParameters.loanProductSubTypeId" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="客户经理">
<base-user-select role-category="base_rolecategory_trackingpersoninfomr" v-model="gridOptions.dataSource.serviceInstanceInputParameters.docOwnUserId" placeholder="请选择">
</base-user-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="9">
<el-form-item label="还款日期">
<t-datetime-range-picker v-model="dateRange" @change="onDateRangeChanged">
</t-datetime-range-picker>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="通用查询">
<el-input v-model="gridOptions.dataSource.serviceInstanceInputParameters.searchKey" placeholder="申请编号、客户名称、身份证号" clearable></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item>
<el-button icon="el-icon-search" type="primary" @click="doSearch()">查询</el-button>
<el-button icon="el-icon-download" @click="doExportExcel()">导出</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
<t-grid ref="searchReulstList" :options="gridOptions">
</t-grid>
</div>
</template>
<script>
import util from '@/util'
export default {
data() {
return {
dateRange: null,
gridOptions: {
dataSource: {
serviceInstance: tapp.services.PL_Report.getLoanRecoveryProfitQuery,
serviceInstanceInputParameters: {
searchKey: null,
}
},
grid: {
mutiSelect: false,
reduceMethod: this.getRecoveryDocImplSummaries,
columns: [{
prop: 'customerCode',
label: '申请编号',
sortable: true,
width: 120
},
...
{
prop: 'docOwnDepartmentName',
label: '所属营业部',
sortable: true,
minWidth: 150,
},
], // 需要展示的列
defaultSort: {
prop: 'id',
order: 'descending'
},
}
}
}
},
components: {},
created() {
},
methods: {
onDateRangeChanged(val) {
this.gridOptions.dataSource.serviceInstanceInputParameters.startDate = val[0];
this.gridOptions.dataSource.serviceInstanceInputParameters.endDate = val[1];
},
getRecoveryDocImplSummaries(param) {
const {
columns,
data,
reduces
} = param;
if (reduces == null) {
return [];
}
const sums = [];
sums[0] = '合计';
sums[5] = util.moneyFormat(reduces.sumLoanMoneyAmount);
return sums;
},
doExportExcel() {
this.$refs.searchReulstList.exportCSV('收入明细');
},
doSearch() {
this.$refs.searchReulstList.refresh();
}
}
}
</script>
简单实用的form表单验证
前端验证抛弃element ui 繁琐的form表单验证方式,使用简单的HTML标记实现验证
必输验证
效果
代码
<el-form-item label="姓名" prop="name" verify :maxLength="50" class="is-required">
<el-input v-model="model.name" ></el-input>
</el-form-item>
身份证号码必输验证
效果
代码
<el-col :span="8">
<el-form-item label="身份证号" prop="customerCardNO" verify idcard >
<el-input v-model="headerEntity.customerCardNO"></el-input>
</el-form-item>
</el-col>
未完待续。。。