导图社区 前端基础工程模板需求分析
这是一篇关于前端基础工程模板需求分析的思维导图,系统管理:用户管理(用户添加对应角色)、权限管理(配置不同的权限范围)、数据字典(元数据/基础数据)。
编辑于2022-09-08 11:37:04前端基础工程-业务分析
缺省页
404页面
410/403页面
500/502页面
缺省图
无数据
图片加载失败
开发中/待建设
注冊/登录
普通注册
手机号注册
邮箱注册
用户名密码注册
快捷注册
手机号+验证码快速注册
邮箱+验证码快速注册
登录
手机号/邮箱+验证码登录
用户名+密码+图形验证码登录
SSO登录(微信/支付宝/WeLink/自有系统)
系统管理
用户管理(用户添加对应角色)
角色管理(角色添加权限)
权限管理(配置不同的权限范围)
数据字典(元数据/基础数据)
设计规则 1、根据类型进行分类; 2、字段key尽量和前端使用的组件保持一致,减少数据转换的步骤; 3、字典表需要预留扩展字段,预防后期需要存储一些其他的数据,增加系统的可扩展性; 数据字典-字段设计 key String 字典Key name String 字典名称 desc String 字典描述 type String 字典类型 extension1 String 扩展字段1 extension2 String 扩展字段2 extension3 String 扩展字段3 extension4 String 扩展字段4 extension5 String 扩展字段5 extension6 String 扩展字段6 // 返回数据,服务可以进行统一处理 const dict = { value: '字典Key', lable: '字典名称', desc: '字典描述', type: '字典数据哪个分类', extension1: '扩展字段1', extension1: '扩展字段2', extension1: '扩展字段3', extension1: '扩展字段4', extension1: '扩展字段5', extension1: '扩展字段6', }
固定的下拉值(后期可能会改变)
国际化数据
菜单/路由管理(配置菜单权限)
动态路由 1、展示使用树状表格,操作按钮可以选择新增的是同级菜单还是子层菜单; 2、新增包含顶级菜单和子层菜单; 菜单管理-字段设计 id String 当前菜单ID pId String 父级ID name String 菜单名称 desc String 菜单描述 path String 路由地址 type Boolean 菜单类型(菜单路由/非菜单路由) permission String 权限类型(菜单授权,添加权限) state Boolean 菜单是否启用 icon String 菜单图标 createBy String 创建人 createDate String 创建时间 updatedBy String 更新人 updatedDate String 更新时间 extension1 String 扩展字段1 extension2 String 扩展字段2
静态路由
登录
注册
首页
...
动态路由
菜单展示路由
菜单不展示路由
公告管理
公告可以使用富文本,原则 1、可以支持直接粘贴word/Excel文档内容; 2、推荐: TinyMCE CKEditor 5(功能很轻大,可以满足大部分的需求)
文档管理
md富文本(mavon-editor)
通过标题提取菜单即可
系统日志/监控(操作/异常)
系统业务
权限
页面权限
未登录,410/403直接跳转登录页
已登陆,410/403跳转403缺省页
内容权限/接口权限
展示403的缺省图
按钮权限
使用v-if进行控制即可
头部
左侧LOGO
右侧操作功能
菜单(动态路由)
多层菜单,使用组件嵌套
主体内容
前端基础工程
技术选型
PC
PC+H5
使用自适应方案/媒体查询
Mobile(Android+IOS)+PC
Mobile可以使用Fullter开发
小程序(微信+支付宝)+Mobile(Android+IOS)+H5+PC
直接上uniAPP
技术选则
Vue
Vue2.0
Vue3.0
setp语法,支持TS
常用
路由
vue-router
状态管理
vuex
pina
UI
iview
vant
element UI
Antd
ICON
阿里巴巴图标库
文档
vuePress
vitePress
动画
animate.css
构建工具
webpack
vite
可视化
Echarts
Antv
G2
JSPlumb
D3.js
富文本
mavon-editor
TinyMCE
CKEditor 5
服务端渲染
SSR
vue-meta
Nuxt.js
常用插件
draggable
swiper
easytable
patch-package
React
一个灵活的JS库,配套设施完善,比Vue上手难度稍高,写法灵活,单向数据流,阿里umi、antd、dva等
Angular
复杂度最高,社区活跃,国内使用较少,必须使用TS语法,新进维护老项目使用较多
Svelte
新进框架,无虚拟DOM,市场站占有率和社区活跃度较低
前端规范
为什么需要规范?
统一代码规范的好处: 提高代码整体的可读性、可维护性、可复用性、可移植性和可靠性,这会从根本上降低开发成本,也是最重要的一点。 保证代码的一致性:软件系统中最重要的因素之一就是编码的一致性。如果编码风格一致,也更加易于维护,因为团队内任何人都可以快速理解并修改。 提升团队整体效率:开发人员通常需要花费大量的时间来解决代码质量问题,如果都按照规范编写,也有助于团队尽早发现问题,甚至完全预防问题,这将提高整个交付过程的效率。 减少code review期间一系列的争议,因为缺乏标准,在争议过程中双方很难妥协(没少因为这事争论过)。 若不统一代码规范,可能会造成的后果: 由于缺乏规范,导致代码风格不一,增加团队成员间的心理负担,极端情况下,某段代码只有某个人能修改(俗称屎山)。 团队间协作更加困难:因为开发人员得适应不同的风格,会导致效率低下(阅读代码是我们花费时间最多的地方)。 在code review期间可能经常为类似的事情做过多的讨论。 影响团队的生产力和质量,严重的甚至会影响团队和谐。 
如何保持规范?
规范难以推广的解决办法: 对规范问题进行归纳分析并通过文档记录(wiki等),寻找业内最佳解决方案,在团队内进行统一。 采用小步快跑的方式,有问题就解决问题,按照优先级和重要性进行排序划分,依次将问题纳入迭代,每个迭代重点解决其中几个即好。 本迭代的规范问题绝不留到下个迭代,防止堆积(当然,有时候还是得向项目经理妥协)。 在code review过程中严格把关,拒绝睁一只眼闭一只眼。 当团队成员对具体某个规范有争议时,及时讨论并定出结论。 没有规则只是为了规则,制定规范的目的并不是一定要按照某某标准来执行,更多的是团队成员达成一致即可。 鼓励大家大胆的质疑规则,若不能提高代码的可读性,可维护性,可复用性,可移植性和可靠性的规则都应该被受到质疑。 以身作则,船头的方向不能偏航。
规范分类
流程规范
开发流程规范: 从需求的提出到需求的交付,我们需要一个规范的流程,避免不必要的反攻与BUG修复。 下面是一个比较好用的流程  对于复杂的需求/功能,我们需要进行技术方案的预研、技术方案的设计、技术方案的评审,并输出详细的设计文档
代码规范
IDE规范
前端现在常用的编辑器一般都是vScode 1、项目添加Eslint插件 npm install eslint --save-dev 2、项目中添加.eslintrc.js配置文件 3、IDE配置 { "interview.updateNotification": 1663643839218, "interview.workspaceFolder": "C:\\Users\\pact\\.FEInterview", "z-reader.onlineSite": "起点", "z-reader.chapterOrder": "倒序", "workbench.colorTheme": "Monokai", "eslint.format.enable": true, "[vue]": { "editor.defaultFormatter": "yoyo930021.vuter" }, "fileheader.customMade": { "Author": "bingbing.geng", "Date": "2022-08-23", "LastEditTime": "2022-08-23", "FilePath": "2022-08-23" }, "fileheader.configObj": { "createFileTime": true, "language": { "languagetest": { "head": "/$$", "middle": " $ @", "end": " $/", "functionSymbol": { "head": "/** ", "middle": " * @", "end": " */" }, "functionParams": "js" } }, "autoAdd": true, "autoAddLine": 100, "autoAlready": true, "annotationStr": { "head": "/*", "middle": " * @", "end": " */", "use": false }, "headInsertLine": { "php": 2, "sh": 2 }, "beforeAnnotation": { "文件后缀": "该文件后缀的头部注释之前添加某些内容" }, "afterAnnotation": { "文件后缀": "该文件后缀的头部注释之后添加某些内容" }, "specialOptions": { "特殊字段": "自定义比如LastEditTime/LastEditors" }, "switch": { "newlineAddAnnotation": true }, "supportAutoLanguage": [], "prohibitAutoAdd": [ "json" ], "folderBlacklist": [ "node_modules", "文件夹禁止自动添加头部注释" ], "prohibitItemAutoAdd": [ "项目的全称, 整个项目禁止自动添加头部注释, 可以使用快捷键添加" ], "moveCursor": true, "dateFormat": "YYYY-MM-DD HH:mm:ss", "atSymbol": [ "@", "@" ], "atSymbolObj": { "文件后缀": [ "头部注释@符号", "函数注释@符号" ] }, "colon": [ ": ", ": " ], "colonObj": { "文件后缀": [ "头部注释冒号", "函数注释冒号" ] }, "filePathColon": "路径分隔符替换", "showErrorMessage": false, "writeLog": false, "wideSame": false, "wideNum": 13, "functionWideNum": 0, "CheckFileChange": false, "createHeader": false, "useWorker": false, "designAddHead": false, "headDesignName": "random", "headDesign": false, "cursorModeInternalAll": {}, "openFunctionParamsCheck": true, "functionParamsShape": [ "{", "}" ], "functionBlankSpaceAll": {}, "functionTypeSymbol": "*", "typeParamOrder": "type param", "customHasHeadEnd": {}, "throttleTime": 60000, "functionParamAddStr": "", "NoMatchParams": "no show param" }, "[javascript]": { "editor.defaultFormatter": "vscode.typescript-language-features" }, "[html]": { "editor.defaultFormatter": "Vue.volar" }, "editor.tabSize": 2, "security.workspace.trust.untrustedFiles": "open", "eslint.codeActionsOnSave.rules": null }
JS规范
使用Eslint格式化即可
CSS规范
自定义规范
命名规范
见名知意
驼峰命名变量
-设置文件名
注释
使用插件自动设置(代码片段即可)
创建人
创建时间
最新更新人
最新修改人
文件功能
功能注释
变量注释
特殊处理/函数注释
组件复杂度(颗粒度大小)
基础组件
功能级别业务组件
页面级别业务组件
三元嵌套
三元运算符在第一级之后变得难以阅读,虽然看起来节省了代码空间,但最好在代码中明确意图,保持良好的阅读性。
错误处理
目录规范
目录主要是src下方的目录结构 pages 页面级别视图 module1 模块1 compontents 模块级别私有组件 com1.vue com2.vue index.vue index.less index.js module2 模块2 components 公共组件 index.js 导出所有的组件 header.vue footer.vue com1 index.vue index.js index.less utils index.js 提取的一些公共函数 style index.less 公共样式 element.less UI框架样式整体修改 config index.js 项目级别的配置文件
commit规范
提交规范
每个提交消息都包含一个subject、一个body和一个footer (中间使用空行分割),提交信息的任何一行不能超过 100 个字符。 type主要有以下几种类型: feat: 一个新特性 fix: 修复bug docs: 文档修改 style: 不影响代码含义的更改(空格、格式、缺少分号等) refactor: 代码重构 perf: 优化性能 test: 测试用例修改 chore: 对构建过程或辅助工具和库的更改,例如文档生成 scope:可以是影响范围的任何内容。 subject:包含对更改的简洁描述,规则: 使用陈述语句 第一个字母不要大写 末尾没有点 (.) body:commit 具体修改内容, 可以分为多行,应该包括改变的动机,并与以前的行为进行对比。 footer: 一些备注, 通常是修复的 bug 的链接。
工具推荐
TortoiseGit
SourceTree
自动化配置
UI规范
VUE
路由
vue-router
状态管理
vuex
pina
UI
iview
vant
element UI
Antd
ICON
阿里巴巴图标库
文档/博客
vuePress
主题
vuepress-theme-vdoing
https://doc.xugaoyi.com/
vuepress-theme-reco
https://vuepress-theme-reco.recoluan.com/
vuepress-theme-hope
https://vuepress-theme-hope.gitee.io/v2/zh/guide/get-started/intro.html
vuepress-theme-vpx
https://qcyblm.gitee.io/vuepress-theme-vpx/
vitePress
动画
animate.css
构建工具
webpack
vite
可视化
Echarts
Antv
G2
JSPlumb
D3.js
富文本
mavon-editor
TinyMCE
CKEditor 5
服务端渲染
SSR
vue-meta
Nuxt.js
常用插件
draggable
swiper
easytable
patch-package
React
问题
Q1:为什么不直接更新 state? 答:不会重新渲染组件
// 错误 this.state.aa = '123' // 正确 this.setState({ aa: '456' })
Q2:React组件的构造函数有什么作用? 答:设置state的初始值/绑定事件
class LikeButton extends React.Component { constructor() { super(); this.state = { liked: false }; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState({liked: !this.state.liked}); } render() { const text = this.state.liked ? 'liked' : 'haven\'t liked'; return ( <div onClick={this.handleClick}> You {text} this. Click to toggle. </div> ); } } ReactDOM.render( <LikeButton />, document.getElementById('example') );
生命周期
初始阶段
componentWillMount(react17删除) 组件即将被装载、渲染到页面上
componentDidMount 组件真正在被装载之后
运行中状态
componentWillReceiveProps(react17删除) 组件将要接收到属性的时候调用
shouldComponentUpdate 组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)
这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在 shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。
componentWillUpdate(react17删除) 组件即将更新不能修改属性和状态
componentDidUpdate 组件已经更新
销毁阶段
componentWillUnmount 组件即将销毁
Hooks
react 父子传值
父传子
// 父组件 import React, { Component } from 'react' import Child from './Child' export class Parent extends Component { state = { msg:'我是父组件的信息' //1.父组件准备数据 } render() { return ( <div> 我是父组件 {/* 父组件直接通过属性传递给子组件 */} <Child pMsg={this.state.msg}></Child> </div> ) } } export default Parent // 子组件 import React, { Component } from 'react' // 子组件 export class Child extends Component { render() { // 子组件通过props接收父组件传递的值 console.log(this.props) return ( <div> 子组件 {/* 使用 */} <p>{this.props.pMsg}</p> </div> ) } } export default Child
1、传递:父组件准备数据,父组件通过属性pMsg直接传递给子组件 2、接收:子组件通过props接收
子传父
// 子组件 class Child extends React.Component { state = { msg: "短视频", }; // 子组件调用父组件中传递的回调函数 handClick = () => { this.props.getMsc(this.state.msg); }; render() { return ( <div> <button onClick={this.handClick}> 点击我 </button> </div> ); } } // 父组件 class Parent extends React.Component { state = { parentMsg: "", }; // 提供回调函数 接收子组件数据 getChidMsg = (msg) => { console.log("子组件的数据", msg); this.setState({ parentMsg: msg, }); }; render() { return ( <div> 父组件:{this.state.parentMsg} <br /> 子组件: <Child getMsc={this.getChidMsg}></Child> </div> ); } }
1、传递:通过props调用属性的方法 2、接受:属性上调用函数进行操作
兄弟传值
class Parent extends React.Component { state = { count: 0, }; onIncrement = () => { this.setState({ count: this.state.count + 1, }); }; render() { return ( <div> <Child1 count={this.state.count}></Child1> <Child2 incrementCount={this.onIncrement}></Child2> </div> ); } } // 组件1 const Child1 = (props) => { const { count } = props; return <div> 计数器:{count} </div>; }; // 组件2 const Child2 = (props) => { const { incrementCount } = props; function onIncrementCount() { incrementCount(); } return <button onClick={onIncrementCount}>+1</button>; };
通过父传子/子传父的形式
通过Bus
bus.js
import {EventEmitter} from 'events' const bus = new EventEmitter() export default bus
com1
import React, { Component } from 'react' import bus from './bus' /*一定要导入bus */ export default class Brother1 extends Component { // dom节点渲染完毕 componentDidMount(){ //接收brother2传来的值 bus.on('sendVal',data=>{ console.log(data) }) } //向brother2传值 sendValueBrother2 = ()=>{ bus.emit('sendValue',{ name:'赵敏', age:600 }) } render() { return ( <div> <h3>兄弟1组件</h3> <button onClick={this.sendValueBrother2}>传值给兄弟2</button> </div> ) } }
com2
import React, { Component } from 'react' import bus from './bus' /*一定要导入bus */ export default class Brother2 extends Component { //向brother1传值 sendValueBrother1 = ()=>{ bus.emit('sendVal',{ name:'周芷若', age:600 }) } // dom节点渲染完毕 componentDidMount(){ //接收brother1传来的值 bus.on('sendValue',data =>{ console.log(data) }) } render() { return ( <div> <h3>兄弟2组件</h3> <br/> <button onClick={this.sendValueBrother1}>传值给brother1</button> </div> ) } }
跨组件传值
import React from "react"; import ReactDOM from "react-dom"; // 跨多个组件传值 // 创建context 会得到两个组件 // Provider用来包裹传值组件 // Consumer用来接收传过来的值 const { Provider, Consumer } = React.createContext(); class Parent extends React.Component { state = { proData: { userName: "偶然网", userPassWord: "123456", }, }; render() { console.log(this); const { proData } = this.state; console.log("proData", proData); return ( <Provider value={proData}> <div style={{ color: "red", background: "pink", height: "600px", width: "100%", }} > 1<Child1></Child1> </div> </Provider> ); } } const Child1 = () => { return ( <div style={{ color: "red", background: "green", height: "400px", width: "100%", }} > 2<Child2></Child2> </div> ); }; const Child2 = () => { return ( <div style={{ color: "red", background: "yellow", height: "200px", width: "100%", }} > 3<Child3></Child3> </div> ); }; const Child3 = () => { return ( <Consumer> {(data) => ( <div style={{ color: "red", background: "purple", height: "100px", width: "100%", }} > 4 这是接收的数据-------{data.userName} -------{data.userPassWord} </div> )} </Consumer> ); }; ReactDOM.render(<Parent />, document.getElementById("root"));
1、Provider用来包裹传值组件 2、Consumer用来接收传过来的值
Diff算法如何比较?
只对同级比较,跨层级的dom不会进行复用
不同类型节点生成的dom树不同,此时会直接销毁老节点及子孙节点,并新建节点
可以通过key来对元素diff的过程提供复用的线索
单节点diff
key和type相同表示可以复用节点
key不同直接标记删除节点,然后新建节点
key相同type不同,标记删除该节点和兄弟节点,然后新创建节点
全局状态管理
redux
常用方法总结
禁用右键和F12
// 禁止右键 document.oncontextmenu = new Function("event.returnValue=false"); // 禁止F12 document.onkeydown = new Function("event.returnValue=false");
生成uuid
export const uuid = () => { const temp_url = URL.createObjectURL(new Blob()) const uuid = temp_url.toString() URL.revokeObjectURL(temp_url) //释放这个url return uuid.substring(uuid.lastIndexOf('/') + 1) } uuid() // a640be34-689f-4b98-be77-e3972f9bffdd
存储操作
class MyCache { constructor(isLocal = true) { this.storage = isLocal ? localStorage : sessionStorage } setItem(key, value) { if (typeof (value) === 'object') value = JSON.stringify(value) this.storage.setItem(key, value) } getItem(key) { try { return JSON.parse(this.storage.getItem(key)) } catch (err) { return this.storage.getItem(key) } } removeItem(key) { this.storage.removeItem(key) } clear() { this.storage.clear() } key(index) { return this.storage.key(index) } length() { return this.storage.length } } const localCache = new MyCache() const sessionCache = new MyCache(false) export { localCache, sessionCache } // demo localCache.getItem('user') sessionCache.setItem('name','树哥') sessionCache.getItem('token') localCache.clear()
下载文件
参数: api 接口 params 请求参数 fileName 文件名 const downloadFile = (api, params, fileName, type = 'get') => { axios({ method: type, url: api, responseType: 'blob', params: params }).then((res) => { let str = res.headers['content-disposition'] if (!res || !str) { return } let suffix = '' // 截取文件名和文件类型 if (str.lastIndexOf('.')) { fileName ? '' : fileName = decodeURI(str.substring(str.indexOf('=') + 1, str.lastIndexOf('.'))) suffix = str.substring(str.lastIndexOf('.'), str.length) } // 如果支持微软的文件下载方式(ie10+浏览器) if (window.navigator.msSaveBlob) { try { const blobObject = new Blob([res.data]); window.navigator.msSaveBlob(blobObject, fileName + suffix); } catch (e) { console.log(e); } } else { // 其他浏览器 let url = window.URL.createObjectURL(res.data) let link = document.createElement('a') link.style.display = 'none' link.href = url link.setAttribute('download', fileName + suffix) document.body.appendChild(link) link.click() document.body.removeChild(link) window.URL.revokeObjectURL(link.href); } }).catch((err) => { console.log(err.message); }) }` downloadFile('/api/download', {id}, '文件名')
模糊搜索
参数: list 原数组 keyWord 查询的关键词 attribute 数组需要检索属性 export const fuzzyQuery = (list, keyWord, attribute = 'name') => { const reg = new RegExp(keyWord) const arr = [] for (let i = 0; i < list.length; i++) { if (reg.test(list[i][attribute])) { arr.push(list[i]) } } return arr } const list = [ { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, { id: 4, name: '汤师爷' }, { id: 5, name: '胡万' }, { id: 6, name: '花姐' }, { id: 7, name: '小梅' } ] fuzzyQuery(list, '树', 'name') // [{id: 1, name: '树哥'}]
工具函数
校验数据类型
export const typeOf = function(obj) { return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase() }
防抖
export const debounce = (() => { let timer = null return (callback, wait = 800) => { timer&&clearTimeout(timer) timer = setTimeout(callback, wait) } })()
节流
export const throttle = (() => { let last = 0 return (callback, wait = 800) => { let now = +new Date() if (now - last > wait) { callback() last = now } } })()
手机号脱敏
export const hideMobile = (mobile) => { return mobile.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2") }
解析URL参数
export const getSearchParams = () => { const searchPar = new URLSearchParams(window.location.search) const paramsObj = {} for (const [key, value] of searchPar.entries()) { paramsObj[key] = value } return paramsObj } // 假设目前位于 https://****com/index?id=154513&age=18; getSearchParams(); // {id: "154513", age: "18"}
判断手机是Andoird还是IOS
/** * 1: ios * 2: android * 3: 其它 */ export const getOSType=() => { let u = navigator.userAgent, app = navigator.appVersion; let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); if (isIOS) { return 1; } if (isAndroid) { return 2; } return 3; }
数组对象根据字段去重
export const uniqueArrayObject = (arr = [], key = 'id') => { if (arr.length === 0) return let list = [] const map = {} arr.forEach((item) => { if (!map[item[key]]) { map[item[key]] = item } }) list = Object.values(map) return list } const responseList = [ { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, { id: 1, name: '黄老爷' }, { id: 2, name: '张麻子' }, { id: 3, name: '树哥' }, { id: 1, name: '树哥' }, { id: 2, name: '黄老爷' }, { id: 3, name: '张麻子' }, ] uniqueArrayObject(responseList, 'id') // [{ id: 1, name: '树哥' },{ id: 2, name: '黄老爷' },{ id: 3, name: '张麻子' }]
滚动到元素位置
export const smoothScroll = element =>{ document.querySelector(element).scrollIntoView({ behavior: 'smooth' }); }; smoothScroll('#target'); // 平滑滚动到 ID 为 target 的元素
格式化
参数: {number} number:要格式化的数字 {number} decimals:保留几位小数 {string} dec_point:小数点符号 {string} thousands_sep:千分位符号 export const moneyFormat = (number, decimals, dec_point, thousands_sep) => { number = (number + '').replace(/[^0-9+-Ee.]/g, '') const n = !isFinite(+number) ? 0 : +number const prec = !isFinite(+decimals) ? 2 : Math.abs(decimals) const sep = typeof thousands_sep === 'undefined' ? ',' : thousands_sep const dec = typeof dec_point === 'undefined' ? '.' : dec_point let s = '' const toFixedFix = function(n, prec) { const k = Math.pow(10, prec) return '' + Math.ceil(n * k) / k } s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.') const re = /(-?\d+)(\d{3})/ while (re.test(s[0])) { s[0] = s[0].replace(re, '$1' + sep + '$2') } if ((s[1] || '').length < prec) { s[1] = s[1] || '' s[1] += new Array(prec - s[1].length + 1).join('0') } return s.join(dec) } 示例: moneyFormat(10000000) // 10,000,000.00 moneyFormat(10000000, 3, '.', '-') // 10-000-000.000
深拷贝
export const clone = parent => { // 判断类型 const isType = (obj, type) => { if (typeof obj !== "object") return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case "Array": flag = typeString === "[object Array]"; break; case "Date": flag = typeString === "[object Date]"; break; case "RegExp": flag = typeString === "[object RegExp]"; break; default: flag = false; } return flag; }; // 处理正则 const getRegExp = re => { var flags = ""; if (re.global) flags += "g"; if (re.ignoreCase) flags += "i"; if (re.multiline) flags += "m"; return flags; }; // 维护两个储存循环引用的数组 const parents = []; const children = []; const _clone = parent => { if (parent === null) return null; if (typeof parent !== "object") return parent; let child, proto; if (isType(parent, "Array")) { // 对数组做特殊处理 child = []; } else if (isType(parent, "RegExp")) { // 对正则对象做特殊处理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, "Date")) { // 对Date对象做特殊处理 child = new Date(parent.getTime()); } else { // 处理对象原型 proto = Object.getPrototypeOf(parent); // 利用Object.create切断原型链 child = Object.create(proto); } // 处理循环引用 const index = parents.indexOf(parent); if (index != -1) { // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 递归 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); };
开放平台系统业务
入驻
认证申请
网关
API网关
认证鉴权
安全认证
统一入口
流量管控
调用频率
应用授权
服务监控
日志
统计报表
告警通知
网管选型
伊士格
得帆
入驻
注册
登录
个人中心-认证
开发者-创建领域
开发者-创建能力
开发者-能力-创建API
领域/能力/API审核
发布
能力申请
注册
登录
个人中心-认证
开发者-创建应用
能力中心-申请能力/API
能力/API审核
使用