Browse Source

feat: click QR to copy/save image instead of link text

MHSanaei 14 hours ago
parent
commit
e4218a1029
1 changed files with 65 additions and 4 deletions
  1. 65 4
      frontend/src/pages/inbounds/QrPanel.vue

+ 65 - 4
frontend/src/pages/inbounds/QrPanel.vue

@@ -1,6 +1,7 @@
 <script setup>
+import { ref } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { CopyOutlined, DownloadOutlined } from '@ant-design/icons-vue';
+import { CopyOutlined, DownloadOutlined, PictureOutlined } from '@ant-design/icons-vue';
 import { message } from 'ant-design-vue';
 
 import { ClipboardManager, FileManager } from '@/utils';
@@ -15,6 +16,8 @@ const props = defineProps({
   showQr: { type: Boolean, default: true },
 });
 
+const qrRef = ref(null);
+
 async function copy() {
   const ok = await ClipboardManager.copyText(props.value);
   if (ok) message.success(t('copied'));
@@ -24,6 +27,55 @@ function download() {
   if (!props.downloadName) return;
   FileManager.downloadTextFile(props.value, props.downloadName);
 }
+
+function svgToPngBlob(size = 360) {
+  const svgEl = qrRef.value?.querySelector('svg');
+  if (!svgEl) return Promise.resolve(null);
+  const svgData = new XMLSerializer().serializeToString(svgEl);
+  const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
+  const url = URL.createObjectURL(svgBlob);
+  return new Promise((resolve) => {
+    const img = new Image();
+    img.onload = () => {
+      const canvas = document.createElement('canvas');
+      canvas.width = size;
+      canvas.height = size;
+      const ctx = canvas.getContext('2d');
+      ctx.fillStyle = '#ffffff';
+      ctx.fillRect(0, 0, size, size);
+      ctx.drawImage(img, 0, 0, size, size);
+      URL.revokeObjectURL(url);
+      canvas.toBlob(resolve, 'image/png');
+    };
+    img.onerror = () => { URL.revokeObjectURL(url); resolve(null); };
+    img.src = url;
+  });
+}
+
+async function copyImage() {
+  const blob = await svgToPngBlob(props.size);
+  if (!blob) return;
+  try {
+    await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
+    message.success(t('copied'));
+  } catch {
+    downloadImageBlob(blob);
+  }
+}
+
+function downloadImageBlob(blob) {
+  const url = URL.createObjectURL(blob);
+  const link = document.createElement('a');
+  link.href = url;
+  link.download = `${props.remark || 'qrcode'}.png`;
+  link.click();
+  URL.revokeObjectURL(url);
+}
+
+async function downloadImage() {
+  const blob = await svgToPngBlob(props.size);
+  if (blob) downloadImageBlob(blob);
+}
 </script>
 
 <template>
@@ -37,6 +89,13 @@ function download() {
           </template>
         </a-button>
       </a-tooltip>
+      <a-tooltip v-if="showQr" :title="t('downloadImage', 'Download Image')">
+        <a-button size="small" @click="downloadImage">
+          <template #icon>
+            <PictureOutlined />
+          </template>
+        </a-button>
+      </a-tooltip>
       <a-tooltip v-if="downloadName" :title="t('download')">
         <a-button size="small" @click="download">
           <template #icon>
@@ -45,9 +104,11 @@ function download() {
         </a-button>
       </a-tooltip>
     </div>
-    <div v-if="showQr" class="qr-panel-canvas">
-      <a-qrcode class="qr-code" :value="value" :size="size" type="svg" :bordered="false"
-        color="#000000" bg-color="#ffffff" :title="t('copy')" @click="copy" />
+    <div v-if="showQr" ref="qrRef" class="qr-panel-canvas">
+      <a-tooltip :title="t('copy')">
+        <a-qrcode class="qr-code" :value="value" :size="size" type="svg" :bordered="false" color="#000000"
+          bg-color="#ffffff" @click="copyImage" />
+      </a-tooltip>
     </div>
   </div>
 </template>