若依nodejs全栈(五:导出Excel与用户列表填坑)

news2024/9/20 0:35:08

回顾

上一章节中,我们学会了ruoyi用户列表简单的增删改查功能,但是上一节还存在一些问题:

  • 查询的列表是全部数据,没加查询条件;
  • 没有导出功能;
  • 新增或修改时,用户关联的角色、岗位、菜单等没做关联;

今天,我们先来解决前两个问题,因为第三个问题涉及到其他基础表,放到后面讲

查询用户列表

封装返回

下文为了方便统一返回,封装了几个返回函数,可以把上篇文字中的增删改查修改一下:
页面引入:import { resBuild } from "@utils/resBuild";

  • resBuild.success():msg:操作成功,code:200,msg、code可修改
  • resBuild.fail():msg:操作失败,code:500,msg、code可修改
  • resBuild.data():返回data键值对,msg:操作成功,code:200,msg、code可修改
  • resBuild.list():返回自定义键值对,msg:操作成功,code:200,msg、code可修改
  • resBuild.other():返回列表,rows: [], total: 0, msg:操作成功,code:200,msg、code可修改

源码如下:

/** 基础提示类,封装成功、失败的提示 */
export class resBuild {
  /**
   * 构建 成功/失败 返回 data对象
   * */
  static data(data: any, code:number = 200, msg:string = '操作成功') {
    return {
      code,
      msg,
      data: data,
    }
  }

  /**
   * 构建 成功/失败 返回 自由传键值对来解构
   * */
  static other(data: any = {}, msg = '操作成功', code = 200) {
    return {
      code,
      msg,
      ...data
    }
  }

  /**
   * 构建列表,返回rows,total等字段
   * */
  static list(rows: any[] , total: number, msg:string = '操作成功', code:number = 200) {
    return {
      code,
      msg,
      rows: rows || [],
      total: total || 0
    }
  }

  /** 操作成功 */
  static success(msg:string = '操作成功', code:number = 200) {
    return {
      code,
      msg,
    }
  }

  /** 操作失败 */
  static fail(msg:string = '操作失败', code:number = 500) {
    return {
      code,
      msg,
    }
  }
}

新建dto

为了方便以后得列表dto,我们先新建公共类分页DTO

import { Rule, RuleType } from "@midwayjs/validate";

// 分页请求参数
export class PageDTO {
  @Rule(RuleType.number())
  pageNum?: number;

  @Rule(RuleType.number())
  pageSize?: number;
}

观察用户列表界面,发现有以下几个搜索条件

  • 用户名称,模糊搜索
  • 手机号码,模糊搜索
  • 状态值,单选等于
  • 创建时间,日期范围,包含当天
  • 部门id,左侧的部门树(大家可以像创建用户列表一样,先把部门Controller、Service、DTO)建立起来

于是,在user.dto.ts中、创建:

import { PageDTO } from "@dto/common/page.dto";

// 查询参数
export class ListUserDTO extends PageDTO {
  @Rule(RuleType.string())
  userName?: string;

  @Rule(RuleType.string())
  nickName?: string;

  @Rule(RuleType.number())
  deptId?: number;

  @Rule(RuleType.string().max(11))
  phonenumber?: string;

  @Rule(RuleType.string())
  status?: string;

  @Rule(RuleType.string()
  'params[beginTime]'?: string;

  @Rule(RuleType.string()
  'params[endTime]'?: string;
}

可能有的人会把params[beginTime]改为下面这样:

class DateParamsDTO {
  @Rule(RuleType.string().required())
  beginTime: string;

  @Rule(RuleType.string().required())
  endTime: string;
}

export class ListUserDTO extends PageDTO {
  @Rule(RuleType.object())
  params?: DateParamsDTO;
}

但是,实测这样无法被解析

源码未细读,估计是@Query()不支持嵌套,以后仔细读源码再做分析

修改user.controller.ts

 @Get('/list')
 async list(@Query() queryParams: ListUserDTO) {
   return await this.userService.list(queryParams);
 }
修改user.service.ts
async list(queryParams: ListUserDTO) {
    const queryBuilder = this.userEntity
      .createQueryBuilder('entity')
      .leftJoinAndSelect('entity.dept', 'dept');

    if(queryParams.userName) {
      queryBuilder.andWhere(`entity.userName LIKE "%${queryParams.userName}%"`,)
    }
    if(queryParams.phonenumber) {
      queryBuilder.andWhere(`entity.phonenumber LIKE "%${queryParams.phonenumber}%"`)
    }
    if(queryParams.status) {
      queryBuilder.andWhere(`entity.status = ${queryParams.status}`)
    }
    // 时间范围,包含全天
    if(queryParams["params[beginTime]"] && queryParams["params[endTime]"]) {
      queryBuilder.andWhere(`entity.createTime BETWEEN :beginTime AND :endTime`, {
        beginTime: queryParams["params[beginTime]"] + ' 00:00:00',
        endTime: queryParams["params[endTime]"] + ' 23:59:59',
      })
    }

    if(queryParams.deptId) {
      queryBuilder.orWhere(`dept.deptId = :deptId`, { deptId: queryParams.deptId });
      queryBuilder.orWhere('FIND_IN_SET(:ancestors, dept.ancestors) > 0', { ancestors: String(queryParams.deptId) });
    }

    if(queryParams.pageNum && queryParams.pageSize) {
      queryBuilder.skip((queryParams.pageNum - 1) * queryParams.pageSize).take(queryParams.pageSize)
    }

    const [ rows, total] = await queryBuilder.getManyAndCount()
    return resBuild.list(rows, total)
  }

然后简单测试一下,条件都生效了
在这里插入图片描述

导出用户列表

分析:导出之前需要先查询用户列表,且去除pageSizepageNum参数,然后导出为excel文件

引入依赖
yarn add exceljs
声明枚举文件

导出时字段翻译用,简单举例如下@/utils/enum.ts

/**
 * 删除标志: 0代表存在 1代表删除
 */
export enum DelFlagEnum {
  // 存在
  NORMAL = '0',
  // 删除
  DELETE = '1',
}

/**
 * 数据状态: 0正常,1停用
 */
export enum StatusEnum {
  // 正常
  NORMAL = '0',
  // 停用
  STOP = '1',
}

/**
 * 性别: 0男,1女
 */
export enum SexEnum {
  // 男
  MAN = '0',
  // 女
  WOMAN = '1',
}
封装导出函数

新建文件@/src/service/common/downloadExcel.ts,如下;

import { Provide, Inject } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import * as ExcelJS from 'exceljs';
import { StatusEnum, SexEnum, DelFlagEnum } from "@utils/enum";
import * as _ from 'lodash';

/**
 * 通用枚举映射配置
 */
export const commonExportMap = {
  status: {
    [StatusEnum.NORMAL]: '正常',
    [StatusEnum.STOP]: '停用',
  },
  sex: {
    [SexEnum.MAN]: '男',
    [SexEnum.WOMAN]: '女',
  },
  delFlag: {
    [DelFlagEnum.NORMAL]: '正常',
    [DelFlagEnum.DELETE]: '已删除',
  },
};

@Provide()
export class DownloadExcelService {
  @Inject()
  ctx: Context;

  /**
  * 导出excel文件,多表头的暂不考虑
   * @params options<Object>: {
   *   headers: [{}] // 表头
   *   data: [] // 数据
   *   dictMap?: {} // 字典映射
   *   sheetName?: string // sheet名称
   * }
  * */
  async downloadExcel(
    options: {
      headers: any[],
      data: any[],
      dictMap?: any,
      sheetName?: string,
    }
  ) {
    let data = options.data;
    // const dictMap = { ...commonExportMap, ...options.dictMap };
    const sheetName = options.sheetName || 'Sheet1';
    let workbook = new ExcelJS.Workbook();
    let worksheet = workbook.addWorksheet(sheetName);

    // 添加表头
    worksheet.columns = options.headers.map((column) => {
      const width = column.width;
      return {
        header: column.label,
        key: column.prop,
        width: isNaN(width) ? 16 : width,
      };
    });

    const dictMap = { ...commonExportMap, ...options.dictMap };

    // 数据过滤+排序
    data = data.map((item) => {
      const newItem = {};
      options.headers.forEach((field) => {
        const dataIndex = field.prop;
        const dataValue = _.get(item, dataIndex);
        if (dictMap && dictMap[dataIndex]) {
          newItem[dataIndex] = dictMap[dataIndex][dataValue] !== undefined ? dictMap[dataIndex][dataValue] : dataValue;
        } else {
          newItem[dataIndex] = dataValue;
        }
      });
      return newItem;
    });

    // 定义表头样式
    const headerStyle: any = {
      font: {
        size: 10,
        bold: true,
        color: { argb: 'ffffff' },
      },
      alignment: { vertical: 'middle', horizontal: 'center' },
      fill: {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: '808080' },
      },
      border: {
        top: { style: 'thin', color: { argb: '9e9e9e' } },
        left: { style: 'thin', color: { argb: '9e9e9e' } },
        bottom: { style: 'thin', color: { argb: '9e9e9e' } },
        right: { style: 'thin', color: { argb: '9e9e9e' } },
      },
    };

    const headerRow = worksheet.getRow(1);
    headerRow.eachCell((cell) => {
      cell.style = headerStyle;
    });

    // 添加数据
    data.forEach((item) => {
      worksheet.addRow(item);
    });

    worksheet.columns.forEach((column) => {
      column.alignment = { vertical: 'middle', horizontal: 'center' };
    });

    this.ctx.set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    // 这个地方的空格不要更改
    this.ctx.set('Content-Disposition', "attachment;filename*=UTF-8' '" + encodeURIComponent(sheetName) + '.xlsx');
    // this.ctx.set('Access-Control-Expose-Headers', 'Content-Disposition');
    return await workbook.xlsx.writeBuffer()
  }
}
修改user.service.ts
import { DownloadExcelService } from "@service/common/downloadExcel";
import { resBuild } from "@utils/resBuild";

@Provide()
export class UserService {
  @Inject()
  downloadExcelService: DownloadExcelService;

	
  // 导出
  async export(queryParams: ListUserDTO) {
    // 默认导出全部,去掉分页参数
    delete queryParams.pageNum;
    delete queryParams.pageSize;
    let headers = [
      { label: "用户编号", prop: "userId", },
      { label: "用户名称", prop: "userName", },
      { label: "用户昵称", prop: "nickName", },
      { label: "部门", prop: "dept.deptName", },
      { label: "手机号码", prop: "phonenumber", },
      { label: "状态", prop: "status", },
      { label: "创建时间", prop: "createTime", width: 25 },
    ];
    const { rows } = await this.list(queryParams)
    return this.downloadExcelService.downloadExcel({
      headers: headers,
      data: rows,
      sheetName: '用户信息',
    });
  }
}

测试一下,导出成功,默认导出全部,也能按条件导出了:
在这里插入图片描述

下期预告

《若依nodejs全栈(六:字典模块接口的实现)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2095004.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【R语言速通】1.数据类型

文章目录 0. 变量名1.基本数据类型1.1 数值型1.2 整型1.3 复数型1.4 逻辑型1.5 字符型 2.复合数据类型2.1 向量向量操作向量的常用函数 2.2 矩阵矩阵操作矩阵的常用函数 2.3 数组数组的操作数据的运算数组的访问数组的维度操作 数组的常用函数 2.4 数据框数据框操作数据框的常用…

Flutter安装问题解决

https://blog.csdn.net/Oven_maizi/article/details/126804404 首次运行 flutter dockor 报的错误&#xff0c;可以看到报错提示&#xff0c;安卓开发、win开发 还缺少依赖&#xff0c;但 web开发是可以的&#xff0c;那么只需要解决 网络资源不可用问题 PS E:\workspace\te…

生信机器学习入门4 - scikit-learn训练逻辑回归(LR)模型和支持向量机(SVM)模型

通过逻辑回归&#xff08;logistic regression&#xff09;建立分类模型 1.1 逻辑回归可视化和条件概率 激活函数 (activation function)&#xff1a; 一种函数&#xff08;如 ReLU 或 S 型函数&#xff09;&#xff0c;用于对上一层的所有输入进行求加权和&#xff0c;然后生…

计算机毕业设计hadoop+spark+hive酒店推荐系统 酒店数据分析可视化大屏 酒店爬虫 酒店预测系统 大数据毕业设计 Sqoop

《HadoopSparkHive酒店推荐系统》开题报告 一、研究背景与意义 随着互联网技术的飞速发展和人们生活水平的提高&#xff0c;旅游和酒店行业迎来了前所未有的发展机遇。然而&#xff0c;面对海量的酒店信息和多样化的用户需求&#xff0c;如何快速、准确地为用户推荐符合其需求…

vscode 远程SSH连接并配置C/C++开发环境

服务器配置 生成用户密钥 ssh-keygen -t rsa -b 4096 执行上面的命令后会在 ~/.ssh/ 目录生成密钥&#xff0c;然后导入密钥到认证文件中 cd .ssh/ cat id_rsa.pub >> authorized_keys最后将 id_rsa 传输到宿主机上 宿主机配置 安装插件 安装 remote-ssh 插件 配…

暑假学习内容简单总结

暑假总结 文章目录 暑假总结前言自动无限轮播图UITableView给不同组设置独立的cell设置cell宽度设置组间距折叠cell CALayer的简单使用CALayer实现一个视图裁剪CALayer的contentGravity的属性CALayer设置背景图自定义UIColor 网络请求正则表达式小结 前言 笔者在暑假通过几个项…

atcoder abc 369

A 369 问题&#xff1a; 思路&#xff1a;暴力枚举 代码&#xff1a; #include <bits/stdc.h>using namespace std;int main() {int a, b;cin >> a >> b;int cnt 0;for(int i -1000; i < 1000; i ) {vector<int> aa;aa.push_back(a);aa.push…

网络编程套接字(含Java示例)

文章目录 Socket套接字概念分类流式套接字&#xff1a;使用传输层TCP协议数据报套接字&#xff1a;使用传输层UDP协议Unix域套接字 TCP vs UDP有连接 vs 无连接可靠传输 vs 不可靠传输面向字节流 vs 面向数据报全双工 vs 半双工 UDP数据报套接字编程DatagramSocketDatagramPack…

AI安全前沿:模型攻击与防御策略

引言 随着chatGPT的横空出世&#xff0c;通用人工智能的时代正式开启。人工智能极大地影响了人类的生活方式和生产方式&#xff0c;例如以ChatGPT为代表的各类大模型&#xff0c;能够理解和生成人类语言&#xff0c;并以对话的方式同人类进行互动&#xff0c;能够执行撰写文本…

央视报道:国产时序数据库 IoTDB 刷新世界性能纪录!

IoTDB&#xff08;Internet of Things Database&#xff09;是一个专为物联网&#xff08;IoT&#xff09;场景设计的时间序列数据库管理系统&#xff08;TSDB&#xff09;。随着物联网技术的发展&#xff0c;大量的设备连接到互联网上&#xff0c;产生了海量的时间序列数据。这…

各位天命人!国自然评审意见出来了,那如何判断是否上会?

公众号&#xff1a;生信漫谈&#xff0c;获取最新科研信息&#xff01; 各位天命人&#xff01;国自然评审意见出来了&#xff0c;那如何判断是否上会&#xff1f;https://mp.weixin.qq.com/s?__bizMzkwNjQyNTUwMw&mid2247487055&idx1&sn1dc8b66e10323d37e477e88…

新版本 Redline 使用 Lua 字节码逃避检测

近日&#xff0c;研究人员观察到 Redline Stealer 木马的新变种&#xff0c;开始利用 Lua 字节码逃避检测。 遥测分布 根据遥测数据&#xff0c;Redline Stealer 木马已经日渐流行&#xff0c;覆盖北美洲、南美洲、欧洲和亚洲甚至大洋洲。 感染链 感染链 微软官方账户的 vcp…

基于Java的汽车推荐购买系统的设计与实现(论文+源码)_kaic

摘要 随着经济水平的不断提高&#xff0c;汽车销售行业存在激烈竞争&#xff0c;人们对于生活中汽车的需求也越来越多&#xff0c;而目前的汽车推荐购买管理由于存在管理不规范性等缺点&#xff0c;严重制约了汽车推荐购买和汽车公司的发展&#xff0c;因此需要设计一个汽车推荐…

一加8T安装 Kali NetHunter 高阶教程 KB2000刷机 param预载失败 高通9008驱动签名 小米刻晴主题

前言 本文包含一加8T手机刷入kaili nethunter 的详细版教程、资源、刷机知识思维导图、param预载失败问题解决、高通刷机驱动故障问题解决、小米刻晴主题等资源的分享 本机环境 手机&#xff1a;OnePlus 8T &#xff08;型号KB2000&#xff09; 系统&#xff1a;Android 13 …

公司招聘中,多个面试官对候选人评价不一致怎么办?

面试过程中&#xff0c;极易出现面试官评价标准不一的情况&#xff0c;为了有效解决这一问题&#xff0c;企业可以建立一套标准化的面试评分体系&#xff0c;在该体系中&#xff0c;应该详细包括统一的评分标准和评分细则&#xff0c;内容覆盖求职者的专业技能、沟通能力、团队…

【Threejs进阶教程-着色器篇】6. 2D SDF(三) 移动图形,限制图形,绘制多个图形

2D SDF 移动与合并图形 前五篇地址&#xff0c;建议按顺序学习本篇使用到的初始代码减小扩散范围clamp函数修改maxDistance来修改扩散范围 移动扩散中心添加第二个扩散点降低点的同步率调整参数来优化效果添加更多扩散点 完整源码如有不明白的&#xff0c;可以在下方留言或者加…

01:【stm32HAL】对GPIO的操作

对GPIO的操作 1、LED闪烁2、按键控制LED3、芯片调试接口被锁死导致无法下载程序 1、LED闪烁 使用的是STM32CubeMXKeilv5进行HAL库的开发。 开发的步骤&#xff1a; 第一步&#xff1a;新建工程 第二步&#xff1a;选择芯片 第三步&#xff1a;如下图哈哈 第四步&#xff1a;…

图像识别智能垃圾桶项目开发--语音命令识别垃圾

一、项目思维导图 二、语音模块配置信息 三、项目程序 main.c garbage.c garbage.h uartTool.c //串口发送数据 uartTool.h

IPv6配置实验(OSPFv3)

1.搭建拓扑图 2.配置接口IP地址

文件上传的学习

文件上传漏洞 文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷&#xff0c;而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马&#xff0c;病毒&#xff0c;恶意脚本或者WebShell等。“文件上传”本身没有…