在此 Codelab 中,您将利用面板小程序开发构建出一个出行两轮车面板,通过 DP 协议、接口将实现以下能力:
详见 面板小程序 - 搭建环境
产品名称:新滑板车蓝牙
首页
需在出行回归 APP 上使用
。更多设置页
首先需要创建一个户外出行类产品,定义产品有哪些功能点,然后面板中再根据这些功能点一一实现。
进入IoT 平台,点击左侧产品菜单,产品开发,创建产品,选择标准类目 -> 户外出行 -> 滑板车
:
选择功能点,这里根据自己需求选择即可,这些功能未选择不影响视频预览。
🎉 在这一步,我们创建了一个名为 PanelOutdoorScooter
的出行滑板车产品。
这部分我们在 小程序开发者
平台上进行操作,注册登录 小程序开发者平台 进行操作。
详细的操作路径可参考 面板小程序 - 创建面板小程序
这部分我们在 Tuya MiniApp Tools
上进行操作,打开 IDE 创建一个基于出行两轮车模板的面板小程序项目。
详细的操作路径可参考 面板小程序 - 初始化项目工程
上面的步骤我们已经初始化好了一个面板小程序
的开发模板,下面我们介绍下工程目录。
├── src
│ ├── @types // 全局类型定义
│ ├── api
│ │ ├── getCachedLaunchOptions.ts // 获取启动参数
│ │ ├── getCachedSystemInfo.ts // 获取设备信息
│ │ └── index.ts // 云端API
│ ├── components
│ │ ├── battery // 电池组件
│ │ ├── dynamic-number // 文字动态变化组件
│ │ ├── record // 骑行记录组件
│ │ ├── service-toast // C端续费服务弹窗
│ │ ├── signal // 4G、GPS信号展示组件
│ │ ├── sjs-slider // 滑块组件
│ │ ├── top-bar // TopBar组件
│ │ ├── unlock-slider // 滑动解锁组件
│ │ └── index.ts
│ ├── constant // 常用配置
│ ├── devices // 设备模型
│ ├── hooks // hooks
│ ├── i18n // 多语言
│ ├── pages
│ │ ├── home // 首页
│ │ ├── more // 更多设置页
│ ├── redux // redux
│ ├── res // 本地图片
│ ├── styles // 全局样式
│ ├── utils // 常用工具方法
│ ├── app.config.ts
│ ├── app.less
│ ├── app.tsx
│ ├── composeLayout.tsx // 处理监听子设备添加,解绑,DP变化等
│ ├── global.config.ts
│ ├── routes.config.ts // 配置路由
产品需要开通出行增值服务才能对车辆进行操作,在代码路径 src/components/service-toast/index.tsx
中通过调用 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)
}
}
滑动开关锁,下发blelock_switch
DP,下发前会检查增值服务是否过期,若服务过期则弹窗提示
// 车辆控制时检查权限
const checkPermissions = (params: ICheckPermissionParams) => {
const {
dpSchema,
dpCode,
inService,
isPidHadVAS,
isBleOnline,
cancelCb,
successCb,
} = params
const { extContent } = dpSchema[dpCode]
const isExtNull = extContent === '' || extContent === undefined
const route = isExtNull ? 0 : JSON.parse(extContent).route
// 若智能服务已到期/未配置赠送用户未购买时,当用户通过蜂窝通道控制车辆时
if (
!inService &&
isPidHadVAS &&
(route === 0 || (route === 1 && !isBleOnline) || isExtNull)
) {
ty.showModal({
title: '',
content: Strings.getLang('expireControl'), // 提示智能服务已到期,无法远程控制车辆
showCancel: false,
confirmText: Strings.getLang('confirm'),
success: ({ confirm }) => {
if (confirm && dpCode === 'blelock_switch') {
cancelCb()
}
},
})
return
}
successCb()
}
// 下发开关锁DP
const changeDp = (val) => {
const { extContent } = switchDpSchema
const isExtNull = extContent === '' || extContent === undefined
const route = isExtNull ? 0 : JSON.parse(extContent).route
if (route > 0 && isBleOnline) {
actions[blelockSwitch].set(val, { pipelines: [6, 5, 4, 3, 2, 1] })
} else {
actions[blelockSwitch].set(val)
}
}
// 开锁
const openLock = () => {
checkPermissions({
dpCode: 'blelock_switch',
inService,
isPidHadVAS,
dpSchema,
isBleOnline,
successCb: () => {
setLoading(true)
changeDp(true)
clearTimeout(openLoadingTimer)
openLoadingTimer = setTimeout(() => {
// 开锁超时
setValue(1)
setLoading(false)
}, 5000)
},
cancelCb: () => {
// 被禁用滑块返回
setValue(100)
setTimeout(() => {
setValue(1)
}, 50)
},
})
}
// 关锁
const closeLock = () => {
setLoading(true)
changeDp(false)
clearTimeout(closeLoadingTimer)
closeLoadingTimer = setTimeout(() => {
// 关锁超时
setValue(100)
setLoading(false)
}, 5000)
}