/** 登录态:首价后补充信息确认窗口(对客户不暴露时限/渠道细节) */ /** * 决策窗:与总窗口对齐,前端不再单独倒计时逼迫用户。 * 驻留页仍需 Redis/浏览器 TTL;过短会导致填写中会话被清。 */ export const MS_REFINE_DECISION_MS = 3_600_000; /** 自首次驻留起的会话保留窗口(1 小时;仅内部资源回收,不对客户提示) */ export const MS_REFINE_TOTAL_MS = 3_600_000; export const MS_REFINE_HOLD_REDIS_PREFIX = "ms_refine_hold:"; export type MsRefineHoldState = { quote_id: string; quote_session_id: string; customer_id: string; parked_at_ms: number; decision_deadline_ms: number; total_deadline_ms: number; /** awaiting_decision | filling | refining | declined | refined | expired */ status: | "awaiting_decision" | "filling" | "refining" | "declined" | "refined" | "expired"; }; export type MsRefineHoldPublic = { available: boolean; quote_session_id: string; decision_deadline_ms: number; total_deadline_ms: number; decision_remaining_ms: number; total_remaining_ms: number; }; export function buildMsRefineHoldState(input: { quoteId: string; sessionId: string; customerId: string; nowMs?: number; }): MsRefineHoldState { const now = input.nowMs ?? Date.now(); return { quote_id: input.quoteId, quote_session_id: input.sessionId, customer_id: input.customerId, parked_at_ms: now, decision_deadline_ms: now + MS_REFINE_DECISION_MS, total_deadline_ms: now + MS_REFINE_TOTAL_MS, status: "awaiting_decision", }; } export function toMsRefineHoldPublic( state: MsRefineHoldState, nowMs = Date.now(), ): MsRefineHoldPublic { const decision_remaining_ms = Math.max(0, state.decision_deadline_ms - nowMs); const total_remaining_ms = Math.max(0, state.total_deadline_ms - nowMs); /** filling/refining 期间始终可用,避免填写过程中因倒计时丢失提交入口 */ const available = state.status === "refining" || state.status === "filling" || (state.status === "awaiting_decision" && total_remaining_ms > 0); return { available, quote_session_id: state.quote_session_id, decision_deadline_ms: state.decision_deadline_ms, total_deadline_ms: state.total_deadline_ms, decision_remaining_ms, total_remaining_ms, }; } /** * 不再因决策窗自动放弃:用户可随时选择继续或放弃。 * (保留函数签名,兼容 GET/服务层调用) */ export function shouldAutoDeclineRefineHold( _state: MsRefineHoldState, _nowMs = Date.now(), ): boolean { return false; } /** 内部会话窗口是否已过(资源回收 / 降级重查用,不对客户直出) */ export function isMsRefineHoldExpired( state: MsRefineHoldState, nowMs = Date.now(), ): boolean { return nowMs >= state.total_deadline_ms; } /** 倒计时文案(内部/兼容;客户 UI 不再展示) */ export function formatMsRefineRemainingLabel( deadlineMs: number, nowMs = Date.now(), ): string { const sec = Math.max(0, Math.ceil((deadlineMs - nowMs) / 1000)); const m = Math.floor(sec / 60); const s = sec % 60; return m > 0 ? `${m}分${String(s).padStart(2, "0")}秒` : `${s}秒`; } /** 客户可见提示(禁止:超时 / 二级 / 官网 / Save&update 等敏感词) */ export const MS_REFINE_MSG = { decisionTimeout: "请补充必要信息后重新获取报价", totalTimeout: "请补充必要信息后重新获取报价", totalTimeoutApi: "暂时无法获取报价,请稍后重试", decisionTimeoutApi: "暂时无法获取报价,请稍后重试", sessionGone: "报价会话已失效,请重新询价", } as const; /** 一级无价、需补详情后刷价(落库 error_code,status=done) */ export const MS_NEEDS_DETAILS_ERROR_CODE = "MS_NEEDS_DETAILS" as const; export const MS_NEEDS_DETAILS_MESSAGE = "请补充必要信息后重新获取报价";