Antd React Tree控件自定义展示效果

最近在用React做项目,使用到antd作为ui,发现官网一些案例说明不是很详细

1
2
3
4
5
6
const data = [
name:'test',
children:[
name:'child'
]
]

有一个这样的数据格式,与官网案例不同的是需要显示的字段为name,官网为title,而且官网的字段中包含key

文档中说明可以使用 TreeNode 自定义显示,但又没有案例说明,下面是一个简单实现Tree自定义显示的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Tree } from "antd";
//引入Tree控件
import React from "react";
const { TreeNode } = Tree;//引入TreeNode控件

class TreeDemo extends React.Component<any, any> {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(props: any) {
super(props);
this.state = {
menu: [
name:'test',
children:[
name:'child'
]
]
};
}
//递归处理数据
loop = (arr: Array<object>) => {
return arr.map((item: any) => {
return (
//测试数据中key为name字段,实际开发中应当使用id等不重复字段
<TreeNode title={<span>{item.name}</span>} key={item.name}>
{item.children && item.children.length > 0
? this.loop(item.children)
: null}
</TreeNode>
);
});
};
render() {
return (
<div>
<Tree>{this.loop(this.state.menu)}</Tree>
</div>
);
}
}

以上方式为antd 3x 至 antd 4x的方式,在4x中不推荐,Tree控件的children即将被废弃,推荐使用titleRender方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Tree } from "antd";
//引入Tree控件
import React from "react";

class TreeDemo extends React.Component<any, any> {
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(props: any) {
super(props);
this.state = {
menu: [
name:'test',
children:[
name:'child'
]
]
};
}
//递归处理数据
loop = (arr: Array<object>): any => {
return arr.map((item: any) => {
item.key = item.id;
return item.children && item.children.length > 0
? { ...item, children: this.loop(item.children) }
: item;
});
};
render() {
return (
<div>
<Tree
titleRender={(nodeData: any) => {
return `${nodeData.name}`;
}}
treeData={this.loop(this.state.menu)}
></Tree>
</div>
);
}
}

虽然此方式可以自定义渲染title,但数据源中不存在key值时依旧会提示key undefind。因此推荐递归处理数据后渲染

JS实现手机号隐藏中间4位

使用字符串分割的方式实现

1
2
3
4
let str = '12345678922';
let pre = str.substr(0,3);
let next = str.substr(7,4);
let result = `${pre}****${next}`;

使用数组方法实现

1
2
3
4
5
6
let tel = '12345678922';
tel = "" + tel;
var ary = tel.split("");
ary.splice(3,4,"****");
let tel1 = ary.join("");
//tel1为操作后的手机号

使用字符串替换方法

1
2
3
4
let tel = '12345678922';
tel = "" + tel;
var tel1 =tel.replace(tel.substring(3,7), "****")
console.log(tel1);

使用正则方法

1
2
3
4
5
let tel = '12345678922';
tel = "" + tel;
var reg=/(\d{3})\d{4}(\d{4})/;
var tel1 = tel.replace(reg, "$1****$2")
console.log(tel1);

js实现base64加密

解密

1
2
3
4
5
let b64EncodeUnicode = (str) => {//解密
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode('0x' + p1);
}))
}

加密

1
2
3
4
5
let b64DecodeUnicode = (str) => {//加密
return decodeURIComponent(atob(str).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}

vue中使用vue-quill-editor

rich-text-editor.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div class="editor-container">
<quill-editor
v-model="content"
ref="richTextEditor"
:options="option"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@change="onEditorChange($event)"
@ready="onEditorReady($event)"
></quill-editor>
<div class="word-limit" v-if="showWordLimit">
<span v-if="currentWords>0">大约还可以输入{{ currentWords }}个字</span>
<span v-else>已达到最大字数限制</span></div>
</div>
</template>

<script>
import controller from "./controller/editor.controller";
import "./style/edit.style.scss";

export default controller;
</script>

<style scoped>
</style>

editor.controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/** 引入样式*/
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
/** 引入编辑器组件*/
import {quillEditor} from "vue-quill-editor/src";
import {addQuillTitle} from "../language/zh-CN";
import * as Quill from 'quill';

let fontSizeStyle = Quill.import('attributors/style/size')
fontSizeStyle.whitelist = ['12px', false, '16px', '18px', '20px', '22px', '24px']
Quill.register(fontSizeStyle, true)

export default {
name: "rich-text-editor",
props: {
totalWords: {
type: Number,
default: 10000
},
content: {
type: String,
default: ''
},
showWordLimit: {
type: Boolean,
default: true
}
},
data() {
return {
content: "",
currentWords: 0,
option: {
theme: 'snow',
placeholder: '编辑文章内容',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'], //加粗,斜体,下划线,删除线
['blockquote', 'code-block'], //引用,代码块
[{'header': 1}, {'header': 2}], // 标题,键值对的形式;1、2表示字体大小
[{'list': 'ordered'}, {'list': 'bullet'}], //列表
[{'script': 'sub'}, {'script': 'super'}], // 上下标
[{'indent': '-1'}, {'indent': '+1'}], // 缩进
[{'direction': 'rtl'}], // 文本方向
[{'size': fontSizeStyle.whitelist}], // 字体大小
[{'header': [1, 2, 3, 4, 5, 6, false]}], //几级标题
[{'color': []}, {'background': []}], // 字体颜色,字体背景颜色
[{'align': [false, 'center', 'right']}], //对齐方式
['clean'], //清除字体样式
// ['image'], //上传图片、上传视频
]
}
}
}
},
components: {quillEditor},
mounted() {
addQuillTitle()
},
methods: {
/** 编辑器准备完成事件*/
onEditorReady(data) {
this.wordLimit(data, 'ready');
// console.log('editor ready', data)
},
/** 失去焦点事件*/
onEditorBlur(data) {
// console.log('editor blur', data)
},
/** 内容焦点事件*/
onEditorFocus(data) {
// console.log('editor focus', data)
},
/** 内容改变事件*/
onEditorChange(data) {
this.wordLimit(data, 'change');
this.$emit('editChange', this.content)
// console.log('editor change', data)
},
/** 内容转码*/
escapeStringHTML(str) {
str = str.replace(/&lt;/g, '<');
str = str.replace(/&gt;/g, '>');
return str;
},
/** 显示剩余字数*/
wordLimit(data, mode) {
/** mode:ready编辑器准备完毕,change编辑器内容更改*/
let opt = mode === 'ready' ? data : data.quill;
let content = this.content opt.text;//文本内容
let total = this.totalWords + 2;//总字数
opt.deleteText(total - 2, 4);
if (content.length === 0) {
this.currentWords = total;
} else {
this.currentWords = total - opt.getLength() - 1;
}
}
},
computed: {
editor() {
return this.$refs.richTextEditor.quill;
},
}

}

zh-CN.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/** 增加提示文字*/
const titleConfig = {
'ql-bold': '加粗',
'ql-color': '颜色',
'ql-font': '字体',
'ql-code': '插入代码',
'ql-italic': '斜体',
'ql-link': '添加链接',
'ql-background': '背景颜色',
'ql-size': '字体大小',
'ql-strike': '删除线',
'ql-script': '上标/下标',
'ql-underline': '下划线',
'ql-blockquote': '引用',
'ql-header': '标题',
'ql-indent': '缩进',
'ql-list': '列表',
'ql-align': '文本对齐',
'ql-direction': '文本方向',
'ql-code-block': '代码块',
'ql-formula': '公式',
'ql-image': '图片',
'ql-video': '视频',
'ql-clean': '清除字体样式'
}

export const addQuillTitle = () => {
const oToolBar = document.querySelector('.ql-toolbar'),
aButton = oToolBar.querySelectorAll('button'),
aSelect = oToolBar.querySelectorAll('select')
aButton.forEach(function (item) {
if (item.className === 'ql-script') {
item.value === 'sub' ? item.title = '下标' : item.title = '上标'
} else if (item.className === 'ql-indent') {
item.value === '+1' ? item.title = '向右缩进' : item.title = '向左缩进'
} else {
item.title = titleConfig[item.classList[0]]
}
})
aSelect.forEach(function (item) {
item.parentNode.title = titleConfig[item.classList[0]]
})
}

edit.style.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
p {
margin: 10px;
}

.editor-container {
height: 370px;
position: relative;

.word-limit {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 20px;
line-height: 20px;
border: 1px solid #ebedf0;
background: #ffffff;
padding: 0 10px;
text-align: right;
color: #aaaaaa;
}
}

.quill-editor {
background-color: #f7f8fa;
height: 300px;

.ql-toolbar {
border: 1px solid #ebedf0;

.ql-formats {
margin-right: 0;
}

.ql-picker.ql-size {

.ql-picker-label, .ql-picker-item {
&::before {
content: "14px";
}
}

@for $i from 1 through 8 {
.ql-picker-label[data-value="#{10+$i*2}px"], .ql-picker-item[data-value="#{10+$i*2}px"] {
&::before {
content: "#{10+$i*2}px";
}
}
.ql-picker-item[data-value="#{10+$i*2}px"] {
&::before {
font-size: #{10+$i*2}px;
}
}
}
}

.ql-picker.ql-header {
.ql-picker-label, .ql-picker-item {
&::before {
content: "段落";
}
}

@for $i from 1 through 6 {
.ql-picker-label[data-value="#{$i}"], .ql-picker-item[data-value="#{$i}"] {
&::before {
content: "标题" "#{$i}";
}
}
}
}

.ql-picker.ql-font {
.ql-picker-label, .ql-picker-item {
&::before {
content: "标准字体";
}
}

.ql-picker-label[data-value="serif"], .ql-picker-item[data-value="serif"] {
&::before {
content: "衬线字体";
}
}

.ql-picker-label[data-value="monospace"], .ql-picker-item[data-value="monospace"] {
&::before {
content: "等宽字体";
}
}
}

.ql-picker.ql-size, .ql-picker.ql-header, .ql-picker.ql-font {
width: 60px;
}
}

.ql-color-picker {
.ql-picker-options {
width: 133px;
}
}

.ql-container {
border: 1px solid #ebedf0;
background: #ffffff;
padding-bottom: 20px;
}
}

tsconfig提示aliased imports are not supported解决方案

问题详情:react-ts项目中配置路径提示不支持别名导入

compilerOptions. paths must not be set (aliased imports are not supported)

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//新建tsconfig.paths.json文件

//在该文件中写入路径配置
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@@/*": ["./*"],
"@/*": ["src/*"],
"@data": ["src/data/*"]
}
}
}

//将此文件引入tsconfig.json文件
{
...
"extends": "./tsconfig.paths.json"
}

解决方案来源:

https://github.com/facebook/create-react-app/issues/5118

React笔记五:使用state

1、新建组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React, { useState } from 'react';
import VelButton from '../velButton';
import styles from './styles.module.scss';

export default function Gradient() {
const [color1, setColor1] = useState('#00f260');//使用useState绑定事件
const [color2, setColor2] = useState('#0575e6');
function handleInputChange(e: any) {
const { name, value } = e.target;
if (Object.is(name, 'color1')) {
setColor1(value);
}
if (Object.is(name, 'color2')) {
setColor2(value);
}
}
function handleClick() {
setColor1('#00f260');
setColor2('#0575e6');
}
return (
<div className={styles.container} style={{ background: `linear-gradient(75deg,${color1},${color2})` }}>
<div className={styles.inputGroup}>
<label htmlFor="">请选择第一个颜色</label>
<input
type="color"
name="color1"
value={color1}
id=""
className={styles.inputColor}
onChange={handleInputChange} />
</div>
<div className={styles.inputGroup}>
<label htmlFor="">请选择第二个颜色</label>
<input
type="color"
name="color2"
value={color2}
id=""
className={styles.inputColor}
onChange={handleInputChange} />
</div>
<div>
<VelButton type="primary" click={handleClick}>重置为默认</VelButton>
</div>
</div>
)
}

2、使用组件

1
<Gradient></Gradient>

React笔记四:使用props

1、之前的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//index.tsx文件
import React from 'react';
import styles from './styles.module.scss';

function VelButton(props) {//接收props
return (
<button className={classnames(
styles.button,{
[styles['primary']]:props.type==='primary'//动态绑定样式
}
)}>{props.children}<button>
)
}

export default VelButton;

//styles.module.scss文件
.primary{
background-color: #409eff;
}

2、使用组件

1
2
3
4
5
6
7
8
9
10
11
//app.tsx文件
import VelButton from './components/button';

function App() {
return(
.....
.....
<VelButton type='primary'>新的按钮</VelButton>
.....
)
}

React笔记三:创建第一个组件

1、src文件夹下新建一个components文件夹

2、在components文件夹中新建一个button文件夹

3、在button文件夹中新建index.tsx文件和styles.module.scss文件

4、编写第一个组件

1
2
3
4
5
6
7
8
9
10
11
//index.tsx文件
import React from 'react';
import styles from './styles.module.scss';

function VelButton() {
return (
<button>默认按钮<button>
)
}

export default VelButton;

5、使用组件

1
2
3
4
5
6
7
8
9
10
11
//app.tsx文件
import VelButton from './components/button';

function App() {
return(
.....
.....
<VelButton></VelButton>
......
)
}

6、添加样式

1
2
3
4
//styles.module.scss
.button{
padding: 10px 20px;
}

7、安装classnames模块

1
npm install classnames -s

8、引入样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//index.tsx文件
import React from 'react';
import styles from './styles.module.scss';
import classnames from 'classnames';

function VelButton() {
return (
<button className={classnames(
styles.button
)}>默认按钮<button>
)
}

export default VelButton;