Commit 13850d16 authored by sliao's avatar sliao

init

parents
Pipeline #472 failed with stages
# 涂鸦面板模板
该模板基于 Ray 框架开发,提供给需要在涂鸦智能、智能生活等 App 中开发面板的业务开发人员,代码提供了 Ray 开发的基本使用方法,包含路由使用、页面跳转、设备操作、多语言等内容的使用,开发人员可参照用例并根据业务需求自行调整代码。
## 使用须知
使用该模板开发前, 需要对 Ray 框架有基本的了解,建议先查阅[Ray 开发文档](https://developer.tuya.com/cn/ray)
## 目录结构
```
ray-panel
├─ README.md
├─ .editorconfig
├─ .eslintrc.js
├─ .gitignore
├─ .npmrc
├─ .prettierrc.js
├─ commitlint.config.js
├─ package.json
├─ project.tuya.json
├─ ray.config.ts
├─ src
│ ├─ api
│ │ └─ index.ts
│ ├─ app.config.ts
│ ├─ app.tsx // 项目入口文件
│ ├─ components // 组件目录
│ │ ├─ connect.tsx
│ │ └─ index.tsx
│ ├─ composeLayout.tsx // 入口高阶,处理设备相关逻辑
│ ├─ config // 配置文件,根据需求删除或保留
│ │ ├─ dpCodes.ts
│ │ ├─ index.ts
│ │ └─ theme.ts
│ ├─ constant // 常量定义
│ │ └─ index.ts
│ ├─ global.config.ts // 项目全局配置项,参照 https://developer.tuya.com/cn/ray/guide/tutorial/global-config
│ ├─ i18n // 多语言本地配置
│ │ ├─ index.ts
│ │ └─ strings.ts
│ ├─ kits.deps.json // 由 IDE 生成,配置 TTT 能力依赖
│ ├─ pages // 页面目录,根据情况添加或删除
│ │ ├─ common
│ │ │ ├─ page4
│ │ │ │ ├─ index.config.ts
│ │ │ │ └─ index.tsx
│ │ │ └─ page6
│ │ │ ├─ index.config.ts
│ │ │ └─ index.tsx
│ │ └─ home
│ │ ├─ index.config.ts
│ │ ├─ index.module.less
│ │ └─ index.tsx
│ ├─ redux // redux 逻辑, 根据情况添加或删除
│ │ ├─ actions
│ │ │ ├─ common.ts
│ │ │ └─ theme.ts
│ │ ├─ index.ts
│ │ ├─ reducers
│ │ │ ├─ common.ts
│ │ │ └─ theme.ts
│ │ └─ store.ts
│ ├─ res // 资源目录,根据需求添加或删除
│ │ ├─ arrow.png
│ │ ├─ close.png
│ │ ├─ delete.png
│ │ ├─ index.ts
│ │ ├─ mode.png
│ │ ├─ notify.png
│ │ ├─ read.ts
│ │ └─ rect.png
│ ├─ routes.config.ts // 路由配置 参照https://developer.tuya.com/cn/ray/guide/tutorial/routes
│ ├─ utils // 工具方法存放目录
│ │ └─ index.ts
│ └─ variables.less
├─ tsconfig.json
└─ typings
└─ index.d.ts
```
## 组件的使用
使用 Ray 框架开发,若要兼容多端方案,则需要使用 Ray 框架所提供的组件,具体可参照[Ray 组件文档](https://developer.tuya.com/cn/ray/components)
## 基本操作及 Api
Ray 框架提供了大量的 Api 可供业务直接调用, 该 Api 已对各平台做了兼容处理,开发者不再需要运行时区分平台编写代码。 具体可参照 [API 文档](https://developer.tuya.com/cn/ray/api/authorize)
## 设备操作
为满足面板开发需求,Ray 框架还提供了设备操作相关的 Api ,开发者对设备进行操作时,可直接使用框架提供的设备操作能力,具体可参照 [设备想着能力](https://developer.tuya.com/cn/ray/api/device-kit/add-timer)
## 组件扩展
若 Ray 框架所提供的组件不能很好地满足业务需求,也可以在遵循[跨端适配](https://developer.tuya.com/cn/ray/guide/tutorial/env)规则的前提下,自行开发扩展组件,组件中的样式需要遵循[样式规则](https://developer.tuya.com/cn/ray/guide/tutorial/stylesheet)
## 面板 SDK
面板开发需要的一些方法, 我们将其做了封装,可能直接使用方法所提供的能力, 具体可参照 [面板 SDK](https://developer.tuya.com/cn/ray/panel)
## 多语言
面板模板中使用的多语言,封装了产品多语言和 UI 多语言, 可在业务中直接使用,具体可参照 [多语言](https://developer.tuya.com/cn/ray/panel/i18n/i18n)。 若该库无法满足业务需求,开发人员可基于 Ray API 提供的 [getLangKey](https://developer.tuya.com/cn/ray/api/get-lang-key)[getLangContent](https://developer.tuya.com/cn/ray/api/get-lang-content) 自行扩展多语言方案。
## 状态管理
面板中使用了 redux 进行数据管理, 中间件默认采用了 redux-thunk, 开发人员可根据自身情况自行切换。
## 能力分包
Ray 框架中设备控制、 小程序基本能力、 涂鸦特有能力等都需要[能力包](https://developer.tuya.com/cn/miniapp/api#%E8%83%BD%E5%8A%9B%E5%88%86%E5%8C%85)的支持, 模板默认添加了 BaseKit、 TYKit、MiniKit、DeviceKit 四大能力集, 开发者可根据需求进行管理。也可参照 [API 能力集成指南](https://developer.tuya.com/cn/miniapp/api)自行调整。
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-empty': [2, 'never'],
'subject-empty': [2, 'never'],
'scope-case': [1, 'always', 'pascal-case'],
'subject-case': [0],
'subject-full-stop': [0],
},
};
{
"schema": [
{
"attr": 1667,
"canTrigger": true,
"code": "switch_led",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_power",
"id": 20,
"mode": "rw",
"name": "灯开关",
"property": {
"type": "bool"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "work_mode",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_mode",
"id": 21,
"mode": "rw",
"name": "灯模式",
"property": {
"range": [
"white",
"colour",
"scene",
"music"
],
"type": "enum"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "bright_value",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-liangdu1",
"id": 22,
"mode": "rw",
"name": "亮度值",
"property": {
"min": 10,
"max": 1000,
"scale": 0,
"step": 1,
"type": "value"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "temp_value",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_half",
"id": 23,
"mode": "rw",
"name": "冷暖值",
"property": {
"min": 0,
"max": 1000,
"scale": 0,
"step": 1,
"type": "value"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "colour_data",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-yanse",
"id": 24,
"mode": "rw",
"name": "彩光",
"property": {
"type": "string",
"maxlen": 255
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "scene_data",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-shoucang",
"id": 25,
"mode": "rw",
"name": "场景",
"property": {
"type": "string",
"maxlen": 255
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "countdown",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-a_nav_timer",
"id": 26,
"mode": "rw",
"name": "灯倒计时",
"property": {
"unit": "s",
"min": 0,
"max": 86400,
"scale": 0,
"step": 1,
"type": "value"
},
"type": "obj"
},
{
"attr": 128,
"canTrigger": true,
"code": "music_data",
"defaultRecommend": false,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-deng",
"id": 27,
"mode": "wr",
"name": "音乐灯",
"property": {
"type": "string",
"maxlen": 255
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "control_data",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_box2",
"id": 28,
"mode": "wr",
"name": "调节",
"property": {
"type": "string",
"maxlen": 255
},
"type": "obj"
},
{
"attr": 128,
"canTrigger": true,
"code": "power_memory",
"defaultRecommend": false,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-Trigger",
"id": 33,
"mode": "rw",
"name": "断电记忆",
"type": "raw"
},
{
"attr": 128,
"canTrigger": true,
"code": "do_not_disturb",
"defaultRecommend": false,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-dp_mode",
"id": 34,
"mode": "rw",
"name": "勿扰模式",
"property": {
"type": "bool"
},
"type": "obj"
},
{
"attr": 128,
"canTrigger": true,
"code": "switch_gradient",
"defaultRecommend": false,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-dp_mode",
"id": 35,
"mode": "rw",
"name": "开关渐变",
"type": "raw"
},
{
"attr": 128,
"canTrigger": true,
"code": "mix_light_scene",
"defaultRecommend": false,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-dp_mode",
"id": 36,
"mode": "rw",
"name": "混光场景",
"type": "raw"
},
{
"attr": 640,
"canTrigger": true,
"code": "mix_rgbcw",
"defaultRecommend": true,
"editPermission": false,
"executable": true,
"extContent": "",
"iconname": "icon-deng",
"id": 51,
"mode": "rw",
"name": "混光",
"type": "raw"
},
{
"attr": 1664,
"canTrigger": true,
"code": "fan_switch",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_power2",
"id": 60,
"mode": "rw",
"name": "风扇开关",
"property": {
"type": "bool"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "fan_mode",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-a_mode_fan",
"id": 61,
"mode": "rw",
"name": "风模式",
"property": {
"range": [
"fresh",
"nature"
],
"type": "enum"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "fan_speed",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-FanSpeed",
"id": 62,
"mode": "rw",
"name": "风速",
"property": {
"min": 1,
"max": 100,
"scale": 0,
"step": 1,
"type": "value"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "fan_direction",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_mode",
"id": 63,
"mode": "rw",
"name": "风向",
"property": {
"range": [
"forward",
"reverse"
],
"type": "enum"
},
"type": "obj"
},
{
"attr": 1664,
"canTrigger": true,
"code": "fan_countdown_left",
"defaultRecommend": true,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_time3",
"id": 64,
"mode": "rw",
"name": "风扇倒计时",
"property": {
"unit": "min",
"min": 0,
"max": 540,
"scale": 0,
"step": 1,
"type": "value"
},
"type": "obj"
},
{
"attr": 1152,
"canTrigger": true,
"code": "fan_beep",
"defaultRecommend": false,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-dp_voice",
"id": 66,
"mode": "rw",
"name": "蜂鸣器",
"property": {
"type": "bool"
},
"type": "obj"
},
{
"attr": 1152,
"canTrigger": true,
"code": "fault",
"defaultRecommend": false,
"editPermission": true,
"executable": true,
"extContent": "",
"iconname": "icon-baojing",
"id": 67,
"mode": "ro",
"name": "故障告警",
"property": {
"label": [
"motor_fault"
],
"type": "bitmap",
"maxlen": 1
},
"type": "obj"
}
],
"panelConfig": {
"bic": [
{
"code": "timer",
"selected": false
},
{
"code": "jump_url",
"selected": false
}
]
},
"pv": "2.0",
"icon": "https://images.tuyacn.com/smart/icon/bay1667456326777tesu/df74fc5b76dbb325c175c5ffbe189828.png",
"isOnline": true,
"uuid": "vdevo166780480910513",
"bv": "2.0",
"ability": 0,
"devId": "vdevo166780480910513",
"productId": "hcwquhbmkmjatays",
"dps": {
"20": false,
"21": "white",
"22": 10,
"23": 0,
"24": "",
"25": "",
"26": 0,
"27": "",
"28": "",
"34": false,
"60": true,
"61": "fresh",
"62": 1,
"63": "forward",
"64": 0,
"66": false,
"67": 0
},
"ip": "36.27.64.253",
"activeTime": 1667804809,
"isShare": false,
"name": "fengshandeng-vdevo",
"i18nTime": 1667804685162,
"localKey": "edb05e1e62e54406",
"dpCodes": {
"switch_led": false,
"work_mode": "white",
"bright_value": 10,
"temp_value": 0,
"colour_data": "",
"scene_data": "",
"countdown": 0,
"music_data": "",
"control_data": "",
"do_not_disturb": false,
"fan_switch": true,
"fan_mode": "fresh",
"fan_speed": 1,
"fan_direction": "forward",
"fan_countdown_left": 0,
"fan_beep": false,
"fault": 0
},
"capability": 1025,
"category": "fsd",
"attribute": 274877906948,
"devAttribute": 0,
"devTimezoneId": "Asia/Shanghai",
"isCloudOnline": true,
"latitude": "",
"longitude": "",
"dpName": {},
"dpsTime": {
"20": 1667805655637,
"21": 1667804809165,
"22": 1667804809165,
"23": 1667804809165,
"24": 1667804809165,
"25": 1667804809165,
"26": 1667804809165,
"27": 1667804809165,
"28": 1667804809165,
"33": 1667804809165,
"34": 1667804809165,
"35": 1667804809165,
"36": 1667804809165,
"51": 1667804809165,
"60": 1667895485379,
"61": 1667804809165,
"62": 1667804809165,
"63": 1667804809165,
"64": 1667804809165,
"66": 1667804809165,
"67": 1667804809165
},
"isVirtualDevice": true,
"idCodes": {
"20": "switch_led",
"21": "work_mode",
"22": "bright_value",
"23": "temp_value",
"24": "colour_data",
"25": "scene_data",
"26": "countdown",
"27": "music_data",
"28": "control_data",
"33": "power_memory",
"34": "do_not_disturb",
"35": "switch_gradient",
"36": "mix_light_scene",
"51": "mix_rgbcw",
"60": "fan_switch",
"61": "fan_mode",
"62": "fan_speed",
"63": "fan_direction",
"64": "fan_countdown_left",
"66": "fan_beep",
"67": "fault"
},
"codeIds": {
"switch_led": 20,
"work_mode": 21,
"bright_value": 22,
"temp_value": 23,
"colour_data": 24,
"scene_data": 25,
"countdown": 26,
"music_data": 27,
"control_data": 28,
"power_memory": 33,
"do_not_disturb": 34,
"switch_gradient": 35,
"mix_light_scene": 36,
"mix_rgbcw": 51,
"fan_switch": 60,
"fan_mode": 61,
"fan_speed": 62,
"fan_direction": 63,
"fan_countdown_left": 64,
"fan_beep": 66,
"fault": 67
}
}
\ No newline at end of file
{
"name": "device-panel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"lint": "eslint src --ext .js,.jsx,.ts,.tsx --fix",
"test": "echo \"Error: no test specified\" && exit 1",
"start:native": "ray start -t native --verbose",
"start:tuya": "ray start -t tuya --verbose",
"build:native": "ray build -t native --verbose",
"build:tuya": "ray build -t tuya --verbose"
},
"dependencies": {
"@ray-js/panel-sdk": "^1.1.3",
"@ray-js/ray": "^0.6.13",
"deep-object-diff": "^1.1.0",
"react-redux": "^7.2.1",
"redux": "^4.1.2",
"redux-actions": "^2.6.5",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.4.0"
},
"devDependencies": {
"@commitlint/cli": "^7.2.1",
"@commitlint/config-conventional": "^9.0.1",
"@ray-js/cli": "^0.6.13",
"@types/lodash": "^4.14.182",
"@types/redux-actions": "^2.6.2",
"@types/redux-logger": "^3.0.9",
"compressing": "^1.5.1",
"core-js": "^3.19.1",
"eslint-config-tuya-panel": "^0.4.1",
"eslint-plugin-literal-check": "^0.1.2",
"eslint-plugin-prettier": "^3.0.1",
"husky": "^1.2.0",
"lint-staged": "^10.2.11",
"prettier": "^1.16.4",
"typescript": "^4.4.3"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS --config commitlint.config.js",
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"git add"
],
"*.{json,md,yml,yaml}": [
"prettier --write",
"git add"
]
},
"author": "",
"license": "ISC"
}
{
"projectname": "fanLight",
"i18n": false,
"description": "",
"baseversion": "2.9.5",
"miniprogramRoot": "./dist/tuya",
"dependencies": {
"BaseKit": "2.4.11",
"MiniKit": "2.5.7",
"TYKit": "2.3.4",
"DeviceKit": "2.4.7"
},
"productId": "hcwquhbmkmjatays"
}
\ No newline at end of file
// 提供给 cli 构建使用的文件,使用 cjs 语法
const config = {
resolveAlias: {},
};
module.exports = config;
import React from 'react';
import { kit } from '@ray-js/panel-sdk';
import 'ray';
import '@/i18n';
import composeLayout from './composeLayout';
const { initPanelEnvironment } = kit;
initPanelEnvironment({ useDefaultOffline: true });
class App extends React.Component {
componentDidMount() {
console.info('app did mount ');
}
render() {
return this.props.children;
}
}
export default composeLayout(App);
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
type ChildrenType = (props: any) => React.ReactElement | null;
const Connect = ({ children, ...props }: { children: ChildrenType }) => children(props);
export default connect(
(state: any, { mapStateToProps }: { mapStateToProps: any }) => mapStateToProps(state),
(dispatch: any, { mapDispatchToProps }: { mapDispatchToProps: any }) => {
if (typeof mapDispatchToProps === 'object') {
return bindActionCreators(mapDispatchToProps, dispatch);
}
if (typeof mapDispatchToProps === 'function') {
return mapDispatchToProps(dispatch);
}
return {};
}
)(Connect);
export { default as Connect } from './connect';
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { kit } from '@ray-js/panel-sdk';
import { actions, store } from '@/redux';
const { initDevInfo } = kit;
interface Props {
devInfo: DevInfo;
// eslint-disable-next-line react/require-default-props
extraInfo?: Record<string, any>;
// eslint-disable-next-line react/require-default-props
preload?: boolean;
}
interface State {
devInfo: DevInfo;
}
const composeLayout = (Comp: React.ComponentType<any>) => {
const { dispatch } = store;
return class PanelComponent extends Component<Props, State> {
constructor(props) {
super(props);
this.state = {
devInfo: null,
};
}
async onLaunch(object: any) {
console.log('app onLaunch: ', object);
const devInfo = await initDevInfo();
this.setState({ devInfo });
if (devInfo && devInfo.devId) {
dispatch(actions.common.devInfoChange(devInfo));
}
}
onShow(object: any) {
// console.log('app onShow: ', object);
}
onHide(object: any) {
// console.log('app onHide: ', object);
}
onError(object: any) {
// console.log('app onError: ', object);
}
onPageNotFound(object: any) {
// console.log('app onPageNotFound: ', object);
}
onUnhandledRejection(object: any) {
// console.log('app onUnhandledRejection: ', object);
}
onThemeChange(object: any) {
// console.log('app onThemeChange', object);
}
render() {
const { extraInfo } = this.props;
const { devInfo } = this.state;
return (
<Provider store={store}>
{devInfo && <Comp devInfo={devInfo} extraInfo={extraInfo} {...this.props} />}
</Provider>
);
}
};
};
export default composeLayout;
export default {};
import theme from './theme';
import dpCodes from './dpCodes';
export { dpCodes, theme };
export default {
type: 'light', // light or dark or custom string
global: {
brand: '#f8f8f8', // 品牌色(主题色)
bgColor: '#f8f8f8', // 背景色
fontSizeBase: 1, // 字体基准比例
dividerColor: '#e5e5e5', // 分隔线颜色
success: '#00C800', // 成功颜色
warning: '#FAAE17', // 警告颜色
error: '#F4182C', // 失败
mask: 'rgba(0, 0, 0, 0.7)', // 遮罩颜色
text: {
light: '#333', // 字体在 light 下的颜色
dark: '#fff', // 字体在 dark 下的颜色
},
},
brickButton: {
bgColor: '#54800F',
},
slider: {
light: {
trackRadius: 2, // 滑块圆角
trackHeight: 4, // 滑块高度
minimumTrackTintColor: '#FF0000', // 最小值颜色
maximumTrackTintColor: '#e5e5e5', // 最大值颜色
thumbSize: 24, // 滑块圆的尺寸
thumbRadius: 14, // 滑块圆的圆角
thumbTintColor: '#fff', // 滑块的颜色
},
dark: {
trackRadius: 2,
trackHeight: 4,
minimumTrackTintColor: '#FF0000',
maximumTrackTintColor: 'rgba(255, 255, 255, 0.3)',
thumbSize: 24,
thumbRadius: 14,
thumbTintColor: '#fff',
},
},
};
export const DEV_INFO_CHANGE = '_DEVINFOCHANGE_';
export const DEVICE_INFO_CHANGE = '_DEVICEINFOCHANGE_';
export const RESPONSE_UPDATE_DP = 'RESPONSE_UPDATE_DP';
export const CHANGE_DP = 'CHANGE_DP';
import { GlobalConfig } from '@ray-js/types';
export const wechat = {
window: {
backgroundColor: '#E3EEF6',
navigationBarTitleText: 'Fan',
navigationBarBackgroundColor: '#E3EEF6',
navigationBarTextStyle: 'black',
},
}
export const web = {
window: {
backgroundColor: '#E3EEF6',
navigationBarTitleText: 'Fan',
},
};
export const tuya = {
window: {
backgroundColor: '#E3EEF6',
navigationBarTitleText: 'Fan',
navigationBarBackgroundColor: '#E3EEF6',
navigationBarTextStyle: 'black',
},
};
const globalConfig: GlobalConfig = {
basename: '',
};
export default globalConfig;
import { kit } from '@ray-js/panel-sdk';
import strings from './strings';
const { I18N } = kit;
const Strings = new I18N(strings);
export default Strings;
export default {
en: {
hit_empty: 'Empty Value!',
hit_input: 'Input',
dp_mode_rw: 'Read and Write',
dp_mode_wr: 'Write only',
dp_mode_ro: 'Read only',
dp_type_value: 'Number',
dp_type_enum: 'Enum',
dp_type_bool: 'Boolean',
dp_type_string: 'String',
dp_type_raw: 'Raw',
dp_type_bitmap: 'Bitmap',
dp_type_fault: 'Fault',
title: 'Title',
cancel: 'Cancel',
confirm: 'Confirm',
unit: 'Unit',
millisecond: 'Millisecond',
time: 'Time',
timestamp: 'Timestamp',
action_none_input: 'no input parameters',
func_name: 'Function Name',
add_item: 'Add',
dp_name: 'DP Name',
raw_tips: 'Please enter a valid binary format',
max_len_tips: 'The inputable string is out of length',
thing_empty: 'The current device model information is empty',
hang:
'The current device does not have any function points, and the template displays a blank screen. If the function points are normal, delete the logic in this step',
},
zh: {
hit_empty: '空值!',
hit_input: '输入',
dp_mode_rw: '可下发可上报',
dp_mode_wr: '只下发',
dp_mode_ro: '只上报',
dp_type_value: '数值型',
dp_type_enum: '枚举型',
dp_type_bool: '布尔型',
dp_type_string: '字符串型',
dp_type_raw: '透传型',
dp_type_bitmap: 'BITMAP型',
dp_type_fault: '故障型',
title: '标题',
cancel: '取消',
confirm: '确认',
unit: '单位',
millisecond: '毫秒',
time: '时间',
timestamp: '时间戳',
action_none_input: '无输入参数',
func_name: '功能名称',
add_item: '添加元素',
dp_name: 'DP 名称',
raw_tips: '请输入合法的二进制形式',
max_len_tips: '可输入的字符串超出长度了',
thing_empty: '当前设备物模型信息为空',
hang: '当前设备不存在功能点,模板会白屏状态,如为正常需求,请自行删除此处判断逻辑',
},
};
export const web = {
backgroundColor: '#f2f4f6',
navigationBarTitleText: 'Ray 基础组件',
};
export const wechat = {
backgroundColor: '#f2f4f6',
navigationBarTitleText: 'Ray 基础组件',
};
export const native = {
navigationBarTitleText: '查看路由信息',
disableScroll: true,
};
import { router, location } from 'ray';
import React from 'react';
import { Button, Text, View } from '@ray-js/components';
import styles from '../../home/index.module.less';
function Page4() {
console.log(location);
return (
<View>
<Button
className={styles.button}
onClick={() => {
router.back();
}}
>
<Text className={styles.bTitle}>返回上一页</Text>
</Button>
</View>
);
}
export default Page4;
export const web = {
backgroundColor: '#f2f4f6',
navigationBarTitleText: 'Ray 基础组件',
};
export const wechat = {
backgroundColor: '#f2f4f6',
navigationBarTitleText: 'Ray 基础组件',
};
export const native = {
backgroundColor: '#f2f4f6',
navigationBarTitleText: '设备dp点及操作',
};
import React from 'react';
import { View, Text } from '@ray-js/components';
import { hooks } from '@ray-js/panel-sdk';
import Strings from '@/i18n';
import { useSelector } from '@/redux';
const { useDpState } = hooks;
function Page10() {
const devInfo = useSelector(state => state.devInfo);
// const dpState = useSelector(state => state.dpState);
const [dpState] = useDpState();
console.log(devInfo, dpState);
return (
<View style={{ flex: 1 }}>
<Text>dp点及状态</Text>
<View>
{Object.keys(dpState).map(key => {
return (
<View
key={key}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
display: 'flex',
paddingLeft: '30rpx',
paddingRight: '30rpx',
}}
>
<Text style={{ flex: 1 }}>{Strings.getDpLang(key)}: </Text>
<Text>{String(dpState[key])}</Text>
</View>
);
})}
</View>
</View>
);
}
export default Page10;
// export const web = {
// backgroundColor: '#f2f4f6',
// navigationBarTitleText: 'Ray 面板模板',
// };
// export const wechat = {
// backgroundColor: '#f2f4f6',
// navigationBarTitleText: 'Ray 面板模板',
// };
// export const tuya = {
// backgroundColor: '#f2f4f6',
// navigationBarTitleText: 'Ray 面板模板',
// };
// export const native = {
// backgroundColor: 'transparent',
// isBleOfflineOverlay: false,
// useSafeAreaView: true,
// navigationBarTitleText: 'Ray 面板模板',
// };
.button {
display: flex;
flex-direction: column;
justify-content: center;
color: 000000;
align-items: center;
width: 64px;
}
.bTitle {
color: black;
font-size: 28rpx;
font-size: 12px;
text-align: center;
margin-top: 8px;
}
.page {
height: 100vh;
padding: 11px 21px 40px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.header {
display: flex;
color: #000000;
width: 100%;
justify-content: flex-start;
}
.icon {
width: 7px;
height: 14px;
}
.title {
font-size: 16px;
margin-left: 12px;
}
.timer {
margin-left: 9px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 5px 16px;
gap: 10px;
background: rgba(68, 68, 68, 0.06);
border-radius: 100px;
}
.fanBg {
width: 264px;
height: 264px;
// margin-top: ;
position: absolute;
top: 0;
left: 0;
}
.fanGroup {
position: relative;
width: 264px;
height: 264px;
align-self: center;
display: flex;
// background-image: 100% 100% url('../../../public/icon-fan-bg.png');
}
.fan {
display: block;
width: 264px;
height: 264px;
margin: 0 auto;
z-index: 1;
animation: 1s spin linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.fantBottom {
padding: 0 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
font-size: 12px;
}
.fanBottomTint {
font-size: 12px;
}
.flexRowBetween {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
position: relative;
}
.modalBg {
position: absolute;
left: 0;
right: 0;
bottom: 101px;
display: flex;
background-color: white;
min-height: 127px;
flex-direction: column;
width: 90vw;
z-index: 999;
box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.05);
border-radius: 24px;
}
.swiper {
width: 100%;
height: 100%;
background-color: #000000;
}
.middleware {
width: 36px;
height: 16px;
margin-bottom: -1px;
}
.sliderBg {
display: flex;
flex-direction: column;
gap: 4px;
// padding: 0 12px;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-start {
justify-content: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-evenly {
justify-content: space-evenly;
}
.justify-around {
justify-content: space-around;
}
.alignCenter {
display: flex;
align-items: center;
}
.menuImage {
width: 64px;
height: 64px;
}
.lightModal {
display: flex;
flex-direction: column;
// margin: auto;
width: 340px;
height: 424px;
background-color: white;
border-radius: 24px;
// position: absolute;
// top: calc(50vh - 175px);
margin-top: 90px;
padding: 20px 0 40px;
color: #000000;
z-index: 9999;
}
.lightModalTop {
display: flex;
align-items: center;
padding: 0 20px;
width: 100%;
justify-content: space-between;
font-size: 16px;
margin-bottom: 40px;
}
.lightModalClose {
width: 24px;
height: 24px;
}
.lightMiddle {
// width: 100%;
height: 155px;
border-radius: 16px 16px 0 0;
background: linear-gradient(-90deg, #cdecfe 0%, #ffffff 45.46%, #ffffff 53.61%, #ffca5c 100%);
position: relative;
margin: 0 3px;
}
.modalback {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
display: flex;
// align-items: center;
justify-content: center;
}
.moveCircle {
width: 28px;
height: 28px;
border: 3px solid #ffffff;
border-radius: 14px;
position: absolute;
background-color: #ffca5c;
z-index: 10001;
}
.progressBg {
// width: 100%;
height: 40px;
border-radius: 0 0 16px 16px;
background-color: #e5e5e5;
display: flex;
align-items: center;
padding-left: 16px;
font-size: 16px;
position: relative;
overflow: hidden;
margin: 0 3px;
}
.progressSun {
width: 21px;
height: 21px;
margin-right: 9px;
z-index: 9999;
}
.progressTitle {
z-index: 9999;
}
.progressBar {
background-color: white;
position: absolute;
left: 1px;
top: 1px;
bottom: 1px;
z-index: 2;
}
.lightBtn {
width: 310px;
height: 56px;
border-radius: 24px;
align-items: center;
justify-content: center;
display: flex;
background-color: rgba(0, 0, 0, 0.5);
align-self: center;
margin-top: 48px;
}
.lightBtnIcon {
width: 18px;
height: 26px;
margin-right: 14px;
}
.lightBtnTitle {
color: white;
font-size: 18px;
}
.lightBtnSubtitle {
color: rgba(255, 255, 255, 0.5);
font-size: 14px;
}
.swiperItem {
display: flex;
align-items: center;
justify-content: space-around;
padding-top: 24px;
}
.swiperItemNext {
display: flex;
align-items: center;
padding-top: 24px;
gap: 18px;
padding-left: 20px;
}
.verticalBg {
}
.verticalItem {
display: flex;
align-items: flex-start;
}
.verticalNumBg {
margin-left: 12px;
flex: 1;
height: 64px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 24px;
// padding: 0 18px;
display: flex;
justify-items: space-between;
font-size: 30px;
font-family: 'DIN';
}
import { Button, ScrollView, Text, View, Swiper, SwiperItem} from '@ray-js/components';
import {
getSystemInfo,
onNetworkStatusChange,
offNetworkStatusChange,
onBluetoothAdapterStateChange,
offBluetoothAdapterStateChange,
showToast,
showLoading,
hideLoading,
getAppInfo,
bluetoothIsPowerOn,
publishDps,
getDeviceInfo,
openDeviceDetailPage,
openTimerPage,
} from '@ray-js/api';
import { hooks } from '@ray-js/panel-sdk';
import { router, usePageEvent } from 'ray';
import React from 'react';
import { useSelector } from '@/redux';
import styles from './index.module.less';
import { Image, Slider, Modal } from '@ray-js/components'
import iconTempr from '../../../public/icon-temperature.png'
import iconFanBg from '../../../public/icon-fan-bg.png'
import iconFan from '../../../public/icon-fan.png'
import iconOpen from '../../../public/icon-open.png'
import iconSleep from '../../../public/icon-sleep.png'
import iconLight from '../../../public/icon-light.png'
import iconVertical from '../../../public/icon-vertical.png'
import iconOpenOff from '../../../public/icon-open-off.png'
import middleware from '../../../public/icon-middleware.png'
import iconClose from '../../../public/icon-close.png'
import iconLightWhite from '../../../public/icon-light-white.png'
import iconSun from '../../../public/icon-sun.png'
import nature from '../../../public/icon-nature.png'
import natureOn from '../../../public/icon-nature-on.png'
import moon from '../../../public/icon-moon.png'
import moonOn from '../../../public/icon-moon-on.png'
import fresh from '../../../public/icon-fresh.png'
import freshOn from '../../../public/icon-fresh-on.png'
import smart from '../../../public/icon-smart.png'
import smartOn from '../../../public/icon-smart-on.png'
import hand from '../../../public/icon-hand.png'
import handOn from '../../../public/icon-hand-on.png'
import mode from '../../../public/icon-mode.png'
import modeOn from '../../../public/icon-mode-on.png'
import horizontalOff from '../../../public/icon-horizontal-off.png'
import horizontalOn from '../../../public/icon-horizontal-on.png'
import verticalOn from '../../../public/icon-vertical-on.png'
import verticalOff from '../../../public/icon-vertical-off.png'
import {useState, useEffect} from 'react'
const { useDevInfo, useDpState } = hooks;
export function Home() {
const devInfo = useDevInfo();
const devState = useDpState()
const menu = [
{
title:'Switch',
icon:iconOpen,
offIcon: iconOpenOff
},
{
title:'Sleep',
icon:iconSleep
},
{
title:'Light',
icon:iconLight
},
{
title:'Vertical',
icon:iconVertical
},
]
const firstMenu =
[ {
title:'Nature',
icon:natureOn,
offIcon: nature,
id: 0
}, {
title:'Sleep',
icon:moonOn,
offIcon: moon,
id: 1
}, {
title:'Fresh',
icon:freshOn,
offIcon: fresh,
id: 2
}, {
title:'Smart',
icon:smartOn,
offIcon: smart,
id: 3
}]
const secondMenu = [
{
title:'Nature',
icon:handOn,
offIcon: hand,
id: 4
}, {
title:'Mode',
icon:modeOn,
offIcon: mode,
id: 5
}
]
const verticalMenu = [
{
title:'Vertical',
icon:verticalOn,
offIcon: verticalOff,
id: 7,
numId:[71, 72, 73]
}, {
title:'Horizontal',
icon:horizontalOn,
offIcon: horizontalOff,
id:8,
numId:[81, 82, 83]
}
]
const brightRange = [10, 1000]
const cctRange = [0, 1000]
const [currentTemp, setTemp] = useState(devState[0]['temp_current'] || 30)
const [spinSpeed, setSpeed] = useState(devState[0]['fan_speed'] || 1)
const [isOpen, setOpen] = useState(devState[0]['fan_switch'] || false)
const [menuIndex, setMenuIndex] = useState(null)
const [preTime, setPreTime] = useState(null)
const [isShowModal, setModal] = useState(false)
const [point, setPoint] = useState({pageX: getOrignPointX(), pageY: 234})
const [selectColor, setColor] = useState(pointToRGB(point.pageX))
//亮度
const [bright, setBright] = useState(getValueInRange(devState[0]['bright_value'] || 10, brightRange).percent)
const [isLightOn, setLightOn] = useState(devState[0]['switch_led'] || false)
const [secondIndex, setSecondIndex] = useState(getFanMode())
const [verticalId, setVerticalId] = useState(getOrignFanVertical())
const [cctPercent, setCctPercent] = useState(getValueInRange(devState[0]['temp_value'] || 0, cctRange).percent)
useEffect(() => {
console.log('useEffffffffff', isOpen);
uploadDps()
}, [isOpen, spinSpeed, isLightOn, bright, cctPercent, secondIndex])
function getFanMode() {
const preMode = devState[0]['fan_mode']
let selected = null
for (let index = 0; index < firstMenu.length; index++) {
const item = firstMenu[index];
const title = item.title.toLowerCase()
if (title == preMode) {
selected = item.id
}
}
return selected
}
function getOrignPointX() {
const percent = getValueInRange(devState[0]['temp_value'] || 10, cctRange).percent
return 28 + 319 * Number((percent / 100).toFixed())
}
//0-value 1-percent
function getValueInRange(value, range, type = 0) {
const diff = range[1] - range[0]
const percent = type == 0 ? (value / diff) : (value / 100)
return {
value: type == 0 ? value : (range[0] + percent * diff).toFixed(),
percent: (percent * 100).toFixed()
}
}
function spinDuration() {
return 2 - ((2 - 0.2) / 100) * spinSpeed + 's'
}
function clickMenu(e) {
console.log(e, preTime);
// if ((e.timeStamp > preTime + 20) || !preTime) {
setPreTime(e.timeStamp)
const index = e.currentTarget.id
if (index == 0) {
setOpen(!isOpen)
setMenuIndex(null)
}else if (isOpen) {
if (index == 2) {
setMenuIndex(null)
setModal(true)
}else {
if (index == menuIndex) {
setMenuIndex( null)
} else {
setMenuIndex(index)
}
}
// }
}
}
function touchStart(e) {
console.log(e, 'start touch');
handlePoint(e)
}
function handleTouch(e) {
// console.log(e, 'handle touch');
// const point = e.changedTouches.length && e.changedTouches[0]
// console.log(point, 'pppppppoint');
// handlePoint(e)
}
function touchEnd(e) {
console.log(e, 'end touch');
handlePoint(e)
}
function handlePoint(e) {
const {pageX, pageY} = e.changedTouches.length && e.changedTouches[0]
let x = pageX
let y = pageY
if (pageX < 28) {
x = 28
}
if (pageX > 347) {
x = 347
}
if (pageY < 197) {
y = 197
}
if (pageY > 320) {
y =320
}
setPoint({pageX: x, pageY: y})
const percent = ((x - 28) / 319 * 100).toFixed()
setCctPercent(percent)
setColor(pointToRGB(x))
}
function pointToRGB(pageX) {
const half = (347 - 28) / 2
const middle = 28 + half
const leftColor = {h: 40, s:100, l:68}
const rightColor = {h:202,s:96, l:90}
let newL = leftColor.l
let color = leftColor
if (pageX < middle) {
newL = 68 + (pageX - 28) * 32 / 161
leftColor.l = newL
color = leftColor
} else {
newL = 100 - (pageX - middle) * 10 / 161
rightColor.l = newL
color = rightColor
}
// setLight(Number(newL).toFixed(0))
console.log(newL, color, leftColor, rightColor);
return HSLToRGB(color)
}
function HSLToRGB({h, s, l}) {
s /= 100;
l /= 100;
const k = n => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = n =>
l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
const color = `rgb(${255 * f(0)}, ${255 * f(8)}, ${255 * f(4)})`
console.log(color)
return color
};
const handleLightOn = () => {
setLightOn(!isLightOn)
}
function progressStart(e) {
console.log(e, 'progressStart');
handleProgress(e)
}
function progressMove(e) {
console.log(e, 'progressMove');
handleProgress(e)
}
function progressEnd(e) {
console.log(e, 'progressEnd');
handleProgress(e)
}
function handleProgress(e) {
const {pageX, pageY} = e.changedTouches.length && e.changedTouches[0]
const minX = 20
const maxX = 355
let differ = pageX - minX
if (differ < 0) {
differ = 0
} else if (differ > 335) {
differ = 335
}
const percent = (differ / 335 * 100).toFixed()
setBright(percent)
}
function getOrignFanVertical() {
const fan_vertical = devState[0]['fan_vertical']
const fan_horizontal = devState[0]['fan_horizontal']
const num_vertical = fan_vertical ? parseInt(fan_vertical / 30) : null
const num_horizontal = fan_horizontal ? parseInt(fan_horizontal / 30) :null
if (num_vertical) {
return 70 + num_vertical
}
if (num_horizontal) {
return 80 + num_horizontal
}
return null
}
//dpId 每个设备不一样
function getSelectFanVertical() {
if (verticalId) {
const value = (verticalId % 70) * 30
if (verticalId < 80) {
return {
'6' : value
}
}else {
return {
'7' : value
}
}
}
return {}
}
function uploadDps(params = {}) {
const brightValue = getValueInRange(bright, brightRange, 1).value
const cctValue = getValueInRange(cctPercent, cctRange, 1).value
const mode = firstMenu.filter(item => item.id == secondIndex)
const selectMode = mode.length > 0 ? {'61': mode[0].title.toLowerCase()} : {'61': null}
console.log(brightValue, 'brightValue');
console.log(cctValue, 'cctValue');
publishDps({
deviceId: devInfo.devId,
// dps: { '60': isOpen, '61':selectMode, '62': spinSpeed, '20':isLightOn, '22': brightValue, '23':cctValue}, // {'dpid': dpValue, '2': false}
dps: { '60': isOpen, ...selectMode, '62': spinSpeed, '20':isLightOn},
mode: 2,
pipelines: [],
options: {}, // 0,静音; 1,震动;2,声音; 3,震动声音
success: () => console.log('success'),
fail: d => {
console.log('-----返回结果错误?', d);
},
});
}
function handleSubmenu(id) {
if (id == secondIndex) {
setSecondIndex(null)
} else {
setSecondIndex(id)
}
}
function submenuItem(index, arr = firstMenu, clickFun = handleSubmenu, currentIndex = secondIndex) {
// const arr = page == 0 ? firstMenu:secondMenu
const {title, id, icon, offIcon} = arr[index]
return (
<View className={styles.button}>
<Image className={styles.menuImage} src={id == currentIndex ? icon:offIcon} onClick={() => {clickFun(id)}}/>
<Text className={styles.bTitle}>{title}</Text>
</View>
)
}
function handleVerticalMenu(id) {
const tensPre = parseInt(verticalId/10)
// const tensCur = id / 10
if (tensPre == id) {
setVerticalId(null)
} else {
setVerticalId(id * 10 + 2)
}
}
function verticalItem(index) {
const item = verticalMenu[index]
const textColor = (subIndex) => {
return {color: verticalId == item.numId[subIndex] ? '#000000':'rgba(0,0,0,0.4)',
width:'33.3%',
textAlign:'center',
lineHeight:'64px',
fontWeight:'500'
}}
const clickNum = (index) => {
const id = item.numId[index]
if (id == verticalId) {
setVerticalId(null)
} else {
setVerticalId(id)
}
}
return (
<View className={styles.verticalItem}>
{submenuItem(index, verticalMenu, handleVerticalMenu, parseInt(verticalId / 10))}
<View className={styles.verticalNumBg}>
<View style={textColor(0)} onClick={() => {clickNum(0)}}>30</View>
<View style={textColor(1)} onClick={() => {clickNum(1)}}>60</View>
<View style={textColor(2)} onClick={() => {clickNum(2)}}>90</View>
</View>
</View>
)
}
return (
<View className={styles.page}>
<View className={styles.header}>
<View className={styles.alignCenter}>
<Image className={styles.icon} src={iconTempr} />
<View className={styles.title}>{currentTemp}°C</View>
</View>
{/* <View className={styles.timer}>Countdown to open 03:00</View> */}
</View>
<View className={styles.fanGroup}>
<Image className={styles.fanBg} src={iconFanBg}></Image>
<Image className={styles.fan} src={iconFan} style={{animationDuration: spinDuration(), animationPlayState: isOpen ? 'running' : 'paused'}}></Image>
</View>
<View className={styles.sliderBg}>
<Slider
activeColor='linear-gradient(180deg, #87ABF2 11.62%, #4B7EE2 100%)'
blockColor='white'
blockSize={28}
min={0}
max={100}
value={spinSpeed}
step={5}
disabled={!isOpen}
onChange={(e) => {
console.log('SliderChange', e)
setSpeed(e.value)
// publishDps()
}}
/>
<View className={styles.fantBottom}>
<View className={styles.fontBottomTint}>Fan Speed</View>
<View className={styles.fontBottomTint}>{spinSpeed}</View>
</View>
</View>
<View className={styles.flexRowBetween}>
{
menu.map(({title, icon, offIcon}, index) => {
return (
<View
className={styles.button}
key={index}
>
{
menuIndex == 1 ?
<View className={styles.modalBg}>
<Swiper dots={true} dotActiveColor='#6395F6' dotColor='rgba(99,149,246, 0.2)'>
<SwiperItem>
<View className={styles.swiperItem}>
{
submenuItem(0)
}
{
submenuItem(1)
}
{
submenuItem(2)
}
{
submenuItem(3)
}
</View>
</SwiperItem>
{/* <SwiperItem>
<View className={styles.swiperItemNext}>
{
submenuItem(0, secondMenu)
}
{
submenuItem(1, secondMenu)
}
</View>
</SwiperItem> */}
</Swiper>
</View>
: menuIndex == 3 ? <View className={styles.modalBg} style={{height: '213px', display: 'flex', flexDirection:'column', padding:'16px 16px 0', gap:'12px'}}>
{
verticalItem(0)
}
{
verticalItem(1)
}
</View>
: null
}
<Image className={styles.middleware} src={middleware} style={{visibility: menuIndex == index ? 'visible':'hidden'}} />
<Image src={index > 0 ? icon: isOpen ? icon:offIcon} className={styles.menuImage} id={index} style={{opacity: isOpen ? 1: index > 0 ? 0.4:1 }} onClick={
clickMenu
}></Image>
<Text className={styles.bTitle}>{title}</Text>
</View>
)
})
}
</View>
<View className={styles.modalback} style={{background:'rgba(0, 0, 0, 0.1)',backdropFilter:'blur(35px)', visibility: isShowModal ? 'visible':'hidden' }}>
<View className={styles.lightModal}>
<View className={styles.lightModalTop}>
<View>Light
</View>
<Image className={styles.lightModalClose} src={iconClose} onClick={() => {
setModal(false)
}}></Image>
</View>
<View
className={styles.lightMiddle}
onTouchStart={touchStart}
onTouchMove={handleTouch}
onTouchEnd={touchEnd}
>
</View>
<View className={styles.progressBg} onTouchStart={progressStart}
onTouchMove={progressMove}
onTouchEnd={progressEnd}>
<Image className={styles.progressSun} src={iconSun}></Image>
<View className={styles.progressTitle}>{bright}%</View>
<View className={styles.progressBar} style={{width: bright + '%'}}></View>
</View>
<View className={styles.lightBtn} style={{backgroundColor: isLightOn ? '#6395F6':'rgba(0,0,0,0.5)'}} onClick={handleLightOn}>
<Image className={styles.lightBtnIcon} src={iconLightWhite}></Image>
<View className={styles.lightBtnTitle}>{isLightOn ? 'ON':'OFF'}</View>
<View className={styles.lightBtnSubtitle}>{isLightOn ? '/OFF':'/ON'}</View>
</View>
</View>
<View className={styles.moveCircle} style={{top: point.pageY - 14 + 'px', left: point.pageX - 14 + 'px', backgroundColor: selectColor}}></View>
</View>
</View>
)
}
export default Home;
import { DEVICE_INFO_CHANGE, DEV_INFO_CHANGE, RESPONSE_UPDATE_DP } from '@/constant';
/**
* actions
*/
const devInfoChange = (devInfo: DevInfo) => {
return {
type: DEV_INFO_CHANGE,
payload: devInfo,
};
};
const deviceChange = (devInfo: DevInfo) => {
return { type: DEVICE_INFO_CHANGE, payload: devInfo };
};
const responseUpdateDp = (dpData: Record<string, any>) => {
return { type: RESPONSE_UPDATE_DP, payload: dpData };
};
export const actions = {
devInfoChange,
deviceChange,
responseUpdateDp,
};
import { shallowEqual, useSelector as useSelectorBase } from 'react-redux';
import { actions as CommonActions } from './actions/common';
import { ReduxState, store } from './store';
const actions = {
common: CommonActions,
};
export { actions, store };
export function useSelector<TSelected>(
selector: (state: ReduxState) => TSelected,
equalityFn?: (left: TSelected, right: TSelected) => boolean
) {
return useSelectorBase<ReduxState, TSelected>(selector, equalityFn || shallowEqual);
}
import { DEVICE_INFO_CHANGE, DEV_INFO_CHANGE, RESPONSE_UPDATE_DP } from '@/constant';
import { formatDevSchema } from '@/utils';
import { actions } from '../actions/common';
export type Actions = {
[K in keyof typeof actions]: ReturnType<typeof actions[K]>;
};
export type DpValue = boolean | number | string;
/**
* reducers
*/
const dpState = (state = {}, action) => {
switch (action.type) {
case DEV_INFO_CHANGE: {
const { state: devState } = formatDevSchema(action.payload);
return {
...state,
...devState,
};
}
case RESPONSE_UPDATE_DP:
return {
...state,
...action.payload,
};
default:
return state;
}
};
const devInfo = (state = {}, action) => {
switch (action.type) {
case DEV_INFO_CHANGE: {
const { schema, state: devState } = formatDevSchema(action.payload);
return {
...state,
...action.payload,
schema,
state: devState,
};
}
case DEVICE_INFO_CHANGE:
return {
...state,
...action.payload,
};
default:
return state;
}
};
export const reducers = {
dpState,
devInfo,
};
import { isNative } from '@ray-js/env';
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import { reducers as commonReducers } from './reducers/common';
const reducers = {
...commonReducers,
};
type Reducers = typeof commonReducers;
export type ReduxState = { [K in keyof Reducers]: ReturnType<Reducers[K]> };
export const rootReducers = combineReducers(reducers);
const isDebuggingInChrome =
process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
const logger = createLogger({
predicate: () => isDebuggingInChrome,
collapsed: true,
duration: true,
});
const middleware = isDebuggingInChrome ? [thunk, logger] : [thunk];
function configureStore(initialState?: Partial<ReduxState>) {
const appliedMiddleware = applyMiddleware(...middleware);
const store = createStore(rootReducers, initialState, compose(appliedMiddleware));
return store;
}
export const store = configureStore();
/* eslint-disable */
const fs = require('fs');
const data = fs.readdirSync('./src/res');
const images = data.filter(file => /\.png$/.test(file));
const previous = images.map(file => file.replace(/(@\dx)?\.png/, ''));
const uniq = [...new Set(previous)];
const imp = uniq.reduce((pre, cur) => {
const item = `const ${cur} = require('./${cur}.png');\n`;
return pre + item;
}, '');
// const exp = uniq.reduce((pre, cur) => {
// const item = `const ${cur} = require('./${cur}.png')\n`;
// return pre + item;
// }, '');
const exp = `\nexport default { ${uniq.join(', ')} };`;
console.log(previous);
console.log(imp, exp);
fs.writeFileSync('./src/res/index.ts', imp + exp);
import { Routes, TabBar } from '@ray-js/types';
export const routes: Routes = [
{
route: '/',
path: '/pages/home/index',
name: 'Home',
},
{
route: '/common/page4/index',
path: '/pages/common/page4/index',
name: 'Page4',
},
{
route: '/common/page6/index',
path: '/pages/common/page6/index',
name: 'Page6',
},
];
export const tabBar = {};
import Strings from '@/i18n';
import { store } from '@/redux';
import { utils } from '@ray-js/panel-sdk';
const { getBitValue } = utils;
export const getFaultStrings = (
faultCode: string,
faultValue: number,
onlyPrior = true
): string => {
const { devInfo } = store.getState();
if (!faultValue) return '';
const { label } = devInfo.schema[faultCode];
const labels = [];
for (let i = 0; i < label!.length; i++) {
const value = label![i];
const isExist = getBitValue(faultValue, i);
if (isExist) {
labels.push(Strings.getDpLang(faultCode, value));
if (onlyPrior) break;
}
}
return onlyPrior ? labels[0] : labels.join(', ');
};
export const formatDevSchema = devInfo => {
const { dps, schema } = devInfo;
const result_schema = {};
const result_state = {};
for (let i = 0; i < schema.length; i++) {
const { code, id, property, type } = schema[i];
const define = {
dptype: type,
id: `${id}`,
...property,
};
result_state[code] = dps[id];
result_schema[code] = define;
delete define.property;
}
return { state: result_state, schema: result_schema };
};
@primary-color: #ff592a;
// font
@font-gray-0: #040a23;
@font-gray-1: #656979;
@font-gray-2: #9aa0b1;
@font-gray-3: #e1e5ec;
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-start {
justify-content: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-evenly {
justify-content: space-evenly;
}
.justify-around {
justify-content: space-around;
}
.justify-between {
justify-content: space-between;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
.items-end {
align-items: flex-end;
}
{
"compileOnSave": false,
"compilerOptions": {
"target": "ES2015",
"module": "CommonJS",
"lib": [
"ESNext",
"DOM"
],
"declaration": false,
"declarationMap": false,
"emitDeclarationOnly": false,
"moduleResolution": "node",
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"noEmitOnError": false,
"jsx": "react",
"baseUrl": "./",
"paths": {
"ray": [
"./.ray"
],
"@/*": [
"./src/*"
]
},
"typeRoots": [
"./node_modules/@types",
"./typings",
"./node_modules/@tuya-miniapp"
],
},
"include": [
"src/**/*.*",
"ray/**/*.*",
"typings"
],
"exclude": [
"node_modules",
"dist"
]
}
\ No newline at end of file
/**
* 面板小程序的通用 TS 类型
*/
/// 一些 TTT 通用工具泛型 ///
type GetTTTAllParams<Fn> = Parameters<Fn>['0'];
type GetTTTParams<Fn> = Omit<GetTTTAllParams<Fn>, 'complete' | 'success' | 'fail'>;
type GetTTTCompleteData<Fn> = Parameters<GetTTTAllParams<Fn>['complete']>['0'];
type GetTTTSuccessData<Fn> = Parameters<GetTTTAllParams<Fn>['success']>['0'];
type GetTTTFailData<Fn> = Parameters<GetTTTAllParams<Fn>['fail']>['0'];
type GetTTTEventListener<Fn> = Parameters<Fn>['0'];
type GetTTTEventListenerParams<Fn> = Parameters<GetTTTEventListener<Fn>>['0'];
/// ///
/**
* TTT 方法统一错误码
*/
type TTTCommonErrorCode = GetTTTFailData<typeof import('@ray-js/api').getDeviceInfo>;
/**
* 功能点描述信息
*/
interface DpSchema {
attr: number;
canTrigger: boolean;
/**
* 功能点标识码,如 switch
*/
code: string;
defaultRecommend: boolean;
editPermission: boolean;
executable: boolean;
extContent: string;
iconname: string;
/**
* 功能点 ID
*/
id: number;
/**
* 功能点模式类型
* rw: 可下发可上报(可读可写)
* ro: 只可上报(仅可读)
* wr: 只可下发(仅可写)
*/
mode: 'rw' | 'ro' | 'wr';
/**
* 功能点名称,一般用于语音等场景
*/
name: string;
/**
* 功能点属性
*/
property: {
/**
* 功能点类型
*/
type: 'bool' | 'value' | 'enum' | 'bitmap' | 'string' | 'raw';
range?: string[];
label?: string[];
maxlen: number;
unit: string;
min: number;
max: number;
scale: number;
step: number;
[key: string]: number | string | string[];
};
type: string;
}
type DeviceInfo = GetTTTSuccessData<typeof import('@ray-js/api').getDeviceInfo>;
/**
* 设备信息
*/
type DevInfo = Omit<DeviceInfo, 'schema' | 'panelConfig'> & {
schema: DpSchema[];
idCodes: Record<string, string>;
codeIds: Record<string, string>;
panelConfig: PanelConfig;
};
/**
* 设备物模型信息
*/
type ThingModelInfo = GetTTTSuccessData<typeof import('@ray-js/api').getDeviceThingModelInfo>;
type DeviceInfoUpdatedHandler = GetTTTEventListener<
typeof import('@ray-js/api').onDeviceInfoUpdated
>;
type DpDataChangeHandler = GetTTTEventListener<typeof import('@ray-js/api').onDpDataChange>;
type DeviceOnlineStatusUpdateHandler = GetTTTEventListener<
typeof import('@ray-js/api').onDeviceOnlineStatusUpdate
>;
type NetworkStatusChangeHandler = GetTTTEventListener<
typeof import('@ray-js/api').onNetworkStatusChange
>;
type NetworkStatus = GetTTTEventListenerParams<typeof import('@ray-js/api').onNetworkStatusChange>;
type BluetoothAdapterStateChangeHandler = GetTTTEventListener<
typeof import('@ray-js/api').onBluetoothAdapterStateChange
>;
type BluetoothState = GetTTTEventListenerParams<
typeof import('@ray-js/api').onBluetoothAdapterStateChange
>;
/**
* 当前设备的 TS 类型
*/
type SmartRobotDpState = {
/**
* 开关
*/
power: boolean;
/**
* 工作模式
*/
mode: string;
/**
* 故障告警
*/
fault: number;
};
declare module '*.png';
declare module '*.module.less' {
const classes: {
readonly [key: string]: string;
};
export default classes;
declare module '*.less';
}
declare global {
interface Window {
devToolsExtension?: () => any;
__DEV__: boolean;
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment