关于antd:不破不立-挥别-lessloaderAnt-Design-50-Alpha-发布-

关注咱们的同学能够晓得在往年(2022)年初的时候咱们公布了一篇《Plan about Ant Design V5》 ,来介绍咱们将要启动的 5.0 打算。社区在经验 8 个月的研发、公布 30 个 experimental 版本验证后,新的技术架构曾经稳固,终于公布了 v5 alpha 版本!欢送大家尝试,并提供贵重的意见。在正式版公布之前,咱们还有更多事件要做。然而如果你曾经急不可待尝鲜,能够拜访咱们的 5.0 官网(迁徙参考此处)。 新个性新的设计5.0 带来了全新的设计理念,围绕用户指标,依据「更聚焦」、「去烦扰」、「轻松感」三大准则,对 Ant Design 设计零碎在视觉上进行重塑,助力用户在虚构办公空间更加「高兴工作」。针对这些准则,咱们在以下方面作出了优化,包含但不限于: 减少梯度圆角并减少了根底圆角的大小,整体格调更轻松;缩小线条应用,以取得更高的空间使用率和更简洁的应用体验;进步了交互动画速度,交互体验更加晦涩;补充了对立的聚焦款式,仅在应用键盘聚焦时呈现,无障碍能力(Accessibility)进一步晋升。 新的动静主题计划在 4.x 版本中,受制于 less 的款式与主题计划,咱们始终解脱不了两个问题的困扰:如何动静更换主题以及如何混合应用主题。通过一段时间的摸索,在 5.0 版本中,咱们放弃了追随 antd 已久的 less 计划,转而拥抱 CSS-in-JS。CSS-in-JS 通过运行时的款式计算能力完满地解决了上述两个问题,并且为咱们带来了更多的晋升: 更小的 BundleSize;不依赖任何插件的款式按需引入能力。有了 CSS-in-JS 的技术加持,咱们推出了全新的定制主题计划。在 5.0 版本中,Design Token 将是主题的根本组成部分,所有组件都会生产他们构筑款式。同时,咱们依赖 Design Token 建设了款式的缓存,即认为在同样的 antd 版本下同样的 Design Token 所对应的组件款式是等同的,因而防止了反复生成款式进行比照的操作,防止了性能损耗。基于 Design Token 和 CSS-in-JS ,咱们制订了更加灵便的定制主题计划。通过 5.0 的定制主题,你能够做到的包含但不限于: 全局主题定制;部分主题(嵌套主题/多主题并行);组件主题定制;在 Sketch 中间接应用配置过主题的设计稿。想要理解更多请查看定制主题文档。 新增组件5.0 版本中打算将会有一些新的组件供用户抉择!他们别离是: Tour 漫游式疏导QrCode 二维码WaterMark 水印FloatButton 悬浮按钮目前已有 FloatButton 悬浮按钮 准备就绪,欢送试用。 ...

October 8, 2022 · 1 min · jiezi

关于antd:antd-Tabs-组件简单实现

之前的工作经验中,始终都是应用 Antd 作为根底组件进行开发,当初咱们要本人实现一套治理后盾的 UI 组件库,在写到 Tabs 这个组件的时候借鉴了 Antd 的思路,次要用到了 React Context 个性,在此记录下次要展示外围思路(仅实现受控组件),款式细节略过,效果图如下 应用形式 const [value, setValue] = useState('1')<Tabs activeKey={value} isCache={true} onChange={value => setValue(value)}> <Tabs.Panel tabKey='1' tab='Tab Item'><div>11111</div></Tabs.Panel> <Tabs.Panel tabKey='1' tab='Tab Item'><div>11111</div></Tabs.Panel></Tabs>组件导出模块,实现 <Tabs.Panel></Tabs.Panel> 这种模式应用 // 模拟 antd 实现 https://github.com/ant-design/ant-design/blob/master/components/radio/index.tsximport TabsComps, { TTabsProp } from './Tabs'import Panel from './Panel'interface CompoundedComponent extends React.ForwardRefExoticComponent< TTabsProp & React.RefAttributes<HTMLElement> > { Panel: typeof Panel}const Tabs = TabsComps as CompoundedComponentTabs.Panel = Panelexport default Tabscontext 实现 import React from 'react'const TabsContext = React.createContext<any>(null)export default TabsContextPanel 组件实现 ...

August 5, 2022 · 2 min · jiezi

关于antd:在react中基于antdesign封装一个中文输入框提高onchange性能

1 antd中,input组件在触发onChange时,如果是中文输出模式,会频繁被触发,导致页面性能升高。尤其是在onChange时须要实时搜寻的状况。2 在mac设施下,如果在onChange中应用value.replace(/\s/g,''/), 会呈现无奈输出中文的问题。优化之后,能够失常输出。默认状况下的Input组件: 优化之后的ChineseInput 应用形式: 和原始Input组件应用形式一样,没用额定api index.tsx import React, { FC, useEffect, useRef, useState } from 'react';import { Input, InputProps } from 'antd';import styles from './index.module.less'interface IProps extends InputProps { [propName: string]: any; value?: string;}const Index: FC<IProps> = ( { value = '', onChange, onInput, onCompositionStart, onCompositionEnd, ...resetProps }) => { const chineseInputting = useRef(false); // 是否是中文(爽字节字符的输出)模式 const [val, setVal] = useState('') useEffect(() => { setVal(value) }, [value]) // 优化搜寻 const change = (e: any) => { onChange && onChange(e) } return ( <Input className={styles.chineseInput} {...resetProps} value={val} onChange={(e: any) => { if (e.target.value === '') { change(e) } setVal(e.target.value) }} onInput={(e: any) => { onInput && onInput(e) if (!chineseInputting.current) { change(e) } }} onCompositionStart={(e: any) => { onCompositionStart && onCompositionStart(e) chineseInputting.current = true; }} onCompositionEnd={(e: any) => { onCompositionEnd && onCompositionEnd(e) if (chineseInputting.current) { change(e) chineseInputting.current = false; } }} /> );};export default Index;index.module.less ...

July 4, 2022 · 1 min · jiezi

关于antd:日常ProComponentAnt-Design-Pro

ProComponent就是算是对antd的又一次集成和封装,缩小了前端对于细节和联动的解决,总之就是用起来特地爽。那这里就不对ProComponent做过多介绍了,咱们直奔主题,如何应用,或者说如何更优雅、更不便的应用组件和编写代码始终是任何一位程序员的外围谋求,我也是PiuPiuPiu~! columns的配置treeSelectrequest能够用来调接口获取值labelInValue:是设置的取到的是这个表单项值的对象,不仅仅包含value,上面的例子取到的就是fieldNames对象 { title: '区域', dataIndex: 'areaIds', hideInTable: true, valueType: 'treeSelect', request: async () => { let res = await areaSituation(); if (res?.status == '00') { return res.data; } }, fieldProps: { showArrow: false, filterTreeNode: true, showSearch: true, dropdownMatchSelectWidth: false, autoClearSearchValue: true, treeNodeFilterProp: 'title', labelInValue: true, fieldNames: { label: 'title', lvl: 'lvl', value: 'nodeId', children: 'childrenList', }, onSelect: (_, dataObj) => { setAreaIds([]); let arr = []; let getData = (data) => { arr = [...arr, data?.nodeId]; if (data?.childrenList.length > 0) { data?.childrenList?.forEach((item) => { getData(item); }); } }; getData(dataObj); setAreaIds(arr); }, }, },dateTimeRangeProFormDateTimePicker这个是可取值到年月日时分秒ProFormDatePicker 是反对到年月日可用moment设置初始值,这个默认获得值是一个数组 如果搜寻后端须要的这个工夫字段不是数组而是其余的两个字段;可用上面的写法 ...

May 8, 2022 · 6 min · jiezi

关于antd:Antd多文件上传后台接收为null问题

Antd多文件上传后盾接管为null问题在应用antd开发过程中,Upload组件的上传,个别是通过action配置后端接口地址,主动上传文件;然而当文件数量较多时,须要进行手动上传,然而手动上传后盾始终无奈接管到数据,数据为null。 代码实现前段组件代码如下: const onRemove = (file) => { this.setState((state) => { const index = state.fileList.indexOf(file); const newFileList = state.fileList.slice(); newFileList.splice(index, 1); return { fileList: newFileList, }; }); }; const beforeUpload = (file) => { const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; if (!isJpgOrPng) { message.error('请上传图片格式文件!'); return isJpgOrPng || Upload.LIST_IGNORE; } this.setState(state => ({ fileList: [...state.fileList, file], })); return false; }; <Upload fileList={fileList} onRemove={onRemove} beforeUpload={beforeUpload} directory accept=".png,.jpg,.jpeg" showUploadList={false} onChange={this.onChange} > <Button icon={<UploadOutlined />} > Click to upload </Button> </Upload>复制代码前端上传逻辑代码: ...

March 14, 2022 · 1 min · jiezi

关于antd:antd-pro-Protable-dataIndex-嵌套写法变数组

留神:新版的antd 曾经勾销的了dataIndex嵌套的写法,认为可能会和数据造成抵触; 原来的嵌套写法:如:dataIndex:'user.name'新的数组写法:dataIndex:['user','name'] 当然antd pro 也变了哈

March 3, 2022 · 1 min · jiezi

关于antd:antd-pro-服务端响应异常统一处理

针对antd pro 对立解决服务端异样的办法,留神:这里的转化是针对响应转化,后续会针对转化后的success等进行判断,如果success=false 则会触发谬误提醒,否不会触发。如下: export const request: RequestConfig = { // errorHandler, errorConfig: { adaptor: (resData, ctx) => { //如果响应的后果和前端要求的不统一,须要转化 let success = true, errorMessage = '', errorCode = 0, showType = 0; if (resData && resData.code !== undefined && resData.code !== 0) { success = false; errorMessage = resData.message; errorCode = resData.code; } else if (ctx.res.status > 300 || ctx.res.status < 200) { //对服务端非手动异样时,辨认状态码解决 success = false; errorMessage = ctx.res.statusText; errorCode = ctx.res.status; } if (resData && resData.showType !== undefined) { showType = resData.showType; } return { ...resData, success: success, errorMessage: errorMessage, errorCode: errorCode, showType: showType, }; }, }, // 新增主动增加AccessToken的申请前拦截器 //requestInterceptors: [authHeaderInterceptor],};

February 23, 2022 · 1 min · jiezi

关于antd:antdthemegenerator-动态主题报错没有对应文件-rootentrynameless

最近在开发我的项目时,应用了最新的antd,发现在联合动静主题更换插件 antd-theme-generator 时提醒报错: Error: ENOENT: no such file or directory, open 'xxxxx/node_modules/antd/lib/style/themes/@{root-entry-name}.less'查看Ant Design 官网也有给出更新的提醒,在动静主题(实验性): 相干变更#为了实现 CSS Variable 并放弃原始用法兼容性,咱们于 dist/antd.xxx.less 文件中增加了 @root-entry-name: xxx; 入口注入以反对 less 动静加载对应的 less 文件。个别状况下,你不须要关注该变动。然而,如果你的我的项目中间接援用了 lib|es 目录下的 less 文件。你须要在 less 入口处配置 @root-entry-name: default; (或者 @root-entry-name: variable;)以使 less 能够找到正确的入口。 此外,咱们将 lib|es/style/minxins/index.less 中的 @import 'motion' 和 @import 'reset' 迁徙至了 lib|es/style/themes/xxx.less 中,因为这两个文件依赖了主题相干变量。如果你应用了相干外部办法,请自行调整。当然,咱们还是倡议间接应用 dist 目录下的 antd.less 文件而非调用外部文件,因为它们常常会受重构影响。 因为在没有更新到最新版本 4.17.4,antd-theme-generator 插件都没有报错,然而最新版本就是应用不了,最初在看了 antd-theme-generator 的源码后找到了问题。 问题定位:// antd-theme-generator -> index.js 458行:// 这里将所有变量捆绑到一个文件中执行报错let antdLess = await bundle({ src: antdStylesFile,});寻找问题bundle 办法是援用了 less-bundle-promise;最初定位到 less-bundle-promise 下的 buildContents 办法获取文件报错;// 21 行:imported = line.replace(stringLiteralRegex, '$1');引入的的 less 文件,后续并没有将变量 @{root-entry-name} 替换,还是原始援用,造成找不到文件;解决问题因为动静生成主题不是生产依赖,所以能够更改源码,这里我只是为了解决我的项目问题,在 21 行上面直接判断替换变量。@{root-entry-name} 替换为 default;// 解决 @{root-entry-name} 报错问题;// 当然还有更好的正则替换或其余形式,这里只是解决问题间接更改if (imported === './@{root-entry-name}.less') { imported = './default.less'}总结对于我的项目中的依赖降级过后,总会有一些兼容问题产生,咱们在网上没有解决方案时,就须要依据现有的提醒错误信息去寻找原因,查看源码也是一份必修课,望能给给位小伙伴们提供到帮忙。(^-^) ...

December 24, 2021 · 1 min · jiezi

关于antd:一次说清为什么在Antd-Modal中调resetFields调了个寂寞

背景在干了大半年增删查改后(node,mysql,serverless),业务端人手短缺,老板开恩让我反对其余团队写几个页面。 久了不摸手生,除了react依稀记得,antd根本只能看着官网demo一行一行写,感觉一天能写完的,后果两天了还没联调完。两头还遇到一些似曾相识的问题,惋惜以前的教训曾经不论用了。 demo地址: https://codesandbox.io/s/antd... 这些问题在antd的仓库issue都重复被提及,看了下文,包含但不限于以下问题都将失去答案: 概括一下: Form表单,React hooks 组件,initialValues初始化数据时候,第二次、第三次……传递新值,表单没有更新,永远显示第一次数据?弹出层新建表单从新设置值不起作用? Modal 用了destroyOnClose,外面有 Form,并应用 form.resetFields,为什么会生效?Modal中initialValues更新了,应用了form.resetFields,要间断关上两次才失效?有事说事语言形容显得太红润,所以间接看动图吧: 这是一个简略的增删查改页面,新增和编辑共享了同一个组件,冀望在关上弹窗编辑表单敞开后,从新关上时,能依据initialValues从新渲染表单, 但失去的后果是,第二次关上,编辑框没有刷新. 实现的伪代码大抵是这样: import React, { useEffect } from "react";import { Modal, Form, Input, Button, Checkbox } from "antd";export function EditModal(props) { const { visible, onOk, onCancel, content = {} } = props; const [form] = Form.useForm(); const isEdit = !!content.sort; const handlSubmit = (close) => { // 一些提交逻辑 }; useEffect(() => { // setTimeout(() => { form.resetFields(); // }); }, [content]); return ( <Modal title={`${isEdit ? "编辑" : "新建"}备注`} visible={visible} destroyOnClose onOk={onOk} onCancel={onCancel} > <Form name="basic" labelCol={{ span: 7 }} wrapperCol={{ span: 14 }} form={form} initialValues={content} autoComplete="off" > {...一些表单} </Form> </Modal> );}置信呈现问题的盆友们,大多都是和我一样,如下面这样的代码这样实现。 ...

December 24, 2021 · 1 min · jiezi

关于antd:使用ProComponents和Antd的一些笔记

antd是咱们罕用的一款react框架(等于没说,哈哈) 什么是ProComponents?对于一个应用这个组件开发了半年之久的菜鸟来说,什么是ProComponents,就是antd的增强集成版本,集成度很高,用起来很不便(对于我这个菜鸟来说 容易踩坑),无论是elementUi vant antd...,组件应用状况大抵相似,抽个工夫记录一下,也增深一下印象,当前再遇到新的组件也好得心应手不是。 ProFormDigit这段代码,为什么要在ProFormDigit套上form.item呢?那是因为ProFormDigit有一个bug,因为如果我间接点提交,就会跳过ProFormDigit对于输出的内容的限度,包含(数字,位数,最大值,最小值)都会没来得及校验,提交下来~~ <Form.Item style={{width:'300px'}} name="percent" rules={[ { required: true, message: '请输出调整比例', }, { pattern:/^([1-9][0-9]{0,1}|100)$/, message:'请输出1到100之间的整数', }, ]} > <ProFormDigit fieldProps={{ precision: 0 }} label="" width={150} placeholder="请输出调整比例" min={0} max={100} /> </Form.Item>这个组件是酱紫的~ 工夫组件ProFormDateRangePicker个别应用 import { ProFormDateRangePicker,} from '@ant-design/pro-form'<ProFormDateRangePicker width="md" name={['contract', 'createTime']} label="合同失效工夫" />在useColumns中应用 const columns = defineProTableColumn<MaintenanceListVo>([ { title: '投诉工夫', dataIndex: 'createTime', key: 'createTime', hideInSearch: true, }, { title:'', dataIndex: 'createTime', key: 'createTime', valueType: 'dateRange', hideInTable: true, fieldProps: { placeholder: ['投诉工夫','投诉工夫'], }, search: { transform: (value) => { return { startTime: value[0], endTime: value[1], }; }, }, }, ]); /** 解决表格列 */export function useColumns() { return { columns };}ProFormSelect 抉择框<ProFormSelect width={364} rules={[ { required: true, message: '请抉择要转让的员工', }, ]} placeholder="请抉择人员" fieldProps={{ onChange: (e) => { setData(staffList.find((item) => item.employeeId == e)); }, }} help={currentStore?.storeType === 'community' && '转让后你就不是该小区的负责人,请慎用!'} name="employeeId" options={staffList.map((item) => ({ label: item.employeeName, value: item.employeeId, }))} label="转让到" />

December 6, 2021 · 1 min · jiezi

关于antd:virtualizedtableforantd4空白bug修复

在业务开发过程中,应用了antd的table,为渲染大量的数据并进行性能调优,应用了virtualizedtableforantd4组件,具体如何应用参考https://cnpmjs.org/package/vi... 因为页面须要动静扭转table的高度,所以scroll的y应用了动静计算 在渲染table的时候,呈现了大量的空白,如下图所示。 于是去看了下源码,问题呈现在scroll.y应用了字符串计算,设置为字符串的时候,ctx.y会获取parentElement.offsetHeight,而因为渲染问题,所获取的值为0,最终所取得的tail为默认的10(tail就是j,overscanRowCount默认为5),所以呈现了空白景象。 else if (typeof scroll_y === "string") { /* a string, like "calc(100vh - 300px)" */ if (ctx.debug) console.warn("AntD.Table.scroll.y: ".concat(scroll_y, ", it may cause performance problems.")); ctx._raw_y = scroll_y; ctx._y = ctx.wrap_inst.current.parentElement.offsetHeight; } 晓得了起因后,就好解决了,既然你获取不到offsetHeight,那么我依据你用户传的string,我去帮你动静计算出实在后果,问题就迎刃而解了。 else if (typeof scroll_y === "string") { /* a string, like "calc(100vh - 300px)" */ if (ctx.debug) console.warn("AntD.Table.scroll.y: ".concat(scroll_y, ", it may cause performance problems.")); ctx._raw_y = scroll_y; ctx._y = transferStringToNumberSize(scroll_y); } transferStringToNumberSize函数如下: ...

November 19, 2021 · 13 min · jiezi

关于antd:antd-table-滚动条-表头-多余

问题: 解决: 成果:

September 17, 2021 · 1 min · jiezi

关于antd:实现antd的穿梭框

题目:实现antd穿梭框的基本功能 antd的穿梭框实现有以下几个要点: 数据分为两局部,source-左侧,target-右侧选项在左右两个框中穿梭,实际上是对 source 和 target 这两个数组进行增删的数据保护上面是残缺实现,线上demo import React, { useState } from "react";import "./styles.css";export default function Transfer(props) { const [source, setSource] = useState(props.dataSource); const [target, setTarget] = useState([]); const [sourceSelectedKeys, setSourceSelectedKeys] = useState([]); const [targetSelectedKeys, setTargetSelectedKeys] = useState([]); const onSelectChange = (key, type, e) => { if (type === "source") { setSourceSelectedKeys([...sourceSelectedKeys, key]); } else { setTargetSelectedKeys([...targetSelectedKeys, key]); } }; const moveToRight = () => { const dataSourceCpy = [...source]; const moveItem = dataSourceCpy.filter((item) => sourceSelectedKeys.includes(item.key) ); const newSource = dataSourceCpy.filter( (item) => !sourceSelectedKeys.includes(item.key) ); setTarget([...moveItem, ...target]); setSource(newSource); setSourceSelectedKeys([]); }; const moveToLeft = () => { const targetCpy = [...target]; const moveItem = targetCpy.filter((item) => targetSelectedKeys.includes(item.key) ); const newTarget = targetCpy.filter( (item) => !targetSelectedKeys.includes(item.key) ); setTarget(newTarget); setSource([...moveItem, ...source]); setTargetSelectedKeys([]); }; const handleTransfer = (type) => { if (type === "right") { moveToRight(); } else { moveToLeft(); } }; return ( <div className="container"> <div className="dataSource"> {source.map((item) => { return ( <div key={item.key}> <input type="checkbox" name={item.title} onChange={(e) => { onSelectChange(item.key, "source", e); }} /> <label htmlFor={item.title}>{item.title}</label> </div> ); })} </div> <div className="operation"> <button onClick={() => { handleTransfer("right"); }} > {">"} </button> <button onClick={() => { handleTransfer("left"); }} > {"<"} </button> </div> <div className="target"> {target.map((item) => { return ( <div key={item.key}> <input type="checkbox" name={item.title} onChange={(e) => { onSelectChange(item.key, "target", e); }} /> <label htmlFor={item.title}>{item.title}</label> </div> ); })} </div> </div> );}.container { display: flex;}.dataSource { border: 1px solid black; width: 200px; height: 300px;}.operation { display: flex; flex-direction: column;}.target { border: 1px solid black; width: 200px; height: 300px;}

September 8, 2021 · 2 min · jiezi

关于antd:antd-table-样式修改

.ant-table-wrapper { width: 98%; height: 100%; position: relative; top: 30px;}.ant-table { background-color: rgb(9,100,100); color: white;}// 表头款式.ant-table-thead > tr > th { background-color: rgb(3,50,50); color: white;}// 批改选中行款式.ant-table-tbody { > tr:hover:not(.ant-table-expanded-row) > td,.ant-table-row-hover,.ant-table-row-hover>td{ background-color: rgb(18, 75, 75) !important; }}// 去除边框,留左边框 && 表格内容居中 && 行高.ant-table-tbody > tr > td ,.ant-table-thead > tr > th{ border-bottom: none; border-right: 1px solid #ccc; text-align: center !important; padding: 10px 10px !important;}// 去除表头最左边边框.ant-table-container table > thead > tr:first-child th:last-child { border-right: none;}// 去除tbody最左边边框.ant-table-container table > tbody > tr td:last-child { border-right: none;}// 暂无数据款式.ant-empty-description { color: white;}// 奇数行.table-color-odd { background-color: rgb(9, 100, 100);}// 偶数行.table-color-even { background-color: rgb(19, 147, 147);}

August 30, 2021 · 1 min · jiezi

关于antd:antd-组件-RangePicker-扩展支持预选范围回填选中

最近在做和时间段范畴相干的,预设罕用的日期范畴能够进步用户体验。能够说 antd 的组件是很不错的,然而美中不足的是,不反对预设范畴回填选中,怎么办?在此基础上封装一下好了。 先来看下效果图: 未选中: 选中回填: 看了下渲染进去的 html 内容,用的是 Tag 实现的。那我也这么实现好了。 次要是利用 RangePicker 的 renderExtraFooter 进行解决。如果回填值和预设值统一,就设置选中,否则就不选中。 还有一个要害的点:就是选中预设的 tag 后,须要敞开日期抉择弹窗。所以还须要额定的解决 open。 具体实现看上面代码: // rangePicker.tsx import React, { forwardRef } from 'react'import { Tag, DatePicker } from 'antd';import { useState } from 'react';import { RangePickerProps, RangePickerValue } from 'antd/lib/date-picker/interface';const { RangePicker } = DatePickerconst Index = ({value, ranges = {}, onChange, open, format = "YYYY-MM-DD", ...props}: RangePickerProps, ref: any) => { const [val, setVal] = useState(value) const [show, setShow] = useState(open) const isDateSame = (key: string) => { const [start, end] = ranges[key] as RangePickerValue return val && val.length && start && end ? start.isSame(val[0]) && end.isSame(val[1]) : false } const tagCheck = (key: string) => () => { setVal(ranges[key]) const [start, end] = ranges[key] as RangePickerValue onChange && onChange(ranges[key] as RangePickerValue, [start ? start.format(format as string) : '', end ? end.format(format as string) : '']) setShow(false) } const footRanges = () => ( <div className="range-quick-selector"> {Object.keys(ranges).map(key => ( <Tag key={key} onClick={tagCheck(key)} color={isDateSame(key) ? 'blue' : ''}>{key}</Tag> ))} </div> ) const onOpenChange = (status: boolean) => { setShow(status) } const onChangeFn = (dates: RangePickerValue, dateStrings: [string, string]) => { onChange && onChange(dates, dateStrings) setVal(dates) } return ( <RangePicker renderExtraFooter={footRanges} value={val} onChange={onChangeFn} onOpenChange={onOpenChange} open={show} format={format} {...props} /> )}export default forwardRef(Index)怎么用呢?原来怎么应用,当初就怎么应用: ...

June 26, 2021 · 2 min · jiezi

关于antd:Antd-Pro-V5-登录后重新远程请求菜单

因为路由是存在 initialState 这个 Hooks 中,如下: const { initialState, setInitialState } = useModel('@@initialState');所以只须要在登录的逻辑中利用这段代码即可: // 本人写的近程申请接口import { R_Menu } from '@/services/builder';// 导入零碎默认的菜单import SystemRoutes from '@/../config/routes'; const fetchUserInfo = async () => { const userInfo = await initialState?.fetchUserInfo?.(); // 从近程接口拉取菜单 const menu = await R_Menu(); // 合并零碎路由 & 业务菜单 const mergeMenu = SystemRoutes.concat(menu); if (userInfo) { setInitialState({ fetchUserInfo(): Promise<API.CurrentUser | undefined> { return Promise.resolve(undefined); }, settings: undefined, ...initialState, menuData: mergeMenu, // 留神:在这里笼罩即可 currentUser: userInfo }); } };

June 9, 2021 · 1 min · jiezi

关于antd:antd3x-Select组件多选框自定义实现全选功能

antd select组件没有一键全选、全不选性能利用dropdownRender这个api 自定义下拉框内容减少全选、全不选option项 const selectGroup = (groupIds: number[]) => { this.setState({ groupIds: groupIds }); this.props.form.setFieldsValue({'department': groupIds});};const selectAllGroup = () => { selectGroup(departmentList.map(({ projectGroupId }) => projectGroupId));};const deselectAllGroup = () => { selectGroup([]);};<FormItem className={styles.nameWrapper} label={formatMessage({ id: 'project.process.department' })}> {getFieldDecorator('department', {})( <FormItemWithTooltip title={this.state.departmentTooltip}> <Select placeholder={formatMessage({ id: 'please.select' })} className={styles.templateName} getPopupContainer={trigger => trigger.parentNode as HTMLElement} allowClear mode="multiple" maxTagCount={5} showArrow filterOption={(value, option) => (option.props.children as string).includes(value) } dropdownRender={(menuNode, props) => { const allChecked = this.state.groupIds.length === departmentList.length; return ( <> <div className={styles.checkAll} onClick={ allChecked ? deselectAllGroup : selectAllGroup } onMouseDown={e => { e.preventDefault(); return false; }} > {allChecked ? formatMessage({id: 'upload.list.select.none'}) : formatMessage({id: 'upload.list.select.all'})} </div> {menuNode} </> ); }} onChange={(value: number[]) => { selectGroup(value); }} > {departmentList.map(({ projectGroupId, projectGroupName }) => { return ( <Option value={projectGroupId} key={projectGroupId} title={projectGroupName}> {projectGroupName} </Option> ); })} </Select> </FormItemWithTooltip> )}</FormItem>

April 29, 2021 · 1 min · jiezi

关于antd:antd-table-filter过滤columns-title的方法

当type为1是不显示‘操作’列 filter写在这里页面初始化只执行一次this.columns = [ { title: '姓名', dataIndex: 'xm', key: 'xm', }, { title: '操作', dataIndex: 'handle', key: 'handle', },].filter((item)=>{ if(this.state.type==1){ return item.title!="操作" }else{ return item }})放在<Table columns={this.columns.filter((item)=>{ if(this.state.type==1){ return item.title!="操作" }else{ return item }}} 依据state type的扭转而执行

October 30, 2020 · 1 min · jiezi

关于antd:如何监听Ant-Design-of-React-的Search组件设置allowClear后的点击事件

因为官网没有给出这个x的点击事件的监听hook,所以只能本人曲线救过。 依据文档阐明: onSearchThe callback function triggered when you click on the search-icon, the clear-icon or press the Enter key 能够理解到onSearch的触发条件。 而后通过event对象进行触发类型的判断。 onSearch = (value, event) => { if (event.nativeEvent.type === 'click' && value === '') { // listen click setTimeout(() => { // TODO }, 300); } if (event.nativeEvent.type === 'enter' && value === '') { // Enter setTimeout(() => { // TODO }, 300); } if (value === '') { return; } // search};

September 27, 2020 · 1 min · jiezi

关于antd:三步解决reactrouterdom与antd的menu在刷新时路由高亮与页面不匹配问题

问题简述:在页面刷新时,meun高亮会跳回设置的默认项,而页面还是浏览器地址对应的页面,从而呈现高亮与理论页面不匹配的景象。 解决办法:引入withRouter包裹导航组件,并在Menu里设置selectedKeys属性。 代码如下: //引入withRouterimport { withRouter } from 'react-router-dom'//包裹导航组件export default withRouter(Home)//设置Menu的selectedKeys属性<Menu theme="dark" mode="inline" selectedKeys = {[this.props.location.pathname]} defaultSelectedKeys={['register']}>附上官网文档链接:React Router-withRouterAntd menu api

September 20, 2020 · 1 min · jiezi

关于antd:antd-upload上传附件不断监听status返回的值

antd上传组件upload上传文件时,不断更新上传status返回的值 handleUpload=(data)=>{     const fileList=data.fileList;     this.setState({    //上传文件一直监听status          fileList,     }) }

August 12, 2020 · 1 min · jiezi

react-基于antd表格自适应宽度显示鼠标滑动显示详细内容解决办法

最终效果内容如下 创建公共组件获取表格宽度class EllipsisTooltip extends React.Component { constructor(props){ super(props); this.state={ visible: false, DtStyle:{}, DtClass:'', } } componentDidMount(){ this.getItemWidth() } getItemWidth=()=>{ if(this.container){ this.setState({ DtStyle:{width:`${this.container.clientWidth}px`}, DtClass:'NowrapAndTitle' }) } } render () { const {DtStyle,DtClass}=this.state; return ( <div className="PublicTableTooltip"> <Tooltip placement="topLeft" title={this.props.title} overlayClassName="ItemPublicTableTooltip"> <div ref={node => this.container = node} style={DtStyle} className={DtClass}>{this.props.children}</div> </Tooltip> </div> ) }}export default EllipsisTooltip2.设置表格内的样式 .PublicTableTooltip{ .NowrapAndTitle{ // 不换行显示。。。 text-align: left; overflow: hidden; -o-text-overflow: ellipsis; text-overflow: ellipsis; white-space: nowrap; }}.ItemPublicTableTooltip{ .ant-tooltip-inner{ background-color: rgba(24, 145, 150, 0.75); } .ant-tooltip-arrow{ border-top-color: rgba(24, 145, 150, 0.75); }}3.基本功能实现哪里需要哪里调用即可 ...

November 4, 2019 · 1 min · jiezi

使用puppeteer-实现对Antd-Select-组件自动化测试

问题描述项目需要使用jest+puppeteer 实现对Antd组件库中的Select组件进行操作,选中其中的某一项的值。测试页面为Antd Select 使用puppeteer Recorder录制出来的测试代码如下所示 const puppeteer = require('puppeteer');(async () => { const browser = await puppeteer.launch() const page = await browser.newPage() await page.goto('https://ant.design/components/select-cn/') await page.setViewport({ width: 1154, height: 586 }) await page.waitForSelector('.ant-select-focused > .ant-select-selection > .ant-select-arrow > .anticon > svg') await page.click('.ant-select-focused > .ant-select-selection > .ant-select-arrow > .anticon > svg') await page.waitForSelector('div > .ant-select-dropdown > #\31 55fc83a-09de-47b7-a57e-a6042a0e3a5b > .ant-select-dropdown-menu > .ant-select-dropdown-menu-item-active') await page.click('div > .ant-select-dropdown > #\31 55fc83a-09de-47b7-a57e-a6042a0e3a5b > .ant-select-dropdown-menu > .ant-select-dropdown-menu-item-active') await browser.close()})()使用这段代码进行E2E测试,经常会在这段代码timeout ...

September 10, 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

nzform-inline-模式下多类型控件打乱布局的问题

nzForm 布局被打乱nz-form 布局被打乱的原因表单样式使用行内:[nzLayout]="'inline'"表单中使用多中类型的控件(input、datepicker、select...)会出现日期选择控件没有充满,同时 select 控件会被挤到下一行,看看下面的效果 使用官方的样式类设置统一宽度解决在模板对应的CSS中使用下面的样式设置统一宽度 /* 通过设置下面两个样式的宽度解决 nz-select 打乱 form 布局的问题*//* nz-form-control 外围类 */.ant-form-item-control-wrapper{ width: 152.16px;}/* nz-form-item 样式类*/.ant-form-item{ width: 221.2px;}设置后的效果是:其他解决方法实际不该使用inline模式,而应该使用horizontal模式,一个 nz-form-item 中放置4个 label 和 control因为一个 nz-form-item 是一行

July 12, 2019 · 1 min · jiezi

antddesign-FormSelect联合使用-placeholder-不起作用问题

antd-design Form,Select联合使用 placeholder 不起作用问题起因最近在用antd写表单的时候遇到个问题:Form,Select组件一起使用时,设置Select组件的placeholder属性并没有起作用。如下图:发现Select组件的placeholder属性并没有起作用。 排查对照官方文档看了一下官方的代码<Form.Item label="Gender" > {getFieldDecorator('gender', { rules: [{ required: true, message: 'Please select your gender!' }], })( <Select placeholder="Select a option and change input text above" onChange={this.handleSelectChange} > <Option value="male">male</Option> <Option value="female">female</Option> </Select> )} </Form.Item>发现跟自己的代码唯一的区别就是我这里设置了这个表单项的initialValue属性,官方文档是这样讲的: 对表单内的组件使用onChange合成事件不介意使用setState以及value为组件绑定值,介意使用initialValue设置初始化值。所以我这里写的也是没问题的。没办法了呀,遇到这种莫名其妙的问题只能去antd-github-issues去找有没有人提出过相同的问题了,搜了一下发现还真有!!! 修改自己的代码之后<Form.Item {...formItemLayout} label="证件类型"> {getFieldDecorator('certType', { initialValue: publicAccount.certType ? publicAccount.certType : undefined, rules: [{ required: true, message: '请选择证件类型' }], })( <Select style={{ width: 280 }} placeholder="请选择证件类型" onChange={this.handleCertTypeChange}> <Option value="1">身份证</Option> <Option value="2">营业执照</Option> </Select>, )} </Form.Item> 总结开源有风险,使用需谨慎。当然不是说antd不好(真香)。对于一些api来说还是希望稳定尽可能能够完整一点吧。当然这里自己踩过这个坑之后下次肯定就记得了,这里也记录一下,万一有人遇到相同的问题呢。

July 1, 2019 · 1 min · jiezi

给组件库加个鸡腿

为了工作,也为了长点见识,费心费力捣腾了一个组件库antd-doddle,还是有成长的,总结都写了两篇,收获还是有的,如果你喜欢听故事: 从dist到es:发一个NPM库,我蜕了一层皮从24m到1m, 一个react+antd后台系统构建打包历程组件文档地址为什么要鸡腿组件库写出来了,项目也陆陆续续用了,但组里小伙伴用起来,总是要问这个配置怎么写,这个组件怎么用,手把手教学,总不能让小伙伴都去看源码实现吧,所以自己一发狠,用一整晚,写了一篇组件库使用指南,基本就是下图这个风格,示例代码有了,输入输出配置有了,输出效果有了,临时解决了一些问题,但随着业务膨胀,使用人员逐步增多,这种静态文档已经不能满足需求了。 鸡腿是要解决什么不是解决解饿,是让生活更富有。这一篇组件库使用指南,该有的内容都有,但问题还是很多: 整个组件库就一个指南,特别长,没有很好的分类,无法快速找到自己的所需;由于篇幅和实现难度,组件库demo太单一,覆盖面太小;组件库没有用例,每一次修改,没有demo用例来快速测试,发布了才测试,有比较大的风险,容易造成频繁发版;自己又想捣腾了;鸡腿原配方市面上有很多成熟的方案,推荐比较多的就是Docz,但试了一下,使用起来并没有介绍的那么好用,规范太死板,复杂demo编写实现困难,所以最后还是采用了antd的组件库方案Bisheng,毕竟是完美兼容react,与antd的组件库。花了一两天折腾了一下Bisheng,发现其还是很好用的,库更多是作为一个路由适配的数据流容器(RenderProps),可配置性很高,UI完全由使用人自己定制(官方称其为主题),库本身提供一个简易的主题Bisheng-Theme-One。只要搞懂了数据长什么样,编写一个展示性组件就显得不是那么难了,从下图看看(图片路由:http://localhost:8090/guide/introduce): data:是所有的文档信息,和你的文档结构具有强匹配,可以用此数据做文档菜单的数据源;pageData:是当前路由,匹配出的文档内容,也就是你正文要显示的内容;themeConfig:是一个主题定制对象,你想自己定义的变量,都可以在这个对象中增加utils:toReactComponent是暴露出来的一个常用方法当然,每一个主题有一个组件路由适配方案,需要在主题根目录下的index.js进行配置,一张图胜所有:左边的配置会告诉bisheng插件,在匹配到某个路由时去调用相应的组件。组件的具体实现,请参看源码,这里不再一一分析。 鸡腿的味道总的来说,鸡腿的味道还是很香的,菜单清晰,组件文档一目了然,还配有相应的demo,上两张高清大图感受一下。 捣腾到此为止,明天开始专心复习【捂脸】。

June 26, 2019 · 1 min · jiezi

Ant-Design-Pro-的-Docker-部署方式

背景Ant Design Pro是一个企业级中后台解决方案,在Ant Design组件库的基础上,提炼出典型模板/业务组件/通用页等,在此基础上能够使开发者快速的完成中后台应用的开发。 在使用Ant Design Pro的过程中,可以发现它提供了一系列基于docker的开发部署方式,如下图。但是官方文档中并没有具体的介绍,本文的主要目的就是解析Ant Design Pro中对于docker的使用。 docker相关为什么使用docker?环境部署是所有团队都必须面对的问题,随着系统越来越大,依赖的服务也越来越多,例如:Web服务器 + MySql数据库 + Redis缓存等依赖服务很多,本地搭建一套环境成本越来越高,初级人员很难解决环境部署中的一些问题服务的版本差异及OS的差异都可能导致线上环境BUG,项目引入新的服务时所有人的环境需要重新配置任何安装过Docker的机器都可以运行这个容器获得同样的结果, 同的容器,从而完全消除了不同环境,不同版本可能引起的各种问题。例如,在前端开发中通常会遇到nodejs版本问题,就可以通过docker的方式进行解决。 docker中的概念Docker有三个基本概念:镜像(image),容器(container),仓库(repository)。 镜像(image): 镜像中包含有需要运行的文件。镜像用来创建container,一个镜像可以运行多个container;镜像可以通过Dockerfile创建,也可以从Docker hub/registry上下载。容器(container): 容器是Docker的运行组件,启动一个镜像就是一个容器,容器是一个隔离环境,多个容器之间不会相互影响,保证容器中的程序运行在一个相对安全的环境中。仓库(repository): 共享和管理Docker镜像,用户可以上传或者下载上面的镜像,官方地址为 https://registry.hub.docker.com/ (类似于github对源代码的管理),也可以搭建自己私有的Docker registry。常见docker命令使用当前目录Dockerfile创建镜像,标签为xxx:v1: docker build -t xxx:v1 .创建新容器并运行: docker run --name mynginx -d nginx:latest在容器中开启交互终端:docker exec -i -t container_id /bin/bash启动容器:docker start container_name/container_id停止容器:docker stop container_name/container_id重启容器:docker restart container_name/container_id什么是docker-compose?实际项目中,不可能只单单依赖于一个服务,例如一个常见的Web项目可能依赖于: 静态文件服务器,应用服务器,Mysql数据库等。我们可以通过分别启动单个镜像,并把镜像绑定到本地对应端口的形式进行部署,达到容器可通信的目的。但是为了更方便的管理多容器的情况,官方提供了docker-compose的方式。docker-compose是Docker的一种编排服务,是一个用于在 Docker 上定义并运行复杂应用的工具,可以让用户在集群中部署分布式应用。 compose中有两个重要的概念: 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。项目 (project):由一组关联的应用容器组成的一个完整业务单元,在docker-compose.yml 文件中定义。一个项目可以由多个服务(容器)关联而成,compose 面向项目进行管理,通过子命令对项目中的一组容器进行便捷地生命周期管理。 脚本解析本地开发运行npm run docker:dev该命令使用docker-compose up命令通过docker-compose.dev.yml模板启动相关容器。 docker-compose.dev.yml内容如下: 这个compose文件定义了一个服务:ant-design-pro_dev。该服务使用Dockerfile.dev构建了当前镜像。将该容器内部的8000端口映射到host的8000端口。为了容器和host的数据同步,该容器挂载三个数据卷:../src:/usr/src/app/src, ../config:/usr/src/app/config, ../mock:/usr/src/app/mock。它将主机目录映射到容器,这样容器内的三个目录可以跟host对应的三个目录做到数据同步。 Dockerfile.dev内容如下:该容器使用node:latest作为基础镜像,并设定/usr/src/app作为工作目录。首先将package.json文件复制到该目录,并安装相关的依赖包,之后复制该文件夹下所有内容到该目录下,并使用npm run start启动应用。由于数据卷的存在,本地的三个文件夹下的任何修改都可以同步到容器中,达到更新的目的。 ...

June 25, 2019 · 1 min · jiezi

JeecgBoot-20-入门视频教程

视频大纲 视频地址: https://pan.baidu.com/s/1Il0T... 提取码:hok5 源码下载: https://github.com/zhangdaisc... (喜欢请Star)

June 6, 2019 · 1 min · jiezi

解决antd-icon打包过大的问题

1.webpack配置alias resolve: { alias: { ... '@ant-design/icons/lib/dist$': path.resolve(__dirname, '../src/icons.ts'), '@': path.resolve(__dirname, '../src') }}2.在src目录下编写icons.ts,内容是使用到的icon export {default as UserOutline} from '@ant-design/icons/lib/outline/UserOutline';export {default as CloseCircleFill} from '@ant-design/icons/lib/fill/CloseCircleFill';export {default as InfoCircleFill} from '@ant-design/icons/lib/fill/InfoCircleFill';export {default as CheckCircleFill} from '@ant-design/icons/lib/fill/CheckCircleFill';注:1.如果打包错误,请检查alias的配置和icons.ts的路径2.icon.ts的内容,包括自己用到的icon和组件用到的icon

June 3, 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

高性能方案实现antdesign在运行时动态改变主题色利用webpackthemecolorreplacer

今天利用webpack-theme-color-replacer插件,为ant-design实现了在运行时动态切换主题色的功能,无需在页面进行less的编译,提升了切换速度。有需要的同学可以参考下。 源码:https://github.com/hzsrc/ant-... 效果预览:https://deploy-preview-4289--...在右侧中央的配置按钮点开可以切换主题色。 实现方案:方案是统一的,见之前的文章:https://segmentfault.com/a/11... 基本步骤:基于 ant-design-pro 这个项目上进行修改: 1、webpack加入插件配置:查看修改:https://github.com/hzsrc/ant-... 2、运行时动态切换主题色,查看修改:https://github.com/hzsrc/ant-... (因笔者时间有限,color.less文件中的colorPalette变换函数所涉及的颜色值没有纳入) 实现效果:初始主题色: 切换后主题色:

May 25, 2019 · 1 min · jiezi

高性能方案实现antdesignvue在运行时动态改变主题色利用webpackthemecolorreplacer

今天利用webpack-theme-color-replacer插件,为ant-design-vue实现了在运行时动态切换主题色的功能,无需在页面进行less的编译,提升了切换速度。有需要的同学可以参考下。 源码:https://github.com/hzsrc/ant-... 效果预览:http://test.hz300.com/ant-des...在右侧中央的配置按钮点开可以切换主题色。 实现方案:方案是统一的,见之前的文章:https://segmentfault.com/a/11... 基本步骤:基于 ant-design-pro-vue 这个项目上进行修改: 1、webpack加入插件配置:查看修改:https://github.com/hzsrc/ant-... 2、运行时动态切换主题色,查看修改:https://github.com/hzsrc/ant-...新增文件:https://github.com/hzsrc/ant-... 实现效果:初始主题色: 切换后主题色:

May 25, 2019 · 1 min · jiezi

长期更新记录一下近期工作中涉及到的内容DvaJs-Ant-Design

DvaJs dispatch在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如: dispatch({ type: 'user/add', // 如果在 model 外调用,需要添加 namespace payload: {}, // 需要传递的信息}); 可以通过类似this.props.dispatch({type: 'siteConfig/eff_getMutantGene', // siteConfig为modal文件夹名,eff_getMutantGene为Effects方法。payload: {itemId: item[i].itemid}})调用Model中的Reducer或者Effects。 connect如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。 Ant Design <From.Item />表单域表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。 这里我们封装了表单域 <Form.Item /> 。 <Form.Item {...props}>{children}</Form.Item> getPopupContainer getPopupContainer菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 如果发现下拉菜单跟随页面滚动,或者需要在其他弹层中触发 Select,请尝试使用 getPopupContainer={triggerNode => triggerNode.parentNode}*将下拉弹层渲染节点固定在触发器的父元素中。getFieldDecorator.1经过 getFieldDecorator 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管,这会导致以下结果: ...

May 23, 2019 · 1 min · jiezi

babel在提升前端效率的实践

大纲遇到的问题场景及解决方案对比什么是babel?解决过程目前遗留的问题目前实现功能API参考遇到的问题场景及解决方案对比我们目前采用的是antd + react(umi)的框架做业务开发。在业务开发过程中会有较多频繁出现并且相似度很高的场景,比如基于一个table的基础的增删改查,这个相信大家都非常熟悉。在接到一个新的业务需求的时候,相信有不少人会选择copy一份功能类似的代码然后基于这份代码去改造以满足当前业务,当然我目前也是这样做的~ 其实想把这块功能提取成一个公共组建的想法由来已久,最近开始做基础组件,便拿这个下手了。经过一周左右的时间完成了基础组件的编写。 查看基础支持的功能点API。基本的思路是通过json生成一些抽象配置,然后通过解析json的抽象配置+渲染器最终生成页面。json配置涵盖了目前80%的业务场景的基本需求,但是可扩展性很低。比如一些复杂的业务场景:表单的关联校验、数据关联显示、多级列表下钻等等功能。虽然通过一些较为复杂的处理可以把这些功能融入进来,但最终组件将会异常庞大难以维护。 所以,我能不能通过这些json配置通过某种工具生成对应的代码?这样一来以上提到的问题就完全不存在了,因为这和我们自己写的代码完全一样,工具只是帮我们完成初始化的过程。所以后来想了很多办法,最初采用template string的方式,这种方式较为简单粗暴,无非通过string中嵌套变量的判断来输出code。但是在实际写的时候发现很多问题,比如 function的输出(JSON.stringify会将function忽略)多层函数嵌套之后怎么获取最终渲染的节点code嵌入变量怎么实现、umi-models-effects/reducer中额外的字典查询怎么生成等等..最终学习了一些生成代码的工具比如angular-cli以及一些关于js生成代码的文章,主要是通过知乎上的这篇讨论了解到了大家是怎么处理这种问题的。最终决定采用babel的生态链来解决上述遇到的问题。 我们目前采用的方式是基于antd+react(umi)编写通用的CRUD模板,然后通过代码生成器解析json中的配置生成对应的代码,大致的流程是: React --> JavaScript AST ---> Code Generator --> Compiler --> Page 目前功能只是完成了初步版本,待应用在项目中使用一段时间稳定之后将会开源~什么是babel?Babel是一个工具链,主要用于编译ECMAScript 2015+代码转换为向后兼容的可运行在各种浏览器上的JavaScript。主要功能: 语法转换环境中缺少的Polyfill功能源代码转换查看更多Babel功能Understanding ASTs by Building Your Own Babel Plugin 如上提供了babel基本的流程及一篇介绍AST的文章。 我的理解中比如一段string类型code,首先通过babel.transform会将code转为一个包含AST(Abstract Syntax Tree)的Object,同样可以使用@babel/generator将AST转为code完成逆向过程。例如一段变量声明代码: const a = 1;在解析之后的结构为: { "type": "Program", "start": 0, "end": 191, "body": [ { "type": "VariableDeclaration", "start": 179, "end": 191, "declarations": [ { "type": "VariableDeclarator", "start": 185, "end": 190, "id": { "type": "Identifier", "start": 185, "end": 186, "name": "a" }, "init": { "type": "Literal", "start": 189, "end": 190, "value": 1, "raw": "1" } } ], "kind": "const" } ], "sourceType": "module"}首先类型为VariableDeclaration,首先他的类型是const,可以通过点击查看api其它还有let、var的值。其次是声明declarations部分,这里值为数组,因为我们可以同时定义多个变量。数组中值的类型为VariableDeclarator,包含id和init两个参数,分别为变量名称以及变量值。id的类型为Identifier,译为修饰符即是变量名称。init类型为Literal,即是常量,一般常用的有stringLiteral、numericliteral、booleanliteral等。此时即完成了变量赋值的过程。 ...

May 21, 2019 · 3 min · jiezi

antdesign-奇技淫巧

如果你在 vscode 中编写Angular,那么安装 ng-zorro-vscode 代码片断,对开发效率很有帮助。

May 12, 2019 · 1 min · jiezi

antd-树形组件常用工具

treeUtil.js import {Tree,message,Button,Input } from 'antd';import React from 'react'import axios from 'axios'const { TreeNode } = Tree;const Search = Input.Search;const getParentKey = (key, tree) => { let parentKey; for (let i = 0; i < tree.length; i++) { const node = tree[i]; if (node.children) { if (node.children.some(item => item.key === key)) { parentKey = node.key; } else if (getParentKey(key, node.children)) { parentKey = getParentKey(key, node.children); } } } return parentKey;};const dataList = [];const generateList = (data) => { for (let i = 0; i < data.length; i++) { const node = data[i]; const key = node.key; dataList.push({ key, title: node.title }); if (node.children) { generateList(node.children); } }};export default class treeUtil extends React.Component{ constructor(props) { super(props); this.state = { treeData:[] }; } componentWillMount (){ this.getTree(); } getTree(){ axios.get(this.props.treeurl).then(res =>{ generateList(res.data.data) this.setState({ treeData:res.data.data, haveTreeData:true, expandedKeys: [], searchValue: '', autoExpandParent: true, }) }) } onExpand = (expandedKeys) => { this.setState({ expandedKeys, autoExpandParent: false, }); } onSelect = (selectedKeys) => { console.log(this.props) // this.formRef.setState({ // disabled:true // }) this.setState({ selectedKeys}); if(selectedKeys.length>0 && this.props.selectMethod){ this.props.selectMethod(selectedKeys) } } onChange = (e) => { const value = e.target.value; const newExpandedKeys = [] let expandedKeys = dataList.map((item) => { if (item.title.indexOf(value) > -1) { return getParentKey(item.key, this.state.treeData); } return null; }).filter((item, i, self) => item && self.indexOf(item) === i); expandedKeys.forEach(x=>{ newExpandedKeys.push(x.toString()) }) this.setState({ expandedKeys:newExpandedKeys, searchValue: value, autoExpandParent: true, }); } onCheck = (checkedKeys) => { console.log('onCheck', checkedKeys); if(checkedKeys.length>0 && this.props.checkMethod){ this.props.checkMethod(checkedKeys) } this.setState({ checkedKeys }); } renderTreeNodes = data => data.map((item) => { if (item.children) { return ( <TreeNode title={item.title} key={item.key} dataRef={item}> {this.renderTreeNodes(item.children)} </TreeNode> ); } return <TreeNode {...item} />; }) addTree(){ // //解除禁用 // this.formRef.setState({ // disabled:false // }) let selectedKey = null; if(this.state.selectedKeys){ selectedKey= this.state.selectedKeys[0] }else { selectedKey = null; } } change(){ // this.formRef.setState({ // disabled:false // }) } delete(){ axios.get(this.props.deleteUrl+this.state.selectedKeys[0] ).then(res =>{ if(res.data.success){ message.success(res.message); this.getTree() this.formRef.props.form.resetFields(); }else{ message.warning("系统异常"); } }) } render(){ const { searchValue, expandedKeys, autoExpandParent } = this.state; const loop = data => data.map((item) => { const index = item.title.indexOf(searchValue); const beforeStr = item.title.substr(0, index); const afterStr = item.title.substr(index + searchValue.length); const title = index > -1 ? ( <span> {beforeStr} <span style={{ color: 'blue' }}>{searchValue}</span> {afterStr} </span> ) : <span>{item.title}</span>; if (item.children) { return ( <TreeNode key={item.key} title={title}> {loop(item.children)} </TreeNode> ); } return <TreeNode key={item.key} title={title} />; }); return( <div> {this.props.button? <div className="btn-group"> {this.props.button.includes("add")?<Button type="primary" onClick={this.addTree.bind(this)}>添加</Button>:""} {this.props.button.includes("update")?<Button type="primary" onClick={this.change.bind(this)}>修改</Button>:""} {this.props.button.includes("delete")?<Button type="primary" onClick={this.delete.bind(this)}>删除</Button>:""} </div>:"" } {this.props.searchable?<Search style={{ marginBottom: 8 }} placeholder="搜索" onChange={this.onChange} />:""} <Tree onExpand={this.onExpand} expandedKeys={this.state.expandedKeys} autoExpandParent={this.state.autoExpandParent} onSelect={this.onSelect} selectedKeys={this.state.selectedKeys} checkable = {this.props.checkable} > {loop(this.state.treeData)} </Tree> </div> ) }}调用 const treeOption = { treeurl:React.rootDirectoryUrl+"/department/getDepartmentTree",//地址 selectMethod:this.onSelect, //选择方法 checkMethod:this.onCheck ,//chect方法 button:["add","update","delete"],//显示哪些按钮 checkable:true,//可check searchable:true,//可搜索}<TreeUtil {...treeOption}/>数据类型 ...

May 9, 2019 · 3 min · jiezi

TypeScript-React-Redux和AntDesign的最佳实践

阿特伍德定律,指的是any application that can be written in JavaScript, will eventually be written in JavaScript,意即“任何可以用JavaScript来写的应用,最终都将用JavaScript来写”在使用新技术的时候,切忌要一步一步的来,如果当你尝试把两门不熟悉的新技术一起结合使用,你很大概率会被按在地上摩擦,会yarn/npm和React脚手架等技术是前提,后面我会继续写PWA深入和Node.js集群负载均衡Nginx,webpack原理解析等~谢谢思否官方对我上篇文章的加精~ 在使用TypeScript前,请你务必万分投入学习好以下内容再尝试:TypeScript必须知识点: javaScript,特别是阮一峰的ES6教程必须要多看几遍,看仔细了,否则你会被TS按在地上摩擦TypeScript文档,什么是TypeScript,一定要看得非常仔细,因为有可能开发时一个极小的问题是你不会的知识点,那么可能会耗费你大量的时间去解决前端性能优化不完全手册 , 这是本人的一篇文章,也应该看看。 哈哈哈~介绍完了配置,后面会有大量的总结~React直接看文档,React官方中文文档,我认为React的中文文档已经写得非常好了,学起来还是比较简单的~Redux,学习Redux之前,建议把官方文档看几遍,然后props context 自定义事件 pubsub-js这些组件传递数据的方式都用熟悉后再上Redux,因为Redux写法非常固定,只是在TS中无法使用修饰器而已,需要最原始的写法。后面的代码有注释,到时候可以看看。(HOOKS和HOC都可以尝试使用,因为React的未来可能大概率使用这些写法)Redux官方文档Ant-Design,目前React生态最好的UI组件库,百分90的使用率,移动端、PC端都支持,pro还可以开箱即用,强烈推荐,开启配置按需加载,后台TO-B项目用起来不要太舒服。Ant-Design官网~学技术切忌过分急躁,一步登天,什么都想学却什么都学不好。作者的心得,持之以恒的努力,把每个技术逐个击破,最后结合起来使用,如鱼得水,基础不牢,地动山摇,本文的代码会把所有配置和Redux,Ant-Design全部配好,开箱即用,其他的功能你看Ant-Design的文档往里面加就行了~正式开启:本文介绍如何配置,已经整体的业务流程如何搭建 GitHub源码地址 包管理器,使用yarn或者npm都可以,这里建议使用yarn,因为Ant-Design官方推荐yarn,它会自动添加依赖。使用官方的 create-react-app的另外一种版本 和 Create React App 一起使用 TypeScriptreact-scripts-ts 自动配置了一个 create-react-app 项目支持 TypeScript。你可以像这样使用:create-react-app my-app --scripts-version=react-scripts-ts, -前提你必须全局下载 create-react-app请注意它是一个第三方项目,而且不是 Create React App 的一部分。 需要的依赖:都在package.json文件中。这里请万分注意,TS的包大部分都是需要下两个,一个原生,一个@types/开头 { "name": "antd-demo-ts", "version": "0.1.0", "private": true, "dependencies": { "@types/jest": "24.0.11", "@types/node": "11.13.7", "@types/react": "16.8.14", "@types/react-dom": "16.8.4", "@types/react-redux": "^7.0.8", "@types/react-router-dom": "^4.3.2", "@types/redux-thunk": "^2.1.0", "babel-plugin-import": "^1.11.0", "customize-cra": "^0.2.12", "less": "^3.9.0", "less-loader": "^4.1.0", "prop-types": "^15.7.2", "react": "^16.8.6", "react-app-rewired": "^2.1.3", "react-dom": "^16.8.6", "react-redux": "^7.0.2", "react-router-dom": "^5.0.0", "react-scripts": "3.0.0", "redux-chunk": "^1.0.11", "redux-devtools-extension": "^2.13.8", "redux-thunk": "^2.3.0", "typescript": "3.4.5" }, "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } * `Ant-Design`按需加载配置 `config-overrides.js`const { override, fixBabelImports, addLessLoader } = require('customize-cra');module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: true, }), addLessLoader({ javascriptEnabled: true, modifyVars: { '@primary-color': '#1DA57A' }, })); ```tsconfig.json ,TS的配置文件 我基本上没怎么改动 ...

April 27, 2019 · 2 min · jiezi

记一次取消ui库的radio过程

概述ui库用的是iview . radio、radioGroup是我们非常常用的组件。radio有一个特征是选中之后无法取消。现实中取消radio的需求是常见且可以理解的。所以看到这个需求之后第一尝试 在iview组件之上搞一搞,这一搞就入坑了,现在就来理一理我的入坑之路吧。1. 原生组件radio的取消首先我们来看一下在 vue 中使用原生的radio组件如何使用取消。 原生radio取消选中的文章非常多,随便拎一篇看看就行,比如 取消radio的三种方法。嗯,原理就是给radio元素的checked属性赋值为 false。具体在 vue 中使用是这样的:const { name, value } = data;<div> <input class=“xxx” type=“radio” name={this.id} ref={name} value={value} onClick={(vlaue) => this.radioGroupChange(value, name)} /> {name}</div>;// 方法radioGroupChange (value, name) { if (this.checked === value) { // 取消选中 this.$refs[name].checked = false; // 当前选中值为空 this.checked = ‘’; } else { this.checked = value; }}这样就OK了。大概唯一区别是 在 vue中通过 ref取到真实 dom。2. iview中radio取消之路借鉴原生的radio,看来取消也不难嘛,监听radio或者 radio的change事件嘛。然而你会发现,重复点选某一个 radio的时候,iview中的on-change函数跟本木有响应。这是为什么呢? 去 iview 源码中看一看2.1 iview组件radio源码探究先上代码为敬:先看下 radio.vue的 template<template> <label :class=“wrapClasses”> <span :class=“radioClasses”> <span :class=“innerClasses”></span> <input type=“radio” :class=“inputClasses” :disabled=“disabled” :checked=“currentValue” :name=“groupName” @change=“change” @focus=“onFocus” @blur=“onBlur”> </span><slot>{{ label }}</slot> </label></template>再看下 change的响应函数 change (event) { // debugger if (this.disabled) { return false; } const checked = event.target.checked; this.currentValue = checked; const value = checked ? this.trueValue : this.falseValue; this.$emit(‘input’, value); if (this.group) { if (this.label !== undefined) { this.parent.change({ value: this.label, checked: this.value }); } } else { this.$emit(‘on-change’, value); this.dispatch(‘FormItem’, ‘on-form-change’, value); }},一开始怀疑 change事件中对 value做了处理,只有 不一样的时候才 emit,仔细看,change函数中并没有这部分处理。问题在于 input 这里监听的 change 事件而不是 click事件,所以反复点击同一个 radio的时候没有 响应是正常的。2.2 如何监听到 click事件看到上面是不是就想 我在 radio上绑定一个click 事件就可以了;事实证明是不可以的,为什么呢? ...

March 14, 2019 · 1 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

从dist到es:发一个NPM库,我蜕了一层皮

这并不是自己第一次发npm包, 所以这里没有多少入门的知识。在此之前已经有一篇前端脚手架,听起来玄乎,实际呢?,但这一次的npm包和上一次的不是一个概念,前者只是一个脚本工具,而这个npm包是日常开发中方法和组件的集合, 是一个库。在读本文前,假定你已经对npm包有一定概念,熟悉Babel编译和webpack打包的常规用法,知道一些前端工程化的知识。假如你也想自己发布一个npm仓库,但对这一块了解的不是很多,推荐webpack官方的创建一个 library打包模式:日常构建与库的构建在前端日常开发中,引入npm库,执行webpack构建已经是一件不能再平常的事情。但大多数时候,我们不关心这个npm库是怎样构成的,我们只需要知道怎么使用,像antd;在工程化成熟的公司,也不关心webpack的配置到底是怎样的,只需要npm run start或npm run build去启动一次热加载或打包。但是如果你是编写一个npm仓库,这些东西你都需要知道。从那时的无知说起,起初,我用公司的构建工具(类似于roadhog)去打包我的库,没有坎坷,构建出一个2M多的包并成功发布。在测试项目中引入,构建成功。import { EnhanceTable, WithSearch } from ‘antd-doddle’; // 引入仓库在浏览器中打开,打击开始到来:提示要引入的对象没有正确导出,就是没有做module.export,所以这是一个打包模式的问题,output.libraryTarget需要了解一下。webpack的output.libraryTarget决定了打包时对外暴露出来的对象是那种模式,默认是var,用于script标签引入,该模式也是我们日常开发构建最常用的模式,除了这一种,还支持的常用选项有:commonjs(2):node环境CommonJS规范,关于commonjs与commonjs2的区别,commonjs 规范只定义了exports,而 module.exports是nodejs对commonjs的实现,实现往往会在满足规范前提下作些扩展,所以把这种实现称为了commonjs2;amd:amd规范,适用于requireJS;this:通过 this 对象访问(libraryTarget:’this’);window:通过 window 对象访问,在浏览器中(libraryTarget:‘window’)。UMD:将你的 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量,(libraryTarget:‘umd’)。jsonp:这是一种比较特殊的模式,适用于有extrnals依赖的时候(splitChunks)。将把入口起点的返回值,包裹到一个 jsonp 包装容器中。所以在这里我们需要设置两个属性来明确打包模式 library: ‘antd-doddle’, libraryTarget: ‘umd’,2M到38KB, 这中间发什么了什么上图是用roadhog打包出来的结果,其显示的是开启gzip后可以压缩到的大小,第一次打包的实际大小大概在2M(antd+moment+react+css),后面仔细一想,公司的组件库也才300kb啊,自己是不是哪里搞错了,所以接着就有了下面的探寻之路。抽离css(300kb),由于此npm库是基于antd的,所以就没有再把antd的css打包一次的必要了。基于roadhog给予的提示,配置了disableAntdStyle为false,css文件降到2kb;接着上面,虽然是基于antd的,但并没有完全用到antd的所有组件,其官方提供了一个按需打包babel插件babel-plugin-import,并在babelrc中配置, js打包体积由1.6M降为1.2M; [“import”, { “libraryName”: “antd”, “libraryDirectory”: “lib”, “style”: “css”, }]如果对webpack多了解一下,或者在写一个库之前读过 创建一个 library,就会发现前面两点都是白扯没有用的,因为对于这个库来说antd就是一个外部依赖(externals),正好roadhog又支持, 打包出来,由1.2M变为38kb, 这是一个质的提升。 externals: { react: { commonjs: ‘react’, commonjs2: ‘react’, amd: ‘react’, }, antd: { commonjs: ‘antd’, commonjs2: ‘antd’, amd: ‘antd’, }, moment: { commonjs: ‘moment’, commonjs2: ‘moment’, amd: ‘moment’, }, }打包大小优化至此就搞定了,但后面发现用roadhog打包库有一些很难解决的难题,为了解决还得去了解他源码逻辑,所以后面还是自己写了一个webpack,非常简单的配置。ES6之后,光有dist是不够的在写这个库之前,我曾想到在我们日常构建时有下面这样一段配置: rules: [{ test: /.js$/, exclude: /(node_modules|bower_components)/, loader: ‘babel-loader’, query: { presets: [’@babel/preset-env’, ‘@babel/preset-react’] } }这段配置是告诉webpack,node_modules中引用的代码不需要再由babel编译一次,但这些代码还是会被打包进dist文件的。在现在前端的主流开发套路中,被引用的库更希望是一个只编译而没有被打包过的,支持按需加载的库。所以dist中被编译打包过的代码再次被打包, 这样就会有不必要的代码出现。所以这样来看,我们只需要将代码编译。babel,没错,就是它,但是多文件编译,还是找个第三方构建工具比较好,我选择了gulp,直接上代码:// 发布打包gulp.task(’lib’, gulp.series(‘clean’, () => { return gulp.src(’./src/**/*.js’) .pipe(babel()) .pipe(gulp.dest(’./lib’));}, ’lessToLib’)); // lessToLib用于将less文件拷贝贷lib文件夹编译过后大概是下面这样的,确实只编译没打包,函数基本原样:本以为到这就结束了,但是这才开始。只编译不打包消除不必要的代码只是很小的原因,重要的东西我觉得换一行说比较好。不顾语文老师的责骂换行,那什么才是是最重要的:按需打包(tree shaking),对于这种组件和方法库,作为使用者,我们希望他能支持按需打包,像lodash和antd这样。所以怀着好奇的心理我去看了他们的package.json,然后发现了这样的配置: “main”: “lib/index.js”, “module”: “es/index.js”, “name”: “antd”,除了认知中的main入口定义,还多了一个module入口.为什么需要这样呢,和我一样无知的,可以先读webpack官方的tree shaking,如果不够直观,可以再看一位大佬写的一篇相关文章聊聊 package.json 文件中的 module 字段。看下面代码:// es6 模块写法 fun.jsexport function square(x) { return x * x;}export function cube(x) { return x * x * x;}// commonJs写法 fun.jsexports.square = function(x) { return x * x;}exports.cube = function(x) { return x * x * x;}// index.js引入import { square} from ‘./fun.js’;const res = square(10);console.log(‘res:’, res);简单来说,就是index.js打包编译时,引入commonJs写法的fun.js,打包会将square与cube两个函数同时打进来。而引入es6写法的fun.js,只会将square打包。这样的操作,对于现在的主流趋势,就是必须的优化,特别对于lodash和antd这种庞大的库。而要使我们的库支持这样的操作,我们需要编译时,禁止babel将es6的module引入方式编译,其实只需要在前面的基础上多配置一个参数:"@babel/preset-react" // lib的打包方式["@babel/preset-env", { “modules”: false }] // 保留es6模块引入的方式得到的是下面这样的结果:和上面的lib对比,感觉更接近原始代码。至此,编译已结束,但是我们还需要在package.json中加上相应的配置: “description”: “antd后台项目前端组件封装和方法库”, “main”: “lib/index.js”, “module”: “es/index.js”, “scripts”: { “build”: “webpack –config webpack.config.js”, “lib”: “gulp lib”, “es”: “gulp es”, “prepublish”: “gulp && webpack –config webpack.config.js” }, “files”: [ “es”, “dist”, “lib”, “utils” ],怎么让库支持多目录输出因为我的库主要包括组件和方法,我把方法放到一起,通过utils作为默认输出。然后项目中引入是这样的:import { EnhanceTable, WithSearch }, utils from ‘antd-doddle’; // 要用里面的方法需要再分解一次或通过utils.xxxconst { DATE_FORMAT, idCodeValid } = utils;虽然感觉上不复杂,但是总感觉别扭,如果你用过dva,就见过下面这样的引入:import { routerRedux } from ‘dva/router’;import dva from ‘dva’;所以我去学习了一下,发现要这样实现也不难分三步,分目录打包,增加一个输出,并增加内部私有映射,package.json增加一个这个映射目录的输出。具体可查看项目源码。实现后,项目引入是这样的:import { EnhanceTable, WithSearch }, utils from ‘antd-doddle’; import { DATE_FORMAT, idCodeValid } from ‘antd-doddle/utils’; // 一步到位小技巧分享npx执行本地命令以前我们很多命令如webpack,gulp命令只有在全局安装(npm install xxx -g)才可以在命令行中直接运行或在项目中安装,通过script定义执行,但在npm5.2以后,我们可以只项目中安装,然后通过新增的npx执行。比如上面scripts中定义的lib打包(“lib”: “gulp”),我们可以直接在命令行中用:npx gulp命令行切换npm registry有可能你和我一样,在到处都是墙的世界,需要在npm,cnpm,公司的npm registry三者之间来回切换,每次都需要这样:npm set registry ‘https://registry.npm.taobao.org/‘麻烦有没有? 幸好,这世界有很多牛逼的人,nrm registry是个很好用的工具,下面这样:// 安装npm install -g nrm// 设置入口npm,cnpm,companynrm add npm ‘http://registry.npmjs.org’nrm add cnpm ‘https://registry.npm.taobao.org’nrm add vnpm ‘http://npm.company.com’// 切换入口到淘宝入口nrm use cnpm后续一个春节自己断断续续就在倒腾这个,收获还是挺大的。后面自己会慢慢去学习怎么加入demo‘,加入单元测试,去建造一个完整的npm库。源码库:githubnpm仓库地址:npm ...

February 22, 2019 · 2 min · jiezi

create-react-app同时对多个框架(antd+antd-mobile)做按需加载的方法

在React项目开发中,经常需要引用一些实用的第三方框架。在使用一些比较庞大的第三方框架时,框架内的各种资源文件数量巨大,这时,如果我们在每次使用框架时,都将框架内所有资源都全部加载的话,这将使得页面的性能大大降低。这时,我们就需要对这些庞大的第三方框架做按需加载了。首先介绍下对单个框架做按需加载的方法其实在使用create-react-app脚手架的情况下,对单个框架做按需加载的方法,网上的相关文章已经很多了,我这里只简单的介绍下。常用的方法就是通过babel-plugin-import来实现按需加载,并通过react-app-rewired来重写项目配置文件,将babel-plugin-import写入配置。1、安装cnpm install babel-plugin-import –devcnpm install react-app-rewired –dev2、修改package.json"scripts": {- “start”: “react-scripts start”,+ “start”: “react-app-rewired start”,- “build”: “react-scripts build”,+ “build”: “react-app-rewired build”,- “test”: “react-scripts test”,+ “test”: “react-app-rewired test”,}3、在项目的根目录下创建一个 config-overrides.js 用于修改默认配置const {injectBabelPlugin} = require(‘react-app-rewired’);const rewireLess = require(‘react-app-rewire-less’);const path = require(‘path’)module.exports = function override(config, env) { config = injectBabelPlugin( [‘import’, { libraryName: ‘antd’, libraryDirectory: ’es’, style: true } ], config ); config = rewireLess.withLoaderOptions({ modifyVars: {"@primary-color": “#4197FC”}, javascriptEnabled: true, })(config, env); return config;};这样就完成了对antd的按需加载那么对多个框架做按需加载应该怎么做呢?对多个框架做按需加载的方法这里拿antd和antd-mobile两个框架来举例子首先还是要按照上面的步骤安装babel-plugin-import和react-app-rewired,并修改默认配置,区别只是在最后一步上。在调用babel-plugin-import的injectBabelPlugin方法时,需要调用两次,并注明相对应的框架名。具体代码如下:const {injectBabelPlugin} = require(‘react-app-rewired’);const rewireLess = require(‘react-app-rewire-less’);const path = require(‘path’)module.exports = function override(config, env) { config = injectBabelPlugin( [‘import’, { libraryName: ‘antd’, libraryDirectory: ’es’, style: true }, ‘ant’ ], config ); config = injectBabelPlugin( [‘import’, { libraryName: “antd-mobile”, libraryDirectory: ’lib’, style: true }, ‘ant-mobile’ ], config ); config = rewireLess.withLoaderOptions({ modifyVars: {"@primary-color": “#4197FC”}, javascriptEnabled: true, })(config, env); return config;}; ...

February 15, 2019 · 1 min · jiezi

物联网-智慧园区实时监控部分总结

本人参与的一个智慧园区的项目,网络地图开发出来的后期效果如下图所示:初次拿到设计图时还没有左上角的全局搜索框,第一步首先是绘制出浮在上层的四张卡片,我是用遍历的方式依次渲染的: {showCards.map((item, index) => { let name = cardInfoName[index]; return ( <CardInfo title={item} id={index} key={index} isLoad={isLoad} isLoadAlarmList={this.isLoadAlarmList} cardInfo={cardInfo[name]} cardLoading={cardLoading[index]} getSearchKey={this.getSearchKey} appid={appId} /> ); })}第三张卡片涉及到滚动加载,和后期增加的点击搜索功能,所以增加了getSearchKey的方法,其中滚动加载使用的是react-infinite-scroller,结合antd的TimeLine: <div className=“timeline”> {val.length > 0 ? ( <InfiniteScroll initialLoad={false} pageStart={0} loadMore={this.handleInfiniteOnLoad} useWindow={false} hasMore={this.state.hasMore} > <Timeline> {val.map((item, index) => { return ( <Timeline.Item key={index}> <div onClick={debounce(() => this.goSearch(item.devEUI), 500)}> <span>{this.formateDate(item.createTime)}</span> <span className=“alarmType”> <i className=“iconfont icon-yichang” /> {item.alarmTypeLabel} </span> <p>{item.tmnName}</p> </div> </Timeline.Item> ); })} </Timeline> </InfiniteScroll> ) : ( <div className=“text-center”> <i className=“iconfont icon-zanwushuju image-icon” /> <p>暂无数据</p> </div> )} </div>之后,开始高德地图上信息的展示这里涉及到一个百度地图经纬度和gps转高德的一个算法,使用的是coordtransform:transferLngLats = (bd_lng: number, bd_lat: number, type = ‘gcj-02’) => { if (‘WGS84’ === type.toUpperCase()) { return coordtransform.wgs84togcj02(bd_lng, bd_lat); } else if (‘BD-09’ === type.toUpperCase()) { return coordtransform.bd09togcj02(bd_lng, bd_lat); } return [bd_lng, bd_lat]; };高德地图上的终端展示方式一共分为3种:单个终端展示分为在线(正常,告警)、离线,通过不同的图标来区分,点击图标获取对应的终端信息。①.获取所有终端所在的位置经纬度,状态,坐标类型等信息,通过坐标类型,把其他类型的经纬度与转换成高德地图类型②.遍历每一个终端经纬度,通过高德地图的new AMap.LngLat方法将经纬度结合成一个point,并且通过终端状态,区分将展示的图标的颜色、同时增加type属性③.通过new AMap.Icon,把选择好的图标配置好相关尺寸、偏移量等,再使用new AMap.Marker(使用extData属性增加status类型,用来存放type)和之前生成的point,将图标用map.add放置在地图上④.增加marker的click事件,点击的时候首先展示loading弹窗,接着用之前生成的point的经纬度和获取到的所有终端位置比对,相等的时候获取到终端Id⑤.根据上述Id调接口获取此终端的详细信息,将信息传递给TerInfo组件(提前声明this.terInfoRef = React.createRef()和this.terInfoWindow,并且给TerInfo组件加上ref={this.terInfoRef}属性),把此组件放到信息窗中,并且代替之前的loading弹窗,展示终端的详细信息把此组件放到信息窗中:this.terInfoWindow.setContent(ReactDOM.findDOMNode(this.terInfoRef.current));其中声明terInfoWindow: this.terInfoWindow = new AMap.InfoWindow({ isCustom: true, closeWhenClickMap: true, offset: new AMap.Pixel(130, 248), //left top });2.两个以上的终端,位置相邻时候自动聚合的展示聚合的图标中含有总聚合终端的总数,分为蓝色(正常,离线),橙色(有告警),点击展示当前聚合终端的离线数量和告警数量。声明聚合信息展示弹窗: this.clustererWindow = new AMap.InfoWindow({ isCustom: true, closeWhenClickMap: true, offset: new AMap.Pixel(136, 65), });在单个终端展示的时候,增加了type类型, 【③.通过new AMap.Icon,把选择好的图标配置好相关尺寸、偏移量等,再使用new AMap.Marker(使用extData属性增加status类型)和之前生成的point,将图标用map.add放置在地图上】,之后通过 this.markerClusterer.addMarker(marker),将marker放到聚合中。然后根据之前增加的status,通过mark.getExtData().status来获取终端type类型,统计告警终端数和离线终端数(一个终端可以同时属于告警状态和离线状态) AMap.plugin([‘AMap.MarkerClusterer’], () => { this.markerClusterer = new AMap.MarkerClusterer(this.map, [], { gridSize: 20, minClusterSize: 2, zoomOnClick: false, renderCluserMarker: obj => { const type = !!obj.markers.filter( mark => mark.getExtData().status === 2 || mark.getExtData().status === 3 ).length; const className = clusterer ${type ? 'alarmBg' : 'normalBg'}; obj.marker.setContent(&lt;div class="${className}"&gt;${obj.count}&lt;/div&gt;); }, }); this.markerClusterer.on(‘click’, obj => { const totalNum = obj.markers.length; let alarmNum = 0; let offlineNum = 0; obj.markers.forEach(item => { const status = item.getExtData().status; if (status === 3) { alarmNum++; offlineNum++; } if (status === 2) { alarmNum++; } if (status === 0) { offlineNum++; } }); this.clustererWindow.setContent(&lt;div class="mapInfo clustererInfo"&gt; &lt;div class="info_title"&gt;汇总情况&lt;/div&gt; &lt;div class="li"&gt;告警终端 : ${alarmNum}&lt;/div&gt; &lt;div class="li"&gt;离线终端 : ${offlineNum}&lt;/div&gt; &lt;/div&gt;); this.clustererWindow.open(this.map, obj.lnglat); }); });3.绑定区域的终端展示绑定区域的终端,在网络地图上展示该区域的终端汇总信息,如果要查看终端的具体信息,需要点击进入当前地图。①.获取所有区域的位置信息,并且转换经纬度为高德的②.根据获取到的园区中心点,将此区域的终端总数展示在marker里,放置在中心点上;marker的点击展示弹窗方式与单个终端一致。③.①中获取到的位置包含区域的各个点经纬度坐标,转化坐标成高德地图上的点,使用new AMap.Polygon方法绘制出该区域在地图上的位置。高德地图上展示的信息主要是这些,其中的状态判断等这里就不一一详述了,接下来开始本地地图部分:![图片上传中…] ...

February 13, 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

Ant Design源码分析(二):Button组件

年底正式总结的好时机, Button组件的源码。Button分析通过官方API文章,大家知道<Button /> 组件具备以下几个功能点1、多种样式风格可选: primary、ghost、 danger等,并且每一种风格都对应各自风格的交互2、接收click事件回调函数3、可以指定点击跳转指定的url4、可以控制图标旋转,模拟请求状态pending源码如下import * as React from ‘react’;import { findDOMNode } from ‘react-dom’;import * as PropTypes from ‘prop-types’;import classNames from ‘classnames’;/* 引入了一个系的模块Wave,可能是功能函数,可能是组件,先不管它是什么,用到时再回来看 /import Wave from ‘../_util/wave’;import Icon from ‘../icon’;import Group from ‘./button-group’;// 组件逻辑的一些辅助常量 /const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;/ 判断是否为两个中文字符*/const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);function isString(str: any) { return typeof str === ‘string’;}// 组件逻辑函数: 在两个中文字符间插入一个空格function insertSpace(child: React.ReactChild, needInserted: boolean) { // Check the child if is undefined or null. if (child == null) { return; } const SPACE = needInserted ? ’ ’ : ‘’; // strictNullChecks oops. if (typeof child !== ‘string’ && typeof child !== ’number’ && isString(child.type) && isTwoCNChar(child.props.children)) { return React.cloneElement(child, {}, child.props.children.split(’’).join(SPACE)); } if (typeof child === ‘string’) { if (isTwoCNChar(child)) { child = child.split(’’).join(SPACE); } return <span>{child}</span>; } return child;}/* 联合类型 Button.props中 type、shape、size、htmlType的取值范围 /export type ButtonType = ‘default’ | ‘primary’ | ‘ghost’ | ‘dashed’ | ‘danger’;export type ButtonShape = ‘circle’ | ‘circle-outline’;export type ButtonSize = ‘small’ | ‘default’ | ’large’;export type ButtonHTMLType = ‘submit’ | ‘button’ | ‘reset’;/ 定义接口 相当于props-types /export interface BaseButtonProps { type?: ButtonType; icon?: string; shape?: ButtonShape; size?: ButtonSize; loading?: boolean | { delay?: number }; prefixCls?: string; className?: string; ghost?: boolean; block?: boolean; children?: React.ReactNode;}export type AnchorButtonProps = { href: string; target?: string; onClick?: React.MouseEventHandler<HTMLAnchorElement>;} & BaseButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;export type NativeButtonProps = { htmlType?: ButtonHTMLType; onClick?: React.MouseEventHandler<HTMLButtonElement>;} & BaseButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>;export type ButtonProps = AnchorButtonProps | NativeButtonProps;export default class Button extends React.Component<ButtonProps, any> { static Group: typeof Group; static __ANT_BUTTON = true; static defaultProps = { prefixCls: ‘ant-btn’, loading: false, ghost: false, block: false, }; static propTypes = { type: PropTypes.string, shape: PropTypes.oneOf([‘circle’, ‘circle-outline’]), size: PropTypes.oneOf([’large’, ‘default’, ‘small’]), htmlType: PropTypes.oneOf([‘submit’, ‘button’, ‘reset’]), onClick: PropTypes.func, loading: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), className: PropTypes.string, icon: PropTypes.string, block: PropTypes.bool, }; private delayTimeout: number; constructor(props: ButtonProps) { super(props); this.state = { /* 控制Button中Icon来旋转,通常应用在异步请返回之前的场景中,比如提交表单,请求结束前让Icon旋转,可以使得体验更好,用来实现文章开头时所描述的功能4 / loading: props.loading, /* 作为子元素中是否有两个中文字符的标识符, 以此作为是否插入空格的标识符*/ hasTwoCNChar: false, }; } componentDidMount() { this.fixTwoCNChar(); } componentWillReceiveProps(nextProps: ButtonProps) { const currentLoading = this.props.loading; const loading = nextProps.loading; if (currentLoading) { clearTimeout(this.delayTimeout); } if (typeof loading !== ‘boolean’ && loading && loading.delay) { this.delayTimeout = window.setTimeout(() => this.setState({ loading }), loading.delay); } else { this.setState({ loading }); } } componentDidUpdate() { this.fixTwoCNChar(); } componentWillUnmount() { if (this.delayTimeout) { clearTimeout(this.delayTimeout); } } fixTwoCNChar() { // Fix for HOC usage like <FormatMessage /> const node = (findDOMNode(this) as HTMLElement); const buttonText = node.textContent || node.innerText; if (this.isNeedInserted() && isTwoCNChar(buttonText)) { if (!this.state.hasTwoCNChar) { this.setState({ hasTwoCNChar: true, }); } } else if (this.state.hasTwoCNChar) { this.setState({ hasTwoCNChar: false, }); } } handleClick: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement> = e => { const { onClick } = this.props; if (onClick) { (onClick as React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>)(e); } } isNeedInserted() { const { icon, children } = this.props; return React.Children.count(children) === 1 && !icon; } render() { /** 通过Props来生成不同的className而达到不同的效果,有兴趣的可以研究下样式 / const { type, shape, size, className, children, icon, prefixCls, ghost, loading: _loadingProp, block, …rest } = this.props; const { loading, hasTwoCNChar } = this.state; / 通过size控制按钮的大小尺寸*/ let sizeCls = ‘’; switch (size) { case ’large’: sizeCls = ’lg’; break; case ‘small’: sizeCls = ‘sm’; default: break; } /* Antd 圣诞彩蛋事件代码,每年12.25搞一次, UI变换思路依旧是通过props.someKey 配合不同的className来实现 / / 修复方式 https://github.com/ant-design/ant-design/issues/13848*/ const now = new Date(); const isChristmas = now.getMonth() === 11 && now.getDate() === 25; /** 通过Props来生成不同的className而达到不同的UI效果吗,有兴趣的可以研究下样式 / const classes = classNames(prefixCls, className, { [${prefixCls}-${type}]: type, [${prefixCls}-${shape}]: shape, [${prefixCls}-${sizeCls}]: sizeCls, [${prefixCls}-icon-only]: !children && icon, [${prefixCls}-loading]: loading, [${prefixCls}-background-ghost]: ghost, [${prefixCls}-two-chinese-chars]: hasTwoCNChar, [${prefixCls}-block]: block, christmas: isChristmas, }); const iconType = loading ? ’loading’ : icon; const iconNode = iconType ? <Icon type={iconType} /> : null; const kids = (children || children === 0) ? React.Children.map(children, child => insertSpace(child, this.isNeedInserted())) : null; const title= isChristmas ? ‘Ho Ho Ho!’ : rest.title; / 可以指定按钮跳转地址,实现功能3 / if (‘href’ in rest) { return ( <a {…rest} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </a> ); } else { // 这里的注释的意思是React不推荐在DOM element上使用 ‘htmlType’ 这个属性,因此在前面的IProps接口中,没有定义htmlType,但仍可以使用 // 在ES6与React,通常使用(剩余参数)这种方式可以扩展组件的IProps接口 // React does not recognize the htmlType prop on a DOM element. Here we pick it out of rest. const { htmlType, …otherProps } = rest; /* * 这里出现了开头的的外部依赖Wave,看到这种写法,对React组件设计比较熟悉的应该能猜到这个Wave是做什么的了,没错Wave是容器组件 * React推崇的是组件化开发,经过这些年的发展与沉淀,有两个关键词越来越活跃:compose与recompose,看过设计模式的知道,良好的软件设计因该是组合优于继承的,这两个关键词也是这个思路 * 为了提高组件的可复用性,社区同时提出了几种React组件的设计思路HOC、Render Callback、 容器组件与展示组件等 * 组件拆分的目的是为了复用,复用什么呢? 通常是是UI逻辑 * 这里我们先不去关注关注这个Wave是做什么的,我们只需要知道此时,返回一个<button><button>即可, 我们在下一篇文章中去看下这个Wave组件,这里我 * 这里我们只需要知道返回了一个button DOM元素,可以接收className、click事件、可以指定点击时跳转到指定url, * / return ( <Wave> <button {…otherProps} type={htmlType || ‘button’} className={classes} onClick={this.handleClick} title={title} > {iconNode}{kids} </button> </Wave> ); } }}分析过&lt;Button /&gt;组件,再结合之前的&lt;Icon /&gt;组件,我们其实可以发现一些Antd的一点设计模式(经过两年的React项目踩坑,回过头来看时,发现React社区中存在着大量的设计模式),将之成为Control CSS with Props,后面简称为CCP。在以前JQuery + CSS 横扫各大浏览器的时候,大家写CSS时已经注意到了复用的便利性,下面的代码,前端开发人员肯定写过,我们来看下面这段css代码// 抽取出一个组件的样式.btn { display: inline-block; font-weight: @btn-font-weight; text-align: center; touch-action: manipulation; cursor: pointer; background-image: none; border: @border-width-base @border-style-base transparent; white-space: nowrap; .button-size(@btn-height-base; @btn-padding-base; @font-size-base; @btn-border-radius-base); user-select: none; transition: all .3s @ease-in-out; position: relative; box-shadow: 0 2px 0 rgba(0, 0, 0, .015);}// 在此基础上变形,与扩展.btn-danger{ color: red;}.btn-primary{ background: blue;}相信上面这段代码对前端开人员来说,如果放到html + css中,如喝水吃饭一样习以为常, 不过是之前的模式在React中经过了变化,此模式将在后面的代码中大量出现,所以与大家约定这种CCP的名字css已经有了,怎么跟Html匹配上呢,看下JSX中的写法class SimpleBtn extends React.component { render(){ const {type} = this.this.props; const cls = ‘btn’; /* 根据props.type 来生成不同DOM节点 */ const btnCls = classNames({ [${cls}-danger]: type === ‘danger’, [${cls}-primary]: type === ’type’, }, cls); return ( <button className={btnCls}> {this.props.children} </button> ) }}调用方式如下improt {SimpleBtn} from ‘smpePath’;class Clent extends React.Component{ render(){ return ( <div> // 显示一个红色的按钮 <SimpleBtn type=“danger”></SimpleBtn> // 显示一个蓝色按钮 <SimpleBtn type=“primary”></SimpleBtn> </div> ) }}相信看到这里,大家对这种设计模式已经了然于心了。在后面的组件中会大量出现这种组件设计模式。本篇完 ...

January 29, 2019 · 4 min · jiezi

ant design pro 解读之Basiclayout.js

/* 基础页面布局,包含了头部导航,侧边栏和通知栏*//* Suspense 处理异步配合lazy使用方法如下 import React, {lazy, Suspense} from ‘react’; const OtherComponent = lazy(() => import(’./OtherComponent’)); function MyComponent() { return ( <Suspense fallback={<div>Loading…</div>}> <OtherComponent /> </Suspense> ); }/import React, { Suspense } from ‘react’;import { Layout } from ‘antd’;/ react-document-title 根据不同的路由改变文档的title*/import DocumentTitle from ‘react-document-title’;import isEqual from ’lodash/isEqual’;/* memoize-one 这个库的每个实例都缓存了一个结果 记忆化库 memoizeOne(resultFn, isEqual) 接收一个结果函数和一个对比函数,对比函数为空则默认使用===来进行入参的比较。/import memoizeOne from ‘memoize-one’;import { connect } from ‘dva’;/ react-container-query https://www.npmjs.com/package/react-container-query 响应组件 参数 query 响应式的断点位置 props.children 需要是一个返回组件的函数 <ContainerQuery query={query}> {params => ( <Context.Provider value={this.getContext()}> <div className={classNames(params)}>{layout}</div> </Context.Provider> )} </ContainerQuery>/import { ContainerQuery } from ‘react-container-query’;/ https://github.com/JedWatson/classnames classNames(‘foo’, ‘bar’); // => ‘foo bar’ classNames(‘foo’, { bar: true }); // => ‘foo bar’ classNames({ ‘foo-bar’: true }); // => ‘foo-bar’ classNames({ ‘foo-bar’: false }); // => ’’ classNames({ foo: true }, { bar: true }); // => ‘foo bar’ classNames({ foo: true, bar: true }); // => ‘foo bar’ // lots of arguments of various types classNames(‘foo’, { bar: true, duck: false }, ‘baz’, { quux: true }); // => ‘foo bar baz quux’ // other falsy values are just ignored classNames(null, false, ‘bar’, undefined, 0, 1, { baz: null }, ‘’); // => ‘bar 1’/import classNames from ‘classnames’;/ 在路径字符串中使用正则*/import pathToRegexp from ‘path-to-regexp’;/* 添加响应式,根据屏幕大小返回不同组件*/import Media from ‘react-media’;/* 基于 umi-plugin-locale 和 react-intl 实现,用于解决 i18n 问题 https://umijs.org/zh/plugin/umi-plugin-react.html#locale https://github.com/umijs/umi/tree/master/packages/umi-plugin-locale*/import { formatMessage } from ‘umi/locale’;/* 权限组件*/import Authorized from ‘@/utils/Authorized’;import logo from ‘../assets/logo.svg’;import Footer from ‘./Footer’;import Header from ‘./Header’;import Context from ‘./MenuContext’;import Exception403 from ‘../pages/Exception/403’;import PageLoading from ‘@/components/PageLoading’;import SiderMenu from ‘@/components/SiderMenu’;import styles from ‘./BasicLayout.less’;// lazy load SettingDrawerconst SettingDrawer = React.lazy(() => import(’@/components/SettingDrawer’));const { Content } = Layout;// 添加 react-container-query 参数,和antd 断点尺寸做搭配const query = { ‘screen-xs’: { maxWidth: 575, }, ‘screen-sm’: { minWidth: 576, maxWidth: 767, }, ‘screen-md’: { minWidth: 768, maxWidth: 991, }, ‘screen-lg’: { minWidth: 992, maxWidth: 1199, }, ‘screen-xl’: { minWidth: 1200, maxWidth: 1599, }, ‘screen-xxl’: { minWidth: 1600, },};class BasicLayout extends React.PureComponent { constructor(props) { super(props); this.getPageTitle = memoizeOne(this.getPageTitle); this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual); } componentDidMount() { const { dispatch, route: { routes, authority }, } = this.props; dispatch({ type: ‘user/fetchCurrent’, }); dispatch({ type: ‘setting/getSetting’, }); dispatch({ type: ‘menu/getMenuData’, payload: { routes, authority }, }); } componentDidUpdate(preProps) { // After changing to phone mode, // if collapsed is true, you need to click twice to display const { collapsed, isMobile } = this.props; if (isMobile && !preProps.isMobile && !collapsed) { this.handleMenuCollapse(false); } } getContext() { const { location, breadcrumbNameMap } = this.props; return { location, breadcrumbNameMap, }; } matchParamsPath = (pathname, breadcrumbNameMap) => { const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname)); return breadcrumbNameMap[pathKey]; }; // 根据当前path 的到路由权限的方法 getRouterAuthority = (pathname, routeData) => { let routeAuthority = [’noAuthority’]; const getAuthority = (key, routes) => { routes.map(route => { // pathToRegexp 将当前路由path转成正则表达式 if (route.path && pathToRegexp(route.path).test(key)) { routeAuthority = route.authority; } else if (route.routes) { // 如果含有子路由递归 routeAuthority = getAuthority(key, route.routes); } return route; }); return routeAuthority; }; return getAuthority(pathname, routeData); }; getPageTitle = (pathname, breadcrumbNameMap) => { const currRouterData = this.matchParamsPath(pathname, breadcrumbNameMap); if (!currRouterData) { return ‘Ant Design Pro’; } const pageName = formatMessage({ id: currRouterData.locale || currRouterData.name, defaultMessage: currRouterData.name, }); return ${pageName} - Ant Design Pro; }; getLayoutStyle = () => { const { fixSiderbar, isMobile, collapsed, layout } = this.props; if (fixSiderbar && layout !== ’topmenu’ && !isMobile) { return { paddingLeft: collapsed ? ‘80px’ : ‘256px’, }; } return null; }; handleMenuCollapse = collapsed => { const { dispatch } = this.props; dispatch({ type: ‘global/changeLayoutCollapsed’, payload: collapsed, }); }; renderSettingDrawer = () => { // Do not render SettingDrawer in production // unless it is deployed in preview.pro.ant.design as demo if (process.env.NODE_ENV === ‘production’ && APP_TYPE !== ‘site’) { return null; } return <SettingDrawer />; }; render() { const { navTheme, layout: PropsLayout, children, location: { pathname }, isMobile, menuData, breadcrumbNameMap, // this.props 中包含 route? 这里 routes中包含所有的当前route下的子路由 route: { routes }, fixedHeader, } = this.props; const isTop = PropsLayout === ’topmenu’; // 路由配置的权限 const routerConfig = this.getRouterAuthority(pathname, routes); const contentStyle = !fixedHeader ? { paddingTop: 0 } : {}; const layout = ( <Layout> {isTop && !isMobile ? null : ( <SiderMenu logo={logo} theme={navTheme} onCollapse={this.handleMenuCollapse} menuData={menuData} isMobile={isMobile} {…this.props} /> )} <Layout style={{ …this.getLayoutStyle(), minHeight: ‘100vh’, }} > <Header menuData={menuData} handleMenuCollapse={this.handleMenuCollapse} logo={logo} isMobile={isMobile} {…this.props} /> <Content className={styles.content} style={contentStyle}> <Authorized authority={routerConfig} noMatch={<Exception403 />}> {children} </Authorized> </Content> <Footer /> </Layout> </Layout> ); return ( <React.Fragment> <DocumentTitle title={this.getPageTitle(pathname, breadcrumbNameMap)}> {/* 全局响应式断点 在布局最外层添加class 方便给不同的元素添加响应式样式 /} <ContainerQuery query={query}> {params => ( <Context.Provider value={this.getContext()}> <div className={classNames(params)}>{layout}</div> </Context.Provider> )} </ContainerQuery> </DocumentTitle> <Suspense fallback={<PageLoading />}>{this.renderSettingDrawer()}</Suspense> </React.Fragment> ); }}/ umi model 分两类,一是全局 model,二是页面 model。全局 model 存于 /src/models/ 目录,所有页面都可引用;页面 model 不能被其他页面所引用。/export default connect(({ global, setting, menu }) => ({ collapsed: global.collapsed, layout: setting.layout, menuData: menu.menuData, breadcrumbNameMap: menu.breadcrumbNameMap, …setting,}))(props => ( / < 599 */ <Media query="(max-width: 599px)"> {isMobile => <BasicLayout {…props} isMobile={isMobile} />} </Media>)); ...

January 24, 2019 · 4 min · jiezi

create-react-app 2.0中使用antd(eject)

早些时候CRA(create-react-app)升级到2.0.3的时候, react-app-rewired没有跟着升级, 导致项目无法启动, 于是乎直接eject 开始改造项目.查看版本> create-react-app –version2.0.3创建项目create-react-app my-projectcd my-projectyarn eject # 输入 y目前为止项目目录结构, 忽略node_modules这个黑洞├── README.md├── config│ ├── env.js│ ├── jest│ │ ├── cssTransform.js│ │ └── fileTransform.js│ ├── paths.js│ ├── webpack.config.js│ └── webpackDevServer.config.js├── package.json├── public│ ├── favicon.ico│ ├── index.html│ └── manifest.json├── scripts│ ├── build.js│ ├── start.js│ └── test.js├── src│ ├── App.css│ ├── App.js│ ├── App.test.js│ ├── index.css│ ├── index.js│ ├── logo.svg│ └── serviceWorker.js└── yarn.lock安装依赖yarn add antdyarn add babel-plugin-import less less-loader @babel/plugin-proposal-decorators -DCRA eject之后package.json里面没有区分devDependencies 和 dependencies, 但是不影响使用因为antd是使用的less, CRA默认不支持, 所以需要改下默认的webpack配置, config/webpack.config.js首先修改babel配置个人习惯使用babelrc, 所以把babel-loader options中babelrc的值改为true, 增加.babelrc文件{ “presets”: [ “react-app” ], “plugins”: [ [ “import”, { “libraryName”: “antd”, “libraryDirectory”: “lib”, “style”: true }, “ant” ], [ “@babel/plugin-proposal-decorators”, // 启用装饰器 { “legacy”: true } ] ]}参照默认的sass配置, 增加less配置const sassRegex = /.(scss|sass)$/;const sassModuleRegex = /.module.(scss|sass)$/;const lessRegex = /.less$/;const lessModuleRegex = /.module.less$/;在module>rules中添加规则// sass rule//… { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap }, ’less-loader’, { javascriptEnabled: true } ), sideEffects: true }, { test: lessModuleRegex, use: getStyleLoaders( { importLoaders: 2, sourceMap: isEnvProduction && shouldUseSourceMap, modules: true, getLocalIdent: getCSSModuleLocalIdent }, ’less-loader’, { javascriptEnabled: true } ) }// file loader至此基本项目虽然已经基本完成, 但是如果你是使用less版本比较高, 项目是无法运行的参考issue需要改造getStyleLoaders函数, 增加第三个参数otherConfig, 就是上面代码中的 javascriptEnabled: trueconst getStyleLoaders = (cssOptions, preProcessor, otherConfig) => { const loaders = [ isEnvDevelopment && require.resolve(‘style-loader’), isEnvProduction && { loader: MiniCssExtractPlugin.loader, options: Object.assign({}, shouldUseRelativeAssetPaths ? { publicPath: ‘../../’ } : undefined) }, { loader: require.resolve(‘css-loader’), options: cssOptions }, { loader: require.resolve(‘postcss-loader’), options: { ident: ‘postcss’, plugins: () => [ require(‘postcss-flexbugs-fixes’), require(‘postcss-preset-env’)({ autoprefixer: { flexbox: ’no-2009’ }, stage: 3 }) ], sourceMap: isEnvProduction && shouldUseSourceMap } } ].filter(Boolean); if (preProcessor) { loaders.push({ loader: require.resolve(preProcessor), options: { sourceMap: isEnvProduction && shouldUseSourceMap, …otherConfig } }); } return loaders; };这样修改之后, 自定义主题modifyVars也可以写在otherConfig中, 一举两得, 不多赘述.至此项目???? ...

January 20, 2019 · 2 min · jiezi

react下实现一个PDF展示组件

简介:在react的antd-pro的框架下展示本地的PDF文件效果图:一、插件选取。听说过大名鼎鼎的PDF.js,但是因为是在react框架下,所以选取了两个可行的插件react-pdfreact-pdf-js两个插件都是对PDF进行的封装。两个插件都进行了尝试,相对而言react-pdf功能更强大并且文档也比较清晰,但是使用也会相对复杂一点。最后使用的是react-pdf-js这个插件。二、展示选择的文件。react-pdf-js第一步:展示一个本地文档。按照官方的文档:render() { let pagination = null; if (this.state.pages) { pagination = this.renderPagination(this.state.page, this.state.pages); } return ( <div> <PDF file=“test.pdf” onDocumentComplete={this.onDocumentComplete} page={this.state.page} /> {pagination} </div> )}注意:官方文档没有任何说明。此处的file是一个require过来的文件。例子:要加载一个’E:\1.pdf’,那么应该那么配置:const PDFTest = require(‘E:\1.pdf’);render() { let pagination = null; if (this.state.pages) { pagination = this.renderPagination(this.state.page, this.state.pages); } return ( <div> <PDF file={PDFTest} onDocumentComplete={this.onDocumentComplete} page={this.state.page} /> {pagination} </div> )}第二步:根据文件选择框更改文件。这一步被卡住过,刚开始想的是根据选择的文件然后获取文件的实际地址然后运用require去获取文件,但是实现的时候发现浏览器的安全策略无法让浏览器获取文件的真实路径。但是!我们可以通过创建一个URL对象去获取文件的一个blob。使用window.URL.createObjectURL创建一个file文件,并且react-pdf-js可以直接接受一个这样子的文件。部分代码如下:handleButtonOnChange = e =>{ if (e.currentTarget.files.length === 0) return; const url = window.URL.createObjectURL(e.currentTarget.files[0]); this.setState({ pdfTest: { key:url, file:url, }, })}createPDF = () =>{ const { pageNumber, numPages, pdfTest } = this.state; if(!pdfTest) return; return( <div> <div className={style.pdfContainer}> <PDF key={pdfTest.key} file={pdfTest.file} onDocumentComplete={this.onDocumentComplete} page={pageNumber} className={style.pdfView} width=‘300px’ /> </div> <p style={{float:‘right’}}>第 {pageNumber} 页 共 {numPages} 页</p> </div> )}render() { return ( <div id=‘PDFViewer’> <input id=‘id’ type=“file” style={{width:‘200px’,height:‘35px’}} accept=".pdf" onChange={this.handleButtonOnChange} /> {this.createPDF()} </div> );}此处还有一个坑,就是key这个值。在文档中没有提到这个值,并且在源代码中也没有怎么出现这个值。这个key值应该是标识每个文件的一个唯一标识,当key值不同的时候会重新渲染canvas。以下做法不推荐:在之前我没发现这个之前,通过修改源码的这个地方改为:componentWillReceiveProps(newProps) { const { page, scale, file:oldfile, onDocumentComplete, cMapUrl, cMapPacked, } = this.props; const { pdf } = this.state; const { file:newfile } = newProps; if(newfile !== oldfile){ PdfJsLib.GlobalWorkerOptions.workerSrc = ‘//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.0.943/pdf.worker.js’; PdfJsLib.getDocument({ url: newfile, cMapUrl, cMapPacked }).then((newPdf) => { this.setState({ pdf:newPdf }); if (onDocumentComplete) { onDocumentComplete(newPdf._pdfInfo.numPages); // eslint-disable-line } pdf.getPage(page).then(p => this.drawPDF(p)); }); }else{ if (newProps.page !== page) { pdf.getPage(newProps.page).then(p => this.drawPDF(p)); } if (newProps.scale !== scale) { pdf.getPage(newProps.page).then(p => this.drawPDF(p)); } }}手动实现了这个功能,但是这个判定方法存在一些问题,只有再加入一个变量去判断才会完善,就完全和他的key这个值一样,但是知道key值之后就没有再对源码进行修改了。第三笔:其他功能。翻页以及跳页:handleTurnPage = e =>{ const { pageNumber, numPages, turnPageNumber } = this.state; if(!numPages){ message.warning(‘请先选择PDF文件’); return; } let newPageNumber = pageNumber; switch (e.target.id) { case ‘pageUp’: newPageNumber -= 1; if(newPageNumber <= 0){ message.warning(‘已经是第一页’); return; } break; case ‘pageDown’: newPageNumber += 1; if(newPageNumber > numPages){ message.warning(‘已经是最后一页’); return; } break; case ’numberPage’: if(!turnPageNumber){ message.warning(‘请先输入数字’); return; }else if(turnPageNumber <= 0||turnPageNumber > numPages){ message.warning(‘请输入在页面范围内的数字’); return; } newPageNumber = turnPageNumber; break; default: break; } this.setState({ pageNumber:newPageNumber, })}render() { return ( <div id=‘PDFViewer’> <input id=‘id’ type=“file” style={{width:‘200px’,height:‘35px’}} accept=".pdf" onChange={this.handleButtonOnChange} /> <div style={{float:‘right’}}> <InputNumber onChange={this.onPageNumberInputChange} style={{width:‘150px’}} placeholder=‘输入需要跳转的页’ /> <Button onClick={this.handleTurnPage} id=“numberPage”>确认跳转</Button> <Button onClick={this.handleTurnPage} id=“pageUp”>上一页</Button> <Button onClick={this.handleTurnPage} id=“pageDown”>下一页</Button> </div> {this.createPDF()} </div> );}完整代码:GitHub。react-pdf这个插件的功能很强大,但是使用就相对而言比较复杂。官方的复杂demo由于最后使用的是另外的一个插件。这里就只写一下踩坑记录。1、file参数。在这里file这个参数和react-pdf-js的不一样,在require的时候都是一样的,但是在转换的时候不用创建URL对象,直接将input里面的file传过去即可。并且不需要key。例子:handleButtonOnChange = e =>{ if (e.currentTarget.files.length === 0) return; this.setState({ pdfTest: { file:e.currentTarget.files[0], }, })}2、不显示text layersPDF存在一个问题无法选择里面的文字以及链接,但是PDF.js通过在里面添加一层文本层用于辅助选取,但是在这个插件里面会存在一个重影,导致文字显示效果不佳,如图:在官方文档中提到使用SVG可以解决这个问题,但是SVG选择出来是乱码。所以在使用的时候希望屏蔽掉text layer,在文档中也有提到,在page中设置。以上是所有内容。 ...

January 10, 2019 · 2 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

wangEditor 与 阿里的[pro ant design]的组合搭配

网上已经有一篇类似的文章wangEditor富文本编辑器+react+antd的使用, 当然我也参考了他的写法, 然后实现了我到需求, 现在拿出来分享分享版本antd: v3.12.1wangEditor: v3.1.1我们用编辑器, 大部分情况是在表单中使用, 而antd的表单系统, 一般也离不了 form.getFieldDecorator(id, options),1 安装很简单 npm install wangeditor (注意,全是小写)2 和getFieldDecorator绑定<Form.Item> {form.getFieldDecorator(‘YourInputName’, { // …一些属性设置 })(<div ref={node => this.node = node} />)}</Form.Item>ref={node => this.node = node}这段代码,eslint会划红线,理由是没有返回值,本人能力有限,不知道怎么解, 还请大神指点指点,如果你没有eslint,那就不用管它3 组件部分// 引入wangeditorimport WangEditor from ‘wangeditor’;// …componentDidMount() { // 这个onChange事件, 是getFieldDecorator绑定的时候带入的, 他会取代被绑定的组件的onChange事件 // 一般和getFieldDecorator绑定过的,都用onChange来传值 // value也是getFieldDecorator绑定过来的, const { onChange, value } = this.props; const editor = new WangEditor(this.node); editor.customConfig.onchange = html => { // 传递html onChange(html); } editor.create(); // 设置初始内容 editor.txt.html(value);}好了,已经绑定好了,很简单吧,getFieldDecorator在使用的时候, 有许多注意的地方, 初学者可能会犯错,多看看官方的文档, 多然后自己摸索吧! ...

January 8, 2019 · 1 min · jiezi

axios请求、和返回数据拦截,统一请求报错提示_012

axios请求、和返回数据拦截,统一请求报错提示官方文档https://github.com/axios/axios 英文文档https://www.kancloud.cn/yunye… 中文文档请求和返回拦截,添加统一的报错信息请求的配置可以通过阅读官方文档来进行配置。axios api也很简介,多看看再自己尝试一下就会了下面是我写的一个在react中的应用,UI用的阿里的Antd 框架,所以报错信息直接用全局弹窗来提示了。比较简陋。写好之后,在写发送请求的文件中引用request 就可以了。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;https://github.com/axios/axios ...

January 2, 2019 · 1 min · jiezi

React+React-Route+Antd+Recharts+Excel(获取Excel数据,绘制Charts)

转眼间2018年就过去了,来到的2019新的一年,在这里,祝大家新年快乐。知乎个人博客GithubDemoRepo开发缘由:因为一个很重要的朋友需要绘制一些Charts,但是嫌弃手绘太慢,因此这次放假写了这个小东西当前进度:简单的Demo Charts展示,包括AreaChart, BarChart, ComposedChart, LineChart, PieChart测试文件:src/common/files/info.xlsx附上RechartsReact-Route先上两张照片吧1、版本2、创建项目因为公司使用的 react+antd+ts, 虽然antd前两天搞了个圣诞惊吓,但是毋庸置疑,这个组件库做的确实很好啊,我不怕被喷,辩证一分为二,不能因为别人犯一点的错误,就否认人家吧,废话不多说,还是讲本文的主题吧首先先安装create-react-appnpm i -g create-react-appcreate-react-app Charts –scripts-version=react-scripts-ts-antd然后安装react-route-dom和rechartsyarn add react-route-domornpm i react-route-dom –savenpm i recharts –save因为TS检查较为严格,所以,我对TS有一些我自己需要rules的配置tslint.js{ “extends”: [“tslint:recommended”, “tslint-react”, “tslint-config-prettier”], “linterOptions”: { “exclude”: [ “config//*.js”, “node_modules//.ts”, “coverage/lcov-report/.js” ] }, “rules”: { “no-string-throw”: true, “no-unused-expression”: false, “no-unused-variable”: false, “no-use-before-declare”: false, “no-duplicate-variable”: false, “curly”: true, “class-name”: true, “triple-equals”: [true, “allow-null-check”], “comment-format”: [false, “check-space”], “eofline”: true, “forin”: false, “indent”: [true, “spaces”, 2], “label-position”: true, “max-line-length”: [true, 150], “member-access”: false, “no-arg”: true, “no-bitwise”: false, “no-console”: [true, “debug”, “info”, “time”, “timeEnd”, “trace” ], “no-construct”: true, “no-debugger”: true, “no-empty”: false, “no-eval”: true, “no-inferrable-types”: true, “no-shadowed-variable”: false, “no-string-literal”: false, “no-switch-case-fall-through”: false, “no-trailing-whitespace”: true, “no-var-keyword”: false, “object-literal-sort-keys”: false, “one-line”: [true, “check-open-brace”, “check-catch”, “check-else” ], “radix”: false, “typedef-whitespace”: [true, { “call-signature”: “nospace”, “index-signature”: “nospace”, “parameter”: “nospace”, “property-declaration”: “nospace”, “variable-declaration”: “nospace” }], “variable-name”: [true, “ban-keywords”], “whitespace”: [true, “check-branch”, “check-decl”, “check-type”, “check-preblock” ], “ordered-imports”: false, “jsx-no-lambda”: false, “interface-name”: [true, “never-prefix”], “prefer-const”: false }}TS初试React-Route 4.x提供给我们使用的都是以组件形式存在的。我们使用的时候就像我们以前使用组件那样使用就行了,详见React-Route官方文档。菜单栏,我觉得日后可能还会增加其他的Charts,所以我将菜单通过配置文件来控制,增加复用性。SideMenu.tsximport React, { Component } from ‘react’;import { Link } from ‘react-router-dom’;import classnames from ‘classnames’;import moment from ‘moment’;import ‘./index.scss’;import { menus } from ‘./menus’;import { Layout, Menu, Icon } from ‘antd’;import Timer from ‘src/components/Timer/Timer’;const { Header, Footer, Sider, Content } = Layout;const { SubMenu } = Menu;interface SideMenuProps { children?: any;}export default class SideMenu extends Component<SideMenuProps, any> { public state = { collapsed: false, selectedKeys: [menus[0].key], }; public toggle = () => { this.setState({ collapsed: !this.state.collapsed }); }; render() { const { collapsed, selectedKeys, } = this.state return ( <Layout className=“side-menu”> <Sider trigger={null} collapsible={true} collapsed={collapsed}> <div className=“logo”> <img src={require(‘src/common/images/logo.png’)} /> <a href=“https://github.com/Rain120/charts" target="_blank”> <span className={classnames(“title”)}><Icon type=“github” /></span> </a> <a href=“https://www.zhihu.com/people/yan-yang-nian-hua-120/activities" target="_blank”> <span className={classnames(“title”)}><Icon type=“zhihu” /></span> </a> </div> <Menu theme=“dark” mode=“inline” defaultSelectedKeys={selectedKeys}> { menus && menus.map(menu => ( menu.children ? ( <SubMenu key={menu.key} title={<span><Icon type={menu.icon} /><span>{menu.text}</span></span>}> { menu.children.map(item => ( <Menu.Item key={item.key}> <Link to={item.path}>{item.text}</Link> </Menu.Item> )) } </SubMenu> ) : ( <Menu.Item key={menu.key}> <Link to={menu.path} style={{ overflow: ‘hidden’ }}><Icon type={menu.icon} />{menu.text}</Link> </Menu.Item> ) )) } </Menu> </Sider> <Layout className=“r-content”> <Header> <Icon className=“trigger” type={collapsed ? ‘menu-unfold’ : ‘menu-fold’} onClick={this.toggle} /> <Timer timerStyle=“timer” /> </Header> <Content style={{ margin: ‘1rem’, padding: ‘1rem’, background: ‘#fff’, minHeight: ‘25rem’, }}> {this.props.children} </Content> <Footer style={{ textAlign: ‘center’ }}> ©{moment().format(‘YYYY’)} Created by Rainy </Footer> </Layout> </Layout> ); }}Menu.ts/* * @Author: Rainy * @Github: https://github.com/Rain120 * @Date: 2018-12-30 15:43:12 * @LastEditTime: 2018-12-31 13:28:04 */export const menus = [ { key: ‘menu-0’, icon: ‘bar-chart’, text: ‘Charts Demo Show’, path: ‘/’, }, { key: ‘menu-1’, icon: ‘dashboard’, text: ‘ReCharts’, children: [ { key: ‘1’, text: ‘Charts Drawer’, path: ‘/charts/charts-drawer’ }, ] }] as any;获取Excel的数据是通过使用大佬的xlsx插件来实现的,详见XLSX官网。npm i xlsx -S因为这次项目没有后端,所以其实我们对Excel文件的解析是在upload之前完成的public beforeUpload = (file: any, fileList: any) => { var rABS = true; const f = fileList[0]; var reader = new FileReader(); reader.onload = (e: any) => { let data: any = e.target.result; if (!rABS) { data = new Uint8Array(data); } var workbook = XLSX.read(data, { type: rABS ? ‘binary’ : ‘array’ }); // more sheet workbook.SheetNames.map(item => { var worksheet = workbook.Sheets[item]; var jsonArr = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); this.handleImpotedJson(jsonArr); }) }; if (rABS) { reader.readAsBinaryString(f); } else { reader.readAsArrayBuffer(f); } return false;}upload configconst props = { accept: ‘application/vnd.openxmlformats-officedocument.spreadsheetml.sheet’, name: ‘file’, headers: { authorization: ‘authorization-text’, }, multiple: false, action: ‘’, beforeUpload: (file, fileList) => this.beforeUpload(file, fileList), onChange(info) { const status = info.file.status; if (status !== ‘uploading’) { console.log(info.file, info.fileList); } if (status === ‘done’) { message.success(${info.file.name} file uploaded successfully.); } else if (status === ’error’) { message.error(${info.file.name} file upload failed.); } }, };Charts组件因为使用的Charts比较多,所以使用recharts提供的组件ResponsiveContainer为了使得这些Charts不够缩放的影响。但是当前做的这些Charts大部分都是相同的结构,所以相同的部分应该抽离出来。因为其他的Charts都差不多,这里我只说一下LineChartsimport React, { Component } from ‘react’;import WrapperCharts from ‘./WrapperCharts’;import ‘./index.scss’;import { Line, Legend, Tooltip, XAxis, YAxis, CartesianGrid, LineChart, Label,} from “recharts”;export const COLOR_LISTS = [’#8884d8’, ‘#cf6868’, ‘#3fb549’, ‘#a6d41f’, ‘#8ad4d8’, ‘#cfdd68’, ‘#354449’, ‘#a75d1f’];interface LineChartsProps { data?: any; names?: any;}export default class LineCharts extends Component<LineChartsProps, any> { render() { const { data, names } = this.props; return ( <WrapperCharts class_name=“line-charts”> <LineChart data={data}> <CartesianGrid /> { names && <XAxis dataKey={names[0].dataKey} name={names[0].name} /> } <YAxis /> <Tooltip /> <Legend /> { names && names.slice(1).map((item, index) => ( <Line type=“monotone” dataKey={item.dataKey} key={index} name={item.name} stroke={COLOR_LISTS[index]} /> )) } <Label /> </LineChart> </WrapperCharts> ) }}github page deploynpm i -g gh-pagespackage.json配置"predeploy": “yarn run build”,“deploy”: “gh-pages -d build"部署yarn run deploy引入图片以上就是这两天做的小东西,写的和讲的都很潦草,请看管轻喷。 ...

January 1, 2019 · 4 min · jiezi

小白在react+dva+antd中踩的一些坑

记录下来,一来给自己提醒,二来帮助一些朋友们解决问题。大部分都是很傻的坑,但就是踩了,orz样式方面1)、使用antd的table 想要改变td的样式,比如想要显示不完时省略 下图中2是官方文档的写法,当我们想要改变td样式时可以自己写一个render,见12.逻辑方面用的是dva,先简要概括一些model里各个的用法: namespace:唯一 state:初始化数据 subscriptions:路由变化,拿页面最初始数据 监听 effects:请求数据 处理异步action reducers: 更新数据 1)、想向后端发送一个请求,得到返回值后再提交另一个请求,像promise.then一样。 错误思路: 一开始很傻地直接前后写了两个dispatch,结果错误,虽然分前后顺序调用了接口(dispatch是同步执行的),可是并没有根据第一个dispatch的结果动态调用第二个接口。 解决方法: 在model里的effects中相关的函数里调用第二个接口所以使用effects的put来触发action。首先用dispatch向后端发起第一个请求,接着在effects里addAddressable函数里调用第二个请求model.js:2)、如何在effects里获取state里维护的值如上图,payload里需要sate里的deviceName、description等根据前面的图应该知道应该用select可是网上大概有两种方法:const todos = yield select(state => state.todos);const {id} = yield select(=>.storeIf) storeIf 是model的namespace我使用第一种获取不到,原因还不知道。所以如果法一获取不到,不妨用法二const {deviceName,description,templateDetail} = yield select(=>.device);3)、如何在render里动态渲染不同的div?在render中使用if else会报错解决:使用三元运算符 a?b:ca是一个触发不同div的条件,b是一个div,c是另一个div4)、根据后端数据动态渲染input数量,并且把输入值存到state中接受到的数据肯定是一个对象,类似:{{ a:1},{ b:2}}用map遍历得到a,b。但接下来一个问题是动态存入数据。解决:把后端可能要显示的字段都先在model的state中设置初始值。我这里是可能显示port或者address.在state写:{ address:"", port:0}通过[] 可以动态存入port或者address暂时遇到的就是这样了,其实准确来说也不是坑,是我在学习这个上的比较费时间的一些东西。发出来就是希望大家百度问题时看到,尽快解决,少花点时间。

December 17, 2018 · 1 min · jiezi

Electron + Antd + Mobx 环境搭建

最近要重构一版桌面管理软件。业务需求与WEB版基本一致,但需要用到断点续传等本地功能。最经济的办法当然是移植到Electron环境中。之前的应用框架主要用到了以下React套餐:ReactReact-Router 路由Mobx 数据管理AntDesign 界面组件经过一天时间的摸索大致找到了门路,构建完成后写了一个脚手架,欢迎下载。下面回顾一下本次环境搭建的过程。安装AntDesign的脚手架关于Antd在CRA框架下的使用,官网有一份很详细的操作说明(包括babel-import等的设置),初学者可以跟着一步一步学习。如果你赶时间,也可以直接用它的脚手架。$ git clone https://github.com/ant-design/create-react-app-antd.git my-project$ cd my-project$ npm install && npm start跑起来以后,会自动弹出一个页面 localhost:3000,网页上显示的就是最基础的包括了antd的demo示例。安装Electron及配置关于electron的介绍,可以看官网文档,本文不涉及。完成antd脚手架安装后,在工程目录下安装electron。$ npm install electron –save-dev安装完成以后,在根目录下需要做一些设置,包括:electron启动脚本npm启动及编译命令render端使用electron模块的设置electron启动脚本在根目录下新建main.js,写入以下内容。(其实与官方例子是一样的)// Modules to control application life and create native browser windowconst {app, BrowserWindow} = require(’electron’)// Keep a global reference of the window object, if you don’t, the window will// be closed automatically when the JavaScript object is garbage collected.let mainWindowfunction createWindow () { // Create the browser window. mainWindow = new BrowserWindow({width: 800, height: 600}) // and load the index.html of the app. mainWindow.loadFile(‘build/index.html’) // Open the DevTools. // mainWindow.webContents.openDevTools() // Emitted when the window is closed. mainWindow.on(‘closed’, function () { // Dereference the window object, usually you would store windows // in an array if your app supports multi windows, this is the time // when you should delete the corresponding element. mainWindow = null })}// This method will be called when Electron has finished// initialization and is ready to create browser windows.// Some APIs can only be used after this event occurs.app.on(‘ready’, createWindow)// Quit when all windows are closed.app.on(‘window-all-closed’, function () { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== ‘darwin’) { app.quit() }})app.on(‘activate’, function () { // On macOS it’s common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (mainWindow === null) { createWindow() }})npm启动及编译命令在工程根目录的package.json中加入以下内容:{ … “main”: “main.js”, “homepage”: “.”, “scripts”: { “electron-dev”: “electron . –env dev”, “electron-build”: “npm run build && electron . –env build”, “start”: “react-app-rewired start”, … }}我们希望在执行npm run electron-dev时,能打开electron应用并展示antd的网页内容,那么需要修改main.js: … // and load the index.html of the app. // mainWindow.loadFile(‘index.html’) // 这个是原内容,修改为以下内容 if (argv && argv[1] === ‘dev’) { // 如果npm run electron-dev,加载本地网页 mainWindow.loadURL(‘http://localhost:3000/’) } else if (argv && argv[1] === ‘build’) { mainWindow.loadURL(url.format({ pathname: path.join(__dirname, ‘./build/index.html’), protocol: ‘file:’, slashes: true })) }…配置到这里后,打开两个终端,分别cd到项目根目录下。$ npm start // 终端1$ npm run electron-dev // 终端2electron界面应该就会展示出来,界面中显示的就是antd的demo内容。render端使用electron模块经过以上设置,界面内容已经可以正常展示并可以实时刷新,但还缺少了一个重要功能:electron模块,用以支持本地化操作。配置的基本思路是,在electron启动过程中在window对象中设置webPreferences的preload属性[文档],将electron模块注册为global对象供renderer侧使用。… // Create the browser window. // mainWindow = new BrowserWindow({ width: 800, height: 600 }) mainWindow = new BrowserWindow({ width: 800, height: 600, autoHideMenuBar: true, fullscreenable: false, webPreferences: { javascript: true, plugins: true, nodeIntegration: false, // 不集成 Nodejs webSecurity: false, preload: path.join(__dirname, ‘./public/renderer.js’) // public目录下的renderer.js } })…设置成功后,在renderer侧即可引入electron的remote模块。// 新建renderer.js,public目录下const process = require(‘process’);process.once(’loaded’, () => { global.electron = require(’electron’)});使用mobx安装mobx及mobx-react$ npm install mobx mobx-react –save安装成功后,在App.js中使用:…import { observer } from ‘mobx-react’;…@observerclass App extends React.Component { …}执行开发命令后,会发现出现了一个编译错误。原因就在于CRA团队并不倾向于支持所有未正式发布的javascript语言特性,目前要使用装饰圈,均需要使用babel等进行转译。这个问题目前有两个方法:使用eject方式或react-app-rewire-mobx模块。使用eject方式将会暴露出所有的webpack配置,开发者如果熟悉可以进行自定义的配置。当然,这样做的话create-react-app的意义就大打折扣了。使用react-app-rewire-mobx则相对方便了许多,只需安装该模块,并在config-overrides.js中加入,即可正常使用。…const rewireMobX = require(‘react-app-rewire-mobx’);module.exports = function override(config, env) { … config = rewireMobX(config, env); return config;}注意按照上面的方式配置以后,仍然会报错。./src/index.js Error: The ‘decorators’ plugin requires a’decoratorsBeforeExport’ option, whose value must be a boolean. If youare migrating from Babylon/Babel 6 or want to use the old decoratorsproposal, you should use the ‘decorators-legacy’ plugin instead of’decorators’.原因在于,新版的babel7.0的描述换了一种方式。应该将上面的配置改为:module.exports = function override(config, env) { … config = injectBabelPlugin(["@babel/plugin-proposal-decorators", { legacy: true }], config); return config;}其实在react-app-rewire-mobx中也仅仅就是就是导入这个修饰符,只不过是旧版的写法。但就因为没有跟随babel7.0更新,所以导致了这个// react-app-rewire-mobx/index.jsconst {injectBabelPlugin} = require(‘react-app-rewired’);function rewireMobX(config, env) { return injectBabelPlugin(’transform-decorators-legacy’, config);}module.exports = rewireMobX; ...

December 13, 2018 · 3 min · jiezi

项目文档说明:react + Ant Design 的 blog-react-admin

前言此 blog-react-admin 项目是基于 蚂蚁金服开源的 ant design pro 之上,用 react 全家桶 + Ant Design 的进行再次开发的,项目已经开源,项目地址在 github 上。效果预览 https://preview.pro.ant.design/user/login1. 后台管理1.1 已经实现功能[x] 登录[x] 文章管理(支持 MarkDown 语法)[x] 标签管理[x] 留言管理[x] 用户管理[x] 友情链接管理[x] 时间轴管理1.2 待实现功能[ ] 点赞、留言和评论 的通知管理[ ] 评论管理[ ] 个人中心(用来设置博主的各种信息)[ ] 工作台( 接入百度统计接口,查看网站浏览量和用户访问等数据 )2. 主要项目结构- pages - Account 博主个人中心 - article 文章管理 - Category 分类 - Dashboard 工作台 - Exection 403 404 500 等页面 - Link 链接管理 - Message 留言管理 - OtherUser 用户管理 - Tag 标签管理 - TimeAsix 时间轴 - User 登录注册管理文章管理、用户管理、留言等 具体业务需求,都是些常用的逻辑可以实现的,也很简单,这里就不展开讲了。3. 使用使用详情请查看 Ant Design Pro ,因为本项目也是在这个基础之上,按这个规范来构建的。4. 缺点开发时,程序出错后,修改正确后,webpack 有时不会及时查觉到内容已经更改,从而不能及时编译,要重新运行命令打包。5. 项目地址开源不易,如果觉得该项目不错或者对你有所帮助,欢迎到 github 上给个 star,谢谢。项目地址:前台展示: https://github.com/乐趣区/blog-react管理后台:https://github.com/乐趣区/blog-react-admin后端:https://github.com/乐趣区/blog-nodeblog:https://github.com/乐趣区/blog本博客系统的系列文章:react + node + express + ant + mongodb 的简洁兼时尚的博客网站react + Ant Design + 支持 markdown 的 blog-react 项目文档说明[基于 node + express + mongodb 的 blog-node 项目文档说明] 敬请期待…6. Build Setup ( 构建安装 )# install dependenciesnpm install # serve with hot reload at localhost: 3000npm start # build for production with minificationnpm run build 如果要看完整的效果,是要和后台项目 blog-node 一起运行才行的,不然接口请求会失败。7. 最后对 全栈开发 有兴趣的朋友可以扫下方二维码关注我的公众号,我会不定期更新有价值的内容。微信公众号:乐趣区分享 前端、后端开发等相关的技术文章,热点资源,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源分享–Python、Java、Linux、Go、node、vue、react、javaScript ...

November 23, 2018 · 1 min · jiezi

react + Ant Design + 支持 markdown 的 blog-react 项目的文档说明

前言此 blog 项目是基于 react 全家桶 + Ant Design 的,项目已经开源,项目地址在 github 上。1. 效果完整效果请看:http://乐趣区.cn/main.html2. 功能描述2.1 已经实现功能[x] 登录[x] 注册[x] 文章列表[x] 标签分类[x] 个人介绍[x] 点赞与评论[x] 留言[x] 时间轴[x] 发文(支持 MarkDown 语法)[x] 文章详情展示(支持代码语法高亮)2.2 待实现功能[ ] 文章归档[ ] 文章分类[ ] 文章详情的目录[ ] 移动端适配[ ] 升级 webpack 版本到 4.X3. 前端技术3.1 主要技术react: 16.5.2antd: 3.9.3react-router::4.3.1webpack: 3.8.1axios:0.18.0redux: 4.0.0highlight.js: 9.12.0marked:0.5.14. 项目搭建项目是按 antd 推荐的教程来搭建的:antd 在 create-react-app 中使用 , 实现了 按需加载组件代码和样式。5. 主要项目结构- components - article 文章详情 - articles 文章列表 - comments 评论 - loadEnd 加载完成 - loading 加载中 - login 登录 - message 留言 - nav 导航 - register 注册 - slider 右边栏(博主 logo 、链接和标签等) - timeLine 时间轴- router 路由- store redux 的状态管理- utils 封装的常用的方法- views 框架页面6. markdown 渲染markdown 渲染效果图: react 相关的支持 markdown 语法的有 react-markdown,但不支持表格的渲染,所以没用。markdown 渲染 采用了开源的 marked, 代码高亮用了 highlight.js 。用法:第一步:npm i marked highlight.js –savenpm i marked highlight.js –save第二步: 导入import marked from ‘marked’;import hljs from ‘highlight.js’;第三步: 设置componentWillMount() { // marked相关配置 marked.setOptions({ renderer: new marked.Renderer(), gfm: true, tables: true, breaks: true, pedantic: false, sanitize: true, smartLists: true, smartypants: false, highlight: function(code) { return hljs.highlightAuto(code).value; }, }); }第四步:<div className=“content”> <div id=“content” className=“article-detail” dangerouslySetInnerHTML={{ __html: this.state.articleDetail.content ? marked(this.state.articleDetail.content) : null, }} /> </div>第五步:引入 monokai_sublime 的 css 样式<link href=“http://cdn.bootcss.com/highlight.js/8.0/styles/monokai_sublime.min.css" rel=“stylesheet”>第六步:对 markdown 样式的补充如果不补充样式,是没有黑色背景的,字体大小等也会比较小,图片也不会居中显示/对 markdown 样式的补充/pre { display: block; padding: 10px; margin: 0 0 10px; font-size: 14px; line-height: 1.42857143; color: #abb2bf; background: #282c34; word-break: break-all; word-wrap: break-word; overflow: auto;}h1,h2,h3,h4,h5,h6{ margin-top: 1em; /* margin-bottom: 1em; /}strong { font-weight: bold;}p > code:not([class]) { padding: 2px 4px; font-size: 90%; color: #c7254e; background-color: #f9f2f4; border-radius: 4px;}p img{ / 图片居中 */ margin: 0 auto; display: flex;}#content { font-family: “Microsoft YaHei”, ‘sans-serif’; font-size: 16px; line-height: 30px;}#content .desc ul,#content .desc ol { color: #333333; margin: 1.5em 0 0 25px;}#content .desc h1, #content .desc h2 { border-bottom: 1px solid #eee; padding-bottom: 10px;}#content .desc a { color: #009a61;}6. 主页的满屏 飘花洒落 的效果大家也看到了,主页的满屏动态 飘花洒落 的效果很棒吧,这效果我也是网上找的,是在单独的一个 main.html 文件上的,代码链接如下:主页的满屏 飘花洒落 的效果7. 注意点7.1 打包的配置因为项目是用了 react-app-rewired (一个对 create-react-app 进行自定义配置的社区解决方案) 来打包了,所以如果你想修改 webpack.config.dev.js 和 webpack.config.prod.js 的配置,打包后可能看不到想要的效果,因为 react-app-rewired 打包时,是根据根目录的 config-overrides.js 来进行打包,所以要修改 webpack 的配置的话,请修改 config-overrides.js 。比如:关闭 sourceMap 和 支持装饰器config.devtool = false; // 关闭 sourceMap config = injectBabelPlugin(‘babel-plugin-transform-decorators-legacy’, config); // 支持装饰器7.2 关于 页面对于 关于 的页面,其实是一篇文章来的,根据文章类型 type 来决定的,数据库里面 type 为 3 的文章,只能有一篇就是 博主介绍 ;达到了想什么时候修改内容都可以。所以当 this.props.location.pathname === ‘/about’ 时就是请求类型为 博主介绍 的文章。type: 3, // 文章类型: 1:普通文章;2:是博主简历;3 :是博主简介;8. Build Setup ( 建立安装 )# install dependenciesnpm install # serve with hot reload at localhost: 3000npm start 或者 yarn start# build for production with minificationnpm run build 或者 yarn run build如果要看完整的效果,是要和后台项目 blog-node 一起运行才行的,不然接口请求会失败。虽然引入了 mock 了,但是还没有时间做模拟数据,想看具体效果,请稳步到我的网站上查看 http://乐趣区.cn/main.html最后其他具体的业务代码,都是些常会见到的需求,这里就不展开讲了。如果你觉得该文章不错,欢迎到我的 github,star 一下,谢谢。项目地址:1. 前台展示: https://github.com/乐趣区/blog-react相关文章:1. react + node + express + ant + mongodb 的简洁兼时尚的博客网站2. blog 文章你以为本文就这么结束了 ? 精彩在后面 !!!对 全栈开发 有兴趣的朋友可以扫下方二维码关注我的公众号,我会不定期更新有价值的内容。微信公众号:乐趣区分享 前端、后端开发 等相关的技术文章,热点资源,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源分享–Python、Java、Linux、Go、node、vue、react、javaScript ...

November 23, 2018 · 2 min · jiezi

如何更好使用 ng-zorro-antd 图标

自 ng-zorro-antd 1.7.x 以后图标发生破坏性变更,虽然带了诸多优势,同时也带来几个劣势:若采用动态加载会产生额外的HTTP请求若静态加载需要逐一注册图标st 组件的 format 参数无法直接指定图标ng-alain 默认使用静态加载的做法,毕竟后端使用图标相对于比较有限,即使将 svg 都打包进脚本相比较之前整个 styles 体积上是所有减少,但比较并不多。而针对以上问题,ng-alain 提供几种方案。使用icon插件(推荐)尽可能从项目中分析并生成静态 Icon,插件会自动在 src 目录下生成两个文件:src/style-icons.ts 自定义部分无法解析(例如:远程菜单图标)src/style-icons-auto.ts 命令自动生成文件自动排除 ng-zorro-antd 和 @delon 已经加载的图标。ng g ng-alain:plugin icon同时,需要手动在 startup.service.ts 中导入:import { ICONS_AUTO } from ‘../../../style-icons-auto’;import { ICONS } from ‘../../../style-icons’;@Injectable()export class StartupService { constructor(iconSrv: NzIconService) { iconSrv.addIcon(…ICONS_AUTO, …ICONS); }}有效语法<i class=“anticon anticon-user”></i><i class=“anticon anticon-question-circle-o”></i><i class=“anticon anticon-spin anticon-loading”></i><i nz-icon class=“anticon anticon-user”></i><i nz-icon type=“align-{{type ? ’left’ : ‘right’}}"></i><i nz-icon [type]=“type ? ‘menu-fold’ : ‘menu-unfold’” [theme]=“theme ? ‘outline’ : ‘fill’"></i><i nz-icon [type]=“type ? ‘fullscreen’ : ‘fullscreen-exit’"></i><i nz-icon type=”{{ type ? ‘arrow-left’ : ‘arrow-right’ }}"></i><i nz-icon type=“filter” theme=“outline”></i><nz-input-group [nzAddOnBeforeIcon]=“focus ? ‘anticon anticon-arrow-down’ : ‘anticon anticon-search’"></nz-input-group>动态加载动态加载,这是为了减少包体积而提供的方式。当 NG-ZORRO 检测用户想要渲染的图标还没有静态引入时,会发起 HTTP 请求动态引入。你只需要配置 angular.json 文件:{ “assets”: [ { “glob”: “**/*”, “input”: “./node_modules/@ant-design/icons-angular/src/inline-svg/”, “output”: “/assets/” } ]}动态使用不管是静态或动态加载,依然无法解决原始方法 class=“anticon anticon-” 的便利性,毕竟字符串就是字符串并非 Angular 模板无法被解析,而针对这个问题,提供两种解决办法。类样式事实上所有 Antd 图标都可以在 iconfont 找得到,可以点选自己需要的图标并生成相应的 css 文件或 cdn,最后在项目中可以直接使用 1.7.0 之前的写法。注意: 在项目编辑里加上 anticon anticon- 前缀才能同之前的类名保持一致。// angular.json"styles”: [ “src/iconfont.css”],如果非cnd还需要将相应的 icon 图标文件复制到 assets 目录下,同时修改 iconfont.css 中 @font-face 对应的 url 路径。@angular/elements动态加载的另一种方式是使用 @angular/elements,只需要 nz-icon 指令重新封装成组件。import { Component, Input } from ‘@angular/core’;@Component({ selector: ’nz-icon’, template: &lt;i nz-icon [type]="type"&gt;&lt;/i&gt;,})export class IconComponent { @Input() type: string;}同时在根模块里注册它。import { createCustomElement } from ‘@angular/elements’;@NgModule({ declarations: [], entryComponents: []})export class AppModule { constructor(injector: Injector) { customElements.define(’nz-icon’, createCustomElement(IconComponent, { injector })); }}最后。@Component({ selector: ‘app-demo’, template: &lt;div [innerHTML]="value"&gt;&lt;/div&gt; ,})export class DemoComponent { constructor(private san: DomSanitizer) { } value = this.san.bypassSecurityTrustHtml( icon: &lt;nz-icon type="bell"&gt;&lt;/nz-icon&gt;, );}结论本文只是针对这一次 ng-zorro-antd 图标上的变更做一个总结,就我个人而言还是比较推荐静态加载的方式,这无关乎包体大小的问题,而是更加明确我需要什么所以我应加载什么。 ...

October 26, 2018 · 1 min · jiezi