在此 Codelab 中,您将利用面板小程序开发构建出一个出行两轮车面板,通过 DP 协议、接口将实现以下能力:
详见 面板小程序 - 搭建环境
产品名称:新滑板车蓝牙
首页
需在涂鸦出行 APP 上使用
。灯控页
更多设置页
骑行模式页
首先需要创建一个户外出行类产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。
进入IoT 平台,点击左侧产品菜单,产品开发,创建产品,选择标准类目 -> 户外出行 -> 滑板车
:
选择功能点,这里根据自己需求选择即可,这些功能未选择不影响视频预览。
🎉 在这一步,我们创建了一个名为 PanelOutdoorScooter
的出行滑板车产品。
这部分我们在 小程序开发者
平台上进行操作,注册登录 小程序开发者平台 进行操作。
详细的操作路径可参考 面板小程序 - 创建面板小程序
这部分我们在 Tuya MiniApp Tools
上进行操作,打开 IDE 创建一个基于出行两轮车模板的面板小程序项目。
详细的操作路径可参考 面板小程序 - 初始化项目工程
上面的步骤我们已经初始化好了一个面板小程序
的开发模板,下面我们介绍下工程目录。
├── src
│ ├── @types // 全局类型定义
│ ├── api
│ │ └── atop.ts // 云端API
│ ├── components
│ │ ├── action-sheet-enum // 多选弹窗
│ │ ├── battery // 电池组件
│ │ ├── control-menu // 首页控制栏
│ │ ├── dp-list-item // dp数据展示项
│ │ ├── dynamic-number // 文字动态变化组件
│ │ ├── icon-svg // svg
│ │ ├── outdoor-slider // 滑动解锁组件
│ │ ├── outdoor-top // 首页TopBar组件
│ │ ├── signal-view // 4G、GPS信号展示组件
│ │ ├── top-bar // 其他页TopBar组件
│ │ └── index.ts
│ ├── constant // 常用配置
│ ├── devices // 设备模型
│ ├── hooks // hooks
│ ├── i18n // 多语言
│ ├── pages
│ │ ├── alarm // 告警页设置
│ │ ├── control // 骑行模式设置
│ │ ├── devInfo // 产品设备页
│ │ ├── home // 首页
│ │ ├── infoDetail // 设备详情页
│ │ ├── light // 灯控页
│ │ ├── more // 更多设置页
│ ├── redux // redux
│ ├── res // 本地图片
│ ├── styles // 全局样式
│ ├── utils // 常用工具方法
│ ├── app.config.ts
│ ├── app.less
│ ├── app.tsx
│ ├── composeLayout.tsx // 处理监听子设备添加,解绑,DP变化等
│ ├── global.config.ts
│ ├── routes.config.ts // 配置路由
产品需要开通出行增值服务才能对车辆进行操作,在代码路径 hooks/useCheckPermission
中 checkServiceAbility
方法通过调用 ty.outdoor.getValueAddedServicesAbility
获取设备增值服务能力,其中 outdoor_data_cloud_store
表示出行增值服务;如果未购买该服务或服务已过期,则需要提示去购买或续费,跳转到出行续费小程序
// C端续费能力判断 (未购买增值服务或服务已过期 弹窗提示)
const checkServiceAbility = async () => {
const res = await getCServicesAbility(devId)
const { inService, isPidHadVAS, commodityUrl, hadPopup } = res
dispatch(
updateCommonInfo({
inService,
isPidHadVAS,
commodityUrl: commodityUrl || '',
})
)
if (!hadPopup && isPidHadVAS && inService) {
// 品牌方有配置C端用户续费套餐有赠送
if (commodityUrl && commodityUrl !== '') {
ty.showModal({
title: '',
content: Strings.getLang('serviceActive'),
confirmText: Strings.getLang('goCheck'),
cancelText: Strings.getLang('confirm'),
showCancel: true,
cancelColor:
theme === 'dark' ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
success: ({ confirm, cancel }) => {
if (confirm) {
// 跳转详情
ty.openInnerH5({ url: commodityUrl })
}
},
})
} else {
// 涂鸦默认赠送
ty.showModal({
title: '',
content: Strings.getLang('serviceActive'),
showCancel: false,
confirmText: Strings.getLang('confirm'),
})
}
await setCServicesPop(devId)
}
}
因为有些设备是 ble + x 设置,下发 DP 操作时需要做一些校验
代码路径 hooks/useCheckPermission
// 检查DP下发权限
const checkPermissions = ({
dpCode,
successCb,
cancelCb,
}: {
dpCode: string
successCb: () => void
cancelCb?: () => void
}) => {
const { extContent, dpId } = dpSchema[dpCode]
const isExtNull = extContent === '' || extContent === undefined
const route = isExtNull ? 0 : JSON.parse(extContent).route
// 若智能服务已到期/未配置赠送用户未购买时,当用户通过蜂窝通道控制车辆时
if (
!inService &&
isPidHadVAS &&
(route === 0 || (route === 1 && !isBleOnline) || isExtNull)
) {
// 当用户通过蜂窝通道控制车辆时:route === 0 || isExtNull || route === 1 && !isBleOnline
ty.showModal({
title: '',
content: Strings.getLang('expireControl'),
showCancel: false,
confirmText: Strings.getLang('confirm'),
success: ({ confirm }) => {
if (confirm && dpCode === 'blelock_switch') {
cancelCb()
}
},
})
return
}
// 非ble+x设备或者蜂窝未激活, 拓展模块是禁用的则提示:扩展模块未激活,该功能无法使用
const notCheckHide = isBleXDevice && isActive
if (
!notCheckHide &&
interactionType === 'disable' &&
assocaitedDps.indexOf(dpId) !== -1
) {
ty.showModal({
title: '',
content: Strings.getLang('extensionDisable'),
confirmText: Strings.getLang('confirm'),
cancelText: Strings.getLang('cancel'),
success: ({ confirm, cancel }) => {
if (confirm && dpCode === 'blelock_switch') {
cancelCb()
}
if (cancel && dpCode === 'blelock_switch') {
cancelCb()
}
},
})
return
}
// 设备为蜂窝+蓝牙设备,当DP的route=0或1时,并且仅蜂窝在线时,部分DP进行二次确认弹窗提示:
const checkDpArray = [
dpCodes.start,
dpCodes.blelockSwitch,
dpCodes.bucketLock,
dpCodes.tailBoxLock,
]
if (
isBleXDevice &&
checkDpArray.indexOf(dpCode) !== -1 &&
isOnline &&
!isBleOnline
) {
if ([0, 1].indexOf(route) === -1) return
const val = dpValueS[dpCode]
if (val) return
ty.showModal({
title: '',
content: Strings.getLang(`blex_wifi_${dpCode}` as any),
confirmText: Strings.getLang('confirm'),
cancelText: Strings.getLang('cancel'),
success: ({ confirm, cancel }) => {
if (confirm) {
if (dpCode === 'blelock_switch') {
successCb()
} else {
actions[dpCode].set(true)
}
}
if (cancel) {
if (dpCode === 'blelock_switch') {
cancelCb && cancelCb()
}
console.log('取消 :>> ')
}
},
})
return
}
// ble+x设备并且蜂窝模组激活后,判断DP的高级能力
if (isBleXDevice && isActive && route === 2 && !isBleOnline) {
ty.showModal({
title: '',
content: Strings.getLang('retryConnectBle'),
showCancel: false,
confirmText: Strings.getLang('confirm'),
})
return
}
successCb()
}