<template>
	<view class="chat-page">
		<!-- Sidebar -->
		<view
			class="fe-sidebar"
			:class="{ 'fe-sidebar--open': sidebarOpen }"
			:style="chatHeaderOffsetStyle"
		>
			<view class="sidebar-head">
				<text class="sidebar-head__title">对话列表</text>
				<view class="sidebar-head__add" @tap="newChat">＋</view>
			</view>
			<scroll-view scroll-y class="sidebar-list" :show-scrollbar="false">
				<view v-if="sessionsLoading" class="sidebar-status">
					<text class="sidebar-status__text">加载中...</text>
				</view>
				<view v-else-if="!sessions.length" class="sidebar-status sidebar-status--action" @tap="newChat">
					<text class="sidebar-status__text sidebar-status__text--action">开启新对话</text>
				</view>
				<block v-else>
					<view
						v-for="(s, sessionIdx) in sessions"
						:key="s.conversationNo"
						class="fe-sidebar-item"
						:class="{ 'fe-sidebar-item--active': currentSession === s.conversationNo }"
						@tap="switchSessionByIndex(sessionIdx)"
					>
						<view class="fe-sidebar-item__title">{{ s.title }}</view>
						<view class="fe-sidebar-item__time">{{ s.time }}</view>
					</view>
				</block>
			</scroll-view>
		</view>

		<!-- Chat Area -->
		<scroll-view
			class="chat-area"
			:style="chatHeaderOffsetStyle"
			scroll-y
			:scroll-top="scrollTop"
			:scroll-into-view="scrollIntoView"
			:scroll-with-animation="scrollWithAnimation"
			:show-scrollbar="false"
			:enhanced="true"
			:upper-threshold="80"
			@scroll="onChatScroll"
			@scrolltoupper="onChatScrollToUpper"
		>
			<view class="chat-area__inner">
				<view v-if="showNewChatEmpty" class="chat-empty">
					<view class="chat-empty__icon">💬</view>
					<text class="chat-empty__title">{{ newChatGreeting }}</text>
					<view class="chat-empty__btn" @tap="newChat">开启新对话</view>
				</view>
				<block v-else>
				<view v-if="messagesLoading" class="chat-status">
					<text class="chat-status__text">加载消息中...</text>
				</view>
				<block v-else>
				<view v-if="messagesLoadingMore" class="chat-status chat-status--top">
					<text class="chat-status__text">加载历史消息...</text>
				</view>
				<view v-else-if="!hasMoreHistory && hasMessageHistory" class="chat-status chat-status--top">
					<text class="chat-status__text chat-status__text--muted">没有更多消息了</text>
				</view>
				<view v-for="(msg, idx) in messages" :key="msg.msgKey" class="chat-msg-wrap">
					<!-- Plain text -->
					<view v-if="msg.type === 'text'" class="fe-chat-msg" :class="'fe-chat-msg--' + msg.role" :id="'msg-' + idx">
						<view v-if="msg.role === 'ai'" class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view v-if="msg.role === 'user'" class="fe-chat-avatar fe-chat-avatar--user">{{ user.avatarText }}</view>
						<view class="fe-chat-bubble">
							<view v-if="msg.streaming && !msg.content" class="fe-thinking-dots fe-thinking-dots--inline">
								<view class="fe-thinking-dots__dot" />
								<view class="fe-thinking-dots__dot" />
								<view class="fe-thinking-dots__dot" />
							</view>
							<view v-else class="fe-chat-bubble__text-wrap">
								<FeChatMarkdown v-if="msg.role === 'ai'" :content="msg.content" />
								<text v-else class="fe-chat-bubble__text" user-select>{{ msg.content }}</text>
							</view>
						</view>
					</view>

					<!-- Report card -->
					<view v-else-if="msg.type === 'report'" class="fe-chat-msg fe-chat-msg--ai report-card-anchor" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view class="fe-biz-card fe-biz-card--report" @tap="openSheet('report')">
								<view class="fe-biz-card__header">📊 今日汇报</view>
								<view class="fe-biz-card__row"><text>1. 浦东仓库</text><text class="fe-biz-card__val">8/10 人已登记</text></view>
								<view class="fe-biz-card__row"><text>2. 王五实名失败</text><text class="fe-biz-card__val fe-biz-card__val--danger">已自动提醒</text></view>
								<view class="fe-biz-card__row"><text>3. 结算单待确认</text><text class="fe-biz-card__val">¥12,800</text></view>
								<view class="fe-biz-card__actions">
									<view class="fe-biz-btn" @tap.stop="toast('已发送催办')">一键催办</view>
								</view>
							</view>
						</view>
					</view>

					<!-- Draft card -->
					<view v-else-if="msg.type === 'draft'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view
								class="fe-biz-card fe-biz-card--draft"
								:class="msg.draftRootClass"
								@tap="openDraftByIndex(idx)"
							>
								<view class="fe-biz-card__header">
									<text class="fe-biz-card__header-title">📄 {{ (msg.draft && msg.draft.title) || '用工草稿' }}</text>
									<text class="fe-draft-card-status" :class="msg.draftStatusClass">{{ msg.draftStatusLabel }}</text>
								</view>
								<view
									v-if="msg.draft && msg.draft.showRecommendMetrics"
									class="fe-draft-recommend"
								>
									<text class="fe-draft-recommend__title">{{ msg.draft.recommendSchemeTitle || '方案推荐' }}</text>
									<view class="fe-draft-recommend__row fe-draft-recommend__row--single">
										<view class="fe-draft-recommend__cell">
											<text class="fe-draft-recommend__val">{{ msg.draft.companyTotalText }}</text>
											<text class="fe-draft-recommend__label">企业总支出</text>
										</view>
									</view>
								</view>
								<view
									v-if="msg.draft && msg.draft.workerGroupRows && msg.draft.workerGroupRows.length"
									class="fe-draft-groups"
								>
									<view
										v-for="(g, gIdx) in msg.draft.workerGroupRows"
										:key="gIdx"
										class="fe-draft-group"
									>
										<view class="fe-draft-group__title">
											工种 {{ gIdx + 1 }}
								</view>
										<view class="fe-biz-card__row">
											<text>岗位类型</text>
											<text class="fe-biz-card__val">{{ g.workType }}</text>
										</view>
										<view class="fe-biz-card__row">
											<text>工作地点</text>
											<text class="fe-biz-card__val">{{ g.workLocation }}</text>
										</view>
										<view class="fe-biz-card__row">
											<text>用工人数</text>
											<text class="fe-biz-card__val">{{ g.headcountText }}</text>
										</view>
									</view>
								</view>
								<view v-else>
								<view class="fe-biz-card__row">
									<text>岗位类型</text>
									<text class="fe-biz-card__val">{{ (msg.draft && msg.draft.workType) || '待补充' }}</text>
								</view>
								<view class="fe-biz-card__row">
									<text>工作地点</text>
									<text class="fe-biz-card__val">{{ (msg.draft && msg.draft.workLocation) || '待补充' }}</text>
								</view>
								<view class="fe-biz-card__row">
									<text>用工人数</text>
									<text class="fe-biz-card__val">{{ (msg.draft && msg.draft.headcountText) || '待补充' }}</text>
								</view>
								</view>
								<view class="fe-biz-card__actions fe-biz-card__actions--draft">
									<view class="fe-biz-btn fe-biz-btn--primary" @tap.stop="openDraftByIndex(idx)">查看详情</view>
									<text class="fe-biz-card__hint">如需修改草稿，请直接在对话中说明</text>
								</view>
								<view v-if="msg.voided" class="fe-draft-void-stamp">已作废</view>
							</view>
						</view>
					</view>

					<!-- 办理进度卡片（确认方案后；刷新时本地回填，不落库） -->
					<view v-else-if="msg.type === 'workflow'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view class="fe-biz-card fe-biz-card--workflow">
								<view class="fe-biz-card__header">
									<text class="fe-biz-card__header-title">📈 {{ (msg.workflow && msg.workflow.title) || '办理进度' }}</text>
								</view>
								<!-- 横向等分步骤条（不含草稿节点）：可点击跳转对应卡片 -->
								<view
									v-if="msg.workflow && msg.workflow.steps && msg.workflow.steps.length"
									class="fe-stepper"
								>
									<view
										v-for="(step, sIdx) in msg.workflow.steps"
										:key="sIdx"
										:class="[step.colClass, sIdx === msg.workflow.stepIndex ? 'fe-stepper__col--viewing' : '']"
										@tap="goWorkflowStepByIndex(idx, sIdx)"
									>
										<view class="fe-stepper__head">
											<view :class="step.leftRailClass" />
											<view :class="step.nodeClass">
												<text v-if="step.showNodeIcon" class="fe-stepper__node-text">{{ step.nodeIcon }}</text>
											</view>
											<view :class="step.rightRailClass" />
										</view>
										<view :class="step.labelClass">
											<text
												v-for="(line, li) in step.labelLines"
												:key="li"
												class="fe-stepper__label-line"
											>{{ line }}</text>
										</view>
									</view>
								</view>
								<view
									v-if="msg.workflow && msg.workflow.steps && msg.workflow.steps.length"
									class="fe-workflow-card__steps"
								>
									<swiper
										:class="msg.workflow.swiperClass || 'fe-workflow-swiper'"
										:current="msg.workflow.stepIndex"
										:indicator-dots="false"
										@change="onWorkflowSwiperByIndex(idx, $event)"
									>
										<swiper-item
											v-for="(step, sIdx) in msg.workflow.steps"
											:key="sIdx"
										>
											<!-- 企业认证引导面板 -->
											<view
												v-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'enterprise_cert'"
												:class="step.rootClass"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '🏢' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<view
													v-if="step.processPanel.bullets && step.processPanel.bullets.length"
													class="fe-process-card__list"
												>
													<view
														v-for="(item, bIdx) in step.processPanel.bullets"
														:key="bIdx"
														class="fe-process-card__bullet"
													>
														<view class="fe-process-card__dot" />
														<text class="fe-process-card__bullet-text">{{ item.text }}</text>
													</view>
												</view>
												<view
													v-if="step.processPanel.showActions"
													class="fe-biz-card__actions"
												>
													<view
														:class="step.processPanel.primaryBtnClass || 'fe-biz-btn fe-biz-btn--primary'"
														@tap.stop="goWorkflowCertByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.readonly ? '◎' : '⬆' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去认证' }}</text>
													</view>
													<view
														v-if="step.processPanel.showSkip"
														:class="step.processPanel.secondaryBtnClass || 'fe-biz-btn'"
														@tap.stop="skipWorkflowCertByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">»</text>
														<text>{{ step.processPanel.secondaryLabel || '跳过' }}</text>
													</view>
												</view>
											</view>
											<!-- 开票归档引导面板 -->
											<view
												v-else-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'invoice_archive'"
												:class="step.rootClass"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon fe-process-card__icon--invoice">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '▤' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<view
													v-if="step.processPanel.bullets && step.processPanel.bullets.length"
													class="fe-process-card__list"
												>
													<view
														v-for="(item, bIdx) in step.processPanel.bullets"
														:key="bIdx"
														class="fe-process-card__bullet"
													>
														<view class="fe-process-card__dot" />
														<text class="fe-process-card__bullet-text">{{ item.text }}</text>
													</view>
												</view>
												<view
													v-if="step.processPanel.showActions"
													class="fe-biz-card__actions"
												>
													<!-- 未到流程：仅展示禁用态，不绑 tap（小程序 pointer-events 不可靠） -->
													<view
														v-if="step.processPanel.actionDisabled"
														class="fe-biz-btn fe-biz-btn--primary fe-biz-btn--disabled"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '📄' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去开票' }}</text>
													</view>
													<view
														v-else
														class="fe-biz-btn fe-biz-btn--primary"
														@tap.stop="goWorkflowInvoiceByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '📄' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去开票' }}</text>
													</view>
												</view>
											</view>
											<!-- 结算打款引导面板 -->
											<view
												v-else-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'settlement'"
												:class="step.rootClass"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon fe-process-card__icon--settle">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '▤' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<view
													v-if="step.processPanel.bullets && step.processPanel.bullets.length"
													class="fe-process-card__list"
												>
													<view
														v-for="(item, bIdx) in step.processPanel.bullets"
														:key="bIdx"
														class="fe-process-card__bullet"
													>
														<view class="fe-process-card__dot" />
														<text class="fe-process-card__bullet-text">{{ item.text }}</text>
													</view>
												</view>
												<view
													v-if="step.processPanel.showActions"
													class="fe-biz-card__actions"
												>
													<view
														v-if="step.processPanel.actionDisabled"
														class="fe-biz-btn fe-biz-btn--primary fe-biz-btn--disabled"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '◎' }}</text>
														<text>{{ step.processPanel.primaryLabel || '查看结算单' }}</text>
													</view>
													<view
														v-else
														class="fe-biz-btn fe-biz-btn--primary"
														@tap.stop="goWorkflowSettlementByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '◎' }}</text>
														<text>{{ step.processPanel.primaryLabel || '查看结算单' }}</text>
													</view>
												</view>
											</view>
											<!-- 任务发布引导面板 -->
											<view
												v-else-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'task_publish'"
												:class="step.rootClass"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon fe-process-card__icon--task">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '✈' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<view
													v-if="step.processPanel.bullets && step.processPanel.bullets.length"
													class="fe-process-card__list"
												>
													<view
														v-for="(item, bIdx) in step.processPanel.bullets"
														:key="bIdx"
														class="fe-process-card__bullet"
													>
														<view class="fe-process-card__dot" />
														<text class="fe-process-card__bullet-text">{{ item.text }}</text>
													</view>
												</view>
												<view
													v-if="step.processPanel.showActions"
													class="fe-biz-card__actions"
												>
													<view
														v-if="step.processPanel.actionDisabled"
														class="fe-biz-btn fe-biz-btn--primary fe-biz-btn--disabled"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '✈' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去发布' }}</text>
													</view>
													<view
														v-else
														class="fe-biz-btn fe-biz-btn--primary"
														@tap.stop="goWorkflowTaskPublishByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '✈' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去发布' }}</text>
													</view>
												</view>
											</view>
											<!-- 临时工招聘引导面板 -->
											<view
												v-else-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'worker_recruitment'"
												:class="step.rootClass"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon fe-process-card__icon--recruit">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '🔍' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<view
													v-if="step.processPanel.progressText"
													class="fe-recruit-progress"
												>
													<text class="fe-recruit-progress__text">{{ step.processPanel.progressText }}</text>
												</view>
												<view
													v-if="step.processPanel.bullets && step.processPanel.bullets.length"
													class="fe-process-card__list"
												>
													<view
														v-for="(item, bIdx) in step.processPanel.bullets"
														:key="bIdx"
														class="fe-process-card__bullet"
													>
														<view class="fe-process-card__dot" />
														<text class="fe-process-card__bullet-text">{{ item.text }}</text>
													</view>
												</view>
												<view
													v-if="step.processPanel.showActions"
													class="fe-biz-card__actions"
												>
													<view
														v-if="step.processPanel.actionDisabled"
														class="fe-biz-btn fe-biz-btn--primary fe-biz-btn--disabled"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '🔍' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去招聘' }}</text>
													</view>
													<view
														v-else
														class="fe-biz-btn fe-biz-btn--primary"
														@tap.stop="goWorkflowRecruitByIndex(idx, sIdx)"
													>
														<text class="fe-biz-btn__icon">{{ step.processPanel.primaryIcon || '🔍' }}</text>
														<text>{{ step.processPanel.primaryLabel || '去招聘' }}</text>
													</view>
												</view>
											</view>
											<!-- 人员登记二维码面板（原独立二维码卡） -->
											<view
												v-else-if="step.hasProcessPanel && step.processPanel && step.processPanel.cardType === 'registration_qr'"
												:class="step.rootClass"
												@tap="openWorkflowQrByIndex(idx, sIdx)"
											>
												<view class="fe-process-card__head">
													<view class="fe-process-card__icon fe-process-card__icon--qr">
														<text class="fe-process-card__icon-text">{{ step.processPanel.iconText || '▦' }}</text>
													</view>
													<view class="fe-process-card__titles">
														<text class="fe-process-card__title">{{ step.processPanel.title }}</text>
														<text class="fe-process-card__subtitle">{{ step.processPanel.subtitle }}</text>
													</view>
													<text :class="step.processPanel.processStatusClass">{{ step.processPanel.processStatusLabel }}</text>
												</view>
												<view class="fe-process-card__divider" />
												<image
													v-if="step.processPanel.qrImageUrl"
													class="fe-workflow-qr__img"
													:src="step.processPanel.qrImageUrl"
													mode="aspectFit"
													@tap.stop="openWorkflowQrByIndex(idx, sIdx)"
												/>
												<view v-else class="fe-workflow-qr__empty" @tap.stop="refreshWorkflowQrByIndex(idx, sIdx)">
													<text class="fe-workflow-qr__empty-text">二维码加载中，可点此刷新</text>
												</view>
												<text v-if="step.processPanel.progressText" class="fe-workflow-qr__progress">{{ step.processPanel.progressText }}</text>
												<text class="fe-workflow-qr__hint">{{ step.processPanel.hint }}</text>
												<view class="fe-biz-card__actions">
													<view
														:class="step.processPanel.secondaryBtnClass || 'fe-biz-btn'"
														@tap.stop="openWorkflowQrProgressByIndex(idx, sIdx)"
													>
														{{ step.processPanel.secondaryLabel || '查看进度' }}
													</view>
													<view
														:class="step.processPanel.primaryBtnClass || 'fe-biz-btn fe-biz-btn--primary'"
														@tap.stop="openWorkflowQrByIndex(idx, sIdx)"
													>
														{{ step.processPanel.primaryLabel || '查看 / 分享' }}
													</view>
												</view>
											</view>
											<!-- 普通步骤摘要 -->
											<view v-else :class="step.rootClass">
												<view :class="step.dotClass">
													<text v-if="step.status === 'done'">✓</text>
													<text v-else-if="step.status === 'active'">⏱</text>
												</view>
												<text class="fe-workflow-step__title">{{ step.title }}</text>
												<text :class="step.statusClass">{{ step.statusLabel }}</text>
												<text v-if="step.highlight" class="fe-workflow-step__highlight">{{ step.highlight }}</text>
												<text v-if="step.time" class="fe-workflow-step__time">{{ step.time }}</text>
											</view>
										</swiper-item>
									</swiper>
									<view
										v-if="msg.workflow.steps.length > 1"
										class="fe-dot-indicator fe-dot-indicator--workflow"
									>
										<view
											v-for="(step, dIdx) in msg.workflow.steps"
											:key="dIdx"
											class="fe-dot-indicator__dot"
											:class="dIdx === msg.workflow.stepIndex ? 'fe-dot-indicator__dot--active' : ''"
										/>
									</view>
									<text class="fe-workflow-card__swipe-hint">点击上方步骤或左右滑动查看</text>
								</view>
							</view>
						</view>
					</view>

					<!-- QR card -->
					<view v-else-if="msg.type === 'qr'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view class="fe-biz-card fe-biz-card--qr" @tap="openQrByIndex(idx)">
								<view class="fe-biz-card__header">
									<text class="fe-biz-card__header-title">工人登记二维码</text>
								</view>
								<text v-if="qrSubtitleByIndex(idx)" class="fe-qr-card__subtitle">{{ qrSubtitleByIndex(idx) }}</text>
								<image
									v-if="qrImageUrlByIndex(idx)"
									class="fe-qr-card__img"
									:src="qrImageUrlByIndex(idx)"
									mode="aspectFit"
									@tap.stop="openQrByIndex(idx)"
								/>
								<view v-else class="fe-qr-card__empty">
									<text class="fe-qr-card__empty-text">二维码加载中，可点此刷新</text>
								</view>
								<text class="fe-qr-card__hint">{{ qrHintByIndex(idx) }}</text>
								<view class="fe-biz-card__actions">
									<view class="fe-biz-btn" @tap.stop="openProgressByQrIndex(idx)">查看进度</view>
									<view class="fe-biz-btn fe-biz-btn--primary" @tap.stop="openQrByIndex(idx)">查看 / 分享</view>
								</view>
							</view>
						</view>
					</view>

					<!-- Orders -->
					<view v-else-if="msg.type === 'orders'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view class="fe-biz-card" style="margin-bottom:16rpx;">
								<view class="fe-biz-card__header">📋 订单 #2024-001 · 活动执行</view>
								<view class="fe-biz-card__row"><text>状态</text><text class="fe-biz-card__val" style="color:#10B981;">进行中</text></view>
								<view class="fe-biz-card__row"><text>人数/时间</text><text class="fe-biz-card__val">10人 · 01-20至21</text></view>
								<view class="fe-biz-card__row"><text>已登记</text><text class="fe-biz-card__val">7/10人</text></view>
							</view>
							<view class="fe-biz-card">
								<view class="fe-biz-card__header">📋 订单 #2024-002 · 促销推广</view>
								<view class="fe-biz-card__row"><text>状态</text><text class="fe-biz-card__val" style="color:#F59E0B;">待确认</text></view>
								<view class="fe-biz-card__row"><text>人数/时间</text><text class="fe-biz-card__val">5人 · 01-25至26</text></view>
							</view>
						</view>
					</view>

					<!-- Summary bubbles -->
					<view v-else-if="msg.type === 'summary1'" class="fe-summary-bubble" @tap="openSheet('draft')" :id="'msg-' + idx">
						<text class="fe-summary-bubble__check">✓</text>
						<text><text class="fe-summary-bubble__text">方案A已确认</text> · 合规优先 · ¥12,800</text>
					</view>
					<view v-else-if="msg.type === 'summary2'" class="fe-summary-bubble" @tap="openSheet('settlement')" :id="'msg-' + idx">
						<text class="fe-summary-bubble__check">✓</text>
						<text><text class="fe-summary-bubble__text">结算已支付</text> · ¥50,400 · BATCH-001</text>
					</view>

					<!-- Knowledge actions -->
					<view v-else-if="msg.type === 'knowledge_actions'" class="fe-chat-msg fe-chat-msg--ai" :id="'msg-' + idx">
						<view class="fe-chat-avatar fe-chat-avatar--ai">AI</view>
						<view class="fe-chat-bubble">
							<view class="fe-quick-actions">
								<view class="fe-quick-action" @tap="triggerAI('政策法规')">
									<view class="fe-quick-action__icon" style="background:#DBEAFE;">📜</view>
									<text class="fe-quick-action__label">政策法规</text>
								</view>
								<view class="fe-quick-action" @tap="triggerAI('税务处理')">
									<view class="fe-quick-action__icon" style="background:#FEF3C7;">💰</view>
									<text class="fe-quick-action__label">税务处理</text>
								</view>
								<view class="fe-quick-action" @tap="triggerAI('合同管理')">
									<view class="fe-quick-action__icon" style="background:#D1FAE5;">📄</view>
									<text class="fe-quick-action__label">合同管理</text>
								</view>
								<view class="fe-quick-action" @tap="triggerAI('社保保险')">
									<view class="fe-quick-action__icon" style="background:#FEE2E2;">🛡</view>
									<text class="fe-quick-action__label">社保保险</text>
								</view>
							</view>
						</view>
					</view>
				</view>
				<view id="chat-bottom-anchor" class="chat-bottom-anchor" />
				</block>
				</block>
			</view>
		</scroll-view>

		<FeReportFloat :visible="showReportFloat" @open="openReportSheet" />

		<FeChatSideFab
			ref="chatSideFab"
			@sessions="openSidebar"
			@new-chat="newChat"
		/>

		<!-- Footer: Quick Actions + Input -->
		<view class="chat-footer">
			<!-- 快捷入口暂不展示
			<view class="fe-quick-actions fe-quick-actions--dock guide-target guide-target--quick-bar">
				<view class="fe-quick-action" @tap="triggerAI('新用工需求')">
					<view class="fe-quick-action__icon" style="background:#FEF3C7;">➕</view>
					<text class="fe-quick-action__label">发布需求</text>
				</view>
				<view class="fe-quick-action" @tap="openConversationCost">
					<view class="fe-quick-action__icon" style="background:#D1FAE5;">💰</view>
					<text class="fe-quick-action__label">成本测算</text>
				</view>
				<view class="fe-quick-action" @tap="openSheet('settlement')">
					<view class="fe-quick-action__icon" style="background:#FEE2E2;">💳</view>
					<text class="fe-quick-action__label">历史订单</text>
				</view>
			</view>
			-->
			<view
				class="fe-input-bar fe-input-bar--embedded"
				:class="{ 'fe-input-bar--disabled': inputDisabled }"
			>
				<view
					class="fe-input-bar__btn fe-input-bar__btn--voice"
					@tap="toggleVoiceMode"
				>{{ voiceModeIcon }}</view>
				<input
					v-if="!voiceMode"
					class="fe-input-bar__field"
					v-model="inputText"
					:disabled="inputDisabled"
					:placeholder="inputPlaceholder"
					confirm-type="send"
					@input="onInput"
					@confirm="sendMsg"
				/>
				<view
					v-else
					class="fe-input-bar__hold"
					:class="voiceHoldClass"
					@touchstart.prevent="onVoiceTouchStart"
					@touchend.prevent="onVoiceTouchEnd"
					@touchcancel.prevent="onVoiceTouchEnd"
				>
					<text class="fe-input-bar__hold-text">{{ voiceHoldLabel }}</text>
				</view>
				<view
					class="fe-input-bar__btn fe-input-bar__btn--send"
					:class="{ active: canSend }"
					@tap="sendMsg"
				>➤</view>
			</view>
		</view>

		<!-- 语音录音遮罩（无取消 / 转文字按钮） -->
		<view v-if="voicePanelVisible" class="fe-voice-mask">
			<view class="fe-voice-mask__tip">
				<view class="fe-voice-mask__wave">
					<view
						v-for="(bar, barIdx) in voiceWaveBars"
						:key="barIdx"
						class="fe-voice-mask__bar"
						:class="bar.cls"
					/>
				</view>
				<text class="fe-voice-mask__countdown">{{ voiceCountdown }}s</text>
			</view>
			<view class="fe-voice-mask__live">
				<text class="fe-voice-mask__live-text" user-select>{{ voiceLiveDisplay }}</text>
			</view>
			<view class="fe-voice-mask__footer">
				<text class="fe-voice-mask__footer-text">松开 发送</text>
			</view>
		</view>

		<!-- Overlay -->
		<view class="fe-overlay" :class="{ 'fe-overlay--active': overlayActive, 'fe-overlay--elevated': sheetElevated }" @tap="closeAll" />

		<!-- Sheets -->
		<FeDraftSheet
			:active="activeSheet === 'draft'"
			:draft="activeDraft"
			@confirm="confirmPlan"
			@updated="onDraftUpdated"
			@view-qr="openDraftQr"
		/>
		<FeConversationCostSheet
			:active="activeSheet === 'cost'"
			:conversation-id="currentConversationId"
			:draft-metas="costDraftMetas"
			@confirm="onConversationCostConfirm"
			@updated="onDraftUpdated($event, { sinkToBottom: false })"
			@view-qr="openDraftQr"
		/>
		<FeReportSheet :active="activeSheet === 'report'" />
		<FeProgressSheet
			:active="activeSheet === 'progress'"
			:elevated="sheetElevated"
			:draft-id="progressDraftId"
			:title="progressSheetTitle"
		/>
		<FeSettlementSheet
			:active="activeSheet === 'settlement'"
			:order-id="settlementOrderId"
			:draft-id="settlementDraftId"
			:readonly="settlementReadonly"
			@close="closeAll"
			@paid="onSettlementPaid"
		/>
		<FeInvoiceSheet
			:active="activeSheet === 'invoice'"
			:order-id="invoiceOrderId"
			:draft-id="invoiceDraftId"
			:source-title="invoiceSourceTitle"
			:readonly="invoiceReadonly"
			@close="closeAll"
			@success="onInvoiceSuccess"
		/>
		<FeEnterpriseCertSheet
			:active="activeSheet === 'enterpriseCert'"
			:draft-id="certDraftId"
			:readonly="certSheetReadonly"
			@close="closeAll"
			@success="onEnterpriseCertSuccess"
		/>
		<FeTaskPublishSheet
			:active="activeSheet === 'taskPublish'"
			:draft-id="taskPublishDraftId"
			:order-id="taskPublishOrderId"
			:readonly="taskPublishReadonly"
			@close="closeAll"
			@success="onTaskPublishSuccess"
		/>
		<FeRecruitSheet
			:active="activeSheet === 'recruit'"
			:draft-id="recruitDraftId"
			:readonly="recruitReadonly"
			@close="closeAll"
			@success="onRecruitSuccess"
		/>

		<!-- Drawer: Message -->
		<view class="fe-side-drawer" :class="{ 'fe-side-drawer--active': activeDrawer === 'message' }">
			<view class="fe-drawer-header"><view class="fe-drawer-title">🔔 消息与待办</view></view>
			<scroll-view scroll-y class="fe-drawer-body" :show-scrollbar="false">
				<view class="fe-drawer-body__inner">
				<view v-for="(m, i) in inbox" :key="i" class="fe-msg-item">
					<view style="display:flex;justify-content:space-between;margin-bottom:8rpx;">
						<text class="fe-msg-type" :class="'fe-msg-type--' + m.type">{{ m.type === 'pending' ? '待办' : '异常' }}</text>
						<text style="font-size:22rpx;color:#94A3B8;">{{ m.time }}</text>
					</view>
					<view style="font-weight:600;font-size:28rpx;">{{ m.title }}</view>
					<view style="font-size:26rpx;color:#64748B;margin-top:4rpx;">{{ m.desc }}</view>
				</view>
				</view>
			</scroll-view>
		</view>

		<!-- Drawer: 企业管理中枢 -->
		<view class="fe-side-drawer" :class="{ 'fe-side-drawer--active': activeDrawer === 'profile' }">
			<view class="fe-drawer-header"><view class="fe-drawer-title">企业管理中枢</view></view>
			<scroll-view scroll-y class="fe-drawer-body" :show-scrollbar="false">
				<view class="fe-drawer-body__inner">
				<EnterpriseHubPanel ref="hubPanel" show-logout @logout="logout" />
				</view>
			</scroll-view>
		</view>

		<!-- Modal Confirm -->
		<view class="fe-modal-overlay" :class="{ 'fe-modal-overlay--active': modal === 'confirm' || modal === 'payment' || modal === 'paySuccess' }" @tap="closeModal" />
		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'confirm' }">
			<view class="fe-modal-body">
				<view class="fe-modal-icon fe-modal-icon--success">✓</view>
				<view class="fe-modal-title">{{ confirmModalTitle }}</view>
				<view class="fe-modal-desc">
					<view>{{ confirmModalDescMain }}</view>
					<view>{{ confirmModalDescSub }}</view>
				</view>
			</view>
			<view class="fe-modal-footer">
				<view class="fe-modal-btn fe-modal-btn--cancel" @tap="closeModal">稍后查看</view>
				<view class="fe-modal-btn fe-modal-btn--confirm" @tap="showQR">查看二维码</view>
			</view>
		</view>

		<FeBatchQrModal
			:active="modal === 'qr'"
			:batch="qrBatch"
			@close="closeModal"
			@progress="openQrProgress"
			@progress-loaded="onQrProgressLoaded"
		/>

		<!-- Modal Payment -->
		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'payment' }">
			<view class="fe-modal-body">
				<view class="fe-modal-title">确认支付 ¥52,800</view>
				<view class="fe-modal-desc">将从企业对公账户扣款\n浦东仓库搬运 BATCH-001</view>
			</view>
			<view class="fe-modal-footer">
				<view class="fe-modal-btn fe-modal-btn--cancel" @tap="closeModal">取消</view>
				<view class="fe-modal-btn fe-modal-btn--confirm" @tap="showPaySuccess">确认支付</view>
			</view>
		</view>

		<!-- Modal Pay Success -->
		<view class="fe-modal" :class="{ 'fe-modal--active': modal === 'paySuccess' }">
			<view class="fe-modal-body">
				<view class="fe-modal-icon fe-modal-icon--success">✓</view>
				<view class="fe-modal-title">支付成功</view>
				<view class="fe-modal-desc">¥52,800 已支付\n结算单 BATCH-001</view>
			</view>
			<view class="fe-modal-footer">
				<view class="fe-modal-btn fe-modal-btn--confirm" style="flex:1;" @tap="closeModal">完成</view>
			</view>
		</view>
	</view>
</template>

<script>
import EnterpriseHubPanel from '@/packageA/components/EnterpriseHubPanel.vue'
import FeDraftSheet from '@/packageA/components/chat/FeDraftSheet.vue'
import FeConversationCostSheet from '@/packageA/components/chat/FeConversationCostSheet.vue'
import FeProgressSheet from '@/packageA/components/chat/FeProgressSheet.vue'
import FeSettlementSheet from '@/packageA/components/chat/FeSettlementSheet.vue'
import FeInvoiceSheet from '@/packageA/components/chat/FeInvoiceSheet.vue'
import FeEnterpriseCertSheet from '@/packageA/components/chat/FeEnterpriseCertSheet.vue'
import FeTaskPublishSheet from '@/packageA/components/chat/FeTaskPublishSheet.vue'
import FeRecruitSheet from '@/packageA/components/chat/FeRecruitSheet.vue'
import FeReportSheet from '@/packageA/components/chat/FeReportSheet.vue'
import FeReportFloat from '@/packageA/components/chat/FeReportFloat.vue'
import FeChatSideFab from '@/packageA/components/chat/FeChatSideFab.vue'
import FeBatchQrModal from '@/packageA/components/chat/FeBatchQrModal.vue'
import FeChatMarkdown from '@/packageA/components/chat/FeChatMarkdown.vue'
import store from '@/common/store.js'
import { MESSAGES, BATCH_QR, showToast } from '@/common/chat-data.js'
import {
	listConversations,
	createConversation,
	streamConversationChat,
	listConversationMessages
} from '@/api/conversation.js'
import { getDraftDetail, getDraftCostComparison, getDraftWorkflowProgress } from '@/api/draft.js'
import { buildWorkflowChatCardMessage, mapRegistrationBatchDetail } from '@/utils/registration-batch.js'
import {
	markEnterpriseCertSkipped,
	clearEnterpriseCertSkipped,
	syncStepProcessPanel
} from '@/utils/process-card.js'
import {
	getNewChatGreeting,
	mapConversationItem,
	mapConversationMessage,
	calcLastMessagePage,
	getLastConversationNo,
	setLastConversationNo,
	CONVERSATION_MESSAGE_PAGE_SIZE
} from '@/utils/conversation.js'
import {
	shouldFetchDraftCard,
	shouldShowOrderQrcode,
	isDraftCardContentType,
	buildDraftCardMessage,
	mapDraftDetailToCard,
	syncDraftCardViewMeta,
	getDraftStatus,
	pickDraftQrCodeUrl,
	pickDraftQrToken,
	parseChatCardPayload,
	formatDraftMoney,
	applyRecommendMetricsToDraftCard,
	applyRecommendMetricsFromDraftPlans
} from '@/utils/draft.js'
import {
	buildJobOfferFromDraft,
	saveJobOffer,
	buildJobOfferPageUrl,
	buildJobOfferShareMessage
} from '@/utils/job-offer.js'
import { resolveApiAssetUrl } from '@/common/config.js'
import {
	isWechatSIAvailable,
	ensureRecordAuth,
	initRecordRecognition,
	startRecordRecognition,
	stopRecordRecognition,
	formatSiError,
	WECHAT_SI_MAX_DURATION_MS
} from '@/utils/wechat-si.js'

export default {
	name: 'EnterpriseHome',
	options: {
		styleIsolation: 'shared'
	},
	props: {
		active: { type: Boolean, default: true }
	},
	components: {
		EnterpriseHubPanel,
		FeDraftSheet,
		FeConversationCostSheet,
		FeProgressSheet,
		FeSettlementSheet,
		FeInvoiceSheet,
		FeEnterpriseCertSheet,
		FeTaskPublishSheet,
		FeRecruitSheet,
		FeReportSheet,
		FeReportFloat,
		FeChatSideFab,
		FeBatchQrModal,
		FeChatMarkdown
	},
	data() {
		return {
			user: {},
			enterprise: {},
			sessions: [],
			sessionsLoading: false,
			creatingSession: false,
			currentSession: '',
			currentConversationId: null,
			messages: [],
			messagesLoading: false,
			messagesLoadingMore: false,
			historyPage: 1,
			hasMoreHistory: false,
			hasMessageHistory: false,
			messagePageSize: CONVERSATION_MESSAGE_PAGE_SIZE,
			inputText: '',
			canSend: false,
			chatStreaming: false,
			abortChat: null,
			scrollTop: 0,
			scrollIntoView: '',
			scrollWithAnimation: true,
			stickToBottom: true,
			sidebarOpen: false,
			activeSheet: '',
			activeDraft: null,
			activeDrawer: '',
			modal: '',
			confirmInfo: null,
			qrBatch: { ...BATCH_QR },
			/** 已处理过「方案已确认 + 二维码」的草稿 id，同会话内只执行一次 */
			orderConfirmHandledIds: {},
			inbox: MESSAGES,
			reportCardVisible: true,
			reportObserver: null,
			_localMsgSeq: 0,
			progressDraftId: null,
			progressSheetTitle: '人员登记',
			settlementOrderId: null,
			settlementDraftId: null,
			settlementReadonly: false,
			invoiceOrderId: null,
			invoiceDraftId: null,
			invoiceSourceTitle: '',
			invoiceReadonly: false,
			certDraftId: null,
			certSheetReadonly: false,
			taskPublishDraftId: null,
			taskPublishOrderId: null,
			taskPublishReadonly: false,
			recruitDraftId: null,
			recruitReadonly: false,
			costDraftMetas: [],
			/** 语音识别：按住说话 */
			voiceMode: false,
			voiceModeIcon: '🎤',
			voiceHoldLabel: '按住说话',
			voiceHoldClass: '',
			voicePanelVisible: false,
			voiceRecording: false,
			voiceRecognizing: false,
			voiceCountdown: 60,
			voiceLiveText: '',
			voiceLiveDisplay: '正在聆听...',
			voiceWaveBars: [
				{ cls: 'fe-voice-mask__bar--1' },
				{ cls: 'fe-voice-mask__bar--2' },
				{ cls: 'fe-voice-mask__bar--3' },
				{ cls: 'fe-voice-mask__bar--4' },
				{ cls: 'fe-voice-mask__bar--5' },
				{ cls: 'fe-voice-mask__bar--4' },
				{ cls: 'fe-voice-mask__bar--3' },
				{ cls: 'fe-voice-mask__bar--2' },
				{ cls: 'fe-voice-mask__bar--1' }
			],
			_voiceTouchActive: false,
			_voiceStarted: false,
			_voiceSiReady: false,
			_voiceCountdownTimer: null,
			_voiceAutoSend: false
		}
	},
	computed: {
		overlayActive() {
			return !!(this.sidebarOpen || this.activeSheet || this.activeDrawer)
		},
		confirmModalTitle() {
			const info = this.confirmInfo
			if (!info || !info.title) return '方案已确认'
			return `已确认：${info.title}`
		},
		confirmModalDescMain() {
			const info = this.confirmInfo
			if (!info) return '方案确认成功'
			const parts = []
			if (info.tagsText) parts.push(info.tagsText)
			if (info.total) parts.push(`总支出 ${info.total}`)
			if (info.avgCost) parts.push(`人均 ${info.avgCost}`)
			return parts.length ? parts.join(' · ') : '方案确认成功'
		},
		confirmModalDescSub() {
			const info = this.confirmInfo
			const orderId = info && info.orderId
			if (orderId) return `订单 ${orderId} 已生成，可查看批次二维码`
			return '确认后可查看并分享批次二维码'
		},
		hasReportMessage() {
			return this.messages.some((m) => m.type === 'report')
		},
		showReportFloat() {
			return this.hasReportMessage && !this.reportCardVisible
		},
		showNewChatEmpty() {
			return !this.sessionsLoading && !this.currentSession && !this.sessions.length
		},
		/** 未开启/选中会话时禁止输入 */
		inputDisabled() {
			return !this.currentConversationId || this.chatStreaming
		},
		inputPlaceholder() {
			if (!this.currentConversationId) return '请先开启新对话'
			if (this.chatStreaming) return 'AI 正在回复...'
			return '输入需求...'
		},
		newChatGreeting() {
			return getNewChatGreeting()
		},
		sheetElevated() {
			return this.modal === 'qr' && this.activeSheet === 'progress'
		},
		chatHeaderOffsetStyle() {
			// 使用微信原生导航栏时，fixed 区域从导航栏下方起算
			return { top: '0px' }
		}
	},
	onShow() {
		// 作为子组件时 onShow 不一定触发；首屏以 mounted 为准
		if (!this.active) return
		this.initHome()
	},
	mounted() {
		this.initVoiceRecognition()
		// 子组件首屏必须在此拉会话；与 onShow 重叠时由 initHome 内 Promise 去重
		if (this.active) this.initHome()
	},
	watch: {
		active(val) {
			if (val) this.initHome()
		}
	},
	onReady() {
		this.setupReportObserver()
	},
	beforeDestroy() {
		this.teardownVoiceRecognition()
		this.disconnectReportObserver()
		this.stopChatStream()
		if (this._scrollTimer) clearTimeout(this._scrollTimer)
	},
	methods: {
		toast: showToast,
		async initHome() {
			// 并发/连点去重：onShow + active watch 可能叠在一起
			if (this._initHomePromise) return this._initHomePromise
			this._initHomePromise = (async () => {
			const s = store.getState()
			this.user = s.user
			this.enterprise = s.enterprise
			await this.fetchSessions({ autoLoad: true })
				this.refreshWorkflowProcessPanels()
			})().finally(() => {
				this._initHomePromise = null
			})
			return this._initHomePromise
		},
		async fetchSessions(options = {}) {
			const { autoLoad = false, showError = false } = options
			this.sessionsLoading = true
			try {
				const list = await listConversations({ status: 'active' }, { showError })
				this.sessions = (list || []).map(mapConversationItem)
				if (autoLoad) {
					if (this.sessions.length) {
						const rememberedNo = this.currentSession || getLastConversationNo(this.user && this.user.userId)
						const current = rememberedNo
							? this.sessions.find((item) => item.conversationNo === rememberedNo)
							: null
						const target = current || this.sessions[0]
						await this.loadSession(target.conversationNo, target.chatType)
					} else if (!this.currentSession) {
						this.messages = []
					}
				}
			} catch (e) {
				if (autoLoad && !this.currentSession) {
					this.messages = []
				}
			} finally {
				this.sessionsLoading = false
			}
		},
		buildWelcomeMessages() {
			return [{
				type: 'text',
				role: 'ai',
				content: getNewChatGreeting(),
				msgKey: this.nextLocalMsgKey()
			}]
		},
		nextLocalMsgKey() {
			this._localMsgSeq += 1
			return -this._localMsgSeq
		},
		resetMessagePagination() {
			this.historyPage = 1
			this.hasMoreHistory = false
			this.hasMessageHistory = false
			this.messagesLoading = false
			this.messagesLoadingMore = false
		},
		async loadSession(conversationNo, chatType) {
			this.abortChatStream()
			this.currentSession = conversationNo
			setLastConversationNo(this.user && this.user.userId, conversationNo)
			const session = this.sessions.find((item) => item.conversationNo === conversationNo)
			this.currentConversationId = session ? session.id : null
			this.resetMessagePagination()
			this.messages = []
			this.orderConfirmHandledIds = {}
			this.reportCardVisible = true
			await this.fetchLatestMessages({ scrollBottom: true })
			this.markOrderConfirmHandledFromMessages()
			this.$nextTick(() => {
				this.setupReportObserver()
				setTimeout(() => this.checkReportVisibility(), 120)
			})
		},
		async fetchLatestMessages(options = {}) {
			const { scrollBottom = false } = options
			if (!this.currentConversationId) {
				this.messages = this.buildWelcomeMessages()
				return
			}

			this.messagesLoading = true
			try {
				const probe = await listConversationMessages(
					this.currentConversationId,
					{ page: 1, size: this.messagePageSize },
					{ showError: true }
				)
				const total = probe.total || 0
				if (!total) {
					this.messages = this.buildWelcomeMessages()
					this.hasMoreHistory = false
					this.hasMessageHistory = false
					return
				}

				const lastPage = calcLastMessagePage(total, this.messagePageSize)
				let pageData = probe
				if (lastPage > 1) {
					pageData = await listConversationMessages(
						this.currentConversationId,
						{ page: lastPage, size: this.messagePageSize },
						{ showError: true }
					)
				}

				this.historyPage = lastPage
				this.hasMoreHistory = lastPage > 1
				this.hasMessageHistory = true
				this.messages = (pageData.items || []).map(mapConversationMessage)
				await this.hydrateDraftCardsFromMessages()
				await this.hydrateWorkflowCardsFromMessages()
				if (scrollBottom) {
					this.stickToBottom = true
					this.ensureChatBottom()
				}
			} catch (e) {
				this.messages = this.buildWelcomeMessages()
				this.hasMoreHistory = false
				this.hasMessageHistory = false
			} finally {
				this.messagesLoading = false
			}
		},
		measureChatContentHeight() {
			return new Promise((resolve) => {
				const query = uni.createSelectorQuery().in(this)
				query.select('.chat-area__inner').boundingClientRect()
				query.exec((res) => {
					resolve((res[0] && res[0].height) || 0)
				})
			})
		},
		restoreScrollAfterPrepend(beforeHeight, beforeTop) {
			return new Promise((resolve) => {
				this.$nextTick(() => {
					setTimeout(async () => {
						const afterHeight = await this.measureChatContentHeight()
						const delta = afterHeight - beforeHeight
						this._programmaticScroll = true
						this.scrollWithAnimation = false
						this._scrollBump = (this._scrollBump || 0) + 1
						this.scrollTop = Math.max(0, beforeTop + delta) + this._scrollBump
						setTimeout(() => {
							this._programmaticScroll = false
							resolve()
						}, 60)
					}, 50)
				})
			})
		},
		async loadMoreHistory() {
			if (
				!this.hasMoreHistory ||
				this.messagesLoadingMore ||
				this.messagesLoading ||
				this.chatStreaming ||
				!this.currentConversationId
			) {
				return
			}

			const olderPage = this.historyPage - 1
			if (olderPage < 1) {
				this.hasMoreHistory = false
				return
			}

			this.messagesLoadingMore = true
			try {
				const beforeHeight = await this.measureChatContentHeight()
				const beforeTop = this._lastScrollTop || 0
				const pageData = await listConversationMessages(
					this.currentConversationId,
					{ page: olderPage, size: this.messagePageSize },
					{ showError: false }
				)
				const older = (pageData.items || []).map(mapConversationMessage)
				if (!older.length) {
					this.hasMoreHistory = false
					return
				}

				this.messages = [...older, ...this.messages]
				this.historyPage = olderPage
				this.hasMoreHistory = olderPage > 1
				await this.hydrateDraftCardsFromMessages(older)
				await this.hydrateWorkflowCardsFromMessages()
				await this.restoreScrollAfterPrepend(beforeHeight, beforeTop)
			} catch (e) {
				// 错误提示由 request.js 统一处理
			} finally {
				this.messagesLoadingMore = false
			}
		},
		onChatScrollToUpper() {
			this.loadMoreHistory()
		},
		switchSession(conversationNo) {
			if (this.currentSession === conversationNo) {
				this.sidebarOpen = false
				return
			}
			this.sidebarOpen = false
			const session = this.sessions.find((item) => item.conversationNo === conversationNo)
			this.loadSession(conversationNo, session && session.chatType)
		},
		switchSessionByIndex(index) {
			const session = this.sessions[index]
			if (!session) return
			this.switchSession(session.conversationNo)
		},
		async newChat() {
			if (this.creatingSession) return
			this.creatingSession = true
			this.sidebarOpen = false
			try {
				const created = await createConversation({
					chatType: 'employment',
					title: '新对话'
				})
				const item = mapConversationItem(created)
				this.sessions = [
					item,
					...this.sessions.filter((session) => session.conversationNo !== item.conversationNo)
				]
				this.loadSession(item.conversationNo, item.chatType)
				this.toast('已开启新对话')
			} catch (e) {
				// 错误提示由 request.js 统一处理
			} finally {
				this.creatingSession = false
			}
		},
		scrollBottom(animated = true, force = false) {
			if (!force && !this.stickToBottom && !this.chatStreaming) return
			if (this._scrollTimer) clearTimeout(this._scrollTimer)
			this._scrollTimer = setTimeout(() => {
				this._scrollTimer = null
				this.scrollWithAnimation = !!animated
				this._programmaticScroll = true
				this._scrollBump = (this._scrollBump || 0) + 1
				this.scrollTop = 100000 + this._scrollBump
				setTimeout(() => {
					this._programmaticScroll = false
				}, animated ? 300 : 60)
				setTimeout(() => this.checkReportVisibility(), 120)
			}, animated ? 40 : 0)
		},
		scrollToBottomAnchor() {
			this.scrollIntoView = ''
			this.$nextTick(() => {
				this.scrollIntoView = 'chat-bottom-anchor'
			})
		},
		ensureChatBottom() {
			this.stickToBottom = true
			const scroll = () => {
				this.scrollBottom(false, true)
				this.scrollToBottomAnchor()
			}
			this.$nextTick(() => {
				scroll()
				setTimeout(scroll, 60)
				setTimeout(scroll, 180)
				setTimeout(scroll, 360)
			})
		},
		onChatScroll(e) {
			const detail = e.detail || {}
			this._lastScrollTop = detail.scrollTop || 0
			if (this._programmaticScroll) {
				this.checkReportVisibility()
				return
			}
			const gap = (detail.scrollHeight || 0) - (detail.scrollTop || 0) - (detail.clientHeight || 0)
			if (this.chatStreaming) {
				// 流式输出时内容可能突增，仅当明显上滑才取消贴底
				if (gap > 280) this.stickToBottom = false
			} else {
				this.stickToBottom = gap < 120
			}
			if (
				!this.chatStreaming &&
				!this.messagesLoading &&
				!this.messagesLoadingMore &&
				this._lastScrollTop <= 20
			) {
				this.loadMoreHistory()
			}
			this.checkReportVisibility()
		},
		setupReportObserver() {
			this.disconnectReportObserver()
			if (!this.hasReportMessage) return
			this.$nextTick(() => {
				this.reportObserver = uni.createIntersectionObserver(this, { thresholds: [0, 0.01, 1] })
				this.reportObserver
					.relativeTo('.chat-area', { top: 0, bottom: 0 })
					.observe('.report-card-anchor', (res) => {
						this.reportCardVisible = res.intersectionRatio > 0
					})
			})
		},
		disconnectReportObserver() {
			if (this.reportObserver) {
				this.reportObserver.disconnect()
				this.reportObserver = null
			}
		},
		checkReportVisibility() {
			if (!this.hasReportMessage) {
				this.reportCardVisible = true
				return
			}
			const query = uni.createSelectorQuery().in(this)
			query.select('.chat-area').boundingClientRect()
			query.select('.report-card-anchor').boundingClientRect()
			query.exec((res) => {
				const area = res[0]
				const card = res[1]
				if (!area || !card) return
				this.reportCardVisible = card.bottom > area.top && card.top < area.bottom
			})
		},
		openReportSheet() {
			this.openSheet('report')
		},
		onInput() {
			this.canSend =
				!this.inputDisabled &&
				!!this.currentConversationId &&
				this.inputText.trim().length > 0 &&
				!this.chatStreaming &&
				!this.voiceRecording &&
				!this.voiceRecognizing &&
				!this.voiceMode
		},
		syncVoiceViewMeta() {
			this.voiceModeIcon = this.voiceMode ? '⌨️' : '🎤'
			if (this.chatStreaming && this.voiceMode) {
				this.voiceHoldLabel = '等待回复中...'
				this.voiceHoldClass = 'is-waiting'
			} else if (this.voiceRecording) {
				this.voiceHoldLabel = '松开 结束'
				this.voiceHoldClass = 'is-recording'
			} else {
				this.voiceHoldLabel = '按住说话'
				this.voiceHoldClass = ''
			}
		},
		clearVoiceCountdown() {
			if (this._voiceCountdownTimer) {
				clearInterval(this._voiceCountdownTimer)
				this._voiceCountdownTimer = null
			}
		},
		startVoiceCountdown() {
			this.clearVoiceCountdown()
			this.voiceCountdown = 60
			this._voiceCountdownTimer = setInterval(() => {
				if (this.voiceCountdown <= 1) {
					this.voiceCountdown = 0
					this.clearVoiceCountdown()
					this._voiceTouchActive = false
					stopRecordRecognition()
					return
				}
				this.voiceCountdown -= 1
			}, 1000)
		},
		setVoicePanel(visible) {
			this.voicePanelVisible = !!visible
			if (!visible) {
				this.clearVoiceCountdown()
				this.voiceCountdown = 60
				this.setVoiceLiveText('')
			}
		},
		setVoiceLiveText(text) {
			const t = (text || '').trim()
			this.voiceLiveText = t
			this.voiceLiveDisplay = t || '正在聆听...'
		},
		toggleVoiceMode() {
			if (this.inputDisabled) {
				if (!this.currentConversationId) this.toast('请先开启新对话')
				else if (this.chatStreaming) this.toast('请等待 AI 回复完成')
				return
			}
			if (this.voiceRecording || this.voiceRecognizing) return
			if (!this.voiceMode) {
				if (!isWechatSIAvailable() || !this._voiceSiReady) {
					this.toast('语音输入仅支持微信小程序')
					return
				}
				this.voiceMode = true
			} else {
				this.voiceMode = false
			}
			this.syncVoiceViewMeta()
			this.onInput()
		},
		exitVoiceModeToText() {
			this.voiceMode = false
			this.syncVoiceViewMeta()
			this.onInput()
		},
		initVoiceRecognition() {
			if (!isWechatSIAvailable()) {
				this._voiceSiReady = false
				return
			}
			const ok = initRecordRecognition({
				onStart: () => {
					this.voiceRecording = true
					this.voiceRecognizing = false
					this._voiceStarted = true
					this._voiceAutoSend = true
					this.setVoicePanel(true)
					this.startVoiceCountdown()
					this.syncVoiceViewMeta()
				},
				onRecognize: (res) => {
					const piece = ((res && res.result) || '').trim()
					if (!piece) return
					this.setVoiceLiveText(piece)
				},
				onStop: (res) => {
					this.voiceRecording = false
					this.voiceRecognizing = false
					this._voiceStarted = false
					const piece = ((res && res.result) || this.voiceLiveText || '').trim()
					const shouldSend = this._voiceAutoSend
					this._voiceAutoSend = false
					this.setVoicePanel(false)
					this.syncVoiceViewMeta()
					if (shouldSend && piece) {
						// 回复未完成时禁止连续发送
						if (this.chatStreaming) {
							this.toast('请等待 AI 回复完成')
							this.onInput()
							return
						}
						// 发送后保持语音模式，等回复结束后才能再按住
						this.submitChatMessage(piece)
						this.syncVoiceViewMeta()
					} else if (shouldSend) {
						this.toast('未识别到内容，请重试')
						this.onInput()
					} else {
						this.onInput()
					}
				},
				onError: (err) => {
					this.voiceRecording = false
					this.voiceRecognizing = false
					this._voiceStarted = false
					this._voiceAutoSend = false
					this.setVoicePanel(false)
					this.syncVoiceViewMeta()
					this.onInput()
					const code = err && (err.retcode != null ? err.retcode : err.errCode)
					if (String(code) === '-30012') return
					this.toast(formatSiError(err))
				}
			})
			this._voiceSiReady = !!ok
		},
		teardownVoiceRecognition() {
			this._voiceTouchActive = false
			this._voiceAutoSend = false
			this.clearVoiceCountdown()
			if (this._voiceStarted || this.voiceRecording) {
				try {
					stopRecordRecognition()
				} catch (e) {
					/* ignore */
				}
			}
			this.voiceRecording = false
			this.voiceRecognizing = false
			this._voiceStarted = false
			this.setVoicePanel(false)
			this.syncVoiceViewMeta()
		},
		async onVoiceTouchStart() {
			if (!this.voiceMode) return
			if (this.chatStreaming) {
				this.toast('请等待 AI 回复完成')
				return
			}
			if (this.inputDisabled) {
				if (!this.currentConversationId) this.toast('请先开启新对话')
				else if (this.chatStreaming) this.toast('请等待 AI 回复完成')
				return
			}
			if (!isWechatSIAvailable() || !this._voiceSiReady) {
				this.toast('语音输入仅支持微信小程序')
				return
			}
			if (this.voiceRecording || this.voiceRecognizing) return

			this._voiceTouchActive = true
			this._voiceAutoSend = true
			this.setVoiceLiveText('')
			this.setVoicePanel(true)
			this.voiceCountdown = 60
			this.voiceRecognizing = true
			this.syncVoiceViewMeta()

			const authed = await ensureRecordAuth()
			if (!this._voiceTouchActive) {
				this.voiceRecognizing = false
				this._voiceAutoSend = false
				this.setVoicePanel(false)
				this.syncVoiceViewMeta()
				return
			}
			// 授权等待期间可能已开始流式回复
			if (this.chatStreaming) {
				this.voiceRecognizing = false
				this._voiceAutoSend = false
				this.setVoicePanel(false)
				this.syncVoiceViewMeta()
				this.toast('请等待 AI 回复完成')
				return
			}
			if (!authed) {
				this.voiceRecognizing = false
				this._voiceAutoSend = false
				this.setVoicePanel(false)
				this.syncVoiceViewMeta()
				this.toast('未获得麦克风权限')
				return
			}

			try {
				await startRecordRecognition({
					duration: WECHAT_SI_MAX_DURATION_MS,
					lang: 'zh_CN'
				})
				if (!this._voiceTouchActive || this.chatStreaming) {
					stopRecordRecognition()
					if (this.chatStreaming) {
						this._voiceAutoSend = false
						this.toast('请等待 AI 回复完成')
					}
				}
			} catch (e) {
				this.voiceRecognizing = false
				this.voiceRecording = false
				this._voiceStarted = false
				this._voiceAutoSend = false
				this.setVoicePanel(false)
				this.syncVoiceViewMeta()
				this.toast(formatSiError(e) || '无法开始录音')
			}
		},
		onVoiceTouchEnd() {
			this._voiceTouchActive = false
			if (!this._voiceStarted && !this.voiceRecording) {
				if (this.voiceRecognizing) {
					this.voiceRecognizing = false
					this._voiceAutoSend = false
					this.setVoicePanel(false)
					this.syncVoiceViewMeta()
				}
				return
			}
			this.voiceRecognizing = true
			stopRecordRecognition()
		},
		stopChatStream() {
			if (this.abortChat) {
				this.abortChat()
				this.abortChat = null
			}
			this.chatStreaming = false
			this.syncVoiceViewMeta()
			this.onInput()
		},
		abortChatStream() {
			if (this.abortChat) {
				this.abortChat()
				this.abortChat = null
			}
		},
		sendMsg() {
			if (this.chatStreaming) {
				this.toast('请等待 AI 回复完成')
				return
			}
			this.submitChatMessage(this.inputText)
		},
		submitChatMessage(rawText) {
			if (this.chatStreaming) {
				this.toast('请等待 AI 回复完成')
				return
			}
			if (!this.currentConversationId) {
				this.toast('请先开启新对话')
				return
			}
			const text = (rawText || '').trim()
			if (!text) return

			this.abortChatStream()
			this.stickToBottom = true
			this.messages.push({ type: 'text', role: 'user', content: text, msgKey: this.nextLocalMsgKey() })
			const aiIdx = this.messages.length
			this.messages.push({
				type: 'text',
				role: 'ai',
				content: '',
				streaming: true,
				msgKey: this.nextLocalMsgKey()
			})
			this.inputText = ''
			this.canSend = false
			this.chatStreaming = true
			this.syncVoiceViewMeta()
			this.scrollBottom()

			this.abortChat = streamConversationChat({
				conversationId: this.currentConversationId,
				message: text,
				onDelta: (assembledText) => {
					const prev = this.messages[aiIdx].content || ''
					this.$set(this.messages[aiIdx], 'content', assembledText || '')
					const force = !!(assembledText && assembledText.length - prev.length > 80)
					this.scrollBottom(false, force)
					if (force) {
						this.$nextTick(() => this.scrollBottom(false, true))
					}
				},
				onDone: async (assembledText, cardMeta) => {
					this.$set(this.messages[aiIdx], 'streaming', false)
					if (assembledText) {
						this.$set(this.messages[aiIdx], 'content', assembledText)
					} else if (!this.messages[aiIdx].content) {
						this.$set(this.messages[aiIdx], 'content', '暂无回复，请稍后重试')
					}
					// 本轮 SSE 带草稿卡：card_draft 作废旧卡并末尾新卡；card_order 仅首次补卡
					if (shouldFetchDraftCard(cardMeta)) {
						await this.handleDraftCardFromChat(cardMeta)
					}
					// next_action=show_order_qrcode：同草稿只生成一次确认文案 + 二维码
					if (shouldShowOrderQrcode(cardMeta)) {
						await this.showOrderConfirmFromChat(cardMeta)
					}
					this.ensureChatBottom()
					this.stopChatStream()
					this.fetchSessions({ showError: false })
				},
				onError: (err) => {
					this.$set(this.messages[aiIdx], 'streaming', false)
					const message = (err && err.message) || '对话失败，请稍后重试'
					if (!this.messages[aiIdx].content) {
						this.$set(this.messages[aiIdx], 'content', message)
					} else {
						this.toast(message)
					}
					this.ensureChatBottom()
					this.stopChatStream()
				}
			})
		},
		triggerAI(text) {
			if (!this.currentConversationId) {
				this.toast('请先开启新对话')
				return
			}
			if (this.chatStreaming) return
			this.inputText = text
			this.canSend = true
			this.sendMsg()
		},
		isDraftVoidedByIndex(idx) {
			const msg = this.messages[idx]
			return !!(msg && msg.type === 'draft' && msg.voided)
		},
		isDraftConfirmedByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || msg.voided) return false
			return !!(msg.draft && getDraftStatus(msg.draft) === 'confirmed')
		},
		draftCardStatusLabelByIndex(idx) {
			if (this.isDraftVoidedByIndex(idx)) return '已作废'
			return this.isDraftConfirmedByIndex(idx) ? '已确认' : '未确认'
		},
		draftCardStatusClassByIndex(idx) {
			if (this.isDraftVoidedByIndex(idx)) return 'fe-draft-card-status--cancelled'
			return this.isDraftConfirmedByIndex(idx)
				? 'fe-draft-card-status--confirmed'
				: 'fe-draft-card-status--pending'
		},
		async openDraftByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || msg.type !== 'draft' || !msg.draft) return
			if (msg.voided) {
				this.toast('该草稿已作废')
				return
			}
			let draft = msg.draft
			this.closeAll()
			this.activeDraft = draft
			setTimeout(() => { this.activeSheet = 'draft' }, 50)

			// 回源刷新状态（确认后缺省字段仍能正确展示）
			const draftId = draft.draftId
			if (draftId == null) return
			try {
				const detail = await getDraftDetail(draftId, { showError: false })
				if (!detail || detail.id == null) return
				const card = mapDraftDetailToCard(detail)
				// 保留本地已确认方案快照，避免刷新后丢失成本展示
				if (draft.costPlans) card.costPlans = draft.costPlans
				if (draft.confirmedPlan) card.confirmedPlan = draft.confirmedPlan
				if (draft.confirmedPlanTitle) card.confirmedPlanTitle = draft.confirmedPlanTitle
				if (draft.orderId && !card.orderId) card.orderId = draft.orderId
				if (draft.qrToken && !card.qrToken) card.qrToken = draft.qrToken
				if (draft.scene && !card.scene) card.scene = draft.scene
				// qrCodeUrl 以详情接口为准；详情暂无时才回退本地
				if (!card.qrCodeUrl && draft.qrCodeUrl) card.qrCodeUrl = draft.qrCodeUrl
				if (card.qrToken && !card.scene) card.scene = card.qrToken
				if (draft.selectedSchemeCode && !card.selectedSchemeCode) {
					card.selectedSchemeCode = draft.selectedSchemeCode
				}
				// 详情未带 worker_groups 时保留卡片上的工种列表
				if (
					(!card.workerGroupRows || !card.workerGroupRows.length)
					&& draft.workerGroupRows
					&& draft.workerGroupRows.length
				) {
					card.workerGroupRows = draft.workerGroupRows
					if (draft.workerGroups) card.workerGroups = draft.workerGroups
				}
				// 方案推荐三列：优先再拉 cost-comparison，失败则保留本地
				const costComparison = await this.fetchDraftCostComparisonSafe(draftId)
				if (costComparison) {
					applyRecommendMetricsToDraftCard(card, costComparison)
				} else if (draft.showRecommendMetrics) {
					card.showRecommendMetrics = draft.showRecommendMetrics
					card.recommendSchemeTitle = draft.recommendSchemeTitle
					card.companyTotalText = draft.companyTotalText
					card.avgPerDayText = draft.avgPerDayText
					card.netPerDayText = draft.netPerDayText
				}
				this.onDraftUpdated(card, { sinkToBottom: false })
			} catch (e) {
				// 静默失败，沿用本地草稿
			}
		},
		/**
		 * 移除会话中指定草稿卡片（保留对话文本）
		 */
		removeDraftCardById(draftId) {
			if (draftId == null) return
			const id = Number(draftId)
			if (Number.isNaN(id)) return
			const key = String(id)
			for (let i = this.messages.length - 1; i >= 0; i--) {
				const msg = this.messages[i]
				if (msg && msg.type === 'draft' && String(msg.draftId) === key) {
					this.messages.splice(i, 1)
				}
			}
		},
		/**
		 * 更新草稿卡片（默认原地更新，不沉底）
		 * @param {object} draftCard
		 * @param {{ sinkToBottom?: boolean }} [options] 仅显式传 true 时才沉底
		 */
		onDraftUpdated(draftCard, options = {}) {
			if (!draftCard || draftCard.draftId == null) return
			applyRecommendMetricsFromDraftPlans(draftCard)
			const sinkToBottom = options.sinkToBottom === true
			const draftKey = String(draftCard.draftId)
			if (!sinkToBottom) {
				for (let i = this.messages.length - 1; i >= 0; i--) {
				const msg = this.messages[i]
					if (
						msg.type === 'draft'
						&& String(msg.draftId) === draftKey
						&& !msg.voided
					) {
						const prev = msg.draft
						if (!draftCard.showRecommendMetrics && prev && prev.showRecommendMetrics) {
							draftCard.showRecommendMetrics = prev.showRecommendMetrics
							draftCard.recommendSchemeTitle = prev.recommendSchemeTitle
							draftCard.companyTotalText = prev.companyTotalText
							draftCard.avgPerDayText = prev.avgPerDayText
							draftCard.netPerDayText = prev.netPerDayText
						} else if (
							draftCard.showRecommendMetrics
							&& !draftCard.recommendSchemeTitle
							&& prev
							&& prev.recommendSchemeTitle
						) {
							draftCard.recommendSchemeTitle = prev.recommendSchemeTitle
						}
						this.activeDraft = draftCard
					this.$set(this.messages[i], 'draft', draftCard)
						syncDraftCardViewMeta(this.messages[i])
						return
					}
				}
				// 仅有已作废同草稿卡时不「复活」新卡（避免普通对话末尾再插一张）
				const hasSameDraft = this.messages.some(
					(m) => m && m.type === 'draft' && String(m.draftId) === draftKey
				)
				if (hasSameDraft) {
					this.activeDraft = draftCard
					return
				}

				// 会话中尚无该草稿卡时追加一次
				this.activeDraft = draftCard
				this.messages.push(syncDraftCardViewMeta({
					type: 'draft',
					role: 'ai',
					msgKey: this.nextLocalMsgKey(),
					draftId: draftCard.draftId,
					voided: false,
					draft: draftCard
				}))
				return
			}
			this.activeDraft = draftCard
			this.removeDraftCardById(draftCard.draftId)
			this.messages.push(syncDraftCardViewMeta({
				type: 'draft',
				role: 'ai',
				msgKey: this.nextLocalMsgKey(),
				draftId: draftCard.draftId,
				voided: false,
				draft: draftCard
			}))
			this.ensureChatBottom()
		},
		/**
		 * SSE 草稿卡：
		 * - card_draft：作废旧卡并在末尾生成新卡（对话改草稿）
		 * - card_order：后端在「已有确认订单+二维码」时每轮都会注入，已有有效卡则跳过，避免普通问答反复插卡
		 */
		async handleDraftCardFromChat(cardMeta) {
			if (!shouldFetchDraftCard(cardMeta)) return null
			const draftId = cardMeta.relatedDraftId
			if (cardMeta.contentType === 'card_order') {
				const exists = this.messages.some(
					(m) => m
						&& m.type === 'draft'
						&& !m.voided
						&& String(m.draftId) === String(draftId)
				)
				if (exists) return null
			}
			return this.appendDraftCardFromChat(draftId)
		},
		/**
		 * 历史消息：每个 related_draft_id 对应一张草稿卡，插到「提到该 id」的文本之后；其它草稿互不影响
		 * @param {Array} [sourceMessages]
		 */
		async hydrateDraftCardsFromMessages(sourceMessages) {
			const list = Array.isArray(sourceMessages) ? sourceMessages : this.messages
			const draftIds = []
			for (let i = 0; i < list.length; i++) {
				const msg = list[i]
				if (!msg || msg.type !== 'text') continue
				if (!isDraftCardContentType(msg.contentType) || msg.relatedDraftId == null) continue
				const id = msg.relatedDraftId
				const prev = draftIds.indexOf(id)
				if (prev >= 0) draftIds.splice(prev, 1)
				draftIds.push(id)
			}
			for (let i = 0; i < draftIds.length; i++) {
				const id = draftIds[i]
				this.removeDraftCardById(id)
				await this.appendDraftCardById(id, { afterRelatedText: true })
			}
		},
		/**
		 * 根据当前消息中已有的二维码卡 / 进度卡 / 已确认草稿，标记对应草稿已处理过确认流程
		 */
		markOrderConfirmHandledFromMessages() {
			const map = Object.assign({}, this.orderConfirmHandledIds)
			for (let i = 0; i < this.messages.length; i++) {
				const msg = this.messages[i]
				if (!msg) continue
				if (msg.type === 'qr' && msg.draftId != null) {
					map[String(msg.draftId)] = true
				}
				if (msg.type === 'workflow' && msg.draftId != null) {
					map[String(msg.draftId)] = true
				}
				if (
					msg.type === 'draft'
					&& !msg.voided
					&& msg.draftId != null
					&& msg.draft
					&& getDraftStatus(msg.draft) === 'confirmed'
				) {
					map[String(msg.draftId)] = true
				}
			}
			this.orderConfirmHandledIds = map
		},
		/**
		 * 刷新/进会话：已确认草稿补「确认文案 + 办理进度卡」（均不落库，靠接口回填）
		 * 逐个调 GET /drafts/{id}/workflow-progress，与确认后一致
		 */
		async hydrateWorkflowCardsFromMessages() {
			const ids = await this.resolveWorkflowDraftIds()
			for (let i = 0; i < ids.length; i++) {
				await this.appendWorkflowCardByDraftId(ids[i], {
					afterDraftCard: true,
					withTip: true,
					silent: true
				})
			}
			this.refreshWorkflowProcessPanels()
		},
		/**
		 * 汇总需要展示进度卡的草稿 id：已确认草稿卡 / card_order / 会话进度列表
		 */
		async resolveWorkflowDraftIds() {
			const ids = []
			const seen = {}
			const push = (id) => {
				if (id == null || id === '') return
				const key = String(id)
				if (seen[key]) return
				seen[key] = true
				ids.push(id)
			}
			for (let i = 0; i < this.messages.length; i++) {
				const msg = this.messages[i]
				if (!msg) continue
				if (msg.type === 'draft' && !msg.voided && msg.draft) {
					const confirmed = getDraftStatus(msg.draft) === 'confirmed'
					const hasOrder = !!(msg.draft.orderId || msg.draft.order_id)
					if (confirmed || hasOrder) push(msg.draftId)
				}
				if (
					msg.type === 'text'
					&& (msg.contentType === 'card_order' || msg.contentType === 'workflow_tip')
					&& msg.relatedDraftId != null
				) {
					push(msg.relatedDraftId)
				}
			}
			// 不再调 registration-progress；草稿 id 仅从会话消息汇总，详情走 workflow-progress
			return ids
		},
		findWorkflowInsertAt(draftId) {
			let insertAt = -1
			for (let i = this.messages.length - 1; i >= 0; i--) {
				const msg = this.messages[i]
				if (
					msg
					&& msg.type === 'draft'
					&& !msg.voided
					&& String(msg.draftId) === String(draftId)
				) {
					insertAt = i + 1
					break
				}
			}
			if (insertAt < 0) {
				for (let i = this.messages.length - 1; i >= 0; i--) {
					const msg = this.messages[i]
					if (
						msg
						&& msg.type === 'text'
						&& msg.relatedDraftId != null
						&& String(msg.relatedDraftId) === String(draftId)
						&& (msg.contentType === 'card_order' || isDraftCardContentType(msg.contentType))
					) {
						insertAt = i + 1
						break
					}
				}
			}
			return insertAt
		},
		ensureWorkflowTipMessage(draftId, insertAt) {
			const exists = this.messages.some(
				(m) => m
					&& m.type === 'text'
					&& m.contentType === 'workflow_tip'
					&& String(m.relatedDraftId) === String(draftId)
			)
			if (exists) return insertAt
			const tip = {
				type: 'text',
				role: 'ai',
				msgKey: this.nextLocalMsgKey(),
				content: '方案已确认。下方为当前办理进度，可左右滑动查看各步骤。',
				contentType: 'workflow_tip',
				relatedDraftId: draftId
			}
			if (insertAt >= 0) {
				this.messages.splice(insertAt, 0, tip)
				return insertAt + 1
			}
			this.messages.push(tip)
			return -1
		},
		/**
		 * 将进度卡插入会话：默认插在对应草稿卡后，否则末尾。
		 * 若已存在同草稿进度卡，用最新 workflow-progress 重建步骤条与面板（保留 msgKey）。
		 * @param {{ afterDraftCard?: boolean, withTip?: boolean }} [options]
		 */
		insertWorkflowCardMessage(raw, draftId, options = {}) {
			if (draftId == null) return null
			const existingIdx = this.messages.findIndex(
				(m) => m && m.type === 'workflow' && String(m.draftId) === String(draftId)
			)
			if (existingIdx >= 0) {
				const existing = this.messages[existingIdx]
				if (options.withTip) {
					const at = this.findWorkflowInsertAt(draftId)
					this.ensureWorkflowTipMessage(draftId, at >= 0 ? at : -1)
				}
				const rebuilt = buildWorkflowChatCardMessage(raw, existing.msgKey, {
					enterpriseRegistered: store.isEnterpriseRegistered()
				})
				const next = Object.assign({}, existing, {
					draftId: rebuilt.draftId != null ? rebuilt.draftId : draftId,
					workflow: rebuilt.workflow
				})
				this.$set(this.messages, existingIdx, next)
				// 复用已拉到的 raw，避免再打一遍 workflow-progress
				return this.enrichWorkflowQrPanel(next, raw).then(() => next)
			}
			const card = buildWorkflowChatCardMessage(raw, this.nextLocalMsgKey(), {
				enterpriseRegistered: store.isEnterpriseRegistered()
			})
			let insertAt = options.afterDraftCard ? this.findWorkflowInsertAt(draftId) : -1
			if (options.withTip) {
				insertAt = this.ensureWorkflowTipMessage(draftId, insertAt)
			}
			if (insertAt >= 0) {
				this.messages.splice(insertAt, 0, card)
			} else {
				this.messages.push(card)
			}
			// 复用已拉到的 raw；仅缺二维码时才补拉
			return this.enrichWorkflowQrPanel(card, raw).then(() => card)
		},
		/** 按草稿重新拉 workflow-progress，刷新横向步骤条与面板 */
		async refreshWorkflowCardByDraftId(draftId) {
			if (draftId == null || draftId === '') return null
			return this.appendWorkflowCardByDraftId(draftId, {
				afterDraftCard: false,
				withTip: false,
				silent: true
			})
		},
		isOrderConfirmHandled(draftId) {
			if (draftId == null) return false
			return !!this.orderConfirmHandledIds[String(draftId)]
		},
		markOrderConfirmHandled(draftId) {
			if (draftId == null) return
			this.$set(this.orderConfirmHandledIds, String(draftId), true)
		},
		/**
		 * 将会话中同草稿 / 同订单的有效草稿卡标记为已作废
		 */
		voidRelatedDraftCards(options = {}) {
			const draftId = options.draftId
			const orderId = options.orderId
			const draftKey = draftId != null ? String(draftId) : ''
			const orderKey = orderId != null && orderId !== '' ? String(orderId) : ''
			if (!draftKey && !orderKey) return

			for (let i = 0; i < this.messages.length; i++) {
				const msg = this.messages[i]
				if (!msg || msg.type !== 'draft' || msg.voided) continue
				const sameDraft = draftKey && String(msg.draftId) === draftKey
				const msgOrderId = msg.draft && (msg.draft.orderId || msg.draft.order_id)
				const sameOrder = orderKey && msgOrderId != null && String(msgOrderId) === orderKey
				if (sameDraft || sameOrder) {
					this.$set(this.messages[i], 'voided', true)
					syncDraftCardViewMeta(this.messages[i])
				}
			}
		},
		/**
		 * SSE 返回草稿卡：作废上方同订单/同草稿旧卡，并在会话末尾生成新卡
		 */
		async fetchDraftCostComparisonSafe(draftId) {
			if (draftId == null) return null
			try {
				return await getDraftCostComparison(draftId, { showError: false })
			} catch (e) {
				return null
			}
		},
		async appendDraftCardFromChat(draftId) {
			if (draftId == null) return null
			const id = Number(draftId)
			if (Number.isNaN(id)) return null

			try {
				const detail = await getDraftDetail(id, { showError: false })
				if (!detail || detail.id == null) return null
				const orderId = detail.orderId || detail.order_id || ''
				this.voidRelatedDraftCards({ draftId: id, orderId })
				const costComparison = await this.fetchDraftCostComparisonSafe(id)
				const cardMsg = buildDraftCardMessage(detail, this.nextLocalMsgKey(), { costComparison })
				cardMsg.voided = false
				this.messages.push(cardMsg)
				this.ensureChatBottom()
				return cardMsg
			} catch (e) {
				return null
			}
		},
		/**
		 * 历史消息还原：插到提到该草稿 id 的文本后（不作废链路）
		 * @param {number} draftId
		 * @param {{ afterRelatedText?: boolean }} [options]
		 */
		async appendDraftCardById(draftId, options = {}) {
			if (draftId == null) return null
			const id = Number(draftId)
			if (Number.isNaN(id)) return null
			const exists = this.messages.some((m) => m.type === 'draft' && m.draftId === id && !m.voided)
			if (exists) return null

			try {
				const detail = await getDraftDetail(id, { showError: false })
				if (!detail || detail.id == null) return null
				const costComparison = await this.fetchDraftCostComparisonSafe(id)
				const cardMsg = buildDraftCardMessage(detail, this.nextLocalMsgKey(), { costComparison })
				cardMsg.voided = false

				if (options.afterRelatedText) {
					let insertAt = -1
					for (let i = this.messages.length - 1; i >= 0; i--) {
						const msg = this.messages[i]
						if (
							msg.type === 'text'
							&& msg.relatedDraftId === id
							&& isDraftCardContentType(msg.contentType)
						) {
							insertAt = i + 1
							break
						}
					}
					if (insertAt < 0) {
					for (let i = this.messages.length - 1; i >= 0; i--) {
						const msg = this.messages[i]
						if (msg.type === 'text' && msg.relatedDraftId === id) {
							insertAt = i + 1
							break
						}
					}
					}
					if (insertAt < 0) return null
						this.messages.splice(insertAt, 0, cardMsg)
				} else {
					this.messages.push(cardMsg)
				}
				this.ensureChatBottom()
				return cardMsg
			} catch (e) {
				return null
			}
		},
		openSidebar() {
			if (this.$refs.chatSideFab && typeof this.$refs.chatSideFab.collapse === 'function') {
				this.$refs.chatSideFab.collapse()
			}
			if (this.sidebarOpen) {
				this.sidebarOpen = false
				return
			}
			this.activeSheet = ''
			this.activeDrawer = ''
			this.sidebarOpen = true
			this.fetchSessions()
		},
		openSheet(name) {
			this.closeAll()
			setTimeout(() => { this.activeSheet = name }, 50)
		},
		openDrawer(name) {
			this.closeAll()
			setTimeout(() => {
				this.activeDrawer = name
				if (name === 'profile' && this.$refs.hubPanel) {
					this.$refs.hubPanel.refreshPasswordStatus()
				}
			}, 50)
		},
		closeAll() {
			const progressDraftId = this.activeSheet === 'progress' ? this.progressDraftId : null
			this.sidebarOpen = false
			this.activeSheet = ''
			this.activeDrawer = ''
			this.activeDraft = null
			if (this.$refs.chatSideFab && typeof this.$refs.chatSideFab.collapse === 'function') {
				this.$refs.chatSideFab.collapse()
			}
			// 关闭进度弹层后回填步骤条（审核/登记状态可能已变）
			if (progressDraftId != null && progressDraftId !== '') {
				this.refreshWorkflowCardByDraftId(progressDraftId)
			}
		},
		closeModal() {
			const qrDraftId = this.modal === 'qr' && this.qrBatch
				? this.qrBatch.draftId
				: null
			this.modal = ''
			this.confirmInfo = null
			if (qrDraftId != null && qrDraftId !== '') {
				this.refreshWorkflowCardByDraftId(qrDraftId)
			}
		},
		/**
		 * 用工场景标题：地点+岗位 / 草稿标题
		 */
		resolveDraftSceneTitle(draft, fallback) {
			if (!draft) return fallback || '用工批次'
			const title = draft.title && draft.title !== '用工草稿' ? draft.title : ''
			if (title) return title
			const loc = draft.workLocation && draft.workLocation !== '待补充' ? draft.workLocation : ''
			const type = draft.workType && draft.workType !== '待补充' ? draft.workType : ''
			const combined = `${loc}${type}`
			return combined || fallback || '用工批次'
		},
		buildQrBatch(draft, options = {}) {
			const draftId = options.draftId
				|| (draft && (draft.draftId != null ? draft.draftId : (draft.detail && draft.detail.id)))
				|| null
			const batchId = (draft && draft.draftNo) || (draftId != null ? `draft_${draftId}` : '')
			const sceneTitle = this.resolveDraftSceneTitle(draft, options.schemeTitle)
			const workerCount = draft && draft.workerCount != null ? Number(draft.workerCount) : 0
			const subtitle = batchId ? `${sceneTitle} ${batchId}` : sceneTitle
			const enterpriseName = (this.enterprise && this.enterprise.name) || ''
			const scheme = options.scheme || (draft && draft.confirmedPlan) || null
			const qrCodeUrl = options.qrCodeUrl || pickDraftQrCodeUrl(draft) || ''
			const qrToken = options.qrToken
				|| pickDraftQrToken(draft)
				|| options.orderId
				|| (draft && draft.orderId)
				|| ''
			const offer = buildJobOfferFromDraft(draft, {
				batchId,
				draftId,
				scheme,
				schemeTitle: options.schemeTitle || (scheme && scheme.title),
				enterpriseName,
				orderId: options.orderId || (draft && draft.orderId) || '',
				qrToken,
				scene: qrToken || options.scene || (draft && draft.scene) || ''
			})
			saveJobOffer(offer)
			return Object.assign({}, BATCH_QR, {
				modalTitle: '方案已确认',
				title: sceneTitle,
				batchId,
				draftId: offer.draftId,
				orderId: offer.orderId || '',
				qrToken: offer.qrToken || qrToken || '',
				scene: offer.scene || offer.qrToken || qrToken || '',
				subtitle,
				qrCodeUrl,
				total: workerCount > 0 ? workerCount : BATCH_QR.total,
				registered: 0,
				hint: '分享给临时工扫码即可登记',
				shareLink: buildJobOfferPageUrl(offer)
			})
		},
		/**
		 * 从草稿详情拉取最新 qrCodeUrl / qrToken，并回写 activeDraft
		 */
		async refreshDraftQrFromDetail(draftId) {
			if (draftId == null) return { qrCodeUrl: '', qrToken: '' }
			try {
				const detail = await getDraftDetail(draftId, { showError: false })
				if (!detail || detail.id == null) return { qrCodeUrl: '', qrToken: '' }
				const qrCodeUrl = pickDraftQrCodeUrl(detail)
				const qrToken = pickDraftQrToken(detail)
				const card = mapDraftDetailToCard(detail)
				const prev = this.activeDraft
				if (prev && prev.draftId === detail.id) {
					const merged = Object.assign({}, prev, card, {
						qrCodeUrl,
						qrToken,
						scene: qrToken || prev.scene || '',
						confirmedPlan: prev.confirmedPlan || card.confirmedPlan,
						confirmedPlanTitle: prev.confirmedPlanTitle || card.confirmedPlanTitle,
						costPlans: prev.costPlans || card.costPlans
					})
					this.onDraftUpdated(merged, { sinkToBottom: false })
				}
				return { qrCodeUrl, qrToken }
			} catch (e) {
				return { qrCodeUrl: '', qrToken: '' }
			}
		},
		/**
		 * 确认方案后在对话框追加二维码卡片（同 draft 已有则不再追加）
		 */
		appendQrCardMessage(batch) {
			if (!batch) return
			const draftId = batch.draftId
			if (draftId != null) {
				const exists = this.messages.some((m) => m && m.type === 'qr' && m.draftId === draftId)
				if (exists) return
			}
			this.messages.push({
				type: 'qr',
				role: 'ai',
				msgKey: this.nextLocalMsgKey(),
				draftId: draftId != null ? draftId : null,
				batch: Object.assign({}, batch)
			})
			this.ensureChatBottom()
		},
		syncQrCardMessage(batch) {
			if (!batch || batch.draftId == null) return
			for (let i = this.messages.length - 1; i >= 0; i--) {
				const m = this.messages[i]
				if (m && m.type === 'qr' && m.draftId === batch.draftId) {
					this.$set(this.messages, i, Object.assign({}, m, {
						batch: Object.assign({}, batch)
					}))
					return
				}
			}
		},
		qrImageUrlByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || msg.type !== 'qr' || !msg.batch) return ''
			const raw = msg.batch.qrCodeUrl || msg.batch.qr_code_url || ''
			return resolveApiAssetUrl(raw)
		},
		qrSubtitleByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || !msg.batch) return ''
			return msg.batch.subtitle || msg.batch.title || ''
		},
		qrHintByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || !msg.batch) return '分享给临时工扫码即可登记'
			return msg.batch.hint || '分享给临时工扫码即可登记'
		},
		openQrByIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || msg.type !== 'qr' || !msg.batch) return
			this.qrBatch = Object.assign({}, msg.batch)
			this.showQR()
		},
		openProgressByQrIndex(idx) {
			const msg = this.messages[idx]
			if (!msg || msg.type !== 'qr') return
			const draftId = msg.draftId
				|| (msg.batch && msg.batch.draftId)
			this.openProgressDetail(draftId)
		},
		/**
		 * AI 对话 next_action=show_order_qrcode：
		 * 同草稿只执行一次：写确认文案 + 办理进度卡，草稿卡原地标已确认（不沉底）
		 */
		async showOrderConfirmFromChat(cardMeta) {
			const payload = parseChatCardPayload(cardMeta && cardMeta.cardPayload)
			const draftId = (cardMeta && cardMeta.relatedDraftId != null)
				? cardMeta.relatedDraftId
				: (payload && (payload.draft_id != null ? payload.draft_id : payload.draftId))
			if (this.isOrderConfirmHandled(draftId)) return

			let draft = null
			if (this.activeDraft && String(this.activeDraft.draftId) === String(draftId)) {
				draft = this.activeDraft
			} else if (draftId != null) {
				for (let i = this.messages.length - 1; i >= 0; i--) {
					const msg = this.messages[i]
					if (msg && msg.type === 'draft' && String(msg.draftId) === String(draftId) && msg.draft) {
						draft = msg.draft
						break
					}
				}
			}
			if (!draft && draftId != null) {
				try {
					const detail = await getDraftDetail(draftId, { showError: false })
					if (detail) draft = mapDraftDetailToCard(detail)
				} catch (e) {
					// ignore
				}
			}

			const orderId = (payload && (payload.order_id != null ? payload.order_id : payload.orderId))
				|| (draft && draft.orderId)
				|| ''
			const qrCodeUrl = (payload && (payload.qr_code_url || payload.qrCodeUrl))
				|| pickDraftQrCodeUrl(draft)
				|| ''
			const qrToken = (payload && (payload.qr_token || payload.qrToken))
				|| pickDraftQrToken(draft)
				|| ''

			this.markOrderConfirmHandled(draftId)

			if (draft && draftId != null) {
				this.onDraftUpdated(Object.assign({}, draft, {
					status: 'confirmed',
					orderId,
					qrToken,
					qrCodeUrl,
					scene: qrToken || orderId || ''
				}), { sinkToBottom: false })
			}

			this.closeAll()
			// 确认后提示 + 进度卡追加到会话末尾，便于往下看
			await this.appendWorkflowCardByDraftId(draftId, {
				afterDraftCard: false,
				withTip: true
			})
			this.ensureChatBottom()
			setTimeout(() => this.ensureChatBottom(), 420)
		},
		async confirmPlan(payload) {
			const scheme = (payload && payload.scheme) || null
			const result = (payload && payload.result) || {}
			const draft = this.activeDraft
			const orderId = result.orderId || result.order_id || ''
			const qrCodeUrl = pickDraftQrCodeUrl(result) || pickDraftQrCodeUrl(draft)
			const qrToken = pickDraftQrToken(result) || pickDraftQrToken(draft) || ''
			const draftId = (payload && payload.draftId != null)
				? payload.draftId
				: (draft && draft.draftId)
			const scene = qrToken || orderId || ''
			this.confirmInfo = {
				title: (scheme && scheme.title) || (payload && payload.schemeCode) || '',
				code: (payload && payload.schemeCode) || (scheme && scheme.code) || '',
				total: (scheme && scheme.total) || '',
				avgCost: (scheme && scheme.avgCost) || '',
				tagsText: '',
				orderId,
				qrToken,
				qrCodeUrl,
				draftId
			}
			if (draft && draftId != null) {
				this.onDraftUpdated(Object.assign({}, draft, {
					orderId,
					qrToken,
					qrCodeUrl,
					scene,
					status: 'confirmed',
					confirmedPlan: (scheme && {
						id: scheme.id || scheme.code,
						code: scheme.code,
						title: scheme.title,
						total: scheme.total,
						avgCost: scheme.avgCost,
						groupRows: scheme.groupRows,
						rows: scheme.rows,
						breakdown: scheme.breakdown,
						compliance: scheme.compliance,
						raw: scheme.raw
					}) || draft.confirmedPlan
				}))
			}
			this.closeAll()
			this.markOrderConfirmHandled(draftId)
			// 确认后提示 + 进度卡追加到会话末尾，并贴底
			await this.appendWorkflowCardByDraftId(draftId, {
				afterDraftCard: false,
				withTip: true
			})
			this.ensureChatBottom()
			setTimeout(() => this.ensureChatBottom(), 420)
		},
		/**
		 * 拉取办理流程进度，插入提示文案 + 可左右滑动的进度卡片（不落库）
		 * @param {number|string} draftId
		 * @param {{ afterDraftCard?: boolean, withTip?: boolean, silent?: boolean }} [options]
		 */
		async appendWorkflowCardByDraftId(draftId, options = {}) {
			if (draftId == null || draftId === '') return null
			const silent = options.silent === true
			const withTip = options.withTip !== false
			try {
				let raw = null
				// 确认刚完成时批次可能尚未可读，轻量重试
				for (let i = 0; i < 3; i++) {
					try {
						raw = await getDraftWorkflowProgress(draftId, { showError: false })
						if (raw) break
					} catch (e) {
						raw = null
					}
					if (i < 2) {
						await new Promise((resolve) => setTimeout(resolve, 400))
					}
				}
				if (!raw) {
					if (!silent) showToast('进度加载失败，可稍后在进度管理中查看')
					return null
				}
				const card = await this.insertWorkflowCardMessage(raw, draftId, {
					afterDraftCard: options.afterDraftCard === true,
					withTip
				})
				this.markOrderConfirmHandled(draftId)
				return card
			} catch (e) {
				if (!silent) showToast('进度加载失败，可稍后在进度管理中查看')
				return null
			}
		},
		onWorkflowSwiperByIndex(msgIdx, e) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const cur = e && e.detail ? e.detail.current : 0
			this.$set(msg.workflow, 'stepIndex', cur)
		},
		/** 点击横向步骤条，跳转到对应步骤卡片 */
		goWorkflowStepByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const steps = msg.workflow.steps || []
			if (stepIdx == null || stepIdx < 0 || stepIdx >= steps.length) return
			if (msg.workflow.stepIndex === stepIdx) return
			this.$set(msg.workflow, 'stepIndex', stepIdx)
		},
		/**
		 * 从登记页返回：轻量同步企业认证面板。
		 * 接口 steps 为 active 时不得被本地 registered 盖成 done。
		 */
		refreshWorkflowProcessPanels() {
			const registered = store.isEnterpriseRegistered()
			if (registered) clearEnterpriseCertSkipped()
			for (let i = 0; i < this.messages.length; i++) {
				const msg = this.messages[i]
				if (!msg || msg.type !== 'workflow' || !msg.workflow || !msg.workflow.steps) continue
				const steps = msg.workflow.steps
				for (let s = 0; s < steps.length; s++) {
					const step = steps[s]
					if (!step || !step.hasProcessPanel || !step.processPanel) continue
					if (step.processPanel.cardType !== 'enterprise_cert') continue
					let nextStatus = step.status || 'pending'
					// 仅 pending → done；active/done 保持卡片已有状态（来自 workflow-progress）
					if (registered && nextStatus === 'pending') {
						nextStatus = 'done'
						this.$set(step, 'status', 'done')
						this.$set(step, 'statusLabel', '已完成')
						this.$set(step, 'nodeIcon', '✓')
						this.$set(step, 'nodeClass', 'fe-stepper__node fe-stepper__node--done')
						this.$set(step, 'labelClass', 'fe-stepper__label fe-stepper__label--done')
						this.$set(step, 'rootClass', 'fe-workflow-step fe-workflow-step--process fe-workflow-step--done')
					}
					syncStepProcessPanel(step, nextStatus, {
						skipped: !registered && step.processPanel.processStatus === 'skipped'
					})
					this.$set(steps, s, step)
				}
				this.$set(this.messages, i, msg)
			}
		},
		goWorkflowCertByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow') return
			const step = msg.workflow && msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'enterprise_cert') return
			const draftId = msg.draftId != null
				? msg.draftId
				: (msg.workflow && msg.workflow.draftId)
			// 以流程步骤为准：active=未完成本单认证需提交；done=仅查看
			const isDone = step.status === 'done'
				|| (step.processPanel && step.processPanel.processStatus === 'done')
			const readonly = !!isDone
			if (!readonly && (draftId == null || draftId === '')) {
				showToast('缺少关联草稿，暂无法认证')
				return
			}
			this.certDraftId = draftId
			this.certSheetReadonly = readonly
			this.openSheet('enterpriseCert')
		},
		async onEnterpriseCertSuccess() {
			const draftId = this.certDraftId
			this.closeAll()
			try {
				await store.syncProfile({ showError: false })
			} catch (e) {
				// 忽略：仍按本地已登记状态刷新进度卡
			}
			if (store.isEnterpriseRegistered()) clearEnterpriseCertSkipped()
			// 重建进度卡步骤条（门禁/连线/面板一并更新）
			if (draftId != null && draftId !== '') {
				await this.refreshWorkflowCardByDraftId(draftId)
			} else {
				this.refreshWorkflowProcessPanels()
			}
			this.ensureChatBottom()
		},
		isWorkflowActionBlocked(step) {
			if (!step || !step.processPanel) return true
			const panel = step.processPanel
			// 已完成：仍可进弹层查看
			if (step.status === 'done' || panel.processStatus === 'done' || panel.viewOnly) return false
			// 登记/招聘：方案确认后可点，不因接口仍 pending 拦截
			const isStaffingPanel = panel.cardType === 'registration_qr'
				|| panel.cardType === 'worker_recruitment'
			if (isStaffingPanel) {
				return panel.actionDisabled === true || panel.actionLocked === true
			}
			if (panel.actionDisabled === true || panel.actionLocked === true) return true
			if (panel.processStatus === 'pending' || step.status === 'pending') return true
			if (panel.processStatus !== 'current' && step.status !== 'active') return true
			return false
		},
		isWorkflowStepViewOnly(step) {
			if (!step || !step.processPanel) return false
			return step.status === 'done'
				|| step.processPanel.processStatus === 'done'
				|| step.processPanel.viewOnly === true
		},
		goWorkflowRecruitByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'worker_recruitment') return
			if (this.isWorkflowActionBlocked(step)) return
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			const panel = step.processPanel
			// 查看招聘：与人员登记进度弹层同一套内容，仅标题改为「临时工招聘」
			const isViewRecruit = this.isWorkflowStepViewOnly(step)
				|| panel.viewOnly === true
				|| panel.primaryLabel === '查看招聘'
			if (isViewRecruit) {
				this.openProgressDetail(draftId, '临时工招聘')
				return
			}
			this.openRecruitSheet(draftId)
		},
		openRecruitSheet(draftId) {
			if (draftId == null || draftId === '') {
				showToast('缺少草稿信息，无法招聘')
				return
			}
			this.recruitDraftId = draftId
			this.recruitReadonly = false
			this.openSheet('recruit')
		},
		async onRecruitSuccess(payload) {
			const draftId = (payload && payload.draftId) || this.recruitDraftId
			if (draftId != null && draftId !== '') {
				await this.refreshWorkflowCardByDraftId(draftId)
			}
			this.ensureChatBottom()
		},
		goWorkflowTaskPublishByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'task_publish') return
			if (this.isWorkflowActionBlocked(step)) return
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			const detail = msg.workflow.detail || {}
			const orderId = detail.orderId || detail.order_id || null
			this.openTaskPublishSheet(draftId, orderId, this.isWorkflowStepViewOnly(step))
		},
		openTaskPublishSheet(draftId, orderId, readonly) {
			if (draftId == null || draftId === '') {
				showToast('缺少草稿信息，无法发布任务')
				return
			}
			this.taskPublishDraftId = draftId
			this.taskPublishOrderId = orderId != null ? orderId : null
			this.taskPublishReadonly = !!readonly
			this.openSheet('taskPublish')
		},
		async onTaskPublishSuccess(payload) {
			const draftId = (payload && payload.draftId) || this.taskPublishDraftId
			if (draftId != null && draftId !== '') {
				await this.refreshWorkflowCardByDraftId(draftId)
			}
			this.ensureChatBottom()
		},
		goWorkflowSettlementByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'settlement') return
			if (this.isWorkflowActionBlocked(step)) return
			const detail = msg.workflow.detail || {}
			const orderId = detail.orderId || detail.order_id || ''
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			const readonly = this.isWorkflowStepViewOnly(step)
			if (orderId !== '' && orderId != null) {
				this.openSettlementByOrderId(orderId, draftId, readonly)
				return
			}
			this.openProgressDetail(draftId)
		},
		goWorkflowInvoiceByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'invoice_archive') return
			if (this.isWorkflowActionBlocked(step)) return
			const detail = msg.workflow.detail || {}
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			const orderId = detail.orderId || detail.order_id || null
			const sourceTitle = detail.subtitle
				|| (detail.batchNo ? `${detail.title || ''} ${detail.batchNo}`.trim() : (detail.title || ''))
			this.openInvoiceSheet(orderId, draftId, sourceTitle, this.isWorkflowStepViewOnly(step))
		},
		openInvoiceSheet(orderId, draftId, sourceTitle, readonly) {
			if ((orderId == null || orderId === '') && (draftId == null || draftId === '')) {
				showToast('缺少订单信息，无法开票')
				return
			}
			this.invoiceOrderId = orderId != null ? orderId : null
			this.invoiceDraftId = draftId != null ? draftId : null
			this.invoiceSourceTitle = sourceTitle || ''
			this.invoiceReadonly = !!readonly
			this.openSheet('invoice')
		},
		async onInvoiceSuccess(payload) {
			const draftId = (payload && payload.draftId) || this.invoiceDraftId
			if (draftId != null && draftId !== '') {
				await this.refreshWorkflowCardByDraftId(draftId)
			}
			this.ensureChatBottom()
		},
		skipWorkflowCertByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'enterprise_cert') return
			markEnterpriseCertSkipped()
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			const detail = msg.workflow.detail || {}
			// 用缓存 detail 本地重建（跳过仅前端标记，需重算门禁与步骤条）
			const rebuilt = buildWorkflowChatCardMessage({
				draft_id: draftId,
				draftId,
				title: detail.title,
				subtitle: detail.subtitle,
				batch_no: detail.batchNo,
				order_id: detail.orderId,
				order_no: detail.orderNo,
				qr_code_url: detail.qrCodeUrl,
				qr_token: detail.qrToken,
				progress: detail.progress,
				stats: {
					pending_count: detail.stats && detail.stats.pending,
					in_progress_count: detail.stats && detail.stats.inProgress,
					completed_count: detail.stats && detail.stats.completed,
					expected_count: detail.stats && detail.stats.expected,
					registered_count: detail.stats && detail.stats.registered
				},
				steps: (detail.steps || []).map((s) => ({
					step_code: s.key,
					title: s.title,
					status: s.status,
					time: s.time,
					highlight: s.highlight
				}))
			}, msg.msgKey, {
				enterpriseRegistered: store.isEnterpriseRegistered()
			})
			this.$set(msg, 'workflow', rebuilt.workflow)
			const nextIdx = Math.min(
				rebuilt.workflow.stepIndex != null ? rebuilt.workflow.stepIndex : stepIdx + 1,
				(rebuilt.workflow.steps || []).length - 1
			)
			this.$set(msg.workflow, 'stepIndex', Math.max(0, nextIdx))
			this.$set(this.messages, msgIdx, msg)
			showToast('已跳过企业认证，可稍后在「我的」完成')
		},
		findWorkflowQrStep(msg) {
			if (!msg || msg.type !== 'workflow' || !msg.workflow || !msg.workflow.steps) return null
			const steps = msg.workflow.steps
			for (let i = 0; i < steps.length; i++) {
				const step = steps[i]
				if (step && step.processPanel && step.processPanel.cardType === 'registration_qr') {
					return { step, stepIdx: i }
				}
			}
			return null
		},
		/**
		 * 补全进度卡内人员登记二维码
		 * @param {object} card
		 * @param {object} [rawPrefetch] 已有的 workflow-progress 响应，传入则不再请求
		 */
		async enrichWorkflowQrPanel(card, rawPrefetch) {
			if (!card || card.type !== 'workflow') return
			const found = this.findWorkflowQrStep(card)
			if (!found) return
			const panel = found.step.processPanel
			let qrCodeUrl = panel.qrCodeUrl || ''
			let qrToken = panel.qrToken || ''
			let registered = panel.registered
			let total = panel.total
			const draftId = card.draftId != null ? card.draftId : (card.workflow && card.workflow.draftId)
			let raw = rawPrefetch || null
			// 已有二维码/进度时不必再打接口；仅缺数据且无预取时才补拉
			const needFetch = !raw
				&& draftId != null
				&& !qrCodeUrl
				&& !(card.workflow && card.workflow.detail && card.workflow.detail.qrCodeUrl)
			if (needFetch) {
				try {
					raw = await getDraftWorkflowProgress(draftId, { showError: false })
				} catch (e) {
					raw = null
				}
			}
			if (raw && draftId != null) {
				const detail = mapRegistrationBatchDetail(Object.assign({}, raw, { draft_id: draftId }))
				if (card.workflow) this.$set(card.workflow, 'detail', detail)
				qrCodeUrl = detail.qrCodeUrl || qrCodeUrl
				qrToken = detail.qrToken || qrToken
				if (detail.progress) {
					registered = detail.progress.current
					total = detail.progress.total
				}
			}
			if (!qrCodeUrl && card.workflow && card.workflow.detail) {
				qrCodeUrl = card.workflow.detail.qrCodeUrl || qrCodeUrl
				qrToken = card.workflow.detail.qrToken || qrToken
			}
			const qrImageUrl = resolveApiAssetUrl(qrCodeUrl)
			this.$set(panel, 'qrCodeUrl', qrCodeUrl || '')
			this.$set(panel, 'qrToken', qrToken || '')
			this.$set(panel, 'qrImageUrl', qrImageUrl || '')
			if (registered != null) this.$set(panel, 'registered', registered)
			if (total != null) this.$set(panel, 'total', total)
			if (total > 0) {
				this.$set(panel, 'progressText', `已登记 ${registered || 0}/${total}`)
			}
			const msgIdx = this.messages.findIndex((m) => m && m.msgKey === card.msgKey)
			if (msgIdx >= 0) {
				const msg = this.messages[msgIdx]
				const step = msg.workflow && msg.workflow.steps && msg.workflow.steps[found.stepIdx]
				if (step && step.processPanel) {
					this.$set(step.processPanel, 'qrCodeUrl', panel.qrCodeUrl)
					this.$set(step.processPanel, 'qrToken', panel.qrToken)
					this.$set(step.processPanel, 'qrImageUrl', panel.qrImageUrl)
					this.$set(step.processPanel, 'registered', panel.registered)
					this.$set(step.processPanel, 'total', panel.total)
					this.$set(step.processPanel, 'progressText', panel.progressText)
				}
			}
		},
		async refreshWorkflowQrByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow') return
			await this.enrichWorkflowQrPanel(msg)
			const step = msg.workflow.steps[stepIdx]
			if (step && step.processPanel && !step.processPanel.qrImageUrl) {
				showToast('二维码暂不可用，请稍后重试')
			}
		},
		/** 人员登记：查看/分享（数据来自 workflow-progress） */
		async openWorkflowQrByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow' || !msg.workflow) return
			const step = msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'registration_qr') return
			if (step.processPanel.actionDisabled) {
				showToast(step.processPanel.lockHint || '当前步骤尚未开始')
				return
			}
			const draftId = msg.draftId != null ? msg.draftId : msg.workflow.draftId
			if (draftId == null || draftId === '') {
				showToast('缺少草稿信息')
				return
			}
			try {
				const raw = await getDraftWorkflowProgress(draftId, { showError: true })
				const detail = mapRegistrationBatchDetail(Object.assign({}, raw, { draft_id: draftId }))
				this.$set(msg.workflow, 'detail', detail)
				const panel = step.processPanel
				const qrCodeUrl = detail.qrCodeUrl || panel.qrCodeUrl || ''
				const qrToken = detail.qrToken || panel.qrToken || ''
				const current = (detail.progress && detail.progress.current) || 0
				const total = (detail.progress && detail.progress.total) || 0
				this.$set(panel, 'qrCodeUrl', qrCodeUrl)
				this.$set(panel, 'qrToken', qrToken)
				this.$set(panel, 'qrImageUrl', resolveApiAssetUrl(qrCodeUrl))
				this.$set(panel, 'registered', current)
				this.$set(panel, 'total', total)
				if (total > 0) this.$set(panel, 'progressText', `已登记 ${current}/${total}`)

				const draft = this.activeDraft && String(this.activeDraft.draftId) === String(draftId)
					? this.activeDraft
					: null
				this.qrBatch = this.buildQrBatch(draft || {
					draftId,
					title: detail.title,
					workerCount: total
				}, {
					draftId,
					qrCodeUrl,
					qrToken,
					orderId: detail.orderId || '',
					schemeTitle: detail.title || ''
				})
				this.$set(this.qrBatch, 'subtitle', detail.subtitle || panel.subtitle || this.qrBatch.subtitle)
				this.$set(this.qrBatch, 'registered', current)
				this.$set(this.qrBatch, 'total', total)
				this.modal = 'qr'
			} catch (e) {
				// request 层已提示
			}
		},
		/** 人员登记：查看进度（FeProgressSheet 走 workflow-progress） */
		openWorkflowQrProgressByIndex(msgIdx, stepIdx) {
			const msg = this.messages[msgIdx]
			if (!msg || msg.type !== 'workflow') return
			const step = msg.workflow && msg.workflow.steps && msg.workflow.steps[stepIdx]
			if (!step || !step.processPanel || step.processPanel.cardType !== 'registration_qr') return
			if (step.processPanel.actionDisabled) {
				showToast(step.processPanel.lockHint || '当前步骤尚未开始')
				return
			}
			this.openProgressDetail(msg.draftId || (msg.workflow && msg.workflow.draftId))
		},
		async showQR() {
			const draftId = (this.qrBatch && this.qrBatch.draftId)
				|| (this.confirmInfo && this.confirmInfo.draftId)
				|| (this.activeDraft && this.activeDraft.draftId)
			let qrCodeUrl = pickDraftQrCodeUrl(this.qrBatch)
				|| pickDraftQrCodeUrl(this.activeDraft)
				|| (this.confirmInfo && this.confirmInfo.qrCodeUrl)
				|| ''
			let qrToken = pickDraftQrToken(this.qrBatch)
				|| pickDraftQrToken(this.activeDraft)
				|| (this.confirmInfo && this.confirmInfo.qrToken)
				|| ''
			if (draftId != null) {
				const fromDetail = await this.refreshDraftQrFromDetail(draftId)
				if (fromDetail && fromDetail.qrCodeUrl) qrCodeUrl = fromDetail.qrCodeUrl
				if (fromDetail && fromDetail.qrToken) qrToken = fromDetail.qrToken
			}
			if (!this.qrBatch || !this.qrBatch.subtitle) {
				this.qrBatch = this.buildQrBatch(this.activeDraft, {
					schemeTitle: this.confirmInfo && this.confirmInfo.title,
					draftId,
					qrCodeUrl,
					qrToken
				})
			} else {
				this.$set(this.qrBatch, 'qrCodeUrl', qrCodeUrl)
				if (qrToken) {
					this.$set(this.qrBatch, 'qrToken', qrToken)
					this.$set(this.qrBatch, 'scene', qrToken)
				}
				if (draftId != null && this.qrBatch.draftId == null) {
					this.$set(this.qrBatch, 'draftId', draftId)
				}
			}
			this.syncQrCardMessage(this.qrBatch)
			this.modal = 'qr'
		},
		async openDraftQr(payload) {
			let draft = this.activeDraft
			const payloadDraftId = payload && payload.draftId
			if (payloadDraftId != null) {
				if (!draft || draft.draftId !== payloadDraftId) {
					if (this.activeDraft && this.activeDraft.draftId === payloadDraftId) {
						draft = this.activeDraft
					} else {
						for (let i = this.messages.length - 1; i >= 0; i--) {
							const msg = this.messages[i]
							if (msg && msg.type === 'draft' && msg.draftId === payloadDraftId && msg.draft) {
								draft = msg.draft
								break
							}
						}
					}
					if (draft) this.activeDraft = draft
				}
			}
			const draftId = (draft && draft.draftId) || payloadDraftId
			let qrCodeUrl = pickDraftQrCodeUrl(draft)
			let qrToken = pickDraftQrToken(draft)
			if (draftId != null) {
				const fromDetail = await this.refreshDraftQrFromDetail(draftId)
				if (fromDetail && fromDetail.qrCodeUrl) qrCodeUrl = fromDetail.qrCodeUrl
				if (fromDetail && fromDetail.qrToken) qrToken = fromDetail.qrToken
			}
			this.qrBatch = this.buildQrBatch(this.activeDraft || draft, {
				scheme: (this.activeDraft || draft) && (this.activeDraft || draft).confirmedPlan,
				schemeTitle: (this.activeDraft || draft) && (this.activeDraft || draft).confirmedPlanTitle,
				qrCodeUrl,
				qrToken
			})
			this.modal = 'qr'
		},
		/**
		 * 供首页 onShareAppMessage 读取
		 * path 参数名仍为 scene，值为 qrToken
		 */
		getShareAppMessage() {
			const draftId = (this.qrBatch && this.qrBatch.draftId)
				|| (this.activeDraft && this.activeDraft.draftId)
				|| ''
			const orderId = (this.qrBatch && this.qrBatch.orderId)
				|| (this.activeDraft && this.activeDraft.orderId)
				|| (this.confirmInfo && this.confirmInfo.orderId)
				|| ''
			const qrToken = (this.qrBatch && this.qrBatch.qrToken)
				|| (this.activeDraft && this.activeDraft.qrToken)
				|| (this.confirmInfo && this.confirmInfo.qrToken)
				|| ''
			const scene = qrToken
				|| (this.qrBatch && this.qrBatch.scene)
				|| (this.activeDraft && this.activeDraft.scene)
				|| orderId
				|| ''
			return buildJobOfferShareMessage({ draftId, orderId, scene, qrToken })
		},
		openQrProgress() {
			const draftId = (this.qrBatch && this.qrBatch.draftId)
				|| (this.activeDraft && this.activeDraft.draftId)
				|| (this.confirmInfo && this.confirmInfo.draftId)
			this.openProgressDetail(draftId)
		},
		onQrProgressLoaded(payload) {
			if (!payload || !this.qrBatch) return
			if (payload.registered != null) {
				this.$set(this.qrBatch, 'registered', payload.registered)
			}
			if (payload.total != null) {
				this.$set(this.qrBatch, 'total', payload.total)
			}
			this.syncQrCardMessage(this.qrBatch)
		},
		collectCostDraftMetas() {
			const metas = []
			const seen = {}
			const pushMeta = (draftId, title, workerCount) => {
				if (draftId == null || draftId === '') return
				const key = String(draftId)
				if (seen[key]) return
				seen[key] = true
				metas.push({
					draftId,
					title: title || `用工草稿 #${draftId}`,
					workerCount: workerCount != null ? Number(workerCount) : null
				})
			}
			for (let i = 0; i < this.messages.length; i++) {
				const msg = this.messages[i]
				if (!msg || msg.type !== 'draft' || msg.draftId == null) continue
				const d = msg.draft || {}
				pushMeta(msg.draftId, d.title, d.workerCount)
			}
			if (this.activeDraft && this.activeDraft.draftId != null) {
				pushMeta(
					this.activeDraft.draftId,
					this.activeDraft.title,
					this.activeDraft.workerCount
				)
			}
			return metas
		},
		openConversationCost() {
			if (!this.currentConversationId) {
				this.toast('请先开启新对话')
				return
			}
			this.costDraftMetas = this.collectCostDraftMetas()
			this.openSheet('cost')
		},
		async onConversationCostConfirm(payload) {
			const draftId = payload && payload.draftId
			if (draftId == null) {
				this.confirmPlan(payload)
				return
			}
			let draft = null
			if (this.activeDraft && this.activeDraft.draftId === draftId) {
				draft = this.activeDraft
			} else {
				for (let i = this.messages.length - 1; i >= 0; i--) {
					const msg = this.messages[i]
					if (msg && msg.type === 'draft' && msg.draftId === draftId && msg.draft) {
						draft = msg.draft
						break
					}
				}
			}
			if (!draft) {
				try {
					const detail = await getDraftDetail(draftId, { showError: false })
					if (detail) draft = mapDraftDetailToCard(detail)
				} catch (e) {
					// ignore
				}
			}
			if (draft) {
				this.activeDraft = draft
				this.onDraftUpdated(Object.assign({}, draft, {
					status: 'confirmed',
					selectedSchemeCode: (payload && payload.schemeCode) || draft.selectedSchemeCode,
					confirmedPlan: (payload && payload.scheme) || draft.confirmedPlan
				}), { sinkToBottom: false })
			}
			this.confirmPlan(payload)
		},
		openProgressDetail(draftId, title) {
			if (draftId == null || draftId === '') {
				showToast('缺少草稿信息，无法查看进度')
				return
			}
			this.progressDraftId = draftId
			this.progressSheetTitle = title || '人员登记'
			this.openSheet('progress')
		},
		/** 待办深链 / 消息跳转：打开指定订单结算确认弹层 */
		handleDeepLink(options = {}) {
			const open = options.open || ''
			const orderId = options.order_id || options.orderId || ''
			if (open === 'settlement' && orderId !== '' && orderId != null) {
				this.openSettlementByOrderId(orderId)
			}
		},
		openSettlementByOrderId(orderId, draftId, readonly) {
			if (orderId == null || orderId === '') {
				showToast('缺少订单信息')
				return
			}
			this.settlementOrderId = orderId
			this.settlementDraftId = draftId != null && draftId !== ''
				? draftId
				: (this.activeDraft && this.activeDraft.draftId) || null
			this.settlementReadonly = !!readonly
			this.openSheet('settlement')
		},
		onSettlementPaid(payload) {
			const settleDraftId = (payload && payload.draftId)
				|| this.settlementDraftId
				|| this.progressDraftId
				|| (this.activeDraft && this.activeDraft.draftId)
				|| null
			this.closeAll()
			if (settleDraftId != null && settleDraftId !== '') {
				this.refreshWorkflowCardByDraftId(settleDraftId)
			}
			setTimeout(() => { this.modal = 'paySuccess' }, 200)
		},
		showPayment() {
			this.closeAll()
			setTimeout(() => { this.modal = 'payment' }, 300)
		},
		showPaySuccess() {
			this.modal = 'paySuccess'
		},
		logout() {
			store.logout()
			uni.reLaunch({ url: '/packageA/auth/login' })
		}
	}
}
</script>

<style lang="scss" scoped>
.chat-page {
	height: 100%;
	overflow: hidden;
	background: $fe-bg;
}

.chat-area {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: calc(#{$fe-h-input-bar} + #{$fe-h-quick-bar} + #{$fe-h-safe-bottom});
	box-sizing: border-box;
}

.chat-footer {
	position: fixed;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 99;
	background: rgba(255, 255, 255, 0.92);
	border-top: 1rpx solid $fe-border-light;
	padding-bottom: $fe-h-safe-bottom;
	box-sizing: border-box;
}

.fe-input-bar--embedded {
	position: relative;
	left: auto;
	right: auto;
	bottom: auto;
	border-top: 1rpx solid $fe-border-light;
	background: transparent;
	padding-bottom: 20rpx;
}

.fe-input-bar--disabled {
	opacity: 0.55;
}

.fe-input-bar--disabled .fe-input-bar__field {
	background: $fe-gray-100;
	color: $fe-gray-400;
}

.fe-input-bar--disabled .fe-input-bar__hold {
	background: $fe-gray-100;
}

.fe-input-bar--disabled .fe-input-bar__hold-text {
	color: $fe-gray-400;
}

.fe-input-bar--disabled .fe-input-bar__btn {
	opacity: 0.7;
}

.chat-area__inner {
	padding: 32rpx;
	padding-bottom: 48rpx;
}

.chat-msg-wrap {
	width: 100%;
}

.chat-status {
	display: flex;
	justify-content: center;
	padding: 24rpx 0 8rpx;
}

.chat-status--top {
	padding: 8rpx 0 24rpx;
}

.chat-status__text {
	font-size: 24rpx;
	color: $fe-gray-500;
}

.chat-status__text--muted {
	color: $fe-gray-400;
}

.chat-bottom-anchor {
	height: 2rpx;
	width: 100%;
}

.sidebar-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 28rpx 28rpx 20rpx;
	border-bottom: 1rpx solid $fe-border-light;
}

.sidebar-head__title {
	font-size: 30rpx;
	font-weight: 700;
}

.sidebar-head__add {
	width: 64rpx;
	height: 64rpx;
	border-radius: $fe-radius-sm;
	background: $fe-primary;
	color: #fff;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 32rpx;
}

.sidebar-list {
	height: calc(100% - 120rpx);
}

.sidebar-status {
	padding: 48rpx 28rpx;
	text-align: center;
}

.sidebar-status__text {
	font-size: 26rpx;
	color: $fe-gray-500;
}

.sidebar-status--action {
	cursor: pointer;
}

.sidebar-status__text--action {
	color: $fe-primary;
	font-weight: 600;
}

.chat-empty {
	min-height: 100%;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 80rpx 48rpx;
	text-align: center;
	box-sizing: border-box;
}

.chat-empty__icon {
	width: 120rpx;
	height: 120rpx;
	border-radius: 50%;
	background: $fe-primary-bg;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 56rpx;
	margin-bottom: 32rpx;
}

.chat-empty__title {
	font-size: 36rpx;
	font-weight: 700;
	color: $fe-foreground;
	margin-bottom: 48rpx;
	line-height: 1.5;
	max-width: 520rpx;
}

.chat-empty__btn {
	padding: 24rpx 64rpx;
	border-radius: $fe-radius-full;
	background: $fe-primary;
	color: #fff;
	font-size: 30rpx;
	font-weight: 600;
}

.fe-biz-card--qr {
	border-left: 6rpx solid $fe-primary;
}

.fe-qr-card__subtitle {
	display: block;
	font-size: 24rpx;
	color: $fe-muted;
	margin-bottom: 20rpx;
	line-height: 1.4;
}

.fe-qr-card__img {
	display: block;
	width: 360rpx;
	height: 360rpx;
	margin: 0 auto 20rpx;
	background: #fff;
}

.fe-qr-card__empty {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 360rpx;
	height: 360rpx;
	margin: 0 auto 20rpx;
	background: $fe-primary-bg;
	border-radius: 16rpx;
}

.fe-qr-card__empty-text {
	font-size: 24rpx;
	color: $fe-muted;
	text-align: center;
	padding: 0 24rpx;
}

.fe-qr-card__hint {
	display: block;
	font-size: 24rpx;
	color: $fe-muted;
	text-align: center;
	margin-bottom: 20rpx;
	line-height: 1.4;
}
</style>
