liu
2026-09-29 982c97ab82f3beb1b4febcf014562eda34b9fc05
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<script lang="ts" setup>
import type { WxCpActivityApi } from '#/api/wxcp/activity';
 
import { onUnmounted, ref } from 'vue';
 
import { useVbenModal } from '@vben/common-ui';
import { Button, message, Spin } from 'ant-design-vue';
 
import {
  getActivityQrCodeBlob,
  getActivitySignupUrl,
} from '#/api/wxcp/activity';
 
defineOptions({ name: 'WxCpActivityQrCodeModal' });
 
const activity = ref<WxCpActivityApi.Activity>({});
const qrCodeUrl = ref('');
const signupUrl = ref('');
const loading = ref(false);
const loadError = ref('');
 
/** 释放上一次的 objectURL,避免内存泄漏 */
function releaseObjectUrl() {
  if (qrCodeUrl.value) {
    URL.revokeObjectURL(qrCodeUrl.value);
    qrCodeUrl.value = '';
  }
}
 
async function loadQrCode(id: number) {
  loading.value = true;
  loadError.value = '';
  releaseObjectUrl();
  try {
    // 二维码接口返回 PNG 二进制,且 <img> 带不了请求头,因此走 blob + objectURL
    const res = (await getActivityQrCodeBlob(id, 400)) as unknown as {
      data?: Blob;
    };
    const blob = res?.data instanceof Blob ? res.data : (res as unknown as Blob);
    if (!(blob instanceof Blob)) {
      throw new Error('二维码返回格式异常');
    }
    qrCodeUrl.value = URL.createObjectURL(blob);
  } catch (error: unknown) {
    loadError.value = (error as Error)?.message || '二维码加载失败';
  } finally {
    loading.value = false;
  }
}
 
async function loadSignupUrl(id: number) {
  try {
    signupUrl.value = await getActivitySignupUrl(id);
  } catch {
    // 报名链接取不到不影响二维码展示,静默处理
    signupUrl.value = '';
  }
}
 
function handleDownload() {
  if (!qrCodeUrl.value || !activity.value.id) {
    return;
  }
  const link = document.createElement('a');
  link.href = qrCodeUrl.value;
  link.download = `活动报名码-${activity.value.title || activity.value.id}.png`;
  document.body.append(link);
  link.click();
  link.remove();
}
 
async function handleCopyLink() {
  if (!signupUrl.value) {
    message.warning('暂无报名链接');
    return;
  }
  try {
    await navigator.clipboard.writeText(signupUrl.value);
    message.success('报名链接已复制');
  } catch {
    message.warning('复制失败,请手动选中链接复制');
  }
}
 
const [Modal, modalApi] = useVbenModal({
  onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      activity.value = {};
      signupUrl.value = '';
      loadError.value = '';
      releaseObjectUrl();
      return;
    }
    const data = modalApi.getData<{ activity: WxCpActivityApi.Activity }>();
    activity.value = data.activity ?? {};
    if (activity.value.id) {
      loadQrCode(activity.value.id);
      loadSignupUrl(activity.value.id);
    }
  },
});
 
onUnmounted(releaseObjectUrl);
</script>
 
<template>
  <Modal title="活动报名二维码" class="w-2/5">
    <div class="flex flex-col items-center px-4">
      <div class="mb-4 text-center text-sm text-gray-500">
        {{ activity.title }}
      </div>
 
      <Spin :spinning="loading">
        <div
          class="flex size-70 items-center justify-center rounded border border-dashed border-gray-300"
        >
          <img
            v-if="qrCodeUrl"
            :src="qrCodeUrl"
            alt="活动报名二维码"
            class="size-65 object-contain"
          />
          <div v-else class="px-6 text-center text-sm text-gray-400">
            {{ loadError || '二维码加载中…' }}
          </div>
        </div>
      </Spin>
 
      <div class="mt-4 w-full text-xs text-gray-400 break-all">
        <div v-if="signupUrl">报名链接:{{ signupUrl }}</div>
        <div class="mt-2">
          成员用企业微信扫一扫即可打开报名页并提交报名。
        </div>
      </div>
    </div>
 
    <template #footer>
      <div class="flex justify-end gap-2">
        <Button :disabled="!signupUrl" @click="handleCopyLink">
          复制报名链接
        </Button>
        <Button type="primary" :disabled="!qrCodeUrl" @click="handleDownload">
          下载二维码
        </Button>
      </div>
    </template>
  </Modal>
</template>