Skip to content
This repository was archived by the owner on Apr 1, 2022. It is now read-only.

Commit 4d185f9

Browse files
author
Ken Berkeley
committed
finished docs
1 parent da0d005 commit 4d185f9

File tree

84 files changed

+9023
-465
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+9023
-465
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
.DS_Store
22
*.log
3-
*.map
43
node_modules

README.md

Lines changed: 4 additions & 370 deletions
Original file line numberDiff line numberDiff line change
@@ -1,372 +1,6 @@
1-
# Vue2 Datatable
2-
> 做 Vue.js 下最好的 Datatable 组件
3-
4-
## § 前言
5-
任何开源的 Datatable 都未必能满足所有的业务需求(否则也不会有这个项目了)
6-
本 README 致力于让您在理解组件设计以及源码的基础上,可自行定制出适合您业务需求的 Datatable
7-
8-
## § 快速体验
9-
我们以 [`example/Basic/index.vue`](example/Basic/index.vue) 为例,效果见 [demo](https://kenberkeley.github.io/vue2-datatable/example-dist)
1+
# Vue2 Datatable
102

11-
```html
12-
<template>
13-
<div>
14-
<code>query: {{ query }}</code>
15-
<datatable v-bind="$data" />
16-
<!-- 上面的写法比下面的要优雅,来源见 https://github.com/vuejs/vue/issues/4962
17-
<datatable
18-
:columns="columns"
19-
:data="data"
20-
:total="total"
21-
:query="query">
22-
</datatable>
23-
-->
24-
</div>
25-
</template>
26-
<script>
27-
import Datatable from 'vue2-datatable'
28-
import mockData from '../_mockData'
3+
> The Best Datatable for Vue.js 2.x which never sucks
294
30-
export default {
31-
components: { Datatable },
32-
data: () => ({
33-
columns: [
34-
{ title: 'User ID', field: 'uid', sort: true },
35-
{ title: 'Username', field: 'name' },
36-
{ title: 'Age', field: 'age', sort: true },
37-
{ title: 'Email', field: 'email' },
38-
{ title: 'Country', field: 'country' }
39-
],
40-
data: [],
41-
total: 0,
42-
query: {}
43-
}),
44-
watch: {
45-
query: {
46-
handler (query) {
47-
mockData(query).then(({ rows, total }) => {
48-
this.data = rows
49-
this.total = total
50-
})
51-
},
52-
deep: true
53-
}
54-
}
55-
}
56-
</script>
57-
```
58-
59-
## § 依赖
60-
* BootStrap 3.x + Font Awesome 4.x(全局引入)
61-
* [lodash / orderBy](https://lodash.com/docs/4.17.4#orderBy)
62-
* [replace-with](https://github.com/kenberkeley/replace-with)
63-
64-
注:BootStrap 以及 Font Awesome 的可替换性极强,您完全可以使用其他库替代(一般就是改一下类名即可)
65-
66-
## § 详解
67-
68-
### ⊙ 整体构造
69-
本 Datatable 的源码目录树 [`lib/`](lib/) 如下:
70-
71-
```
72-
lib/
73-
├─ HeaderSettings/ # 表头设置
74-
│   ├─ ColumnGroup.vue # 表头设置分栏组件
75-
│   └─ index.vue # 表头设置主体
76-
├─ HeadSort.vue # 排序
77-
├─ LimitSelect.vue # 每页显示记录数下拉选择框
78-
├─ MultiSelect.vue # 行首多选框
79-
├─ Pagination.vue # 分页
80-
└─ index.vue # Datatable 主体
81-
```
82-
83-
[`example/Advanced/index.vue`](example/Advanced/index.vue)[demo](https://kenberkeley.github.io/vue2-datatable/example-dist/#advanced) 为例,标出对应的组件如下图所示:
84-
85-
![Datatable Structure](structure.png)
86-
87-
### ⊙ 配置项
88-
> 【 Vue.js 小技巧 】
89-
> `HelloWorld` 组件中定义 `props: { hi: Boolean }`
90-
> `<hello-world hi />` 等同于 `<hello-world :hi="true" />`
91-
> 显然地,前者在写法上更加优雅
92-
93-
[`lib/index.vue`](lib/index.vue) 中的 `props` 如下:
94-
95-
```js
96-
props: {
97-
columns: { type: Array, required: true }, // 列定义
98-
data: { type: Array, required: true }, // 当页纪录 (rows)
99-
total: { type: Number, required: true }, // 记录总数
100-
query: { type: Object, required: true }, // 查询对象
101-
selection: Array, // 多项选择的容器
102-
summary: Object, // 汇总行数据 (summary row)
103-
HeaderSettings: { type: Boolean, default: true }, // 是否显示表头设置组件
104-
Pagination: { type: Boolean, default: true }, // 是否显示分页相关组件
105-
xprops: Object, // 额外传给动态组件的东东
106-
supportBackup: Boolean, // 是否支持使用 LocalStorage 保存表头设置
107-
supportNested: Boolean, // 是否支持内嵌组件 (nested component)
108-
tableBordered: Boolean // 是否添加 .table-bordered 类到 <table> 元素
109-
}
110-
```
111-
112-
下面仅讲解 `columns` / `data` / `query` / `selection` / `xprops` 以及三种动态组件(`thComp` / `tdComp` / `nested component`
113-
114-
***
115-
116-
#### `:columns`
117-
我们来对比一下 [`example/`](example/) 中的 [`Basic`](example/Basic/index.vue)[`Advanced`](example/Advanced/index.vue)`columns` 定义:
118-
119-
```js
120-
// example/Basic - 简单写法
121-
columns: [
122-
{ title: 'User ID', field: 'uid', sort: true },
123-
{ title: 'Username', field: 'name' },
124-
{ title: 'Age', field: 'age', sort: true },
125-
{ title: 'Email', field: 'email' },
126-
{ title: 'Country', field: 'country' }
127-
]
128-
129-
// example/Advanced - 标准写法
130-
columns: [{
131-
groupName: 'Normal',
132-
columns: [
133-
{ title: 'Email', field: 'email', visible: false, thComp: 'FilterTh', tdComp: 'Email' },
134-
{ title: 'Username', field: 'name', thComp: 'FilterTh' },
135-
{ title: 'Country', field: 'country', thComp: 'FilterTh' },
136-
{ title: 'IP', field: 'ip', visible: false, tdComp: 'IP' }
137-
]
138-
}, {
139-
groupName: 'Sortable',
140-
columns: [
141-
{ title: 'User ID', field: 'uid', sort: true, visible: 'true', weight: 1 },
142-
{ title: 'Age', field: 'age', sort: true },
143-
{ title: 'Create time', field: 'createTime', sort: true,
144-
thClass: 'w-240', tdClass: 'w-240', thComp: 'CreatetimeTh', tdComp: 'CreatetimeTd' }
145-
]
146-
}, {
147-
groupName: 'Extra (radio)',
148-
type: 'radio',
149-
columns: [
150-
{ title: 'Operation', tdComp: 'Opt' },
151-
// don't forget to set the columns below `visible: false`, since the `type` is `radio`
152-
{ title: 'Color', field: 'color', explain: 'Favorite color', visible: false, tdComp: 'Color' },
153-
{ title: 'Language', field: 'lang', visible: false, thComp: 'FilterTh' },
154-
{ title: 'PL', field: 'programLang', explain: 'Programming Language', visible: false, thComp: 'FilterTh' }
155-
]
156-
}]
157-
```
158-
159-
实际上 `Basic` 的这种简写最终都会被转为 `Advanced` 的标准形式(见源码 [`lib/index.vue`](lib/index.vue) 中的 `computed.columns$`
160-
161-
下面列出 `columns[i]` 中的可配置项:
162-
163-
| 参数 | 说明 | 类型 | 可选项 | 默认值 | 是否必须 |
164-
|---------|--------------------------------------------------|----------------|---------------------------|--------|----------|
165-
| title | 显示名称 | String | - | - ||
166-
| field | 字段名称 | String | - | - ||
167-
| explain | 说明文字 | String | - | - ||
168-
| sort | 是否支持排序 | Boolean | - | false ||
169-
| weight | 显示排名权重 | Number | - | 0 ||
170-
| visible | 是否显示(若为字符串类型则禁止设置该列显隐状态) | Boolean / String | true / false / 'true' / 'false' | true ||
171-
| thClass | 用于 `<th>` 的类名 | String | - | - ||
172-
| thStyle | 用于 `<th>` 的内联样式 | String | - | - ||
173-
| thComp | 用于 `<th>` 的动态组件名 | String | - | - ||
174-
| tdClass | 用于 `<td>` 的类名 | String | - | - ||
175-
| tdStyle | 用于 `<td>` 的内联样式 | String | - | - ||
176-
| tdComp | 用于 `<td>` 的动态组件名 | String | - | - ||
177-
178-
>【 JS 小技巧 】
179-
>
180-
```js
181-
cols.map(col => {
182-
if (!col.weight) col.weight = 0
183-
return col
184-
})
185-
// 利用逗号运算符,可以把上面的代码缩写为一行
186-
cols.map(col => ((col.weight = col.weight || 0), col))
187-
```
188-
***
189-
190-
#### `:data`
191-
实际上该项应该叫 `rows` 才合理,但主流的 Datatable 都是这样称呼,我也不能免俗
192-
本身该项是没啥好讲的,但本 Datatable 支持**无限递归内嵌组件**,靠的就是在这里做文章
193-
在此把源码 `lib/index.vue` 中的 `computed.data$` 直接搬出来:
194-
195-
```js
196-
data$ () {
197-
const { data } = this
198-
if (this.supportNested) {
199-
// support nested components with extra magic
200-
data.forEach(item => {
201-
if (!item.__nested__) {
202-
this.$set(item, '__nested__', {
203-
comp: '', // name of nested component
204-
visible: false,
205-
$toggle (comp, visible) {
206-
switch (arguments.length) {
207-
case 0:
208-
this.visible = !this.visible
209-
break
210-
case 1:
211-
switch (typeof comp) {
212-
case 'boolean':
213-
this.visible = comp
214-
break
215-
case 'string':
216-
this.comp = comp
217-
this.visible = !this.visible
218-
break
219-
}
220-
break
221-
case 2:
222-
this.comp = comp
223-
this.visible = visible
224-
break
225-
}
226-
}
227-
})
228-
Object.defineProperty(item, '__nested__', { enumerable: false })
229-
}
230-
})
231-
}
232-
return data
233-
}
234-
```
235-
236-
由源码可知,我们对 `data (rows)` 内的各个元素 `item (row)` 设置了一个不可遍历属性 `__nested__`,包含以下三个属性:
237-
238-
| 参数 | 说明 | 类型 | 可选项 | 默认值 |
239-
|---------|--------------|----------|-------------------------------------------------------------|--------|
240-
| comp | 内嵌组件名 | String | - | '' |
241-
| visible | 是否显示 | Boolean | true / false | false |
242-
| $toggle | 便捷操作函数 | Function | $toggle(comp) / $toggle(visible) / $toggle(comp, visible) | - |
243-
244-
`__nested__` 作为 `props.nested` 传入到对应的 `tdComp``nested component`
245-
由此,在对应的动态组件内部即可通过 `nested.$toggle` 实现对 `nested component` 的控制
246-
(当然,您要直接操作 `row.__nested__.$toggle` 也是没问题的,都是同一个东西)
247-
248-
***
249-
250-
#### `:query`
251-
让我们来看看 Datatable 是如何初始化 `query` 的(见源码 [`lib/index.vue`](lib/index.vue) 中的 `created` 钩子函数):
252-
253-
```js
254-
created () { // init query
255-
const { query } = this
256-
const q = { limit: 10, offset: 0, sort: '', order: '', ...query }
257-
Object.keys(q).forEach(key => this.$set(query, key, q[key]))
258-
}
259-
```
260-
261-
一般情况下,您只需要传入一个空对象 `{}` 即可。若还有其他查询条件(例如 `search`),则以下两种方式二选一:
262-
1. 在初始化时就传入 `{ search: '' }`(推荐)
263-
2. 自行使用 [`Vue.set / $vm.$set`](https://vuejs.org/v2/api/#Vue-set) 设置:`this.$set(this.query, 'search', '')`
264-
265-
上述两种方式均在 `example/Advanced` 中有所体现,最终目的都是让额外的查询属性变成[响应式](https://vuejs.org/v2/guide/reactivity.html)
266-
(其中第 2 种见 [`example/Advanced/comps/th-Filter.vue`](example/Advanced/comps/th-Filter.vue) 中的 `methods.search`
267-
268-
在此提一个常见的需求:实现刷新后保持查询条件
269-
最常见的解决方案是**同步查询条件到 URL**。拿 `example/Basic` 来说:
270-
271-
```html
272-
<template>
273-
<div>
274-
<code>query: {{ query }}</code>
275-
<datatable v-bind="$data" />
276-
</div>
277-
</template>
278-
<script>
279-
import Datatable from 'vue2-datatable'
280-
import mockData from '../_mockData'
281-
282-
export default {
283-
components: { Datatable },
284-
data () {
285-
return {
286-
columns: [
287-
{ title: 'User ID', field: 'uid', sort: true },
288-
{ title: 'Username', field: 'name' },
289-
{ title: 'Age', field: 'age', sort: true },
290-
{ title: 'Email', field: 'email' },
291-
{ title: 'Country', field: 'country' }
292-
],
293-
data: [],
294-
total: 0,
295-
query: this.$route.query // 初始化时传入 URL query(在业务中请注意安全性)
296-
}
297-
},
298-
watch: {
299-
// 同步本地 query 到 URL query
300-
query: {
301-
handler (query) {
302-
this.$router.push({ path: this.$route.path, query })
303-
},
304-
deep: true
305-
},
306-
// 通过监听 URL query 的变化来重新获取数据
307-
'$route.query' (query) {
308-
mockData(query).then(({ rows, total }) => {
309-
this.data = rows
310-
this.total = total
311-
})
312-
}
313-
}
314-
}
315-
</script>
316-
```
317-
318-
***
319-
320-
#### `:selection`
321-
一般情况下,您只需要传入一个空数组 `[]` 即可
322-
若有行被勾选,则对应的 `row` 将会进入到 `selection`
323-
假如您的产品经理要求默认就是全部勾选,也是没问题的。就以 `example/Advanced` 为例:
324-
325-
```js
326-
methods: {
327-
handleQueryChange () {
328-
mockData(this.query).then(({ rows, total, summary }) => {
329-
this.data = rows
330-
this.total = total
331-
this.summary = summary
332-
333-
// 就是这么简单!
334-
this.$nextTick(() => this.selection = [...this.data])
335-
})
336-
}
337-
}
338-
```
339-
340-
***
341-
342-
#### `:xprops`
343-
由于 `thComp / tdComp / nested component` 都是通过动态组件实现
344-
而业务需求又是不固定的,很可能需要传入很多额外的 props
345-
那么,源码 [`lib/index.vue`](lib/index.vue) 中的模板,很有可能会演变成这样子:
346-
347-
```html
348-
<component
349-
...
350-
:XXX="XXX"
351-
:YYY="YYY"
352-
:ZZZ="ZZZ"
353-
:XYZ="XYZ"
354-
:ZYX="ZYX"
355-
...><!-- 100+ extra props -->
356-
</component>
357-
```
358-
359-
再三斟酌下,我引入了 `xprops`,用于承载这些额外的 props,以避免污染源码
360-
361-
最常见的用处是,传入一个仅限于当前 Datatable 内部使用的 [eventbus](https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication),这样的话就不需要区分命名空间了
362-
(可以实现无限递归嵌套的 `example/Advanced` 就是通过这种方式来避免不必要的麻烦)
363-
364-
## 深入
365-
366-
## 设计理念
367-
full ES5
368-
关键:扁平的动态组件(全局化 局部化)
369-
反范式:允许子组件修改状态,毋须引入鸡肋的状态管理
370-
xprops 传递其余属性
371-
源码技巧 缩短 .map
372-
同步 URL 技巧
5+
[Documentation](https://OneWayTech.github.com/vue2-datatable/docs/_book) |
6+
[Online examples](https://OneWayTech.github.com/vue2-datatable/examples/dist)
122 KB
Binary file not shown.
74.7 KB
Binary file not shown.

0 commit comments

Comments
 (0)