datacom
,全称是data components
,数据驱动的组件。
这种组件也是vue组件,是一种子类,是基础组件的再封装。
相比于普通的vue组件,datacom
组件的特点是,给它绑定一个data,data中包含一组候选数据,即可自动渲染出结果。
比如 uni-data-checkbox (opens new window) 组件,给它绑定一个data,即可直接生成一组选择框。
<template>
<!-- 传入符合 datacom 规范的数据,即可渲染出一组 checkbox -->
<!-- 使用 v-model 双向绑定 checkbox 的选中值 -->
<uni-data-checkbox v-model="value" :localdata="options" multiple></uni-data-checkbox>
</template>
<script>
export default {
data() {
return {
value: ['sh'],
options: [
{value: 'bj',text: '北京'},
{value: 'sh',text: '上海'},
{value: 'gz',text: '广州'}
],
};
},
};
</script>
而使用基础组件的写法,代码量则要增加很多,如下:
<template>
<view>
<view class="uni-list">
<checkbox-group @change="checkboxChange">
<label class="uni-list-cell" v-for="item in items" :key="item.value">
<view>
<checkbox :value="item.value" :checked="item.checked" />
</view>
<view>{{item.name}}</view>
</label>
</checkbox-group>
</view>
</view>
</template>
<script>
export default {
data() {
return {
items: [{
value: 'bj',
name: '北京'
},
{
value: 'sh',
name: '上海',
checked: 'true'
},
{
value: 'gz',
name: '广州'
}
]
}
},
methods: {
checkboxChange: function (e) {
var items = this.items,
values = e.detail.value;
for (var i = 0, lenI = items.length; i < lenI; ++i) {
const item = items[i]
if(values.includes(item.value)){
this.$set(item,'checked',true)
}else{
this.$set(item,'checked',false)
}
}
}
}
}
</script>
<style>
.uni-list-cell {
justify-content: flex-start
}
</style>
图例:
显然,datacom组件不是只有一个<uni-data-checkbox>
,radio、check、select、picker、segement、tree...还有很多组件都可以成为datacom组件,变成类似<uni-data-picker>
。
那么“datacom组件规范”,就定义了什么是datacom组件
,以及它们的互联互通标准。
所有开发者,都可以在 DCloud插件市场 (opens new window) 提交符合datacom组件规范
的组件。在插件市场组件分类下可以找到所有已上传的datacom组件,详见 (opens new window)
详细的“datacom组件规范”见后文。
datacom组件,对服务器数据规范、前端组件的数据输入和输出规范,做了定义。它提升了产业的标准化程度、细化了分工,提升了效率。
且不论产业影响,对开发者个人而言,显而易见的好处也很多:
datacom组件
,服务器代码直接就不用写了举个例子,假使我们想实现一个城市选择的业务。
<xx-data-citypicker>
unicloud-db组件
把数据库的城市表数据拉下来,绑定给<xx-data-citypicker>
datacom组件规范
,可能有的是全屏选择、有的是底部半屏选择,有的顶部有搜索框、有的右侧有索引字母....不管什么样的城市选择组件,你都可以随便的换,反正它们都符合一个数据规范。<uni-forms>
组件的表单校验规范datacom组件接受的数据结构,包含了“数组”和“树”两种数据结构规范。
data数据是一组可循环的数据集合。数组中每条数据如下基本key:
key | 描述 |
---|---|
value | 值。必填 |
text | 显示文字。必填 |
selected | 是否默认选中。默认值false |
disable | 是否禁用。默认值false |
group | 分组标记 |
如果熟悉html的<select>
标签的话,其<option>
标签的属性也是value、text、selected。
除了这些基本key,开发者也可以自由扩展key。比如电影票、机票、火车票的选座,都需要扩展额外的信息:行、列、单元格类型(座位或过道)等。
完整的 JSON Schema 定义详见:https://gitee.com/dcloud/datacom/blob/master/array.schema.json (opens new window)
[
{"value": "bj","text": "北京"},
{"value": "sh","text": "上海"}
]
使用示例:
<template>
<!-- 传入符合 datacom 规范的数据,即可渲染出一组 checkbox -->
<!-- 使用 v-model 双向绑定 checkbox 的选中值 -->
<uni-data-checkbox v-model="value" :localdata="options" multiple></uni-data-checkbox>
</template>
<script>
export default {
data() {
return {
value: ['bj'],
options: [
{ value: "bj", text: "北京" },
{ value: "sh", text: "上海" },
],
};
},
};
</script>
data数据是可遍历嵌套的数据集合。数组中每条数据如下基本key:
key | 描述 |
---|---|
value | 值。必填 |
text | 显示文字。必填 |
selected | 是否默认选中。默认值false |
disable | 是否禁用。默认值false |
isleaf | 是否为叶子节点。默认值false,为true时会忽略children |
children | 子节点。其值的格式与父节点相同 |
完整的 JSON Schema 定义详见:https://gitee.com/dcloud/datacom/blob/master/tree.schema.json (opens new window)
[{
"value": "110000",
"text": "北京市",
"children": [{
"value": "110105",
"text": "朝阳区"
}, {
"value": "110108",
"text": "海淀区"
}]
}]
使用示例:
<template>
<!-- 传入符合 datacom 规范的数据,即可渲染出一个选择器 -->
<!-- 使用 v-model 双向绑定 picker 的选中值 -->
<uni-data-picker v-model="value" :localdata="items"></uni-data-picker>
</template>
<script>
export default {
data() {
return {
value: ["110000","110105"],
items: [{
"value": "110000",
"text": "北京市",
"children": [{
"value": "110105",
"text": "朝阳区"
}, {
"value": "110108",
"text": "海淀区"
}]
}],
};
},
};
</script>
datacom组件
的data,可以来自页面本地,即localdata;也可以直接指定uniCloud的云数据库查询结果,即指定collection表名、field字段名、where条件,这些写法与unicloud-db组件
的写法相同,如果localdata和collection同时存在,优先使用localdata。
localdata的示例上文已经举例,下面来看下直接指定uniCloud云数据库查询的写法。
<template>
<!-- 传入符合 datacom 规范的数据,即可渲染出一组 checkbox -->
<!-- 使用 v-model 双向绑定 checkbox 的选中值 -->
<uni-data-checkbox v-model="value" collection="" where="" field="" multiple></uni-data-checkbox>
</template>
<script>
export default {
data() {
return {
};
},
};
</script>
collection表名、field字段名、where条件的写法,详见clientDB组件文档
当然,支持绑定uniCloud数据,对于datacom组件规范来说,是可选的。
更为常见的场景,是在整个页面组件外围套一个clientDB组件,一次性查库,把查询结果的data分拆赋值给不同的datacom组件。
datacom组件规范还要求支持绑定 value,且支持双向绑定,即:支持v-model
指令。这同时也是为了uni-forms的表单校验。
当datacom组件
的data来自uniCloud的云数据库或cdn等云端,且数据量较大时,我们通常可以选择分步查询来优化用户体验,如以下场景:
datacom组件
为分步查询云端数据,设计了以下组件属性、事件:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
step-search | Boolean | true | 是否分步查询云端数据。常用于树,picker,分页列表等,参考:uni-data-picker |
step-search-url | String | 分步查询的云端数据请求地址。常用于树,picker,分页列表等,参考:uni-data-picker | |
self-field | String | "树"结构的当前字段名称。常用于树,picker,参考:uni-data-picker | |
parent-field | String | "树"结构的父字段名称。常用于树,picker,参考:uni-data-picker | |
@stepsearch | EventHandle | 分步查询数据时触发。可用于自定义分步查询数据,参考:uni-data-picker |
datacom组件
为弹出类组件,设计了以下组件属性、事件:
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
preload | Boolean | false | 是否预加载云端数据。参考:uni-data-picker |
@popupopened | EventHandle | 组件弹出显示时触发。参考:uni-data-picker | |
@popupclosed | EventHandle | 组件弹出关闭时触发。参考:uni-data-picker |
选择类组件很多,基本逻辑都是在指定的数据范围内,选择其中的一个或多个。
根据不同维度可以划分为:
这里列一下常见的选择类组件,以及它们按不同维度的分类,有助于更透彻的理解它们的本质
组件 | 选择模式 | 数据结构 | 展现方式 | 使用场景 | 说明 |
---|---|---|---|---|---|
radio(单选框) | 单选 | 数组 | 平铺 | 表单 | 列表单选、按钮组单选、标签组单选 |
checkbox(多选框) | 多选 | 数组 | 平铺 | 表单 | 列表多选、按钮组多选、标签组多选 |
select(下拉列表) | 单选、多选 | 数组 | 弹出 | 表单 | 单选下拉列表、多选下拉列表 |
picker(滚动选择器) | 单选 | 数组、树 | 弹出 | 表单 | 单列选择器(数组)、多列选择器(树) |
cascader(级联选择) | 单选、多选 | 树 | 弹出 | 表单 | |
transfer(穿梭框) | 多选 | 数组 | 平铺 | 表单 | |
slider(滑块) | 单选 | 数字范围 | 平铺 | 表单 | |
rate(评分) | 单选 | 数字范围 | 平铺 | 表单 | |
stepper(步进器) | 单选 | 数字范围 | 平铺 | 表单 | |
表头筛选 | 多选 | 数组 | 弹出 | 表单 | |
城市选择 | 单选 | 树 | 弹出、平铺 | 表单 | |
segement(分段器) | 单选 | 数组 | 平铺 | 展示 | |
侧边导航 | 单选 | 数组 | 平铺 | 展示 | |
tree(树状控件) | 单选、多选 | 树 | 平铺 | 展示 |
欢迎开发者们开发这些datacom组件
,后续插件市场将单列出datacom组件,给予更高的显示权重。
版本要求:HBuilderX 3.1.0+
开发一个支持localdata的datacom组件相对容易,但要开发支持云端数据的datacom组件,实现对collection、field、where等属性的解析,工作量还是不小的。
为此官方提供了一个mixin混入库,开发者在自己的datacom组件中混入uniCloud.mixinDatacom
,即可方便的让自己的组件支持本地和云端的数据绑定,快速完成datacom组件。
mixin是vue的技术,不熟悉的可以点此了解vue官网的mixin文档 (opens new window)
uniCloud.mixinDatacom
的props
与标准的datacom组件相同,除了localdata外,其他都是uniCloud-db组件
的标准属性。
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
localdata | Array | 本地数据,详情 | |
spaceInfo | Object | 服务空间信息,新增于HBuilderX 3.2.11 。同uniCloud.init参数,参考:uniCloud.init | |
collection | String | 表名。支持输入多个表名,用 , 分割 | |
field | String | 查询字段,多个字段用 , 分割 | |
where | String | 查询条件,内容较多,另见jql文档:详情 | |
orderby | String | 排序字段及正序倒叙设置 | |
groupby | String | 对数据进行分组 | |
group-field | String | 对数据进行分组统计 | |
distinct | Boolean | false | 是否对数据查询结果中重复的记录进行去重 |
action | string | 云端执行数据库查询的前或后,触发某个action函数操作,进行预处理或后处理,详情。场景:前端无权操作的数据,比如阅读数+1 | |
page-data | String | add | 分页策略选择。值为 add 代表下一页的数据追加到之前的数据中,常用于滚动到底加载下一页;值为 replace 时则替换当前data数据,常用于PC式交互,列表底部有页码分页按钮 |
page-current | Number | 0 | 当前页 |
page-size | Number | 20 | 每页数据数量 |
getcount | Boolean | false | 是否查询总数据条数,默认 false ,需要分页模式时指定为 true |
getone | Boolean | false | 指定查询结果是否仅返回数组第一条数据,默认 false。在false情况下返回的是数组,即便只有一条结果,也需要[0]的方式获取。在值为 true 时,直接返回结果数据,少一层数组。一般用于非列表页,比如详情页 |
gettree | Boolean | false | 是否查询树状数据,默认 false |
startwith | String | '' | gettree 的第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询 |
limitlevel | Number | 10 | gettree 查询返回的树的最大层级。超过设定层级的节点不会返回。默认10级,最大15,最小1 |
foreign-key | String | '' | 手动指定使用的关联关系,HBuilderX 3.1.10+ 详情 |
uniCloud.mixinDatacom
的data
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
mixinDatacomLoading | Boolean | false | 加载数据状态 |
mixinDatacomHasMore | Boolean | false | 是否有更多数据 |
mixinDatacomResData | Array | [] | 查询返回的数据 |
mixinDatacomErrorMessage | String | 错误消息 | |
mixinDatacomPage | OBject | 分页信息 |
uniCloud.mixinDatacom
methods
方法名 | 说明 |
---|---|
mixinDatacomGet | 加载数据 |
mixinDatacomEasyGet | 加载数据,包含 mixinDatacomLoading 、mixinDatacomHasMore 、mixinDatacomErrorMessage 逻辑 |
onMixinDatacomPropsChange | 属性发生变化时触发 |
使用 uniCloud.mixinDatacom
开发 datacom
组件需要以下步骤
mixin: [uniCloud.mixinDatacom]
uniCloud.mixinDatacom
的 data
状态,加载中mixinDatacomLoading
、加载出错提示 mixinDatacomErrorMessage
、处理数据及相关UI展现 mixinDatacomResData
uniCloud.mixinDatacom
中的 mixinDatacomGet()
或 mixinDatacomEasyGet()
方法请求云端数据库。这两种方法的区别如下:
mixinDatacomGet()
仅请求数据,自行处理各种状态和异常。mixinDatacomEasyGet()
在 mixinDatacomGet()
的基础之上封装了加载状态、分页及错误消息,可通过模板绑定。用起来更简单使用 uniCloud.mixinDatacom
开发 datacom
组件的优势
datacom
组件的属性uniClinetDB
APIonMixinDatacomPropsChange(needReset, changed) {}
参数 needReset
是否为 true
即可uniClinetDB
有新增属性时,组件代码也不需要跟随更新例如要开发一个datacom组件,名为uni-data-jql:
mixinDatacomEasyGet()
<!-- uni-data-jql.vue -->
<template>
<view>
<view v-if="mixinDatacomLoading">Loading...</view>
<view v-else-if="mixinDatacomErrorMessage">
请求错误:{{mixinDatacomErrorMessage}}
</view>
<view v-else="mixinDatacomResData">
<!-- 需要自行处理数据及相关UI展现 -->
{{mixinDatacomResData}}
</view>
</view>
</template>
<script>
export default {
mixins: [uniCloud.mixinDatacom],
data() {
return {}
},
created() {
// 调用 uniCloud.mixinDatacom 中的方法加载数据
this.mixinDatacomEasyGet()
},
methods: {
// 当组件属性发生变化时
onMixinDatacomPropsChange(needReset, changed) {
// needReset=true 需要重置已加载数据和分页信息,例如 collection,orderby
// changed,变化的属性名,类型为 Array,例如 ['collection', 'orderby']
if (needReset) {
// 清空已加载的数据
this.mixinDatacomResData = []
// 重置分页数据,如果没有分页不需要处理
this.mixinDatacomPage.size = this.pageSize // 重置分页大小
this.mixinDatacomPage.current = 0 // 重置当前分页
this.mixinDatacomPage.count = 0 // 重置数据总数
}
}
}
}
</script>
mixinDatacomGet()
需要多写些代码处理各种状态。如果mixinDatacomEasyGet
的封装无法灵活满足你的需求,可以使用这种方式。
<!-- uni-data-jql.vue -->
<template>
<view>
<view v-if="mixinDatacomLoading">Loading...</view>
<view v-else-if="mixinDatacomErrorMessage">
请求错误:{{mixinDatacomErrorMessage}}
</view>
<view v-else="mixinDatacomResData">
<!-- 需要自行处理数据及相关UI展现 -->
{{mixinDatacomResData}}
</view>
</view>
</template>
<script>
export default {
mixins: [uniCloud.mixinDatacom],
data() {
return {}
},
created() {
this.load()
},
methods: {
load() {
if (this.mixinDatacomLoading == true) {
return
}
this.mixinDatacomLoading = true
this.mixinDatacomGet().then((res) => {
this.mixinDatacomLoading = false
const {
data,
count
} = res.result
this.mixinDatacomResData = data
}).catch((err) => {
this.mixinDatacomLoading = false
this.mixinDatacomErrorMessage = err
})
},
// 当组件属性发生变化时
onMixinDatacomPropsChange(needReset, changed) {
// needReset=true 需要重置已加载数据和分页信息,例如 collection,orderby
// changed,变化的属性名,类型为 Array,例如 ['collection', 'orderby']
if (needReset) {
// 清空已加载的数据
this.mixinDatacomResData = []
// 重置分页数据,如果没有分页不需要处理
this.mixinDatacomPage.size = this.pageSize // 重置分页大小
this.mixinDatacomPage.current = 0 // 重置当前分页
this.mixinDatacomPage.count = 0 // 重置数据总数
}
}
}
}
</script>
做好这个uni-data-jql组件后,就可以在页面中使用了:
<template>
<view>
<uni-data-jql collection="table1"></uni-data-jql>
</view>
</template>
<script>
// jql.vue 组件
import UniData from "./jql.vue" // 如果符合easycom规范,无需本代码
export default {
components: {
UniData // 如果符合easycom规范,无需本代码
},
data() {
return {}
},
methods: {}
}
</script>
uniCloud.mixinDatacom
源码 为方便开发者理解mixinDatacom的工作原理,这里贴出mixinDatacom的源码:
export default {
props: {
localdata: {
type: Array,
default () {
return []
}
},
options: {
type: [Object, Array],
default () {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String],
default: false
},
gettreepath: {
type: [Boolean, String],
default: false
},
startwith: {
type: String,
default: ''
},
limitlevel: {
type: Number,
default: 10
},
groupby: {
type: String,
default: ''
},
groupField: {
type: String,
default: ''
},
distinct: {
type: [Boolean, String],
default: false
},
manual: {
type: Boolean,
default: false
}
},
data() {
return {
mixinDatacomLoading: false, // 网络请求状态
mixinDatacomHasMore: false, // 是否有更多数据
mixinDatacomResData: [], // 请求返回的数据,调用 loadData 后会更新
mixinDatacomErrorMessage: '', // 请求出错时的错误消息
mixinDatacomPage: {} // 分页信息,详情见 created 生命周期
}
},
created() {
this.mixinDatacomPage = {
current: this.pageCurrent, // 当前页面,初始化设置 props中的 pageCurrent
size: this.pageSize, // 页面大小,初始化设置 props中的 pageSize
count: 0, // 数据总数,getcount=true时有效
}
this.$watch(() => {
var al = [];
['pageCurrent',
'pageSize',
'localdata',
'collection',
'action',
'field',
'orderby',
'where',
'getont',
'getcount',
'gettree'
].forEach(key => {
al.push(this[key])
})
return al
}, (newValue, oldValue) => {
let needReset = false
let changed = []
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] !== oldValue[i]) {
needReset = true
changed.push(newValue[i])
}
}
if (newValue[0] !== oldValue[0]) {
this.mixinDatacomPage.current = this.pageCurrent
}
this.mixinDatacomPage.size = this.pageSize
this.onMixinDatacomPropsChange(needReset, changed)
})
},
methods: {
// props发生变化时被调用,在组件中覆盖此方法
// 非 pageCurrent,pageSize 改变时 needReset=true,需要重置数据
// changed,发生变化的属性名,类型为Array,例如 ['collection', 'action']
onMixinDatacomPropsChange(needReset, changed) {},
// 加载数据
mixinDatacomEasyGet({
getone = false,
success,
fail
} = {}) {
if (this.mixinDatacomLoading) {
return
}
this.mixinDatacomLoading = true
this.mixinDatacomErrorMessage = ''
this.mixinDatacomGet().then((res) => {
this.mixinDatacomLoading = false
const {
data,
count
} = res.result
if (this.getcount) {
this.mixinDatacomPage.count = count
}
this.mixinDatacomHasMore = data.length < this.pageSize
const responseData = getone ? (data.length ? data[0] : undefined) : data
this.mixinDatacomResData = responseData
if (success) {
success(responseData)
}
}).catch((err) => {
this.mixinDatacomLoading = false
this.mixinDatacomErrorMessage = err
fail && fail(err)
})
},
// 调用 uniClientDB 查询数据
mixinDatacomGet(options = {}) {
let db = uniCloud.database()
const action = options.action || this.action
if (action) {
db = db.action(action)
}
const collection = options.collection || this.collection
db = db.collection(collection)
const where = options.where || this.where
if (!(!where || !Object.keys(where).length)) {
db = db.where(where)
}
const field = options.field || this.field
if (field) {
db = db.field(field)
}
const groupby = options.groupby || this.groupby
if (groupby) {
db = db.groupBy(groupby)
}
const groupField = options.groupField || this.groupField
if (groupField) {
db = db.groupField(groupField)
}
const distinct = options.distinct !== undefined ? options.distinct : this.distinct
if (distinct === true) {
db = db.distinct()
}
const orderby = options.orderby || this.orderby
if (orderby) {
db = db.orderBy(orderby)
}
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.mixinDatacomPage.current
const size = options.pageSize !== undefined ? options.pageSize : this.mixinDatacomPage.size
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
const gettree = options.gettree !== undefined ? options.gettree : this.gettree
const gettreepath = options.gettreepath !== undefined ? options.gettreepath : this.gettreepath
const limitLevel = options.limitlevel !== undefined ? options.limitlevel : this.limitlevel
const startWith = options.startwith !== undefined ? options.startwith : this.startwith
const getOptions = {
getCount
}
const treeOptions = {
limitLevel,
startWith
}
if (gettree) {
getOptions.getTree = treeOptions
}
if (gettreepath) {
getOptions.getTreePath = treeOptions
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
return db
}
}
}