使用jsx封装复杂组件

avatar

使用 jsx 封装移动端级联组件

JSX

JSX,是一个 JavaScript 的语法扩展,通常在 React 中使用。
在 Vue 里使用的话需安装对应包:

1
npm i @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props

.babelrc 或者 bable.config.js 配置

1
2
3
{
"presets": ["@vue/babel-preset-jsx"]
}

语法

这里直接丢文档,里面有详细使用介绍

jsx 文档

实际项目代码

直接上代码:
Cascader 是父组件

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import { Button, Toast } from "vant";
import axios from "axios";
import qs from "qs";

import Item from "./item";

const treeToList = (function() {
return function(tree, result = [], level = 0, parentKey = "") {
tree.forEach(node => {
node.parentKey = parentKey;
result.push(node);
node.deepIndex = level + 1;
node.children &&
treeToList(node.children, result, level + 1, node["label"]);
});
return result;
};
})();

const mapTree = (function() {
return function(list) {
const result = {};
list.forEach(i => {
if (result[i.deepIndex]) return false;
result[i.deepIndex] = {
index: i.deepIndex - 1,
deepIndex: i.deepIndex,
value: "",
columns: list.filter(item => item.deepIndex === i.deepIndex)
};
});
return result;
};
})();

export default {
name: "Cascader",
props: {
value: { type: Array },
visible: { type: Boolean, default: false },
item: { type: Object }
},
computed: {
calculateValue: {
get() {
return this.value;
},
set(v) {
this.$emit("input", v);
}
},
stepItem() {
if (!Object.keys(this.pickerArr).length) return null;
return this.pickerArr[this.stepIndex + 1];
},
defaultSelectIndex() {
if (!this.calculateValue?.length) return 0;
if (!this.stepItem?.columns) return 0;
const current = this.calculateValue[this.stepIndex];
const defaultItem = this.stepItem.columns.findIndex(
i => i[this.pickTitle] === current
);
return defaultItem;
},
hasNext() {
if (!Object.keys(this.pickerArr).length) return false;
return !!this.pickerArr[this.stepIndex + 2]?.columns.length;
}
},
data() {
return {
// 原数据数组
resultArr: [],
stepIndex: 0,
pickTitle: "",
// 格式化后树结构
pickerArr: [],
// 选择过的数组
selectArr: [],
isClosed: false
};
},
created() {
this.init();
this.stepIndex = 0;
},
methods: {
// 过滤出当前层级下的所有数据
filterPick(arr) {
if (!arr) return [];
const currentPerent = this.selectArr[this.stepIndex - 1];
if (!currentPerent) return arr;
return arr.filter(item => {
if (item.parentKey === "") return true;
return item.parentKey == currentPerent;
});
},
async init() {
// 判断是使用后台配置的数据还是另使用请求接口的数据
const isRemote = !!this.item?.targetSource?.relationField;
// resultArr为判断后取到的树数据,按照树结构层级过滤成自定义的数据
const tree = treeToList(this.resultArr);
// 过滤成所需要的结构
this.pickerArr = mapTree(tree);
},
// 远程请求方法
fetch() {},
confirm(val, index) {
if (!this.hasNext) {
// this.selectArr[this.stepIndex] = val[this.pickTitle];
this.selectArr.push(val[this.pickTitle]);
this.calculateValue = this.selectArr;
this.$emit("update:visible", false);
this.$nextTick(() => {
this.selectArr = [];
});
return;
}
if (!this.stepItem.columns[index]?.children?.length) {
Toast.fail("当前选项没有下一级");
return;
}
this.selectArr.push(val[this.pickTitle]);
this.stepIndex++;
},
cancel() {
if (this.stepIndex === 0) return;
this.stepIndex--;
this.selectArr.pop();
},
btnCancel() {
this.$emit("update:visible", false);
}
},
render() {
const itemAttrs = {
attrs: {
value: this.visible,
position: "bottom",
getContainer: () => document.getElementById("chooseDom"),
"value-key": this.pickTitle,
closeOnClickOverlay: false,
confirmButtonText:
this.stepIndex >= 0 && this.hasNext ? "下一步" : "确定",
cancelButtonText: this.stepIndex === 0 ? " " : "上一步",
columns: this.filterPick(this.stepItem?.columns),
"default-index": this.defaultSelectIndex,
"close-on-popstate": true
},
on: {
confirm: this.confirm,
cancel: this.cancel
}
};
return (
<div id="cascaderContainer">
// 选中后回显以' / '隔开
<div>
{this.calculateValue
? this.calculateValue
.map(i => ` / ${i}`)
.join("")
.substr(3)
: ""}
</div>

<Item {...itemAttrs}>
<Button
slot="btn"
text={"取消"}
style={{ width: "100%" }}
onClick={this.btnCancel}
/>
</Item>
</div>
);
}
};

ItemCascader 的子元素

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
import { Popup, Picker } from "vant";

var getPropsAndAttrs = (function() {
return function(attrs, props) {
const propsObj = {};
for (const k in props) {
if (props.hasOwnProperty(k)) {
const { default: defaultValue } = props[k];
propsObj[k] = defaultValue;
}
}
Object.keys(attrs).forEach(key => {
const attrKey = getCamelCase(key);
if (!propsObj.hasOwnProperty(attrKey)) return;
const attrsVal = attrs[key] === "" ? true : attrs[key];
delete propsObj[attrKey];
propsObj[attrKey] = attrsVal;
});
return { props: propsObj };
};
})();

// 兼容驼峰入参
var getCamelCase = (function() {
return function(str) {
return str.replace(/-([a-z])/g, function(all, i) {
return i.toUpperCase();
});
};
})();

export default {
name: "CascaderItem",
render() {
const ctx = this;
const { props: PopupDefaultProps } = getPropsAndAttrs(
this.$attrs,
Popup.props
);
const { props: pickerDefaultProps } = getPropsAndAttrs(
this.$attrs,
Picker.props
);
const popupAttr = {
props: {
...PopupDefaultProps,
...this.$attrs,
value: this.$attrs.value
},
on: {
input: e => {
this.$emit("update:visible", e);
}
},
style: {
display: "flex",
flexDirection: "column",
background: "#f7f8fa"
}
};
const pickerAttr = {
props: {
...pickerDefaultProps,
showToolbar: true
},
on: {
...ctx.$listeners
}
};
return (
<div>
<Popup {...popupAttr}>
<Picker {...pickerAttr} />
{this.$slots.btn}
</Popup>
</div>
);
}
};

结语

pc 端配置数据:
avatar

移动端展示效果:
avatar