<template>
    <div class="dh5">
        <!-- 顶部：搜索 + 日期/状态 chip 横滚 -->
        <header class="dh5-topbar">
            <div class="dh5-search">
                <el-input
                    v-model="formData.keyword"
                    placeholder="患者姓名 / 手机号"
                    clearable
                    :prefix-icon="Search"
                    @keyup.enter="doSearch"
                    @clear="doSearch"
                />
                <el-button class="dh5-search-btn" type="primary" @click="doSearch">查询</el-button>
            </div>

            <div class="dh5-chips dh5-chips--scroll">
                <span
                    v-for="tab in dateTabs"
                    :key="tab.value || 'all'"
                    :class="[
                        'dh5-chip',
                        {
                            active:
                                formData.pending_assign !== '1' &&
                                formData.pending_booking !== '1' &&
                                formData.completed_appointment !== '1' &&
                                formData.appointment_date === tab.value
                        }
                    ]"
                    @click="handleDateTabClick(tab.value)"
                >
                    {{ tab.label }}
                    <em v-if="dateCounts[tab.value] !== undefined">{{ dateCounts[tab.value] }}</em>
                </span>
                <span
                    :class="['dh5-chip', 'dh5-chip--booking', { active: formData.pending_booking === '1' }]"
                    @click="handlePendingBookingTabClick"
                >
                    待预约
                    <em v-if="pendingBookingCount !== undefined">{{ pendingBookingCount }}</em>
                </span>
                <span
                    :class="['dh5-chip', 'dh5-chip--done', { active: formData.completed_appointment === '1' }]"
                    @click="handleCompletedVisitTabClick"
                >
                    已完成
                    <em v-if="completedVisitCount !== undefined">{{ completedVisitCount }}</em>
                </span>
                <span
                    v-if="hasPermission(['tcm.diagnosis/assign'])"
                    :class="['dh5-chip', 'dh5-chip--pending', { active: formData.pending_assign === '1' }]"
                    @click="handlePendingAssignTabClick"
                >
                    待分配医助
                    <em v-if="pendingAssignCount !== undefined">{{ pendingAssignCount }}</em>
                </span>
            </div>

            <div class="dh5-quick">
                <div class="dh5-quick-group">
                    <span class="dh5-quick-label">挂号</span>
                    <span
                        v-for="opt in hasAptOptions"
                        :key="'a' + opt.value"
                        :class="['dh5-pill', { active: formData.has_appointment === opt.value }]"
                        @click="setHasAppointment(opt.value)"
                    >
                        {{ opt.label }}
                    </span>
                </div>
                <div class="dh5-quick-group">
                    <span class="dh5-quick-label">确认</span>
                    <span
                        v-for="opt in confirmedOptions"
                        :key="'c' + opt.value"
                        :class="['dh5-pill', { active: formData.diagnosis_confirmed === opt.value }]"
                        @click="setConfirmed(opt.value)"
                    >
                        {{ opt.label }}
                    </span>
                </div>
                <el-button
                    link
                    type="primary"
                    size="small"
                    class="dh5-more-toggle"
                    @click="showMoreFilter = !showMoreFilter"
                >
                    {{ showMoreFilter ? '收起' : '更多' }}
                    <el-icon :class="{ 'rotate-180': showMoreFilter }"><ArrowDown /></el-icon>
                </el-button>
            </div>

            <div v-show="showMoreFilter" class="dh5-more-filter">
                <el-select v-model="formData.diagnosis_type" placeholder="诊断类型" clearable size="small" @change="doSearch">
                    <el-option v-for="item in diagnosisTypeOptions" :key="item.value" :label="item.name" :value="item.value" />
                </el-select>
                <el-select v-model="formData.syndrome_type" placeholder="证型" clearable size="small" @change="doSearch">
                    <el-option v-for="item in syndromeTypeOptions" :key="item.value" :label="item.name" :value="item.value" />
                </el-select>
                <el-select v-model="formData.assistant_id" placeholder="医助" clearable filterable size="small" @change="doSearch">
                    <el-option v-for="item in assistantOptions" :key="item.id" :label="item.name" :value="Number(item.id)" />
                </el-select>
                <el-button size="small" @click="handleReset">重置</el-button>
            </div>
        </header>

        <!-- 卡片列表 -->
        <main class="dh5-list" v-loading="pager.loading">
            <div v-if="pager.lists.length === 0 && !pager.loading" class="dh5-empty">
                <el-empty description="暂无诊单" :image-size="120" />
            </div>

            <article
                v-for="row in pager.lists"
                :key="row.id"
                :class="['dh5-card', getCardClass(row)]"
            >
                <!-- 卡片头：姓名 / 性别年龄 / 主状态 -->
                <header class="dh5-card-head" @click="handleDetail(row.id)">
                    <div class="dh5-card-avatar">
                        {{ (row.patient_name || '患').charAt(0) }}
                    </div>
                    <div class="dh5-card-id-name">
                        <div class="dh5-card-name">
                            {{ row.patient_name || '-' }}
                            <span v-if="row.gender_desc" class="dh5-meta-gender">{{ row.gender_desc }}</span>
                            <span v-if="row.age != null" class="dh5-meta-age">{{ row.age }}岁</span>
                        </div>
                        <div class="dh5-card-sub">
                            #{{ row.id }} · {{ row.diagnosis_date_text || formatDateTime(row.create_time) }}
                        </div>
                    </div>
                    <div class="dh5-card-tags">
                        <span v-if="isDiagnosisConfirmed(row)" class="dh5-tag dh5-tag--ok">已确认</span>
                        <span v-else class="dh5-tag dh5-tag--warn">未确认</span>
                        <span v-if="row.has_prescription" class="dh5-tag dh5-tag--info">已开方</span>
                    </div>
                </header>

                <!-- 卡片体：挂号 / 复诊 / 助理 / 类型 -->
                <section class="dh5-card-body" @click="handleDetail(row.id)">
                    <!-- 挂号块 -->
                    <div :class="['dh5-row', 'dh5-row--apt', appointmentRowClass(row)]">
                        <span class="dh5-row-key">挂号</span>
                        <div class="dh5-row-val">
                            <template v-if="row.has_appointment">
                                <template v-if="appointmentRows(row).length">
                                    <div
                                        v-for="apt in appointmentRows(row)"
                                        :key="apt.id || `${apt.doctor_id}-${apt.time_text}`"
                                        :class="['dh5-apt-item', appointmentItemClass(apt)]"
                                    >
                                        <span class="dh5-apt-status">{{ appointmentStatusLabelByStatus(apt.status) }}</span>
                                        <span class="dh5-apt-doctor">{{ apt.doctor_name || '-' }}</span>
                                        <span class="dh5-apt-time">{{ apt.time_text || '-' }}</span>
                                    </div>
                                </template>
                                <template v-else>
                                    <span class="dh5-apt-status">{{ appointmentStatusLabel(row) }}</span>
                                    <span class="dh5-apt-doctor">{{ row.appointment_doctor_name || '-' }}</span>
                                    <span class="dh5-apt-time">{{ row.appointment_time_text || '-' }}</span>
                                </template>
                            </template>
                            <span v-else class="dh5-apt-none">未挂号</span>
                        </div>
                    </div>

                    <!-- 复诊块（仅已开方时显示） -->
                    <div v-if="row.has_prescription" class="dh5-row dh5-row--followup">
                        <span class="dh5-row-key">复诊</span>
                        <div class="dh5-row-val">
                            <span>{{ row.followup_time_text || '—' }}</span>
                            <span class="dh5-meta-doctor">{{ row.followup_doctor_name || '—' }}</span>
                            <span v-if="row.followup_rx_voided" class="dh5-tag dh5-tag--gray">处方作废</span>
                        </div>
                    </div>

                    <!-- 助理 / 类型 -->
                    <div class="dh5-row">
                        <span class="dh5-row-key">助理</span>
                        <span class="dh5-row-val">{{ getAssistantName(row.assistant_id || row.assistant) }}</span>
                    </div>
                    <div v-if="row.diagnosis_type" class="dh5-row">
                        <span class="dh5-row-key">诊断</span>
                        <span class="dh5-row-val">
                            {{ getDictLabel(diagnosisTypeOptions, row.diagnosis_type) }}
                            <span v-if="row.syndrome_type" class="dh5-meta-mute">·</span>
                            <span v-if="row.syndrome_type">{{ getDictLabel(syndromeTypeOptions, row.syndrome_type) }}</span>
                        </span>
                    </div>
                </section>

                <!-- 视频旁观（受限角色 / 状态时只读说明） -->
                <div v-if="watchCallVisibleForRow(row)" class="dh5-card-watch">
                    <template v-if="canWatchCallEntry(row) && watchCallShowEnterButton(row)">
                        <el-button type="primary" size="small" :icon="VideoCamera" @click="onWatchCallEntryClick(row)">
                            进入旁观
                        </el-button>
                        <span class="dh5-watch-tip">{{ watchCallEnterTooltip(row) }}</span>
                    </template>
                    <template v-else>
                        <span class="dh5-watch-mute">{{ canWatchCallEntry(row) ? watchCallAssistantIdleText(row) : watchCallPublicStatus(row) }}</span>
                    </template>
                </div>

                <!-- 卡片操作：3 个常用 + 更多 -->
                <footer class="dh5-card-foot">
                    <el-button
                        v-if="hasPermission(['tcm.diagnosis/edit'])"
                        type="primary"
                        size="small"
                        plain
                        @click="handleEdit(row.id)"
                    >
                        诊单
                    </el-button>
                    <el-button
                        v-if="hasPermission(['tcm.diagnosis/kaifang'])"
                        :type="prescriptionActionLabel(row) === '查看' ? 'success' : 'primary'"
                        size="small"
                        @click="handlePrescription(row)"
                    >
                        {{ prescriptionActionLabel(row) }}
                    </el-button>
                    <el-button
                        v-if="hasPermission(['tcm.diagnosis/guahao'])"
                        type="success"
                        size="small"
                        plain
                        @click="handleAppointment(row)"
                    >
                        预约
                    </el-button>
                    <el-button
                        size="small"
                        class="dh5-more-btn"
                        @click="openMoreSheet(row)"
                    >
                        更多<el-icon class="el-icon--right"><ArrowDown /></el-icon>
                    </el-button>
                </footer>
            </article>

            <div v-if="hasMore" class="dh5-load-more" @click="loadMore">
                {{ pager.loading ? '加载中…' : '加载更多' }}
            </div>
            <div v-if="!hasMore && pager.lists.length > 0" class="dh5-no-more">— 没有更多了 —</div>
        </main>

        <!-- 悬浮新增 -->
        <button
            v-if="hasPermission(['tcm.diagnosis/add'])"
            class="dh5-fab"
            type="button"
            aria-label="新增诊单"
            @click="handleAdd"
        >
            <el-icon :size="22"><Plus /></el-icon>
        </button>

        <!-- 复用业务弹窗 -->
        <edit-popup ref="editRef" @success="refreshListAfterPopupSave" />
        <detail-popup ref="detailRef" />
        <tcm-prescription ref="prescriptionRef" @success="onPrescriptionSuccess" />
        <appointment-popup ref="appointmentRef" @success="refreshListAfterPopupSave" />
        <assistant-watch-call-dialog
            v-model="watchCallDialogVisible"
            :diagnosis-id="watchCallDiagnosisId"
            @closed="getLists"
        />

        <!-- 行操作 Action Sheet -->
        <el-drawer
            v-model="moreSheetVisible"
            :title="moreSheetRow ? `${moreSheetRow.patient_name || '诊单'} · 更多操作` : '更多操作'"
            direction="btt"
            size="auto"
            :with-header="true"
            :destroy-on-close="true"
        >
            <div class="dh5-actions" v-if="moreSheetRow">
                <button
                    v-if="hasPermission(['tcm.diagnosis/assign'])"
                    class="dh5-action"
                    @click="runAction('assign')"
                >
                    <el-icon><User /></el-icon>
                    <span>指派医助</span>
                </button>
                <button
                    v-if="isAppointmentActiveForVideo(moreSheetRow) && hasPermission(['tcm.diagnosis/videoQr'])"
                    class="dh5-action"
                    @click="runAction('videoQr')"
                >
                    <el-icon><Picture /></el-icon>
                    <span>视频二维码</span>
                </button>
                <button
                    v-if="isAppointmentActiveForVideo(moreSheetRow) && hasPermission(['tcm.diagnosis/guahao'])"
                    class="dh5-action"
                    @click="runAction('confirmQr')"
                >
                    <el-icon><Picture /></el-icon>
                    <span>诊单二维码</span>
                </button>
                <button
                    v-if="canCancelAppointmentRow(moreSheetRow) && hasPermission(['tcm.diagnosis/guahao'])"
                    class="dh5-action"
                    @click="runAction('cancelApt')"
                >
                    <el-icon><CircleClose /></el-icon>
                    <span>取消挂号</span>
                </button>
                <button
                    v-if="hasPermission(['tcm.diagnosis/edit'])"
                    class="dh5-action"
                    @click="runAction('fillIdCard')"
                >
                    <el-icon><Postcard /></el-icon>
                    <span>补全身份证</span>
                </button>
                <button
                    v-if="hasPermission(['tcm.diagnosis/order'])"
                    class="dh5-action"
                    @click="runAction('order')"
                >
                    <el-icon><Document /></el-icon>
                    <span>创建订单</span>
                </button>
                <button class="dh5-action" @click="runAction('wechat')">
                    <el-icon><ChatDotRound /></el-icon>
                    <span>企微记录</span>
                </button>
                <button
                    v-if="hasPermission(['tcm.diagnosis/delete'])"
                    class="dh5-action dh5-action--danger"
                    @click="runAction('delete')"
                >
                    <el-icon><Delete /></el-icon>
                    <span>删除诊单</span>
                </button>
            </div>
        </el-drawer>

        <!-- 指派医助 Drawer -->
        <el-drawer v-model="assignDialogVisible" title="指派医助" direction="btt" size="46%">
            <el-form :model="assignForm" label-position="top" class="dh5-form">
                <el-form-item label="选择医助" required>
                    <el-select
                        v-model="assignForm.assistant_id"
                        placeholder="请选择医助"
                        class="!w-full"
                        filterable
                        clearable
                    >
                        <el-option
                            v-for="item in assistantOptions"
                            :key="item.id"
                            :label="item.name"
                            :value="Number(item.id)"
                        />
                    </el-select>
                </el-form-item>
                <el-form-item label="诊单信息">
                    <div class="dh5-form-tip">
                        <div>患者：{{ currentRow?.patient_name }}</div>
                        <div>诊断日期：{{ currentRow?.diagnosis_date_text || '—' }}</div>
                    </div>
                </el-form-item>
            </el-form>
            <template #footer>
                <el-button class="dh5-flex1" @click="assignDialogVisible = false">取消</el-button>
                <el-button class="dh5-flex1" type="primary" :loading="assignLoading" @click="handleConfirmAssign">
                    确定指派
                </el-button>
            </template>
        </el-drawer>

        <!-- 创建订单 Drawer -->
        <el-drawer v-model="createOrderVisible" title="创建订单" direction="btt" size="64%" @close="resetOrderForm">
            <el-form
                ref="orderFormRef"
                :model="orderForm"
                :rules="orderRules"
                label-position="top"
                class="dh5-form"
            >
                <el-form-item label="患者">
                    <span>{{ orderForm.patient_name }} ({{ maskPhone(orderForm.phone) }})</span>
                </el-form-item>
                <el-form-item label="订单类型" prop="order_type">
                    <el-select v-model="orderForm.order_type" placeholder="请选择" class="!w-full">
                        <el-option label="挂号费" :value="1" />
                        <el-option label="问诊费" :value="2" />
                        <el-option label="药品费用" :value="3" />
                        <el-option label="首付费用" :value="4" />
                        <el-option label="尾款费用" :value="5" />
                        <el-option label="其他费用" :value="6" />
                        <el-option label="全部费用" :value="7" />
                        <el-option label="驼奶费用" :value="8" />
                    </el-select>
                </el-form-item>
                <el-form-item label="订单金额" prop="amount">
                    <el-input-number
                        v-model="orderForm.amount"
                        :min="0"
                        :step="0.01"
                        :precision="2"
                        placeholder="请输入金额"
                        class="!w-full"
                    />
                </el-form-item>
                <el-form-item label="备注" prop="remark">
                    <el-input
                        v-model="orderForm.remark"
                        type="textarea"
                        :rows="3"
                        placeholder="请输入备注（可选）"
                    />
                </el-form-item>
            </el-form>
            <template #footer>
                <el-button class="dh5-flex1" @click="createOrderVisible = false">取消</el-button>
                <el-button class="dh5-flex1" type="primary" :loading="orderLoading" @click="submitOrder">
                    创建订单
                </el-button>
            </template>
        </el-drawer>

        <!-- 补全身份证 Dialog -->
        <el-dialog
            v-model="fillIdCardVisible"
            title="补全身份证号"
            :close-on-click-modal="false"
            @close="fillIdCardForm.id_card = ''"
        >
            <el-form
                ref="fillIdCardFormRef"
                :model="fillIdCardForm"
                :rules="fillIdCardRules"
                label-position="top"
                class="dh5-form"
            >
                <el-form-item label="患者">
                    <span class="font-medium">{{ fillIdCardForm.patient_name }}</span>
                </el-form-item>
                <el-form-item label="身份证号" prop="id_card">
                    <el-input
                        v-model="fillIdCardForm.id_card"
                        placeholder="请输入15或18位身份证号"
                        maxlength="18"
                        show-word-limit
                        clearable
                    />
                </el-form-item>
            </el-form>
            <template #footer>
                <el-button @click="fillIdCardVisible = false">取消</el-button>
                <el-button type="primary" :loading="fillIdCardLoading" @click="submitFillIdCard">
                    提交
                </el-button>
            </template>
        </el-dialog>

        <!-- 二维码 Dialog -->
        <el-dialog
            v-model="qrcodeDialogVisible"
            :title="qrcodeDialogTitle"
            :close-on-click-modal="false"
        >
            <div class="dh5-qr">
                <div v-if="qrcodeLoading" class="dh5-qr-loading">
                    <el-icon class="is-loading" :size="36"><Loading /></el-icon>
                    <div class="dh5-qr-tip">生成中...</div>
                </div>
                <div v-else-if="qrcodeUrl">
                    <img :src="qrcodeUrl" alt="小程序二维码" class="dh5-qr-img" />
                    <div class="dh5-qr-meta">
                        <div>患者：{{ currentQRCodePatient?.patient_name }}</div>
                        <div>挂号时间：{{ qrcodeAppointmentTimeText }}</div>
                        <div class="dh5-qr-hint">请使用企业微信扫描二维码，然后转发给患者</div>
                    </div>
                </div>
                <div v-else class="dh5-qr-fail">生成失败，请重试</div>
            </div>
            <template #footer>
                <el-button @click="qrcodeDialogVisible = false">关闭</el-button>
                <el-button v-if="!qrcodeLoading" type="primary" @click="handleRegenerateQRCode">重新生成</el-button>
            </template>
        </el-dialog>

        <!-- 企微记录 Drawer -->
        <el-drawer
            v-model="wechatDrawerVisible"
            :title="`企微记录 - ${wechatCurrentPatient?.patient_name || ''}`"
            direction="rtl"
            size="100%"
            :destroy-on-close="true"
        >
            <div v-if="wechatContactInfo" class="dh5-wx-contact">
                <div class="dh5-wx-contact-row">
                    <span class="dh5-wx-key">手机号</span>
                    <span class="dh5-wx-val">{{ maskPhone(wechatContactInfo.phone) || '-' }}</span>
                </div>
                <div class="dh5-wx-contact-row">
                    <span class="dh5-wx-key">企微 ID</span>
                    <span class="dh5-wx-val">
                        <span v-if="wechatContactInfo.external_userid">{{ wechatContactInfo.external_userid }}</span>
                        <el-tag v-else type="info" size="small">未关联</el-tag>
                    </span>
                </div>
                <div class="dh5-wx-contact-row">
                    <span class="dh5-wx-key">会话存档</span>
                    <span class="dh5-wx-val">
                        <el-tag v-if="wechatContactInfo.msgaudit_enabled" type="success" size="small">已开通</el-tag>
                        <el-tag v-else type="info" size="small">未配置</el-tag>
                    </span>
                </div>
            </div>

            <div class="dh5-wx-add">
                <el-input
                    v-model="wechatNewNote"
                    type="textarea"
                    :rows="2"
                    placeholder="添加沟通记录..."
                />
                <el-button
                    type="primary"
                    :loading="wechatAddLoading"
                    size="small"
                    @click="handleAddRecord"
                >
                    添加
                </el-button>
            </div>

            <div v-loading="wechatRecordsLoading" class="dh5-wx-list-wrap">
                <div v-if="wechatRecords.length === 0 && !wechatRecordsLoading" class="dh5-wx-empty">
                    暂无聊天记录
                </div>
                <div v-else class="dh5-wx-list">
                    <div v-for="record in wechatRecords" :key="record.id" class="dh5-wx-item">
                        <div class="dh5-wx-head">
                            <div class="dh5-wx-head-left">
                                <el-tag :type="record.direction === 0 ? 'primary' : 'success'" size="small">
                                    {{ record.direction === 0 ? '员工' : '客户' }}
                                </el-tag>
                                <span class="dh5-wx-name">
                                    {{ record.direction === 0 ? record.staff_name : record.external_name || wechatCurrentPatient?.patient_name }}
                                </span>
                                <el-tag v-if="record.msg_type === 'note'" type="warning" size="small">备注</el-tag>
                            </div>
                            <div class="dh5-wx-head-right">
                                <span class="dh5-wx-time">{{ formatTimestamp(record.chat_time) }}</span>
                                <el-button type="danger" link size="small" @click="handleDeleteRecord(record.id)">
                                    删除
                                </el-button>
                            </div>
                        </div>
                        <div class="dh5-wx-content">{{ record.content }}</div>
                        <div v-if="record.media_url" class="dh5-wx-media">
                            <el-image
                                :src="record.media_url"
                                style="max-width: 200px"
                                fit="contain"
                                :preview-src-list="[record.media_url]"
                            />
                        </div>
                    </div>
                </div>
                <div v-if="wechatRecordTotal > wechatRecords.length" class="dh5-load-more" @click="loadMoreRecords">
                    加载更多
                </div>
            </div>
        </el-drawer>
    </div>
</template>

<script setup lang="ts" name="tcmDiagnosisH5">
import {
    tcmDiagnosisLists,
    tcmDiagnosisDelete,
    tcmDiagnosisAssign,
    getAssistants,
    generateMiniProgramQrcode,
    generateOrderQrcode,
    getWechatChatRecords,
    addWechatChatRecord,
    deleteWechatChatRecord,
    getWechatExternalContact,
    fillIdCard
} from '@/api/tcm'
import { orderCreate } from '@/api/order'
import { cancelAppointment } from '@/api/doctor'
import { getDictData } from '@/api/app'
import { getWeappConfig } from '@/api/channel/weapp'
import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback'
import { hasPermission } from '@/utils/perm'
import {
    Loading,
    Plus,
    Search,
    ArrowDown,
    Picture,
    User,
    Document,
    Delete,
    CircleClose,
    Postcard,
    ChatDotRound,
    VideoCamera
} from '@element-plus/icons-vue'
import useUserStore from '@/stores/modules/user'
import { defineAsyncComponent, computed, reactive, ref, onMounted, onUnmounted } from 'vue'
import dayjs from 'dayjs'

const EditPopup = defineAsyncComponent(() => import('./edit.vue'))
const DetailPopup = defineAsyncComponent(() => import('./detail.vue'))
const TcmPrescription = defineAsyncComponent(() => import('@/components/tcm-prescription/index.vue'))
const AppointmentPopup = defineAsyncComponent(() => import('./appointment.vue'))
const AssistantWatchCallDialog = defineAsyncComponent(() => import('./components/AssistantWatchCallDialog.vue'))

const userStore = useUserStore()

// ========== 工具 ==========
const formatDateTime = (timestamp: number | string) => {
    if (!timestamp) return '-'
    const ts = String(timestamp).length === 10 ? Number(timestamp) * 1000 : Number(timestamp)
    return dayjs(ts).format('YYYY-MM-DD HH:mm')
}

const formatTimestamp = (ts: number) => {
    if (!ts) return '-'
    const t = String(ts).length === 10 ? ts * 1000 : ts
    return dayjs(t).format('YYYY-MM-DD HH:mm')
}

const maskPhone = (phone: string) => {
    if (!phone || phone.length < 11) return phone
    return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
}

const pad = (n: number) => String(n).padStart(2, '0')
const yesterdayStr = computed(() => {
    const t = new Date(); t.setDate(t.getDate() - 1)
    return `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}`
})
const dayBeforeStr = computed(() => {
    const t = new Date(); t.setDate(t.getDate() - 2)
    return `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}`
})
const todayStr = computed(() => {
    const t = new Date()
    return `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}`
})
const tomorrowStr = computed(() => {
    const t = new Date(); t.setDate(t.getDate() + 1)
    return `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}`
})
const dayAfterStr = computed(() => {
    const t = new Date(); t.setDate(t.getDate() + 2)
    return `${t.getFullYear()}-${pad(t.getMonth() + 1)}-${pad(t.getDate())}`
})

// ========== 表单 / 列表 ==========
const formData = reactive({
    keyword: '',
    diagnosis_type: '',
    syndrome_type: '',
    assistant_id: '',
    diagnosis_confirmed: '' as '' | '0' | '1',
    appointment_date: '' as string,
    has_appointment: '' as '' | '0' | '1',
    pending_assign: '' as '' | '1',
    pending_booking: '' as '' | '1',
    completed_appointment: '' as '' | '1'
})

const showMoreFilter = ref(false)
const hasAptOptions = [
    { label: '全部', value: '' },
    { label: '已挂号', value: '1' },
    { label: '未挂号', value: '0' }
]
const confirmedOptions = [
    { label: '全部', value: '' },
    { label: '已确认', value: '1' },
    { label: '未确认', value: '0' }
]

const dateTabs = computed(() => [
    { label: '昨天挂号', value: yesterdayStr.value },
    { label: '前天挂号', value: dayBeforeStr.value },
    { label: '当天挂号', value: todayStr.value },
    { label: '明天挂号', value: tomorrowStr.value },
    { label: '后天挂号', value: dayAfterStr.value },
    { label: '全部', value: '' }
])

const dateCounts = ref<Record<string, number>>({})
const pendingBookingCount = ref<number | undefined>(undefined)
const completedVisitCount = ref<number | undefined>(undefined)
const pendingAssignCount = ref<number | undefined>(undefined)

const { pager, getLists } = usePaging({
    fetchFun: tcmDiagnosisLists,
    params: formData
})

const hasMore = computed(() => pager.lists.length < (pager.count || 0))

const refreshListAfterPopupSave = () => getLists({ silent: true })

const doSearch = () => {
    pager.page = 1
    getLists()
    fetchDateCounts()
}

const handleReset = () => {
    formData.keyword = ''
    formData.diagnosis_type = ''
    formData.syndrome_type = ''
    formData.assistant_id = ''
    formData.diagnosis_confirmed = ''
    formData.appointment_date = todayStr.value
    formData.has_appointment = ''
    formData.pending_assign = ''
    formData.pending_booking = ''
    formData.completed_appointment = ''
    pager.page = 1
    getLists()
    fetchDateCounts()
}

const setHasAppointment = (v: string) => {
    formData.has_appointment = v as '' | '0' | '1'
    formData.pending_booking = ''
    formData.completed_appointment = ''
    pager.page = 1
    getLists()
    fetchDateCounts()
}

const setConfirmed = (v: string) => {
    formData.diagnosis_confirmed = v as '' | '0' | '1'
    formData.pending_booking = ''
    formData.completed_appointment = ''
    pager.page = 1
    getLists()
    fetchDateCounts()
}

const handleDateTabClick = async (value: string) => {
    formData.pending_assign = ''
    formData.pending_booking = ''
    formData.completed_appointment = ''
    formData.has_appointment = ''
    formData.appointment_date = value
    pager.page = 1
    await getLists()
    fetchDateCounts()
}

const handlePendingBookingTabClick = async () => {
    formData.pending_assign = ''
    formData.pending_booking = '1'
    formData.completed_appointment = ''
    formData.appointment_date = ''
    formData.has_appointment = '0'
    pager.page = 1
    await getLists()
    fetchDateCounts()
}

const handleCompletedVisitTabClick = async () => {
    formData.pending_assign = ''
    formData.pending_booking = ''
    formData.completed_appointment = '1'
    formData.has_appointment = ''
    formData.appointment_date = ''
    pager.page = 1
    await getLists()
    fetchDateCounts()
}

const handlePendingAssignTabClick = async () => {
    formData.pending_assign = '1'
    formData.pending_booking = ''
    formData.completed_appointment = ''
    formData.has_appointment = ''
    formData.appointment_date = ''
    pager.page = 1
    await getLists()
    fetchDateCounts()
}

const fetchDateCounts = async () => {
    try {
        const [yesterday, dayBefore, today, tomorrow, dayAfter, all, noApt, doneVisit, pending] = await Promise.all([
            tcmDiagnosisLists({ appointment_date: yesterdayStr.value, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ appointment_date: dayBeforeStr.value, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ appointment_date: todayStr.value, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ appointment_date: tomorrowStr.value, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ appointment_date: dayAfterStr.value, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }),
            tcmDiagnosisLists({ pending_assign: 1, page_no: 1, page_size: 1 })
        ])
        dateCounts.value = {
            [yesterdayStr.value]: yesterday?.count ?? 0,
            [dayBeforeStr.value]: dayBefore?.count ?? 0,
            [todayStr.value]: today?.count ?? 0,
            [tomorrowStr.value]: tomorrow?.count ?? 0,
            [dayAfterStr.value]: dayAfter?.count ?? 0,
            '': all?.count ?? 0
        }
        pendingBookingCount.value = noApt?.count ?? 0
        completedVisitCount.value = doneVisit?.count ?? 0
        pendingAssignCount.value = pending?.count ?? 0
    } catch (e) {
        console.error('获取日期数量失败', e)
    }
}

const loadMore = async () => {
    if (pager.loading || !hasMore.value) return
    const next = (pager.page || 1) + 1
    pager.loading = true
    try {
        const res = await tcmDiagnosisLists({
            page_no: next,
            page_size: pager.size,
            ...formData
        })
        pager.page = next
        pager.lists.push(...(res?.lists || []))
        pager.count = res?.count ?? pager.count
    } catch (e) {
        console.error('加载更多失败:', e)
    } finally {
        pager.loading = false
    }
}

// ========== 字典 ==========
const diagnosisTypeOptions = ref<any[]>([])
const syndromeTypeOptions = ref<any[]>([])
const assistantOptions = ref<any[]>([])

const getDictOptions = async () => {
    try {
        const [diagnosisType, syndromeType, assistants] = await Promise.all([
            getDictData({ type: 'diagnosis_type' }),
            getDictData({ type: 'syndrome_type' }),
            getAssistants()
        ])
        diagnosisTypeOptions.value = diagnosisType?.diagnosis_type || []
        syndromeTypeOptions.value = syndromeType?.syndrome_type || []
        assistantOptions.value = assistants || []
    } catch (e) {
        console.error('获取字典数据失败:', e)
    }
}

const getDictLabel = (options: any[], value: string) => {
    const item = options.find(opt => opt.value === value)
    return item ? item.name : value || '-'
}

const getAssistantName = (assistantId: number | string | null | undefined) => {
    if (!assistantId && assistantId !== 0) return '-'
    const id = typeof assistantId === 'string' ? parseInt(assistantId) : (assistantId as number)
    if (assistantOptions.value.length === 0) return '...'
    const a = assistantOptions.value.find(item => {
        const itemId = typeof item.id === 'string' ? parseInt(item.id) : item.id
        return itemId === id
    })
    return a ? a.name : '-'
}

// ========== 列卡片状态 ==========
const isDiagnosisConfirmed = (row: any) => {
    const records = row.DiagnosisViewRecord || []
    return records.length > 0 && records.some((r: any) => r.is_confirmed == 1)
}

const getCardClass = (row: any) => {
    if (!isDiagnosisConfirmed(row)) return 'dh5-card--unconfirmed'
    if (!row.has_appointment) return 'dh5-card--noapt'
    return ''
}

const appointmentStatusLabel = (row: any) => {
    const s = Number(row.appointment_status)
    if (s === 3) return '已完成'
    if (s === 4) return '已过号'
    return '已挂号'
}

const appointmentStatusLabelByStatus = (status: number | string) => {
    const s = Number(status)
    if (s === 3) return '已完成'
    if (s === 4) return '已过号'
    return '已挂号'
}

const appointmentRows = (row: any) => {
    const list = Array.isArray(row.appointments) ? row.appointments : []
    if (list.length > 0) return list
    if (!row.has_appointment) return []
    return [{
        id: row.appointment_id,
        status: row.appointment_status,
        doctor_id: row.appointment_doctor_id,
        doctor_name: row.appointment_doctor_name,
        time_text: row.appointment_time_text
    }]
}

const appointmentItemClass = (apt: any) => {
    const s = Number(apt?.status)
    return {
        'dh5-apt-item--done': s === 3,
        'dh5-apt-item--missed': s === 4,
        'dh5-apt-item--active': s === 1
    }
}

const appointmentRowClass = (row: any) => {
    const s = Number(row.appointment_status)
    return {
        'has-apt': row.has_appointment,
        'apt-row-done': row.has_appointment && s === 3,
        'apt-row-missed': row.has_appointment && s === 4
    }
}

const isAppointmentActiveForVideo = (row: any) =>
    row.has_appointment && Number(row.appointment_status) === 1

const canCancelAppointmentRow = (row: any) => {
    const s = Number(row.appointment_status)
    return !!(row.has_appointment && row.appointment_id && (s === 1 || s === 4))
}

// 视频旁观（与 PC 端一致）
type VideoCallHint = { state: string; label: string; start_time?: number; end_time?: number }
const isAssignedAssistant = (row: any) => {
    const aid = row.assistant_id ?? row.assistant
    if (aid === null || aid === undefined || aid === '') return false
    return Number(aid) === Number(userStore.userInfo?.id)
}
const canWatchCallEntry = (row: any) =>
    isAssignedAssistant(row) && hasPermission(['tcm.diagnosis/watchCall'])
const watchCallState = (row: any) => (row.video_call_hint?.state as string) || 'none'
const watchCallShowEnterButton = (row: any) => {
    const st = watchCallState(row)
    return st === 'live' || st === 'pending_room'
}
const watchCallEnterTooltip = (row: any) => {
    if (watchCallState(row) === 'pending_room') {
        return '医生未接通或未同步房间'
    }
    return '点击进入实时房间'
}
const watchCallAssistantIdleText = (row: any) => {
    const h = row.video_call_hint as VideoCallHint | undefined
    if (h?.label) return h.label
    return '暂无可旁观通话'
}
const watchCallPublicStatus = (row: any) => {
    const st = watchCallState(row)
    if (st === 'none') return ''
    if (st === 'live') return '通话中'
    if (st === 'pending_room') return '接通中'
    const h = row.video_call_hint as VideoCallHint | undefined
    return h?.label || ''
}
/** 是否需要在卡片显示视频旁观条 */
const watchCallVisibleForRow = (row: any) => {
    if (canWatchCallEntry(row)) return true
    const st = watchCallState(row)
    return st === 'live' || st === 'pending_room'
}
const watchCallDialogVisible = ref(false)
const watchCallDiagnosisId = ref(0)
const onWatchCallEntryClick = (row: any) => {
    if (watchCallState(row) !== 'live') {
        feedback.msgWarning('医生未接通或未同步房间号，请稍后再试')
        return
    }
    watchCallDiagnosisId.value = Number(row.id) || 0
    watchCallDialogVisible.value = true
}

// ========== 主操作 ==========
const editRef = ref()
const detailRef = ref()
const prescriptionRef = ref()
const appointmentRef = ref()

const handleAdd = () => editRef.value?.open('add')
const handleEdit = (id: number) => editRef.value?.open('edit', id)
const handleDetail = (id: number) => detailRef.value?.open(id)

const prescriptionActionLabel = (row: any) => {
    if (
        row?.prescription_audit_status === 1 &&
        Number(row?.prescription_void_status) !== 1
    ) {
        return '查看'
    }
    return '开方'
}

const handlePrescription = (row: any) => {
    if (!row?.id) {
        feedback.msgWarning('诊单信息不完整')
        return
    }
    prescriptionRef.value?.open({
        ...row,
        diagnosis_id: row.id,
        id: row.appointment_id || 0
    })
}

const onPrescriptionSuccess = () => {
    getLists()
    ;(editRef.value as { refreshCaseList?: () => void } | undefined)?.refreshCaseList?.()
}

const handleAppointment = (row: any) => {
    if (!row.patient_id) { feedback.msgWarning('患者信息不完整'); return }
    appointmentRef.value?.open(row)
}

const handleDelete = async (id: number) => {
    await feedback.confirm('确定要删除该诊单吗？')
    await tcmDiagnosisDelete({ id })
    getLists()
}

// ========== Action Sheet ==========
const moreSheetVisible = ref(false)
const moreSheetRow = ref<any>(null)
const openMoreSheet = (row: any) => {
    moreSheetRow.value = row
    moreSheetVisible.value = true
}
const runAction = (cmd: string) => {
    const row = moreSheetRow.value
    moreSheetVisible.value = false
    if (!row) return
    switch (cmd) {
        case 'assign': handleSingleAssign(row); break
        case 'videoQr':
            if (!isAppointmentActiveForVideo(row)) {
                feedback.msgWarning('仅「已预约」状态可生成视频二维码')
                return
            }
            handleVideoQRCode(row); break
        case 'confirmQr': handleMiniProgramQRCode(row); break
        case 'cancelApt': handleCancelAppointment(row); break
        case 'fillIdCard': handleFillIdCard(row); break
        case 'order': handleCreateOrder(row); break
        case 'wechat': handleWechatRecords(row); break
        case 'delete': handleDelete(row.id); break
    }
}

// ========== 指派 ==========
const assignDialogVisible = ref(false)
const assignLoading = ref(false)
const currentRow = ref<any>(null)
const assignForm = ref({ assistant_id: null as number | null })
const handleSingleAssign = (row: any) => {
    currentRow.value = row
    assignForm.value.assistant_id = row.assistant_id ? Number(row.assistant_id) : null
    assignDialogVisible.value = true
}
const handleConfirmAssign = async () => {
    if (!assignForm.value.assistant_id) { feedback.msgWarning('请选择医助'); return }
    assignLoading.value = true
    try {
        await tcmDiagnosisAssign({
            id: currentRow.value.id,
            assistant_id: assignForm.value.assistant_id
        })
        assignDialogVisible.value = false
        getLists()
    } catch (e) {
        console.error('指派失败:', e)
    } finally {
        assignLoading.value = false
    }
}

// ========== 创建订单 ==========
const createOrderVisible = ref(false)
const orderFormRef = ref()
const orderLoading = ref(false)
const orderForm = reactive({
    patient_id: '' as string | number,
    patient_name: '',
    phone: '',
    order_type: '' as string | number,
    amount: 0,
    remark: '',
    has_appointment: false,
    appointment_time_text: ''
})
const orderRules = {
    order_type: [{ required: true, message: '请选择订单类型', trigger: 'change' }],
    amount: [{ required: true, message: '请输入订单金额', trigger: 'blur' }]
}
const handleCreateOrder = (row: any) => {
    orderForm.patient_id = row.patient_id || row.id
    orderForm.patient_name = row.patient_name
    orderForm.phone = row.phone || ''
    orderForm.order_type = ''
    orderForm.amount = 0
    orderForm.remark = ''
    orderForm.has_appointment = !!row.has_appointment
    orderForm.appointment_time_text = row.appointment_time_text || ''
    orderFormRef.value?.clearValidate()
    createOrderVisible.value = true
}
const resetOrderForm = () => {
    orderForm.patient_id = ''
    orderForm.patient_name = ''
    orderForm.phone = ''
    orderForm.order_type = ''
    orderForm.amount = 0
    orderForm.remark = ''
    orderForm.has_appointment = false
    orderForm.appointment_time_text = ''
    orderFormRef.value?.clearValidate()
}
const submitOrder = async () => {
    try {
        await orderFormRef.value?.validate()
        orderLoading.value = true
        const res = await orderCreate({
            patient_id: orderForm.patient_id,
            order_type: orderForm.order_type,
            amount: orderForm.amount,
            remark: orderForm.remark
        })
        createOrderVisible.value = false
        getLists()

        const orderNo = res?.order_no
        if (orderNo) {
            qrcodeDialogVisible.value = true
            qrcodeLoading.value = true
            qrcodeUrl.value = ''
            qrcodeDialogTitle.value = '收款二维码'
            currentQRCodePatient.value = {
                patient_name: orderForm.patient_name,
                has_appointment: orderForm.has_appointment,
                appointment_time_text: orderForm.appointment_time_text
            }
            try {
                const qrRes = await generateOrderQrcode({ order_no: orderNo })
                if (qrRes?.qrcode_url) qrcodeUrl.value = qrRes.qrcode_url
                else feedback.msgError('二维码生成失败')
            } catch (e: any) {
                feedback.msgError(e?.msg || '生成二维码失败')
            } finally {
                qrcodeLoading.value = false
            }
        }
    } catch (e) {
        console.error('创建订单失败:', e)
    } finally {
        orderLoading.value = false
    }
}

// ========== 补全身份证 ==========
const fillIdCardVisible = ref(false)
const fillIdCardLoading = ref(false)
const fillIdCardFormRef = ref()
const fillIdCardForm = reactive({ id: 0, patient_name: '', id_card: '' })
const validateIdCard = (_rule: any, value: string, callback: (e?: Error) => void) => {
    if (!value) { callback(new Error('请输入身份证号')); return }
    const valid18 = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(value)
    const valid15 = /^[1-9]\d{5}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}$/.test(value)
    if (valid18 || valid15) callback()
    else callback(new Error('请输入15或18位有效身份证号'))
}
const fillIdCardRules = {
    id_card: [{ required: true, validator: validateIdCard, trigger: 'blur' }]
}
const handleFillIdCard = (row: any) => {
    fillIdCardForm.id = row.id
    fillIdCardForm.patient_name = row.patient_name || '-'
    fillIdCardForm.id_card = row.id_card || ''
    fillIdCardVisible.value = true
}
const submitFillIdCard = async () => {
    await fillIdCardFormRef.value?.validate()
    fillIdCardLoading.value = true
    try {
        await fillIdCard({ id: fillIdCardForm.id, id_card: fillIdCardForm.id_card })
        feedback.msgSuccess('补全成功，年龄已自动更新')
        fillIdCardVisible.value = false
        getLists()
    } catch (e: any) {
        feedback.msgError(e?.msg || '补全失败')
    } finally {
        fillIdCardLoading.value = false
    }
}

// ========== 二维码 ==========
const qrcodeDialogVisible = ref(false)
const qrcodeLoading = ref(false)
const qrcodeUrl = ref('')
const currentQRCodePatient = ref<any>(null)
const qrcodeDialogTitle = ref('小程序二维码')
const lastQRCodeType = ref<'video' | 'confirm'>('confirm')
const qrcodeAppointmentTimeText = computed(() => {
    const p = currentQRCodePatient.value
    if (!p) return '—'
    if (!p.has_appointment) return '未挂号'
    return p.appointment_time_text || '—'
})

const handleVideoQRCode = async (row: any) => {
    if (!isAppointmentActiveForVideo(row)) {
        feedback.msgWarning('仅「已预约」状态可生成视频二维码'); return
    }
    if (!row.patient_id) { feedback.msgWarning('患者信息不完整'); return }
    lastQRCodeType.value = 'video'
    currentQRCodePatient.value = row
    qrcodeDialogVisible.value = true
    qrcodeLoading.value = true
    qrcodeUrl.value = ''
    qrcodeDialogTitle.value = '视频二维码'
    try {
        const config = await getWeappConfig()
        if (!config?.app_id) {
            feedback.msgError('小程序未配置')
            qrcodeDialogVisible.value = false
            return
        }
        const result = await generateMiniProgramQrcode({
            doctor_id: row.appointment_doctor_id,
            diagnosis_id: row.appointment_doctor_id,
            patient_id: row.patient_id,
            share_user_id: userStore.userInfo?.id || '',
            mini_program_path: 'pages/login/login'
        })
        if (result?.qrcode_url) qrcodeUrl.value = result.qrcode_url
        else feedback.msgError('二维码生成失败')
    } catch (e: any) {
        feedback.msgError(e?.msg || '生成二维码失败')
    } finally {
        qrcodeLoading.value = false
    }
}

const handleMiniProgramQRCode = async (row: any) => {
    if (!isAppointmentActiveForVideo(row)) {
        feedback.msgWarning('仅「已预约」状态可使用诊单二维码'); return
    }
    if (!row.patient_id) { feedback.msgWarning('患者信息不完整'); return }
    lastQRCodeType.value = 'confirm'
    currentQRCodePatient.value = row
    qrcodeDialogVisible.value = true
    qrcodeLoading.value = true
    qrcodeUrl.value = ''
    qrcodeDialogTitle.value = '诊单二维码'
    try {
        const config = await getWeappConfig()
        if (!config?.app_id) {
            feedback.msgError('小程序未配置')
            qrcodeDialogVisible.value = false
            return
        }
        const result = await generateMiniProgramQrcode({
            diagnosis_id: row.id,
            doctor_id: row.appointment_doctor_id,
            patient_id: row.patient_id,
            share_user_id: userStore.userInfo?.id || ''
        })
        if (result?.qrcode_url) qrcodeUrl.value = result.qrcode_url
        else feedback.msgError('二维码生成失败')
    } catch (e: any) {
        feedback.msgError(e?.msg || '生成二维码失败')
    } finally {
        qrcodeLoading.value = false
    }
}

const handleRegenerateQRCode = () => {
    if (currentQRCodePatient.value) {
        lastQRCodeType.value === 'video'
            ? handleVideoQRCode(currentQRCodePatient.value)
            : handleMiniProgramQRCode(currentQRCodePatient.value)
    }
}

// ========== 取消挂号 ==========
const handleCancelAppointment = async (row: any) => {
    if (!canCancelAppointmentRow(row)) {
        const s = Number(row.appointment_status)
        if (row.has_appointment && s === 3) feedback.msgWarning('已完成就诊，无法取消挂号')
        else feedback.msgWarning('该诊单未挂号或挂号信息异常')
        return
    }
    try {
        await feedback.confirm(`确定要取消患者「${row.patient_name}」的挂号吗？`)
        await cancelAppointment({ id: row.appointment_id })
        feedback.msgSuccess('取消挂号成功')
        getLists()
        fetchDateCounts()
    } catch (e: any) {
        if (e !== 'cancel') feedback.msgError(e?.msg || '取消挂号失败')
    }
}

// ========== 企业微信 ==========
const wechatDrawerVisible = ref(false)
const wechatCurrentPatient = ref<any>(null)
const wechatRecords = ref<any[]>([])
const wechatRecordTotal = ref(0)
const wechatRecordsLoading = ref(false)
const wechatContactInfo = ref<any>(null)
const wechatNewNote = ref('')
const wechatAddLoading = ref(false)
const wechatPage = ref(1)

const handleWechatRecords = async (row: any) => {
    wechatCurrentPatient.value = row
    wechatDrawerVisible.value = true
    wechatRecords.value = []
    wechatRecordTotal.value = 0
    wechatContactInfo.value = null
    wechatNewNote.value = ''
    wechatPage.value = 1
    wechatRecordsLoading.value = true
    try {
        const [recordsRes, contactRes] = await Promise.all([
            getWechatChatRecords({ diagnosis_id: row.id, patient_id: row.patient_id, page_no: 1, page_size: 20 }),
            getWechatExternalContact({ patient_id: row.patient_id || row.id }).catch(() => null)
        ])
        wechatRecords.value = recordsRes?.lists || []
        wechatRecordTotal.value = recordsRes?.count || 0
        wechatContactInfo.value = contactRes
    } catch (e) {
        console.error(e)
    } finally {
        wechatRecordsLoading.value = false
    }
}

const loadMoreRecords = async () => {
    if (!wechatCurrentPatient.value) return
    wechatPage.value++
    try {
        const res = await getWechatChatRecords({
            diagnosis_id: wechatCurrentPatient.value.id,
            patient_id: wechatCurrentPatient.value.patient_id,
            page_no: wechatPage.value, page_size: 20
        })
        wechatRecords.value.push(...(res?.lists || []))
    } catch (e) { console.error(e) }
}

const handleAddRecord = async () => {
    if (!wechatNewNote.value.trim()) { feedback.msgWarning('请输入内容'); return }
    wechatAddLoading.value = true
    try {
        await addWechatChatRecord({
            diagnosis_id: wechatCurrentPatient.value.id,
            patient_id: wechatCurrentPatient.value.patient_id,
            content: wechatNewNote.value.trim(),
            msg_type: 'note',
            staff_name: userStore.userInfo?.name || '',
            external_name: wechatCurrentPatient.value.patient_name || ''
        })
        wechatNewNote.value = ''
        handleWechatRecords(wechatCurrentPatient.value)
    } catch (e: any) {
        feedback.msgError(e?.msg || '添加失败')
    } finally {
        wechatAddLoading.value = false
    }
}

const handleDeleteRecord = async (id: number) => {
    await feedback.confirm('确定删除该记录吗？')
    try {
        await deleteWechatChatRecord({ id })
        wechatRecords.value = wechatRecords.value.filter(r => r.id !== id)
        wechatRecordTotal.value--
    } catch (e: any) {
        feedback.msgError(e?.msg || '删除失败')
    }
}

// ========== 生命周期 ==========
const SILENT_LIST_POLL_MS = 15_000
let silentListPollTimer: ReturnType<typeof setInterval> | null = null
const silentRefresh = () => {
    if (typeof document !== 'undefined' && document.hidden) return
    void getLists({ silent: true }).catch(() => {})
    void fetchDateCounts()
}

onMounted(async () => {
    document.body.classList.add('h5-mode')
    formData.appointment_date = todayStr.value
    getDictOptions()
    getLists()
    if (typeof requestIdleCallback === 'function') {
        requestIdleCallback(() => fetchDateCounts(), { timeout: 2500 })
    } else {
        setTimeout(fetchDateCounts, 200)
    }
    silentListPollTimer = setInterval(silentRefresh, SILENT_LIST_POLL_MS)
})

onUnmounted(() => {
    document.body.classList.remove('h5-mode')
    if (silentListPollTimer) {
        clearInterval(silentListPollTimer)
        silentListPollTimer = null
    }
})
</script>

<style lang="scss" scoped>
/* ============= 设计变量（手机端紧凑） ============= */
$dh5-primary: #2563eb;
$dh5-primary-soft: #eff6ff;
$dh5-success: #10b981;
$dh5-warn: #f59e0b;
$dh5-danger: #ef4444;
$dh5-text: #0f172a;
$dh5-text-mute: #475569;
$dh5-line: #e2e8f0;
$dh5-bg: #f4f6fb;
$dh5-card-bg: #ffffff;

.dh5 {
    max-width: 560px;
    margin: 0 auto;
    min-height: 100vh;
    background: $dh5-bg;
    padding-bottom: 88px;
    color: $dh5-text;
    -webkit-font-smoothing: antialiased;
}

/* ============= 顶部 ============= */
.dh5-topbar {
    background: $dh5-card-bg;
    padding: 12px 14px 10px;
    position: sticky;
    top: 0;
    z-index: 20;
    box-shadow: 0 1px 0 $dh5-line;
}

.dh5-search {
    display: flex;
    gap: 8px;
    align-items: center;

    :deep(.el-input__wrapper) {
        border-radius: 10px;
        background: $dh5-bg;
        box-shadow: none !important;
        padding: 4px 10px;
    }

    .dh5-search-btn {
        border-radius: 10px;
        height: 36px;
    }
}

.dh5-chips {
    display: flex;
    gap: 6px;
    margin-top: 10px;
}

.dh5-chips--scroll {
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;

    &::-webkit-scrollbar { display: none; }
}

.dh5-chip {
    flex: 0 0 auto;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
    color: $dh5-text-mute;
    background: $dh5-bg;
    border-radius: 999px;
    cursor: pointer;
    transition: background-color 0.18s, color 0.18s;
    user-select: none;
    white-space: nowrap;
    line-height: 1;

    em {
        font-style: normal;
        margin-left: 4px;
        font-size: 12px;
        opacity: 0.7;
    }

    &:active {
        background: #e2e8f0;
    }

    &.active {
        color: #fff;
        background: $dh5-primary;

        em { opacity: 0.85; }
    }
}

.dh5-chip--booking.active { background: $dh5-warn; }
.dh5-chip--done.active { background: $dh5-success; }
.dh5-chip--pending.active { background: $dh5-danger; }

.dh5-quick {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 12px;
    margin-top: 10px;
}

.dh5-quick-group {
    display: flex;
    align-items: center;
    gap: 6px;
}

.dh5-quick-label {
    font-size: 12px;
    color: $dh5-text-mute;
}

.dh5-pill {
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 12px;
    color: $dh5-text-mute;
    background: $dh5-bg;
    cursor: pointer;
    transition: background-color 0.18s, color 0.18s;
    line-height: 1.4;

    &:active { background: #e2e8f0; }

    &.active {
        color: $dh5-primary;
        background: $dh5-primary-soft;
        font-weight: 600;
    }
}

.dh5-more-toggle {
    margin-left: auto;
    font-size: 12px !important;

    .el-icon {
        margin-left: 2px;
        transition: transform 0.2s;
    }

    .rotate-180 { transform: rotate(180deg); }
}

.dh5-more-filter {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px dashed $dh5-line;

    .el-select { flex: 1 1 calc(50% - 4px); min-width: 120px; }
}

/* ============= 列表 ============= */
.dh5-list {
    padding: 10px 12px;
}

.dh5-empty {
    padding: 60px 0;
}

.dh5-card {
    background: $dh5-card-bg;
    border-radius: 14px;
    margin-bottom: 10px;
    overflow: hidden;
    box-shadow: 0 1px 4px rgba(15, 23, 42, 0.04);
    border: 1px solid transparent;
    position: relative;

    &::before {
        content: '';
        position: absolute;
        left: 0;
        top: 0;
        bottom: 0;
        width: 3px;
        background: transparent;
    }
}

.dh5-card--unconfirmed::before { background: $dh5-danger; }
.dh5-card--noapt::before { background: $dh5-warn; }

.dh5-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px 8px;
    cursor: pointer;
}

.dh5-card-avatar {
    flex: 0 0 36px;
    width: 36px;
    height: 36px;
    border-radius: 10px;
    background: $dh5-primary-soft;
    color: $dh5-primary;
    font-size: 16px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

.dh5-card-id-name {
    flex: 1;
    min-width: 0;
}

.dh5-card-name {
    font-size: 16px;
    font-weight: 600;
    color: $dh5-text;
    line-height: 1.2;
    display: flex;
    align-items: baseline;
    gap: 6px;
    flex-wrap: wrap;
}

.dh5-meta-gender,
.dh5-meta-age {
    font-size: 12px;
    color: $dh5-text-mute;
    font-weight: 400;
}

.dh5-card-sub {
    font-size: 12px;
    color: $dh5-text-mute;
    margin-top: 4px;
    line-height: 1.2;
}

.dh5-card-tags {
    display: flex;
    flex-direction: column;
    gap: 4px;
    align-items: flex-end;
}

.dh5-tag {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    font-size: 11px;
    border-radius: 999px;
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
}

.dh5-tag--ok { background: rgba(16, 185, 129, 0.10); color: #047857; }
.dh5-tag--warn { background: rgba(239, 68, 68, 0.10); color: #b91c1c; }
.dh5-tag--info { background: rgba(37, 99, 235, 0.10); color: #1d4ed8; }
.dh5-tag--gray { background: #f1f5f9; color: #64748b; }

.dh5-card-body {
    padding: 4px 14px 12px;
    cursor: pointer;
}

.dh5-row {
    display: flex;
    align-items: flex-start;
    font-size: 13px;
    line-height: 1.6;
    gap: 8px;
    padding: 4px 0;
}

.dh5-row-key {
    flex: 0 0 44px;
    color: $dh5-text-mute;
    font-size: 12px;
    padding-top: 1px;
}

.dh5-row-val {
    flex: 1;
    color: $dh5-text;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.dh5-meta-doctor,
.dh5-meta-mute {
    color: $dh5-text-mute;
    font-size: 12px;
}

/* 挂号块 - 状态色 */
.dh5-row--apt {
    background: #fafbfc;
    border-radius: 10px;
    padding: 8px 10px;
    margin-bottom: 6px;

    .dh5-row-key { padding-top: 2px; }

    &.apt-row-done {
        background: rgba(16, 185, 129, 0.06);
    }

    &.apt-row-missed {
        background: rgba(148, 163, 184, 0.10);
    }
}

.dh5-apt-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
    border-radius: 6px;
    background: rgba(37, 99, 235, 0.06);
    font-size: 12px;
    width: 100%;
}

.dh5-apt-item--done { background: rgba(16, 185, 129, 0.10); }
.dh5-apt-item--missed { background: rgba(148, 163, 184, 0.14); }
.dh5-apt-item--active { background: rgba(37, 99, 235, 0.10); }

.dh5-apt-status {
    font-weight: 600;
    font-size: 12px;
}

.dh5-apt-doctor {
    color: $dh5-text;
    font-weight: 500;
}

.dh5-apt-time {
    color: $dh5-text-mute;
    font-size: 12px;
    margin-left: auto;
}

.dh5-apt-none {
    color: $dh5-warn;
    font-weight: 600;
    font-size: 13px;
}

/* 视频旁观条 */
.dh5-card-watch {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: linear-gradient(90deg, rgba(37, 99, 235, 0.04), transparent);
    border-top: 1px dashed $dh5-line;
    font-size: 12px;
}

.dh5-watch-tip {
    color: $dh5-text-mute;
    font-size: 11px;
    line-height: 1.3;
    flex: 1;
}

.dh5-watch-mute {
    color: $dh5-text-mute;
    font-size: 12px;
}

/* 卡片底部按钮 */
.dh5-card-foot {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 12px;
    border-top: 1px solid #f1f5f9;

    .el-button {
        flex: 1;
        min-width: 0;
        padding: 0;
        height: 32px;
        font-size: 13px;
        border-radius: 8px;
    }

    .dh5-more-btn {
        flex: 0 0 auto;
        min-width: 64px;
    }
}

/* ============= FAB ============= */
.dh5-fab {
    position: fixed;
    right: 16px;
    bottom: 28px;
    width: 52px;
    height: 52px;
    border: none;
    border-radius: 999px;
    background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
    color: #fff;
    box-shadow: 0 8px 22px rgba(37, 99, 235, 0.32);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 99;
    transition: transform 0.18s ease;

    &:active { transform: scale(0.94); }
}

/* ============= Load more / empty ============= */
.dh5-load-more {
    text-align: center;
    padding: 16px;
    font-size: 13px;
    color: $dh5-primary;
    cursor: pointer;
}

.dh5-no-more {
    text-align: center;
    padding: 16px;
    font-size: 12px;
    color: #94a3b8;
}

/* ============= Action Sheet ============= */
.dh5-actions {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px 8px;
    padding: 8px 4px 8px;
}

.dh5-action {
    border: none;
    background: transparent;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 10px 4px;
    border-radius: 10px;
    cursor: pointer;
    color: $dh5-text;
    font-size: 12px;
    transition: background-color 0.18s;

    .el-icon {
        width: 36px;
        height: 36px;
        font-size: 18px;
        background: $dh5-primary-soft;
        color: $dh5-primary;
        border-radius: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    &:active { background: $dh5-bg; }

    &--danger {
        color: $dh5-danger;

        .el-icon {
            background: rgba(239, 68, 68, 0.10);
            color: $dh5-danger;
        }
    }
}

/* ============= 通用 form / drawer ============= */
.dh5-form {
    :deep(.el-form-item__label) {
        font-size: 13px !important;
        color: $dh5-text-mute !important;
        font-weight: 500 !important;
    }
}

.dh5-form-tip {
    font-size: 13px;
    color: $dh5-text-mute;
    line-height: 1.6;
}

.dh5-flex1 {
    flex: 1;
}

/* ============= QR Dialog ============= */
.dh5-qr {
    padding: 16px 4px 8px;
    text-align: center;
}

.dh5-qr-loading {
    padding: 24px 0;
    color: $dh5-text-mute;
}

.dh5-qr-tip {
    margin-top: 10px;
    font-size: 13px;
}

.dh5-qr-img {
    width: 220px;
    height: 220px;
    border-radius: 12px;
    border: 1px solid $dh5-line;
}

.dh5-qr-meta {
    margin-top: 12px;
    color: $dh5-text-mute;
    font-size: 13px;
    line-height: 1.7;

    .dh5-qr-hint { color: $dh5-text-mute; opacity: 0.85; }
}

.dh5-qr-fail {
    padding: 24px;
    color: $dh5-danger;
}

/* ============= 企微 Drawer ============= */
.dh5-wx-contact {
    background: $dh5-bg;
    border-radius: 10px;
    padding: 10px 12px;
    margin-bottom: 12px;
}

.dh5-wx-contact-row {
    display: flex;
    justify-content: space-between;
    font-size: 13px;
    line-height: 1.8;
}

.dh5-wx-key { color: $dh5-text-mute; }
.dh5-wx-val { color: $dh5-text; }

.dh5-wx-add {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;

    .el-textarea { flex: 1; }
    .el-button { align-self: flex-end; }
}

.dh5-wx-empty {
    text-align: center;
    color: #94a3b8;
    padding: 32px 0;
}

.dh5-wx-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.dh5-wx-item {
    background: #fff;
    border: 1px solid $dh5-line;
    border-radius: 10px;
    padding: 10px;
}

.dh5-wx-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
    flex-wrap: wrap;
}

.dh5-wx-head-left,
.dh5-wx-head-right {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.dh5-wx-name { font-size: 13px; font-weight: 500; }
.dh5-wx-time { font-size: 11px; color: #94a3b8; }

.dh5-wx-content {
    font-size: 13px;
    color: $dh5-text;
    line-height: 1.6;
    white-space: pre-wrap;
    word-break: break-all;
}

.dh5-wx-media {
    margin-top: 8px;
}

:deep(.el-drawer__footer) {
    display: flex;
    gap: 12px;
    padding: 12px 16px;
    border-top: 1px solid $dh5-line;
}
</style>

<style lang="scss">
/* 仅 H5 模式下的全局微调（沿用旧代码的 body.h5-mode 上下文） */
body.h5-mode {
    .el-drawer {
        width: 100% !important;
        max-width: 100vw !important;
    }

    .el-drawer__body {
        padding: 0 14px 14px !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch;
    }

    .el-drawer__header {
        padding: 14px 16px !important;
        margin-bottom: 0 !important;
        border-bottom: 1px solid #eee;
        background: #fff;

        .el-drawer__title {
            font-size: 17px;
            font-weight: 600;
        }
    }

    .el-drawer__footer {
        padding: 10px 16px !important;
        border-top: 1px solid #eee;
        background: #fff;
    }

    .el-overlay-dialog .el-dialog,
    .el-dialog {
        width: 92% !important;
        max-width: 460px !important;
        margin: 8vh auto !important;
        border-radius: 12px !important;
    }

    .el-dialog__body {
        max-height: 65vh;
        overflow-y: auto;
    }

    .dialog .el-dialog {
        width: 100% !important;
        max-width: 100% !important;
        margin: 0 !important;
        height: 100vh;
        border-radius: 0 !important;

        .el-dialog__body {
            max-height: calc(100vh - 110px);
            overflow-y: auto;
        }
    }

    /* 编辑诊单 / 处方 等老抽屉的兼容样式（保留） */
    .edit-drawer {
        .el-row {
            display: block !important;
            margin-left: 0 !important;
            margin-right: 0 !important;
        }

        .el-col {
            max-width: 100% !important;
            flex: 0 0 100% !important;
            width: 100% !important;
            padding-left: 0 !important;
            padding-right: 0 !important;
        }
    }

    .diagnosis-drawer {
        .el-drawer__body { padding-bottom: 60px !important; }

        .el-form {
            padding-bottom: 10px;

            .el-form-item {
                flex-direction: column !important;
                align-items: flex-start !important;
                margin-bottom: 10px !important;
                padding: 0 !important;

                .el-form-item__label {
                    width: auto !important;
                    max-width: 100% !important;
                    min-width: 0 !important;
                    text-align: left !important;
                    padding: 0 0 3px 0 !important;
                    font-size: 14px !important;
                    font-weight: 500;
                    color: #475569;
                    line-height: 1.4 !important;
                    height: auto !important;
                    justify-content: flex-start !important;
                }

                .el-form-item__content {
                    margin-left: 0 !important;
                    width: 100% !important;
                }
            }

            .el-input,
            .el-input-number,
            .el-select,
            .el-date-editor,
            .el-textarea {
                width: 100% !important;
            }

            .el-input-number .el-input__wrapper { width: 100% !important; }

            .el-input__wrapper,
            .el-textarea__inner {
                border-radius: 8px !important;
            }

            .el-radio-group {
                display: flex !important;
                flex-wrap: wrap !important;
                gap: 8px !important;
            }
        }

        .el-divider {
            margin: 14px 0 8px !important;
            border-top: none !important;

            .el-divider__text {
                font-size: 15px;
                font-weight: 600;
                color: #303133;
                background: #fff;
                padding-left: 0 !important;
            }

            &::before { width: 0 !important; }
        }

        .checkbox-form-item,
        .compact-form-item {
            .el-checkbox-group,
            .el-radio-group {
                display: flex !important;
                flex-wrap: wrap !important;
                gap: 8px !important;
            }

            .el-checkbox-button,
            .el-radio-button {
                .el-checkbox-button__inner,
                .el-radio-button__inner {
                    padding: 6px 14px !important;
                    font-size: 13px !important;
                    border-radius: 16px !important;
                    border: 1px solid #e0e0e0 !important;
                    background: #f9f9f9 !important;
                    color: #606266 !important;
                    transition: all 0.2s !important;
                }

                &.is-checked {
                    .el-checkbox-button__inner,
                    .el-radio-button__inner {
                        background: #ecf5ff !important;
                        border-color: #2563eb !important;
                        color: #2563eb !important;
                    }
                }
            }

            .el-checkbox-button + .el-checkbox-button,
            .el-radio-button + .el-radio-button {
                margin-left: 0 !important;

                .el-checkbox-button__inner,
                .el-radio-button__inner {
                    border-left: 1px solid #e0e0e0 !important;
                }
            }
        }

        .form-tips {
            font-size: 11px;
            color: #b0b0b0;
            line-height: 1.4;
            margin-top: 4px;
        }
    }

    .diagnosis-tabs {
        .el-tabs__header {
            position: sticky;
            top: 0;
            z-index: 10;
            background: #fff;
            margin: 0 0 6px 0 !important;
            padding-top: 2px;
        }

        .el-tabs__nav-wrap::after { height: 1px !important; }
        .el-tabs__item { font-size: 14px !important; }
    }

    /* 预约时间网格（保留） */
    .appointment-form {
        .el-form-item {
            flex-direction: column !important;
            align-items: flex-start !important;
            margin-bottom: 12px !important;

            .el-form-item__label {
                width: auto !important;
                min-width: 0 !important;
                text-align: left !important;
                padding: 0 0 4px 0 !important;
                font-size: 13px !important;
                font-weight: 500;
                color: #8c8c8c;
                line-height: 1.4 !important;
                height: auto !important;
                justify-content: flex-start !important;
            }

            .el-form-item__content {
                margin-left: 0 !important;
                width: 100% !important;
            }
        }

        .el-radio-group {
            display: flex !important;
            flex-wrap: wrap !important;
            gap: 8px !important;
        }

        .el-radio { margin-right: 0 !important; }

        .el-textarea__inner { border-radius: 8px !important; }
    }

    .appointment-time-container { width: 100% !important; }

    .date-selector {
        display: grid !important;
        grid-template-columns: repeat(3, 1fr) !important;
        gap: 8px !important;

        > .el-button,
        > button {
            min-width: 0 !important;
            width: 100% !important;
            height: 40px !important;
            font-size: 13px !important;
            padding: 0 4px !important;
            border-radius: 10px !important;
            box-sizing: border-box !important;
            margin-left: 0 !important;
        }
    }

    .time-slots-container {
        padding: 12px !important;
        border-radius: 10px !important;
        background-color: #f7f8fa !important;
    }

    .time-slots-grid {
        grid-template-columns: repeat(3, 1fr) !important;
        gap: 8px !important;
        max-height: 300px !important;

        .time-slot-item {
            min-height: 56px !important;
            padding: 6px 4px !important;
            border-radius: 10px !important;
            border-width: 1.5px !important;
        }
    }

    .drawer-footer { padding: 0 !important; }

    .detail-popup {
        .el-descriptions__body .el-descriptions__table {
            display: block !important;

            tbody { display: block !important; }

            .el-descriptions-row {
                display: block !important;
                border-bottom: none !important;

                .el-descriptions__cell {
                    display: block !important;
                    width: 100% !important;
                    border: none !important;

                    &.el-descriptions__label {
                        background: transparent;
                        font-weight: 500;
                        font-size: 13px;
                        color: #999;
                        padding: 10px 0 2px 0 !important;
                        border-bottom: none !important;
                    }

                    &.el-descriptions__content {
                        font-size: 14px;
                        color: #303133;
                        padding: 2px 0 10px 0 !important;
                        border-bottom: 1px solid #f5f5f5 !important;
                    }
                }
            }
        }
    }
}
</style>
