关于antdesign:修改Ant-Design默认样式的方法及遇到的问题避坑必看

始终想钻研一下AntDesign组件库的组件款式,碰巧最近在Umi+Ant Design+dva.js的我的项目中,须要配置theme和对组件款式批改的问题,因而间接认真的钻研,并记录为文档。本想钻研明确再发文章总结,然而碍于例子不太好复现,因而每遇到一个问题,就在此记录。 本篇文章,以<Select>组件为例进行演示和解说。 1.批改款式的办法及须要留神的问题最简略的办法就是依据Ant Design Por官网文档API中提供的属性、Style笼罩、ClassNme等办法,对组件的款式进行批改,然而这并不能齐全满足咱们的需要。比方: <Select defaultValue="lucy" style={{width: 120, backgroundColor: 'red'}}></Select>在此处对select组件的backgroudColor进行批改,然而因为Antd组件自身就有了一个属性,因而出现成果如下:这并不满足咱们的需要,因而提供下文的一系列办法。 全局批改某一种组件的款式在src/global.less文件中增加如下代码:(此处,我尝试了放到其余.less文件中后果同样能够,然而为了治理不便,倡议波及到全局批改的款式,还是在global.less文件中) #root .ant-select-selection{//在src/global.less文件中增加background: red;}出现成果如下:此时咱们发现,两个Select选择器都变成了红色背景,可是如果咱们只想扭转某一个组件背景的话,咱们该怎么办呢? 全局批改某一种组件的款式(办法2)同样,在src/global.less文件中增加如下代码: :global .ant-select-selection{background: blue;}出现成果如下:款式没有扭转的起因,见下图:因为本来的.ant-select-selection中曾经存在background-color,同时:global的优先级又没有#root高,因而不会笼罩,解决这个问题的办法就是加上! important;代码如下: :global .ant-select-selection{background: blue !important;}出现成果如下:然而这仍然解决不了,咱们只想扭转某一个组件背景的问题,上面解决办法来了! 批改某一个组件的款式(办法一)实现代码如下: .selection{:global{ .ant-select-selection{ background-color: red !important; //设置色彩 }}}import styles from '../../page.less'//.less文件为上文中定义.selection所在文件<Select defaultValue="lucy" style={{width: 120}} className={styles.selection}>实现成果如下:(因为此处我只给第一个<Select>加了className) 批改某一个组件的款式(办法二)留神:这种办法会存在className被css loader编译成哈希字符串的状况,但提供了解决办法实现代码如下: :global{ .select_test{ //给该select框加的className .ant-select-selection{ background-color: red; } }}import styles from '../../page.less'//.less文件为上文中定义.select_test所在文件<Select defaultValue="lucy" style={{width: 120}} className={styles.select_test}></Select>此时咱们会发现并不失效,(起因是:className被css loader编译成哈希字符串)解决办法:(为了对立代码和浏览器class的统一,将select的className的增加形式换了一下) <Select defaultValue="lucy" style={{width: 120}} className="select_test"></Select>问题解决: (未完!) 参考资料:怎么批改antd默认款式及遇到的问题批改ant design 默认css款式 了解尚浅,望不吝赐教!

April 23, 2022 · 1 min · jiezi

关于antdesign:自我提升项目中React及JavaScript遇到问题记录

刚刚理解了费曼学习法,我认为有必要记录在我的项目开发中遇到的问题,进而不断丰富本人的教训。本篇文章用于记录在我的项目过程中遇到的问题及解决方案,进一步加深印象。 问题一:JavaScript数组在React中Setstate后数据变动但不渲染的问题及解决方案问题形容:如下图,基于Ant Design的Table组件进行数据渲染,同时实现点击后数据上移、下移、删除的操作。解决思路:通过点击回调函数获取到惟一标识符key值即序号。遍历数组判断是不是边界数据,即:上移时被点击的数据如果不是第一个、点击下移时的数据如果不是最初一个,则找到该条数据执行如下操作:被点击上移的key-1,被点击的数据上一条key+1后,依据key值排序,从而实现上移、下移的性能。性能实现代码如下: dealTableDataUp(text) { const {tableData} = this.state; let [...tableDataChange] = tableData; for (let i = 0; i < tableDataChange.length; i += 1) { if (i > 0) { if (text.key === tableDataChange[i].key) { tableDataChange[i].key = tableDataChange[i].key - 1; tableDataChange[i - 1].key = tableDataChange[i].key + 1; } } } tableDataChange = this.doSrotByAttribute(tableDataChange, `key`); this.setState({tableData: tableDataChange}); } dealTableDataDown(text) { const {tableData} = this.state; let [...tableDataChange] = tableData; for (let i = 0; i < tableDataChange.length; i += 1) { if (i !== tableDataChange.length - 1) { if (text.key === tableDataChange[i].key) { tableDataChange[i].key = tableDataChange[i].key + 1; tableDataChange[i + 1].key = tableDataChange[i].key - 1; } } } tableDataChange = this.doSrotByAttribute(tableDataChange, `key`); this.setState({tableData: tableDataChange}); } dealTableDataDelete(text) { const {tableData} = this.state; let [...tableDataChange] = tableData; let flag = false; for (let i = 0; i < tableDataChange.length; i += 1) { if (flag === true) { tableDataChange[i].key = tableDataChange[i].key - 1; } if (flag === false) { if (text.key === tableDataChange[i].key) { flag = true; tableDataChange.splice(i, 1); i -= 1; } } } tableDataChange = this.doSrotByAttribute(tableDataChange, `key`); this.setState({tableData: tableDataChange}); }//排序代码 doSrotByAttribute(arr, attribute) { function compare(propertyName) { return function (object1, object2) { const value1 = parseFloat(object1[propertyName]); const value2 = parseFloat(object2[propertyName]); if (value2 < value1) { return 1; } if (value2 > value1) { return -1; } return 0; } } arr.sort(compare(attribute)); return arr; }数据变动但不渲染问题起因:在Java Script中数组作为援用数据类型,定义的数组存储的是其首地址,而数据局部的变动是在堆中,因而当咱们从新SetState时,尽管console.log()的数据曾经变动了,然而不会从新进行渲染,因而不会显示。数据变动但不渲染问题解决办法:对原数组进行深拷贝,应用深拷贝后的数据进行SetState,此时原援用地址产生扭转,则会从新渲染。代码如下: ...

April 10, 2022 · 2 min · jiezi

关于antdesign:两步实现让antd与IDE和睦相处的处理案例

导读: Web IDE的开发素来是整个大数据平台开发中十分简约和轻便的一环,从零搭建一个 Web IDE 通常意味着大量的殚精竭虑和苦思冥想,工夫老本更是不可计数。两个UI组件库一起用更是bug的代名词,有没有什么方法能解决这个问题呢? 你能够看到 ▫ 一个新的web UI轻量级框架 ▫ 同用IDE组件库和antd产生的抵触如何解决 ▫ 它们如何在Taier上完满配合 Keep It Simple, Stupid. 这是开发人耳熟能详的 KISS 准则,也像是一句有调侃象征的善意揭示,揭示每个前端人,简洁易懂的用户体验和删繁就简的搭建逻辑就是前端开发的至简小道。 这也是袋鼠云数栈前端开发团队谋求的指标。 数栈是一个专一一站式产品体系,笼罩数据全链路开发流程,全面国产化兼容,外围源码自主可控的云原生一站式大数据开发治理平台。 2021年12月16日,基于数栈多年大数据开发教训的根底积淀出的轻量级 Web IDE 组件库,Molecule 开源。2022年2月22日,将Molecule 作为其中一个重要 UI 组件搭建出的分布式可视化的 DAG 任务调度零碎——Taier,也紧锣密鼓退出了开源社区。 在Taier通过数百家企业客户的生产环境实战测验之后的明天,咱们想用明天这篇文章跟大家分享一些在 Taier 的搭建过程中时遇到的Molecule 与antd如何适配的解决教训和它们在 Web IDE 开发中的实战体现。 【GitHub开源地址】 https://github.com/DTStack/Taier https://github.com/DTStack/mo... 何为Molecule与TaierTaier 是数栈搭建的一个可视化 DAG 任务调度零碎,其搭建初衷是为了让大数据开发工程师能够将精力集中在业务逻辑的开发上,而不必被工作盘根错节的依赖关系捆住手脚。这一指标就要靠其中 Web IDE 的局部实现。 而在数栈平台与日俱增的迭代中积淀而来的Molecule ,作为轻量级的 Web IDE UI 框架,能极大地缩小搭建编辑器的工夫,其具备的扩大(Extension)机制也使它能够轻易地应答各种需要增长。Molecule形象自数栈产品中离线工作的在线编辑配置性能,同时反哺撑持了各个产品中都存在的在线编写 SQL 代码。Molecule在各方面的不俗体现使咱们很快意识到,用Molecule来承当Taier零碎中的IDE组件角色简直是牵强附会。 IDE搭建,为何MoleculeMolecule之前,前端苦Web IDE久矣。 Web IDE的开发素来是整个大数据平台开发中十分简约和轻便的一环,从零搭建一个 Web IDE 通常意味着大量的殚精竭虑和苦思冥想,工夫老本更是不可计数。传统的 IDE 框架相熟门槛高,维护费用大,对保护人员的技术始终都有很高要求。数栈的研发团队对此深有体会,咱们开源的Molecule从最开始就对“开箱即用,保护不便”这个指标十分动摇,从开源以来它也在不断完善,当初咱们能够自信的说,Molecule将Web IDE UI框架做到了轻量级,高延展,在不就义需要可能性的前提下极大地晋升了前端开发体验,是一套具备残缺的解决方案的开发组件框架。 ...

March 25, 2022 · 1 min · jiezi

关于antdesign:Day-13100-Ant-Design-上传文件如何加token

前言今儿遇到了带宽上传文件的攻打(每秒150M+),导致了服务宕机1.5h。排查发现,上传文件没有token的参数。1、需要上传组件增加token 2、为什么要增加token?1)因为能够拦截非平台申请; 2)平台内申请如果异样,能够禁用那个用户; 3、增加组件headers参数1)页面组件<a-upload-dragger v-model:fileList="file" name="file" :action="apiLink" :beforeUpload="beforeUploadFile" :headers="requestHeaders" @change="handleChange"> <p class="ant-upload-drag-icon"> <inbox-outlined></inbox-outlined> </p> <p class="ant-upload-text"> 点击或拖拽到区域上传 </p></a-upload-dragger>2)header 传 tokendata(){ requestHeaders: { Authorization: 'Bearer ' + token //页面token },}

December 10, 2021 · 1 min · jiezi

关于antdesign:Antd中代码示例是怎么在CodeSandBox中打开的

image-20210906111439548 应用过Antd的小伙伴应该很相熟,Antd组件文档有在CodeSandBox和CodePen中关上间接预览和编辑的性能,这么炫酷且实用的性能具体是怎么实现的? codesandbox.io[1] 是一个前端代码的在线编辑器,反对各种不同的框架,能够随时预览代码的运行后果。创立沙盒“在CodeSandBox中关上”是CodeSandbox提供的性能,让咱们能够通过间接调用API来创立CodeSandbox沙盒。 CodeSandbox提供了几种导入到沙盒中预览的形式: 间接应用提供的公共模板从GitHub导入:https://codesandbox.io/s/github应用GitHubBox:将仓库地址中github.com替换为githubbox.comhttps://github.com/Iamxiaozhu... => https://githubbox.com/Iamxiao...装置浏览器扩大,关上GitHub,页面中会增加一个“在CodeSandBox中关上”的按钮通过命令行从本地导入: npm install -g codesandboxcodesandbox ./通过调用API形式创立沙箱[2]: CodeSandbox提供了通过API让咱们能够通过编程的形式来创立sandbox。咱们能够在文档里通过示例代码来创立sandbox,不便用户编辑和查看。 通过Get和Post申请调用https://codesandbox.io/api/v1/sandboxes/define,即可实现创立CodeSandbox沙箱。 Define API Get调用Demo[3]   Post调用Demo[4] Important:CodeSandBox官网Demo[5] Antd中示例代码跳转CodeSandbox、CodePen等:模板示例[6] 嵌入SandBox[7]CodeSandBox还反对间接嵌入:在文档,博客和其余网站中嵌入沙箱,能够展现代码和预览成果: 嵌入SandBox 以官网Demo[8]为例: 点击Share,这里抉择Embed 嵌入SandBox 自定义展现内容和主题,复制嵌入代码就能够了,是通过iframe标签来嵌套页面。 嵌入SandBox配置 相似CodeSandBox的在线编辑器有很多,比方:CodePen[9]、StackBlitz[10]、JSFiddle[11]、JSBin[12]、JSRun[13]等。 微软和GitHub也都推出了本人的在线代码编辑器(和下面几个不同,只提供了代码编辑性能,无奈实时预览): Online VS:https://online.visualstudio.com/GitHub CodeSpaces: https://github.com/features/c...其余相干:Code-Server[14]这里举荐一个能够自定义部署的在线代码编辑器:Code-Server。实际上就是VSCode的在线版本,反对装置VSCode插件,内嵌Terminal中会间接在服务器端运行,十分弱小。 Code-Server在线编辑器示例 Sandpack[15]Sandpack 是 CodeSandbox 的浏览器打包器。 demo 参考资料[1]codesandbox.io: https://codesandbox.io/ [2]通过调用API形式创立沙箱: https://codesandbox.io/docs/i... [3]Get调用Demo: https://codesandbox.io/s/6yzn... [4]Post调用Demo: https://codesandbox.io/s/qzlp... [5]CodeSandBox官网Demo: https://codesandbox.io/exampl... [6]模板示例: https://hub.fastgit.org/ant-d... [7]嵌入SandBox: https://codesandbox.io/docs/e... [8]官网Demo: https://codesandbox.io/s/reac... [9]CodePen: https://codepen.io/ [10]StackBlitz: https://stackblitz.com/ [11]JSFiddle: https://jsfiddle.net/ [12]JSBin: https://jsbin.com/ [13]JSRun: http://jsrun.net/ ...

September 6, 2021 · 1 min · jiezi

关于antdesign:antdesign填坑-select下拉选项随着滚动而定位失效-添加getPopupContainer-失效

1- 官网给的解释然而当我增加getPopupContainer 之后,定位任然生效 解决: .tel-box { width: 140px; display: inline-block; position: relative;}

July 28, 2021 · 1 min · jiezi

关于antdesign:FormItem中设置select的initialtValue

FormItem中设置select的initialtValue【状况形容】:编辑表单时,须要回显数据。【解决方案】:select的option已设置过value和label。只须要将详情接口的value赋值给initialtValue,他会主动显示对应的label。如果找不到,就间接显示这个value。

July 21, 2021 · 1 min · jiezi

关于antdesign:antd-vue-获取半选节点以及全选节点

一:效果图如下:二:实现过程(2-1)通过角色id获取须要要数据因为我这个性能是角色调配权限,所有这里是通过角色id获取已调配的权限菜单以及全副菜单而后在获取到树结构的菜单数据代码如下: /** * 调配权限按钮 */ permissions(row) { this.permissionsConfirmLoading = true getRoleTree(row.roleId) .then(res => { // 获取以后用户角色调配菜单的id data例如:[1002, 1003, 1004, 1213, 1214, 1000, 1001] this.checkedKeys = res.data.data.checkedMenuId // 获取菜单的全副id data例如[1002, 1003, 1004, 1213, 1214, 1000, 1001] this.allMenuIds = res.data.data.menuIds // 这一步是预防用户不点击节点选项间接保留 this.menuIds = res.data.data.checkedMenuId.join(',') return getMenuTree() }) .then(response => { this.treeData = response.data.data this.roleId = row.roleId this.permissionsConfirmLoading = false this.permissionsModalVisible = true }) }(2-2)页面的配置代码如下: <a-modal :visible="permissionsModalVisible" title="调配权限" :width="740" @cancel="permissionsModalVisible = false" > <a-spin :spinning="permissionsConfirmLoading"> <a-tree ref="roleTree" :checked-keys="checkedKeys" :selected-keys="selectedKeys" checkable :check-strictly="checkStrictly" :expanded-keys="expandedKeys" :tree-data="treeData" :replace-fields="{ key:'id' }" @check="onChangePermissionsTree" @select="onTreeNodeSelect" @expand="onExpand" /> </a-spin> <template slot="footer"> <a-dropdown style="float: left" :trigger="['click']" placement="topCenter"> <a-menu slot="overlay"> <a-menu-item key="1" @click="switchCheckStrictly(1)"> 父子关联 </a-menu-item> <a-menu-item key="2" @click="switchCheckStrictly(2)"> 勾销关联 </a-menu-item> <a-menu-item key="3" @click="checkALL"> 全副勾选 </a-menu-item> <a-menu-item key="4" @click="cancelCheckALL"> 勾销全选 </a-menu-item> <a-menu-item key="5" @click="expandAll"> 开展所有 </a-menu-item> <a-menu-item key="6" @click="closeAll"> 合并所有 </a-menu-item> </a-menu> <a-button> 树操作 <a-icon type="up" /> </a-button> </a-dropdown> <a-button @click="permissionsModalVisible = false"> cancal </a-button> <a-button type="primary" @click="permissionsModalSubmit"> ok </a-button> </template> </a-modal>(2-3)对应的办法代码如下: ...

August 17, 2020 · 2 min · jiezi

关于antdesign:Ant-Design-Editable-Tree-Table-可编辑树形表格

Ant Design Editable Tree TableAnt Design of React - Editable Tree Table可编辑的树形表格 Need in the work, but i did not find ready-made components easy to understand after Baidu and Google.工作中须要,但百度谷歌后并没有发现现成的、可用的、易了解的组件。 So i try to do it.所以我本人尝试写了下。 installrecommend using yarn举荐应用 yarn yarnbuildnpm run buildexample GitHubhttps://github.com/blueju/ant-design-editable-tree-table

August 6, 2020 · 1 min · jiezi

组件库重构支持antd-4x

前言React 15.x 升 React 16.x 是一次内部重构,对于使用者来说,原来的使用方式仍然可用,额外加了新的功能;而Antd 3.x 升 Antd 4.x, 在我的认知范围里,可以称作是飞(po)跃(huai)性的重构, 因为以前很多写法都不兼容了,组件代码重构,使用者的代码也得重构。但这次重构解决了3.x的很多问题,比如: 由于Icon无法按需加载导致打包体积过大;由于Form表单项变化会造成其他表单项全量渲染,大表单会有性能问题;时间库moment包体积太大。说这么多,还是直接来张图吧,我个人项目的打包体积变化:Antd 3.x VS Antd 4.x 升4.x之后,gzip少了150kb,也就是包大小少了500多kb,这不香么。关于升级我和我的小组,为了用更爽的方式来开发迭代,针对于antd的Form和Table等组件做了一些简单的二次封装,形成了组件库antd-doddle。虽然Antd 4.x推出快小半年了,受疫情影响,今年业务迭代比较缓慢,没有新系统,也觉得暂时没必要去重构业务代码,所以一直只关注不动手。最近比较闲,组件库针对Antd 4.0做了适应性重构,作为一个胶水层,最大程度的去磨平4.0版本Form这种破坏性变动,减少以后业务代码升级4.x版本的调整量。 Antd 4.x到底做了哪些变化,在官方文档可以看到。 这篇文章主要讲针对于4.x Form的变化,我重构组件库的思路。 Antd-doddle 2.x文档地址, 支持4.x: http://doc.closertb.site, 首次加载较慢,请耐心等候。 Antd-doddle 1.x文档地址, 支持3.x: http://static.closertb.site, 首次加载较慢,请耐心等候。 试用项目Git 地址 项目在线试用地址, 请勿乱造 FormGroup重构思路Form组件变化4.x 中除了Icon,最大的更改就在于Form,我自己感受到的变化是: 舍去了Form.create高阶组件包裹表单的写法,而改采用hooks或ref的方式去操作form实例;以前每个表单组件的数据绑定是通过getFieldDecorator这个装饰方法完成,现在改由FormItem来完成;初始values设置,以前是通过getFieldDecorator设置,并且是动态的,即value改变,表单值跟随改变。现在是在Form最外层设置,但是以defaultValue形式设置,即value改变,表单值不跟随变化;最大的改变就是增量式更新,3.x版本,任意表单项改变,都会造成Form.create包裹的全部表单项重新render,这是非常大的性能消耗;而4.x之后,任意表单项改变,只有设置了shouldUpdate属性的表单项有可能执行render,类似于React 16新增的componentShouldUpdate;根据上面的变化点,由外向内层层剖析,针对性的做重构; FormGroup的变化由于以前FormGroup组件,除了收集form方法和公共配置,也作为一个标识,接管了组件内部的渲染层;3.x版本其form实例由Form.create,即业务代码提供;4.x与其相似,只不过是通过hooks生成form实例。 变化点主要在于4.x版本Form要提供initialValues的设置,且这是一个defaultValue的设置,所以我们需要拓展,让其支持values为异步数据时,表单项的值能跟随其改变, 其原理很简单,监听value的变化,并重置表单数据,实现代码如下: // 伪代码,只涉及相关改动const FormGroup = (props, ref) => { const { formItemLayout = layout, children, datas = {}, ...others } = props; // 兼容了非hooks 组件调用的写法,内部再声明一个ref, 以备用; const insideRef = useRef(); const _ref = ref || insideRef; const formProps = { initialValues: {}, // why ...formItemLayout, ...others }; // 如果datas 值变化,重置表单的值 useEffect(() => { const [data, apiStr] = Type.isEmpty(datas) ? [undefined, 'resetFields'] : [datas, 'setFieldsValue']; // 函数式组件采用form操作; if (props.form) { props.form[apiStr](data); return; } // 如果是类组件,才采用ref示例更新组件 if (typeof _ref === 'object') { _ref.current[apiStr](data); } }, [datas]); return ( <Form {...formProps} ref={_ref}> {deepMap(children, extendProps, mapFields)} </Form>);};上面有句代码 initialValues: {},会让人困惑, 为什么没有赋值为datas呢;这个又是antd的一个隐藏知识点,举个例子: ...

July 2, 2020 · 2 min · jiezi

rcpress-基于React的文档生成器

前言以前开发vue组件时,写文档使用的是vuepress,之后转战react后觉得没有顺手的文档生成工具,就模仿vuepress写了这个rcpress。 特点RcPress 是一个基于 React.js 的静态文档生成器。文档UI是模仿 ant design 官网功能配置模仿Vuepress支持mdx,可以在markdown中使用jsx。支持service worker。生产模式下支持生成静态html页面和打包spa两种模式。开发模式下支持ssr,spa两种模式。技术栈ReactJsAnt Designmdxremarkprismjsservice worker快速上手安装安装命令行工具 @rcpress/cli yarn global add @rcpress/cli# 或者如果你用npmnpm i @rcpress/cli -g用法创建目录以及markdown文件 # 创建 docs 目录(docs是默认的文档目录)mkdir docs#创建markdown文件echo '# Hello RcPress' > docs/README.md运行 # 启动spa模式的服务rcpress dev# 启动服务端渲染的服务rcpress server# 访问`3000`端口即可。打包构建 # 在生产环境下构建sparcpress build# 在生产环境下构建ssr并且声称静态html文件rcpress generate文档获取详细的文档, 推荐访问网站上的向导一节。 首页截图 与vuepress的对比首先说下不同点 rcpress 使用了 react.js 驱动,而 vuepress 是由 vue 驱动的。rcpress 是使用了 Ant Design 作为 UI 框架,而 vuepress 是使用了自定义的样式。说下欠缺的功能 没有plugin(插件)这个概念,当然以后可以考虑加入。说下优势 可以在文档里使用所有ant design的组件,不用自己写。支持在开发模式下运行spa,ssr两种模式。vuepress貌似只能运行spa模式。支持生产spa打包。相关链接GitHub 仓库地址: rcpress文档地址: rcpress

November 5, 2019 · 1 min · jiezi

createreactapp中按需加载antd

使用create-react-app脚手架创建出来的react项目,他的配置项是隐藏的,如果想要修改原始的配置项,需要npm run eject,但是这个操作是不可逆的,一般情况下我们不会去直接修改他的原始配置项。 那么如果我想在用create-react-app脚手架创建的项目中使用antd design 官方推荐的按需加载要怎么添加自己的配置项呢?此时我们可以用 react-app-rewired 来实现。 第一步:安装antd按需加载的插件babel-plugin-import npm install babel-plugin-import --save-dev第二步:安装react-app-rewired $ npm install react-app-rewired --save-devreact-app-rewired是一个再配置的工具。安装完之后在根目录新建一个config-overrides.js的文件,在这个配置文件中新增加自己的自定义配置,可以实现在不eject的情况下自定义配置。 第三步:安装customize-cra npm install customize-cra --save-dev使用customize-cra要确保先安装了react-app-rewired。接下来就可以来配置按需加载了。首先在项目的根目录下新建一个config-overrides.js文件,接下来在这个文件中写我们自己的配置 const { override, fixBabelImports } = require('customize-cra');module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }))之后在组件中测试 import React, { Component } from 'react';import { Button } from 'antd';class Test extends Component { render() { return ( <div> <Button type="primary">点击</Button> </div> ) }}export default Test;这样就可以实现按需加载antd的组件,并且无需手动引入样式了。 ...

July 17, 2019 · 1 min · jiezi

antd-design-中表格数据导出为excel支持多表头

主要用到了better-xlsx和file-saver两个库import { File } from 'better-xlsx';import { saveAs } from 'file-saver';function ExportExcel(column, dataSource, fileName = 'example') { // 新建工作谱 const file = new File(); // 新建表 let sheet = file.addSheet('sheet-test'); // 获取表头行数 let depth = getDepth(column); // 获取表头的列数 let columnNum = getColumns(column); // 新建表头行数 let rowArr = []; for (let k = 0; k < depth; k++) { rowArr.push(sheet.addRow()); } // 根据列数填充单元格 rowArr.map(ele => { for (let j = 0; j < columnNum; j++) { let cell = ele.addCell(); cell.value = j; } }); // 初始化表头 init(column, 0, 0); // 按顺序展平column let columnLineArr = []; columnLine(column); // 根据column,将dataSource里面的数据排序,并且转化为二维数组 let dataSourceArr = []; dataSource.map(ele => { let dataTemp = []; columnLineArr.map(item => { dataTemp.push({ [item.dataIndex]: ele[item.dataIndex], value: ele[item.dataIndex], }); }); dataSourceArr.push(dataTemp); }); // debugger; // 绘画表格数据 dataSourceArr.forEach((item, index) => { //根据数据,创建对应个数的行 let row = sheet.addRow(); row.setHeightCM(0.8); //创建对应个数的单元格 item.map(ele => { let cell = row.addCell(); if (ele.hasOwnProperty('num')) { cell.value = index + 1; } else { cell.value = ele.value; } cell.style.align.v = 'center'; cell.style.align.h = 'center'; }); }); //设置每列的宽度 for (var i = 0; i < 4; i++) { sheet.col(i).width = 20; } file.saveAs('blob').then(function(content) { saveAs(content, fileName + '.xlsx'); }); // 按顺序展平column function columnLine(column) { column.map(ele => { if (ele.children === undefined || ele.children.length === 0) { columnLineArr.push(ele); } else { columnLine(ele.children); } }); } // 初始化表头 function init(column, rowIndex, columnIndex) { column.map((item, index) => { let hCell = sheet.cell(rowIndex, columnIndex); // 如果没有子元素, 撑满列 if (item.title === '操作') { hCell.value = ''; return; } else if (item.children === undefined || item.children.length === 0) { // 第一行加一个单元格 hCell.value = item.title; hCell.vMerge = depth - rowIndex - 1; hCell.style.align.h = 'center'; hCell.style.align.v = 'center'; columnIndex++; // rowIndex++ } else { let childrenNum = 0; function getColumns(arr) { arr.map(ele => { if (ele.children) { getColumns(ele.children); } else { childrenNum++; } }); } getColumns(item.children); hCell.hMerge = childrenNum - 1; hCell.value = item.title; hCell.style.align.h = 'center'; hCell.style.align.v = 'center'; let rowCopy = rowIndex; rowCopy++; init(item.children, rowCopy, columnIndex); // 下次单元格起点 columnIndex = columnIndex + childrenNum; } }); } // 获取表头rows function getDepth(arr) { const eleDepths = []; arr.forEach(ele => { let depth = 0; if (Array.isArray(ele.children)) { depth = getDepth(ele.children); } eleDepths.push(depth); }); return 1 + max(eleDepths); } function max(arr) { return arr.reduce((accu, curr) => { if (curr > accu) return curr; return accu; }); } // 计算表头列数 function getColumns(arr) { let columnNum = 0; arr.map(ele => { if (ele.children) { getColumns(ele.children); } else { columnNum++; } }); return columnNum; }}export default ExportExcel;引入文件,只要传入表头字段和表格数据即可。

July 2, 2019 · 2 min · jiezi

基于ReactDvaJSUmiJSAntdesignexpressmongo搭建的CMS内容管理后台

项目地址前端部分:React+Ant-design+DvaJS+UmiJS - 地址(欢迎star&fork&issue)后端部分:express+mongoose+redsi(鉴权)+pm2 - 地址(欢迎star&fork&issue)项目简介此项目为初中级项目,主要为理解React+Ant-design+DvaJS+UmiJS搭建中后台系统结构后端项目为通用模型,包含用户管理,角色管理,内容管理,评论管理,内容分类管理,适用于任何内容型产品后台(博客、社区等,可以直接套个简易版CNode)项目预览在线演示地址

June 18, 2019 · 1 min · jiezi

react-antd-开发时关闭和开启热更新

在开发时页面模型比较大 每次保存文件浏览器都会刷新这时需要禁止浏览器实时刷写页面先暴露配置文件npm run eject 或 yarn eject 修改webpack 的配置文件configwebpackDevServer.config.js60行默认hot:true修改为hot:false

May 31, 2019 · 1 min · jiezi

前端小知识10点2019528

1、火狐(firefox)的mouseenter问题 <MyIcon onMouseEnter={e => { this.mouseEnter(e,); }} onBlur={() => {}} onMouseLeave={e => { this.mouseOut(e,); }}/>onMouseEnter事件在火狐上会不断地触发mouseenter和mouseleave事件,所以需要先设置一个flag=false,在onMouseEnter时设为true,在onMouseLeave设为false,让不断触发的onMouseEnter事件只触发一次即可 this.state={ flag:false}mouseEnter(){ if(!this.state.flag){ //...do something this.setState({ flag:true, }) }}mouseOut(){ this.setState({ flag:false, })}2、ESLint Unary operator '++' usedi++是不安全的,所以用i+=1 //badfor(let i=0;i<a.length;i++)//goodfor(let i=0;i<a.length;i+=1)3、兼容 ie11之 SVG 的transform旋转从 0 度 //非IE可以这样写svg.style('transform', `rotate(0deg)`)//IE需要这么写svg.attr('transform',`rotate(0,0 0)`)转到 180 度 //非IE可以这样写svg.style('transform', `rotate(180)`)//IE需要这么写svg.attr('transform', `rotate(180,0 0)`)详情请参考:https://www.zhangxinxu.com/wordpress/2015/10/understand-svg-transform/ 4、border-block-end边界块结束 border-block-end: 1px solid #d5d5d5;第一次知道这个属性,好像是新边框属性,但兼容性不太好,IE11 不兼容,所以还得改回下面这样: border-bottom: 1px solid #d5d5d5;5、调整 svg 中<g>标签的位置使用<g>标签自带的transform属性 <g transform={'translate(0 30) scale(1.4)'}></g>6、get请求中的参数有中文,ie11无法识别使用encodeURI()方法来识别,也不影响其他浏览器 encodeURI( url )7、document.activeElement.tagName返回文档中当前获得焦点的元素 ...

May 28, 2019 · 1 min · jiezi

jQuery源码解析之click的事件绑定

前言:这篇依旧长,请耐心看下去。 一、事件委托DOM有个事件流特性,所以触发DOM节点的时候,会经历3个阶段:(1)阶段一:Capturing 事件捕获(从祖到目标)在事件自上(document->html->body->xxx)而下到达目标节点的过程中,浏览器会检测 针对该事件的 监听器(用来捕获事件),并运行捕获事件的监听器。 (2)阶段二:Target 目标浏览器找到监听器后,就运行该监听器 (3)阶段三:Bubbling 冒泡(目标到祖)在事件自下而上(document->html->body->xxx)到达目标节点的过程中,浏览器会检测不是 针对该事件的 监听器(用来捕获事件),并运行非捕获事件的监听器。 二、$().click()作用:为目标元素绑定点击事件 源码: //这种写法还第一次见,将所有鼠标事件写成字符串再换成数组 //再一一绑定到DOM节点上去 //源码10969行 jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup contextmenu" ).split( " " ), function( i, name ) { //事件绑定 // Handle event binding jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? //如果有参数的话,就用jQuery的on绑定 this.on( name, null, data, fn ) : //否则使用trigger this.trigger( name ); }; } );解析:可以看到,jQuery 将所有的鼠标事件都一一列举了出来,并通过jQuery.fn[ name ] = function( data, fn ) { xxx } ...

May 27, 2019 · 6 min · jiezi

支持大数据渲染下拉列表组件开发-SuperSelect基于antd-Select

功能简介antd 的 Select 组件不支持大数据量的下拉列表渲染,下拉列表数量太多会出现性能问题,SuperSelect 基于 antd 封装实现,替换原组件下拉列表,只渲染几十条列表数据,随下拉列表滚动动态刷新可视区列表状态,实现大数据量列表高性能渲染。 特性 基于 antd Select 组件,不修改组件用法替换 antd Select 组件的下拉列表部分实现动态渲染列表初步测试 10w 条数据不卡顿实现方案 使用 antd Select dropdownRender 方法自定义原组件下拉列表部分对自定义列表项绑定原 Select 组件的各项方法和回调函数支持同步使用 antd 组件下拉列表样式在线地址使用基本使用同 antd Select,只是使用 SuperSelect 代替 Select import SuperSelect from 'components/SuperSelect';import { Select } from 'antd';const Option = Select.Option;const Example = () => { const children = []; for (let i = 0; i < 100000; i++) { children.push( <Option value={i + ''} key={i}> {i} </Option> ); } return ( <SuperSelect showSearch // mode="multiple" // onChange={onChange} // onSearch={onSearch} // onSelect={onSelect} > {children} </SuperSelect> );};问题多选模式选择后鼠标点击输入框中删除等图标时不能直接 hover 时获取焦点直接删除,需要点击两次 ...

May 22, 2019 · 6 min · jiezi

前端小知识10点2019518

1、当给数组的index赋负数或小数时,数组的长度有无变化? let arr=[] arr[10]=11 console.log(arr.length); //11 arr[-1]=-1 console.log(arr.length) //11 arr[3.14]=3.14 console.log(arr.length) //11 //=================================== let arr1=[] arr1[2.1]=2.1 console.log(arr1[2.1],'arr144') //2.1 console.log(arr1.length,'arr145') //0 arr1[1]=1 console.log(arr1.length,'arr147') //2 //======================== let arr2=[] arr2[-1]=-1 console.log(arr2.length,'arr253') //0 //======================== let arr3=[] arr3[5]=5 console.log(arr3.length,'arr258') //6由此可见,array的length属性只计算非负整数下标!不计算负数、小数 2、antd-pro 项目热更新慢并且是在95%(emitting)时卡住怎么办? 本人实际上是less文件里多写了个逗号。。。。???? 3、less 子类名使用 active span { position: relative; .leftIcon{ border-radius: 17px; } &.active { background:rgba(94,112,231,1); .leftIcon{ background:rgba(255,255,255,1); } } }4、antd 的 Spin 组件不认识 undefined(Spin 组件的 spinning 属性只对 true/false 生效),如果是 undefined 状态会是一直读取的状态 ...

May 18, 2019 · 1 min · jiezi

antdesignvue-Table组件和分页组件的自定义

最近在个新项目里开发CMS端,vue技术栈和antd的UI框架表格table使用链接:antd的table分页pagination使用链接:antd的pagination 表格单独使用时,自带简单分页,只包含 上一页, 页面码, 下一页,例如: 但是有时候产品和甲方的需求很奇葩,非得可选的pageSize,显示总数等功能,文档里有时候也阐述不全,甚至不对,关键时候还要靠自己动动脑子,看代码: //template部分 <a-table :pagination="pagination" :rowSelection="rowSelection" :columns="columns" :dataSource="dataList" @change="TableChange" rowKey="id" >data(){ return{ pagination:{ defaultPageSize:5, showTotal: total => `共 ${total} 条数据`, showSizeChanger:true, pageSizeOptions: ['5', '10', '15', '20'], onShowSizeChange:(current, pageSize)=>this.pageSize = pageSize } } }, defaultPageSize 设置默认一页table里多少个条目 showTotal 用于显示数据总量和当前数据顺序 showSizeChanger:true 是否可以改变 pageSize,这个很重要一定要加上,很多网上的教程里都没写 pageSizeOptions 以arr形式设置每页可以展示多少条的选项 onShowSizeChange:()=>{} 点击切换每页可以展示多少条的下拉框,会触发这个方法,可以理解为监听“xx条/页”下拉框的方法,网上很多文章都是用showSizeChange,实践证明并不能用,文档上也没写明“onShowSizeChange”这个方式,所以在这要跟大家强调一下。

May 10, 2019 · 1 min · jiezi

antd-常用表单组件

basicFormUtil.js import React from 'react'import { Form, Input, Tooltip, Icon, Cascader, Select,Radio, Row, Col,DatePicker, Checkbox, Button, AutoComplete} from 'antd';import moment from 'moment';import 'moment/locale/zh-cn';moment.locale('zh-cn');const { Option } = Select;const { TextArea } = Input;const CheckboxGroup = Checkbox.Group;const RadioGroup = Radio.Group;const { MonthPicker, RangePicker,WeekPicker } = DatePicker;class basicFormUtil extends React.Component{ constructor(props){ super(props); } handleSubmit = (e) => { e.preventDefault(); this.props.form.validateFieldsAndScroll((err, values) => { if (!err) { console.log('Received values of form: ', values); } }); } getInput(item){ if(item.tag =="Input"){ return <Input {...item.props }/> }else if(item.tag =="TextArea"){ return <TextArea {...item.props }/> }else if(item.tag =="Checkbox"){ return <CheckboxGroup {...item.props } /> }else if(item.tag =="Radio"){ return <RadioGroup {...item.props } /> }else if(item.tag =="Select"){ const options = item.props.options.map(d => <Option key={d.value}>{d.label}</Option>); return <Select {...item.props }>{options}</Select> }else if(item.tag =="RangePicker"){ return <RangePicker {...item.props }/> }else if(item.tag =="DatePicker"){ return <DatePicker {...item.props }/> }else if(item.tag =="Button"){ return <Button {...item.props }>{item.props.text}</Button> }else{ return <div style={{color:"red"}}>表单配置信息有误</div> } } getElement (){ const { getFieldDecorator } = this.props.form; let elements = [] this.props.formItme.forEach((item,index)=>{ if(item.tag){ elements.push( <Form.Item key={item.tag+index} {...item.tailFormItemLayout} label={item.lable} > {getFieldDecorator(item.id, item.getFieldDecoratorOption)( this.getInput(item) )} </Form.Item> ) } }) return elements } render(){ return( <Form {...this.props.formItemLayout} onSubmit={this.handleSubmit}> {this.getElement( )} </Form> ) }}const WrappedBasicForm = Form.create({ name: 'basicForm' })(basicFormUtil);export default WrappedBasicForm;调用basicFormUtil.js ...

May 9, 2019 · 2 min · jiezi

react离开页面自定义弹框拦截路由拦截

前言:项目有个需求是:跳转路由,在离开页面前,需要弹框询问用户是否确定离开。用react-router的<Prompt>组件是可以的,但是,怎么使用antd组件(或者说自定义组件)呢?请看下面 先看的这个:https://stackoverflow.com/questions/32841757/detecting-user-leaving-page-with-react-router(1)使用react-router的<Prompt> import { Prompt } from 'react-router'<Prompt message="你确定要离开嘛?"/> (2)<Prompt>支持函数 <Prompt when={true} message={(location) => { return window.confirm(`confirm to leave to ${location.pathname}?`); }}/> (3)history.block,这个是个坑! import { history } from 'path/to/history';class MyComponent extends React.Component { componentDidMount() { history.block(targetLocation => { // take your action here return false; }); } render() { //component render here }}坑的原因是history.block()方法是不能和<Modal>组件并列使用的,而必须在history.block()内部去调用<Modal>组件(就是注释take your action here那里),这就导致一个问题:<Modal>组件里的 onOk、onCancel 如何返回值给 history.block()的 return 语句(return false/true)的同时,不让history.block()的 return 语句和 <Modal>组件顺序执行呢? 说白点就是,<Modal>组件先显示出来,阻塞后面的 return false/true,等 <Modal>组件的 onOk、onCancel 方法执行后,再 return false/true ...

May 8, 2019 · 1 min · jiezi

如何在antdPro中使用自定义SVG图标

1、项目使用的是svg图片,一般这样调用: import CustomIcon from './aa.svg';render(){ return(<img src={CustomIcon}/>)}或者是这样:使用 js 文件来存储 svg,并能直接在<Icon>组件中使用: dd.js: import React from 'react';const cc = props => ( <svg width="1em" height="1em" viewBox="0 0 28 28" > <g stroke="xxx" strokeWidth={2} fill="none" fillRule="evenodd"> <path d="xxx" strokeLinecap="xxx" /> </g> </svg>);export default bb;使用: import bb from './dd'render(){ return(<Icon component={bb} />)}但是!不能直接将svg作为Icon的component:ee.svg: <?xml version="1.0" encoding="UTF-8"?><svg width="28px" height="28px" viewBox="0 0 28 28" xxx> <desc></desc> <g xxx></g></svg>ff.js: import Ee from './ee.svg'render(){ return(<Icon component={Ee} />)}这样程序报错 2、将自定义SVG直接作为Icon组件的注意点如下: 以上截图出自antd的Icon组件 3、但是我使用的antdPro框架,配置文件是config.js和 plugin.config.js,没有webpack.config.js文件,所以不知道怎么配置webpack,如何解决? 解决方案:(1)在config.js中添加这行代码: urlLoaderExcludes: [/.svg$/], (2)在plugin.config.js中添加 ...

May 1, 2019 · 1 min · jiezi

antd的table进行列筛选时,更新dataSource,为什么table显示暂无数据?

我想当然地认为只要dataSource改变,那么<Table>组件就会重新渲染,但是有一种特殊情况例外:在onFilter()中不写筛选条件,在调用filterDropdown 进行列筛选的时候,通过handleSearch改变/保存dataSource的状态,此时<Table>重新渲染,但是拿的不是dataSource={xxx},而是拿的filterDropdown中的onFilter()中的dataSource,而onFilter中是没有写代码的,所以返回暂无数据。 PS:解释下我不在onFilter()中写代码的原因,因为我已将dataSource保存到state中,所以需要setState去更改dataSource数据,但是onFilter()方法是在componentDidUpdate()周期调用的,所以setState会报错,所以我想到了在onClick中setState,但这样console.log出来,dataSource更改了,但是table显示暂无数据。 示例代码: handleSearch=()=>{ this.setState({dataSource:dataSourceB})}getColumnSearchProps = (dataIndex) => ({ filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, }) => ( <div> <Input value={selectedKeys[0]} onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])} onPressEnter={() => this.handleSearch(selectedKeys, confirm)} /> <Button onClick={() => this.handleSearch(selectedKeys, confirm)} > Search </Button> </div> ), //筛选条件,没有写代码,所以没有数据返回,所以是暂无数据 onFilter: (value, record) =>{ }, })render{ return( <Table column={ [{...this.getColumnSearchProps('name')}} dataSource={dataSourceA} > ) }示例代码地址:https://ant.design/components/table-cn/#components-table-demo-custom-filter-panel 列筛选逻辑的流程图如下: onFilter()的源码: getLocalData(state?: TableState<T> | null, filter: boolean = true): Array<T> { const currentState: TableState<T> = state || this.state; const { dataSource } = this.props; let data = dataSource || []; // 优化本地排序 //就是这行代码,通过slice,另开内存来保存dataSource data = data.slice(0); const sorterFn = this.getSorterFn(currentState); if (sorterFn) { data = this.recursiveSort(data, sorterFn); } // 筛选 if (filter && currentState.filters) { Object.keys(currentState.filters).forEach(columnKey => { const col = this.findColumn(columnKey) as any; if (!col) { return; } const values = currentState.filters[columnKey] || []; if (values.length === 0) { return; } const onFilter = col.onFilter; data = onFilter ? data.filter(record => { return values.some(v => onFilter(v, record)); }) : data; }); } return data; }onFilter()的源码地址:https://github.com/ant-design/ant-design/blob/d922c377cba03bef39ddb7d271fce3f67c353be9/components/table/Table.tsx ...

April 22, 2019 · 1 min · jiezi

前端小知识10点(2019.4.14)

1、React.PureComponent 与 React.Component 的区别React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过 prop 和 state 的浅对比来实现 shouldComponentUpate()React.Component: class A extends React.Component{ //xxx}React.PureComponent: class B extends React.PureComponent{ //xxx}注意:如果 props 和 state 包含复杂的数据结构,React.PureComponent 可能会因深层数据不一致而产生错误的否定判断,即 state、props 深层的数据已经改变,但是视图没有更新。 2、shouldComponentUpate() 的机制只要 state、props 的状态发生改变,就会 re-render,即使 state、props 的值和之前一样 有三种办法优化 shouldComponentUpate 生命周期(1)只用简单的 props 和 state 时,考虑 PureComponent (理由如 第 1 点)(2)当开发者知道 深层的数据结构 已经发生改变时使用 forceUpate() (3)使用 不可变对象(如 Immutable.js) 来促进嵌套数据的快速比较 3、React 强制更新状态之 forceUpdate()我们都知道,当 state、props 状态改变时,React 会重渲染组件。 但如果你不用 props、state,而是其他数据,并且在该数据变化时,需要更新组件的话,就可以调用 forceUpdate(),来强制渲染 举个例子: class A extends Component { this.a=1 Add(){ this.a+=1 this.forceUpdate() } //调用Add() ...}流程:当调用 forceUpdate() 方法后 ...

April 22, 2019 · 2 min · jiezi

Antd中List组件的复用

最近在使用List组件的时候,发现组件没有重新渲染,导致状态发送错误。首先,我复现下代码。function Other() { const [data, setData] = useState([6, 7, 8, 3, 4, 5]) function handleData() { setData([6, 7, 8, 9]) } function handleData1() { setData([6,7,8]) } return ( <> <Button onClick={handleData}>12</Button> <Button onClick={handleData1}>12</Button> <List dataSource={data} renderItem={(item)=><ListData list={item}/>}> </List> </> )}function ListData(props) { const [check, setCheck] = useState(false) console.log(check) return <div onClick={() => setCheck(!check)}>{props.list}{check?" true":" false"}</div>}export default Other这里我们使用了List组件,这是一个很常用的组件。但是当我们点击按钮的时候,发现6,7,8三个组件 都没有重新渲染。 我们很容易 就会想到 map key唯一性的问题。 但是如果我们这样改 <List dataSource={data} renderItem={(item,index)=><ListData list={item} key={index}/>}> </List> //or <List dataSource={data} renderItem={(item,index)=><ListData list={item} key={item}/>}> </List>我们会发现 依然没有用 。组件还是没有被重复渲染当我们查看 antd文档的时候,也没有关于该解决方案的 props。所以我们得去查看源码 renderItem = (item: any, index: number) => { const { renderItem, rowKey } = this.props; let key; if (typeof rowKey === ‘function’) { key = rowKey(item); } else if (typeof rowKey === ‘string’) { key = item[rowKey]; } else { key = item.key; } if (!key) { key = list-item-${index}; } this.keys[index] = key; return renderItem(item, index); };so, 看到这段代码,看名字我们也可以猜出 rowKey这个属性,就是我们需要使用的方法了。所以我们将代码修改 <List dataSource={data} rowKey={()=>uuid()} renderItem={(item,index)=><ListData list={item} key={index}/>}> </List>这样就可以解决,组件没有被重新渲染的问题了 ...

March 29, 2019 · 1 min · jiezi

React+Antd+Redux实现待办事件

之前也是写过一篇关于Redux的文章,来简单理解一下Redux,以及该如何使用。今天我就来分享一个也是入门级别的,React+Redux+antd来实现简单的待办事件。同时也讲讲自己对Redux的理解。先来看一张图吧:我们简单的比喻来让我们更加好的理解Redux,我们这样比喻(图书馆借书):1.React Component:借书人2.Action Creators:你要说你要借书这句话,肯定要说话吧,就是一句话:我要借书3.Store:图书馆管理员4.Reducer:图书馆管理员肯定不可能记得所有书,那么Reducer就是作为一本小册子,供图书馆管理员查通俗理解:我要借书,我要先说话,告诉图书馆管理员我要借书,当图书馆管理员知道了之后,但是它不可能知道所有的书籍在哪里,所以需要一本小册子去找,最后找到了之后,再送到你手上。专业术语理解:(Component)要借书,我要先说话(Action ),告诉图书馆管理员(Store)我要借书,当图书馆管理员知道了之后,但是它不可能知道所有的书籍在哪里,所以需要一本小册子(Reducer)去找,最后找到了之后,再送到你(Component)手上。当你看图觉得蒙的时候你再看看这个比喻是不是更好理解了?流程我们大概清楚了,我们就开始来看怎么写这个待办事项吧。我们先来列一个提纲吧,屡清楚思路再写代码。1.react component(todolist.js)2.引入antd3.写store4.写reducer5.写action大概就是上面的一些流程:如何引入antd呢?官方文档:链接描述文件目录结构如下:创建文件之前,首先创建图书馆管理员(store),他不知道书具体在哪里,所以再创建小册子(redux),给到图书馆管理员(store)://src/redux/index.jsimport {createStore} from ‘redux’;import reducer from ‘./reducer’const store=createStore(reducer);export default store;//src/redux/reducer.jsconst defaultState={ inputValue:’’, list:[1,2]}export default(state=defaultState,action)=>{ return state;}*注释:刚开始state,这里一定要给state赋一个初始值,才不会报错接下来你就可以,在todolist.js中用store.getState()获取到store的值,我把他直接赋值给状态:我先实现一个由Component发送action,store收到action,在由reducer接受处理,最后返回一个新的状态,Component接收显示://src/redux/TodoList.jsimport React from ‘react’;import ‘antd/dist/antd.css’;import { Input,Button,List} from ‘antd’;import store from ‘./index’;export default class TodoList extends React.Component{ constructor(props){ super(props); this.state=store.getState(); } componentDidMount(){ console.log(this.state); } handleChg=(e)=>{ const action={ type:‘change_input_value’, inputValue:e.target.value } store.dispatch(action); } render(){ console.log(this.state) return( <div style={{marginTop:“10px”,marginLeft:“20px”}}> <Input placeholder=“请输入” style={{width:“400px”,marginRight:“10px”}} onChange={this.handleChg} value={this.state.inputValue}/> </div> </div> ); } }思路:我们通过input框中监听内容变化发送action,reucer去处理//src/redux/reducer.jsconst defaultState={ inputValue:’’, list:[1,2]}export default(state=defaultState,action)=>{ if(action.type===‘change_input_value’){ const newState=JSON.parse(JSON.stringify(state)) newState.inputValue=action.inputValue; return newState; } return state;}你可以打印出newState看一下,你就会发现inputValue就是你输入的值了。接下来的就可以举一反三了。完整代码:///src/redux/index.jsimport {createStore} from ‘redux’;import reducer from ‘./reducer’const store=createStore(reducer);///src/redux/reducers.jsexport default store;const defaultState={ inputValue:’’, list:[1,2]}export default(state=defaultState,action)=>{ if(action.type===‘change_input_value’){ const newState=JSON.parse(JSON.stringify(state)) newState.inputValue=action.inputValue; return newState; } if(action.type===‘send_message’){ const newState=JSON.parse(JSON.stringify(state)) newState.list.push(newState.inputValue); newState.inputValue=’’; return newState; } if(action.type===‘delete_message’){ const newState=Object.assign({},state); newState.list.splice(action.index,1); return newState; } return state;}///src/redux/todoList.jsimport React from ‘react’;import ‘antd/dist/antd.css’;import { Input,Button,List} from ‘antd’;import store from ‘./index’;const data=[ 1,2,3];export default class TodoList extends React.Component{ constructor(props){ super(props); this.state=store.getState(); store.subscribe(this.F5) } componentDidMount(){ console.log(this.state); } handleChg=(e)=>{ const action={ type:‘change_input_value’, inputValue:e.target.value } store.dispatch(action); } handleSend=()=>{ const action={ type:‘send_message’, } store.dispatch(action); } F5=()=>{ this.setState(store.getState()); } handleItem=(index)=>{ const action={ type:‘delete_message’, index:index } store.dispatch(action); } render(){ console.log(this.state) return( <div style={{marginTop:“10px”,marginLeft:“20px”}}> <Input placeholder=“请输入” style={{width:“400px”,marginRight:“10px”}} onChange={this.handleChg} value={this.state.inputValue}/> <Button type=“primary” onClick={this.handleSend}>发送</Button> <div style={{width:“400px”,marginTop:“10px”}}> <List bordered dataSource={this.state.list} renderItem={(item,index) => (<List.Item onClick={this.handleItem.bind(this,index)}>{item}</List.Item>)}/> </div> </div> ); } }//index.jsimport React from ‘react’;import ReactDOM from ‘react-dom’;import ‘./index.css’;import TodoList from ‘./redux/TodoList’;ReactDOM.render(<TodoList />, document.getElementById(‘root’));这样就实现了一个利用redux来实现简单的待办事项.相信你如果写完这个demo之后,肯定对Redux大致有了了解。如果自己在写的过程中有什么疑惑,欢迎提出,我会给你解答。后期也会更新一些关于Redux的其他方面的知识。 ...

March 13, 2019 · 1 min · jiezi

Ant Desing Pro2.0(四)与服务端交互

写在前面1 新建页面1.1 在src->pages->『新建文件夹』NewPage->『新建js文件』NewPage.js 和 『新建less文件』NewPage.less1.2 在NewPage.js填入如下代码// 必须引入import React, { PureComponent } from “react”;//面包屑import PageHeaderWrapper from “@/components/PageHeaderWrapper”;// 引入阿里dva框架,不然不能和服务端交互,必须引入import { connect } from “dva”;// 引入lessimport styles from “./NewPage.less”;// 这个注解解释起来有点麻烦,但要注意以下几点// 1.@connect必须放在export default class前面// 2.这个不写,你在这个页面里面获取不到服务器返回给你的数据// 3.采用解构赋值的方式,第一个参数newPage是命名空间,我们数据就是从这里拿到的@connect(({ newPage, loading }) => ({ data: newPage.data, // 将data赋值给 loading: loading}))class NewPage extends PureComponent { // componentWillMount渲染之前调用,一般处理ajax异步回来的数据, // 等下面render渲染的时候好绑定 componentWillMount() { console.log(“渲染之前调用”); console.log(“之调用一次”); } // 每次调用render之前渲染 componentDidMount() { // 分发器,用dispatch一定要写@connect注解 const { dispatch } = this.props; // 分发器调用models发起请求,具体流程是dispatch=>models=>services dispatch({ // newPage命名空间,fetch是该文件中的方法,对应src/models/newPage.js,因为newPage的namespace的值newPage type: “newPage/fetch”, // 参数,一般采用json格式 payload: { a: “1”, b: “2” } }); } render() { let { data,loading } = this.props; console.log(loading); return ( <PageHeaderWrapper> <div> 姓名:{data.userName}<br/> 学号:{data.studentNo}<br/> 年龄:{data.age}<br/> 性别:{data.sex}<br/> </div> </PageHeaderWrapper> ); }}export default NewPage;1.3 在NewPage.less填入如下代码//样式文件默认使用 CSS Modules,如果需要,你可以在样式文件的头部引入 antd 样式变量文件://这样可以很方便地获取 antd 样式变量并在你的文件里使用,有利于保持页面的一致性,也方便实现定制主题。@import “~antd/lib/style/themes/default.less”;2 创建Models2.1 在src->models->『新建js文件』NewPage.js2.2 填入以下代码// 导入接口文件,并采用解构的方式,// 将newPage.js的文件里面的queryUser1赋值给这里的queryUser1import { queryUser1 } from “@/services/newPage”;export default { namespace: “newPage”, // State 是储存数据的地方,收到 Action 以后,会更新数据。 state: { data: {} }, effects: { /** * @param payload 参数 * @param call 执行异步函数调用接口 * @param put 发出一个 Action,类似于 dispatch 将服务端返回的数据传递给上面的state * @returns {IterableIterator<*>} / fetch({ payload }, { call, put }) { // 访问之前可以做一些操作 const response = yield call(queryUser1, payload); // 拿到数据之后可以做一些操作 yield put({ // 这行对应下面的reducers处理函数名字 type: “save”, // 这是将最后的处理数据传递给下面的reducers函数 payload: response }); } // * fetch2({ payload }, { call, put }) { // const response = yield call(queryCurrent); // yield put({ // type: “saveCurrentUser”, // payload: response // }); // } }, reducers: { /** * * @param state * @param action * @returns {{[p: string]: *}} */ save(state, action) { console.log(action); return { …state, // es6三点运算符合,有点模糊解释不清楚 data: action.payload // 上面与服务器交互完的数据赋值给data,这里的data 对应最上面 state 里面的data }; } }}; ...

March 7, 2019 · 2 min · jiezi

Ant Desing Pro2.0(三)设置代理

写在前面1.Ant Desing Pro2.0(一)项目初始化2.Ant Desing Pro2.0(二)新增页面1.修改文件1.1 在config->config.js->『将82行和88行的注释打开』 proxy: { ‘/server/api/’: { // 代理前缀,请求格式:http://localhost:8000/server/api/资源地址 target: ‘http://www.example.com’, // 代理目标地址 changeOrigin: true, // 是否跨域访问 pathRewrite: { ‘^/server’: ’’ }, // 最终请求时候忽略掉server }, }, 举个例子吧1.有个接口 http://www.example.com/api/test,请求之后会返回{“message”:“200”}2.按照上面的的代理配置ant desing pro会发一个『http://localhost:8000/server/api/test』请求,之后会去掉『http://localhost:8000/api/test』最终代理成『http://www.example.com/api/test』2.测试问题如何测试呢?我准备放在后面更服务器进行交互的时候再说

March 6, 2019 · 1 min · jiezi

Ant Desing Pro2.0 (二)新增页面

参考资料[1.ant design pro][1]1.新增页面1.1 在src->pages->『新建文件夹』NewPage->『新建js文件』NewPage.js 和 『新建less文件』NewPage.less1.2填入如下代码// 这是NewPage.js内容import React, { PureComponent } from “react”;//面包屑import PageHeaderWrapper from “@/components/PageHeaderWrapper”;// 引入lessimport styles from “./NewPage.less”;class NewPage extends PureComponent { render() { return ( <PageHeaderWrapper> <div> HELLO WORD! </div> </PageHeaderWrapper> ); }}export default NewPage;// 这是NewPage.less内容//样式文件默认使用 CSS Modules,如果需要,你可以在样式文件的头部引入 antd 样式变量文件://这样可以很方便地获取 antd 样式变量并在你的文件里使用,有利于保持页面的一致性,也方便实现定制主题。@import “~antd/lib/style/themes/default.less”;2.配置路由2.1 在config->router.config.js->『47行新增如下内容』 // 这行是新增的内容 { path: “/newPage”, icon: “file”, name: “newPage”, routes: [ { path: “/newPage/newPage”, name: “newPage”, component: “./NewPage/NewPage” } ], }, 3.查看效果

March 6, 2019 · 1 min · jiezi

Ant Desing Pro2.0(一)

1.写在前面最近做毕设的时候发现网络上关于ant designpro2.0版本的基础入门资料太少,我一个后端开发人员入门当时也是跌跌撞撞,现在我将我所学的分享出来,避免大家少走一些弯路。2.开发环境你的本地环境需要安装 node 和 git ant desing pro框架基于 ES2015+、React、UmiJS、dva、g2 和 antd,提前了解和学习这些知识会非常有帮助。3.参考资料1.[ant desing pro官网][1]1.下载项目源码1.12.删除无关代码3.安装依赖以及运行

March 6, 2019 · 1 min · jiezi

Ant Design UI组件之Select踩坑

Ant Design UI组件之Select踩坑前言在使用Ant design UI组件时总会遇到一些奇奇怪怪的问题,在本篇中将总结中在使用Select中几种容易常见的问题,持续更新遇到的问题在初始化Select值,如何根据value显示对应文本实现代码如下…this.props.form.setFieldsValue({ latticeId, latticeNo, goodsId, remaining});…<FormItem {…formItemLayout} label=“商品”> {getFieldDecorator(‘goodsId’, { })( <Select style={{ width: ‘150px’ }}> {this.state.goodsData.map((item,index) => <Option key={item.goodsId} >{item.goodsId +’-’ + item.goodsName}</Option>)} </Select> )} </FormItem> 此时,代码实现的效果并不如预期效果,显示出了商品的id在尝试加上value属性的时候出现了一个问题查阅相关文档是支持number的,百思不得其解。了解到项目使用版本是2.13.10版本的,怀疑是版本问题。查阅对应版本的文档,问题就出现在这里,在2.13.11版本中value是还不支持number类型的,只支持string。在了解到问题的根源后,修改相应代码。…this.props.form.setFieldsValue({ goodsId: goodsId && goodsId.toString(),});…<FormItem {…formItemLayout} label=“商品”> {getFieldDecorator(‘goodsId’, { })( <Select style={{ width: ‘150px’ }}> {this.state.goodsData.map((item,index) => <Option key={item.goodsId} value={item.goodsId.toString()}>{item.goodsId +’-’ + item.goodsName}</Option>)} </Select> )} </FormItem>Antd select如何设置能够实现输入筛选在使用select实现输入筛选时只需要在Select中加上showSearch即可,不过需要注意的是默认是根据value值筛选,需要使用内容实现输入筛选的话可以使用设置optionFilterProp属性为"children"。 总结在使用Ant Design UI组件时遇到一些不符合预期的错误时,可以查看是否是因版本问题导致的,才能对症下药第二个问题出现是因为一开始有人告知筛选属性只能根据value去筛选而忽略了去查看官方文档,而采用蹩脚的方式去实现,花费了较长时间。再去查看相应官方文档由于英文不好,没有理解到官方文档的意思。还是需要加强对英文官方文档的理解。在使用组件时最好能帮该组件的属性都熟悉理解一遍,不要偷懒只听从他人的,很多问题的出现都可以从官方文档中找到想要的答案。

February 27, 2019 · 1 min · jiezi

Ant Design源码分析(三):Wave组件

Wave组件效果预览 在上一篇文章Button组件的源码分析中遇到了一个Wave组件, Wave组件在Ant design中提供了通用的表单控件点击效果,在自己阅读源码之前,也并没有过更多留心过在这些表单控件的动画效果是如何实现的,甚至可能有时都没注意到这些动画效果。下面先一起来看以下具体的效果: 看完UI效果之后我们大概已经知道是什么了,再看代码部分,由于代码书写顺序与阅读顺序并不一致,为了方便理解,我们在分析源码的过程中,会调整代码解释的顺序源码分析// 一个新的依赖,暂时不知道是什么,依据名字推测与动画效果有关import TransitionEvents from ‘css-animation/lib/Event’;export default class Wave extends React.Component<{insertExtraNode?: boolean}> { //… some type code // 我们发现Wave组件只提供组件逻辑,不参与UI展示,这种容器组件,往往在DidMount或WillMount声明周期中开始 // 构建组件逻辑,往下看 render() { return this.props.children; } // 只有一行代码, 先看下方bindAnimationEvent方法 componentDidMount() { this.instance = this.bindAnimationEvent(findDOMNode(this) as HTMLElement); } // 在组件卸载时,清除掉事件监听与定时器,避免内存泄漏 componentWillUnmount() { if (this.instance) { this.instance.cancel(); } if (this.clickWaveTimeoutId) { clearTimeout(this.clickWaveTimeoutId); } } // 根据名字推测: 为DOM节点绑定动画效果,进入函数内部查看 bindAnimationEvent = (node: HTMLElement) => { //… some code const onClick = (e: MouseEvent) => { //… some code // 无论是否正在执行动画,先清除掉动画效果(至于怎么清除,先不关注) this.resetEffect(node); // 从target取得颜色值 const waveColor = getComputedStyle(node).getPropertyValue(‘border-top-color’) || // Firefox Compatible getComputedStyle(node).getPropertyValue(‘border-color’) || getComputedStyle(node).getPropertyValue(‘background-color’); // 在这里可以看到之前定义的私有变量clickWaveTimeoutId,被用作储存一个定时器 this.clickWaveTimeoutId = window.setTimeout(() => this.onClick(node, waveColor), 0); }; // 监听node(this.props.children)的onClick事件 node.addEventListener(‘click’, onClick, true); // 将移除监听事件的回调函数封装在一个对象中并作为返回值,看着这里应该想起之前定义的私有变量instance, // 回顾DidMount生命周期函数,你会发现这个返回的对象将会被储存在instance中 return { cancel: () => { node.removeEventListener(‘click’, onClick, true); }, }; } //未完待续我们通过观察上方bindAnimationEvent方法,其主要做了三件事,调用了两个外部函数this.resetEffect 与 this.onClick 1、过滤不执行的条件(代码省略掉了,非主干逻辑) 2、声明onClick函数,并作为node的点击事件触发时要执行的函数 3、返回一个储存了取消监听click事件方法的对象个人认为bindAnimationEvent方法,做了太多的事情,而ComponentDidMount做的事情太少(单一原则) 往下方,继续查看 this.resetEffect 与 this.onClick 分别是做什么的,以及如何实现的 // 这个函数是实现动画效果的核心,其主要有三个行为:1、创建内联style标签, 2、插入css字符串 3、并将其插入到document中 // 我们知道css也是可以控制DOM变化的,比如伪类元素:after :before 这里正是通过:after来实现效果的 onClick = (node: HTMLElement, waveColor: string) => { //… some code 1 const { insertExtraNode } = this.props; /* 创建了一个div元素extraNode,装饰该div,在inserExtracNode= true时,将extraNode作为node的子元素 / / 创建一个div元素,并缓存中私有变量extraNode中 / this.extraNode = document.createElement(‘div’); / 这里用let 更合适 / const extraNode = this.extraNode; extraNode.className = ‘ant-click-animating-node’; // 可能有人好奇这个extraNode是干嘛的? // 往之后的代码中看的时候会发现,在insertExtraNode为false时,Wave通过插入伪类元素:after 来作为承载动画效果的DOM元素 // 在insertExtraNode = true时,会生成一个div替代:after伪类元素,猜测是某些this.props.children可能自带:after,所以 // 使用div元素来替代:after避免冲突,在这里我们只需要知道它是作为承载动画css的DOM元素即可 // 获取指定的string insertExtraNode ? ‘ant-click-animating’ : ‘ant-click-animating-without-extra-node’; const attributeName = this.getAttributeName(); // Element.removeAttribute(‘someString’); 从element中删除值为someString的属性 // Element.setAttribute(name, value); 为element元素值为value的name属性 node.removeAttribute(attributeName); node.setAttribute(attributeName, ’true’); // 行为1:这里创建了一个内联style标签 styleForPesudo = styleForPesudo || document.createElement(‘style’); if (waveColor && waveColor !== ‘#ffffff’ && waveColor !== ‘rgb(255, 255, 255)’ && this.isNotGrey(waveColor) && / 透明度不为0的任意颜色 / !/rgba(\d, \d*, \d*, 0)/.test(waveColor) && // any transparent rgba color waveColor !== ’transparent’) { /* 给子元素加上borderColor / extraNode.style.borderColor = waveColor; / 行为2:在内联style标签中插入样式字符串, 利用伪元素:after作为承载效果的DOM / styleForPesudo.innerHTML = [ant-click-animating-without-extra-node]:after { border-color: ${waveColor}; }; / 行为3:将style标签插入到document中 / if (!document.body.contains(styleForPesudo)) { document.body.appendChild(styleForPesudo); } } / 在inserExtarNode为true时,将extraNode插入到node子元素中 / if (insertExtraNode) { node.appendChild(extraNode); } / 为元素增加动画效果 / TransitionEvents.addEndEventListener(node, this.onTransitionEnd); } /* * 重置效果 * 顾名思义:这个函数通过三个行为,致力于一件事情,取消动画效果 * 1、删除node的attribute属性 2、node的子元素 3、删除对应的内联style标签 / resetEffect(node: HTMLElement) { // come code … const { insertExtraNode } = this.props; const attributeName = this.getAttributeName(); / 行为1:删除node的attribute属性 / node.removeAttribute(attributeName); / 行为3: 清空了为伪类元素内置的style标签 styleForPesudo */ this.removeExtraStyleNode(); if (insertExtraNode && this.extraNode && node.contains(this.extraNode)) { // Node.removeChild() 方法从DOM中删除一个子节点。返回删除的节点。 node.removeChild(this.extraNode); } TransitionEvents.removeEndEventListener(node, this.onTransitionEnd); } // 删除内联style标签 removeExtraStyleNode() { if (styleForPesudo) { styleForPesudo.innerHTML = ‘’; } } // 在每次动画执行结束后,清除掉状态,完成一个生命周期 onTransitionEnd = (e: AnimationEvent) => { // todo if (!e || e.animationName !== ‘fadeEffect’) { return; } this.resetEffect(e.target as HTMLElement); }}组件逻辑:我们回过头来看第一部分的代码,组件逻辑体现在componentDidMount 与 componentWillUnMount中1、在componentDidMount中为this.props.children的click事件绑定动画执行函数this.onClick,2、在componentWillUnMount中清除与动画相关的状态避免内存泄漏。组件运行逻辑:此时,Wave组件的代码与逻辑已经全部分析完了,整个Wave组件的运行逻辑可以通过下方这张图来概括本篇完~ ...

February 25, 2019 · 2 min · jiezi

webpack4+react+antd+axios+router4+redux 学习以及脚手架搭建_014

webpack4 学习脚手架搭建安装和初始化首先附上官方的文档github地址https://github.com/xiaopingzh…会不定时更新,如果觉得有帮助到你,给个Star当做鼓励可好。.├── README.md├── build│ ├── webpack.dev.conf.js│ ├── webpack.dll.conf.js│ └── webpack.prod.conf.js├── dist├── dll├── manifest.json├── package-lock.json├── package.json├── public│ ├── favicon.ico│ └── index.html├── src│ ├── components│ │ ├── Bread│ │ │ └── Bread.js│ │ └── SiderBar│ │ └── SiderBar.js│ ├── index.js│ ├── layouts│ │ └── BasicLayout.js│ ├── pages│ │ ├── Counter│ │ │ └── Counter.js│ │ └── Home│ │ └── Home.js│ ├── redux│ │ ├── actions│ │ │ └── counter.js│ │ ├── reducer.js│ │ ├── reducers│ │ │ └── counter.js│ │ └── store.js│ ├── request│ │ └── request.js│ ├── router│ │ └── Router.js│ └── util│ └── loadable.js└── yarn.lock新创建一个目录并初始化npm,在本地安装webpack,再安装webpack-cli>npm initThis utility will walk you through creating a package.json file.It only covers the most common items, and tries to guess sensible defaults.See npm help json for definitive documentation on these fieldsand exactly what they do.Use npm install &lt;pkg&gt; afterwards to install a package andsave it as a dependency in the package.json file.Press ^C at any time to quit.package name: (webpack4)version: (1.0.0)description:entry point: (index.js)test command:git repository:keywords:author:license: (ISC)About to write to /Users/xiaopingzhang/UCloud/webpack4/package.json:{ “name”: “webpack4”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo "Error: no test specified" && exit 1” }, “author”: “”, “license”: “ISC”}Is this OK? (yes) yes初始化之后按照提示一步步往下就可以了,可以输入该项目的描述等等信息。一开始也没有关系,后面也还可以更改。下一步 本地安装webpack,再安装webpack-clinpm install webpack webpack-cli –save-dev==–save-dev 是你开发时候依赖的东西,–save 是你发布之后还依赖的东西。==>npm install webpack webpack-cli –save-dev> fsevents@1.2.7 install /Users/xiaopingzhang/UCloud/webpack4/node_modules/fsevents> node installnode-pre-gyp WARN Using needle for node-pre-gyp https download[fsevents] Success: “/Users/xiaopingzhang/UCloud/webpack4/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node” is installed via remote> webpack-cli@3.2.1 postinstall /Users/xiaopingzhang/UCloud/webpack4/node_modules/webpack-cli> lightercollective *** Thank you for using webpack-cli! ***Please consider donating to our open collective to help us maintain this package. https://opencollective.com/webpack/donate ***npm WARN webpack4@1.0.0 No descriptionnpm WARN webpack4@1.0.0 No repository field.+ webpack-cli@3.2.1+ webpack@4.29.0added 458 packages from 239 contributors and audited 5208 packages in 18.624sfound 0 vulnerabilities安装好之后,也会显示安装的哪个版本,一般安装没有啥问题。实在安装不成功,试一下全局安装。2.新建src文件夹,入口的js文件和html文件。.├── index.html├── package.json└── src └── index.jsindex.js文件const component = () => { let element = document.createElement(“div”); element.innerHTML = “webpackworks”; return element;};document.body.appendChild(component());index.html<!DOCTYPE html><html> <head> <title>Start</title> </head> <body> <script src="./dist/main.js"></script> </body></html>3.学会使用webpack编译文件输入 npx webpack>npx webpackHash: 9ad2a368debc9967c1f4Version: webpack 4.29.0Time: 269msBuilt at: 2019-01-27 21:15:22 Asset Size Chunks Chunk Namesmain.js 1.01 KiB 0 [emitted] mainEntrypoint main = main.js[0] ./src/index.js 218 bytes {0} [built]WARNING in configurationThe ‘mode’ option has not been set, webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable defaults for each environment.You can also set it to ’none’ to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/再用浏览器打开index.html,查看网页是否正常的显示了。webpack 把入口文件 index.js 经过处理之后,生成 main.js配置文件经过第一部分的尝试,已经初步了解webpack的作用,这一部分通过配置文件进行相应的一些设置。babelBabel 把用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本。 这一过程叫做“源码到源码”编译, 也被称为转换编译。npm install –save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0新建babel配置文件.babelrc{ “presets”: [ “es2015”, “react”, “stage-0” ], “plugins”: []}//babel-core 调用Babel的API进行转码//babel-loader//babel-preset-es2015 用于解析 ES6//babel-preset-react 用于解析 JSX//babel-preset-stage-0 用于解析 ES7 提案新建配置文件webpack.base.conf.jswebpack.dev.conf.jswebpack.prod.conf.js分别是公共配置,开发配置,生产配置。目前目录结构为.├── build│ ├── webpack.base.conf.js│ ├── webpack.dev.conf.js│ └── webpack.prod.conf.js├── dist│ └── main.js├── index.html├── package.json└── src └── index.js加载js/jsx文件npm install –save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0在 src 目录下新建.babelrc{ “presets”: [ [ “env”, { “targets”: { “browsers”: [">1%", “last 3 versions”] } } ], “stage-2”, “latest”, “react” ], “plugins”: [ “syntax-dynamic-import”, “transform-class-properties”, <!–[–> <!– “import”,–> <!– {–> <!– “libraryName”: “antd”,–> <!– “libraryDirectory”: “es”,–> <!– “style”: true–> // “style”: “css” //主题设置 <!– }–> <!–]–> 不用antd 可以去掉 ]}文件新增 { test: /.(js|jsx)$/, exclude: /(node_modules|bower_components)/, //排除 include: [ path.resolve(__dirname, ‘../src’) ], //包括 use: { loader: ‘babel-loader’ } },加载CSS文件npm install –save-dev style-loader css-loader在配置文件里添加 { test: /.css$/, use: [“style-loader”, “css-loader”] }加载图片npm install –save-dev url-loader file-loader在配置文件里添加 { test: /.(png|jpg|gif)$/, use: [ { loader: “url-loader”, options: { limit: 8192 } } ] }options limit:8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求。加载less在这个踩了一个坑,记得安装 lessnpm install –save-dev less-loader less更改antd 默认主题设置需要,不用的话应该把相应的设置忽略即可。 { test: /.less$/, use: [ { loader: ‘style-loader’ }, { loader: ‘css-loader’ // translates CSS into CommonJS }, { loader: ’less-loader’, // compiles Less to CSS options: { modifyVars: { ‘font-size-base’: ‘12px’, ‘primary-color’: ‘#0EA679’ }, javascriptEnabled: true } } ] }加载字体那么,像字体这样的其他资源如何处理呢?file-loader 和 url-loader 可以接收并加载任何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文件,包括字体。更新 webpack.config.js 来处理字体文件: { test: /.(woff|woff2|eot|ttf|otf)$/, use: [“file-loader”] }增加HtmlWebpackPluginHtmlWebpackPlugin作用是生成一个HTML模板。HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader首先需要安装插件:npm install –save-dev html-webpack-plugin在生产配置文件里添加 plugins: [ new HtmlWebpackPlugin({ template: ‘public/index.html’, title: ’title’, // 更改HTML的title的内容 favicon: ‘public/favicon.ico’, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true, }, }),清理 /dist 文件夹在每次构建前清理 /dist 文件夹.npm install clean-webpack-plugin –save-devnew CleanWebpackPlugin([’../dist’])模块热替换https://webpack.docschina.org…模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。有两种方式一更改package.json"dev": “webpack –config build/webpack.dev.config.js –color –progress –hot"更改index.jsimport React from ‘react’;import ReactDom from ‘react-dom’;if (module.hot) { module.hot.accept();}//增加二更改配置文件const webpack = require(‘webpack’);devServer: { hot: true}plugins:[ new webpack.HotModuleReplacementPlugin()]reduxhttps://www.redux.org.cn/官方文档先给上,一开始学的时候也以为这个比较难,开始写就不会了。网上看看例子,自己在coding一下就差不多了。这边用到了一个中间件 redux-thunknpm install –save redux-thunk附上写的代码store注释的部分为生产环境使用。为了方便debug代码,在控制台打印readux日志。// import { createStore, applyMiddleware } from ‘redux’;// import thunk from ‘redux-thunk’;// import rootReducer from ‘./reducer’;// const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);// const store = createStoreWithMiddleware(rootReducer);// export default store;// 打印操作日志,方便调试,生产环境可以去掉,用上面注释的配置。import thunk from “redux-thunk”; // redux 作者开发的异步处理方案 可以在action 里传入 dispatch getStateimport { createLogger } from “redux-logger”; // 利用redux-logger打印日志import { createStore, applyMiddleware } from “redux”; // 引入redux createStore、中间件及composeimport { composeWithDevTools } from “redux-devtools-extension”; // devToolsEnhancer,import reducer from “./reducer”; // 引入reducers集合// 调用日志打印方法 collapsed是让action折叠,看着舒服点const loggerMiddleware = createLogger({ collapsed: true });// 创建一个中间件集合const middleware = [thunk, loggerMiddleware];// 创建storeconst store = createStore( reducer, composeWithDevTools(applyMiddleware(…middleware)));export default store;actionexport const INCREMENT = ‘counter/INCREMENT’;export const DECREMENT = ‘counter/DECREMENT’;export const RESET = ‘counter/RESET’;export function increment() { return { type: INCREMENT };}export function decrement() { return { type: DECREMENT };}export function reset() { return { type: RESET };}reducer每个页面的reduce文件import { INCREMENT, DECREMENT, RESET } from ‘../actions/counter’;const initState = { count: 0,};export default function reducer(state = initState, action) { switch (action.type) { case INCREMENT: return { count: state.count + 1, }; case DECREMENT: return { count: state.count - 1, }; case RESET: return { count: 0 }; default: return state; }}redecers 整合所有文件的reducerimport { combineReducers } from “redux”;import counter from “./reducers/counter”;export default combineReducers({ counter});react-loadablehttps://github.com/jamiebuild…官方文档先附上// 加载页面import Loadable from ‘react-loadable’;import React, { Component } from ‘react’;import { Spin, Icon } from ‘antd’;const antIcon = <Icon type=“loading” style={{ fontSize: 24 }} spin />;const antLong = ( <Icon type=“loading” style={{ fontSize: 24, color: ‘red’ }} spin />);const antError = ( <Icon type=“loading” style={{ fontSize: 24, color: ‘red’ }} spin />);export const Loading = props => { if (props.error) { return ( <Spin size=“large” tip=“加载错误 。。。” indicator={antError} style={{ position: ‘absolute’, color: ‘red’, top: ‘40%’, left: ‘50%’ }} /> ); } else if (props.timedOut) { return ( <Spin size=“large” tip=“加载超时 。。。” indicator={antLong} style={{ position: ‘absolute’, color: ‘red’, top: ‘40%’, left: ‘50%’ }} /> ); } else if (props.pastDelay) { return ( <Spin size=“large” tip=“Loading 。。。” indicator={antError} style={{ position: ‘absolute’, color: ‘red’, top: ‘40%’, left: ‘50%’ }} /> ); } else { return null; }};export const importPath = ({ loader }) => { return Loadable({ loader, loading: Loading, delay: 200, timeout: 10000 });};axios 统一拦截所有的请求和返回数据在需要用到的地方引入这个文件就ok了。只是简单的写了一个例子,后续再完善吧。axios使用起来很简洁。import axios from “axios”;import { message } from “antd”;import NProgress from “nprogress”;import “nprogress/nprogress.css”;// 拦截所有有请求与回复// Add a request interceptoraxios.interceptors.request.use( config => { NProgress.start(); return config; }, error => { message.error(“请求错误,请重试”); return Promise.reject(error); });// Add a response interceptoraxios.interceptors.response.use( response => { // NProgress.done(); // if (response.data.RetCode === 101) { // message.error(response.data.Message); // return response; // } // if (response.data.RetCode === 100) { // message.error(response.data.Message); // return response; // } return response; }, error => { message.error(“请求错误,请重试”); NProgress.done(); return Promise.reject(error); });export default request;公共路径(public path)插件配置 plugins: [ // 处理html new HtmlWebpackPlugin({ template: ‘public/index.html’, path: ‘../public/index.html’, inject: ‘body’, title: ‘管理平台’, favicon: ‘public/favicon.ico’, filename: ‘index.html’, hash: true, minify: { html5: true, removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true } }), new CleanWebpackPlugin([’../dist’], { allowExternal: true }), new BundleAnalyzerPlugin(), new MiniCssExtractPlugin({ chunkFilename: ‘[chunkhash].css’ }), new webpack.HashedModuleIdsPlugin(), new webpack.DllReferencePlugin({ context: __dirname, manifest: require(’../dll/manifest.json’) }), new CopyWebpackPlugin([ { from: ‘dll/Dll.js’, to: DIST_PATH } ]) ]html-webpack-pluginconst HtmlWebpackPlugin = require(‘html-webpack-plugin’);new HtmlWebpackPlugin({ template: ‘public/index.html’, path: ‘../public/index.html’, inject: ‘body’, title: ‘管理平台’, favicon: ‘public/favicon.ico’, filename: ‘index.html’, hash: true, minify: { html5: true, removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true } }),copy-webpack-pluginconst CopyWebpackPlugin = require(‘copy-webpack-plugin’);new CopyWebpackPlugin([ { from: ‘dll/Dll.js’, to: DIST_PATH } ])clean-webpack-pluginconst CleanWebpackPlugin = require(‘clean-webpack-plugin’);new CleanWebpackPlugin([’../dist’], { allowExternal: true })webpack-bundle-analyzerconst BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’) .BundleAnalyzerPlugin; new BundleAnalyzerPlugin(), mini-css-extract-pluginconst MiniCssExtractPlugin = require(‘mini-css-extract-plugin’); new MiniCssExtractPlugin({ chunkFilename: ‘[chunkhash].css’ }) 附上三个配置文件webpack.dev.conf.jsconst path = require(‘path’);const webpack = require(‘webpack’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const CopyWebpackPlugin = require(‘copy-webpack-plugin’);const DIST_PATH = path.resolve(__dirname, ‘../dist’); //生产目录const APP_PATH = path.resolve(__dirname, ‘../src’); //源文件目录module.exports = { mode: ‘development’, entry: { index: ‘./src/index.js’ }, output: { path: DIST_PATH, //出口路径 filename: ‘index.js’, chunkFilename: ‘js/[name].[chunkhash].js’, //按需加载名称 // publicPath: “./” }, // 源错误检查 devtool: ‘inline-source-map’, //模块配置 module: { rules: [ { test: /.(js|jsx)$/, exclude: /(node_modules|bower_components)/, //排除 include: [ path.resolve(__dirname, ‘../src’), path.resolve(__dirname, ‘../node_modules/antd/’) ], //包括 use: { loader: ‘babel-loader’ } }, { test: /.css$/, use: [‘style-loader’, ‘css-loader’] }, { test: /.(png|jpg|gif)$/, use: [ { loader: ‘url-loader’, options: { limit: 8192 } } ] }, { test: /.(woff|woff2|eot|ttf|otf)$/, use: [‘file-loader’] }, //更改antd主题设置 { test: /.less$/, use: [ { loader: ‘style-loader’ }, { loader: ‘css-loader’ // translates CSS into CommonJS }, { loader: ’less-loader’, // compiles Less to CSS options: { modifyVars: { ‘font-size-base’: ‘12px’, ‘primary-color’: ‘#0EA679’ }, javascriptEnabled: true } } ] } ] }, //插件 plugins: [ new HtmlWebpackPlugin({ template: ‘public/index.html’, path: ‘../public/index.html’, inject: ‘body’, favicon: ‘public/favicon.ico’, title: ‘管理平台’, overlay: true, minify: { html5: false }, hash: true }), // 热更新 new webpack.HotModuleReplacementPlugin(), new webpack.HashedModuleIdsPlugin(), new webpack.DllReferencePlugin({ context: __dirname, manifest: require(’../dll/manifest.json’) }), new CopyWebpackPlugin([ { from: ‘dll/Dll.js’, to: DIST_PATH } ]) ], // 热更新 devServer: { port: ‘3300’, contentBase: DIST_PATH, historyApiFallback: true, hot: true, // 开启 https: false, compress: false, noInfo: true, open: true, proxy: { // ‘/’: { // target: ‘’, // changeOrigin: true, // secure: false, // }, } }};webpack.dll.conf.jsconst path = require(‘path’);const webpack = require(‘webpack’);const CleanWebpackPlugin = require(‘clean-webpack-plugin’);const vendors = [ ‘antd’, ‘axios’, ’nprogress’, ‘react’, ‘react-dom’, ‘react-loadable’, ‘react-redux’, ‘react-router’, ‘react-router-dom’, ‘redux’];module.exports = { entry: { vendor: vendors }, output: { path: path.resolve(__dirname, ‘../dll’), filename: ‘Dll.js’, library: ‘[name][hash]’ }, plugins: [ new webpack.DllPlugin({ path: path.resolve(__dirname, ‘../dll’, ‘manifest.json’), name: ‘[name][hash]’, context: __dirname }), new CleanWebpackPlugin([’../dll’], { allowExternal: true }) ]};webpack.prod.conf.jsconst path = require(‘path’);const webpack = require(‘webpack’);const CopyWebpackPlugin = require(‘copy-webpack-plugin’);const CleanWebpackPlugin = require(‘clean-webpack-plugin’);const HtmlWebpackPlugin = require(‘html-webpack-plugin’);const MiniCssExtractPlugin = require(‘mini-css-extract-plugin’);const BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’) .BundleAnalyzerPlugin;const DIST_PATH = path.resolve(__dirname, ‘../dist’); //生产目录module.exports = { mode: ‘production’, entry: { index: ‘./src/index.js’ }, output: { path: DIST_PATH, //出口路径 filename: ‘index.js’, chunkFilename: ‘[name]_[hash].js’, //按需加载名称 // publicPath: ‘./’ }, // 源错误检查 devtool: ‘source-map’, //模块配置 module: { rules: [ { test: /.(js|jsx)$/, exclude: /(node_modules|bower_components)/, //排除 include: [ path.resolve(__dirname, ‘../src’), path.resolve(__dirname, ‘../node_modules/antd/’) ], //包括 use: { loader: ‘babel-loader’ } }, { test: /.css$/, use: [‘style-loader’, ‘css-loader’] }, { test: /.(png|jpg|gif)$/, use: [ { loader: ‘url-loader’, options: { limit: 8192 } } ] }, //更改antd主题设置 { test: /.less$/, use: [ { loader: ‘style-loader’ }, { loader: ‘css-loader’ // translates CSS into CommonJS }, { loader: ’less-loader’, // compiles Less to CSS options: { minimize: true, modifyVars: { ‘font-size-base’: ‘12px’, ‘primary-color’: ‘#0EA679’ }, javascriptEnabled: true } } ] }, { test: /.(woff|woff2|eot|ttf|otf)$/, use: [‘file-loader’] } ] }, //插件 plugins: [ // 处理html new HtmlWebpackPlugin({ template: ‘public/index.html’, path: ‘../public/index.html’, inject: ‘body’, title: ‘管理平台’, favicon: ‘public/favicon.ico’, filename: ‘index.html’, hash: true, minify: { html5: true, removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true } }), new CleanWebpackPlugin([’../dist’], { allowExternal: true }), new BundleAnalyzerPlugin(), new MiniCssExtractPlugin({ chunkFilename: ‘[chunkhash].css’ }), new webpack.HashedModuleIdsPlugin(), new webpack.DllReferencePlugin({ context: __dirname, manifest: require(’../dll/manifest.json’) }), new CopyWebpackPlugin([ { from: ‘dll/Dll.js’, to: DIST_PATH } ]) ] // 热更新};学习过程中的踩坑生产环境打包报错ERROR in Path must be a string. Received undefinedChild html-webpack-plugin for “index.html”: 1 asset Entrypoint undefined = index.html 这个错误不影响打包结果,应该是版本问题导致。https://github.com/jantimon/h…写完才发现有些忘记记录了,会保持更新。学习的过程中也学习参考了其他优秀的博客和github,以及文档。https://github.com/brickspert…https://github.com/NewPrototy…https://github.com/axios/axioshttps://github.com/jamiebuild…https://www.webpackjs.com/con… ...

January 31, 2019 · 9 min · jiezi

umi 配置多环境打包

平时我们开发应用时环境有开发环境、测试环境、生产环境等,此时我们需要配置不同的环境,获取不同的apiUrl前缀,以满足日常开发需要。1.安装 cross-env 插件npm install –save-dev cross-env2.在config/config.js文件里配置开发环境的apiUrl define: { “process.env.apiUrl”:‘https://www.dev.com/' },3.复制两次config/config.js,并更改文件名为config/config.test.js 和 config/config.prod.js,分别配置apiUrl// config.test.js define: { “process.env.apiUrl”:‘https://www.test.com/' },// config.prod.js define: { “process.env.apiUrl”:‘https://www.prod.com/' },4.在其他文件可以获取process.env.apiUrl 作为url前缀,如封装axios的request.js// request.jsconsole.log(process.env.apiUrl);5.在package.json 的scripts处配置打包命令"build-dev": “cross-env UMI_ENV=dev umi dev”,“build-test”: “cross-env UMI_ENV=test umi build”,“build-prod”: “cross-env UMI_ENV=prod umi build”,

January 30, 2019 · 1 min · jiezi

(踩坑回忆录)Dva踩坑与解决方案

前言问题antd-pro组件的使用小结未完待续…持续追更…

January 11, 2019 · 1 min · jiezi

react-redux-antd项目搭建(1)

因为是想搭建一个后台系统,所以组件直接定了antd,脚手架以create-react-app为基准,加上redux和sass(因为我一直用的less,这次换个口味),搭建一个简单的项目。安装依赖首先安装create-react-appnpm i create-react-app给项目起个名字create-react-app my_react_cli(项目名)啪嗒回车,开始安装项目,此过程会持续几分钟,可以去干点别的~安装完成后,就是下图的样子了图片描述运行一下看看,有没有问题npm start图片描述 There might be a problem with the project dependency tree. It is likely not a bug in Create React App, but something you need to fix locally. The react-scripts package provided by Create React App requires a dependency: “babel-loader”: “8.0.4” Don’t try to install it manually: your package manager does it automatically. However, a different version of babel-loader was detected higher up in the tree: C:\Users\liu\node_modules\babel-loader (version: 7.1.5)因为文主之前搭建别的项目时安装了babel-loader,导致的版本不对,那就卸掉babel-loader,按照所需的8.0.4版本安装一下卸载babel-loadernpm uninstall babel-loader 然后安装正确的版本npm i babel-loader@8.0.4可能会出现再次报错的情况,可以删掉在你的项目文件夹里面的 node_modules重新安装继续安装各种依赖这里我暂时装了antd@3.12.1和react-redux@6.0.0运行一下看看图片描述接下里就要开始封装公共方法休息一下 有空再写 ...

January 11, 2019 · 1 min · jiezi

React实现打印功能

一、需求分析:环境:react,antd,antd-pro将选中的数据进行打印,可以自定义分页的大小。由于打印的列等多个因素,导致如果写成组件在使用的时候依旧会改变源码,所以采用了写成页面的方式,二、实现需求:1、数据传值进行传值的时候,刚开始使用的是在通过this.props.location进行传值,但是这样数据被写死了,导致再次进入页面的时候无法更新打印的值。最后采用了一个全局的model进行实现的传值。2、表格生成分为四部分进行生成,分别是标题、表头、表格、表尾。其中表头,表尾因为需求的原因写死。以下为代码:createTitle = (title)=>( <div> <h1 style={styleObj.title}>{title}</h1> </div>)createHeader = (headerData)=>{ headerData = [ { orderID:‘订单编号’, value:‘P201901020002’, },{ people:‘采购人员’, value:‘xxx’, },{ time:‘采购时间’, value:‘2019年01月01日’, } ]; return ( <table> <tbody style={styleObj.header}> <tr> <th>订单编号:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“P201901020002” /> </th> </tr> <tr> <th>采购员:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“xxx” /> </th> <th>采购时间:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“2019年01月01日” /> </th> </tr> </tbody> </table> )}createForm = (printCol,printData)=>( <table style={styleObj.printTable}> <tbody> { ( <tr style={styleObj.printTableTr}> {printCol.map(item=><th style={{…styleObj[item.key],…styleObj.printTableTh}}><div>{item.name}</div></th>)} </tr> ) } { printData.map(item=> ( <tr style={styleObj.printTableTr}> {Object.keys(item).map(i => <th style={styleObj.printTableTh}>{item[i]}</th>)} </tr> ) ) } </tbody> </table>)createFooter = (footerData)=>{ return ( <table> <tbody style={styleObj.footer}> <tr> <th>供应商(签字)</th> <th> <div style={styleObj.footerSpace} /> </th> <th colSpan=“4”> <input style={styleObj.printInputFooter} /> </th> <th>库管员(签字)</th> <th> <div style={styleObj.footerSpace} /> </th> <th colSpan=“4”> <input style={styleObj.printInputFooter} /> </th> <th>{第${footerData.current}页}</th> <th>{共${footerData.total}页}</th> </tr> </tbody> </table> )}createPrintArea = (printCol)=>{ const {printGroupData} = this.state; return ( printGroupData.map((item,index)=>{ if(item.length){ return ( <div style={styleObj.printArea}> {this.createTitle(‘xxxxxxx公司xxx单’)} {this.createHeader(‘asd’)} {this.createForm(printCol,item)} {this.createFooter({current:index+1,total:printGroupData.length})} </div> ) } }) )}最主要的是CSS的调整,因为之后的打印需求将所有的CSS内联:以下为css:export const styleObj = { printArea:{ width: ‘500px’, fontSize: ‘12px’, align:‘center’ }, printInput:{ fontWeight:‘bold’, border: ’none’, }, title:{ textAlign: ‘center’, fontSize: ‘15px’, fontWeight: ‘700’, }, header:{ fontWeight:‘bold’, fontSize: ‘12px’, }, printTable:{ fontSize: ‘12px’, fontWeight: ‘700’, color: ‘black’, border: ‘1px black’, borderCollapse: ‘collapse’, textAlign: ‘center’, }, printTableTh:{ padding: ‘4px’, border: ‘1px solid black’, textAlign: ‘center’, }, printTableTr:{ textAlign:‘center’, padding: ‘4px’, border: ‘1px solid black’, }, number:{ width: ‘60px’, }, goodsName:{ width: ‘180px’, }, unitName:{ width: ‘75px’, }, specifications:{ width: ‘90px’, }, goodsType:{ width: ‘90px’, }, footer:{ fontSize:‘12px’, }, footerSpace:{ width: ‘20px’, display: ‘block’, }, printInputFooter:{ fontWeight:‘bold’, border:’none’, width: ‘85px’, },};3、实现分页分页即将数据进行分割,然后每次生成表格的时候将分割后的每个表格数据依次传入表格生成函数,从而生成全部表格。分页函数://传入的数据为:分页的大小,需要分页的数据。page = (pageNumber,printData)=>{ const printDataBack = printData.concat(); const printGroupData = []; while(printDataBack.length >= pageNumber){ let tempGroup = []; tempGroup = printDataBack.splice(0,pageNumber); printGroupData.push(tempGroup); } if(printDataBack.length){ printGroupData.push(printDataBack); } printGroupData.forEach((item)=>{ item.forEach((i,index)=>{ i.number = index+1; }) }); return printGroupData;}注意:解构出来的数据是引用,需要进行备份。设置一个input框以及一个按钮,input框用于输入分页的数字,再点击按钮以及第一次进入页面的时候进行分页。4、实现打印实现方法一(不推荐):直接在本页面进行刷新优点:css不用内嵌。缺点:导致本页面刷新,某些数据丢失。实现方法:直接获取到需要打印的区域,然后将本页面的innerHTML设置为获取的区域,然后调用系统的print,最后调用reload代码:print = () => { window.document.body.innerHTML = window.document.getElementById(‘billDetails’).innerHTML; window.print(); window.location.reload();}实现方法二:打开一个页面进行打印优点:打印不在关乎本页面的业务缺点:CSS需要内联代码:handlePrint = () => { const win = window.open(’’,‘printwindow’); win.document.write(window.document.getElementById(‘printArea’).innerHTML); win.print(); win.close();}三、完整代码以下为完整代码:index.jsimport React, { PureComponent } from ‘react’;import { connect } from ‘dva’;import { Row, Button, Col, Card, Form, message, InputNumber,} from ‘antd’;import PageHeaderWrapper from ‘@/components/PageHeaderWrapper’;import {styleObj} from ‘./style’;@Form.create()@connect(({ print }) => ({ print,}))class PrintTable extends PureComponent { state = { printData:[], printCol:[], pageNumber:10, printGroupData:[], } componentDidMount() { const printCol = [ { key:’number’, name:‘序号’, },{ key:‘goodsName’, name:‘商品名称’, },{ key:‘goodsType’, name:‘商品类型’, },{ key:‘unitName’, name:‘单位’, },{ key:‘specifications’, name:‘规格’, }]; const printData = []; const { pageNumber } = this.state; const { print:{ payload:{formPrintData} } } = this.props; formPrintData.forEach((i,index)=>{ const colData = {}; printCol.forEach(j=>{ colData[j.key] = i[j.key]; }) colData.number = index+1; printData.push(colData); }); const printGroupData = this.page(pageNumber,printData); this.setState({ printData, printCol, printGroupData, }) } componentWillReceiveProps(nextProps){ const printCol = [ { key:’number’, name:‘序号’, },{ key:‘goodsName’, name:‘商品名称’, },{ key:‘goodsType’, name:‘商品类型’, },{ key:‘unitName’, name:‘单位’, },{ key:‘specifications’, name:‘规格’, }]; const printData = []; const { pageNumber } = this.state; const { print:{ payload:{formPrintData} } } = nextProps; formPrintData.forEach((i,index)=>{ const colData = {}; printCol.forEach(j=>{ colData[j.key] = i[j.key]; }) colData.number = index+1; printData.push(colData); }); const printGroupData = this.page(pageNumber,printData); this.setState({ printData, printCol, printGroupData, }) } createTitle = (title)=>( <div> <h1 style={styleObj.title}>{title}</h1> </div> ) createHeader = (headerData)=>{ headerData = [ { orderID:‘订单编号’, value:‘P201901020002’, },{ people:‘采购人员’, value:‘xxx’, },{ time:‘采购时间’, value:‘2019年01月01日’, } ]; return ( <table> <tbody style={styleObj.header}> <tr> <th>订单编号:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“P201901020002” /> </th> </tr> <tr> <th>采购员:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“xxx” /> </th> <th>采购时间:</th> <th colSpan=“7”> <input style={styleObj.printInput} value=“2019年01月01日” /> </th> </tr> </tbody> </table> ) } createForm = (printCol,printData)=>( <table style={styleObj.printTable}> <tbody> { ( <tr style={styleObj.printTableTr}> {printCol.map(item=><th style={{…styleObj[item.key],…styleObj.printTableTh}}><div>{item.name}</div></th>)} </tr> ) } { printData.map(item=> ( <tr style={styleObj.printTableTr}> {Object.keys(item).map(i => <th style={styleObj.printTableTh}>{item[i]}</th>)} </tr> ) ) } </tbody> </table> ) createFooter = (footerData)=>{ return ( <table> <tbody style={styleObj.footer}> <tr> <th>供应商(签字)</th> <th> <div style={styleObj.footerSpace} /> </th> <th colSpan=“4”> <input style={styleObj.printInputFooter} /> </th> <th>库管员(签字)</th> <th> <div style={styleObj.footerSpace} /> </th> <th colSpan=“4”> <input style={styleObj.printInputFooter} /> </th> <th>{第${footerData.current}页}</th> <th>{共${footerData.total}页}</th> </tr> </tbody> </table> ) } handlePrint = () => { const win = window.open(’’,‘printwindow’); win.document.write(window.document.getElementById(‘printArea’).innerHTML); win.print(); win.close(); } createPrintArea = (printCol)=>{ const {printGroupData} = this.state; return ( printGroupData.map((item,index)=>{ if(item.length){ return ( <div style={styleObj.printArea}> {this.createTitle(‘xxxxxxx公司xxx单’)} {this.createHeader(‘asd’)} {this.createForm(printCol,item)} {this.createFooter({current:index+1,total:printGroupData.length})} </div> ) } }) ) } handlePage = ()=>{ const { pageNumber, printData } = this.state; if(pageNumber <= 0){ message.warning(‘输出正确的分页’); return; } this.setState({ printGroupData:this.page(pageNumber, printData) }) } page = (pageNumber,printData)=>{ const printDataBack = printData.concat(); const printGroupData = []; while(printDataBack.length >= pageNumber){ let tempGroup = []; tempGroup = printDataBack.splice(0,pageNumber); printGroupData.push(tempGroup); } if(printDataBack.length){ printGroupData.push(printDataBack); } printGroupData.forEach((item)=>{ item.forEach((i,index)=>{ i.number = index+1; }) }); return printGroupData; } onChange = (value)=>{ this.setState({ pageNumber:value, }) } render() { const { printCol, printData } = this.state; return ( <PageHeaderWrapper title=“查询表格”> <Card> <Row> <Col span={6}> <Row> <Col span={12}> <InputNumber onChange={this.onChange} placeholder=‘输入自定义分页数量’ style={{width:‘100%’}}/> </Col> <Button onClick={this.handlePage}>确认分页</Button> </Row> <Row> <Button onClick={this.handlePrint} type=‘primary’>打印</Button> </Row> </Col> <Col span={12}> <div id=‘printArea’> <div style={styleObj.printArea}> {printCol.length&&printData.length? this.createPrintArea(printCol):null} </div> </div> </Col> </Row> </Card> </PageHeaderWrapper> ); }}export default PrintTable;style.jsexport const styleObj = { printArea:{ width: ‘500px’, fontSize: ‘12px’, align:‘center’ }, printInput:{ fontWeight:‘bold’, border: ’none’, }, title:{ textAlign: ‘center’, fontSize: ‘15px’, fontWeight: ‘700’, }, header:{ fontWeight:‘bold’, fontSize: ‘12px’, }, printTable:{ fontSize: ‘12px’, fontWeight: ‘700’, color: ‘black’, border: ‘1px black’, borderCollapse: ‘collapse’, textAlign: ‘center’, }, printTableTh:{ padding: ‘4px’, border: ‘1px solid black’, textAlign: ‘center’, }, printTableTr:{ textAlign:‘center’, padding: ‘4px’, border: ‘1px solid black’, }, number:{ width: ‘60px’, }, goodsName:{ width: ‘180px’, }, unitName:{ width: ‘75px’, }, specifications:{ width: ‘90px’, }, goodsType:{ width: ‘90px’, }, footer:{ fontSize:‘12px’, }, footerSpace:{ width: ‘20px’, display: ‘block’, }, printInputFooter:{ fontWeight:‘bold’, border:’none’, width: ‘85px’, },}; ...

January 10, 2019 · 5 min · jiezi

???? Ant Plus,Ant Design Form 从未如此简单

简介Ant Plus 是 Ant Design Form 的增强版,在其基础上,封装了极其简便的 Form 使用方式与 Form 相关组件的简化 API。文档https://nanxiaobei.github.io/ant-plus特点???? 极其简便:告别繁琐的 form.getFieldDecorator 样板代码与冗长的 rules 校验代码。???? 渐进增强:若不使用新的功能,完全可以把组件当作 Ant Design 中的组件来使用。⛳️ 统一提示:可全局定义 rules 校验提示信息,统一体验,告别烦乱的自定义与不可控。???? 简化 API:对 Form 相关组件的常用 API 进行了简化,一切只为更流畅的开发。安装Yarnyarn add antxnpmnpm install antx使用表单域组件的 Props 中,id 为表单域唯一标识,label 为 Form.Item 的 label。getFieldDecorator(id, options) options 参数中的项,均可直接用于组件的 Props,例如 rules、initialValue 等。Ant Plus 还对 rules 做了优化,可使用简洁的字符串,来简化校验规则的生成。完整的使用介绍,请查阅 Ant Plus Form 组件文档。import { Button } from ‘antd’;import { Form, Input } from ‘antx’;const App = ({ form }) => ( <Form api={form} data={{ username: ‘Emily’ }}> <Input label=“用户名” id=“username” rules={[‘required’, ‘string’, ‘max=10’]} max={10} msg=“full” /> <Button htmlType=“submit”>提交</Button> </Form>);export default Form.create()(App);是的,一切就是如此的简洁清晰。示例:https://codesandbox.io/s/q75nvj6vrj。对比使用 Ant Plus 与使用传统 Ant Design 搭建 Form 的代码对比。链接GitHub: https://github.com/nanxiaobei/ant-plusnpm: https://www.npmjs.com/package/antx最后欢迎尝试,欢迎 Star,体验一种从未如此简单的开发方式。 ...

January 9, 2019 · 1 min · jiezi

react+antd系列之引入echart图表

要在antd中引入echart图表,首先得安装echart包,命令如下:cnpm install echarts –saveechart安装完毕,要如何使用呢,在react中我们要引入我们刚刚安装的echart,如下:import echarts from ’echarts/lib/echarts’;但是这里引入这个还不止,假设你要画柱状图,则你得引入:import ’echarts/lib/chart/bar’;如果你要画折线图,你得引入:import ’echarts/lib/chart/line’;这里你要画什么的时候,就引入什么,具体参考引入路径地址:https://www.npmjs.com/package…底下有个echart图表例子,代码如下如下:import React from ‘react’;import echarts from ’echarts/lib/echarts’;import ’echarts/lib/chart/bar’;import ’echarts/lib/chart/line’;import ’echarts/lib/component/tooltip’;import ’echarts/lib/component/title’;import ’echarts/lib/component/legend’;import ’echarts/lib/component/toolbox’;import ’echarts/lib/component/markPoint’;import ’echarts/lib/component/markLine’;class Test extends React.Component { componentDidMount() { // 初始化 var myChart = echarts.init(document.getElementById(‘main’)); // 绘制图表 myChart.setOption({ title: { text: ‘某地区蒸发量和降水量’ }, tooltip : { trigger: ‘axis’ }, legend: { data:[‘蒸发量’,‘降水量’] }, toolbox: { show : true, feature : { dataView : {show: true, readOnly: false}, magicType : {show: true, type: [’line’, ‘bar’]}, restore : {show: true}, saveAsImage : { show: true, type: ‘jpg’ } } }, xAxis : [ { type : ‘category’, data : this.props.data.xdata } ], yAxis : [ { type : ‘value’ } ], series : [ { name:‘蒸发量’, type:‘bar’, data: this.props.data.ydata.ydata1, markPoint : { data : [ {type : ‘max’, name: ‘最大值’}, {type : ‘min’, name: ‘最小值’} ] }, markLine : { data : [ {type : ‘average’, name: ‘平均值’} ] } }, { name:‘降水量’, type:‘bar’, data: this.props.data.ydata.ydata2, markPoint : { data : [ {type : ‘max’, name: ‘最大值’}, {type : ‘min’, name: ‘最小值’} ] }, markLine : { data : [ {type : ‘average’, name : ‘平均值’} ] } }, ] });}render() { return ( <div id=“main” style={{ width: ‘100%’, height: 500 }}></div> );}}export default Test;我们把这个图表封装成一个组件,然后我们在别的页面可以引入这个组件,代码如下:import React from ‘react’;import { connect } from ‘dva’;import styles from ‘./IndexPage.css’;import Test from ‘../components/echart.js’;function IndexPage() { return ( <div className={styles.normal}> <Test data={{ xdata: [‘1月’,‘2月’,‘3月’,‘4月’,‘5月’,‘6月’,‘7月’,‘8月’,‘9月’,‘10月’,‘11月’,‘12月’], ydata: { ydata1:[2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3], ydata2:[2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3], } }}/> </div> );}IndexPage.propTypes = {};export default connect()(IndexPage);效果如下:如果我们要让图表自适应,也就是跟随屏幕缩放而自动缩放改怎么处理呢?其实只要添加一句代码即可,这里有两种方案,第一种如下:window.onresize = myChart.resize;第二种如下: window.addEventListener(“resize”,function(){ myChart.resize(); });具体源码地址如下:https://gitee.com/hope93/intr… ...

December 18, 2018 · 2 min · jiezi

react+antd系列之Form表单(1):添加与删除

在用antd的时候,我们如果要对表单进行添加和删除该怎么弄呢,如下:import { connect } from ‘dva’;import { Form, Input, Button } from ‘antd’;import styles from ‘./eg1.css’;const FormItem = Form.Item;function Page(props) { const { form } = props; const { getFieldDecorator, getFieldValue } = form // 表单提交 const handleSubmit = (e) => { e.preventDefault(); form.validateFields((err, values) => { if (!err) { console.log(values); } }); } // 添加 const add = () => { const list = form.getFieldValue(’list’); const nextList = list.concat({}); form.setFieldsValue({ list: nextList, }); } // 删除 const deleteRow = (index) => { const list = form.getFieldValue(’list’); const content = form.getFieldValue(‘content’); if (list.length === 1) { return; } form.setFieldsValue({ list: list.filter((item, key) => key !== index), content: content.filter((item, key) => key !== index), }); } getFieldDecorator(’list’, { initialValue: [{}] }); const list = getFieldValue(’list’); const listContent = list.map((item, index) => { return ( <FormItem label=‘名称:’ key={index}> {getFieldDecorator(content[${index}].name, { rules: [{ required: true, message: “名称不能为空!”, }], })( <Input placeholder=“请输入名称” style={{ width: ‘60%’, marginRight: 8 }} /> )} {index > 0 ? ( <Button type=“primary” onClick={deleteRow.bind(this, index)}>删除</Button> ) : null} </FormItem> ); }); return ( <div className={styles.normal}> <Form onSubmit={handleSubmit}> {listContent} <FormItem> <Button type=“primary” htmlType=“submit”>提交</Button> <Button type=“primary” style={{ marginLeft: ‘10px’ }} onClick={add}>增加</Button> </FormItem> </Form> </div> );}const page = Form.create()(Page);export default connect()(page);这里不仅能对表单进行增加和删除,还能对表单进行验证,看是否有输入,以上是本身没有数据的情况,如果是有数据的情况如下:import React from ‘react’;import { connect } from ‘dva’;import { Form, Input, Button } from ‘antd’;import styles from ‘./eg2.css’;const FormItem = Form.Item;function Page(props) { const { form } = props; const { getFieldDecorator, getFieldValue } = form // 表单提交 const handleSubmit = (e) => { e.preventDefault(); form.validateFields((err, values) => { if (!err) { console.log(values); } }); } // 添加 const add = () => { const list = form.getFieldValue(’list’); const nextList = list.concat({}); form.setFieldsValue({ list: nextList, }); } // 删除 const deleteRow = (index) => { const list = form.getFieldValue(’list’); const content = form.getFieldValue(‘content’); if (list.length === 1) { return; } form.setFieldsValue({ list: list.filter((item, key) => key !== index), content: content.filter((item, key) => key !== index), }); } const slist = [{ id:‘0001’, name: ‘黎明’ }, { id:‘0002’, name: ‘晴天’ }] getFieldDecorator(’list’, { initialValue: slist }); const list = getFieldValue(’list’); const listContent = list.map((item, index) => { getFieldDecorator(content[${index}].id, {initialValue: item.id || ‘’}) return ( <FormItem label=‘名称:’ key={index}> {getFieldDecorator(content[${index}].name, { rules: [{ required: true, message: “名称不能为空!”, }], initialValue: item.name || ’’ })( <Input placeholder=“请输入名称” style={{ width: ‘60%’, marginRight: 8 }} /> )} {index > 0 ? ( <Button type=“primary” onClick={deleteRow.bind(this, index)}>删除</Button> ) : null} </FormItem> ); }); return ( <div className={styles.normal}> <Form onSubmit={handleSubmit}> {listContent} <FormItem> <Button type=“primary” htmlType=“submit”>提交</Button> <Button type=“primary” style={{ marginLeft: ‘10px’ }} onClick={add}>增加</Button> </FormItem> </Form> </div> );}const page = Form.create()(Page);export default connect()(page);一般如果本身有数据,都会有每行数据的id,但是这个id不显示,我们都会用getFieldDecorator给id声明,这样在我们提交表单的时候,就可以得到表单抓取到id的数据,有数据跟没有数据的差别就是,有数据需要在表单getFieldDecorator的时候给一个初始值,其他两者都一样具体代码下载地址:https://gitee.com/hope93/antd… ...

December 18, 2018 · 2 min · jiezi

react+antd系列之Form表单(2):格式限制验证

格式限制antd中表单的功能很多,下面就为大家整理了一下antd中常用的几种表单输入格式验证:1. 输入框不能为空限制,如下: {getFieldDecorator(’name’, { rules: [{ required: true, message: ‘名称不能为空’, }], })( <Input placeholder=“请输入名称” /> )}2. 输入框字符限制,如下:字符长度范围限制: {getFieldDecorator(‘password’, { rules: [{ required: true, message: ‘密码不能为空’, }, { min:4, message: ‘密码不能少于4个字符’, }, { max:6, message: ‘密码不能大于6个字符’, }], })( <Input placeholder=“请输入密码” type=“password”/> )}字符长度限制: {getFieldDecorator(’nickname’, { rules: [{ required: true, message: ‘昵称不能为空’, }, { len: 4, message: ‘长度需4个字符’, }], })( <Input placeholder=“请输入昵称” /> )}3. 自定义校验 {getFieldDecorator(‘passwordcomfire’, { rules: [{ required: true, message: ‘请再次输入密码’, }, { validator: passwordValidator }], })( <Input placeholder=“请输入密码” type=“password”/> )} // 密码验证 const passwordValidator = (rule, value, callback) => { const { getFieldValue } = form; if (value && value !== getFieldValue(‘password’)) { callback(‘两次输入不一致!’) } // 必须总是返回一个 callback,否则 validateFields 无法响应 callback(); }validator属性自定义效验,必须返回一个callback4.whitespace空格报错 {getFieldDecorator(‘hobody’, { rules: [{ whitespace: true, message: ‘不能输入空格’, } ], })( <Input placeholder=“请输入昵称” /> )}若输入只有一个空格,则会报错5.pattern正则验证 {getFieldDecorator(‘qbc’, { rules: [{ message:‘只能输入数字’, pattern: /^[0-9]+$/ } ], })( <Input placeholder=“请输入ABC” />)}如果输入的不是数字,则提示错误完整代码地址:https://gitee.com/hope93/antd… ...

December 18, 2018 · 1 min · jiezi