| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- <template>
- <el-card shadow="never" class="registration-confirm-card">
- <template #header>
- <div class="flex items-center justify-between">
- <span class="text-base font-semibold">挂号确认</span>
- <el-tag v-if="!finalized" type="danger" size="small">L2 需确认</el-tag>
- <el-tag v-else-if="confirmed" type="success" size="small">已完成</el-tag>
- <el-tag v-else type="info" size="small">{{ command.status }}</el-tag>
- </div>
- </template>
- <!-- 确认信息 -->
- <div class="mb-3">
- <div class="text-sm text-gray-500 mb-2">请确认以下挂号信息</div>
- <div class="summary-grid">
- <div v-for="(v, k) in summaryEntries" :key="k" class="summary-item">
- <span class="summary-key">{{ k }}</span>
- <span class="summary-val">{{ v }}</span>
- </div>
- </div>
- </div>
- <!-- 操作按钮(等待确认) -->
- <div v-if="command.status === 'AWAITING_CONFIRMATION' || command.status === 'PREPARED'" class="flex gap-2 items-center">
- <el-button type="primary" size="large" :loading="confirming" @click="handleConfirm">
- 确认挂号
- </el-button>
- <el-button size="large" :loading="rejecting" @click="handleReject">
- 取消
- </el-button>
- </div>
- <!-- 执行中 -->
- <div v-if="command.status === 'EXECUTING'" class="flex items-center gap-2 text-gray-500">
- <el-icon class="is-loading"><i class="el-icon-loading" /></el-icon>
- <span>正在执行挂号,请稍候…</span>
- </div>
- <!-- 成功 -->
- <el-result
- v-if="command.status === 'SUCCEEDED'"
- icon="success"
- title="挂号成功"
- :sub-title="successSubtitle"
- >
- <template #extra>
- <div v-if="command.result && Object.keys(command.result).length" class="mt-2">
- <div class="text-sm text-gray-500 mb-1">预约详情</div>
- <pre class="text-xs bg-gray-50 p-2 rounded">{{ JSON.stringify(command.result, null, 2) }}</pre>
- </div>
- </template>
- </el-result>
- <!-- 失败 -->
- <el-result
- v-if="command.status === 'FAILED'"
- icon="error"
- title="挂号失败"
- :sub-title="command.error?.message ?? '请稍后重试'"
- >
- <template #extra>
- <el-button type="primary" @click="$emit('retry')">重试</el-button>
- <el-button @click="$emit('cancel')">返回重选</el-button>
- </template>
- </el-result>
- <!-- 已拒绝 -->
- <el-result
- v-if="command.status === 'REJECTED'"
- icon="info"
- title="已取消挂号"
- sub-title="您可以重新选择号源"
- >
- <template #extra>
- <el-button type="primary" @click="$emit('cancel')">重新选择</el-button>
- </template>
- </el-result>
- <!-- 已过期 -->
- <el-result
- v-if="command.status === 'EXPIRED'"
- icon="warning"
- title="挂号已过期"
- sub-title="号源可能已被释放,请重新查询"
- >
- <template #extra>
- <el-button type="primary" @click="$emit('revaluate')">重新查询</el-button>
- </template>
- </el-result>
- <!-- UNKNOWN 轮询中 -->
- <el-alert
- v-if="command.status === 'UNKNOWN'"
- title="正在查询挂号结果…"
- type="warning"
- :closable="false"
- show-icon
- />
- <!-- 操作反馈 -->
- <div v-if="feedbackMessage" :class="['mt-2 text-sm', feedbackOk ? 'text-green-600' : 'text-red-500']">
- {{ feedbackMessage }}
- </div>
- </el-card>
- </template>
- <script setup lang="ts">
- import { computed, ref, watch, onUnmounted } from 'vue';
- import { confirmCommand, rejectCommand, getCommand } from '@/api/v2';
- import type { Command } from '@/api/v2/types';
- const props = defineProps<{
- command: Command;
- }>();
- const emit = defineEmits<{
- update: [command: Command];
- retry: [];
- cancel: [];
- revaluate: [];
- }>();
- const confirming = ref(false);
- const rejecting = ref(false);
- const feedbackMessage = ref('');
- const feedbackOk = ref(false);
- const confirmed = computed(() => props.command.status === 'SUCCEEDED');
- const finalized = computed(() =>
- ['SUCCEEDED', 'FAILED', 'REJECTED', 'EXPIRED'].includes(props.command.status));
- const successSubtitle = computed(() => {
- const r = props.command.result;
- if (!r) return '';
- const id = r['appointmentId'] ?? r['appointment_id'] ?? '';
- return id ? `预约编号:${id}` : '请按时就诊';
- });
- // ---- 轮询(UNKNOWN 状态) ----
- let pollTimer: ReturnType<typeof setInterval> | null = null;
- watch(() => props.command.status, (status) => {
- if (pollTimer) { clearInterval(pollTimer); pollTimer = null; }
- if (status === 'UNKNOWN' && props.command) {
- pollTimer = setInterval(async () => {
- try {
- const res = await getCommand(props.command!.commandId);
- const updated = (res as any).data?.data ?? (res as any).data ?? res;
- if (updated) {
- emit('update', updated as Command);
- if (updated.status !== 'UNKNOWN' && pollTimer) {
- clearInterval(pollTimer);
- pollTimer = null;
- }
- }
- } catch { /* silent */ }
- }, 3000);
- }
- });
- onUnmounted(() => { if (pollTimer) clearInterval(pollTimer); });
- // ---- 操作 ----
- async function handleConfirm() {
- confirming.value = true;
- feedbackMessage.value = '';
- try {
- const res = await confirmCommand(props.command.commandId, {
- commandVersion: props.command.commandVersion,
- confirmation: { channel: 'CARD', explicit: true },
- });
- const data = (res as any).data?.data ?? (res as any).data ?? res;
- if (data) {
- emit('update', data as Command);
- feedbackMessage.value = '挂号成功';
- feedbackOk.value = true;
- }
- } catch (e: any) {
- feedbackMessage.value = parseError(e);
- feedbackOk.value = false;
- } finally {
- confirming.value = false;
- }
- }
- async function handleReject() {
- rejecting.value = true;
- feedbackMessage.value = '';
- try {
- const res = await rejectCommand(props.command.commandId, {
- commandVersion: props.command.commandVersion,
- reason: '用户取消挂号',
- });
- const data = (res as any).data?.data ?? (res as any).data ?? res;
- if (data) {
- emit('update', data as Command);
- feedbackMessage.value = '已取消';
- feedbackOk.value = true;
- }
- } catch (e: any) {
- feedbackMessage.value = parseError(e);
- feedbackOk.value = false;
- } finally {
- rejecting.value = false;
- }
- }
- function parseError(e: any): string {
- return e?.response?.data?.message ?? e?.message ?? '操作失败';
- }
- // ---- summary ----
- const labelMap: Record<string, string> = {
- department: '科室', doctor: '医生', visitTime: '就诊时间',
- feeYuan: '费用', note: '备注', lockId: '锁号编号',
- patientName: '患者',
- };
- const summaryEntries = computed<[string, string][]>(() => {
- if (!props.command?.summary) return [];
- return Object.entries(props.command.summary)
- .filter(([k]) => k !== 'lockExpiresAt')
- .map(([k, v]) => [labelMap[k] ?? k, String(v)]);
- });
- </script>
- <style scoped>
- .registration-confirm-card {
- border-left: 3px solid #3b82f6;
- }
- .summary-grid {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
- padding: 12px;
- background: #f0f9ff;
- border-radius: 6px;
- }
- .summary-item {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
- .summary-key { font-size: 12px; color: #6b7280; }
- .summary-val { font-size: 15px; color: #1f2937; font-weight: 500; }
- .flex { display: flex; }
- .items-center { align-items: center; }
- .justify-between { justify-content: space-between; }
- .gap-2 { gap: 8px; }
- .mb-1 { margin-bottom: 4px; }
- .mb-2 { margin-bottom: 8px; }
- .mb-3 { margin-bottom: 12px; }
- .mt-2 { margin-top: 8px; }
- .text-xs { font-size: 12px; }
- .text-sm { font-size: 13px; }
- .text-base { font-size: 15px; }
- .font-semibold { font-weight: 600; }
- .text-gray-500 { color: #6b7280; }
- .text-green-600 { color: #16a34a; }
- .text-red-500 { color: #ef4444; }
- .bg-gray-50 { background: #f9fafb; }
- .p-2 { padding: 8px; }
- .rounded { border-radius: 4px; }
- </style>
|