diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4d29575de80483b005c29bfcac5061cd2f45313e --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md index fb0d674e6ecf62a9b4cb50f54b1d1186fc5ce8fc..30745367f1b82b5353ee69ead38bfa4581bf2f16 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,23 @@ -瑜美科技出品 -强大的前端库 \ No newline at end of file +# YumeiSoft.CommonFrontUtils +## 瑜美科技出品 +强大的通用前端算法库 + +如何启动演示: +``` +yarn install +yarn start +``` + +目录结构: +``` +src React项目 +src/demo 示例 +src/Yumeisoft/CommonFrontUtils 源代码 +``` + +工具类型: +``` +Objs 对象数组操作工具 +Obj 对象操作工具 +List 数组操作工具 +``` \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a3afdd02c9a493d64005f09ea242cdabebe84aee --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "commonfrontutils", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-scripts": "3.4.3" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "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" + ] + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..bcd5dfd67cd0361b78123e95c2dd96031f27f743 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..aa069f27cbd9d53394428171c3989fd03db73c76 --- /dev/null +++ b/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 Binary files /dev/null and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e47a6545bc15971f8f63fba70e4013df88a664 Binary files /dev/null and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..080d6c77ac21bb2ef88a6992b2b73ad93daaca92 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9e57dc4d41b9b46e05112e9f45b7ea6ac0ba15e --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000000000000000000000000000000000000..b928a6b9d61b02135439b1a3560c7efd66f276e7 --- /dev/null +++ b/src/App.js @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import Welcome from './demo/Welcome'; +import ObjsDemo from './demo/ObjsDemo'; + +export default class App extends Component { + constructor() { + super(); + this.state = { demo: Welcome }; + } + render() { + let DemoTarget = this.state.demo; + return ( + <> +
+ + +
+
+ +
+ + ); + } +} diff --git a/src/Yumeisoft/CommonFrontUtils/List.js b/src/Yumeisoft/CommonFrontUtils/List.js new file mode 100644 index 0000000000000000000000000000000000000000..3829e9adcb3cd2232c61793a99a2b73440a030fc --- /dev/null +++ b/src/Yumeisoft/CommonFrontUtils/List.js @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2020 YumeiSoft + * CommonFrontUtils is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +/** + * List 数组操作工具 + * @author 黑龙江省瑜美科技发展有限公司 杨若瑜 + * @since 2020年09月12日 + */ +class List { + constructor(input) { + this.input = input; + } + /** 取纯数字部分 */ + nums() { + let result = []; + for (let i in this.input) { + let item = this.input[i]; + if (!isNaN(item) && item !== Infinity) { + result.push(Number(item)); + } + } + return result; + } + /** 计算平均值 */ + avg(num) { + let nums = this.nums(); + let sum = this.sum(); + if (num) { + return sum / num; + } else { + return sum / nums.length; + } + } + /** 求最大值 */ + max() { + return Math.max(...this.nums()); + } + /** 求最小值 */ + min() { + return Math.min(...this.nums()); + } + /** 求和 */ + sum() { + let sum = 0; + let nums = this.nums(); + for (let i in nums) { + sum += nums[i]; + } + return sum; + } + /** 获得数量 */ + count(){ + return this.input.length; + } +} +export default (input) => { + return new List(input); +}; diff --git a/src/Yumeisoft/CommonFrontUtils/Obj.js b/src/Yumeisoft/CommonFrontUtils/Obj.js new file mode 100644 index 0000000000000000000000000000000000000000..d1f092b802699b907b5a02ed73c67b3cf5377328 --- /dev/null +++ b/src/Yumeisoft/CommonFrontUtils/Obj.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2020 YumeiSoft + * CommonFrontUtils is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +/** + * Obj 对象操作工具 + * @author 黑龙江省瑜美科技发展有限公司 杨若瑜 + * @since 2020年09月12日 + */ +class Obj { + constructor(input) { + this.input = input; + } + /** 转成JSON字符串 */ + toJson(){ + return JSON.stringify(this.input); + } + /** 通过序列化和反序列化深拷贝对象 */ + deepClone(){ + return JSON.parse(this.toJson(this.input)); + } + /** 获取当前对象所有键 */ + keys() { + if (!this.input) return []; + return Object.keys(this.input); + } + /** 判断是否为字符串 */ + isStr() { + return typeof this.input === 'string'; + } + /** 判断是否为空 */ + isNull() { + if (this.input === null) return true; + } + /** 判断是否为数字 */ + isNum() { + return typeof this.input === 'number'; + } + /** 判断是否与目标对象的值相等 */ + eq(target) { + if (this.input === target) return true; + if(this.isNum()||new Obj(target).isNum()){ + if(Number(this.input)===Number(target)){ + return true; + } + } + if(this.isStr()||new Obj(target).isStr()){ + if(String(this.input)===String(target)){ + return true; + } + } + return false; + } +} +export default (input) => { + return new Obj(input); +}; diff --git a/src/Yumeisoft/CommonFrontUtils/Objs.js b/src/Yumeisoft/CommonFrontUtils/Objs.js new file mode 100644 index 0000000000000000000000000000000000000000..13a9fbacf71fde9193d6aef793d90910f412f784 --- /dev/null +++ b/src/Yumeisoft/CommonFrontUtils/Objs.js @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2020 YumeiSoft + * CommonFrontUtils is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ +import Obj from './Obj'; +import List from './List'; +/** + * Objs 对象数组操作工具 + * @author 黑龙江省瑜美科技发展有限公司 杨若瑜 + * @since 2020年09月12日 + */ +class Objs { + constructor(input) { + this.input = input; + this.funcList = []; + this.deepCloned = false; + } + /** 增加判断条件 */ + filter(func) { + this.funcList.push(func); + return this; + } + /** 列出所有符合判断条件的数据 */ + list() { + let result = []; + let funcList = this.funcList; + outer: for (let i in this.input) { + let obj = this.input[i]; + for (let j in funcList) { + if (!funcList[j](obj)) { + continue outer; + } + } + result.push(obj); + } + return result; + } + /** 遍历回调 */ + each(func) { + let list = this.list(); + let result = []; + for (let idx in list) { + let item = list[idx]; + let handleResult = func(item, idx); + if (handleResult || handleResult == null) { + result.push(handleResult); + } else { + result.push(item); + } + } + return result; + } + /** 只取出一个符合条件的数据 */ + one(column, value) { + if (column && value) { + this.filter((o) => Obj(o[column]).eq(value)); + } + let listResult = this.list(); + if (Obj(listResult.length).eq(1)) { + return listResult[0]; + } else if (Obj(listResult.length).eq(0)) { + throw new Error('DataNotFound'); + } else if (listResult.length > 1) { + throw new Error('DataNotSingle'); + } + } + /** 建立映射关系表 */ + keyColumn(keyColumn, valueColumn) { + let listResult = this.list(); + let result = {}; + for (let i in listResult) { + let key = listResult[i][keyColumn]; + let value = listResult[i][valueColumn]; + result[key] = value; + } + return result; + } + /** 建立主键-对象映射关系表 */ + keyObj(keyColumn) { + let listResult = this.list(); + let result = {}; + for (let i in listResult) { + let key = listResult[i][keyColumn]; + result[key] = listResult[i]; + } + return result; + } + /** 抽取某个字段 */ + colList(colName) { + let result = []; + this.each((item) => { + result.push(item[colName]); + }); + return result; + } + /** 抽取多个字段 */ + colLists(colNames) { + if (!colNames) { + colNames = this.colNames(); + } + colNames = new Objs(colNames); + let result = {}; + this.each((item) => { + colNames.each((colName) => { + if (!result[colName]) { + result[colName] = []; + } + result[colName].push(item[colName]); + }); + }); + return result; + } + /** 取平均值 */ + colAvg(columnName) { + let fieldResult = this.colList(columnName); + return List(fieldResult).avg(); + } + /** 取最大值 */ + colMax(columnName) { + let fieldResult = this.colList(columnName); + return List(fieldResult).max(); + } + /** 取最小值 */ + colMin(columnName) { + let fieldResult = this.colList(columnName); + return List(fieldResult).min(); + } + /** 计数 */ + count() { + let listResult = this.list(); + return listResult.length; + } + /** 求和 */ + colSum(columnName) { + let fieldResult = this.colList(columnName); + return List(fieldResult).sum(); + } + /** 取出所有键名 */ + colNames() { + let result = []; + let listResult = this.list(); + for (let i in listResult) { + let keys = Obj(listResult[i]).keys(); + for (let keyIdx in keys) { + let key = keys[keyIdx]; + if (!result.includes(key)) { + result.push(key); + } + } + } + return result; + } + /** 取出公共键名 */ + sharedColNames() { + let result = []; + let listResult = this.list(); + for (let i in listResult) { + let keys = Obj(listResult[i]).keys(); + if (Obj(i).eq(0)) { + result = keys; + continue; + } + for (let resultIdx in result) { + let key = result[resultIdx]; + if (!keys.includes(key)) { + result.splice(resultIdx, 1); + } + } + } + return result; + } + /** 转换成二维数组 */ + toLists(colNames) { + if (!colNames) { + colNames = this.colNames(); + } + colNames = new Objs(colNames); + let result = this.each((item) => { + let colVals = []; + colNames.each((colName) => { + colVals.push(item[colName]); + }); + return colVals; + }); + return result; + } + /** 分组 */ + group(func) { + let getGroupKey = func; + // 单字段分组 + if (Obj(typeof func).eq('string')) { + let colName = func; + getGroupKey = (item) => { + return item[colName]; + }; + } + // 多字段分组 + if (Array.isArray(func)) { + let colNames = new Objs(func); + getGroupKey = (item) => { + let keyObj = colNames.each((colName) => { + return item[colName]; + }); + return JSON.stringify(keyObj); + }; + } + let result = {}; + this.each((item) => { + let groupKey = getGroupKey(item); + if (!result[groupKey]) { + result[groupKey] = []; + } + result[groupKey].push(item); + }); + return result; + } + /** 聚合分组 */ + aggGroup(func, colName, oper) { + let groupData = this.group(func); + let aggFunc = oper; + // 预设聚合方式 + if (Obj(typeof oper).eq('string')) { + aggFunc = (list) => { + return List(list)[oper](); + }; + } + for (let key in groupData) { + let groupList = groupData[key]; + let colList = new Objs(groupList).colList(colName); + let aggResult = aggFunc(colList); + groupData[key] = aggResult; + } + return groupData; + } + /** 深拷贝 */ + deepClone() { + this.input = Obj(this.input).deepClone(); + this.deepCloned = true; + return this; + } + /** 转树形结构 */ + toTrees(id, pid, branchName) { + let list = new Objs(this.list()); + let result = []; + let idMap = list.keyObj(id); + let pidMap = list.group(pid); + for (let key in pidMap) { + let branchObjs = pidMap[key]; + if (branchObjs) { + if (idMap[key]) { + idMap[key][branchName] = branchObjs; + } else { + new Objs(branchObjs).each((item) => result.push(item)); + } + } + } + return result; + } + /** 升序排列 */ + sortBy(columnName) { + let sortColumn = columnName; + let nextSortColumns = []; + // 如果是多字段 + if (Array.isArray(sortColumn)) { + if (sortColumn.length === 0) { + return this.list(); + } + sortColumn = columnName[0]; + nextSortColumns = columnName.splice(1); + } + let listResult = this.list(); + let listMap = {}; + let orderedMap = {}; + let result = []; + for (let i in listResult) { + let sortVal = listResult[i][sortColumn]; + if (!listMap[sortVal]) { + listMap[sortVal] = []; + } + listMap[sortVal].push(listResult[i]); + } + Object.keys(listMap) + .sort() + .forEach((key) => { + orderedMap[key] = listMap[key]; + }); + for (let key in orderedMap) { + let objs = orderedMap[key]; + objs = new Objs(objs).sortBy(nextSortColumns); + result.push(...objs); + } + return result; + } + /** 倒序排列 */ + sortDescBy(columnName) { + return this.sortBy(columnName).reverse(); + } +} + +export default (input) => { + return new Objs(input); +}; diff --git a/src/demo/ObjsDemo.jsx b/src/demo/ObjsDemo.jsx new file mode 100644 index 0000000000000000000000000000000000000000..711c6f482a9f66d737d184b2cac8d208c6e3b8c5 --- /dev/null +++ b/src/demo/ObjsDemo.jsx @@ -0,0 +1,131 @@ +import React, { Component } from 'react'; +import ObjsService from './ObjsService'; +/** 输入参数 */ +let input = [ + { + id: '823585b8-062d-4e15-b17c-2d8932d4a1a4', + name: '张三', + age: '25', + balance: 1000, + gender: '男', + dept: '采购部', + post: '部门经理', + superior: '1bc06853-b0fd-4cbc-9d53-16c054b8ca9b', + }, + { + id: '39d2fd99-00f5-4996-9a3f-165148ab7972', + name: '李四', + age: 17, + balance: 400, + gender: '男', + dept: '技术部', + post: '管理培训生', + deptId: '2', + superior: '37f15bff-e0ee-4b44-9291-5c9fa0bcf954', + }, + { + id: '37f15bff-e0ee-4b44-9291-5c9fa0bcf954', + name: '王五', + age: 30, + balance: 500, + gender: '男', + dept: '技术部', + post: '部门经理', + superior: '1bc06853-b0fd-4cbc-9d53-16c054b8ca9b', + }, + { + id: '0f0479cc-59fe-4c56-9685-75c4067300b6', + name: '小红', + age: 16, + balance: 200, + gender: '女', + dept: '采购部', + post: '管理培训生', + superior: '823585b8-062d-4e15-b17c-2d8932d4a1a4', + }, + { + id: 'f01430fd-5df3-402a-92ef-aca63d882e2b', + name: '小明', + age: 24, + balance: 700, + gender: '男', + dept: '技术部', + post: '管理培训生', + superior: '37f15bff-e0ee-4b44-9291-5c9fa0bcf954', + }, +]; +export default class extends Component { + constructor() { + super(); + this.state = { + inputText: JSON.stringify(input, null, 2), + }; + this.demos = new ObjsService().getDemos(); + } + + run(testUnit, input) { + this.setState({ + execFunc: String(this.demos[testUnit]).replace(new RegExp('Obj(.)+?\\)'), 'Objs'), + }); + return this.demos[testUnit](input); + } + + execute(testUnit) { + let outputText; + try { + let input = JSON.parse(this.state.inputText); + let output = this.run(testUnit, input); + outputText = JSON.stringify(output, null, 2); + } catch (e) { + console.error(e); + outputText = '执行出错:\n' + e.message; + } + this.setState({ + outputText, + }); + } + + render() { + let { inputText, outputText } = { ...this.state }; + return ( + <> +
+
输入:
+