Add QR code to aftersales electronic form
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import QRCode from 'qrcode';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -79,6 +80,10 @@ function formatDateTime(value?: string) {
|
|||||||
return new Date(value).toLocaleString('zh-CN');
|
return new Date(value).toLocaleString('zh-CN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAftersalesPublicUrl(serialNumber: string) {
|
||||||
|
return `${window.location.origin}/aftersales/${serialNumber}`;
|
||||||
|
}
|
||||||
|
|
||||||
function AftersalesDetailPage() {
|
function AftersalesDetailPage() {
|
||||||
const { serialNumber = '' } = useParams<{ serialNumber: string }>();
|
const { serialNumber = '' } = useParams<{ serialNumber: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -95,6 +100,7 @@ function AftersalesDetailPage() {
|
|||||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
||||||
const [qrUrl, setQrUrl] = useState('');
|
const [qrUrl, setQrUrl] = useState('');
|
||||||
const [electronicFormVisible, setElectronicFormVisible] = useState(false);
|
const [electronicFormVisible, setElectronicFormVisible] = useState(false);
|
||||||
|
const [electronicFormQrCodeDataUrl, setElectronicFormQrCodeDataUrl] = useState('');
|
||||||
|
|
||||||
const [reassignModalVisible, setReassignModalVisible] = useState(false);
|
const [reassignModalVisible, setReassignModalVisible] = useState(false);
|
||||||
const [reassignTechnicianId, setReassignTechnicianId] = useState<number | undefined>();
|
const [reassignTechnicianId, setReassignTechnicianId] = useState<number | undefined>();
|
||||||
@@ -201,6 +207,20 @@ function AftersalesDetailPage() {
|
|||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openElectronicForm = async () => {
|
||||||
|
if (!order) return;
|
||||||
|
try {
|
||||||
|
const qrCode = await QRCode.toDataURL(getAftersalesPublicUrl(order.serialNumber), {
|
||||||
|
width: 132,
|
||||||
|
margin: 1,
|
||||||
|
});
|
||||||
|
setElectronicFormQrCodeDataUrl(qrCode);
|
||||||
|
setElectronicFormVisible(true);
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(err?.message || '生成电子表单二维码失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePrintElectronicForm = () => {
|
const handlePrintElectronicForm = () => {
|
||||||
const formNode = document.querySelector('.aftersales-electronic-form');
|
const formNode = document.querySelector('.aftersales-electronic-form');
|
||||||
if (!formNode) return;
|
if (!formNode) return;
|
||||||
@@ -219,10 +239,15 @@ function AftersalesDetailPage() {
|
|||||||
<style>
|
<style>
|
||||||
body { margin: 24px; color: #111827; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
body { margin: 24px; color: #111827; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
||||||
.aftersales-electronic-form { max-width: 1080px; margin: 0 auto; }
|
.aftersales-electronic-form { max-width: 1080px; margin: 0 auto; }
|
||||||
.electronic-form-header { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 16px; }
|
.electronic-form-header { display: grid; grid-template-columns: minmax(180px, 1fr) auto minmax(180px, 1fr); align-items: center; gap: 20px; margin-bottom: 16px; }
|
||||||
|
.electronic-form-brand { justify-self: start; }
|
||||||
.electronic-form-logo { height: 34px; object-fit: contain; }
|
.electronic-form-logo { height: 34px; object-fit: contain; }
|
||||||
.electronic-form-title { flex: 1; text-align: center; font-size: 20px; font-weight: 700; }
|
.electronic-form-title { justify-self: center; text-align: center; font-size: 20px; font-weight: 700; }
|
||||||
.electronic-form-serial { border: 2px solid #111827; padding: 8px 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 16px; font-weight: 700; }
|
.electronic-form-meta { justify-self: end; display: flex; align-items: center; gap: 12px; }
|
||||||
|
.electronic-form-qr { width: 82px; height: 82px; object-fit: contain; }
|
||||||
|
.electronic-form-serial { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
|
||||||
|
.electronic-form-serial-label { font-size: 12px; color: #4b5563; }
|
||||||
|
.electronic-form-serial-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 16px; font-weight: 700; white-space: nowrap; }
|
||||||
.electronic-form-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 13px; }
|
.electronic-form-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 13px; }
|
||||||
.electronic-form-table th, .electronic-form-table td { border: 1px solid #1f2937; padding: 9px 10px; vertical-align: top; word-break: break-word; }
|
.electronic-form-table th, .electronic-form-table td { border: 1px solid #1f2937; padding: 9px 10px; vertical-align: top; word-break: break-word; }
|
||||||
.electronic-form-table th { width: 120px; background: #f3f4f6; text-align: left; font-weight: 600; }
|
.electronic-form-table th { width: 120px; background: #f3f4f6; text-align: left; font-weight: 600; }
|
||||||
@@ -323,7 +348,7 @@ function AftersalesDetailPage() {
|
|||||||
}
|
}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button icon={<FileTextOutlined />} onClick={() => setElectronicFormVisible(true)}>
|
<Button icon={<FileTextOutlined />} onClick={openElectronicForm}>
|
||||||
电子表单
|
电子表单
|
||||||
</Button>
|
</Button>
|
||||||
<Button icon={<QrcodeOutlined />} onClick={handleGenerateQrCode}>
|
<Button icon={<QrcodeOutlined />} onClick={handleGenerateQrCode}>
|
||||||
@@ -535,9 +560,23 @@ function AftersalesDetailPage() {
|
|||||||
>
|
>
|
||||||
<div className="aftersales-electronic-form">
|
<div className="aftersales-electronic-form">
|
||||||
<div className="electronic-form-header">
|
<div className="electronic-form-header">
|
||||||
<img src={logo} alt="浙江贝凡" className="electronic-form-logo" />
|
<div className="electronic-form-brand">
|
||||||
|
<img src={logo} alt="浙江贝凡" className="electronic-form-logo" />
|
||||||
|
</div>
|
||||||
<div className="electronic-form-title">浙江贝凡售后服务电子表单</div>
|
<div className="electronic-form-title">浙江贝凡售后服务电子表单</div>
|
||||||
<div className="electronic-form-serial">售后码:{order.serialNumber}</div>
|
<div className="electronic-form-meta">
|
||||||
|
{electronicFormQrCodeDataUrl && (
|
||||||
|
<img
|
||||||
|
src={electronicFormQrCodeDataUrl}
|
||||||
|
alt="售后码二维码"
|
||||||
|
className="electronic-form-qr"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="electronic-form-serial">
|
||||||
|
<span className="electronic-form-serial-label">售后码</span>
|
||||||
|
<strong className="electronic-form-serial-code">{order.serialNumber}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table className="electronic-form-table">
|
<table className="electronic-form-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|||||||
@@ -3,28 +3,55 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.electronic-form-header {
|
.electronic-form-header {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(180px, 1fr) auto minmax(180px, 1fr);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.electronic-form-brand {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
.electronic-form-logo {
|
.electronic-form-logo {
|
||||||
height: 34px;
|
height: 34px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.electronic-form-title {
|
.electronic-form-title {
|
||||||
flex: 1;
|
justify-self: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.electronic-form-meta {
|
||||||
|
justify-self: end;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.electronic-form-qr {
|
||||||
|
width: 82px;
|
||||||
|
height: 82px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
.electronic-form-serial {
|
.electronic-form-serial {
|
||||||
border: 2px solid #111827;
|
display: flex;
|
||||||
padding: 8px 12px;
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.electronic-form-serial-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.electronic-form-serial-code {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -68,10 +95,16 @@
|
|||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.electronic-form-header {
|
.electronic-form-header {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
grid-template-columns: 1fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.electronic-form-brand,
|
||||||
|
.electronic-form-title,
|
||||||
|
.electronic-form-meta {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
.electronic-form-title {
|
.electronic-form-title {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|||||||
Reference in New Issue
Block a user