zsmj
2 years ago
35 changed files with 2111 additions and 2 deletions
@ -0,0 +1,17 @@
|
||||
module.exports = { |
||||
title: 'Hello VuePress', |
||||
description: 'Just playing around', |
||||
themeConfig: { |
||||
sidebar:{ |
||||
"/guide/":[ |
||||
"/guide/", |
||||
{ |
||||
title: 'Guide', |
||||
children:[ |
||||
"/guide/1", |
||||
] |
||||
} |
||||
] |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
--- |
||||
|
@ -0,0 +1,6 @@
|
||||
# 0.前端工作需要哪些必备技能 |
||||
|
||||
## 1.熟悉各种链工具的使用,包括但不限于webpack,rollup,less,babel,代理配置等 |
||||
## 2.了解前后端分离开发模式,熟悉对接流程 |
||||
## 3.了解持续集成工具teamcity的基本流程 |
||||
## 4.掌握icon-font原理,知晓图标使用方式 |
@ -0,0 +1,19 @@
|
||||
# FineUI中如何获取当前时间 |
||||
|
||||
## 错误写法 |
||||
|
||||
``` |
||||
const date = new Date(); |
||||
const time = date.getTime(); |
||||
``` |
||||
|
||||
## 正确写法 |
||||
|
||||
``` |
||||
const time = BI.getTime(); |
||||
``` |
||||
|
||||
## 答案解析 |
||||
|
||||
首先思考一个场景: 某跨国公司系统管理员人在北京,在系统上配置数据更新时间晚上七点半。可是等到过了七点半,数据还是没有更新。这是为什么呢?原来服务器在伦敦,伦敦时间比北京时间慢7个小时,还没到更新时间呢 |
||||
FineUI中BI.getTime方法专门这种场景做了处理,依据服务器时区进行偏移,正确的获取当前时间。 |
@ -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加载顺序所带来的隐患,其次使配置可以无需立即执行并且可以多次配置. |
@ -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")); |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}); |
||||
``` |
@ -0,0 +1,7 @@
|
||||
# 如何记录子路由信息并重定向 |
||||
|
||||
## 业务场景 |
||||
|
||||
BI6.0公共数据的路由为`analysis`,有若干子路由,如`analysis/table/:id` |
||||
|
||||
当由`analysis`跳转到`management`,再跳转回来时,这时候进入的是`analysis`根,而不是上一次的子路由,如何解决呢 |
@ -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(); |
||||
}, |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
``` |
@ -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, |
||||
}, |
||||
``` |
@ -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) |
@ -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 |
||||
``` |
@ -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: {}, |
||||
}; |
||||
} |
||||
} |
||||
``` |
@ -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) |
@ -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("点击了"); |
||||
}, |
||||
}; |
||||
``` |
@ -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, |
||||
}, |
||||
], |
||||
}); |
||||
``` |
@ -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,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; |
||||
} |
||||
} |
||||
|
||||
``` |
@ -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; |
||||
|
||||
``` |
@ -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/'; |
||||
``` |
@ -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"); |
||||
}, |
||||
}); |
||||
``` |
@ -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) |
@ -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) |
@ -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,模块依赖等 |
||||
}); |
||||
``` |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 695 KiB |
After Width: | Height: | Size: 1.1 MiB |
@ -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…
Reference in new issue