开发 Vue PeriodUse 移动端时段组件
通过使用 PeriodUse 时段组件,实现选择时间范围效果,也可以使用时段组件的预设功能快速选择常用时间范围。
设计思路
使用平台基于 vant Field 和 RadioGroup 组件进行二次封装的 Field 和 RadioGroup 组件,来实现 PeriodUse 时段组件核心逻辑,使用 Ext:use 方式来实现属性传递,使用 vant 的 CellGroup 、 Cell 、 Row 、 Col 来实现时段组件的布局逻辑,CSS 控制 RadioGroup 默认样式。
使用方法如下
<RadioGroup value={"none"} direction="horizontal" optionsLabel="label"
optionsValue="value"
options={optionsData}
onChange={handleGroupChange}>
</RadioGroup>
<Field clearTrigger={"focus"} inputAlign="center" refDataId={refDataId}
refColumnName={beginRefColumnName} refRow={refRow}
onUpdate:modelValue={event => handleFieldChange(event, "begin")}
onConfirm={event => handleFieldChange(event, "begin")}
></Field>
<span>-</span>
<Field clearTrigger={"focus"} inputAlign="center" refDataId={refDataId}
refColumnName={endRefColumnName} refRow={refRow}
onUpdate:modelValue={event => handleFieldChange(event, "end")}
onConfirm={event => handleFieldChange(event, "end")}
></Field>
<Col span={24} {...use.getProps("Col","presetCol")}>
</Col>
开发过程
新建组件
创建 vuemobilecomp 组件包,创建 PeriodUse 组件,注意大小写。创建组件功能参考《创建组件包和组件》。
配置组件名称、图标和模板
打开组件模板文件 UI2/comp/vuemobilecomp/components/PeriodUse/designer/PeriodUse.xml,如下所示
<element name="vuemobilecomp:PeriodUse"
tag-name="PeriodUse"
text="时段"
icon="PeriodUse.png"
discriminate-condition="executeXpath(name()='vuemobilecomp:PeriodUse')"
component-type="layout-container"
design-view="web-designer">
<events>
<include path="$UI/wxsys/comps/vueContainer/commonConfig.xml#//vue-events-mx/*"/>
</events>
<templates>
<template name="default">
<![CDATA[
<vuemobilecomp:PeriodUse xmlns:vuemobilecomp="$UI/comp/vuemobilecomp/components" >
</vuemobilecomp:PeriodUse>
]]></template>
</templates>
</element>
- element 中的 text 是组件名称
- element 中的 icon 是组件图标
- 图片文件位于 UI2/comp/vuemobilecomp/components/PeriodUse/designer/img/PeriodUse.png
- include 引用标准 W3C 事件,非组件事件。
- template 中的代码是组件模板
- 在页面添加组件后,模板是加在 w 文件中的代码
在组件面板中显示
打开组件面板配置文件 UI2/comp/vuemobilecomp/components/vuemobilecomp.components.xml,如下所示
<reg-info>
<!-- 组件目录注册 -->
<component-dirs framework="vue">PeriodUse</component-dirs>
<!-- 组件工具箱配置 -->
<toolbox>
</toolbox>
<!-- 兼容旧端工具箱配置 -->
<quickide-toolbox>
</quickide-toolbox>
<!-- 傻瓜式ide工具栏配置 -->
<uixide-toolbox framework="vue">
<catalog name="定制" order="13">
<item component-name="vuemobilecomp:PeriodUse" device="mx"/>
</catalog>
</uixide-toolbox>
<depend-css>
</depend-css>
<depend-js>
</depend-js>
</reg-info>
- catalog 中的 name 是组件分类名称
- item 中的 device 属性声明组件所在端
- item 中的 module 属性一般组件不需要,删除即可
切换到 vuemobilecomp 开发端后,点击 vuemobilecomp 组件包右侧的“更新配置文件”按钮,如下图所示。
更新后,切换到桌面端或移动端,在组件面板的高级分类中会显示出该组件
在设计器中显示
在设计器 js 文件 (UI2/comp/vuemobilecomp/components/PeriodUse/designer/PeriodUse.js) ,引入样式文件,和设计时 js 文件。
require("css!./css/PeriodUse").load();
vuemobilecompBaseComponent.PeriodUse = require("babel!./PeriodUse.react");
打开组件设计时 css 文件 (UI2/comp/vuemobilecomp/components/PeriodUse/designer/css/PeriodUse.css) ,修改 vant Radio 默认样式,只显示文字不显示图标,定义样式如下。
.van-period-use .van-radio-group.van-radio-group--horizontal {
justify-content: center;
}
.van-period-use .van-radio-group.van-radio-group--horizontal .van-radio {
flex: 1;
justify-content: center;
}
.van-period-use input[type="date"] {
width: 100%;
}
.van-period-use .van-period-datetimebox {
display: flex;
align-items: center;
justify-content: center;
}
设计时 js 文件,引入依赖的组件,完成组件设计时渲染。
<script lang="jsx">
let {commonProps, useRefData, useUse, useFireEvent, useData, omitProps} = window.vueContainer;
</script>
<script setup lang="jsx">
let {RadioGroup, Radio, Field, Cell, Row, Col} = vant;
let {useAttrs, useSlots, reactive, watch} = Vue;
let props = defineProps({
class: {
type: String,
default: ""
},
preSetSpan: {type: Number, default: 24},
preSetOffset: Number,
preSetGutter: Number,
span: {type: Number, default: 24},
offset: Number,
gutter: Number,
...commonProps
})
let emits = defineEmits([]);
let fireEvent = useFireEvent(emits);
let attrs = useAttrs();
let slots = useSlots();
let use = useUse(props);
let refData = useRefData(props);
let optionsData = [
{value: "today", label: "今日"},
{value: "week", label: "本周"},
{value: "month", label: "本月"},
{value: "year", label: "本年"}
]
let PeriodProRender = () => {
let {
class: className, ...others
} = props;
let {CellGroup} = vantBaseComponent;
others.class = " van-period-use " + className;
let omittedProps = omitProps(others, commonProps);
return <CellGroup {...omittedProps} {...attrs} inputAlign="right">
<Row>
<Col span={24} {...use.getProps("Col","presetCol")}>
<Cell>
<RadioGroup value={"none"} direction="horizontal">
{
optionsData.map(item => <Radio vSlots={{
icon: () => {
}
}} value={item.value}>{item.label}</Radio>)
}
</RadioGroup>
</Cell>
</Col>
<Col span={24} {...use.getProps("Col","datepickerrangepickerCol")} class="van-period-datetimebox">
<Field type={"date"} inputAlign="center"></Field>
<span>-</span>
<Field type={"date"} inputAlign="center"></Field>
</Col>
</Row>
</CellGroup>
}
</script>
<template>
<PeriodProRender >
<slot/>
</PeriodProRender>
</template>
配置组件属性
打开组件 meta 文件 UI2/comp/vuemobilecomp/components/PeriodUse/PeriodUse.meta.json,定义属性,如下所示
{
"PeriodUse": {
"properties": {
"dateRangeRef": {
"label": "日期区间",
"editor": "refDataMultipleColEditor",
"editor-parameter": {
"hideExprEditorTool": true,
"outputWsrcProps": [
{
"name": "refDataId",
"outName": "refDataId",
"label": "关联数据集id属性"
},
{
"name": "refRow",
"outName": "refRow",
"label": "关联数据集行属性"
},
{
"name": "refColumns",
"label": "关联数据集列属性",
"properties": {
"beginRefColumnName": {
"label": "开始时间"
},
"endRefColumnName": {
"label": "结束时间"
}
}
}
]
}
}
}
}
}
- 在 properties 中定义属性
- 所有属性都需要定义
- 不显示的属性不定义 label
- dateRangeRef 是个日期范围选择属性
- refDataMultipleColEditor 编辑器用来选择页面上的数据集组件,以及选择开始和结束两个字段,.w 中会生成 refDataId 、 beginRefColumnName 、endRefColumnName 、 refRow 4个属性。
配置引用
通过使用 ext:use 特性,引用平台封装的 Col 组件提供的属性。来减少组件元信息文件中的冗余配置,以达到简便开发目的。
{
"PeriodUse": {
"uses": {
"presetCol": {
"key": "presetCol",
"componentName": "vant:Col",
"label": "预设区配置",
"editor-parameter": {
"ignore-toolbar": [
"addRow",
"addLeftCol",
"addRightCol"
],
"ignore-events": [
"onClick"
]
}
},
"datepickerrangepickerCol": {
"key": "datepickerrangepickerCol",
"componentName": "vant:Col",
"label": "日期范围配置",
"editor-parameter": {
"ignore-toolbar": [
"addRow",
"addLeftCol",
"addRightCol"
],
"ignore-events": [
"onClick"
]
}
}
}
}
}
配置组件事件
打开组件 meta 文件 UI2/comp/vuemobilecomp/components/PeriodUse/PeriodUse.meta.json,定义事件,如下所示
{
"PeriodUse": {
"events": {
"onChange": {
"label": "改变",
"data": [
{
"label": "日期",
"name": "date"
},
{
"label": "日期字符串",
"name": "dateString"
}
]
}
}
}
}
- 在 events 中定义事件
- events 中的节点是事件名称
- label 是事件中文名称
- data 是参数名称
在运行时显示
在组件运行时 js 文件 (UI2/comp/vuemobilecomp/components/PeriodUse/PeriodUse.vue) 中,导入平台封装的 Field 和 RadioGroup 两个组件和 vant 的 CellGroup 、 Cell 、 Row 、 Col 组件,以及 Vuemobilecomp 、 BaseComponent 和样式文件。代码如下
import {commonProps, useUse, useFireEvent, omitProps, useRefRow} from "vue_addon/core";
import {Cell, Row, Col} from "vant";
import RadioGroup from "../../../vant/components/RadioGroup/RadioGroup.vue";
import CellGroup from "../../../vant/components/CellGroup/CellGroup.vue";
import Field from "../../../vant/components/Field/Field.vue";
import moment from "moment";
import {useAttrs, useSlots, reactive, watch} from "vue";
import Vuemobilecomp from "../vuemobilecomp/vuemobilecomp";
- Vuemobilecomp 封装了 getTimeDistance Api 。从预设值转换成时间范围数组。
Vuemobilecomp.getTimeDistance=function(type) {
const now = new Date();
const oneDay = 1000 * 60 * 60 * 24;
if (type === 'today') {
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
return [moment(now), moment(now.getTime() + (oneDay - 1000))];
}
if (type === 'week') {
let day = now.getDay();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
if (day === 0) {
day = 6;
} else {
day -= 1;
}
const beginTime = now.getTime() - day * oneDay;
return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))];
}
const year = now.getFullYear();
if (type === 'month') {
const month = now.getMonth();
const nextDate = moment(now).add(1, 'months');
const nextYear = nextDate.year();
const nextMonth = nextDate.month();
return [
moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
];
}
return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
}
vue_addon/core 文件中,封装了可获取操作数据的各种 Api。
详见《组件运行时 JS 文件》。
运行时样式文件,覆盖 vant RadioGroup 默认样式,只显示文字不显示图标。
.van-period-use .van-radio-group.van-radio-group--horizontal {
justify-content: center;
}
.van-period-use .van-radio-group.van-radio-group--horizontal .van-radio {
flex: 1;
justify-content: center;
}
.van-period-use .van-radio-group.van-radio-group--horizontal .van-radio .van-icon {
display: none;
}
.van-period-use .van-cell .van-cell__value .van-cell {
padding: 0;
}
.van-period-use .van-period-datetimebox {
display: flex;
align-items: center;
justify-content: center;
}
- 定义 render 方法,使用 use.getProps 来获取 ext:use 中设置的属性。代码如下:
let PeriodProRender = () => {
let { refDataId, beginRefColumnName, endRefColumnName, refRow, class: className, ...others } = props;
others.class = " van-period-use " + className;
let omittedProps = omitProps(others, commonProps);
return <CellGroup {...omittedProps} {...attrs} inputAlign="center">
<Row>
<Col span={24} {...use.getProps("Col","presetCol")}>
<Cell>
<RadioGroup value={"none"} direction="horizontal" optionsLabel="label"
optionsValue="value"
options={optionsData}
onChange={handleGroupChange}>
</RadioGroup>
</Cell>
</Col>
<Col span={24} {...use.getProps("Col","datepickerrangepickerCol")} class="van-period-datetimebox">
<Field clearTrigger={"focus"} inputAlign="center" refDataId={refDataId}
refColumnName={beginRefColumnName} refRow={refRow}
onUpdate:modelValue={event => handleFieldChange(event, "begin")}
onConfirm={event => handleFieldChange(event, "begin")}
></Field>
<span>-</span>
<Field clearTrigger={"focus"} inputAlign="center" refDataId={refDataId}
refColumnName={endRefColumnName} refRow={refRow}
onUpdate:modelValue={event => handleFieldChange(event, "end")}
onConfirm={event => handleFieldChange(event, "end")}
></Field>
</Col>
</Row>
</CellGroup>
}
- 接管 RadioGroup 组件 onChange 事件,把预设值转为时间数组,设置给数据集行对象 refRow 的 beginRefColumnName 和 endRefColumnName 属性上,通过 fireEvent 把事件抛给开发者。
let handleGroupChange = (value) => {
let dates = Vuemobilecomp.getTimeDistance(value);
let values = [];
let valueStrs = []
if (dates) {
values[0] = dates[0]?.toDate?.();
valueStrs[0] = dates[0].format('YYYY-MM-DD');
values[1] = dates[1]?.toDate?.();
valueStrs[1] = dates[1].format('YYYY-MM-DD');
}
fireEvent("change", [values, valueStrs], () => {
refRow[props.beginRefColumnName] = values[0];
refRow[props.endRefColumnName] = values[1];
})
}
- 接管 Field 组件的 onChange 事件,把日期类型的值改为日期数组和日期字符串数组,通过 fireEvent 把事件抛给开发者。
let handleFieldChange = (event, type) => {
let {date: value} = event;
let values, valueStrs;
if (value) {
if (type == "begin") {
values = [value, refRow[props.endRefColumnName] ]
valueStrs = [moment(value).format("YYYY-MM-DD"), moment(refRow[props.endRefColumnName]).format("YYYY-MM-DD")]
} else if (type == "end") {
values = [refRow[props.beginRefColumnName], value]
valueStrs = [moment(refRow[props.beginRefColumnName]).format("YYYY-MM-DD"), moment(value).format("YYYY-MM-DD")]
}
fireEvent("change", [values, valueStrs], () => {
})
}
}
调试组件
- 切换到桌面端或移动端
- 在页面上添加时段组件、选择数据、设置时段
- 保存、预览
发布组件
导出组件包
切换到 vuemobilecomp 开发端,点击 vuemobilecomp 组件包右侧的导出按钮,如下图所示,导出 vuemobilecomp.zip 文件
上传市场并审核
进入控制台,打开“组件管理-组件发布”功能,点击“发布组件”按钮,输入组件包信息,上传组件包文件,点击“提交审核”按钮
控制台管理员 system 进入控制台,打开“组件管理-组件管理”功能,在“状态”选择器中选择“审核中”或“更新审核中”,显示出提交待审核的组件,点击“审核”按钮,再点击“通过”按钮
打开应用,添加市场组件,可以看到时段组件
至此,时段组件开发完毕。