@pisell/private-materials 6.11.180 → 6.11.181

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.
Files changed (24) hide show
  1. package/build/lowcode/assets-daily.json +11 -11
  2. package/build/lowcode/assets-dev.json +2 -2
  3. package/build/lowcode/assets-prod.json +11 -11
  4. package/build/lowcode/meta.js +1 -1
  5. package/build/lowcode/render/default/view.js +1 -1
  6. package/build/lowcode/view.js +1 -1
  7. package/es/plus/pisellReservation/PisellReservation.js +13 -4
  8. package/es/plus/pisellReservation/PisellVenueWallPage.d.ts +1 -0
  9. package/es/plus/pisellReservation/PisellVenueWallPage.js +1 -0
  10. package/es/plus/pisellReservation/floorMap/useReservationFloorPlan.d.ts +0 -1
  11. package/es/plus/pisellReservation/hooks/useReservationSalesHostData.d.ts +5 -0
  12. package/es/plus/pisellReservation/hooks/useReservationSalesHostData.js +7 -3
  13. package/es/plus/pisellReservation/hooks/useReservationWallClockFollow.d.ts +7 -4
  14. package/es/plus/pisellReservation/hooks/useReservationWallClockFollow.js +54 -15
  15. package/es/plus/pisellReservation/types.d.ts +6 -0
  16. package/lib/plus/pisellReservation/PisellReservation.js +13 -3
  17. package/lib/plus/pisellReservation/PisellVenueWallPage.d.ts +1 -0
  18. package/lib/plus/pisellReservation/floorMap/useReservationFloorPlan.d.ts +0 -1
  19. package/lib/plus/pisellReservation/hooks/useReservationSalesHostData.d.ts +5 -0
  20. package/lib/plus/pisellReservation/hooks/useReservationSalesHostData.js +4 -2
  21. package/lib/plus/pisellReservation/hooks/useReservationWallClockFollow.d.ts +7 -4
  22. package/lib/plus/pisellReservation/hooks/useReservationWallClockFollow.js +47 -16
  23. package/lib/plus/pisellReservation/types.d.ts +6 -0
  24. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- var _excluded = ["gridDataSourceKey", "dataSources", "scheduleValue", "onScheduleChange", "toolBar", "floorMap", "scheduleStartSlot", "scheduleEndSlot", "onNewReservation", "timeNavigatorProps", "scheduleProps", "fab", "defaultBodyView", "data", "total", "pagination", "onPageChange", "onSearch", "onReset", "searchParams", "loading", "rowKey", "className", "style", "floorPlanId", "floorPlanCanvasWidth", "floorPlanCanvasHeight", "floorPlanCode", "floorPlanName", "floorPlanSort", "floorPlanStatus", "onFloorPlanPersisted", "putFloorPlan", "onFloorMapBookingClick", "bodyViewSwitchable", "lockedBodyView", "bodyViewStorageKey", "floorMapHudTableDrawer"];
1
+ var _excluded = ["gridDataSourceKey", "dataSources", "scheduleValue", "onScheduleChange", "toolBar", "floorMap", "scheduleStartSlot", "scheduleEndSlot", "onNewReservation", "timeNavigatorProps", "scheduleProps", "fab", "defaultBodyView", "data", "total", "pagination", "onPageChange", "onSearch", "onReset", "searchParams", "loading", "rowKey", "className", "style", "floorPlanId", "floorPlanCanvasWidth", "floorPlanCanvasHeight", "floorPlanCode", "floorPlanName", "floorPlanSort", "floorPlanStatus", "onFloorPlanPersisted", "putFloorPlan", "onFloorMapBookingClick", "bodyViewSwitchable", "lockedBodyView", "bodyViewStorageKey", "floorMapHudTableDrawer", "forceFollowWallClock"];
2
2
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
3
3
  function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
4
4
  function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
@@ -115,6 +115,7 @@ export var PisellReservationCore = function PisellReservationCore(props) {
115
115
  bodyViewStorageKey = props.bodyViewStorageKey,
116
116
  _props$floorMapHudTab = props.floorMapHudTableDrawer,
117
117
  floorMapHudTableDrawer = _props$floorMapHudTab === void 0 ? false : _props$floorMapHudTab,
118
+ forceFollowWallClockProp = props.forceFollowWallClock,
118
119
  rest = _objectWithoutProperties(props, _excluded);
119
120
  void _unusedDefaultBodyView;
120
121
 
@@ -236,6 +237,11 @@ export var PisellReservationCore = function PisellReservationCore(props) {
236
237
  _useState2 = _slicedToArray(_useState, 2),
237
238
  shellBodyView = _useState2[0],
238
239
  setShellBodyView = _useState2[1];
240
+
241
+ /** 锁定大屏子视图、显式 prop、或 Segmented 切到「大屏」:强制墙钟跟随且不可关 */
242
+ var wallClockFollowLocked = useMemo(function () {
243
+ return Boolean(forceFollowWallClockProp) || lockedBodyView === 'resourceWall' || shellBodyView === 'resourceWall';
244
+ }, [forceFollowWallClockProp, lockedBodyView, shellBodyView]);
239
245
  var _useReservationFloorP = useReservationFloorPlan({
240
246
  resolvedFloorPlanId: resolvedFloorPlanId,
241
247
  effectiveFloorPlanCode: effectiveFloorPlanCode,
@@ -374,7 +380,7 @@ export var PisellReservationCore = function PisellReservationCore(props) {
374
380
  debouncedSchedule = _useReservationSchedu.debouncedSchedule,
375
381
  flushCommittedSchedule = _useReservationSchedu.flushCommittedSchedule,
376
382
  queueCommittedSchedule = _useReservationSchedu.queueCommittedSchedule;
377
- var _useReservationWallCl = useReservationWallClockFollow(scheduleValueProp, setInternalSchedule, flushCommittedSchedule, onScheduleChange),
383
+ var _useReservationWallCl = useReservationWallClockFollow(scheduleValueProp, setInternalSchedule, flushCommittedSchedule, onScheduleChange, wallClockFollowLocked),
378
384
  followWallClock = _useReservationWallCl.followWallClock,
379
385
  setFollowWallClock = _useReservationWallCl.setFollowWallClock,
380
386
  onFollowWallClockToggle = _useReservationWallCl.onFollowWallClockToggle,
@@ -387,6 +393,7 @@ export var PisellReservationCore = function PisellReservationCore(props) {
387
393
  pisellosRef: pisellosRef,
388
394
  hasPisellosGetModule: hasPisellosGetModule,
389
395
  debouncedSchedule: debouncedSchedule,
396
+ followWallClock: followWallClock,
390
397
  resourceBoardLoading: resourceBoardData.loading,
391
398
  floorDayBookingLoading: floorDayBookingData.loading,
392
399
  floorDayBookingData: floorDayBookingData.data,
@@ -530,13 +537,15 @@ export var PisellReservationCore = function PisellReservationCore(props) {
530
537
  }
531
538
  }, [debouncedSchedule, isInternalListData, floorDayBookingData.patchSearchParams, operatingDayBoundary]);
532
539
  var handleScheduleChange = useCallback(function (next) {
533
- setFollowWallClock(false);
540
+ if (!wallClockFollowLocked) {
541
+ setFollowWallClock(false);
542
+ }
534
543
  if (scheduleValueProp === undefined) {
535
544
  setInternalSchedule(next);
536
545
  }
537
546
  onScheduleChange === null || onScheduleChange === void 0 || onScheduleChange(next);
538
547
  queueCommittedSchedule(next);
539
- }, [scheduleValueProp, onScheduleChange, setFollowWallClock, queueCommittedSchedule]);
548
+ }, [scheduleValueProp, onScheduleChange, setFollowWallClock, queueCommittedSchedule, wallClockFollowLocked]);
540
549
 
541
550
  // --- 平面图:合并 props、点击预约、HUD;loading 防抖后交给 RecordBoard ---
542
551
 
@@ -7,6 +7,7 @@ import type { PisellReservationProps } from './types';
7
7
  * - `lockedBodyView="resourceWall"`:锁定在大屏视图。
8
8
  *
9
9
  * **无需新增 props 类型**:沿用 {@link PisellReservationProps};可选行为与预约页相同(如 `toolBar`、`scheduleValue`、`floorMap` 等)。
10
+ * Core 在锁定大屏或预约页内 Segmented 切到「大屏」时(`shellBodyView === 'resourceWall'`)均 **强制跟随当前时间**(不可关闭)。
10
11
  * 默认使用 {@link PISELL_RESERVATION_VENUE_WALL_PAGE_STORAGE_KEY} 作为 `bodyViewStorageKey`,以便大屏筛选与主预约页 localStorage 隔离;可传入自定义 key 覆盖。
11
12
  */
12
13
  export declare const PisellVenueWallPage: React.FC<PisellReservationProps>;
@@ -12,6 +12,7 @@ import { PISELL_RESERVATION_VENUE_WALL_PAGE_STORAGE_KEY } from "./reservationCon
12
12
  * - `lockedBodyView="resourceWall"`:锁定在大屏视图。
13
13
  *
14
14
  * **无需新增 props 类型**:沿用 {@link PisellReservationProps};可选行为与预约页相同(如 `toolBar`、`scheduleValue`、`floorMap` 等)。
15
+ * Core 在锁定大屏或预约页内 Segmented 切到「大屏」时(`shellBodyView === 'resourceWall'`)均 **强制跟随当前时间**(不可关闭)。
15
16
  * 默认使用 {@link PISELL_RESERVATION_VENUE_WALL_PAGE_STORAGE_KEY} 作为 `bodyViewStorageKey`,以便大屏筛选与主预约页 localStorage 隔离;可传入自定义 key 覆盖。
16
17
  */
17
18
  export var PisellVenueWallPage = function PisellVenueWallPage(props) {
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import type { FloorMapViewConfig } from '@pisell/materials';
3
2
  export declare function useReservationFloorPlan(options: {
4
3
  resolvedFloorPlanId: number | null;
@@ -10,12 +10,17 @@ declare type PisellOSLike = {
10
10
  * 内置列表且资源台 + floorDayBooking 均就绪后,向宿主 sales 拉两套数据:
11
11
  * - `getResourceBookingList`:游标时刻下的预约子集,参与合并进平面图 dataSources;
12
12
  * - `getTimelineHighlights`:时间轴密度折线(无则退回 PisellReservation 内本地计算)。
13
+ *
14
+ * **跟随当前**:`followWallClock === true` 时第三个参数 `currentTimeStr` 与 `timeStr` 相同(均来自 `debouncedSchedule.at`),
15
+ * 避免与墙钟 `dayjs()` 差一分钟;手动关闭跟随后仍传实时 `dayjs()`,保留「游标 vs 此刻」双通道语义。
13
16
  */
14
17
  export declare function useReservationSalesHostData(options: {
15
18
  isInternalListData: boolean;
16
19
  pisellosRef: MutableRefObject<PisellOSLike>;
17
20
  hasPisellosGetModule: boolean;
18
21
  debouncedSchedule: ReservationScheduleBandValue;
22
+ /** 顶栏「跟随当前时间」:为 true 时 `getResourceBookingList` 的 currentTime 与 timeStr 一致 */
23
+ followWallClock: boolean;
19
24
  resourceBoardLoading: boolean;
20
25
  floorDayBookingLoading: boolean;
21
26
  floorDayBookingData: unknown[];
@@ -12,12 +12,16 @@ import { normalizeSalesResourceBookingListResult } from "../data/reservationTabl
12
12
  * 内置列表且资源台 + floorDayBooking 均就绪后,向宿主 sales 拉两套数据:
13
13
  * - `getResourceBookingList`:游标时刻下的预约子集,参与合并进平面图 dataSources;
14
14
  * - `getTimelineHighlights`:时间轴密度折线(无则退回 PisellReservation 内本地计算)。
15
+ *
16
+ * **跟随当前**:`followWallClock === true` 时第三个参数 `currentTimeStr` 与 `timeStr` 相同(均来自 `debouncedSchedule.at`),
17
+ * 避免与墙钟 `dayjs()` 差一分钟;手动关闭跟随后仍传实时 `dayjs()`,保留「游标 vs 此刻」双通道语义。
15
18
  */
16
19
  export function useReservationSalesHostData(options) {
17
20
  var isInternalListData = options.isInternalListData,
18
21
  pisellosRef = options.pisellosRef,
19
22
  hasPisellosGetModule = options.hasPisellosGetModule,
20
23
  debouncedSchedule = options.debouncedSchedule,
24
+ followWallClock = options.followWallClock,
21
25
  resourceBoardLoading = options.resourceBoardLoading,
22
26
  floorDayBookingLoading = options.floorDayBookingLoading,
23
27
  floorDayBookingData = options.floorDayBookingData,
@@ -106,7 +110,7 @@ export function useReservationSalesHostData(options) {
106
110
  if (!Array.isArray(list)) {
107
111
  return;
108
112
  }
109
- var retryGateKey = "".concat(debouncedAtMs !== null && debouncedAtMs !== void 0 ? debouncedAtMs : 'na', "|").concat(floorDayBookingDepKey, "|v").concat(floorDayBookingDataVersion, "|").concat(hasPisellosGetModule ? 1 : 0);
113
+ var retryGateKey = "".concat(debouncedAtMs !== null && debouncedAtMs !== void 0 ? debouncedAtMs : 'na', "|").concat(floorDayBookingDepKey, "|v").concat(floorDayBookingDataVersion, "|").concat(hasPisellosGetModule ? 1 : 0, "|fw").concat(followWallClock ? 1 : 0);
110
114
  if (retryGateKey !== resourceListRetryGateRef.current) {
111
115
  resourceListRetryGateRef.current = retryGateKey;
112
116
  resourceListHostSkipRef.current = false;
@@ -139,7 +143,7 @@ export function useReservationSalesHostData(options) {
139
143
 
140
144
  var resourceBookingListTimeFormat = 'YYYY-MM-DD HH:mm';
141
145
  var timeStr = dayjs(debouncedSchedule.at).format(resourceBookingListTimeFormat);
142
- var currentTimeStr = dayjs().format(resourceBookingListTimeFormat);
146
+ var currentTimeStr = followWallClock ? timeStr : dayjs().format(resourceBookingListTimeFormat);
143
147
  var applyHostList = function applyHostList(raw) {
144
148
  if (salesBookingRequestRef.current !== reqId) return;
145
149
  var normalized = normalizeSalesResourceBookingListResult(raw);
@@ -177,7 +181,7 @@ export function useReservationSalesHostData(options) {
177
181
  * 兜底信号:哪怕 cheap hash 没变,只要有 WS 推送 mutate 就重跑 host call。
178
182
  * 配合 `resourceListRetryGateRef` 保证非宿主场景不会重复打接口。
179
183
  */
180
- floorDayBookingDataVersion, debouncedAtMs, pisellosRef]);
184
+ floorDayBookingDataVersion, debouncedAtMs, followWallClock, pisellosRef]);
181
185
  var _useState3 = useState(null),
182
186
  _useState4 = _slicedToArray(_useState3, 2),
183
187
  salesTimelineHighlights = _useState4[0],
@@ -1,12 +1,15 @@
1
- /// <reference types="react" />
1
+ import { type SetStateAction } from 'react';
2
2
  import type { ReservationScheduleBandValue } from '@pisell/materials';
3
3
  declare type OnScheduleChange = ((v: ReservationScheduleBandValue) => void) | undefined;
4
4
  /**
5
- * 顶栏「跟随当前时间」:30s 对齐日程,并通过 flushCommittedSchedule 触发防抖链。
5
+ * 顶栏「跟随当前时间」:在每自然分钟整点(*: * :00)对齐日程,`at` 使用当前分钟的 `startOf('minute')`,
6
+ * 并通过 flushCommittedSchedule 触发防抖链。
7
+ *
8
+ * @param wallClockFollowLocked 为 true 时(如大屏锁定 resourceWall):定时器始终运行,且不可通过交互关闭跟随。
6
9
  */
7
- export declare function useReservationWallClockFollow(scheduleValueProp: ReservationScheduleBandValue | undefined, setInternalSchedule: (v: ReservationScheduleBandValue) => void, flushCommittedSchedule: (v: ReservationScheduleBandValue) => void, onScheduleChange: OnScheduleChange): {
10
+ export declare function useReservationWallClockFollow(scheduleValueProp: ReservationScheduleBandValue | undefined, setInternalSchedule: (v: ReservationScheduleBandValue) => void, flushCommittedSchedule: (v: ReservationScheduleBandValue) => void, onScheduleChange: OnScheduleChange, wallClockFollowLocked?: boolean): {
8
11
  followWallClock: boolean;
9
- setFollowWallClock: import("react").Dispatch<import("react").SetStateAction<boolean>>;
12
+ setFollowWallClock: (value: SetStateAction<boolean>) => void;
10
13
  onFollowWallClockToggle: () => void;
11
14
  onTimeNavigatorUserInteraction: () => void;
12
15
  };
@@ -7,51 +7,90 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import { useCallback, useEffect, useRef, useState } from 'react';
8
8
  import dayjs from 'dayjs';
9
9
  /**
10
- * 顶栏「跟随当前时间」:30s 对齐日程,并通过 flushCommittedSchedule 触发防抖链。
10
+ * 顶栏「跟随当前时间」:在每自然分钟整点(*: * :00)对齐日程,`at` 使用当前分钟的 `startOf('minute')`,
11
+ * 并通过 flushCommittedSchedule 触发防抖链。
12
+ *
13
+ * @param wallClockFollowLocked 为 true 时(如大屏锁定 resourceWall):定时器始终运行,且不可通过交互关闭跟随。
11
14
  */
12
15
  export function useReservationWallClockFollow(scheduleValueProp, setInternalSchedule, flushCommittedSchedule, onScheduleChange) {
16
+ var wallClockFollowLocked = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
13
17
  var _useState = useState(true),
14
18
  _useState2 = _slicedToArray(_useState, 2),
15
19
  followWallClock = _useState2[0],
16
- setFollowWallClock = _useState2[1];
20
+ setFollowWallClockInternal = _useState2[1];
17
21
  var onScheduleChangeRef = useRef(onScheduleChange);
22
+ /** 避免 effect 依赖整个 `scheduleValueProp` 引用:父级每次 render 新对象会误重置定时器并多次首帧 tick */
23
+ var scheduleValuePropRef = useRef(scheduleValueProp);
24
+ scheduleValuePropRef.current = scheduleValueProp;
25
+ var scheduleControlled = scheduleValueProp !== undefined;
26
+ var followWallClockEffective = wallClockFollowLocked || followWallClock;
27
+
28
+ /** 锁定模式下始终保持内部开关为 true,避免闭包里读到 false */
29
+ useEffect(function () {
30
+ if (wallClockFollowLocked) setFollowWallClockInternal(true);
31
+ }, [wallClockFollowLocked]);
18
32
  useEffect(function () {
19
33
  onScheduleChangeRef.current = onScheduleChange;
20
34
  });
21
35
  useEffect(function () {
22
- if (!followWallClock) return;
36
+ if (!followWallClockEffective) return;
37
+ var timeoutId = null;
38
+ var cancelled = false;
39
+
40
+ /** 距下一自然分钟整点的毫秒数(下一分钟的 :00.000) */
41
+ var msUntilNextMinuteBoundary = function msUntilNextMinuteBoundary() {
42
+ var now = dayjs();
43
+ return Math.max(0, now.startOf('minute').add(1, 'minute').diff(now));
44
+ };
23
45
  var tick = function tick() {
24
46
  var _onScheduleChangeRef$;
25
- var now = dayjs();
47
+ var now = dayjs().startOf('minute');
26
48
  var next = {
27
49
  date: now.startOf('day'),
28
50
  at: now
29
51
  };
30
- if (scheduleValueProp === undefined) {
52
+ if (scheduleValuePropRef.current === undefined) {
31
53
  setInternalSchedule(next);
32
54
  }
33
55
  (_onScheduleChangeRef$ = onScheduleChangeRef.current) === null || _onScheduleChangeRef$ === void 0 || _onScheduleChangeRef$.call(onScheduleChangeRef, next);
34
56
  /** 跟随当前:立即提交日程,patch / 外部 onSearch 由 debouncedSchedule effect 统一处理 */
35
57
  flushCommittedSchedule(next);
36
58
  };
59
+ var scheduleNextBoundary = function scheduleNextBoundary() {
60
+ if (cancelled) return;
61
+ timeoutId = window.setTimeout(function () {
62
+ if (cancelled) return;
63
+ tick();
64
+ scheduleNextBoundary();
65
+ }, msUntilNextMinuteBoundary());
66
+ };
67
+
68
+ /** 首帧:立刻对齐到当前整分;之后仅在每分钟 rollover 时再 tick */
37
69
  tick();
38
- var id = window.setInterval(tick, 30000);
70
+ scheduleNextBoundary();
39
71
  return function () {
40
- return window.clearInterval(id);
72
+ cancelled = true;
73
+ if (timeoutId != null) window.clearTimeout(timeoutId);
41
74
  };
42
- // setInternalSchedule 为 schedule hook 的 setState,引用稳定;与旧版一致不把其列入 deps
43
- // eslint-disable-next-line react-hooks/exhaustive-deps -- match legacy interval deps
44
- }, [followWallClock, scheduleValueProp, flushCommittedSchedule]);
75
+ // setInternalSchedule 为 schedule hook 的 setState,引用稳定;不把 scheduleValueProp 放进 deps(见 ref)
76
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- scheduleControlled:仅受控/非受控切换时重启定时器
77
+ }, [followWallClockEffective, flushCommittedSchedule, scheduleControlled]);
78
+ var setFollowWallClock = useCallback(function (value) {
79
+ if (wallClockFollowLocked) return;
80
+ setFollowWallClockInternal(value);
81
+ }, [wallClockFollowLocked]);
45
82
  var onFollowWallClockToggle = useCallback(function () {
46
- return setFollowWallClock(function (v) {
83
+ if (wallClockFollowLocked) return;
84
+ setFollowWallClockInternal(function (v) {
47
85
  return !v;
48
86
  });
49
- }, []);
87
+ }, [wallClockFollowLocked]);
50
88
  var onTimeNavigatorUserInteraction = useCallback(function () {
51
- return setFollowWallClock(false);
52
- }, []);
89
+ if (wallClockFollowLocked) return;
90
+ setFollowWallClockInternal(false);
91
+ }, [wallClockFollowLocked]);
53
92
  return {
54
- followWallClock: followWallClock,
93
+ followWallClock: followWallClockEffective,
55
94
  setFollowWallClock: setFollowWallClock,
56
95
  onFollowWallClockToggle: onFollowWallClockToggle,
57
96
  onTimeNavigatorUserInteraction: onTimeNavigatorUserInteraction
@@ -165,4 +165,10 @@ export interface PisellReservationProps extends Omit<RecordBoardProps, 'toolBar'
165
165
  bodyViewStorageKey?: string;
166
166
  /** 点击平面图左上角 HUD 打开窄屏表格抽屉(画布已绑定资源行) */
167
167
  floorMapHudTableDrawer?: boolean;
168
+ /**
169
+ * 强制「跟随当前时间」不可关闭(定时对齐墙钟、`getResourceBookingList` 与游标时间一致)。
170
+ * - **默认**:{@link lockedBodyView} 为 `resourceWall`,或用户在 RecordBoard 内**切换到「大屏 / resourceWall」子视图**时自动启用。
171
+ * - 设为 `true` 可在其它场景也强制跟随。
172
+ */
173
+ forceFollowWallClock?: boolean;
168
174
  }
@@ -118,6 +118,7 @@ var PisellReservationCore = (props) => {
118
118
  lockedBodyView,
119
119
  bodyViewStorageKey,
120
120
  floorMapHudTableDrawer = false,
121
+ forceFollowWallClock: forceFollowWallClockProp,
121
122
  ...rest
122
123
  } = props;
123
124
  const isInternalListData = (0, import_reservationDataUtils.shouldUseBuiltinReservationList)(
@@ -204,6 +205,10 @@ var PisellReservationCore = (props) => {
204
205
  const [shellBodyView, setShellBodyView] = (0, import_react.useState)(
205
206
  () => (0, import_reservationDataUtils.readBodyViewFromStorageKey)(bodyViewStorageKey)
206
207
  );
208
+ const wallClockFollowLocked = (0, import_react.useMemo)(
209
+ () => Boolean(forceFollowWallClockProp) || lockedBodyView === "resourceWall" || shellBodyView === "resourceWall",
210
+ [forceFollowWallClockProp, lockedBodyView, shellBodyView]
211
+ );
207
212
  const { baseFloorConfig, floorPlanRemoteId, setRemoteFloorPlanView } = (0, import_useReservationFloorPlan.useReservationFloorPlan)({
208
213
  resolvedFloorPlanId,
209
214
  effectiveFloorPlanCode,
@@ -338,7 +343,8 @@ var PisellReservationCore = (props) => {
338
343
  scheduleValueProp,
339
344
  setInternalSchedule,
340
345
  flushCommittedSchedule,
341
- onScheduleChange
346
+ onScheduleChange,
347
+ wallClockFollowLocked
342
348
  );
343
349
  const prevCommittedDateRef = (0, import_react.useRef)(null);
344
350
  const { salesResourceBookings, salesTimelineHighlights } = (0, import_useReservationSalesHostData.useReservationSalesHostData)({
@@ -346,6 +352,7 @@ var PisellReservationCore = (props) => {
346
352
  pisellosRef,
347
353
  hasPisellosGetModule,
348
354
  debouncedSchedule,
355
+ followWallClock,
349
356
  resourceBoardLoading: resourceBoardData.loading,
350
357
  floorDayBookingLoading: floorDayBookingData.loading,
351
358
  floorDayBookingData: floorDayBookingData.data,
@@ -487,7 +494,9 @@ var PisellReservationCore = (props) => {
487
494
  ]);
488
495
  const handleScheduleChange = (0, import_react.useCallback)(
489
496
  (next) => {
490
- setFollowWallClock(false);
497
+ if (!wallClockFollowLocked) {
498
+ setFollowWallClock(false);
499
+ }
491
500
  if (scheduleValueProp === void 0) {
492
501
  setInternalSchedule(next);
493
502
  }
@@ -498,7 +507,8 @@ var PisellReservationCore = (props) => {
498
507
  scheduleValueProp,
499
508
  onScheduleChange,
500
509
  setFollowWallClock,
501
- queueCommittedSchedule
510
+ queueCommittedSchedule,
511
+ wallClockFollowLocked
502
512
  ]
503
513
  );
504
514
  const dataSources = (0, import_react.useMemo)(() => {
@@ -7,6 +7,7 @@ import type { PisellReservationProps } from './types';
7
7
  * - `lockedBodyView="resourceWall"`:锁定在大屏视图。
8
8
  *
9
9
  * **无需新增 props 类型**:沿用 {@link PisellReservationProps};可选行为与预约页相同(如 `toolBar`、`scheduleValue`、`floorMap` 等)。
10
+ * Core 在锁定大屏或预约页内 Segmented 切到「大屏」时(`shellBodyView === 'resourceWall'`)均 **强制跟随当前时间**(不可关闭)。
10
11
  * 默认使用 {@link PISELL_RESERVATION_VENUE_WALL_PAGE_STORAGE_KEY} 作为 `bodyViewStorageKey`,以便大屏筛选与主预约页 localStorage 隔离;可传入自定义 key 覆盖。
11
12
  */
12
13
  export declare const PisellVenueWallPage: React.FC<PisellReservationProps>;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import type { FloorMapViewConfig } from '@pisell/materials';
3
2
  export declare function useReservationFloorPlan(options: {
4
3
  resolvedFloorPlanId: number | null;
@@ -10,12 +10,17 @@ declare type PisellOSLike = {
10
10
  * 内置列表且资源台 + floorDayBooking 均就绪后,向宿主 sales 拉两套数据:
11
11
  * - `getResourceBookingList`:游标时刻下的预约子集,参与合并进平面图 dataSources;
12
12
  * - `getTimelineHighlights`:时间轴密度折线(无则退回 PisellReservation 内本地计算)。
13
+ *
14
+ * **跟随当前**:`followWallClock === true` 时第三个参数 `currentTimeStr` 与 `timeStr` 相同(均来自 `debouncedSchedule.at`),
15
+ * 避免与墙钟 `dayjs()` 差一分钟;手动关闭跟随后仍传实时 `dayjs()`,保留「游标 vs 此刻」双通道语义。
13
16
  */
14
17
  export declare function useReservationSalesHostData(options: {
15
18
  isInternalListData: boolean;
16
19
  pisellosRef: MutableRefObject<PisellOSLike>;
17
20
  hasPisellosGetModule: boolean;
18
21
  debouncedSchedule: ReservationScheduleBandValue;
22
+ /** 顶栏「跟随当前时间」:为 true 时 `getResourceBookingList` 的 currentTime 与 timeStr 一致 */
23
+ followWallClock: boolean;
19
24
  resourceBoardLoading: boolean;
20
25
  floorDayBookingLoading: boolean;
21
26
  floorDayBookingData: unknown[];
@@ -42,6 +42,7 @@ function useReservationSalesHostData(options) {
42
42
  pisellosRef,
43
43
  hasPisellosGetModule,
44
44
  debouncedSchedule,
45
+ followWallClock,
45
46
  resourceBoardLoading,
46
47
  floorDayBookingLoading,
47
48
  floorDayBookingData,
@@ -104,7 +105,7 @@ function useReservationSalesHostData(options) {
104
105
  if (!Array.isArray(list)) {
105
106
  return;
106
107
  }
107
- const retryGateKey = `${debouncedAtMs ?? "na"}|${floorDayBookingDepKey}|v${floorDayBookingDataVersion}|${hasPisellosGetModule ? 1 : 0}`;
108
+ const retryGateKey = `${debouncedAtMs ?? "na"}|${floorDayBookingDepKey}|v${floorDayBookingDataVersion}|${hasPisellosGetModule ? 1 : 0}|fw${followWallClock ? 1 : 0}`;
108
109
  if (retryGateKey !== resourceListRetryGateRef.current) {
109
110
  resourceListRetryGateRef.current = retryGateKey;
110
111
  resourceListHostSkipRef.current = false;
@@ -129,7 +130,7 @@ function useReservationSalesHostData(options) {
129
130
  const timeStr = (0, import_dayjs.default)(debouncedSchedule.at).format(
130
131
  resourceBookingListTimeFormat
131
132
  );
132
- const currentTimeStr = (0, import_dayjs.default)().format(resourceBookingListTimeFormat);
133
+ const currentTimeStr = followWallClock ? timeStr : (0, import_dayjs.default)().format(resourceBookingListTimeFormat);
133
134
  const applyHostList = (raw) => {
134
135
  if (salesBookingRequestRef.current !== reqId) return;
135
136
  const normalized = (0, import_reservationTablesMerge.normalizeSalesResourceBookingListResult)(raw);
@@ -178,6 +179,7 @@ function useReservationSalesHostData(options) {
178
179
  */
179
180
  floorDayBookingDataVersion,
180
181
  debouncedAtMs,
182
+ followWallClock,
181
183
  pisellosRef
182
184
  ]);
183
185
  const [salesTimelineHighlights, setSalesTimelineHighlights] = (0, import_react.useState)(null);
@@ -1,12 +1,15 @@
1
- /// <reference types="react" />
1
+ import { type SetStateAction } from 'react';
2
2
  import type { ReservationScheduleBandValue } from '@pisell/materials';
3
3
  declare type OnScheduleChange = ((v: ReservationScheduleBandValue) => void) | undefined;
4
4
  /**
5
- * 顶栏「跟随当前时间」:30s 对齐日程,并通过 flushCommittedSchedule 触发防抖链。
5
+ * 顶栏「跟随当前时间」:在每自然分钟整点(*: * :00)对齐日程,`at` 使用当前分钟的 `startOf('minute')`,
6
+ * 并通过 flushCommittedSchedule 触发防抖链。
7
+ *
8
+ * @param wallClockFollowLocked 为 true 时(如大屏锁定 resourceWall):定时器始终运行,且不可通过交互关闭跟随。
6
9
  */
7
- export declare function useReservationWallClockFollow(scheduleValueProp: ReservationScheduleBandValue | undefined, setInternalSchedule: (v: ReservationScheduleBandValue) => void, flushCommittedSchedule: (v: ReservationScheduleBandValue) => void, onScheduleChange: OnScheduleChange): {
10
+ export declare function useReservationWallClockFollow(scheduleValueProp: ReservationScheduleBandValue | undefined, setInternalSchedule: (v: ReservationScheduleBandValue) => void, flushCommittedSchedule: (v: ReservationScheduleBandValue) => void, onScheduleChange: OnScheduleChange, wallClockFollowLocked?: boolean): {
8
11
  followWallClock: boolean;
9
- setFollowWallClock: import("react").Dispatch<import("react").SetStateAction<boolean>>;
12
+ setFollowWallClock: (value: SetStateAction<boolean>) => void;
10
13
  onFollowWallClockToggle: () => void;
11
14
  onTimeNavigatorUserInteraction: () => void;
12
15
  };
@@ -34,41 +34,72 @@ __export(useReservationWallClockFollow_exports, {
34
34
  module.exports = __toCommonJS(useReservationWallClockFollow_exports);
35
35
  var import_react = require("react");
36
36
  var import_dayjs = __toESM(require("dayjs"));
37
- function useReservationWallClockFollow(scheduleValueProp, setInternalSchedule, flushCommittedSchedule, onScheduleChange) {
38
- const [followWallClock, setFollowWallClock] = (0, import_react.useState)(true);
37
+ function useReservationWallClockFollow(scheduleValueProp, setInternalSchedule, flushCommittedSchedule, onScheduleChange, wallClockFollowLocked = false) {
38
+ const [followWallClock, setFollowWallClockInternal] = (0, import_react.useState)(true);
39
39
  const onScheduleChangeRef = (0, import_react.useRef)(onScheduleChange);
40
+ const scheduleValuePropRef = (0, import_react.useRef)(scheduleValueProp);
41
+ scheduleValuePropRef.current = scheduleValueProp;
42
+ const scheduleControlled = scheduleValueProp !== void 0;
43
+ const followWallClockEffective = wallClockFollowLocked || followWallClock;
44
+ (0, import_react.useEffect)(() => {
45
+ if (wallClockFollowLocked) setFollowWallClockInternal(true);
46
+ }, [wallClockFollowLocked]);
40
47
  (0, import_react.useEffect)(() => {
41
48
  onScheduleChangeRef.current = onScheduleChange;
42
49
  });
43
50
  (0, import_react.useEffect)(() => {
44
- if (!followWallClock) return;
51
+ if (!followWallClockEffective) return;
52
+ let timeoutId = null;
53
+ let cancelled = false;
54
+ const msUntilNextMinuteBoundary = () => {
55
+ const now = (0, import_dayjs.default)();
56
+ return Math.max(0, now.startOf("minute").add(1, "minute").diff(now));
57
+ };
45
58
  const tick = () => {
46
59
  var _a;
47
- const now = (0, import_dayjs.default)();
60
+ const now = (0, import_dayjs.default)().startOf("minute");
48
61
  const next = {
49
62
  date: now.startOf("day"),
50
63
  at: now
51
64
  };
52
- if (scheduleValueProp === void 0) {
65
+ if (scheduleValuePropRef.current === void 0) {
53
66
  setInternalSchedule(next);
54
67
  }
55
68
  (_a = onScheduleChangeRef.current) == null ? void 0 : _a.call(onScheduleChangeRef, next);
56
69
  flushCommittedSchedule(next);
57
70
  };
71
+ const scheduleNextBoundary = () => {
72
+ if (cancelled) return;
73
+ timeoutId = window.setTimeout(() => {
74
+ if (cancelled) return;
75
+ tick();
76
+ scheduleNextBoundary();
77
+ }, msUntilNextMinuteBoundary());
78
+ };
58
79
  tick();
59
- const id = window.setInterval(tick, 3e4);
60
- return () => window.clearInterval(id);
61
- }, [followWallClock, scheduleValueProp, flushCommittedSchedule]);
62
- const onFollowWallClockToggle = (0, import_react.useCallback)(
63
- () => setFollowWallClock((v) => !v),
64
- []
65
- );
66
- const onTimeNavigatorUserInteraction = (0, import_react.useCallback)(
67
- () => setFollowWallClock(false),
68
- []
80
+ scheduleNextBoundary();
81
+ return () => {
82
+ cancelled = true;
83
+ if (timeoutId != null) window.clearTimeout(timeoutId);
84
+ };
85
+ }, [followWallClockEffective, flushCommittedSchedule, scheduleControlled]);
86
+ const setFollowWallClock = (0, import_react.useCallback)(
87
+ (value) => {
88
+ if (wallClockFollowLocked) return;
89
+ setFollowWallClockInternal(value);
90
+ },
91
+ [wallClockFollowLocked]
69
92
  );
93
+ const onFollowWallClockToggle = (0, import_react.useCallback)(() => {
94
+ if (wallClockFollowLocked) return;
95
+ setFollowWallClockInternal((v) => !v);
96
+ }, [wallClockFollowLocked]);
97
+ const onTimeNavigatorUserInteraction = (0, import_react.useCallback)(() => {
98
+ if (wallClockFollowLocked) return;
99
+ setFollowWallClockInternal(false);
100
+ }, [wallClockFollowLocked]);
70
101
  return {
71
- followWallClock,
102
+ followWallClock: followWallClockEffective,
72
103
  setFollowWallClock,
73
104
  onFollowWallClockToggle,
74
105
  onTimeNavigatorUserInteraction
@@ -165,4 +165,10 @@ export interface PisellReservationProps extends Omit<RecordBoardProps, 'toolBar'
165
165
  bodyViewStorageKey?: string;
166
166
  /** 点击平面图左上角 HUD 打开窄屏表格抽屉(画布已绑定资源行) */
167
167
  floorMapHudTableDrawer?: boolean;
168
+ /**
169
+ * 强制「跟随当前时间」不可关闭(定时对齐墙钟、`getResourceBookingList` 与游标时间一致)。
170
+ * - **默认**:{@link lockedBodyView} 为 `resourceWall`,或用户在 RecordBoard 内**切换到「大屏 / resourceWall」子视图**时自动启用。
171
+ * - 设为 `true` 可在其它场景也强制跟随。
172
+ */
173
+ forceFollowWallClock?: boolean;
168
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pisell/private-materials",
3
- "version": "6.11.180",
3
+ "version": "6.11.181",
4
4
  "main": "./lib/index.js",
5
5
  "module": "./es/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -69,8 +69,8 @@
69
69
  "styled-components": "^6.0.0-rc.3",
70
70
  "@pisell/utils": "3.0.2",
71
71
  "@pisell/materials": "6.11.57",
72
- "@pisell/icon": "0.0.11",
73
- "@pisell/date-picker": "3.0.8"
72
+ "@pisell/date-picker": "3.0.8",
73
+ "@pisell/icon": "0.0.11"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "react": "^18.0.0",