@ledvance/base 1.3.71 → 1.3.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,362 @@
1
+ import React, { Component } from 'react';
2
+ import _ from 'lodash';
3
+ import { View, ViewStyle } from 'react-native';
4
+ import RectPicker, {
5
+ ValidBound,
6
+ ILinear,
7
+ ILinearColors,
8
+ Point,
9
+ defaultProps as baseDefault,
10
+ } from './RectPicker';
11
+ import Slider, { IBrightOption } from './Slider';
12
+ import ColorUtils from './utils/color';
13
+
14
+ export interface IWhite {
15
+ brightness: number;
16
+ temperature: number;
17
+ }
18
+ const defaultProps = {
19
+ ...baseDefault,
20
+ brightOption: {} as IBrightOption,
21
+ value: { brightness: 500, temperature: 500 } as IWhite,
22
+ lossSliderColor: 'rgba(255,255,255,0.4)',
23
+ hideBright: false,
24
+ /**
25
+ * 排布方向
26
+ * leftBottom 对角线, 0在左下角
27
+ * leftTop 对角线, 0在左上角
28
+ * rightBottom 反向对角线, 0在右下角
29
+ * rightTop 反向对角线, 0在右上角
30
+ * left 从左往右,0在左边
31
+ * right 从右往左,0在右边
32
+ * top 从上往下,0在上边
33
+ * bottom 从下往上,0在下边
34
+ * @version ^0.3.0
35
+ */
36
+ direction: 'left' as
37
+ | 'leftBottom'
38
+ | 'leftTop'
39
+ | 'rightBottom'
40
+ | 'rightTop'
41
+ | 'left'
42
+ | 'right'
43
+ | 'top'
44
+ | 'bottom',
45
+ bgs: [
46
+ { offset: '0%', stopColor: '#FFCA5C', stopOpacity: 1 },
47
+ { offset: '60%', stopColor: '#FFFFFF', stopOpacity: 1 },
48
+ { offset: '100%', stopColor: '#CDECFE', stopOpacity: 1 },
49
+ ] as ILinearColors[],
50
+ onGrant(_v: any, _option?: { isChangeBright: boolean }) {},
51
+ onMove(_v: any, _option?: { isChangeBright: boolean }) {},
52
+ onRelease(_v: any, _option?: { isChangeBright: boolean }) {},
53
+ onPress(_v: any, _option?: { isChangeBright: boolean }) {},
54
+ };
55
+ type DefaultProps = {
56
+ style?: ViewStyle;
57
+ rectStyle?: ViewStyle;
58
+ } & Readonly<typeof defaultProps>;
59
+
60
+ type WhiteProps = DefaultProps;
61
+
62
+ export default class WhitePicker extends Component<WhiteProps, IWhite> {
63
+ static defaultProps = defaultProps;
64
+
65
+ constructor(props: WhiteProps) {
66
+ super(props);
67
+ const { value: { brightness, temperature }, } = this.props;
68
+ this.state = { brightness, temperature };
69
+ }
70
+
71
+ componentDidUpdate(prevProps: WhiteProps) {
72
+ const {
73
+ value: { temperature, brightness },
74
+ } = this.props;
75
+ if (temperature !== prevProps.value.temperature) {
76
+ this.setState({ temperature });
77
+ this.thumbPosition = this.autoTemperaturePosition(temperature);
78
+ this.currentTemperature = temperature;
79
+ }
80
+ if (brightness !== prevProps.value.brightness && brightness !== this.state.brightness) {
81
+ this.setState({ brightness });
82
+ }
83
+ }
84
+
85
+ componentWillUnmount() {
86
+ // 清理引用和状态
87
+ this.pickerRef = null;
88
+ }
89
+
90
+ shouldComponentUpdate(nextProps: WhiteProps, nextState: IWhite) {
91
+ return !_.isEqual(nextProps, this.props) || !_.isEqual(nextState, this.state);
92
+ }
93
+
94
+ onBrightGrant = () => {
95
+ const { brightness, temperature } = this.state;
96
+ this.firPropsEvent(this.props.onGrant, { brightness, temperature }, { isChangeBright: true });
97
+ };
98
+
99
+ onBrightMove = (brightness: number) => {
100
+ const { temperature } = this.state;
101
+ this.firPropsEvent(this.props.onMove, { temperature, brightness }, { isChangeBright: true });
102
+ };
103
+
104
+ onBrightRelease = (brightness: number) => {
105
+ const { temperature } = this.state;
106
+ this.setState({ temperature, brightness });
107
+ this.firPropsEvent(this.props.onRelease, { temperature, brightness }, { isChangeBright: true });
108
+ };
109
+
110
+ onBrightPress = (brightness: number) => {
111
+ const { temperature } = this.state;
112
+ this.setState({ temperature, brightness });
113
+ this.firPropsEvent(this.props.onPress, { temperature, brightness }, { isChangeBright: true });
114
+ };
115
+
116
+ getBgs() {
117
+ const { direction, bgs } = this.props;
118
+ // left bottom
119
+ let x1 = '0%';
120
+ let y1 = '100%';
121
+ let x2 = '100%';
122
+ let y2 = '0%';
123
+ switch (direction) {
124
+ case 'leftTop':
125
+ x1 = '0%';
126
+ y1 = '0%';
127
+ x2 = '100%';
128
+ y2 = '100%';
129
+ break;
130
+ case 'rightBottom':
131
+ x1 = '100%';
132
+ y1 = '100%';
133
+ x2 = '0%';
134
+ y2 = '0%';
135
+ break;
136
+ case 'rightTop':
137
+ x1 = '100%';
138
+ y1 = '0%';
139
+ x2 = '0%';
140
+ y2 = '100%';
141
+ break;
142
+ case 'left':
143
+ x1 = '0%';
144
+ y1 = '0%';
145
+ x2 = '100%';
146
+ y2 = '0%';
147
+ break;
148
+ case 'right':
149
+ x1 = '100%';
150
+ y1 = '0%';
151
+ x2 = '0%';
152
+ y2 = '0%';
153
+ break;
154
+ case 'top':
155
+ x1 = '0%';
156
+ y1 = '0%';
157
+ x2 = '0%';
158
+ y2 = '100%';
159
+ break;
160
+ case 'bottom':
161
+ x1 = '0%';
162
+ y1 = '100%';
163
+ x2 = '0%';
164
+ y2 = '0%';
165
+ break;
166
+
167
+ default:
168
+ break;
169
+ }
170
+
171
+ return [{ x1, y1, x2, y2, colors: bgs }] as ILinear[];
172
+ }
173
+
174
+ getNormalVector({ width, height, x, y }: ValidBound) {
175
+ switch (this.props.direction) {
176
+ case 'leftTop':
177
+ return { x: width, y: height, originX: x, originY: y };
178
+ case 'rightBottom':
179
+ return { x: -width, y: -height, originX: width + x, originY: height + y };
180
+ case 'rightTop':
181
+ return { x: -width, y: height, originX: width + x, originY: y };
182
+ case 'left':
183
+ return { x: width, y: 0, originX: x, originY: height / 2 + y };
184
+ case 'right':
185
+ return { x: -width, y: 0, originX: width + x, originY: height / 2 + y };
186
+ case 'top':
187
+ return { x: 0, y: height, originX: width / 2 + x, originY: y };
188
+ case 'bottom':
189
+ return { x: 0, y: -height, originX: width / 2 + x, originY: height + y };
190
+ default:
191
+ return { x: width, y: -height, originX: x, originY: height + y };
192
+ }
193
+ }
194
+
195
+ setValue({ temperature, brightness }: IWhite) {
196
+ this.thumbPosition = this.autoTemperaturePosition(temperature);
197
+ this.currentTemperature = temperature;
198
+ this.setState({ temperature, brightness });
199
+ }
200
+
201
+ initData = async (validBound: ValidBound) => {
202
+ const { temperature } = this.state;
203
+ // 根据色温计算位置
204
+ this.thumbPosition = this.autoTemperaturePosition(temperature, validBound);
205
+ this.currentTemperature = temperature;
206
+ this.pickerBound = validBound;
207
+ };
208
+
209
+ autoTemperaturePosition(temperature: number, validBound: any = { width: 0, height: 0 }) {
210
+ let position;
211
+ if (this.pickerRef) {
212
+ position = this.pickerRef.valueToCoor({ temperature });
213
+ } else {
214
+ position = this.valueToCoor(
215
+ { temperature, brightness: this.state.brightness },
216
+ null,
217
+ validBound
218
+ );
219
+ }
220
+ return position;
221
+ }
222
+
223
+ coorToValue = ({ x, y }: Point, bound: ValidBound) => {
224
+ const { brightness } = this.state;
225
+ // 获取基准向量
226
+ const normalVector = this.getNormalVector(bound);
227
+ const vector1 = { x: x - normalVector.originX, y: y - normalVector.originY };
228
+ // 对角线的长度
229
+ const total = Math.sqrt(normalVector.x ** 2 + normalVector.y ** 2);
230
+ const diff = (vector1.x * normalVector.x + vector1.y * normalVector.y) / total;
231
+ const temperature = Math.round((diff / total) * 1000);
232
+ return { temperature, brightness };
233
+ };
234
+
235
+ handleTemperaturePosition(temperature: number, bound: ValidBound) {
236
+ // 获取基准向量
237
+ const normalVector = this.getNormalVector(bound);
238
+ const total = Math.sqrt(normalVector.x ** 2 + normalVector.y ** 2);
239
+ const normal = { x: normalVector.x / total, y: normalVector.y / total };
240
+ const length = total * (temperature / 1000);
241
+ const position = {
242
+ x: normal.x * length + normalVector.originX,
243
+ y: normal.y * length + normalVector.originY,
244
+ };
245
+ return position;
246
+ }
247
+
248
+ valueToCoor = ({ temperature }: IWhite, origin: Point, validBound: ValidBound): Point => {
249
+ // origin 不存在时,不在滑动时候
250
+ if (!origin) {
251
+ let cacheEnabled = true;
252
+ if (!_.isEqual(validBound, this.pickerBound)) {
253
+ cacheEnabled = false;
254
+ }
255
+ if (this.currentTemperature === temperature && cacheEnabled) {
256
+ if (
257
+ this.thumbPosition &&
258
+ typeof this.thumbPosition.x === 'number' &&
259
+ this.thumbPosition.x >= 0
260
+ ) {
261
+ return this.thumbPosition;
262
+ }
263
+ }
264
+ return this.handleTemperaturePosition(temperature, validBound);
265
+ }
266
+ this.currentTemperature = temperature;
267
+ this.thumbPosition = origin;
268
+ return origin;
269
+ };
270
+
271
+ valueToColor = (data: IWhite): string => {
272
+ const { temperature, brightness } = data;
273
+ return ColorUtils.brightKelvin2rgba(brightness, temperature);
274
+ };
275
+
276
+ firPropsEvent(cb: (params?: any) => void, ...args: any[]) {
277
+ typeof cb === 'function' && cb(...args);
278
+ }
279
+
280
+ private thumbPosition: Point;
281
+ private currentTemperature: number;
282
+ private pickerRef: RectPicker | null;
283
+ private pickerBound: ValidBound;
284
+
285
+ handlePickerGrant = () => {
286
+ const { temperature, brightness } = this.state;
287
+ this.firPropsEvent(this.props.onGrant, { temperature, brightness });
288
+ };
289
+
290
+ handlePickerMove = (white: IWhite) => {
291
+ this.firPropsEvent(this.props.onMove, white);
292
+ };
293
+
294
+ handlePickerRelease = (white: IWhite) => {
295
+ this.setState({ ...white });
296
+ this.firPropsEvent(this.props.onRelease, white);
297
+ };
298
+
299
+ handlePickerPress = (white: IWhite) => {
300
+ this.setState({ ...white });
301
+ this.firPropsEvent(this.props.onPress, white);
302
+ };
303
+
304
+ render() {
305
+ const {
306
+ style,
307
+ rectStyle,
308
+ brightOption,
309
+ lossShow,
310
+ lossSliderColor,
311
+ clickEnabled,
312
+ hideBright,
313
+ opacityAnimationValue,
314
+ opacityAnimationDuration,
315
+ ...pickerProps
316
+ } = this.props;
317
+ const { temperature, brightness } = this.state;
318
+ const sliderProps: any = {};
319
+ if (lossShow) {
320
+ sliderProps.activeColor = lossSliderColor;
321
+ }
322
+ return (
323
+ <View style={[{ flex: 1 }, style]}>
324
+ <RectPicker
325
+ ref={(ref: RectPicker) => {
326
+ this.pickerRef = ref;
327
+ }}
328
+ coorToValue={this.coorToValue}
329
+ valueToColor={this.valueToColor}
330
+ valueToCoor={this.valueToCoor}
331
+ value={{ temperature, brightness }}
332
+ lossShow={lossShow}
333
+ clickEnabled={clickEnabled}
334
+ opacityAnimationValue={opacityAnimationValue}
335
+ opacityAnimationDuration={opacityAnimationDuration}
336
+ {...pickerProps}
337
+ bgs={this.getBgs()}
338
+ style={rectStyle}
339
+ onGrant={this.handlePickerGrant}
340
+ onMove={this.handlePickerMove}
341
+ onRelease={this.handlePickerRelease}
342
+ onPress={this.handlePickerPress}
343
+ initData={this.initData}
344
+ />
345
+ {!hideBright && (
346
+ <Slider
347
+ opacityAnimationValue={opacityAnimationValue}
348
+ opacityAnimationDuration={opacityAnimationDuration}
349
+ {...brightOption}
350
+ {...sliderProps}
351
+ clickEnabled={clickEnabled}
352
+ value={brightness}
353
+ onGrant={this.onBrightGrant}
354
+ onMove={this.onBrightMove}
355
+ onRelease={this.onBrightRelease}
356
+ onPress={this.onBrightPress}
357
+ />
358
+ )}
359
+ </View>
360
+ );
361
+ }
362
+ }
@@ -0,0 +1,8 @@
1
+ export default {
2
+ brightLevel1:
3
+ 'M512 736a32 32 0 1 1 0 64 32 32 0 0 1 0-64z m-158.4-65.6a32 32 0 1 1-45.248 45.248 32 32 0 0 1 45.248-45.248z m362.048 0a32 32 0 1 1-45.248 45.248 32 32 0 0 1 45.248-45.248zM512 352a160 160 0 1 1 0 320 160 160 0 0 1 0-320z m-256 128a32 32 0 1 1 0 64 32 32 0 0 1 0-64z m512 0a32 32 0 1 1 0 64 32 32 0 0 1 0-64z m-52.352-171.648a32 32 0 1 1-45.248 45.248 32 32 0 0 1 45.248-45.248z m-362.048 0a32 32 0 1 1-45.248 45.248 32 32 0 0 1 45.248-45.248zM512 224a32 32 0 1 1 0 64 32 32 0 0 1 0-64z',
4
+ brightLevel2:
5
+ 'M512 736a32 32 0 0 1 32 32v32a32 32 0 0 1-64 0v-32a32 32 0 0 1 32-32z m-158.4-65.6a32 32 0 0 1 0 45.248l-22.624 22.624a32 32 0 0 1-45.248-45.248l22.624-22.624a32 32 0 0 1 45.248 0z m362.048 0l22.624 22.624a32 32 0 0 1-45.248 45.248l-22.624-22.624a32 32 0 1 1 45.248-45.248zM512 352a160 160 0 1 1 0 320 160 160 0 0 1 0-320z m-256 128a32 32 0 0 1 0 64H224a32 32 0 0 1 0-64h32z m544 0a32 32 0 0 1 0 64h-32a32 32 0 0 1 0-64h32z m-61.728-194.272a32 32 0 0 1 0 45.248l-22.624 22.624a32 32 0 1 1-45.248-45.248l22.624-22.624a32 32 0 0 1 45.248 0z m-407.296 0l22.624 22.624a32 32 0 1 1-45.248 45.248l-22.624-22.624a32 32 0 0 1 45.248-45.248zM512 192a32 32 0 0 1 32 32v32a32 32 0 0 1-64 0V224a32 32 0 0 1 32-32z',
6
+ brightLevel3:
7
+ 'M512 736a32 32 0 0 1 32 32v64a32 32 0 0 1-64 0v-64a32 32 0 0 1 32-32z m-158.4-65.6a32 32 0 0 1 0 45.248l-45.248 45.248a32 32 0 1 1-45.248-45.248l45.248-45.248a32 32 0 0 1 45.248 0z m362.048 0l45.248 45.248a32 32 0 0 1-45.248 45.248l-45.248-45.248a32 32 0 1 1 45.248-45.248zM512 352a160 160 0 1 1 0 320 160 160 0 0 1 0-320z m320 128a32 32 0 0 1 0 64h-64a32 32 0 0 1 0-64h64zM256 480a32 32 0 0 1 0 64H192a32 32 0 0 1 0-64h64z m504.896-216.896a32 32 0 0 1 0 45.248l-45.248 45.248a32 32 0 1 1-45.248-45.248l45.248-45.248a32 32 0 0 1 45.248 0z m-452.544 0l45.248 45.248a32 32 0 1 1-45.248 45.248L263.104 308.352a32 32 0 0 1 45.248-45.248zM512 160a32 32 0 0 1 32 32v64a32 32 0 0 1-64 0V192a32 32 0 0 1 32-32z',
8
+ };
@@ -0,0 +1,5 @@
1
+ import ColourPicker from './ColourPicker';
2
+ import WhitePicker from './WhitePicker';
3
+ import Slider from './Slider';
4
+
5
+ export default { ColourPicker, WhitePicker, BrightnessSlider: Slider };
@@ -0,0 +1,3 @@
1
+ export default {
2
+ thumbMask: require('./thumb-mask.png'),
3
+ };
@@ -0,0 +1,73 @@
1
+ /* eslint-disable func-names */
2
+ /* eslint-disable @typescript-eslint/ban-ts-comment */
3
+ import { Utils } from 'tuya-panel-kit';
4
+ import ColorObj from 'color';
5
+
6
+ const { color: ColorUtils } = Utils.ColorUtils;
7
+
8
+ ColorUtils.temp2rgb = function (
9
+ kelvin: number,
10
+ { temperatureMin = 4000, temperatureMax = 8000 } = {}
11
+ ) {
12
+ let newKelvin = kelvin;
13
+ newKelvin /= 10; // 0 - 1000 范围
14
+ const temp = temperatureMin + ((temperatureMax - temperatureMin) * newKelvin) / 100;
15
+ const hsv = this.rgb2hsv(...this.kelvin2rgb(temp));
16
+ return this.hsv2RgbString(...hsv);
17
+ };
18
+
19
+ ColorUtils.brightKelvin2rgb = function (
20
+ bright = 1000,
21
+ kelvin = 1000,
22
+ { temperatureMin = 4000, temperatureMax = 8000 } = {}
23
+ ) {
24
+ let newKelvin = kelvin;
25
+ let newBright = bright;
26
+ newBright /= 10;
27
+ newKelvin /= 10;
28
+ const temp = temperatureMin + ((temperatureMax - temperatureMin) * newKelvin) / 100;
29
+ const hsv = this.rgb2hsv(...this.kelvin2rgb(temp));
30
+ const brightV = newBright;
31
+ hsv[2] = brightV;
32
+ return this.hsv2RgbString(...hsv);
33
+ };
34
+
35
+ ColorUtils.bright2Opacity = (
36
+ brightness: number,
37
+ option: { min: number; max: number } = { min: 0.2, max: 1 }
38
+ ) => {
39
+ const { min = 0.2, max = 1 } = option;
40
+ return Math.round((min + ((brightness - 10) / (1000 - 10)) * (max - min)) * 100) / 100;
41
+ };
42
+
43
+ /**
44
+ * 格式化hsv
45
+ * 亮度将转化为透明度变化
46
+ */
47
+ ColorUtils.hsv2rgba = function (hue: number, saturation: number, bright: number) {
48
+ try {
49
+ let color: string = ColorUtils.hsb2hex(hue, saturation / 10, 100);
50
+ // @ts-ignore
51
+ color = new ColorObj(color as string).alpha(this.bright2Opacity(bright)).rgbString();
52
+ return color;
53
+ } catch (error) {
54
+ // eslint-disable-next-line no-console
55
+ console.warn(error);
56
+ return 'transparent';
57
+ }
58
+ };
59
+
60
+ ColorUtils.brightKelvin2rgba = function (bright: number, kelvin: number) {
61
+ try {
62
+ let color = ColorUtils.brightKelvin2rgb(1000, kelvin || 0);
63
+ // @ts-ignore
64
+ color = new ColorObj(color).alpha(this.bright2Opacity(bright)).rgbString();
65
+ return color;
66
+ } catch (error) {
67
+ // eslint-disable-next-line no-console
68
+ console.warn(error);
69
+ return 'transparent';
70
+ }
71
+ };
72
+
73
+ export default ColorUtils;
@@ -8,10 +8,6 @@ const { withTheme } = Utils.ThemeUtils
8
8
 
9
9
  const repeatPeriod = [
10
10
  {
11
- index: 1,
12
- title: I18n.getLang('timeschedule_add_schedule_weekday7_text'),
13
- enabled: false,
14
- }, {
15
11
  index: 2,
16
12
  title: I18n.getLang('timeschedule_add_schedule_weekday1_text'),
17
13
  enabled: false,
@@ -35,14 +31,18 @@ const repeatPeriod = [
35
31
  index: 7,
36
32
  title: I18n.getLang('timeschedule_add_schedule_weekday6_text'),
37
33
  enabled: false,
34
+ }, {
35
+ index: 1,
36
+ title: I18n.getLang('timeschedule_add_schedule_weekday7_text'),
37
+ enabled: false,
38
38
  },
39
39
  ]
40
40
 
41
- export const setDataSource = (loop) => {
42
- return repeatPeriod.map((item, index) => {
41
+ export const setDataSource = (loop: number[] | string[] | string) => {
42
+ return repeatPeriod.map(item => {
43
43
  return {
44
44
  ...item,
45
- enabled: loop[index] === 1,
45
+ enabled: [1, '1'].includes(loop[item.index - 1]),
46
46
  }
47
47
  })
48
48
  }
@@ -10,7 +10,7 @@ import {
10
10
  NativeProps,
11
11
  setGroupDevices,
12
12
  setGroupNativeProps,
13
- setNativeProps,
13
+ setNativeProps, setNewPalette,
14
14
  setSystemTimeFormat,
15
15
  setTimeZone,
16
16
  UAGroupInfo,
@@ -26,6 +26,7 @@ interface Props {
26
26
  ldvDevInfo: LdvDevInfo
27
27
  uaGroupInfo: UAGroupInfoProps
28
28
  colorScheme?: string
29
+ newPalette?: boolean
29
30
  }
30
31
 
31
32
  interface LdvDevInfo extends DeviceInfo {
@@ -130,6 +131,8 @@ const composeLayout = (component: React.ComponentType) => {
130
131
  getTimeZone().then(timeZone => {
131
132
  dispatch(setTimeZone(timeZone))
132
133
  })
134
+
135
+ dispatch(setNewPalette(!!props.newPalette))
133
136
  }
134
137
 
135
138
  initReduxDeviceNativeProps(ldvDevInfo: LdvDevInfo) {
@@ -1,4 +1,5 @@
1
1
  import { useRoute, useNavigation } from '@react-navigation/core'
2
+ import { useCallback, useEffect, useRef } from 'react'
2
3
 
3
4
  export function createParams<T>(params: T): T {
4
5
  return { ...params }
@@ -13,3 +14,48 @@ export function useCurrentPage(routeName: string): boolean {
13
14
  const { index, routes} = navigation.getState()
14
15
  return routeName === routes[index].name
15
16
  }
17
+
18
+ /**
19
+ * dp 响应时间校验 hook
20
+ * 用于在指定时间内阻止长连接响应更新界面状态
21
+ */
22
+ export function useDpResponseValidator(timeoutMs: number = 1000) {
23
+ const dpTimestamps = useRef<{[dpKey: string]: number}>({});
24
+
25
+ // dp 响应时间校验函数
26
+ const sendDpWithTimestamps = useCallback((dpKey: string, sendFunc: () => Promise<any>) => {
27
+ // 记录发送时间戳
28
+ const timestamp = Date.now();
29
+ dpTimestamps.current[dpKey] = timestamp;
30
+
31
+ // 发送命令
32
+ return sendFunc();
33
+ }, []);
34
+
35
+ // 监听 dp 响应,比较时间戳
36
+ const onDpResponse = useCallback((dpKey: string) => {
37
+ const sendTimestamp = dpTimestamps.current[dpKey];
38
+ if (sendTimestamp) {
39
+ const responseTimestamp = Date.now();
40
+ const timeDiff = responseTimestamp - sendTimestamp;
41
+
42
+ if (timeDiff <= timeoutMs) {
43
+ // 指定时间内收到响应,不接收更新
44
+ return true; // 阻止更新
45
+ }
46
+ }
47
+ return false; // 允许更新
48
+ }, [timeoutMs]);
49
+
50
+ // 组件卸载时清理所有记录
51
+ useEffect(() => {
52
+ return () => {
53
+ dpTimestamps.current = {};
54
+ };
55
+ }, []);
56
+
57
+ return {
58
+ sendDpWithTimestamps,
59
+ onDpResponse,
60
+ };
61
+ }