智能小程序 Ray 开发面板 SDK —— 无线开关一键执行模板教程(二)

点击查看上面章节

6. 按键绑定状态

  1. 首先我们需要在项目初始化时,获取一些项目强依赖的一些数据,比如 当前设备已绑定的一键执行列表,因此在这里我们通过 Redux dispatch getBindTapToRunRules 异步 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;
  1. 可以看到这个异步 Action 通过 TapToRun 能力的 getBindTapToRunRules API 来获取到 当前设备已绑定的一键执行列表,然后再写入到 Redux Store 中。
export const getBindTapToRunRules = createAsyncThunk<BindTapToRunRules>(
  'scene/getBindTapToRunRules',
  async (_, thunkApi) => {
    try {
      const data = await devices.common.model.abilities.tapToRun.getBindTapToRunRules();
      thunkApi.dispatch(sceneSlice.actions.initBindTapToRunRules(data));
      return data;
    } catch (error) {
      return [];
    }
  }
);

export const selectTapToRunRules = (state: ReduxState) => state.scene.tapToRunRules;
  1. 现在,我们就可以回到 首页 通过 useSelector 获取到 Redux Store 中的 tapToRunRules 数据,并实现之前暂时未实现的逻辑代码以判断。
const sceneDpList = useCreation(() => {
 return getSceneDps(schema).map(btnId => {
   let bindDpId: number;
   let bindDpValue: string;
   const bindScene = bindTapToRunRules?.find(b => {
     const switchSceneSchema = dpSchema[`switch_type_${btnId}`] as DpSchema;
     bindDpId = +b?.associativeEntityId.split('#')?.[0]; // 已经绑定一键执行的功能点
     bindDpValue = b?.associativeEntityId.split('#')?.[1]; // 已经绑定一键执行的功能点值
     if (switchSceneSchema?.id === bindDpId) {
       return true;
     }
     return false;
   });
   const bindRule = bindScene?.associativeEntityValueList?.[0];
   return {
     btnId,
     bindDpId,
     bindDpValue,
     bindScene,
     bindRule,
   };
 });
}, [schema, bindTapToRunRules]);
  1. 最后,我们就可以根据解析好的额外数据(btnIdbindScenebindRule) 等来判断当前按键是否已经绑定了一键执行,以及在页面中展示名称或相关信息。
import React, { useState } from 'react';
import { useRequest } from 'ahooks';
import {
  Text,
  View,
  Image,
  router,
  showLoading,
  hideLoading,
  showToast,
  openEditScene,
} from '@ray-js/ray';
import { BindTapToRunRules } from '@ray-js/panel-sdk/lib/sdm/abilities/tapToRun/type';
import { ActionSheet, Dialog, DialogInstance, SwipeCell } from '@ray-js/smart-ui';
import Strings from '@/i18n';
import { Empty } from '@/components';
import { useAppDispatch } from '@/redux';
import { unbindAsync, updateTapToRunRulesAsync } from '@/redux/modules/sceneSlice';
import { mapObject2QueryString } from '@/utils/mapObject2QueryString';
import { devices } from '@/devices';
import { usePageReShow } from '@/hooks/usePageReShow';
import styles from './index.module.less';

interface Props {
  sceneDpList: Array<{
    btnId: string;
    bindDpId?: number;
    bindDpValue?: string;
    bindScene?: BindTapToRunRules[number];
    bindRule?: BindTapToRunRules[number]['associativeEntityValueList'][0];
  }>;
}

enum SwitchType {
  SingleClick = 'single_click',
  DoubleClick = 'double_click',
  LongPress = 'long_press',
}

const actions = Object.values(SwitchType).map(type => ({
  id: type,
  name: Strings.getLang(`sceneSwitchType_${type}`),
}));

export const Scene: React.FC<Props> = ({ sceneDpList }) => {
  const dispatch = useAppDispatch();
  const [showSwitchTypeDpId, setShowSwitchTypeDpId] = useState('');

  // 从 App 一键执行页面创建或编辑后需要刷新下数据源
  usePageReShow(() => {
    dispatch(updateTapToRunRulesAsync());
  });

  const hideActionSheet = React.useCallback(() => {
    setShowSwitchTypeDpId('');
  }, []);

  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 handleSelectSwitchType = React.useCallback(
    data => {
      const switchType = data?.detail?.id as string;
      const path = mapObject2QueryString('/scene-select', {
        dpId: showSwitchTypeDpId,
        dpValue: switchType,
      });
      hideActionSheet();
      return router.push(path);
    },
    [showSwitchTypeDpId]
  );

  const handleClose = React.useCallback((data: (typeof sceneDpList)[number]) => {
    return event => {
      const { position, instance } = event.detail;
      switch (position) {
        case 'cell':
          instance.close();
          break;
        case 'right':
          DialogInstance.confirm({
            message: Strings.getLang('unbindTip'),
            cancelButtonText: Strings.getLang('cancel'),
            confirmButtonText: Strings.getLang('confirm'),
          })
            .then(() => {
              const bindId = `${data?.bindScene?.bindId}`;
              dispatch(unbindAsync({ bindId })).then(result => {
                instance.close();
                if (result.meta.requestStatus === 'rejected') {
                  showToast({
                    title: Strings.getLang('requestFailed'),
                    icon: 'error',
                  });
                }
              });
            })
            .catch(() => instance.close());
          break;
        default:
      }
    };
  }, []);

  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);
    };
  }, []);

  const handleClickSetting = React.useCallback((data: (typeof sceneDpList)[number]) => {
    return (evt => {
      evt?.origin?.stopPropagation();
      const sceneId = data?.bindRule?.id;
      if (data?.bindRule?.id) {
        openEditScene({ sceneId });
      }
    }) as React.ComponentProps<typeof View>['onClick'];
  }, []);

  return (
    <View>
      {sceneDpList.length === 0 ? (
        <Empty title={Strings.getLang('sceneEmptyTip')} />
      ) : (
        <View>
          {sceneDpList.map((d, idx) => {
            const { bindRule, bindDpValue } = d;
            const itemText = bindRule?.name
              ? `${Strings.getLang(`sceneSwitchType_${bindDpValue}` as any)}: ${bindRule?.name}`
              : Strings.getLang('defaultName');
            return (
              <SwipeCell
                key={idx}
                asyncClose
                rightWidth={65}
                disabled={!bindRule} // 没有绑定规则的不可滑动解绑
                slot={{
                  right: <View className={styles.right}>{Strings.getLang('unbind')}</View>,
                }}
                onClose={handleClose(d)}
              >
                <View className={styles.item} onClick={handleSceneItemClick(d)}>
                  <View className={styles.content}>
                    <View className={styles.textWrapper}>
                      <Text className={styles.textNo}>{idx + 1}</Text>
                      <Text className={styles.textScene}>{itemText}</Text>
                    </View>
                  </View>
                  {bindRule && (
                    <View onClick={handleClickSetting(d)}>
                      <Image className={styles.imageIcon} src="/images/icon_triangle.png" />
                    </View>
                  )}
                </View>
              </SwipeCell>
            );
          })}
        </View>
      )}
      <Dialog id="smart-dialog" />
      <ActionSheet
        description={Strings.getLang('sceneSwitchTypeSelect')}
        cancelText={Strings.getLang('cancel')}
        show={!!showSwitchTypeDpId}
        actions={actions}
        onSelect={handleSelectSwitchType}
        onClose={hideActionSheet}
        onCancel={hideActionSheet}
      />
    </View>
  );
};

点击查看后面章节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT砖家涂拉拉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值