点击查看上面章节。
7. 按键绑定一键执行
- 在之前按键绑定状态的篇章里,我们介绍了获取
当前设备已绑定的一键执行列表
相关逻辑,但在这个篇章里,我们同样的需要在项目初始化时,获取当前家庭下可绑定的一键执行列表
数据,因此在这里我们通过 Redux dispatchgetTapToRunsAsync
异步 Action。
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { hideLoading, showLoading } from '@ray-js/ray';
import { devices, dpKit } from './devices';
import store from './redux';
import { getCurrentHomeInfoAsync, setIsInitialized } from './redux/modules/uiStateSlice';
import { getBindTapToRunRules, getTapToRunRulesAsync } from './redux/modules/sceneSlice';
import './styles/index.less';
interface Props {
devInfo: DevInfo;
// eslint-disable-next-line react/require-default-props
extraInfo?: Record<string, any>;
}
interface State {
devInfo: DevInfo;
}
const composeLayout = (SubComp: React.ComponentType<any>) => {
const { dispatch } = store;
return class PanelComponent extends Component<Props, State> {
async onLaunch(object: any) {
devices.common.init();
devices.common.onInitialized(device => {
dpKit.init(device);
// 在数据加载完毕之前,阻塞用户的操作行为
showLoading({ title: '', mask: true });
Promise.all([dispatch(getBindTapToRunRules())]).finally(() => {
dispatch(setIsInitialized(true));
hideLoading();
});
dispatch(getTapToRunRulesAsync());
dispatch(getCurrentHomeInfoAsync());
});
}
render() {
const { extraInfo } = this.props;
return (
<Provider store={store}>
<SubComp extraInfo={extraInfo} {...this.props} />
</Provider>
);
}
};
};
export default composeLayout;
👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。
- 在我们进入
选择联动页
后,假设当前已经获取到了可绑定的一键执行列表
数据,我们就可以根据这些数据来动态渲染一键执行列表,如下所示。
- 在我们选择完要绑定的一键执行后,点击保存时,此时需要 dispatch
bindAsync
异步 Action 来绑定当前设备的按键与一键执行的关联关系并同时刷新已绑定的一键执行列表,其中 query 的入参 dpId 和 dpValue 分别代表的是当前设备的按键 DP ID 和按键 DP Value,比如在按键1选中了双击时进入选择联动页时,那么传入的 DP ID 为 1,DP Value 为 double_click,即在设备的按键1上报 double_click 时,会自动触发当前绑定的一键执行。
import React from 'react';
import { Image, View, openCreateTapToRunScene, openEditScene, router, useQuery } from '@ray-js/ray';
import clsx from 'clsx';
import { useSelector } from 'react-redux';
import { TopBar, Empty, FixedBottom, RuleItem } from '@/components';
import Strings from '@/i18n';
import { TopBarHeight } from '@/components/top-bar';
import { getCachedSystemInfo } from '@/api/getCachedSystemInfo';
import { usePageReShow } from '@/hooks/usePageReShow';
import { useHideMenuButton } from '@/hooks/useHideMenuButton';
import {
bindAsync,
selectTapToRunRules,
updateTapToRunRulesAsync,
} from '@/redux/modules/sceneSlice';
import { useAppDispatch } from '@/redux';
import styles from './index.module.less';
import { SceneSelectQuery } from './index.type';
const FooterHeight = 88;
const { screenHeight } = getCachedSystemInfo();
const contentHeight = `${screenHeight - (TopBarHeight + FooterHeight)}px`;
export function SceneSelect() {
// 从 App 一键执行页面创建或编辑后需要刷新下数据源
usePageReShow(() => {
dispatch(updateTapToRunRulesAsync());
});
useHideMenuButton();
const dispatch = useAppDispatch();
const query: SceneSelectQuery = useQuery();
const tapToRunRules = useSelector(selectTapToRunRules);
const [fixedBottomHeight, setFixedBottomHeight] = React.useState('180rpx'); // 默认为 iphone 6 基准
const [activeIdx, setActiveIdx] = React.useState(-1);
const isEmpty = tapToRunRules.length === 0;
const handleItemClick = React.useCallback((idx: number) => {
return () => setActiveIdx(idx);
}, []);
const handleSave = React.useCallback(() => {
const data = tapToRunRules?.[activeIdx];
dispatch(bindAsync({ dpId: query.dpId, dpValue: query.dpValue, ruleId: data.id }));
router.back();
}, [tapToRunRules, activeIdx]);
const handleAddClick = React.useCallback(() => {
const rule = tapToRunRules[activeIdx];
if (!rule) {
// App 5.13.0 以上版本开始支持
openCreateTapToRunScene().catch(err => console.warn('openCreateTapToRunScene failed', err));
return;
}
openEditScene({ sceneId: rule.id });
}, [tapToRunRules, activeIdx]);
return (
<View className={styles.view}>
<TopBar title={Strings.getLang('sceneSelect')} isSubPage />
<View className={styles.content} style={{ marginBottom: fixedBottomHeight }}>
{isEmpty ? (
<Empty
style={{ height: contentHeight, marginTop: 0 }}
title={Strings.getLang('sceneSelectEmptyTip')}
/>
) : (
<View className={styles.list}>
{tapToRunRules?.map((data, idx) => {
const bindDeviceNums = data?.actions?.length ?? 0;
const isActive = activeIdx === idx;
return (
<RuleItem
key={idx}
isActive={isActive}
title={data.name}
subTitle={Strings.formatValue(
bindDeviceNums > 1 ? 'sceneBindDeviceNums' : 'sceneBindDeviceNum',
bindDeviceNums
)}
onClick={handleItemClick(idx)}
/>
);
})}
</View>
)}
<FixedBottom
className={styles.footer}
contentHeight="100rpx"
getFixedBottomHeight={setFixedBottomHeight}
>
<View
className={clsx(
styles.button,
(isEmpty || activeIdx === -1) && styles.button__disabled
)}
onClick={handleSave}
>
{Strings.getLang('save')}
</View>
<Image className={styles.imageAdd} src="/images/icon_add.png" onClick={handleAddClick} />
</FixedBottom>
</View>
</View>
);
}
export const bindAsync = createAsyncThunk<boolean, BindParams>(
'scene/bindAsync',
async (params, thunkApi) => {
await devices.common.model.abilities.tapToRun.bind({
dpId: params.dpId,
dpValue: params.dpValue,
ruleId: params.ruleId,
});
thunkApi.dispatch(updateBindTapToRunRulesAsync());
return true;
}
);
👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。
- 绑定成功后,页面会自动返回到
首页
,并且会刷新已绑定的一键执行列表,此时就完成了绑定一键执行的相关流程。
注意:在首页中,我们需要通过 `usePageReShow` hook 来监听页面的重新展示事件,当我们从 `选择联动页` 返回到 `首页` 时,会触发 `updateTapToRunRulesAsync` 异步 Action 来刷新已绑定的一键执行列表。
// 从 App 一键执行页面创建或编辑后需要刷新下数据源
usePageReShow(() => {
dispatch(updateTapToRunRulesAsync());
});
import { usePageEvent } from '@ray-js/ray';
import { useRef } from 'react';
/**
* 在页面第二次 onShow 的时候才会触发,用于一些首次不需要执行的逻辑情况,比如从 App 页面返回
*/
export function usePageReShow(fn: (...arg: any) => any) {
const isMounted = useRef(false);
usePageEvent('onShow', () => {
if (isMounted.current) fn();
isMounted.current = true;
});
}
8. 手动触发一键执行
- 在绑定成功回到
首页
后,我们可以点击已绑定的一键执行按键,会弹出确认手动执行的二次确认框,如下图所示。
- 在点击确认时,我们此时可以直接调用
SmartTapToRunAbility
的trigger
API 来触发一键执行,如下代码所示。
注意:我们过滤了一些非必要的代码逻辑
const { run: runTriggerRule } = useRequest(
(ruleId: string) => devices.common.model.abilities.tapToRun.trigger({ ruleId }),
{
manual: true,
loadingDelay: 1000,
onBefore: () => showLoading({ title: '', mask: true }),
onSuccess: (_, [ruleId]) => {
const scene = sceneDpList.find(d => d?.bindRule?.triggerRuleId === ruleId);
hideLoading();
return showToast({
title: Strings.formatValue('sceneTriggerSuccess', scene.bindRule.name),
});
},
onError: (_, [ruleId]) => {
const scene = sceneDpList.find(d => d?.bindRule?.triggerRuleId === ruleId);
hideLoading();
return showToast({
title: Strings.formatValue('sceneTriggerFailed', scene.bindRule.name),
icon: 'error',
});
},
}
)
const handleSceneItemClick = React.useCallback((data: (typeof sceneDpList)[number]) => {
return () => {
const { btnId, bindRule } = data;
if (!bindRule) {
setShowSwitchTypeDpId(`${btnId}`);
return;
}
DialogInstance.confirm({
message: Strings.getLang('triggerTip'),
cancelButtonText: Strings.getLang('cancel'),
confirmButtonText: Strings.getLang('confirm'),
})
.then(() => {
const triggerRuleId = data?.bindRule?.triggerRuleId;
runTriggerRule(`${triggerRuleId}`);
})
.catch(() => true);
};
}, []);
- 如果传入的
triggerRuleId
无误,对应的一键执行规则有效且关联的设备运行状态正常,那么就会触发一键执行,否则会提示触发失败。
9. 按键操作触发一键执行
- 在
按键绑定一键执行
的篇章里,我们假定了在设备的按键1上报 double_click 时会触发当前绑定的一键执行,那么在实体按键1双击操作时,如果正常上报了 double_click,那么就会正常触发一键执行,但务必要注意的是,本文介绍的一键执行必须在有网的环境下才可以使用。 - 假设在我们不存在实体设备的情况下,此时也可以借助涂鸦开发者平台的设备调试功能进行模拟上报,验证当前开发的功能是否正常,如下图所示。
10. 结束
- 恭喜你 🎉 完成了本教程的学习!
- 👉 立即免费领取开发资源,体验涂鸦 MiniApp 小程序开发。