Expanse 培训:Day2:前后端开发 Demo

前后端开发 Demo

创建 Controller:PL_TestController

规范:PL_TestController,PL 模块名称

参考 Base_LogController,

菜单路径不要重复,

@RequestMapping("/authapi/pl_test")
@RestController
@RequestMapping("/authapi/pl_test")
@Display("测试操作日志")
public class PL_TestController {

    @Autowired
    private Base_LogService service;


    @PostMapping("/get")
    @Log("调用PL_TestController")
    @PreAuthorize("hasAuthority('menu_base_log')")
    public Base_Log get(String id) {

        return service.get(id);
    }
}

创建 dto(数据传输对象)

规范:dto 在 Controller 包下面

DAO:
data access object 数据访问对象
主要用来封装对数据库的访问。通过它可以把 POJO 持久化为 PO,用 PO 组装出来 VO、DTO

DTO :
Data Transfer Object 数据传输对象
主要用于远程调用等需要大量传输对象的地方。
比如我们一张表有 100 个字段,那么对应的 PO 就有 100 个属性。
但是我们界面上只要显示 10 个字段,
客户端用 WEB service 来获取数据,没有必要把整个 PO 对象传递到客户端,
这时我们就可以用只有这 10 个属性的 DTO 来传递结果到客户端,这样也不会暴露服务端表结构. 到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为 VO

创建 VUE 页面

created activated
触发顺序 组件创建最初始 created => mounted =>activated
触发次数 只在组件刚创建时创建 在使用 keep-alive 标签中有效,每次进入都会执行钩子中的函数

配置菜单权限

表:base_navigation 导航

name: vue 跳转名称的引用

path:路径 vue 路径

component:物理路径,设置与 path 相同

表:base_permission 权限

菜单权限,按钮权限

INSERT INTO `expanse`.`base_navigation`(`id`, `parentId`, `name`, `title`, `path`, `icon`, `component`, `requiredPermissionId`, `show`, `keepAlive`, `layoutType`, `remark`, `sortIndex`, `gmtCreatedBy`, `gmtCreatedOn`, `gmtUpdatedBy`, `gmtUpdatedOn`, `gmtVersion`) VALUES ('pl_test1', 'pl_report', 'base_test1', '测试1', '/loansubsystem/task/test1', 'pl_rpt_loanapplyprocess', 'loansubsystem/task/test1', 'menu_pl_rpt_vchart', 1, 1, 0, NULL, 1, NULL, NULL, NULL, NULL, 0);

说明

框架使用 mybatis-plus 做为数据访问框架

使用参考
https://mp.baomidou.com/guide/quick-start.html# 开始使用

https://www.jianshu.com/p/ceb1df475021

https://www.jianshu.com/p/a4d5d310daf8

后端映射请求自动生成前端 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; 
  });

后端处理映射类名如:Base_LogController,去除 Controller, 首字母小写。即为前端类名称 ,如:base_log。

后端类“expanse-framework\Server\infrastructures\src\main\java\com\epichust\epichustexpansesystem\infrastructures\common\jsscript\RequestMappingScriptManager.java 实现把后端 controller 生成前端 js 方法

前端通过 static\config\index-prod.js 中的 window.SITE_CONFIG[‘baseScriptUrl’],引入后端生成的 js 文件

统一的异常处理

前端几乎不需要自己写异常处理代码

后端服务方法抛出 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();
        })

前端会直接的显示给用户
[用户禁止删除]
其它系统错误,系统会在后端记录,前端提示用户重试或联系系统管理员
[系统错误]
后端日志记录

系统日志 ( 用户操作审计)

记录用户在系统的所有操作,包括方法,参数等;记录系统错误

  • 使用 com.epichust.epichustexpansesystem.infrastructures.common.annotations.Log 注解标注的请求映射 (RequestMapping) 会被记录操作日志。
  • Log 注解可设置 disabled=true,禁用日志记录
  • 也可以通过 application-dev/prod.yml my.methodlog.enabled:false 配置关闭方法日志记录

事务

使用 Spring @Transactional 注解做事务管理

使用时注意:

  • 注解只能应用到 public 方法才有效
  • 事务注解应该应用于 servicesimpl,不要

在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截
可参考博文:https://blog.csdn.net/nextyu/article/details/78669997

swagger2 api 文档

菜单导航

  • 数据库(表 base_navigation) 结构及 vue-router 对应关系
列名 备注 vue-router 对应
id 主键 path
parentId 父 Id
name 名称 name
title 标题
path 路径 path
icon 图标 component
component 组件地址
requiredPermissionId 关联的权限主键
show 是否显示在导航菜单
keepAlive vue router-view keepAlive
  • 后端定义菜单自动注册到 Vue-router

后端定义的菜单 (show=1) 自动会显示在系统导航,自动在 vue-router 中注册,无须前端手工处理

根据数据库中 icon 的值,如:base_test1
从 iconfont(http://www.iconfont.cn/) 下载 svg 格式的文件, 命名为:icon-base_test1
放入前端“***\Client\src\icons\svg“文件夹

功能权限

  • 在数据库表 base_permission 定义权限

数据库表结构说明

列名 备注
id 权限码
name 权限名称
  • 请求映射 RequestMapping 使用(@PreAuthorize)权限限定,如下:
public class Base_LogController {

    @PreAuthorize("hasAuthority('权限码')")
    public Base_Log get(String id) {
    ...
    }
}
  • 前端权限限定:this.$util.hasPermission(‘权限码’) 如下:
  <el-button v-if="$util.hasPermission('权限码')">发送系统消息
      </el-button>
  • 菜单会自动根据权限过滤,但仍需后端请求映射 RequestMapping 添加权限限定

  • 按钮权限,按照步骤 2,3 操作

  • 请求映射访问路径以 "/authapi/" 开始只有登陆后用户可以访问;以 "pmtapi/" 的路径,匿名或登陆后用户皆可以访问。

### 数据权限

  • 本系统基于组织机构做数据权限单位

  • 分配岗位可查看的组织机构数据 [角色管理]

  • 后端代码做数据过滤,如下:

       //非系统管理员,做数据权限过滤。 SecurityUtils.getCurrentUser().getDataPermissionIds():当前用户拥有的数据权限列表
        if(!SecurityUtils.getCurrentUser().isSystemAdmin()){
            ew.andNew().in("docOwnGroupId",SecurityUtils.getCurrentUser().getDataPermissionIds());
        }

工作流开发

流程定义

  • 点击“系统”-》“流程模型”进入流程模型设计页面

  • 点击“设计”按钮打开设计页面,如下 image-20200310181157002点击“代理”输入,弹出代理设置页面

  • image-20200310181220192 image-20200310181237394

  • 程序传入审批步骤审批人的结点设置如下图

  • image-20200310181255847

  • 会签节点设置如下图

  • 流程设计完成后,需保存更改,如下图

  • 流程设计保存完毕后,点击“部署“按钮,发布流程,如下图

> 流程实例是带版本的,即流程修改部署后,旧流程还会按钮旧流程图审批,新发起的流程按照新的流程图审批。

工作流服务方法

在后端代码中 "expanse-framework\Server\publicsubsystem\src\main\java\com\epichust\epichustexpansesystem\publicsubsystem\servicesimpl" 提供了下列工作流服务方法:

/**
 * 工作流服务
 */
@Service
public class WF_WorkflowServiceImpl extends BaseServiceImpl implements WF_WorkflowService {

    @Autowired
   private WorkflowService workflowService;
    @Autowired
    private WF_ProcessInstService processInstService;


    /**
     * 启动流程
     * @param processDefinitionKey 流程定义key
     * @param originator 流程发起人Id
     * @param departmentId 流程所在部门
     * @param id 业务单据主键
     * @param variables 工作流变量
     * @param saveDate 流程保存时间
     * @param folio 流程描述
     * @param folio 提交时审批意见
     * @return 流程实例Id
     */
    public String startWorkflow(String processDefinitionKey, String originator, String departmentId, String id, Map<String, Object> variables, LocalDateTime saveDate,String folio, String remark) {

        WF_BusinessKey businessKey = new WF_BusinessKey(originator,departmentId,id,folio);

        String procInstId = workflowService.startWorkflow(processDefinitionKey,businessKey,originator,variables);

        processInstService.createProcessInst(procInstId,processDefinitionKey,originator,saveDate,folio,remark);

        return  procInstId;
    }
    /**
     * 任务处理
     * @param taskGotoDto 任务对象
     */
    public void handleTask(WF_TaskActionDto taskActionDto) {
        workflowService.handleTask(taskActionDto);
        String status = taskActionDto.getClaim()?"签收完成":"办理完成";
        processInstService.handleTask(taskActionDto.getTaskId(),
                SecurityUtils.getCurrentUser().getId(),
                taskActionDto.getAction(),
                taskActionDto.getAction(),
                taskActionDto.getTaskRemark(),
                status);


    }
    /**
     * 任务跳转
     * @param taskGotoDto 任务对象
     */
    public  void goTo(WF_TaskGotoDto taskGotoDto){
        workflowService.goTo(taskGotoDto);

        String status = taskGotoDto.getTaskStatus();
        processInstService.handleTask(taskGotoDto.getTaskId(),
                SecurityUtils.getCurrentUser().getId(),
                taskGotoDto.getAction(),
                taskGotoDto.getAction(),
                taskGotoDto.getTaskRemark(),
                status);

    }
    /**
     * 获取当前任务中流程变量值
     * @param sn 任务Id
     * @param variableName 变量名称
     * @return  变量值
     */
    public  Object getTaskVariable(String sn,String variableName){

        return  workflowService.getTask(sn).getProcessVariables().get(variableName);
    }
    /**
     * 获取当前任务所在审批结点
     * @param sn 任务Id
     * @return 审批结点Id
     */
    public  String getTaskActivityId(String sn){

        return  workflowService.getTask(sn).getTaskDefinitionKey();
    }
    /**
     * 获取当前任务上一个审批结点
     * @param sn 任务Id
     * @return 审批结点Id
     */
    public String getPreviewActivityId(String sn){
        return workflowService.getPreviewActivityId(sn);
    }
    /**
     * 删除流程实例
     * @param processInstanceId 流程实例Id
     * @param reason 删除原因
     */
    @Transactional
    public  void deleteProcessInstance(String processInstanceId,String reason){
        workflowService.deleteProcessInstance(processInstanceId,reason);//删除流程

        processInstService.delete(processInstanceId);
    }

    /***
     * 设置流程实例状态
     * @param instId 流程实例Id
     * @param status 状态 {@link com.epichust.epichustexpansesystem.infrastructures.workflow.enums.EProcessInstStatus}
     */
    @Override
    @Transactional
    public void setProcessInstStatus(String instId, String status)
    {
        processInstService.setProcessInstStatus(instId,status);

    }
/**
     * 获取岗位审批任务最少的用户Id
     * @param processDefinitionKey 流程定义key
     * @param activityId 流程结点
     * @param roleId 岗位
     * @return 审批任务最少的用户Id
     */
    public  String getRoleUser(String processDefinitionKey, String activityId,String roleId){

       return  workflowService.getRoleUser(processDefinitionKey,activityId,roleId);
    }
    /**
     * 获取部门下特定岗位类别审批任务最少的用户Id
     * @param processDefinitionKey 流程定义key
     * @param activityId 流程结点
     * @param roleId 岗位
     * @return 审批任务最少的用户Id
     */
    public  String getRoleCategoryUsers(String processDefinitionKey, String activityId,String departmentId, String roleCategoryId){

        return  workflowService.getDepartmentRoleCategoryUser(processDefinitionKey,activityId,departmentId,roleCategoryId);
    }
}

表单扩展字段定义

  • 表单定义

表单内容定义 点击菜单“系统 -》扩展字段”加入新的表单定义,拖拽设计表单,输入表单名称

  • 业务系统引用表单定义

后端:

实体类加入字段,如“expanse-framework\Server\loansubsystem\src\main\java\com\epichust\epichustexpansesystem\loansubsystem\entities”,


/**
 * 页面生成展示-主
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pl_loan_demo")
public class PL_LoanDemo extends FullAuditedEntity {

    -------
    /**
     * 扩展信息
     */
    @TableField(exist = false)
    private Map<String, Object> extendFields; 
    --------
}
服务代码,如 :expanse-framework\Server\loansubsystem\src\main\java\com\epichust\epichustexpansesystem\loansubsystem\servicesimpl

    @Override
    @Transactional(readOnly = true)
    public PL_LoanDemo get(String id) {
        PL_LoanDemo entity = repository.selectById(id);
        -------
        //获取扩展值
        Map<String, Object> extendFields = extendFieldValueService.getMapEValue(id);
        entity.setExtendFields(extendFields);

        return entity;
    }
        @Override
    @Transactional
    public String insert(PL_LoanDemo input) {
        ------

        //存储扩展值

        extendFieldValueService.setMapEValue(input.getId(), input.getExtendFields());

      -----
    }
     @Override
    @Transactional
    public Integer delete(String id) {

        //页面生成展示-子
        pL_LoanDemoDetailService.deleteByLoanDocId(id);
        //扩展值
        extendFieldValueService.delete(id);

    }

    /**
     * <p>
     * 根据单据id集合 ,批量删除记录
     * </p>
     *
     * @param ids 单据id集合
     */
    @Override
    @Transactional
    public Integer batchDelete(List<String> ids) {
        //页面生成展示-子
        pL_LoanDemoDetailService.deleteBatchByLoanDocId(ids);

        //扩展值
        extendFieldValueService.batchDelete(ids);

        Integer rowsEffected = repository.deleteBatchIds(ids);
        return rowsEffected;
    }
  • 前端引用
<!-- \expanse-framework\Client\src\views\loansubsystem\PL_LoanDocForm.vue -->
<template>
<div>
  <el-form :model="model" ref="ruleForm" @keyup.enter.native="doSave()" label-width="140px">
     ------
    <!-- v-model:值 definition-id: 扩展字段定义id ,readOnly:是否只读 -->
    <t-extend-form ref="extendForm" v-model="extendFields" definition-id="PL_LoanDoc" :readOnly="readOnly"></t-extend-form>

  </el-form>
</div>
</template>
<script>

export default {
  data() {
    return {
      readOnly: false,
      docId: null,
      extendFields: {}, //扩展字段值 
    }
  },
  components: {},

  created() {},

  methods: {
    load() {

      if (self.docId) {
        tapp.services.pL_LoanDoc.get(self.docId).then(function(result) {

          self.extendFields = result.extendFields; //设置扩展字段值
          delete self.model.extendFields;

        });
      } 
    },
    clearValidate() {
      this.$nextTick((_) => { 
        this.$refs.extendForm.clearValidate(); 
      });
    },
    doSave(formName) {

      validPromises.push(Promise.resolve(self.$refs.extendForm.validate()));
      Promise.all(validPromises)
        .then(resultList => {
          let requestModel = { ...self.model
          };
          requestModel.extendFields = self.extendFields;
          tapp.services.pL_LoanDoc.save(requestModel).then(function(result) {
            self.model = result;
            self.extendFields = result.extendFields; //扩展字段值

          });
        }) 
    },

  }
}
</script>

数据字典组件

后端数据字典自动生成为前端 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>
  • 在列表查询结果显示字典的行筛选及中文内容, 代码:
 {
              prop: 'sexId',
              label: '性别',
              sortable: true,
              width: 120,
              filters: this.$util.getListDataDicFilters('public_sex'), //行筛选
              formatter: (row, column, cellValue) => {
                return this.$util.dataDicFormat('public_sex',row.sexId);
              }//显示字典的中文内容
            },

功能强大的 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>-列</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>