Browse Source

update

master
zsmj 2 years ago
parent
commit
a70cfdd3ae
  1. 2
      README.md
  2. 17
      docs/.vuepress/config.js
  3. 15
      docs/README.md
  4. 1
      docs/guide/1.md
  5. 6
      docs/guide/README.md
  6. 6
      docs/guide/questions/0.前端工作进阶需要哪些必备技能.md
  7. 19
      docs/guide/questions/1.如何获取当前时间.md
  8. 83
      docs/guide/questions/10.BI.config的执行顺序是什么,为什么这么设计.md
  9. 80
      docs/guide/questions/11.在props中处理生命周期函数.md
  10. 7
      docs/guide/questions/15.如何记录子路由信息并重定向.md
  11. 1
      docs/guide/questions/2.如何格式化输出日期.md
  12. 174
      docs/guide/questions/2.组件的代码设计基本思路.md
  13. 1
      docs/guide/questions/20.如何监听元素大小变化.md
  14. 219
      docs/guide/questions/21.computed进行列表组件的状态控制.md
  15. 87
      docs/guide/questions/28.几个常用的布局场景.md
  16. 40
      docs/guide/questions/3.为什么传递时间信息时候推荐使用时间戳.md
  17. 97
      docs/guide/questions/3.关于组件引用的奥秘,ref知多少.md
  18. 115
      docs/guide/questions/4.使用ButtonGroup控制可点击组件的控制选中状态.md
  19. 58
      docs/guide/questions/4.高阶组件的render-props.md
  20. 51
      docs/guide/questions/40.我们为什么要设计el这个属性.md
  21. 184
      docs/guide/questions/41.绝对布局的隐藏知识点.md
  22. 0
      docs/guide/questions/42.何时会滚动,滚动条位置如何贴边,究竟是怎么回事.md
  23. 167
      docs/guide/questions/43.利用响应式编写组件代码的新思路.md
  24. 150
      docs/guide/questions/44.如何提供异步配置的接口.md
  25. 21
      docs/guide/questions/50.前端如何正确书写资源路径.md
  26. 55
      docs/guide/questions/55.Fix中对于对象属性的监听,为什么要先定义属性才可以正常watch,如何解决.md
  27. 7
      docs/guide/questions/55.watch使用的常见误区.md
  28. 245
      docs/guide/questions/80.combo的一些特性详解.md
  29. 86
      docs/guide/questions/9.BI.config都可以做那些事情.md
  30. BIN
      images/34.png
  31. BIN
      images/35.png
  32. BIN
      images/38.gif
  33. BIN
      images/39.gif
  34. 58
      questions/28.几个常用的布局场景.md
  35. 61
      questions/29.BI.Layers.create参数详解原理.md

2
README.md

@ -49,4 +49,4 @@ FineUI 100个问题题,带你走进FineUI的世界
- [26、处理树结构的常用算法]()
- [27、组件生命周期与Model状态控制](https://code.fineres.com/projects/BUSSINESS/repos/nuclear-webui/pull-requests/9193/diff/#packages/bi-webui/src/modules/conf/pack/analysis/transfer/operator/dimension/combo/combo.tsx)
- [28、几个常用的布局场景]()
- [29、BI.Layers.create参数详解原理]()
- [29、BI.Layers.create参数详解原理](https://code.fanruan.com/dailer/FineUI-100-Questions/src/branch/master/questions/29.BI.Layers.create参数详解原理.md)

17
docs/.vuepress/config.js

@ -0,0 +1,17 @@
module.exports = {
title: 'Hello VuePress',
description: 'Just playing around',
themeConfig: {
sidebar:{
"/guide/":[
"/guide/",
{
title: 'Guide',
children:[
"/guide/1",
]
}
]
}
}
}

15
docs/README.md

@ -1 +1,14 @@
# Hello VuePress
---
home: true
heroImage: /logo.jpg
actionText: 快速上手 →
actionLink: /guide/
features:
- title: 简洁至上
details: 以 Markdown 为中心的项目结构,以最少的配置帮助你专注于写作。
- title: Vue驱动
details: 享受 Vue + webpack 的开发体验,在 Markdown 中使用 Vue 组件,同时可以使用 Vue 来开发自定义主题。
- title: 高性能
details: VuePress 为每个页面预渲染生成静态的 HTML,同时在页面被加载的时候,将作为 SPA 运行。
footer: MIT Licensed | Copyright © 2018-present Evan You
---

1
docs/guide/1.md

@ -0,0 +1 @@
# helloooo

6
docs/guide/README.md

@ -0,0 +1,6 @@
# index
## index1
## index2

6
docs/guide/questions/0.前端工作进阶需要哪些必备技能.md

@ -0,0 +1,6 @@
# 0.前端工作需要哪些必备技能
## 1.熟悉各种链工具的使用,包括但不限于webpack,rollup,less,babel,代理配置等
## 2.了解前后端分离开发模式,熟悉对接流程
## 3.了解持续集成工具teamcity的基本流程
## 4.掌握icon-font原理,知晓图标使用方式

19
docs/guide/questions/1.如何获取当前时间.md

@ -0,0 +1,19 @@
# FineUI中如何获取当前时间
## 错误写法
```
const date = new Date();
const time = date.getTime();
```
## 正确写法
```
const time = BI.getTime();
```
## 答案解析
首先思考一个场景: 某跨国公司系统管理员人在北京,在系统上配置数据更新时间晚上七点半。可是等到过了七点半,数据还是没有更新。这是为什么呢?原来服务器在伦敦,伦敦时间比北京时间慢7个小时,还没到更新时间呢
FineUI中BI.getTime方法专门这种场景做了处理,依据服务器时区进行偏移,正确的获取当前时间。

83
docs/guide/questions/10.BI.config的执行顺序是什么,为什么这么设计.md

@ -0,0 +1,83 @@
# BI.config的执行顺序是什么,为什么这么设计.md
BI.config配置的方法除了`bi.provider.system`直接执行之外,全都是在获取资源的时候执行
对于旧版本的FineUI, config配置的函数是在`BI.init()`中执行的,`BI.init()`又是在什么时候调用的呢,是在第一次`BI.createWidget()`时候调用的.
这样带来了一个弊端,如果`BI.init()`先被调用了,那么后续`BI.config()`的内容将不再执行
例如如下代码,javascript文件加载顺序为abcd,在旧有设计中,d.js获取的items长度是几?答案是1,因为在b.js中调用了`BI.Msg.toast`方法,而这个方法一定会间接的调用到`BI.init()`,这是不可控的,因为我们无法控制开发者要怎么写代码.很多时候出了bug,但是面对茫茫多的插件,完全不知道是由哪个文件引发的.
```
// a.js
BI.constant("bi.constant.items", [
{
id: 1,
text: 1,
},
]);
// b.js
BI.Msg.toast("123");
// c.js
BI.config("bi.constant.items", items => {
items.push({
id: 2,
text: 2,
});
return items;
});
// d.js
var items = BI.Constants.getConstant("bi.constant.items"); // 有几个?
```
BI.config注册的方法结构是什么样呢?
```
interface configFunctions {
[key: string]: Function[];
}
```
`BI.config`的配置函数是如何执行的呢?
对于每一个资源,都有一个配置函数队列,在每次获取资源的时候执行队列中的配置方法,顺序执行然后清空队列,如果之后再调用`BI.config`配置当前资源,重新讲配置函数加入队列中
```
BI.constant("bi.constant.items", [
{
id: 1,
text: 1,
},
]);
// 此时资源bi.constant.items的配置函数队列中只有一个配置方法等待执行
BI.config("bi.constant.items", items => {
items.push({
id: 2,
text: 2,
});
return items;
});
// 获取资源的是执行并清空了队列
var items = BI.Constants.getConstant("bi.constant.items"); // 2个
// 又给资源bi.constant.items的配置函数队列中增加了一个配置方法等待执行
BI.config("bi.constant.items", items => {
items.push({
id: 3,
text: 3,
});
return items;
});
// 获取资源的是又执行并清空了队列
items = BI.Constants.getConstant("bi.constant.items"); // 3个
```
这样设计有什么好处呢?
首先在获取资源时候才执行配置函数,完全避免了js加载顺序所带来的隐患,其次使配置可以无需立即执行并且可以多次配置.

80
docs/guide/questions/11.在props中处理生命周期函数.md

@ -0,0 +1,80 @@
# 在props中处理生命周期函数
在[组件生命周期](http://fanruan.design/doc.html?post=244ba71889)一文中,详细解释了组件中各个生命周期钩子的执行时机.那么是不是我们想要使用生命周期钩子,就一定要写一个组件类呢?答案是否定的.
类似于[9、高阶组件的render-props](https://code.fanruan.com/dailer/FineUI-100-Questions/src/branch/master/questions/4.高阶组件的render-props.md)文中所讲,render可以通过props指定,那么beforeRender,mounted等其他生命周期函数可否通过props控制呢? 如果和类组件中方法产生冲突,又是如何处理的呢?
FineUI中有两种处理方式直接覆盖和一并执行
直接覆盖的有: beforeInit,beforeRender,render. 如果在props中指定了相应生命周期钩子,则组件类内的方法不会执行
一并执行的有: beforeCreate,created,beforeMount,mounted,beforeDestroy,destroyed,beforeUpdate,updated,如果在props中指定了相应生命周期钩子,则按照先执行类方法,再执行props中方法的顺序依次执行
如下示例,最终的输出会是什么呢?
```demo
const logs = [];
const Menus = BI.inherit(BI.Widget, {
beforeRender: function () {
logs.push("内部的beforeRender");
return Promise.resolve();
},
render: function () {
logs.push("内部的render");
return {
type: "bi.button",
text: "内部的render",
};
},
beforeMount: function () {
logs.push("内部的beforeMount");
},
mounted: function () {
logs.push("内部的beforeMount");
},
setText: function (text) {
this.button.setText(text);
},
});
BI.shortcut("demo.menus", Menus);
const Widget10 = BI.createWidget({
type: "bi.vertical",
items: [
{
el: {
type: "demo.menus",
beforeRender: () => {
logs.push("外面的beforeRender");
return Promise.resolve();
},
render: function () {
logs.push("外面的render");
return {
type: "bi.label",
ref: (_ref) => {
this.button = _ref;
},
text: "外面的render",
whiteSpace: "normal",
};
},
beforeMount: () => {
logs.push("外面的beforeMount");
},
mounted: function () {
logs.push("外面的mounted");
this.setText(logs.join("\n"));
},
},
},
],
});
```

7
docs/guide/questions/15.如何记录子路由信息并重定向.md

@ -0,0 +1,7 @@
# 如何记录子路由信息并重定向
## 业务场景
BI6.0公共数据的路由为`analysis`,有若干子路由,如`analysis/table/:id`
当由`analysis`跳转到`management`,再跳转回来时,这时候进入的是`analysis`根,而不是上一次的子路由,如何解决呢

1
docs/guide/questions/2.如何格式化输出日期.md

@ -0,0 +1 @@
# FineUI中如何格式化输出日期

174
docs/guide/questions/2.组件的代码设计基本思路.md

@ -0,0 +1,174 @@
# 组件的代码设计基本思路
我们以一个分页组件为例,阐述一下FineUI中组件代码的基本思路
## 控件功能
上一页,下一页,带个输入框,有总页数.四个按钮带disabled逻辑,无上一页或者下一页时候灰化
![示例](../../../images/9.png)
## 第一步: 明确对外提供的props
总页数,当前页数 就这两个属性够了
```javascript
const props = {
total: 200,
page: 10,
}
```
## 第二步: 明确要对外提供哪些方法
一个getValue方法加一个populate就行了
```javascript
// 获取当前页码
const page = this.page.getValue()
// 设置总页数和页码
this.pager.attr("total", 100)
this.pager.attr("page", 1)
this.pager.populate()
```
## 第三步: 明确对外提供哪些事件
事件只需要一个就够了,通知外部使用者当前page,养成良好习惯,事件尽量带上参数
```javascript
this.fireEvent("EVENT_CHANGE", page)
```
## 生命周期
万物皆render,能render一遍就执行好的,尽量别用其他生命周期,mounted之类的,避免没必要的回流重绘 如果组件是异步的, beforeInit/beforeRender.那么你在render中一定可以拿到所有的状态了,就没必要再通过一次watch来实现效果.当然,有些事情必须在mounted中做(例如获取组件宽高)那就另说了.
## 抛砖引玉
比如这个pager组件按钮的灰化,我就借用最新的响应式写个例子 [自动相应变更](http://fanruan.design/doc.html?post=afe4c84120)
```demo
const Pager = BI.inherit(BI.Widget, {
props: {
total: 1,
page: 1,
},
init: function () {
this.state = Fix.define({
currentPage: this.options.page,
total: this.options.total,
});
},
render: function () {
return {
type: "bi.vertical_adapt",
columnSize: [24, 24, "fill", "", 24, 24],
hgap: 5,
items: [
{
type: "bi.icon_button",
cls: "pre-page-h-font",
disabled: () => {
return this.state.currentPage === 1;
},
handler: () => {
this.state.currentPage = 1;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.icon_button",
cls: "pre-page-h-font",
disabled: () => {
return this.state.currentPage === 1;
},
handler: () => {
this.state.currentPage--;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.text_editor",
ref: ref => this.editor = ref,
value: () => this.state.currentPage,
validationChecker: (v) => (BI.parseInt(v) >= 1) && (BI.parseInt(v) <= this.state.total),
errorText: "error",
listeners: [
{
eventName: "EVENT_CHANGE",
action: () => {
this.state.currentPage = BI.parseInt(this.editor.getValue());
this.fireEvent("EVENT_CHANGE", this.getValue());
},
},
],
}, {
type: "bi.label",
text: () => "/" + this.state.total,
}, {
type: "bi.icon_button",
cls: "next-page-h-font",
disabled: () => {
return this.state.currentPage === this.state.total;
},
handler: () => {
this.state.currentPage++;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.icon_button",
cls: "next-page-h-font",
disabled: () => {
return this.state.currentPage === this.state.total;
},
handler: () => {
this.state.currentPage = this.state.total;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
},
],
};
},
getValue: function () {
return this.state.currentPage;
},
populate: function () {
this.state.currentPage = this.options.page;
this.state.total = this.options.total;
},
});
BI.shortcut("pager", Pager);
let pager;
BI.createWidget({
type: "bi.vertical",
vgap: 20,
items: [
{
type: "pager",
ref: ref => pager = ref,
height: 24,
width: 250,
total: 100,
page: 2,
}, {
type: "bi.button",
text: "populate",
handler: () => {
pager.attr("total", 500);
pager.attr("page", 400);
pager.populate();
},
},
],
});
```

1
docs/guide/questions/20.如何监听元素大小变化.md

@ -0,0 +1 @@
# 如何监听元素大小变化

219
docs/guide/questions/21.computed进行列表组件的状态控制.md

@ -0,0 +1,219 @@
# computed进行列表组件的状态控制
开发中我们经常使用computed计算属性控制状态,如列表刷新,列表选中等.
正确理解computed原理并灵活使用
看如下示例,把选中状态和列表节点放入同一个computed属性中计算,会导致什么问题呢?
```js
computed: {
listItems: function () {
return this.model.items.map(item => {
return {
...item,
type: "bi.text_item",
cls: "bi-list-item-active2",
selected: item.value === this.model.selectedValue,
};
});
},
}
```
下面书写了一个简单的demo,可以看到,每次改变selectedValue状态,都会触发列表的重新渲染.当列表节点过多,浏览器性能较差的时候,会出现明显的闪烁或者卡顿.
究其原因,是因为listItems属性的计算同时依赖items和selectedValue属性,每当selectedValue属性改变都会触发listItems的重新计算,返回值是一个新的数据,因此触发watch,重新populate
```js
let listModel = BI.inherit(Fix.Model, {
state: function () {
return {
selectedValue: 1,
items: [
{
text: 1,
value: 1,
}, {
text: 2,
value: 2,
}, {
text: 3,
value: 3,
}, {
text: 4,
value: 4,
},
],
};
},
computed: {
listItems: function () {
return this.model.items.map(item => {
return {
...item,
type: "bi.text_item",
cls: "bi-list-item-active2",
selected: item.value === this.model.selectedValue,
};
});
},
},
actions: {
changeSelectedValue: function (value) {
this.model.selectedValue = value;
},
addItem: function () {
this.model.items.push({
text: this.model.items.length + 1,
value: this.model.items.length + 1,
});
},
},
});
BI.model("list", listModel);
let list = BI.inherit(BI.Widget, {
_store: function () {
return BI.Models.getModel("list");
},
watch: {
listItems: function (items) {
BI.Msg.toast("populate");
this.list.populate(items);
},
},
render: function () {
return {
type: "bi.vertical",
vgap: 15,
width: 300,
items: [
{
type: "bi.button_group",
ref: ref => this.list = ref,
layouts: [
{
type: "bi.vertical",
},
],
items: this.model.listItems,
listeners: [
{
eventName: "EVENT_CHANGE",
action: (value) => {
this.store.changeSelectedValue(value);
},
},
],
}, {
type: "bi.button",
text: "选中下一个节点",
handler: () => {
this.store.changeSelectedValue(this.model.selectedValue + 1);
},
}, {
type: "bi.button",
text: "添加一个节点",
handler: () => {
this.store.addItem();
},
},
],
};
},
});
BI.shortcut("list", list);
const ListWidget = BI.createWidget({
type: "bi.vertical",
items: [
{
type: "list",
},
],
});
return ListWidget;
```
![示例](../../../images/20.gif)
要如何避免这类问题呢?其实就一句话,将选中状态与列表节点状态分离开.在view层进行控件层面的属性控制
例如,computed属性中不再控制选中状态,改为单独watch选中状态,调用组件的setValue api 设置选中状态,此时两个状态分离,仅改变选中状态不会触发列表的重新渲染
```js
computed: {
listItems: function () {
return this.model.items.map(item => {
return {
...item,
type: "bi.text_item",
cls: "bi-list-item-active2",
// selected: item.value === this.model.selectedValue,
};
});
},
}
watch: {
selectedValue: function (value) {
this.list.setValue(value);
},
listItems: function (items) {
BI.Msg.toast("populate");
this.list.populate(items);
},
},
```
为了列表重新渲染之后依旧保持选中值,一般有两种处理方式
1. populate之后调用 setValue
2. 在populate之前进行一次遍历,设置selected属性
```js
watch: {
listItems: function (items) {
this.list.populate(items);
this.list.setValue(this.model.selectedValue);
},
},
```
```js
watch: {
listItems: function (items) {
this.list.populate(items.map(item => ({ ...item, selected: item.value === this.model.selectedValue })));
},
},
```
利用响应式新特性,可以更加逻辑清晰的进行选中和列表渲染控制
不过响应式并不能解决populate之后状态保持问题,因此需要在populate之前额外设置selected属性
```js
{
type: "bi.button_group",
ref: ref => this.list = ref,
layouts: [
{
type: "bi.vertical",
},
],
items: () => this.model.listItems,
value: () => this.model.selectedValue,
},
```

87
docs/guide/questions/28.几个常用的布局场景.md

@ -0,0 +1,87 @@
# 几个常用的布局场景
## 1.上对齐的水平布局如何对齐?
举一个反面例子
![示例1](../../../images/26.png)
![示例2](../../../images/27.png)
缓存配置页面,一个水平方向布局,左侧是配置项名称,右侧是具体的配置细节. 可以很明显的看出,左右两段的水平并没有对齐
如何通过布局来实现齐平呢
一般情况下,只需要让左右两侧的元素保持相同高度即可
```js
const widget = {
type: "bi.horizontal",
verticalAlign: "top",
columnSize: [100, ""],
items: [
{
type: "bi.label",
height: 36,
text: "全局缓存策略",
}, {
type: "bi.vertical",
items: [
{
type: "bi.single_select_radio_item",
height: 36,
text: "radio1",
}, {
type: "bi.single_select_radio_item",
height: 36,
text: "radio2",
},
],
},
],
};
```
![示例3](../../../images/28.png)
如果是文本段落需要对齐的场景,对文本段落设置相应的行高即可.对于FineUI中label组件来说即为`textHeight`属性. FineUI中toast组件的布局方式就是如此,通过控制`textHeight`
使得icon与文字对齐
```js
const widget = {
type: "bi.horizontal",
width: 300,
scrollx: false,
verticalAlign: "top",
columnSize: [100, "fill"],
items: [
{
type: "bi.label",
height: 36,
text: "全局缓存策略",
}, {
type: "bi.vertical",
items: [
{
type: "bi.label",
whiteSpace: "normal",
textHeight: 36,
text: "S2 定位是一个轻量级的表格渲染核心,所以没有 Excel/SpreadJS 那样复杂的功能和数据模型。在大数据表格组件这个场景下,其实不需要那么重的数据模型,S2 的轻量级设计是更合适的。在性能表现上,10 个Tab 切换时,切换耗时为 80ms,是老方案的 6倍。在 5 万条数据的情况下,S2 渲染和初始化只需要 1秒。是老方案的 8 倍。\n",
},
],
},
],
};
```
![示例4](../../../images/30.png)
## 2. 高度自适应的tab如何使用
## 3. 让滚动条贴附在容器边缘的技巧
日常业务中会出现滚动条,如果没有贴边显示的话,视觉效果会大打折扣.直接原因是一层又一层的布局嵌套,使得当前滚动的容器自身就已经脱离了页面区域.
如何在不借助`padding`的情况下,让滚动条贴附在容器边缘呢?
![示例5](../../../images/31.png)
![示例6](../../../images/32.png)

40
docs/guide/questions/3.为什么传递时间信息时候推荐使用时间戳.md

@ -0,0 +1,40 @@
# 为什么传递时间信息时候推荐使用时间戳
前后端传递时间信息的时候,推荐使用标准时间戳,即自1970-1-1 00:00:00 UTC(世界标准时间)至今所经过的毫秒数。为什么?
## 错误写法
```
// 前端向后端传递时间
const time = "2022-04-19 10:56:05"
// 前端获取到后端传递的时间信息进行解析
const timeStr = "2022-04-19 10:56:05"
const time = BI.getDate(timeStr).getTime() // 期望输出:1650336965000
console.log(`距离现在经过了${(BI.getTime() - time) / 1000}秒`)
```
## 正确写法
```
// 前端向后端传递时间
const time = BI.getTime()
// 前端获取到后端传递的时间信息进行解析
const time = 1650336965000 // 直接接收时间戳
console.log(`距离现在经过了${(BI.getTime() - time) / 1000}秒`)
```
## 答案解析
在[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date)中关于Date对象的构造函数描述中,特别强调了**由于浏览器之间的差异与不一致性,强烈不推荐使用Date构造函数来解析日期字符串**,这是为什么呢,如果把上面的错误示例放到IE浏览器中执行
```
const timeStr = "2022-04-19 10:56:05"
const time = BI.getDate(timeStr).getTime() // 会收到报错:Invalid DateDate
```
如果一定要解析时间字符串,FineUI中提供了`BI.parseDateTime`方法,仅适用于处理一些用户手动输入的时间格式字符串场景
```
const timeStr = "2022-04-19 10:56:05"
const time = BI.getDate(BI.parseDateTime(timeStr,"%Y-%X-%d %H:%M:%S")).getTime() // 期望输出:1650336965000
```

97
docs/guide/questions/3.关于组件引用的奥秘,ref知多少.md

@ -0,0 +1,97 @@
# 关于组件引用的奥秘,ref知多少
类似于React和Vue获取DOM元素引用的形式FineUI中也可以获取组件实例的引用,有两种方式
1. BI.createWidget方法的返回值
2. 组件props中的ref属性(推荐)
## ref在什么时候调用
组件ref会在组件render之后回调`this.options.ref.call(this, this)`,同时也会在组件destoryed之后再次回调`this.options.ref.call(null, null)`
这也是为什么在[combo的一些特性详解](./80.combo的一些特性详解.md)一文中强调combo的popup是在弹出时候创建的,有时候虽然写了ref,但是暂时还拿不到.
## ref不是只回调一次哦
使用ref时推荐如下写法,使用参数
```
{
type: "bi.button",
text: "左上角",
ref: function (_ref) {
xxx = _ref;
},
}
```
不推荐用this赋值的写法,因为在组件destoryed之后的回调,this可能是window对象.
```
{
type: "bi.button",
text: "左上角",
ref: function () {
xxx = this;
},
}
```
## 用BI.createWidget有什么不好吗,什么时候可以用BI.createWidget呢
1. 相较于ref,BI.createWidget缺少相应的内存回收
2. BI.createWidget会破坏render()=>json 的清晰代码结构
3. 在封装高阶组件的时候,组件内部需要获取子组件引用,同时还有外部传入的props存在ref的场景.
例如如下示例,此时外部传入的ref失效了
```
class MyComponent extends BI.Widget {
static xtype = "my.component";
render() {
const el = this.options.el;
return {
type: "bi.combo",
el: {
...el,
ref: ref => {
this.trigger = ref;
},
},
popup: {},
};
}
}
let myRef;
const widget = {
type: MyComponent.xtype,
ref: ref => {
myRef = ref;
},
}; render:
}
```
如何避免这类错误呢,有两种可选方案,使用BI.createWidget方法,或者使用私有的__ref
```
class MyComponent extends BI.Widget {
static xtype = "my.component";
render() {
const el = this.options.el;
return {
type: "bi.combo",
el: {
...el,
__ref: ref => {
this.trigger = ref;
},
},
popup: {},
};
}
}
```

115
docs/guide/questions/4.使用ButtonGroup控制可点击组件的控制选中状态.md

@ -0,0 +1,115 @@
# 使用ButtonGroup控制可点击组件的控制选中状态
先看如下示例,被选中的radio,再次点击,会使其取消选中,大多数时候这不符合我们的预期
```demo
{
type: "bi.vertical_adapt",
height: 30,
scrollable: false,
items: [
{
type: "bi.single_select_radio_item",
text: "选项1",
}, {
type: "bi.single_select_radio_item",
text: "选项2",
},
],
}
```
![示例](../../../images/13.gif)
为了实现真正的单选效果,我们需要进行状态控制,尽管如此,选中状态还是会跳来跳去,如果选项比较多的话将是个灾难
```demo
const radios = [];
const widget = BI.createWidget({
type: "bi.vertical_adapt",
height: 30,
scrollable: false,
items: [
{
type: "bi.single_select_radio_item",
ref: function (_ref) {
radios[0] = _ref;
},
text: "选项1",
handler: () => {
radios[1].setSelected(!radios[0].isSelected());
},
}, {
type: "bi.single_select_radio_item",
ref: function (_ref) {
radios[1] = _ref;
},
text: "选项2",
handler: () => {
radios[0].setSelected(!radios[1].isSelected());
},
},
],
});
```
![示例](../../../images/14.gif)
FineUI中提供了ButtonGroup控件,名字叫逻辑列表.他内部封装了选中逻辑控制,简单几行代码,就可以实现一个选项切换
```demo
const widget = BI.createWidget({
type: "bi.button_group",
height: 30,
layouts: {
type: "bi.vertical_adapt",
},
items: [
{
type: "bi.single_select_radio_item",
text: "选项1",
value: 1,
}, {
type: "bi.single_select_radio_item",
text: "选项2",
value: 2,
},
],
});
```
![示例](../../../images/15.gif)
ButtonGroup组件的默认chooseType属性为单选,也可以实现多选
```demo
const widget = BI.createWidget({
type: "bi.button_group",
height: 30,
layouts: {
type: "bi.vertical_adapt",
},
chooseType: BI.ButtonGroup.CHOOSE_TYPE_MULTI,
items: [
{
type: "bi.multi_select_item",
text: "选项1",
value: 1,
}, {
type: "bi.multi_select_item",
text: "选项2",
value: 2,
}, {
type: "bi.multi_select_item",
text: "选项3",
value: 3,
}, {
type: "bi.multi_select_item",
text: "选项4",
value: 4,
},
],
});
```
![示例](../../../images/16.gif)

58
docs/guide/questions/4.高阶组件的render-props.md

@ -0,0 +1,58 @@
## 高阶组件的render props.md
在FineUI中,我们经常见到一些继承抽象组件的写法,为了实现某种功能,继承抽象组件重新定义一个组件
依据我们的编码规范,一个组件一个文件,被迫新建众多文件
```
// 用于loading效果的组件
const Widget1 = BI.inherit(BI.Pane, {
beforeRender: function () {
this.loading();
},
});
// 用于封装可以点击的组件
const Widget2 = BI.inherit(BI.BasicButton, {
render: function () {
},
});
// 用于封装带有tooltip功能的组件
const Widget3 = BI.inherit(BI.Single, {
render: function () {
},
});
```
以BI.BasicButton举例,很多时候我们只是想快速创建一个可以点击的组件,此时可以借助render-props来实现
```
const MyButton = {
type: "bi.basic_button",
render: () => {
return {
type: "bi.vertical_adapt",
items: [
{
type: "bi.label",
text: "文字",
}, {
type: "bi.icon_button",
cls: "delete-font",
title: "删除按钮",
},
],
};
},
handler: () => {
console.log("点击了");
},
};
```

51
docs/guide/questions/40.我们为什么要设计el这个属性.md

@ -0,0 +1,51 @@
# 我们为什么要设计el这个属性
我们在使用布局组件的时候,有时候会在item中直接写lgap,hgap,vgap之类的属性,通常最终的页面结果是符合我们的预期的,但是当items中的子组件同时接收lgap作为props的时候,结果就变了,通常会出现间距变成两倍了的情况
```
BI.createWidget({
type: "bi.left",
items: [
{
type: "bi.label",
lgap: 10,
},
],
});
```
![示例](../../../images/3.png)
![示例](../../../images/4.png)
出现这种情况是因为布局组件会根据itemw中元素的gap属性设置margin值,而子组件也会读取gap属性在组件内部逻辑值使用这个值,因此出现了双倍间距.
采用el做隔离,可以避免这种情况,这时候子组件bi.label并没有lgap属性,但是布局组件依然可以正确获取items子元素的lgap属性
```
BI.createWidget({
type: "bi.left",
items: [
{
el: {
type: "bi.label",
text: "测试",
},
lgap: 10,
},
],
});
```
为了简化写法,FineUI新增了下划线+gap属性,如_hgap,_vgap,_lgap等,同样起到了隔离作用
```
BI.createWidget({
type: "bi.left",
items: [
{
type: "bi.label",
_lgap: 10,
},
],
});
```

184
docs/guide/questions/41.绝对布局的隐藏知识点.md

@ -0,0 +1,184 @@
# 绝对布局的隐藏知识点
bi.absolute布局可以说是在flex类布局没有大规模应用之前,解决布局问题的好伙伴
下面列举一下绝对布局的特性和在开发中可以解决的问题场景
1. 绝对布局子元素位置与宽高的影响
对于绝对布局的子元素来说,如果还设置了width,且同时设置了left/right属性,那么仅有left生效.同理如果还设置了height,且top/bottom同时存在时top生效.
```demo
BI.createWidget({
type: "bi.absolute",
width: 200,
height: 200,
items: [
{
el: {
type: "bi.layout",
css: {
background: "blue",
},
},
inset: 0,
}, {
el: {
type: "bi.layout",
css: {
background: "red",
},
width: 24,
height: 24,
},
inset: 0,
},
],
});
```
如示例所示,虽然设置了right和bottom为0,但实际并未生效
![示例](../../../images/5.png)
2. 可以利用绝对布局实现拉伸效果
如果想让子元素撑满父元素,可以利用绝对布局设置位置值来实现
```demo
BI.createWidget({
type: "bi.absolute",
width: 300,
height: 300,
css: {
background: "blue",
},
items: [
{
el: {
type: "bi.layout",
css: {
background: "red",
},
},
inset: 10,
},
],
});
```
![示例](../../../images/6.png)
3. 绝对布局也有隐藏的z-index层级,排在后面的层级高
利用这种特性,我们可以实现一些元素覆盖在下层元素的场景
```demo
BI.createWidget({
type: "bi.absolute",
width: 200,
height: 200,
items: [
{
el: {
type: "bi.layout",
css: {
background: "blue",
},
},
inset: 0,
}, {
el: {
type: "bi.layout",
css: {
background: "red",
},
width: 24,
height: 24,
},
right: 10,
bottom: 10,
},
],
});
```
![示例](../../../images/7.png)
4. 位置属性使用负值会有起效
在开发过程中会遇到有些按钮图标为了位置美观,会脱离正常位置的情况,此时利用负值属性可以很轻松实现
```demo
BI.createWidget({
type: "bi.absolute",
width: 300,
height: 300,
css: {
background: "red",
},
items: [
{
el: {
type: "bi.button",
text: "保存",
},
top: -30,
right: 10,
},
],
});
```
![示例](../../../images/8.png)
5. 绝对布局支持shorthand写法inset
使用Chrome审查元素的同学可能发现了,绝对布局的元素在chrome中会显示`inset: 0`这种形式,实际上这是top right bottom left属性的简写,规则遵循顺时针方向.FineUI同样支持了这种写法,需要注意的是只有第一种情况支持属性名为数字,其余情况需要加引号,毕竟我们是写js而不是css
```
inset: 10 /* value applied to all edges */
inset: "4 8" /* top/bottom left/right */
inset: "5 15 10" /* top left/right bottom */
inset: "2 3 3 3" /* top right bottom left */
```
6. 绝对布局可以和其他布局共存
FineUI中组件render方法支持return对象或者数组,可以利用绝对布局几乎没有副作用的特点,附加一些内容
```
BI.createWidget({
type: "bi.basic_button",
width: 200,
height: 200,
cls: "bi-border",
render: () => {
return [
{
type: "bi.center_adapt",
items: [
{
type: "bi.button",
text: "居中",
},
],
}, {
type: "bi.absolute",
items: [
{
el: {
type: "bi.button",
text: "左上角",
},
top: 10,
right: 10,
},
],
},
];
},
});
```
![示例](../../../images/10.png)

0
docs/guide/questions/42.何时会滚动,滚动条位置如何贴边,究竟是怎么回事.md

167
docs/guide/questions/43.利用响应式编写组件代码的新思路.md

@ -0,0 +1,167 @@
有了响应式,我们在开发组件的时候也可以体验到数据双向绑定.省去了进行状态控制需要额外定义store的流程
类似于Fix.Model, 我们约定state属性用来保存状态,在beforeCreate生命周期中定义.
```js
const Pager = BI.inherit(BI.Widget, {
props: {
total: 1,
page: 1,
},
beforeCreate: function () {
this.state = Fix.define({
currentPage: this.options.page,
total: this.options.total,
});
},
render: function () {
return {
type: "bi.vertical_adapt",
columnSize: [24, 24, "fill", "", 24, 24],
hgap: 5,
items: [
{
type: "bi.icon_button",
cls: "pre-page-h-font",
disabled: () => {
return this.state.currentPage === 1;
},
handler: () => {
this.state.currentPage = 1;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.icon_button",
cls: "pre-page-h-font",
disabled: () => {
return this.state.currentPage === 1;
},
handler: () => {
this.state.currentPage--;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.text_editor",
ref: ref => this.editor = ref,
value: () => this.state.currentPage,
validationChecker: (v) => (BI.parseInt(v) >= 1) && (BI.parseInt(v) <= this.state.total),
errorText: "error",
listeners: [
{
eventName: "EVENT_CHANGE",
action: () => {
this.state.currentPage = BI.parseInt(this.editor.getValue());
this.fireEvent("EVENT_CHANGE", this.getValue());
},
},
],
}, {
type: "bi.label",
text: () => "/" + this.state.total,
}, {
type: "bi.icon_button",
cls: "next-page-h-font",
disabled: () => {
return this.state.currentPage === this.state.total;
},
handler: () => {
this.state.currentPage++;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
}, {
type: "bi.icon_button",
cls: "next-page-h-font",
disabled: () => {
return this.state.currentPage === this.state.total;
},
handler: () => {
this.state.currentPage = this.state.total;
this.fireEvent("EVENT_CHANGE", this.getValue());
},
},
],
};
},
getValue: function () {
return this.state.currentPage;
},
populate: function () {
this.state.currentPage = this.options.page;
this.state.total = this.options.total;
},
});
BI.shortcut("pager", Pager);
let pager;
BI.createWidget({
type: "bi.vertical",
vgap: 20,
items: [
{
type: "pager",
ref: ref => pager = ref,
height: 24,
width: 250,
total: 100,
page: 2,
}, {
type: "bi.button",
text: "populate",
handler: () => {
pager.attr("total", 500);
pager.attr("page", 400);
pager.populate();
},
},
],
});
```
如果使用的是es6或者typescript开发,那么可以直接定义类的state属性
```javascript
import { shortcut } from "@core";
@shortcut()
export class Steps extends BI.Widget {
static xtype = "steps";
props = {
current: 1,
};
state = Fix.define({
current: 1,
});
beforeCreate() {
this.state.current = this.options.current;
}
render() {
return {
type: "bi.label",
text: () => `第${this.state.current}步`,
};
}
setValue(step) {
return this.state.current = step;
}
getValue() {
return this.state.current;
}
}
```

150
docs/guide/questions/44.如何提供异步配置的接口.md

@ -0,0 +1,150 @@
# 如何提供异步配置的接口
回顾一下我们常用的提供拓展接口方式
1. 定义privider
2. 在被拓展的组件中调用`BI.Providers.getProvider().xxx`
3. 开发者通过`BI.config(xxx,privider => provider.inject())`注册
我们提供的配置接口,绝大多数时候都要求BI.config 要在资源加载前执行
但是有些时候,插件资源是异步加载到,配置代码要异步执行,该如何设计接口?
![示例](../../../images/18.gif)
这里分享一个利用响应式特性开放异步接口的方式,核心思路是将拓展数据定义为响应式的,然后组件层采用响应式写法
首先我们在Provider中定义响应式数据
```js
const Provider = function () {
const state = Fix.define({
menus: [],
});
this.inject = function (menu) {
state.menus.push(menu);
};
this.$get = function () {
return BI.inherit(BI.OB, {
getMenus: () => {
return state.menus.slice(0);
},
});
};
};
BI.provider("demo.provider.menus", Provider);
```
之后再组件中通过函数响应式使用provider提供的数据即可
```js
const Menus = function () {
const getMenuItems = function () {
return BI.Providers.getProvider("demo.provider.menus").getMenus();
};
const defaultMenus = [
{
type: "bi.button",
text: "第一个",
}, {
type: "bi.button",
text: "第二个",
},
];
return {
type: "bi.vertical",
items: () => BI.concat(defaultMenus, getMenuItems()),
};
};
```
开发者依然通过同步的方式调用`BI.config`,但是配置函数却可以是异步的
(思考一下为什么不是`setTimout(() => BI.config()`这种写法)
```js
BI.config("demo.provider.menus", provider => {
setTimeout(() => {
provider.inject({
type: "bi.button",
text: "第三个",
});
}, 4000);
});
```
最终的效果呈现
![示例](../../../images/19.gif)
```demo
const Provider = function () {
const state = Fix.define({
menus: [],
});
this.inject = function (menu) {
state.menus.push(menu);
};
this.$get = function () {
return BI.inherit(BI.OB, {
getMenus: () => {
return state.menus.slice(0);
},
});
};
};
BI.provider("demo.provider.menus", Provider);
const Menus = function () {
const getMenuItems = function () {
return BI.Providers.getProvider("demo.provider.menus").getMenus();
};
const defaultMenus = [
{
type: "bi.button",
text: "第一个",
}, {
type: "bi.button",
text: "第二个",
},
];
return {
type: "bi.vertical",
items: () => BI.concat(defaultMenus, getMenuItems()),
};
};
BI.config("demo.provider.menus", provider => {
setTimeout(() => {
provider.inject({
type: "bi.button",
text: "第三个",
});
}, 4000);
});
const Widget10 = BI.createWidget({
type: "bi.vertical",
items: [
{
el: Menus(),
},
],
});
return Widget10;
```

21
docs/guide/questions/50.前端如何正确书写资源路径.md

@ -0,0 +1,21 @@
# 前端如何正确书写资源路径
项目开发中,字体,图片,脚本等内容都涉及到资源加载,FineUI中在项目中默认的资源路径是如下结构
```
@webUrl: 'resources?path=/com/fr/web/ui/';
@fontUrl: '@{webUrl}font/'; //图片的基本地址
@imageUrl: '@{webUrl}images/1x/'; //图片的基本地址
@image2xUrl: '@{webUrl}images/2x/'; //2倍图片的基本地址
```
如果想要使用自己版本的字体文件或图片,可以通过在项目中定义less变量来实现
```
@webUrl: 'resources?path=/com/fr/plugin/xxxweb/ui/';
@fontUrl: '@{webUrl}font/';
@imageUrl: '@{webUrl}images/1x/';
@image2xUrl: '@{webUrl}images/2x/';
```

55
docs/guide/questions/55.Fix中对于对象属性的监听,为什么要先定义属性才可以正常watch,如何解决.md

@ -0,0 +1,55 @@
# Fix中对于对象属性的监听,为什么要先定义属性才可以正常watch,如何解决?
先看如下代码示例,很明显按钮被点击后并不会输出phone的值,其实这和Vue2类似,是因为Fix在响应式处理的时候,遍历对象的每一个属性添加依赖的,并不能检测到新增的属性.
```javascript
const state = Fix.define({
name: "小明",
details: {
age: 18,
address: "北京",
},
});
Fix.watch(state, "details.phone", phone => {
console.log(phone);
});
const widget = BI.createWidget({
type: "bi.button",
text: "button",
handler: () => {
state.details.phone = "123456789";
},
});
```
在日常实践中我们一般采用两种方式来处理这种情况
1. 修改整个对象的引用
```
const widget = BI.createWidget({
type: "bi.button",
text: "button",
handler: () => {
state.details = { ...state.details, phone: "123456789"};
},
});
```
2. 使用Fix.set方法
类似vue的$set方法
```
const widget = BI.createWidget({
type: "bi.button",
text: "button",
handler: () => {
Fix.set(state.details, "phone", "123456789");
},
});
```

7
docs/guide/questions/55.watch使用的常见误区.md

@ -0,0 +1,7 @@
# watch使用的常见误区
示例
https://code.fineres.com/projects/DEC/repos/decision-webui/pull-requests/7973/diff#src/modules/management/authority/carrierdimenision/entities/system/system.authority.js
![示例](../../../images/1.png)

245
docs/guide/questions/80.combo的一些特性详解.md

@ -0,0 +1,245 @@
# combo一些不为人知的特性
## combo的trigger属性是可以设置为空的
去掉combo的默认弹出事件,手动控制showView,在某些场景中很有用.例如需要控制有些场景要弹出,有些场景不要弹出.
例如BI业务中有一个场景,叫做"开启权限继承后气泡提示"
![示例](../../../images/17.gif)
```demo
let combo;
let switchBtn;
BI.createWidget({
type: "bi.bubble_combo",
ref: (ref) => combo = ref,
el: {
type: "bi.switch",
ref: (ref) => switchBtn = ref,
handler: () => {
switchBtn.isSelected() ? combo.showView() : combo.hideView();
},
},
trigger: "",
popup: {
el: {
type: "bi.label",
text: "提示文字",
},
},
});
```
另一个场景,有些提示文本提示一次后不再提示,
```demo
let combo;
BI.createWidget({
type: "bi.vertical",
items: [
{
type: "bi.combo",
ref: ref => combo = ref,
trigger: "",
el: {
type: "bi.button",
text: "触发弹出",
handler: () => {
if (BI.Cache.getItem("confirmed")) {
combo.showView();
return;
}
BI.Msg.confirm("弹出是手动的,你知道吧", confirmed => {
if (confirmed) {
BI.Cache.setItem("confirmed", true);
combo.showView();
}
});
},
},
popup: {
el: {
type: "bi.label",
text: "popup",
},
},
},
],
});
```
## combo的popup是在弹出时候创建的,但是props却是在combo创建时候确定的
应对这种情况,常见的解决方案是`isDefaultInit: true`或者将popup属性改为函数
注意设置了`isDefaultInit: true`后会有响应的性能或体验损失
```demo
BI.createWidget({
type: "bi.vertical",
items: [
{
type: "bi.combo",
el: {
type: "bi.label",
text: BI.getDate(),
},
popup: {
el: {
type: "bi.label",
text: BI.getDate(),
},
},
}, {
type: "bi.combo",
el: {
type: "bi.label",
text: BI.getDate(),
},
isDefaultInit: true,
popup: {
el: {
type: "bi.label",
text: BI.getDate(),
},
},
}, {
type: "bi.combo",
el: {
type: "bi.label",
text: BI.getDate(),
},
popup: () => ({
el: {
type: "bi.label",
text: BI.getDate(),
},
}),
},
],
});
```
## combo的value传递和getValue很奇怪
在多数场景中,给bi.combo传递value属性,可以使下拉框在展开时候自动选中某一项.前提是popup的el组件支持value属性
如下的combo,value是不生效的.需要对bi.button_group传递value属性
同样的,如果在展开之前value可能发生改变,要注意value的同步
```demo
var Combo = BI.inherit(BI.Widget, {
render: function () {
return {
type: "bi.combo",
el: {
type: "bi.label",
},
ref: ref => this.combo = ref,
value: this.options.value,
popup: {
el: {
type: "bi.vertical",
items: [
{
type: "bi.button_group",
layouts: [
{
type: "bi.vertical",
},
],
items: [
{
type: "bi.single_select_radio_item",
text: "1",
value: 1,
}, {
type: "bi.single_select_radio_item",
text: "2",
value: 2,
},
],
},
],
},
},
};
},
getValue: function () {
return this.combo.getValue();
},
setValue: function (v) {
this.combo.setValue(v);
},
});
BI.shortcut("demo.combo", Combo);
```
## destroyWhenHide
当控制下拉框的状态比较复杂的时候,可以通过收起时候销毁再重新创建来简化
例如消息下拉框,内部两个独立的业务组件,一个展示消息列表,一个展示二维码.希望每次弹出都能重新加载消息.配置destroyWhenHide可以完全交友message_list
的生命周期去控制,避免了监听combo弹出事件,在命令式调用刷新逻辑.
```demo
BI.createWidget({
type: "bi.vertical",
items: [
{
type: "bi.combo",
ref: ref => combo = ref,
trigger: "",
el: {
type: "bi.button",
text: "触发弹出",
},
destroyWhenHide: true,
popup: {
el: {
type: "bi.vertical",
items: [
{
type: "message_list",
}, {
type: "qr_code",
},
],
},
},
listeners: [
{
eventName: BI.Combo.EVENT_AFTER_POPUPVIEW,
action: function () {
// do something
},
},
],
},
],
});
```
## hideWhenBlur
先说怎么屏蔽这个行为,再说为什么设计这个属性
1. 全局变量`BI.EVENT_BLUR`改为`false`
2. combo配置`hideWhenBlur: false`
为什么要添加这一个特性呢,实际上是因为iframe会隔离事件的缘故,下拉框弹出后,鼠标在iframe中操作并不会触发外部的`document.mousedown`
因此下拉框也就无法收起了.
以前是怎么处理呢,`trigger: "click-hover"`,即点击触发弹出,鼠标移除触发收起,但是体验差点意思
![](../../../images/2.png)

86
docs/guide/questions/9.BI.config都可以做那些事情.md

@ -0,0 +1,86 @@
# BI.config都可以做哪些事情,有哪些应用场景?
1. 修改组件的props
```
// 本示例修改了组件的type属性,也是常用的一种场景,讲一个组件替换为另外一个
BI.config("bi.button", props => {
props.type = "my.button";
return props;
});
```
2. 修改组件的默认props
```
// 本示例修改了bi.button的默认属性,配置方法是在组件的shortcut后面添加.props
BI.config("bi.button.props", props => {
props.minWidth = 100;
return props;
});
```
3. 修改常量资源
```
// 有如下常量资源
BI.constant("bi.constant.items", [
{
id: 1,
text: 1,
},
]);
// 通过config方法拓展常量资源
BI.config("bi.constant.items", items => {
items.push({
id: 2,
text: 2,
});
return items;
});
```
4. 修改provider
```
// 定义provider
var Provider = function () {
var items = [
{
id: 1,
text: 1,
},
];
this.inject = function (item) {
items.push(item);
};
this.$get = function () {
return BI.inherit(BI.OB, {
getItems: function () {
return items.slice(0);
},
});
};
};
BI.provider("bi.provider.demo", Provider);
// 配置provider,对外开放的拓展接口大多采用provider的形式. 这样有什么好处? 和直接配置constant有什么比优点吗
BI.config("bi.provider.demo", (provider) => {
provider.inject({
id: 2,
text: 2,
});
});
```
5. 配置一些系统级的配置
```
BI.config("bi.provider.system", (provider) => {
// Size相关,响应式,worker,模块依赖等
});
```

BIN
images/34.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
images/35.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/38.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

BIN
images/39.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

58
questions/28.几个常用的布局场景.md

@ -85,3 +85,61 @@ const widget = {
![示例5](../images/31.png)
![示例6](../images/32.png)
多层级嵌套时,避免在顶层设置gap属性
例如常用的上面导航下面tab示例,由于外层组件设置了`hgap:10`,因此即使内部`bi.vertical`组件出现滚动条,也无法贴附在容器边缘
因此开发过程中药尽量将gap设置下沉到具体的子组件
```js
const widget = {
type: "bi.vtape",
cls: "bi.border",
width: 300,
hgap: 10,
items: [
{
type: "demo.nav",
height: 36,
},
{
type: "bi.tab",
showIndex: 1,
cardCreator: v => {
return {
type: "bi.vertical", // 假设这个布局会出现滚动条
};
},
},
],
};
```
进行一次额外的布局包装,多一层DOM,收获一份体验
因为vertical2没有高度,因此其会被子节点撑起高度,而后导致vertical1发生溢出,出现滚动条
这样既实现了滚动条贴边,又保证了每一个列表项的左右间距.
```js
const widget3 = {
type: "bi.vertical", // vertical1
cls: "bi-border vertical1",
hgap: 10,
height: 300,
width: 300,
items: [
{
type: "bi.vertical", // vertical2,假设这个布局会出现滚动条,
cls: "vertical2",
items: (new Array(100)).fill(0).map((v, i) => {
return {
type: "bi.label",
cls: "bi-list-item-active2",
text: `第${i}项`,
};
}),
},
],
};
```
![示例7](../images/34.png)
![示例8](../images/35.png)

61
questions/29.BI.Layers.create参数详解原理.md

@ -0,0 +1,61 @@
# BI.Layers.create方法参数原理解释
今天看了一个BUG,通过BI.Layers创建的蒙层,在窗口大小改变时候没有随之改变。
先看FineUI中Layer.create方法的描述
```typescript
create<T>(name: string, from?: any, op?: any, context?: any): T;
```
推荐的统一写法
```javascript
const name; // your name
const container; // your container
BI.Layers.create(name, null, {
container: container,
render: {
type: "xxx",
},
}).show(name);
```
我们重点关注`from`属性和`op.container`属性
from属性控制的是,layer相对定位的元素.通过控制 `position: fixed` 搭配top,left,width,height属性,遮罩覆盖到对于元素上.同时监听元素resize,动态的更新宽高,使之一直"覆盖"在目标元素上
```javascript
const name = BI.UUID();
const form = this.element;
BI.Layers.create(name, form, {
type: "xxx",
}).show(name);
```
这样实现有什么弊端吗?举个例子,具体业务中经常有需要一个layer遮罩做一些具体操作的场景. 以定时调度新建任务为例:创建layer后,拖动容器宽度,会有明显的延迟.
这是因为resize事件的监听并不是实时的,做了节流.因此导致触发更新宽度慢了一拍.
![示例](../images/38.gif)
`op.container`属性又是什么作用呢,其控制的是弹出层在DOM树中属于哪个节点的子节点.默认情况下Layer弹出层是挂载到body下面的.
指定container之后,layer层将以position:absolute的形式,挂载到对于container节点下面.
当未指定form参数的时候,Layer层将以绝对布局形式固定相对container定位.在此场景下,宽高尺寸的动态调整全由浏览器渲染引擎自动控制.
根据[absolute定位的特性](./41.绝对布局的隐藏知识点.md),当`top:0;left:0`且元素未指定宽度时,绝对定位元素将随定位元素动态改变大小
这样再调整宽高,缩放等场景下表现就很顺滑了
```javascript
const name = BI.UUID();
BI.Layers.create(name, null, {
container: this.element,
render: {
type: "xxx",
},
}).show(name);
```
![示例2](../images/39.gif)
既然指定form参数有这些弊端,为什么还要保留这个设计呢.当遇到layer弹出层中存在下拉框,气泡等特殊场景的时候.layer挂在到body上面可以避免这些痛点.
当然这种场景属于特事特办,需要开发者对stacking context和combo原理有深入的理解
Loading…
Cancel
Save