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],
},
};
This diff is collapsed.
{
"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';
}
This diff is collapsed.
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 diff is collapsed.
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