Building fresh packages
错误描述
[4/4] Building fresh packages... [-/5] ⠄ waiting... [-/5] ⠄ waiting... [-/5] ⠄ waiting... [4/5] ⡀ phantomjs-prebuilt
|
如上,yarn 或者 npm install 的时候到了第四步 Building fresh packages 一直waiting,或者是直接报错。
错误原因
一直在转圈的 phantomjs-prebuilt ,或者是其他包在外网上,无法下载下来,需要配置国内镜像。
解决方案
在项目根目录下新建 .yarnrc
或者 .npmrc
文件,然后添加如下内容。
registry "https://registry.npm.taobao.org"
sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" phantomjs_cdnurl "http://cnpmjs.org/downloads" electron_mirror "https://npm.taobao.org/mirrors/electron/" sqlite3_binary_host_mirror "https://foxgis.oss-cn-shanghai.aliyuncs.com/" profiler_binary_host_mirror "https://npm.taobao.org/mirrors/node-inspector/" chromedriver_cdnurl "https://cdn.npm.taobao.org/dist/chromedriver"
|
装完之后可以删除了,有试过在 React 项目中 删除此文件以及 node-modules
然后重新安装依赖,没有报错或者一直waiting。
React 配置 webpack 之 alias 路径别名
这里提供的是使用 react-app-rewired
的方式。(README)
首先安装 react-app-rewired
。
yarn add react-app-rewired
|
然后在项目根目录创建 config-overrides.js
文件,在此文件中做个性化配置。
module.exports = function override(config, env) { return config; }
|
webpack配置均在 override
方法中配置,其他配置可以参照上面的 README。
在文件中添加如下配置以配置路径别名。
const path = require('path')
const resolvePath = dir => path.resolve(__dirname, dir)
module.exports = function override(config, env) { config.resolve.alias = { "@": resolvePath('./src'), "pages": resolvePath('./src/pages'), "components": resolvePath('./src/components'), "assets": resolvePath('./src/assets'), "router": resolvePath('./src/router'), "store": resolvePath('./src/store'), "utils": resolvePath('./src/utils'), } return config; }
|
在React中将table导出为Excel
业务背景
直接查询数据库数据,并将其结果渲染在表格内,可导出为Excel。
知识储备
Blob、FilReader、FileSaver、a、csv、Content-type、\ufeff、转义字符
数据处理
直接从数据库查数据,结果是完全不固定的,所以只能根据结果动态生成表格。首先处理列,将所有列都显示出来,且不会重复。
getDynamicColumns = (arr) => { let headerObj = {} const headerColumns = [] arr.forEach(item => { headerObj = { ...headerObj, ...item, } }) Object.keys(headerObj).forEach(item => { headerColumns.push({ dataIndex: item, title: item, align: 'center', width: 120, render: record => <Popover content={record}>{record || '-'}</Popover>, }) }) return headerColumns }
|
数据显示很简单,主要就是生成Excel了。
导出表格
在做的时候,尝试了不同的方法,这里会一一介绍。
方法一 Blob + FileSaver
思路:使用Blob生成FileSaver可以识别的文件流,然后调用FileSaver的saveAs方法下载。
实现:
首先安装 FileSaver 依赖。
然后在文件中引入 FileSaver。
import FileSaver from 'file-saver'
|
然后将数据处理成cvs文件形式。
注意:此段代码及之后的代码应位于你的导出方法之内
const { columns, data } = this.state let blobData = '\uFEFF' blobData += `${columns.map(item => item.dataIndex).join(',')} \n` data.forEach(item => { const itemData = [] columns.forEach(ele => { itemData.push(item[ele.dataIndex] || '-') }) blobData += `${itemData.join(',')}\n` })
|
通过上面这段代码,已经将数据转换为逗号分隔类型数据,第一行为表头,其他行为数据。
然后使用Blob将上面这段生成的 blobData 转换为Blob类文件格式。
const blob = new Blob([blobData], { type: 'application/vnd.ms-excel;charset=utf-8', });
|
最后使用FileSaver下载文件
FileSaver.saveAs(blob, `执行结果-${+new Date()}.xls`);
|
缺陷优化
打开生成的文件后,会发现长数字类型的字段全部变成了科学记数法,很尴尬是不是。
出现此问题是因为excel在处理数据时,将纯数字的字符串识别为数字类型,然后又因为长度太长转换为科学记数法了。
解决方法很简单,在第一步处理数据的时候额外处理一下纯数字就可以了。
data.forEach(item => { const itemData = [] columns.forEach(ele => { let val = item[ele.dataIndex] || '-' if ((+val).toString() === val) { val = `\t${ val.toString()}` } itemData.push(val) }) blobData += `${itemData.join(',')}\n` })
|
方法二 FileReader + Blob + FileSaver
思路:使用html模板,将表格元素放入模板中,然后通过FileReader 将模板文件字符串转成base64,再使用FileSaver下载文件。
实现:
首先写一个html模板
const tagName = 'table' const template = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40"> <head> <meta http-equiv=Content-Type content="text/html; charset=utf-8"> <meta name=ProgId content=Excel.Sheet> <!--[if gte mso 9]> <xml> <x:ExcelWorkbook> <x:ExcelWorksheets> <x:ExcelWorksheet> <x:Name>${tagName}</x:Name> <x:WorksheetOptions> <x:DisplayGridlines/> </x:WorksheetOptions> </x:ExcelWorksheet> </x:ExcelWorksheets> </x:ExcelWorkbook> </xml> <![endif]--> <style type="text/css"> table td { text-align: center; }; .header{ background-color:'gray' } .align-left {text-align: left !important;} .align-right {text-align: right !important;} .align-center {text-align: center !important;} </style> </head> <body> <table> ${document.getElementsByTagName('thead')[0].innerHTML} ${document.getElementsByTagName('tbody')[0].innerHTML} </table> </body> </htmlxmlns:o="urn:schemas-microsoft-com:office:office">`;
|
因为用了ant,所以在代码中体现的就是 thead 和 tbody 在两个table里面,只能分别放入模板中。
然后就使用fileReader+Blob来转换文件流。因为 fileReader 是读取文件,所以需要先通过Blob将模板转换为类文件,然后通过 fileReader 将其转换为base64。
const fileReader = new FileReader();
const blob = new Blob([`${template}`],{type:"application/vnd.ms-excel;charset=utf-8"});
fileReader.readAsDataURL(blob); fileReader.onloadend = () => { FileSaver.saveAs(fileReader.result, `执行结果-${+new Date()}.xls`); };
|
缺陷与优点
先说优点,就是可以设置表格的样式,以及当前工作区的名称。
缺陷:
首先是数字会变成科学记数法。可以通过改样式来解决。
td{ mso-number-format:'\@'; }
|
此类型为单元格的格式。可以在Excel中设置单元格格式,在自定义中看到。
还有一个缺陷就是,直接获取html标签,如果表格有多页,就无法获取到后面页的数据,需要点击其他页,重新导出。
解决思路就是,将数据自己生成为tbody放入模板中。
方法三 H5 a标签
思路:直接使用a标签的dowload属性下载文件。当前前提是先获取文件的url或者base64.
实现:
首先将数据处理成 csv形式。
const { columns, data } = this.state let blobData = '\uFEFF' blobData += `${columns.map(item => `${item.dataIndex}`).join(',')}\n` data.forEach(item => { const itemData = [] columns.forEach(ele => { let val = item[ele.dataIndex] || '-' if ((+val).toString() === val) { val = `\t${ val.toString()}` } itemData.push(val) }) blobData += `${itemData.join(',')}\n` })
|
然后对处理好的字符串进行编码,并拼接上base64的头信息
const uri = `data:text/csv;charset=utf-8,${encodeURIComponent(blobData)}`;
|
然后生成一个临时的a标签来执行其点击事件。
const link = document.createElement("a"); link.href = uri;
link.download = `执行结果-${+new Date()}.csv`; document.body.appendChild(link); link.click();
|
最后别忘了移除a标签
document.body.removeChild(link);
|
缺陷
缺陷?不能自定义样式吧。
总结
方法千千万,总有一个适合你。
React刷新当前组件
业务背景
当前页面有一个抽屉,在抽屉中,执行一定操作后需要重新调获取数据的接口更新form表单的数据,而当前抽屉结构较复杂,需要执行多个方法。为了方便起见,决定用刷新当前组件的方法去重新获取数据渲染组件。
如何刷新当前抽屉组件?
因为是个抽屉,就想到了在父组件中关闭再重新打开。然后我就试了一下。
onClose = () => { this.setState({ showSlideFrame: false }, () => { this.setState({ showSlideFrame: true }) }); };
|
然后出现了不可预料的问题,状态反了,看了一下代码,是项目上封装的问题。于是我又写了一个demo,用antd的Drawer试了一下,在完全没有其他操作的情况下虽然重新打开了抽屉,但是会有一个闪现的过程,非常丑,而且组件并没有被销毁,需要手动销毁,可看demo中的打开1。
然后经过一番百度,发现可以手动调用 componentDidMount()
方法。感觉自己发现了新大陆。赶紧试一下。
onCloseSide = () => { this.componentDidMount() };
|
完全好使,同样demo了一下,可看demo中的打开2。
DEMO
DEMO
拓展:路由跳转刷新组件
如果要刷新的组件是一个完整的页面的话,可以创建一个指向空白的路由,然后跳转到空白路由再跳转回来。
node-sass 安装失败
error D:\xxx\node_modules\node-sass: Command failed.
npm ERR! node-sass@4.14.0 postinstall: node scripts/build.js
npm i -g node-gyp windows-build-tools
降低node版本